188 lines
7.7 KiB
Python
188 lines
7.7 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
自适应三分位趋势策略 - 实盘交易
|
|||
|
|
拉取 Bitmart 5/15/60 分钟数据,计算信号,执行开平仓(沿用 交易/bitmart-三分之一策略交易 的浏览器/API 逻辑)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import os
|
|||
|
|
import sys
|
|||
|
|
import time
|
|||
|
|
import datetime
|
|||
|
|
from typing import List, Dict, Optional, Tuple
|
|||
|
|
try:
|
|||
|
|
from loguru import logger
|
|||
|
|
except ImportError:
|
|||
|
|
import logging
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|||
|
|
if ROOT_DIR not in sys.path:
|
|||
|
|
sys.path.insert(0, ROOT_DIR)
|
|||
|
|
|
|||
|
|
from bitmart.api_contract import APIContract
|
|||
|
|
from adaptive_third_strategy.config import (
|
|||
|
|
STEP_5M,
|
|||
|
|
STEP_15M,
|
|||
|
|
STEP_60M,
|
|||
|
|
ATR_PERIOD,
|
|||
|
|
EMA_SHORT,
|
|||
|
|
EMA_MID_FAST,
|
|||
|
|
EMA_MID_SLOW,
|
|||
|
|
EMA_LONG_FAST,
|
|||
|
|
EMA_LONG_SLOW,
|
|||
|
|
BASE_POSITION_PERCENT,
|
|||
|
|
MAX_POSITION_PERCENT,
|
|||
|
|
CONTRACT_SYMBOL,
|
|||
|
|
)
|
|||
|
|
from adaptive_third_strategy.indicators import get_ema_atr_from_klines, align_higher_tf_ema
|
|||
|
|
from adaptive_third_strategy.strategy_core import check_trigger, build_volume_ma
|
|||
|
|
from adaptive_third_strategy.data_fetcher import fetch_klines, _format_bar
|
|||
|
|
|
|||
|
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AdaptiveThirdStrategyTrader:
|
|||
|
|
"""实盘:获取多周期K线 -> 策略信号 -> 开平仓(可接浏览器或纯 API)"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
api_key: str,
|
|||
|
|
secret_key: str,
|
|||
|
|
memo: str = "合约交易",
|
|||
|
|
symbol: str = CONTRACT_SYMBOL,
|
|||
|
|
bit_id: Optional[str] = None,
|
|||
|
|
):
|
|||
|
|
self.api_key = api_key
|
|||
|
|
self.secret_key = secret_key
|
|||
|
|
self.memo = memo
|
|||
|
|
self.symbol = symbol
|
|||
|
|
self.bit_id = bit_id
|
|||
|
|
self.contractAPI = APIContract(api_key, secret_key, memo, timeout=(5, 15))
|
|||
|
|
self.check_interval = 3
|
|||
|
|
self.last_trigger_kline_id: Optional[int] = None
|
|||
|
|
self.last_trigger_direction: Optional[str] = None
|
|||
|
|
self.last_trade_kline_id: Optional[int] = None
|
|||
|
|
self.position_direction: int = 0 # 1 多 -1 空 0 无
|
|||
|
|
|
|||
|
|
def get_klines_multi(self) -> Tuple[List[Dict], List[Dict], List[Dict]]:
|
|||
|
|
"""拉取当前 5/15/60 分钟 K 线(最近约 3 小时足够算 EMA/ATR)"""
|
|||
|
|
end_ts = int(time.time())
|
|||
|
|
start_ts = end_ts - 3600 * 3
|
|||
|
|
k5 = fetch_klines(self.contractAPI, STEP_5M, start_ts, end_ts, self.symbol)
|
|||
|
|
k15 = fetch_klines(self.contractAPI, STEP_15M, start_ts, end_ts, self.symbol)
|
|||
|
|
k60 = fetch_klines(self.contractAPI, STEP_60M, start_ts, end_ts, self.symbol)
|
|||
|
|
return k5, k15, k60
|
|||
|
|
|
|||
|
|
def get_position_status(self) -> bool:
|
|||
|
|
"""查询当前持仓,更新 self.position_direction"""
|
|||
|
|
try:
|
|||
|
|
response = self.contractAPI.get_position(contract_symbol=self.symbol)[0]
|
|||
|
|
if response.get("code") != 1000:
|
|||
|
|
return False
|
|||
|
|
positions = response.get("data", [])
|
|||
|
|
if not positions:
|
|||
|
|
self.position_direction = 0
|
|||
|
|
return True
|
|||
|
|
self.position_direction = 1 if positions[0].get("position_type") == 1 else -1
|
|||
|
|
return True
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error(f"持仓查询异常: {e}")
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
def check_realtime_signal(
|
|||
|
|
self,
|
|||
|
|
klines_5m: List[Dict],
|
|||
|
|
klines_15m: List[Dict],
|
|||
|
|
klines_60m: List[Dict],
|
|||
|
|
) -> Tuple[Optional[str], Optional[float], Optional[Dict]]:
|
|||
|
|
"""
|
|||
|
|
基于当前 K 线(最后一根未收盘)检测实时信号。
|
|||
|
|
返回 (方向, 触发价, 当前K线) 或 (None, None, None)。
|
|||
|
|
"""
|
|||
|
|
if len(klines_5m) < ATR_PERIOD + 2:
|
|||
|
|
return None, None, None
|
|||
|
|
from adaptive_third_strategy.indicators import get_ema_atr_from_klines
|
|||
|
|
from adaptive_third_strategy.strategy_core import check_trigger
|
|||
|
|
ema_5m, atr_5m = get_ema_atr_from_klines(klines_5m, EMA_SHORT, ATR_PERIOD)
|
|||
|
|
ema_15m_align = align_higher_tf_ema(klines_5m, klines_15m, EMA_MID_FAST, EMA_MID_SLOW)
|
|||
|
|
ema_60m_align = align_higher_tf_ema(klines_5m, klines_60m, EMA_LONG_FAST, EMA_LONG_SLOW)
|
|||
|
|
volume_ma = build_volume_ma(klines_5m)
|
|||
|
|
curr_idx = len(klines_5m) - 1
|
|||
|
|
direction, trigger_price, _, _ = check_trigger(
|
|||
|
|
klines_5m, curr_idx, atr_5m, ema_5m, ema_15m_align, ema_60m_align, volume_ma, use_confirm=True
|
|||
|
|
)
|
|||
|
|
curr = klines_5m[curr_idx]
|
|||
|
|
curr_id = curr["id"]
|
|||
|
|
if direction is None:
|
|||
|
|
return None, None, None
|
|||
|
|
if self.last_trigger_kline_id == curr_id and self.last_trigger_direction == direction:
|
|||
|
|
return None, None, None
|
|||
|
|
if self.last_trade_kline_id == curr_id:
|
|||
|
|
return None, None, None
|
|||
|
|
if (direction == "long" and self.position_direction == 1) or (direction == "short" and self.position_direction == -1):
|
|||
|
|
return None, None, None
|
|||
|
|
return direction, trigger_price, curr
|
|||
|
|
|
|||
|
|
def run_loop(self, ding_callback=None):
|
|||
|
|
"""
|
|||
|
|
主循环:拉数据 -> 检测信号 -> 若需交易则调用开平仓(需外部实现或注入)。
|
|||
|
|
ding_callback(msg, error=False) 可选,用于钉钉通知。
|
|||
|
|
"""
|
|||
|
|
def ding(msg: str, error: bool = False):
|
|||
|
|
if ding_callback:
|
|||
|
|
ding_callback(msg, error)
|
|||
|
|
else:
|
|||
|
|
if error:
|
|||
|
|
logger.error(msg)
|
|||
|
|
else:
|
|||
|
|
logger.info(msg)
|
|||
|
|
|
|||
|
|
logger.info("自适应三分位趋势策略实盘启动,按 Bitmart 方式拉取 5/15/60 分钟数据")
|
|||
|
|
while True:
|
|||
|
|
try:
|
|||
|
|
k5, k15, k60 = self.get_klines_multi()
|
|||
|
|
if len(k5) < ATR_PERIOD + 2:
|
|||
|
|
time.sleep(self.check_interval)
|
|||
|
|
continue
|
|||
|
|
if not self.get_position_status():
|
|||
|
|
time.sleep(self.check_interval)
|
|||
|
|
continue
|
|||
|
|
direction, trigger_price, curr = self.check_realtime_signal(k5, k15, k60)
|
|||
|
|
if direction and trigger_price is not None and curr is not None:
|
|||
|
|
curr_id = curr["id"]
|
|||
|
|
ding(f"信号: {direction} @ {trigger_price:.2f} 当前K线 id={curr_id}")
|
|||
|
|
# 这里接入你的开平仓实现:平仓 + 开单(可调用 交易/bitmart-三分之一策略交易 的 平仓/开单 或 API 下单)
|
|||
|
|
# 示例:仅记录,不实际下单
|
|||
|
|
self.last_trigger_kline_id = curr_id
|
|||
|
|
self.last_trigger_direction = direction
|
|||
|
|
self.last_trade_kline_id = curr_id
|
|||
|
|
time.sleep(self.check_interval)
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.exception(e)
|
|||
|
|
time.sleep(60)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
import argparse
|
|||
|
|
parser = argparse.ArgumentParser(description="自适应三分位趋势策略实盘")
|
|||
|
|
parser.add_argument("--api-key", default="", help="Bitmart API Key")
|
|||
|
|
parser.add_argument("--secret-key", default="", help="Bitmart Secret Key")
|
|||
|
|
parser.add_argument("--bit-id", default="", help="比特浏览器 ID(若用浏览器下单)")
|
|||
|
|
args = parser.parse_args()
|
|||
|
|
api_key = args.api_key or os.environ.get("BITMART_API_KEY", "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8")
|
|||
|
|
secret_key = args.secret_key or os.environ.get("BITMART_SECRET_KEY", "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5")
|
|||
|
|
trader = AdaptiveThirdStrategyTrader(api_key, secret_key, bit_id=args.bit_id or None)
|
|||
|
|
try:
|
|||
|
|
from 交易.tools import send_dingtalk_message
|
|||
|
|
def ding(msg, error=False):
|
|||
|
|
prefix = "❌自适应三分位:" if error else "🔔自适应三分位:"
|
|||
|
|
send_dingtalk_message(f"{prefix}{msg}")
|
|||
|
|
except Exception:
|
|||
|
|
def ding(msg, error=False):
|
|||
|
|
logger.info(msg)
|
|||
|
|
trader.run_loop(ding_callback=ding)
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|