From b5d4f6dfdc2f87b122f9c047982bc4f8a8071f70 Mon Sep 17 00:00:00 2001 From: 27942 <1313123@342> Date: Sun, 1 Feb 2026 19:50:32 +0800 Subject: [PATCH] 14564 --- 1111 | 28 +- bitmart/交易,一直加仓,拉平开仓价位.py | 986 ------ bitmart/优化拿仓版本.py | 458 --- bitmart/回测.py | 83 - bitmart/回测图表.png | Bin 147692 -> 0 bytes bitmart/回测图表_交互式.html | 3888 ----------------------- bitmart/均线回归.py | 866 ----- bitmart/均线自动化开单.py | 1165 ------- bitmart/抓取数据_30分钟.py | 169 - mexc/30分钟.py | 91 +- 10 files changed, 82 insertions(+), 7652 deletions(-) delete mode 100644 bitmart/交易,一直加仓,拉平开仓价位.py delete mode 100644 bitmart/优化拿仓版本.py delete mode 100644 bitmart/回测.py delete mode 100644 bitmart/回测图表.png delete mode 100644 bitmart/回测图表_交互式.html delete mode 100644 bitmart/均线回归.py delete mode 100644 bitmart/均线自动化开单.py delete mode 100644 bitmart/抓取数据_30分钟.py diff --git a/1111 b/1111 index d1ec074..337f7ae 100644 --- a/1111 +++ b/1111 @@ -23,31 +23,7 @@ Abc12345678 yx20250715@gmail.com -3. 更多交易信号 -包住形态(原策略) -锤子线和上吊线 -早晨之星和黄昏之星 -乌云盖顶和刺透形态 -三只乌鸦和三白兵 -移动平均线金叉死叉 -RSI超买超卖 -布林带突破 - -4. 风险管理 -止损止盈:可配置的百分比止损止盈 -时间止损:持仓超过24小时自动平仓 -凯利公式改进版:仓位管理 -最大回撤控制 -手续费考虑:0.05%交易手续费 -申请API原因:我计划通过API实现自动化量化交易策略,以减少人工干预,提高交易效率并减少人为错误。通过API,我能够实时获取市场数据、执行交易指令、监控账户状态,并根据市场变化自动调整策略。 -申请API具体方式用途:我将使用API来获取实时的市场行情数据,执行各种量化策略(如网格交易、动量策略、均值回归等),并进行账户管理。API将自动监控并调整我的交易参数,比如仓位、止损、止盈等,同时还需要定期回测策略和优化模型。 -申请API主要交易什么币种:我主要通过API进行ETH和BTC的合约交易,还有一起其他的主流币 -申请API操作频率:预计API的调用频率将根据市场波动而变化,通常情况下,我的API请求频率大约在每秒5-10次,有时会根据策略的调整需要进行更频繁的请求。 -申请API大概体量:预计每日API请求量为5,000 - 15,000次,具体取决于市场波动和交易策略的复杂度。月度交易量预估为50,000 - 200,000 ETH/BTC。 -申请API对接后预估增长目标:通过API对接,我预计能够实现量化策略的高效执行和自动化交易,减少人工干预和错误。目标是在对接后的3个月内,将交易频率提高50%,并将月盈利提高20%至30%。 - - -45.84 -4998.6 \ No newline at end of file +555.73 +2278 \ No newline at end of file diff --git a/bitmart/交易,一直加仓,拉平开仓价位.py b/bitmart/交易,一直加仓,拉平开仓价位.py deleted file mode 100644 index 7507fab..0000000 --- a/bitmart/交易,一直加仓,拉平开仓价位.py +++ /dev/null @@ -1,986 +0,0 @@ -""" -BitMart 被动做市/高频刷单策略 -核心逻辑:在盘口两侧不断挂单,赚取价差+返佣 -使用浏览器自动化下单,获取高返佣 -""" - -import time -from loguru import logger -from threading import Lock -from dataclasses import dataclass -from bitmart.api_contract import APIContract -from typing import Optional, Dict, List, Tuple -from DrissionPage import ChromiumPage, ChromiumOptions - -from bit_tools import openBrowser - - -# ================================================================ -# 📊 配置类 -# ================================================================ - -@dataclass -class MarketMakingConfig: - bit_id: str = "f2320f57e24c45529a009e1541e25961" - """做市策略配置""" - # API配置(仅用于查询,不下单) - api_key: str = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" - secret_key: str = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - memo: str = "合约交易" - contract_symbol: str = "ETHUSDT" - - # 浏览器配置 - tge_id: int = 196495 # TGE浏览器ID - tge_url: str = "http://127.0.0.1:50326" - tge_headers: Dict = None - trading_url: str = "https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT" - - # 做市参数 - spread_percent: float = 0.01 # 价差百分比(0.01% = 买一卖一之间) - order_size_usdt: float = 10.0 # 每单金额(USDT) - max_position_usdt: float = 100.0 # 最大持仓金额(USDT) - - # 订单管理 - order_refresh_interval: float = 2.0 # 订单刷新间隔(秒) - order_timeout: float = 60.0 # 订单超时时间(秒),超时后撤单重新挂 - - # 风险控制 - max_daily_loss: float = 50.0 # 每日最大亏损(USDT) - max_daily_trades: int = 1000 # 每日最大交易次数 - - # 杠杆和模式 - leverage: str = "30" # 杠杆倍数 - open_type: str = "cross" # 全仓模式 - - def __post_init__(self): - """初始化TGE headers""" - if self.tge_headers is None: - self.tge_headers = { - "Authorization": "Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91", - "Content-Type": "application/json" - } - - -# ================================================================ -# 📊 订单簿数据结构 -# ================================================================ - -@dataclass -class OrderBook: - """订单簿数据""" - bids: List[Tuple[float, float]] # [(价格, 数量), ...] 买盘,价格从高到低 - asks: List[Tuple[float, float]] # [(价格, 数量), ...] 卖盘,价格从低到高 - timestamp: float - - @property - def best_bid(self) -> Optional[float]: - """买一价""" - return self.bids[0][0] if self.bids else None - - @property - def best_ask(self) -> Optional[float]: - """卖一价""" - return self.asks[0][0] if self.asks else None - - @property - def spread(self) -> Optional[float]: - """价差""" - if self.best_bid and self.best_ask: - return self.best_ask - self.best_bid - return None - - @property - def mid_price(self) -> Optional[float]: - """中间价""" - if self.best_bid and self.best_ask: - return (self.best_bid + self.best_ask) / 2 - return None - - -@dataclass -class PendingOrder: - """pending订单信息""" - order_id: str - side: str # "buy" or "sell" - price: float - size: float - create_time: float - status: str # "pending", "filled", "cancelled" - - -# ================================================================ -# 📊 浏览器管理器 -# ================================================================ - -class BrowserManager: - """浏览器管理器:负责浏览器的启动、接管和标签页管理""" - - def __init__(self, config: MarketMakingConfig, bit_id="f2320f57e24c45529a009e1541e25961"): - self.bit_id = "f2320f57e24c45529a009e1541e25961" - self.config = config - self.tge_port: Optional[int] = None - self.page: Optional[ChromiumPage] = None - - def open_browser(self) -> bool: - """打开浏览器并获取端口""" - try: - bit_port = openBrowser(id=self.bit_id) - co = ChromiumOptions() - co.set_local_port(port=bit_port) - self.page = ChromiumPage(addr_or_opts=co) - return True - except Exception as e: - logger.error(f"打开浏览器失败: {e}") - return False - - def take_over_browser(self) -> bool: - """接管浏览器""" - if not self.tge_port: - logger.error("浏览器端口未设置") - return False - - try: - co = ChromiumOptions() - co.set_local_port(self.tge_port) - self.page = ChromiumPage(addr_or_opts=co) - self.page.set.window.max() - logger.success("成功接管浏览器") - return True - except Exception as e: - logger.error(f"接管浏览器失败: {e}") - return False - - def close_extra_tabs(self) -> bool: - """关闭多余的标签页,只保留第一个""" - if not self.page: - return False - - try: - tabs = self.page.get_tabs() - closed_count = 0 - for idx, tab in enumerate(tabs): - if idx == 0: - continue - tab.close() - closed_count += 1 - if closed_count > 0: - logger.info(f"已关闭{closed_count}个多余标签页") - return True - except Exception as e: - logger.warning(f"关闭多余标签页失败: {e}") - return False - - -# ================================================================ -# 📊 浏览器交易执行器 -# ================================================================ - -class BrowserTradingExecutor: - """浏览器交易执行器:通过浏览器自动化下单(获取高返佣)""" - - def __init__(self, page: ChromiumPage): - self.page = page - - def click_safe(self, xpath: str, sleep: float = 0.5) -> bool: - """安全点击""" - try: - ele = self.page.ele(xpath) - if not ele: - return False - ele.scroll.to_see(center=True) - time.sleep(sleep) - ele.click() - return True - except Exception as e: - logger.error(f"点击失败 {xpath}: {e}") - return False - - def 开单(self, marketPriceLongOrder: int = 0, limitPriceShortOrder: int = 0, - size: Optional[float] = None, price: Optional[float] = None) -> bool: - - size = 0.1 - """ - 开单操作(通过浏览器自动化,获取高返佣) - - Args: - marketPriceLongOrder: 市价最多或者做空,1是最多,-1是做空 - limitPriceShortOrder: 限价最多或者做空,1是最多,-1是做空 - size: 数量 - price: 价格(限价单需要) - - Returns: - 是否成功 - """ - try: - # 市价单 - if marketPriceLongOrder == -1: - # 市价做空 - if not self.click_safe('x://button[normalize-space(text()) ="市价"]'): - return False - self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]'): - return False - logger.success(f"市价做空成功: {size}") - return True - - elif marketPriceLongOrder == 1: - # 市价做多 - if not self.click_safe('x://button[normalize-space(text()) ="市价"]'): - return False - self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="买入/做多"]'): - return False - logger.success(f"市价做多成功: {size}") - return True - - # 限价单 - if limitPriceShortOrder == -1: - # 限价做空 - if not self.click_safe('x://button[normalize-space(text()) ="限价"]'): - return False - self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True) - time.sleep(1) - self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]'): - return False - logger.success(f"限价做空成功: {size} @ {price}") - return True - - elif limitPriceShortOrder == 1: - # 限价做多 - if not self.click_safe('x://button[normalize-space(text()) ="限价"]'): - return False - self.page.ele('x://*[@id="price_0"]').input(vals=price, clear=True) - time.sleep(1) - self.page.ele('x://*[@id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="买入/做多"]'): - return False - logger.success(f"限价做多成功: {size} @ {price}") - return True - - return False - except Exception as e: - logger.error(f"开单异常: {e}") - return False - - def 平仓(self) -> bool: - """市价平仓""" - try: - if self.click_safe('x://span[normalize-space(text()) ="市价"]'): - logger.success("平仓成功") - return True - return False - except Exception as e: - logger.error(f"平仓异常: {e}") - return False - - def place_limit_order(self, side: str, price: float, size: float) -> bool: - """ - 下限价单(通过浏览器) - - Args: - side: "buy" 或 "sell" - price: 价格 - size: 数量(张数) - - Returns: - 是否成功 - """ - try: - # size已经是张数,直接使用 - if side == "buy": - # 限价做多 - return self.开单(limitPriceShortOrder=1, size=size, price=price) - else: - # 限价做空 - return self.开单(limitPriceShortOrder=-1, size=size, price=price) - except Exception as e: - logger.error(f"限价下单异常: {e}") - return False - - -# ================================================================ -# 📊 BitMart API 封装(仅用于查询,不下单) -# ================================================================ - -class BitMartMarketMakerAPI: - """BitMart做市API封装(仅用于查询,不下单)""" - - def __init__(self, config: MarketMakingConfig): - self.config = config - self.contractAPI = APIContract( - config.api_key, - config.secret_key, - config.memo, - timeout=(5, 15) - ) - - def get_order_book(self, depth: int = 20) -> Optional[OrderBook]: - """ - 获取订单簿 - - Args: - depth: 深度数量(可能不使用) - - Returns: - OrderBook对象或None - """ - try: - # BitMart合约API获取深度数据 - # 根据错误信息,get_depth()不接受size参数 - # 尝试不同的调用方式 - try: - # 方法1:不传深度参数,使用默认值(最可能的方式) - response = self.contractAPI.get_depth( - contract_symbol=self.config.contract_symbol - )[0] - except TypeError as e1: - try: - # 方法2:尝试使用 limit 参数 - response = self.contractAPI.get_depth( - contract_symbol=self.config.contract_symbol, - limit=depth - )[0] - except TypeError as e2: - try: - # 方法3:尝试使用 depth 参数 - response = self.contractAPI.get_depth( - contract_symbol=self.config.contract_symbol, - depth=depth - )[0] - except TypeError as e3: - logger.error(f"get_depth()方法调用失败,尝试的参数方式都失败: {e1}, {e2}, {e3}") - return None - - if response.get('code') == 1000: - data = response.get('data', {}) - # BitMart返回格式可能是不同的,需要根据实际调整 - bids = [] - asks = [] - - if isinstance(data, dict): - bids_raw = data.get('bids', []) - asks_raw = data.get('asks', []) - - # 处理不同格式 - for b in bids_raw: - if isinstance(b, (list, tuple)) and len(b) >= 2: - bids.append((float(b[0]), float(b[1]))) - elif isinstance(b, dict): - bids.append((float(b.get('price', 0)), float(b.get('size', 0)))) - - for a in asks_raw: - if isinstance(a, (list, tuple)) and len(a) >= 2: - asks.append((float(a[0]), float(a[1]))) - elif isinstance(a, dict): - asks.append((float(a.get('price', 0)), float(a.get('size', 0)))) - - # 买盘按价格从高到低排序,卖盘按价格从低到高排序 - bids.sort(key=lambda x: x[0], reverse=True) - asks.sort(key=lambda x: x[0]) - - if bids and asks: - return OrderBook( - bids=bids, - asks=asks, - timestamp=time.time() - ) - return None - except Exception as e: - logger.error(f"获取订单簿异常: {e}") - # 如果获取订单簿失败,尝试使用最新价格作为备用方案 - logger.warning("尝试使用最新价格作为备用方案") - current_price = self.get_current_price() - if current_price: - # 使用当前价格和价差百分比计算买一卖一 - spread_amount = current_price * self.config.spread_percent / 100 - bids = [(current_price - spread_amount / 2, 1.0)] - asks = [(current_price + spread_amount / 2, 1.0)] - return OrderBook( - bids=bids, - asks=asks, - timestamp=time.time() - ) - return None - - def get_current_price(self) -> Optional[float]: - """获取当前最新价格""" - try: - end_time = int(time.time()) - response = self.contractAPI.get_kline( - contract_symbol=self.config.contract_symbol, - step=1, # 1分钟 - start_time=end_time - 60, - end_time=end_time - )[0] - - if response.get('code') == 1000: - data = response.get('data', []) - if data: - return float(data[-1]["close_price"]) - return None - except Exception as e: - logger.error(f"获取价格异常: {e}") - return None - - def get_available_balance(self) -> Optional[float]: - """获取合约账户可用USDT余额""" - try: - response = self.contractAPI.get_assets_detail()[0] - if response.get('code') == 1000: - data = response['data'] - if isinstance(data, dict): - return float(data.get('available_balance', 0)) - elif isinstance(data, list): - for asset in data: - if asset.get('currency') == 'USDT': - return float(asset.get('available_balance', 0)) - return None - except Exception as e: - logger.error(f"余额查询异常: {e}") - return None - - def get_position(self) -> Optional[Dict]: - """获取当前持仓""" - try: - response = self.contractAPI.get_position( - contract_symbol=self.config.contract_symbol - )[0] - - if response.get('code') == 1000: - positions = response.get('data', []) - if positions: - return positions[0] - return None - return None - except Exception as e: - logger.error(f"持仓查询异常: {e}") - return None - - def set_leverage(self) -> bool: - """设置杠杆和全仓模式""" - try: - response = self.contractAPI.post_submit_leverage( - contract_symbol=self.config.contract_symbol, - leverage=self.config.leverage, - open_type=self.config.open_type - )[0] - - if response.get('code') == 1000: - logger.success(f"全仓模式 + {self.config.leverage}x 杠杆设置成功") - return True - else: - logger.error(f"杠杆设置失败: {response}") - return False - except Exception as e: - logger.error(f"设置杠杆异常: {e}") - return False - - # ============== 新增:撤单、平仓 ============== - def get_open_orders(self) -> List[Dict]: - """获取当前所有挂单""" - try: - resp = self.contractAPI.get_open_order( - contract_symbol=self.config.contract_symbol - )[0] - if resp.get("code") == 1000: - data = resp.get("data", []) - return data if isinstance(data, list) else [] - return [] - except Exception as e: - logger.error(f"查询挂单异常: {e}") - return [] - - def cancel_order(self, order_id: str) -> bool: - """撤销单个挂单""" - try: - resp = self.contractAPI.post_cancel_order( - contract_symbol=self.config.contract_symbol, - order_id=order_id - )[0] - if resp.get("code") == 1000: - logger.success(f"撤单成功: {order_id}") - return True - logger.error(f"撤单失败: {resp}") - return False - except Exception as e: - logger.error(f"撤单异常: {e}") - return False - - def cancel_all_orders(self) -> None: - """撤销所有挂单(无精确超时信息时,直接全撤)""" - open_orders = self.get_open_orders() - for od in open_orders: - oid = str(od.get("order_id") or od.get("id") or "") - if oid: - self.cancel_order(oid) - - def close_position(self) -> bool: - """ - 使用API平仓(市价/近似市价) - 逻辑:查询当前持仓,根据方向下相反方向的平仓单 - """ - try: - position = self.get_position() - if not position: - logger.info("无持仓,无需平仓") - return True - - position_type = int(position.get("position_type", 0)) # 1=多, 2=空 - current_amount = float(position.get("current_amount", 0)) - if current_amount <= 0: - logger.info("持仓数量为0,无需平仓") - return True - - # 获取现价作为平仓价格参考 - current_price = self.get_current_price() - if not current_price: - logger.error("无法获取现价,平仓失败") - return False - - # BitMart合约订单类型:3=平多(限价),4=平空(限价) - if position_type == 1: - order_type = 3 # 平多 - elif position_type == 2: - order_type = 4 # 平空 - else: - logger.error(f"未知持仓方向: {position_type}") - return False - - # 下平仓单 - resp = self.contractAPI.post_submit_order( - contract_symbol=self.config.contract_symbol, - type=order_type, - price=str(current_price), - size=str(current_amount) - )[0] - - if resp.get("code") == 1000: - logger.success(f"API平仓成功,方向={position_type}, 数量={current_amount}, 价格={current_price}") - return True - - logger.error(f"API平仓失败: {resp}") - return False - except Exception as e: - logger.error(f"API平仓异常: {e}") - return False - - -# ================================================================ -# 📊 做市策略核心 -# ================================================================ - -class MarketMakingStrategy: - """被动做市策略(使用浏览器自动化下单,获取高返佣)""" - - def __init__(self, config: MarketMakingConfig, bit_id=None): - self.bit_id = bit_id - self.config = config - self.api = BitMartMarketMakerAPI(config) # 仅用于查询 - - # 浏览器管理 - self.browser_manager = BrowserManager(config) - self.trading_executor: Optional[BrowserTradingExecutor] = None - - # 订单管理(使用时间戳作为订单ID,因为浏览器下单无法直接获取订单ID) - self.pending_orders: Dict[str, PendingOrder] = {} - self.order_lock = Lock() - - # 统计 - self.daily_trades = 0 - self.daily_profit = 0.0 - self.total_trades = 0 - self.total_profit = 0.0 - - # 运行状态 - self.running = False - self.last_order_refresh = 0.0 - - # 初始化浏览器和杠杆 - if not self._initialize_browser(): - raise Exception("浏览器初始化失败") - self.api.set_leverage() - - def _initialize_browser(self) -> bool: - """初始化浏览器""" - try: - # 打开浏览器 - if not self.browser_manager.open_browser(): - logger.error("打开浏览器失败") - return False - - # # 接管浏览器 - # if not self.browser_manager.take_over_browser(): - # logger.error("接管浏览器失败") - # return False - - # 关闭多余标签页 - self.browser_manager.close_extra_tabs() - - # 打开交易页面 - self.browser_manager.page.get(self.config.trading_url) - time.sleep(2) # 等待页面加载 - - # 初始化交易执行器 - self.trading_executor = BrowserTradingExecutor(self.browser_manager.page) - - logger.success("浏览器初始化完成") - return True - except Exception as e: - logger.error(f"浏览器初始化异常: {e}") - return False - - def calculate_order_prices(self, order_book: OrderBook) -> Tuple[Optional[float], Optional[float]]: - """ - 计算挂单价格 - - Args: - order_book: 订单簿 - - Returns: - (buy_price, sell_price) - """ - if not order_book.mid_price or not order_book.best_bid or not order_book.best_ask: - return None, None - - mid = order_book.mid_price - spread_amount = mid * self.config.spread_percent / 100 - - # 买单价格:中间价 - 价差的一半,但不能低于买一 - buy_price = mid - spread_amount / 2 - buy_price = min(buy_price, order_book.best_bid * 0.9999) # 略低于买一,确保能成交 - - # 卖单价格:中间价 + 价差的一半,但不能高于卖一 - sell_price = mid + spread_amount / 2 - sell_price = max(sell_price, order_book.best_ask * 1.0001) # 略高于卖一,确保能成交 - - # 确保价差合理 - if sell_price <= buy_price: - # 如果价差太小,使用买一卖一价格 - buy_price = order_book.best_bid * 0.9999 - sell_price = order_book.best_ask * 1.0001 - - return buy_price, sell_price - - def should_refresh_orders(self) -> bool: - """判断是否需要刷新订单""" - now = time.time() - if now - self.last_order_refresh >= self.config.order_refresh_interval: - return True - return False - - def cancel_stale_orders(self): - """撤销超时订单(使用API撤单)""" - now = time.time() - to_cancel = [] - - with self.order_lock: - for order_id, order in self.pending_orders.items(): - if order.status == "pending": - if now - order.create_time > self.config.order_timeout: - to_cancel.append(order_id) - - if not to_cancel: - return - - logger.info(f"发现{len(to_cancel)}个超时订单,尝试API撤单") - try: - # 先通过API获取真实挂单列表并撤单 - self.api.cancel_all_orders() - # 本地状态同步 - with self.order_lock: - for order_id in to_cancel: - if order_id in self.pending_orders: - self.pending_orders[order_id].status = "cancelled" - except Exception as e: - logger.error(f"API撤单失败: {e}") - - def update_pending_orders(self): - """更新挂单状态(通过持仓变化判断订单是否成交)""" - try: - # 获取当前持仓 - current_position = self.api.get_position() - current_position_type = 0 - current_position_amount = 0.0 - - if current_position: - current_position_type = int(current_position.get('position_type', 0)) - current_position_amount = abs(float(current_position.get('current_amount', 0))) - - with self.order_lock: - # 检查挂单是否成交(通过持仓变化判断) - for order_id, order in list(self.pending_orders.items()): - if order.status == "pending": - # 检查订单是否超时 - if time.time() - order.create_time > self.config.order_timeout: - # 订单超时,标记为取消 - order.status = "cancelled" - logger.info(f"订单超时: {order_id} {order.side} @ {order.price}") - continue - except Exception as e: - logger.error(f"更新挂单状态异常: {e}") - - def _place_counter_order(self, filled_order: PendingOrder): - """ - 订单成交后,在另一侧挂单 - - Args: - filled_order: 已成交的订单 - """ - # 等待一小段时间,确保订单状态更新 - time.sleep(0.1) - - order_book = self.api.get_order_book() - if not order_book: - logger.warning("无法获取订单簿,无法挂反向单") - return - - # 计算反向订单价格 - buy_price, sell_price = self.calculate_order_prices(order_book) - - if filled_order.side == "buy": - # 买单成交,挂卖单 - if sell_price and self.trading_executor: - contract_size = self.config.order_size_usdt / sell_price / 0.01 - if contract_size < 1: - contract_size = 1 - - if self.trading_executor.place_limit_order("sell", sell_price, contract_size): - order_id = f"sell_{int(time.time() * 1000)}" - with self.order_lock: - self.pending_orders[order_id] = PendingOrder( - order_id=order_id, - side="sell", - price=sell_price, - size=self.config.order_size_usdt, - create_time=time.time(), - status="pending" - ) - logger.info(f"买单成交后挂卖单: {sell_price}, 订单ID: {order_id}") - else: - logger.warning("买单成交后挂卖单失败") - else: - # 卖单成交,挂买单平空或开多 - if buy_price and self.trading_executor: - contract_size = self.config.order_size_usdt / buy_price / 0.01 - if contract_size < 1: - contract_size = 1 - - if self.trading_executor.place_limit_order("buy", buy_price, contract_size): - order_id = f"buy_{int(time.time() * 1000)}" - with self.order_lock: - self.pending_orders[order_id] = PendingOrder( - order_id=order_id, - side="buy", - price=buy_price, - size=self.config.order_size_usdt, - create_time=time.time(), - status="pending" - ) - logger.info(f"卖单成交后挂买单: {buy_price}, 订单ID: {order_id}") - else: - logger.warning("卖单成交后挂买单失败") - - def place_market_making_orders(self): - """放置做市订单""" - # 获取订单簿 - order_book = self.api.get_order_book() - if not order_book or not order_book.mid_price: - logger.warning("无法获取订单簿") - return - - # 检查持仓 - position = self.api.get_position() - position_value = 0.0 - if position: - current_price = order_book.mid_price - position_amount = abs(float(position.get('current_amount', 0))) - # 计算持仓价值(USDT) - position_value = position_amount * current_price - - # 如果持仓超过限制,只挂反向单 - if position_value >= self.config.max_position_usdt: - logger.warning(f"持仓超过限制: {position_value} USDT,只挂反向单") - # 只挂反向单平仓 - if position: - position_type = int(position.get('position_type', 0)) - if position_type == 1: # 多仓 - # 挂卖单平多 - _, sell_price = self.calculate_order_prices(order_book) - if sell_price and self.trading_executor: - contract_size = self.config.order_size_usdt / sell_price / 0.01 - if contract_size < 1: - contract_size = 1 - self.trading_executor.place_limit_order("sell", sell_price, contract_size) - elif position_type == 2: # 空仓 - # 挂买单平空 - buy_price, _ = self.calculate_order_prices(order_book) - if buy_price and self.trading_executor: - contract_size = self.config.order_size_usdt / buy_price / 0.01 - if contract_size < 1: - contract_size = 1 - self.trading_executor.place_limit_order("buy", buy_price, contract_size) - return - - # 计算挂单价格 - buy_price, sell_price = self.calculate_order_prices(order_book) - - if not buy_price or not sell_price: - return - - # 检查当前挂单数量 - with self.order_lock: - pending_buy_count = sum(1 for o in self.pending_orders.values() - if o.side == "buy" and o.status == "pending") - pending_sell_count = sum(1 for o in self.pending_orders.values() - if o.side == "sell" and o.status == "pending") - - # 如果两侧都有挂单,不重复挂 - if pending_buy_count > 0 and pending_sell_count > 0: - return - - # 挂买单(通过浏览器) - if pending_buy_count == 0: - # 计算张数(根据合约规格调整) - # 假设页面输入框单位是张数,需要将USDT金额转换为张数 - # size_usdt / price = ETH数量,再除以合约面值得到张数 - contract_size = self.config.order_size_usdt / buy_price / 0.01 - if contract_size < 1: - contract_size = 1 - - if self.trading_executor and self.trading_executor.place_limit_order("buy", buy_price, contract_size): - # 使用时间戳作为订单ID - order_id = f"buy_{int(time.time() * 1000)}" - with self.order_lock: - self.pending_orders[order_id] = PendingOrder( - order_id=order_id, - side="buy", - price=buy_price, - size=self.config.order_size_usdt, - create_time=time.time(), - status="pending" - ) - logger.info(f"挂买单成功: {buy_price}, 订单ID: {order_id}") - else: - logger.warning("挂买单失败") - - # 挂卖单(通过浏览器) - if pending_sell_count == 0: - # 计算张数 - contract_size = self.config.order_size_usdt / sell_price / 0.01 - if contract_size < 1: - contract_size = 1 - - if self.trading_executor and self.trading_executor.place_limit_order("sell", sell_price, contract_size): - # 使用时间戳作为订单ID - order_id = f"sell_{int(time.time() * 1000)}" - with self.order_lock: - self.pending_orders[order_id] = PendingOrder( - order_id=order_id, - side="sell", - price=sell_price, - size=self.config.order_size_usdt, - create_time=time.time(), - status="pending" - ) - logger.info(f"挂卖单成功: {sell_price}, 订单ID: {order_id}") - else: - logger.warning("挂卖单失败") - - self.last_order_refresh = time.time() - - def check_risk_limits(self) -> bool: - """检查风险限制""" - # 检查每日交易次数 - if self.daily_trades >= self.config.max_daily_trades: - logger.warning(f"达到每日最大交易次数: {self.daily_trades}") - return False - - # 检查每日亏损 - if self.daily_profit <= -self.config.max_daily_loss: - logger.error(f"达到每日最大亏损: {self.daily_profit}") - # send_dingtalk_message(f"做市策略达到每日最大亏损: {self.daily_profit} USDT", error=True) - return False - - return True - - def run(self): - """主运行循环""" - self.running = True - logger.info("做市策略启动") - - while self.running: - try: - # 检查风险限制 - if not self.check_risk_limits(): - logger.error("风险限制触发,停止策略") - break - - # 撤销超时订单 - self.cancel_stale_orders() - - # 更新挂单状态 - self.update_pending_orders() - - # 刷新订单 - if self.should_refresh_orders(): - self.place_market_making_orders() - - # 短暂休眠 - time.sleep(0.5) - - except KeyboardInterrupt: - logger.info("收到中断信号,停止策略") - break - except Exception as e: - logger.error(f"策略运行异常: {e}") - time.sleep(1) - - # 清理:刷新页面,手动撤销挂单 - logger.info("清理挂单...使用API撤单") - try: - self.api.cancel_all_orders() - with self.order_lock: - for order_id in list(self.pending_orders.keys()): - if self.pending_orders[order_id].status == "pending": - self.pending_orders[order_id].status = "cancelled" - except Exception as e: - logger.error(f"清理挂单失败: {e}") - - logger.info("做市策略已停止") - - def stop(self): - """停止策略""" - self.running = False - - -# ================================================================ -# 🚀 主程序 -# ================================================================ - -if __name__ == '__main__': - config = MarketMakingConfig( - contract_symbol="ETHUSDT", - bit_id="f2320f57e24c45529a009e1541e25961", # TGE浏览器ID - trading_url="https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT", - spread_percent=0.01, # 0.01%价差 - order_size_usdt=0.1, # 每单10 USDT - max_position_usdt=3.0, # 最大持仓100 USDT - order_refresh_interval=2.0, # 2秒刷新一次 - order_timeout=60.0, # 60秒超时 - max_daily_loss=50.0, # 每日最大亏损50 USDT - max_daily_trades=1000, # 每日最大1000笔 - leverage="35", - open_type="cross" - ) - - strategy = MarketMakingStrategy(config) - - try: - strategy.run() - except Exception as e: - logger.error(f"程序异常: {e}") - # send_dingtalk_message(f"做市策略异常: {e}", error=True) - -# 9359,53 -# 14.35 diff --git a/bitmart/优化拿仓版本.py b/bitmart/优化拿仓版本.py deleted file mode 100644 index 489fcbb..0000000 --- a/bitmart/优化拿仓版本.py +++ /dev/null @@ -1,458 +0,0 @@ -""" -BitMart 被动做市/高频刷单策略 (修复版 V2) -修复内容: -1. 修正 get_order_book 中解析深度数据的方式,由字典键名访问改为列表索引访问 (['price'] -> [0]) -""" - -import time -import requests -from typing import Optional, Dict, List, Tuple -from dataclasses import dataclass -from loguru import logger -from threading import Lock - -from DrissionPage import ChromiumPage, ChromiumOptions -from bitmart.api_contract import APIContract - - -# ================================================================ -# 📊 配置类 -# ================================================================ - -@dataclass -class MarketMakingConfig: - """做市策略配置""" - # API配置(仅用于查询,不下单) - api_key: str = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" - secret_key: str = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - memo: str = "合约交易" - contract_symbol: str = "ETHUSDT" - - # 浏览器配置 - tge_id: int = 196495 # TGE浏览器ID - tge_url: str = "http://127.0.0.1:50326" - tge_headers: Dict = None - trading_url: str = "https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT" - - # 做市基础参数 - spread_percent: float = 0.04 # 基础价差 (0.04% 约为 $1左右 on ETH) - order_size_usdt: float = 10.0 # 每单金额(USDT) - max_position_usdt: float = 100.0 # 最大持仓金额(USDT) - - # 🚀 高级策略参数 - # 库存倾斜:每持有100U,价格偏移多少。正数表示持有多单时价格下调(利于卖出,不利于买入) - inventory_skew_factor: float = 0.0005 - # 价格容忍度:只有当(目标价 - 当前挂单价) / 目标价 > 0.05% 时才改单,避免频繁操作 - price_tolerance: float = 0.0005 - - # 风险控制 - max_daily_loss: float = 50.0 - leverage: str = "30" - open_type: str = "cross" - - def __post_init__(self): - """初始化TGE headers""" - if self.tge_headers is None: - self.tge_headers = { - "Authorization": "Bearer asp_174003986c9b0799677c5b2c1adb76e402735d753bc91a91", - "Content-Type": "application/json" - } - - -# ================================================================ -# 📊 订单簿数据结构 -# ================================================================ - -@dataclass -class OrderBook: - """订单簿数据""" - bids: List[Tuple[float, float]] # [(价格, 数量), ...] - asks: List[Tuple[float, float]] # [(价格, 数量), ...] - timestamp: float - - @property - def mid_price(self) -> Optional[float]: - """中间价""" - if self.bids and self.asks: - return (self.bids[0][0] + self.asks[0][0]) / 2 - return None - - -# ================================================================ -# 📊 浏览器管理器 -# ================================================================ - -class BrowserManager: - """浏览器管理器:负责浏览器的启动、接管和标签页管理""" - - def __init__(self, config: MarketMakingConfig): - self.config = config - self.tge_port: Optional[int] = None - self.page: Optional[ChromiumPage] = None - - def open_browser(self) -> bool: - """打开浏览器并获取端口""" - try: - response = requests.post( - f"{self.config.tge_url}/api/browser/start", - json={"envId": self.config.tge_id}, - headers=self.config.tge_headers, - timeout=10 - ) - data = response.json() - if "data" in data and "port" in data["data"]: - self.tge_port = data["data"]["port"] - logger.success(f"成功打开浏览器,端口:{self.tge_port}") - return True - else: - logger.error(f"打开浏览器响应异常: {data}") - return False - except Exception as e: - logger.error(f"打开浏览器失败: {e}") - return False - - def take_over_browser(self) -> bool: - """接管浏览器""" - if not self.tge_port: - logger.error("浏览器端口未设置") - return False - - try: - co = ChromiumOptions() - co.set_local_port(self.tge_port) - self.page = ChromiumPage(addr_or_opts=co) - logger.success("成功接管浏览器") - return True - except Exception as e: - logger.error(f"接管浏览器失败: {e}") - return False - - def close_extra_tabs(self) -> bool: - """关闭多余的标签页,只保留第一个""" - if not self.page: - return False - try: - tabs = self.page.get_tabs() - for idx, tab in enumerate(tabs): - if idx == 0: continue - tab.close() - return True - except Exception as e: - logger.warning(f"关闭多余标签页失败: {e}") - return False - - -# ================================================================ -# 📊 浏览器交易执行器 -# ================================================================ - -class BrowserTradingExecutor: - """浏览器交易执行器:通过浏览器自动化下单(获取高返佣)""" - - def __init__(self, page: ChromiumPage): - self.page = page - - def click_safe(self, xpath: str, sleep: float = 0.5) -> bool: - """安全点击""" - try: - ele = self.page.ele(xpath) - if not ele: - return False - ele.scroll.to_see(center=True) - time.sleep(sleep) - ele.click() - return True - except Exception as e: - logger.error(f"点击失败 {xpath}: {e}") - return False - - def 开单(self, marketPriceLongOrder: int = 0, limitPriceShortOrder: int = 0, - size: Optional[float] = None, price: Optional[float] = None) -> bool: - """开单操作""" - size = 0.1 - - try: - # 市价单 (代码略) - if marketPriceLongOrder == -1: pass - elif marketPriceLongOrder == 1: pass - - # 限价单 - if limitPriceShortOrder == -1: - # 限价做空 - if not self.click_safe('x://button[normalize-space(text()) ="限价"]'): return False - self.page.ele('x://*[ @id="price_0"]').input(vals=price, clear=True) - time.sleep(0.2) - self.page.ele('x://*[ @id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]'): return False - logger.success(f"浏览器下单: 限价做空 {size} @ {price}") - return True - - elif limitPriceShortOrder == 1: - # 限价做多 - if not self.click_safe('x://button[normalize-space(text()) ="限价"]'): return False - self.page.ele('x://*[ @id="price_0"]').input(vals=price, clear=True) - time.sleep(0.2) - self.page.ele('x://*[ @id="size_0"]').input(vals=size, clear=True) - if not self.click_safe('x://span[normalize-space(text()) ="买入/做多"]'): return False - logger.success(f"浏览器下单: 限价做多 {size} @ {price}") - return True - - return False - except Exception as e: - logger.error(f"开单异常: {e}") - return False - - def place_limit_order(self, side: str, price: float, size: float) -> bool: - """统一接口""" - if side == "buy": - return self.开单(limitPriceShortOrder=1, size=size, price=price) - else: - return self.开单(limitPriceShortOrder=-1, size=size, price=price) - - -# ================================================================ -# 📊 BitMart API 封装 (修复 get_order_book) -# ================================================================ - -class BitMartMarketMakerAPI: - """BitMart做市API封装(仅用于查询,不下单)""" - - def __init__(self, config: MarketMakingConfig): - self.config = config - self.contractAPI = APIContract( - config.api_key, - config.secret_key, - config.memo, - timeout=(5, 15) - ) - - def get_order_book(self) -> Optional[OrderBook]: - try: - # 移除不支持的 limit 参数 - response = self.contractAPI.get_depth(contract_symbol=self.config.contract_symbol)[0] - - if response.get('code') == 1000: - data = response.get('data', {}) - bids = [] - asks = [] - # 解析数据 - if isinstance(data, dict): - bids_raw = data.get('bids', []) - asks_raw = data.get('asks', []) - - # 修复:b 是列表 [price, size],不是字典 - for b in bids_raw[:10]: - # b[0] 是价格, b[1] 是数量 - bids.append((float(b[0]), float(b[1]))) - - for a in asks_raw[:10]: - # a[0] 是价格, a[1] 是数量 - asks.append((float(a[0]), float(a[1]))) - - if bids and asks: - return OrderBook(bids=bids, asks=asks, timestamp=time.time()) - else: - logger.warning(f"获取深度失败: {response}") - return None - except Exception as e: - logger.error(f"获取订单簿异常: {e}") - return None - - def get_position_net(self) -> float: - """获取净持仓 (多为正,空为负)""" - try: - response = self.contractAPI.get_position(contract_symbol=self.config.contract_symbol)[0] - if response.get('code') == 1000: - data = response.get('data', []) - if data: - pos = data[0] - current_amount = float(pos.get('current_amount', 0)) - position_type = int(pos.get('position_type', 0)) # 1多 2空 - if position_type == 1: return current_amount - if position_type == 2: return -current_amount - return 0.0 - except Exception as e: - logger.error(f"持仓查询异常: {e}") - return 0.0 - - def get_open_orders(self) -> List[Dict]: - """获取当前挂单""" - try: - resp = self.contractAPI.get_open_order(contract_symbol=self.config.contract_symbol)[0] - if resp.get("code") == 1000: - return resp.get("data", []) - return [] - except Exception as e: - logger.error(f"查询挂单异常: {e}") - return [] - - def cancel_order(self, order_id: str) -> bool: - """API撤单""" - try: - resp = self.contractAPI.post_cancel_order(contract_symbol=self.config.contract_symbol, order_id=order_id)[0] - return resp.get("code") == 1000 - except Exception as e: - logger.error(f"API撤单异常: {e}") - return False - - def set_leverage(self): - try: - self.contractAPI.post_submit_leverage(contract_symbol=self.config.contract_symbol, leverage=self.config.leverage, open_type=self.config.open_type) - except: - pass - - -# ================================================================ -# 🧠 策略核心 -# ================================================================ - -class MarketMakingStrategy: - """优化版被动做市策略""" - - def __init__(self, config: MarketMakingConfig): - self.config = config - self.api = BitMartMarketMakerAPI(config) - self.browser_manager = BrowserManager(config) - self.trading_executor: Optional[BrowserTradingExecutor] = None - self.running = False - - # 初始化流程 - if not self._initialize_browser(): - raise Exception("浏览器初始化失败") - self.api.set_leverage() - - def _initialize_browser(self) -> bool: - try: - if not self.browser_manager.open_browser(): return False - if not self.browser_manager.take_over_browser(): return False - self.browser_manager.close_extra_tabs() - - # 访问交易页 - logger.info(f"正在访问交易页: {self.config.trading_url}") - self.browser_manager.page.get(self.config.trading_url) - time.sleep(3) - - self.trading_executor = BrowserTradingExecutor(self.browser_manager.page) - logger.success("浏览器环境就绪") - return True - except Exception as e: - logger.error(f"浏览器初始化异常: {e}") - return False - - def calculate_target_prices(self, mid_price: float, net_position: float) -> Tuple[float, float]: - """核心算法:计算考虑了库存倾斜的目标买卖价""" - # 1. 基础价差的一半 - half_spread = mid_price * (self.config.spread_percent / 100) / 2 - - # 2. 库存倾斜调整 - skew_adjust = net_position * self.config.inventory_skew_factor * mid_price - - quote_mid = mid_price - skew_adjust - - target_bid = quote_mid - half_spread - target_ask = quote_mid + half_spread - - # 3. 价格修正 (防止穿仓) - if target_ask <= target_bid: - target_ask = target_bid + mid_price * 0.0001 - - return round(target_bid, 2), round(target_ask, 2) - - def reconcile_orders(self, target_bid: float, target_ask: float): - """调节逻辑:对比API实际挂单 vs 目标价格""" - open_orders = self.api.get_open_orders() - - current_bids = [] - current_asks = [] - - for o in open_orders: - side = o.get('side') - # 兼容API返回 - side_str = str(side).lower() - if side_str == '1' or 'buy' in side_str: - current_bids.append(o) - elif side_str == '2' or 'sell' in side_str: - current_asks.append(o) - - # --- 调节买单 --- - valid_bid_exists = False - for order in current_bids: - price = float(order.get('price', 0)) - diff_pct = abs(price - target_bid) / target_bid - - if diff_pct < self.config.price_tolerance: - valid_bid_exists = True - else: - logger.info(f"♻️ 买单价格偏离 (现{price} vs 标{target_bid}),撤单") - self.api.cancel_order(order.get('order_id') or order.get('id')) - - if not valid_bid_exists: - # 计算张数 - size_contract = self.config.order_size_usdt / target_bid / 0.01 - size_contract = max(1, int(size_contract)) - logger.info(f"➕ 补买单: {target_bid} (数量:{size_contract})") - self.trading_executor.place_limit_order("buy", target_bid, size_contract) - - # --- 调节卖单 --- - valid_ask_exists = False - for order in current_asks: - price = float(order.get('price', 0)) - diff_pct = abs(price - target_ask) / target_ask - - if diff_pct < self.config.price_tolerance: - valid_ask_exists = True - else: - logger.info(f"♻️ 卖单价格偏离 (现{price} vs 标{target_ask}),撤单") - self.api.cancel_order(order.get('order_id') or order.get('id')) - - if not valid_ask_exists: - size_contract = self.config.order_size_usdt / target_ask / 0.01 - size_contract = max(1, int(size_contract)) - logger.info(f"➕ 补卖单: {target_ask} (数量:{size_contract})") - self.trading_executor.place_limit_order("sell", target_ask, size_contract) - - def run(self): - self.running = True - logger.info("🚀 策略已启动") - - while self.running: - try: - # 1. 获取市场数据 - ob = self.api.get_order_book() - if not ob: - time.sleep(1) - continue - mid_price = ob.mid_price - - # 2. 获取持仓 - net_position = self.api.get_position_net() - - # 3. 计算目标价 - t_bid, t_ask = self.calculate_target_prices(mid_price, net_position) - logger.info(f"Mid:{mid_price:.2f} | Pos:{net_position} | Target Bid:{t_bid} Ask:{t_ask}") - - # 4. 调节挂单 - self.reconcile_orders(t_bid, t_ask) - - # 5. 循环间隔 - time.sleep(3) - - except KeyboardInterrupt: - logger.warning("停止策略") - break - except Exception as e: - logger.error(f"Loop Exception: {e}") - time.sleep(2) - -if __name__ == '__main__': - config = MarketMakingConfig( - contract_symbol="ETHUSDT", - spread_percent=0.04, - order_size_usdt=0.1, - max_position_usdt=50.0, - inventory_skew_factor=0.0005, - price_tolerance=0.0005 - ) - - strategy = MarketMakingStrategy(config) - strategy.run() diff --git a/bitmart/回测.py b/bitmart/回测.py deleted file mode 100644 index f709dce..0000000 --- a/bitmart/回测.py +++ /dev/null @@ -1,83 +0,0 @@ -import time -import csv - -import loguru -from bitmart.api_contract import APIContract - -# ------------------ 配置 ------------------ -START_YEAR = 2025 -CONTRACT_SYMBOL = "ETHUSDT" -STEP = 3 # K 线周期,单位分钟 -CSV_FILE = f"kline_{STEP}.csv" - -memo = "合约交易" -api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" -secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - -contractAPI = APIContract(api_key, secret_key, memo, timeout=(5, 15)) - -# ------------------ 时间戳 ------------------ -start_of_year = int(time.mktime((START_YEAR, 1, 1, 0, 0, 0, 0, 0, 0))) -current_time = int(time.time()) - -# ------------------ 抓取数据 ------------------ -all_data = [] -existing_ids = set() -start_time = start_of_year - -# 每次请求时间长度 = step * 500 条 K 线 -request_interval_ms = STEP * 60 * 500 - -while start_time < current_time: - end_time = min(start_time + request_interval_ms, current_time) - loguru.logger.info(f"抓取时间段: {start_time} ~ {end_time}") - - try: - response = contractAPI.get_kline( - contract_symbol=CONTRACT_SYMBOL, - step=STEP, - start_time=start_time, - end_time=end_time - )[0]["data"] - - formatted = [] - for k in response: - print(k) - k_id = int(k["timestamp"]) - if k_id in existing_ids: - continue - existing_ids.add(k_id) - formatted.append({ - 'id': int(k["timestamp"]), - 'open': float(k["open_price"]), - 'high': float(k["high_price"]), - 'low': float(k["low_price"]), - 'close': float(k["close_price"]), - 'volume': float(k["volume"]) - }) - - formatted.sort(key=lambda x: x['id']) - all_data.extend(formatted) - - if len(response) < 500: - start_time = end_time - else: - start_time = formatted[-1]['id'] + 1 - - except Exception as e: - print(f"请求出错: {e},等待 60 秒后重试") - time.sleep(60) - - time.sleep(0.2) # 控制速率,保证 <= 每 2 秒 12 次 - -# ------------------ 保存 CSV ------------------ -csv_columns = ['id', 'open', 'high', 'low', 'close', 'volume'] -try: - with open(CSV_FILE, 'w', newline='') as csvfile: - writer = csv.DictWriter(csvfile, fieldnames=csv_columns) - writer.writeheader() - for data in all_data: - writer.writerow(data) - print(f"数据已保存到 {CSV_FILE},共 {len(all_data)} 条") -except IOError: - print("I/O error") diff --git a/bitmart/回测图表.png b/bitmart/回测图表.png deleted file mode 100644 index d3df2fd82f48d99c6dfc7161b2b44d82e6de3a91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147692 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 ?mbaoi{%blR>AMCmQvSLHUJ+r~_eA%tW27!~l?�YBc@#YU8Ii^oh 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 z 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�TE$ZWzwXK%sjW9XwPFprDL}0^G&{G++x zYZBwo1o(!`R=Rk{=YAtj>9}>}Mysp#(Wq3S``?B?81!LovJJxFFoA(()eDzMDFsQ)K11 z(RKeoWlmYu8mD^9gngV=tUd)T8fAm?M|5@eSQ7fz%d0OVFM*2*2*2%~s zW0GRw(XX{esqP{D`~BhJ&$$uD@K`3Tdp?*XG+81MZx$vDlG{pkRwH#6eaYN^Ers%h zfN31hy->06j<&Z=O{ZYJYc3>QwvWKp%=4|>Py-T?AKnh+cJPhw8YqJ z>CDmIad*qdIWXyY+{%lle5IZ>bqn(#ManyUAtLT{>HuM9ytNL3_B^!`&4wx-#o3c6<&z0%u8l=#52{G3O|mOlkW z%dpwsIZUd(rXqCzf`3aiCS7lyF*5NSwKHiA-9HxjNhj&ml37t@2AnwcX}{VjWK4XM zo^yhtQaBU#tCBlQ2Jmm_O07!in5I$NoU^E=mPllI2FzvSCWQQnkvKl1xQE7%Jd)X> zIzn}flwTOzb|bMbPPy89X{$kZH`UU0RAGC+6g6FjrZd-HJaL(7*LMhy&85*x@zsb) zTT8UF`66tL1hnrzz<@2gyc4fR**96N-ovK8zt@8M4J^O{81DTWOzL_@LK4i<_Z|hz z{ypFGxD8xf)TYyHB=)$Pnq#t@|8T~>HCEgK=Gpjmb%~Zr+}M?kMvboG+@G(rq)6?9G8Ct^&FQfl{r8IEI)uFgdxq$b)IH&Vr0~k3 z3of#_Bt6t8JZu<$ct_htEvj_3aCI#Ep$);jwq;mvh_oo=T$o*4*}Gzzc7%sQiW-X`U^OjNc%RO3__{(W5l9di3j>2R+KAzDCT zto!`3gF$pDN&D*hTV*HbTw>IwAbDFI#ss#V|ZD<5m(&OzOYx~!|{c^@KAMO8%?aLf$wfAsoxA0Kvlrc0oC z_uRILXBIV<4-R+uIsO&A$TDd+3@A*7RqQ}j}d*|b~VCa7oKCisCMK z+-_c#7Q2$USKPjRr-qe8x)z1uA}>wn&O+At{<%e$t2*TEV=vO7Rc&Pf)&Pcp(K4;8 z*Zx9RowF3G4md@$-Le79y0Qj81QSAWoV!(GBnlZnZ)bdyaYM*zx7Elim0VN`k+J)S zM=FciH;41CO)uv*7?#(V{H$@>X093+Nz37dm&;E+uezVW?q3^I$)26#B67mm=2?Qo zA5ov17`Lhoci7T%@QjZ1UY|UzliAKFFcHpF*)AB-Gg8%idC_%TfkPY!K{J^Y!p8P8 zZ}ON;VlJ$bi0&BP5;7@`fNiPnJ0!aHn0I+%ye>iZVqcC=#)D62#xR0qMzPs745)Mb zjTvNjR%~LLC{0a+gOVWl>1E|8=2UE%`q|8N@OS0qkk+pS1^q>9M-S)gR9?c0?!s!h ziZA;wo%^*X^gsR9N0ygH!XVP4GgnoVV`V9It^Vzsi|ZgFkKb;|k5Wg_pcYLC_7F0@ zkRtWE#hNuJwvEW%Y{REjT@{u1&aBAja*x}qIjhZKYTJ#W2aJv9ubfXdS7!B(*9SYT z*sMj4BVSRM8o}ReXF7ARc7`Q0!eXwEv-xnf-yGn53fg*k*LJSa4NR$_Iy^iK3rh*M z{NiJ1Zq6VSLQH*s3OPMd9Heq1&mJj$=JjD>Z7Su=pL&EC6)!$Xy;w+wLj9izKakvG z7GN#V)47f~pIg`*sr+2ZE0LY0R3iZ~Y;eeu*?ygeie+3o57r<4d9`DEzswnH8ERoy zd~12X#QbZaqJGne?b+c-Gl?fD)%G;V4BX_7i+tVn@TZ7~S(AgoTI?>l>Ki@hO1@>c zKtZWJ={M!;FNU1^|57+i&&2cbxpVE;$ZpStua~L#O4T|x0xr{Qp{=O`zOC1wtdcEa z-12-r=lcIp^_5XowOiXD2qN9x-Q8>&q&ozpyQRBD8l-zu(j~F!?hffjkZz>&T|VbL z=NsQ2{xJrux$b+-dDZ+Am zC*u3uMV0cO$2)MOfX^)NmR~$}I=fxM+z(90iU2rrr~sk96aj~2A-TAJ9a(Rnhsiaw z=QWQVluk*@QDL&NR8yQ7*qZt3z9ku$C1tgqcCv#1!qHmVK|R;ZwKXcPWkSujzQL(( zwj``0h&2KFz|=o+<+|y%=JOX5tV@_%A?8iiOx2GLS%bJl-aLyYE1e6Owk^EMM`kCj z>8$LHeQOf|uIL|6_#0a_AM1Yb(FjPTzm;8BTIH?ipFsTz?`{~FqcE~uvdKYjI5Mne zk?D{nu+F#``E3yQh;qio;g)UI=kKVNW z|2la-czkH+v!x~myD%JV$tlf6FRF+qLgHSQORVS9W(evQ@R@P#lwL~LNKL%)E1#(P zJrqw_QdNZ$R~T;d<%b^BM6ho(BRrb;Yr*P?nxlMaB<8Rx~na41dhZe%OcMS37nGHvL03Xd( zS?9y|`C$`jRYYv`lq%{Fcn1x&Y8hI$i&RL7W3-E{8x*emW^LDKF zo0w1lD{pN)>?hY+Jxm{OPMV%BM-^98)|5sXs76Ld*G6T>J~e$NC-L4VuQ2R1SlkD@ z&Fh#DkKBFF0LKYkhJR$?cRx-`ZLfNdA*imd?zGj15Dy}tqiT}7NI^iMw@S1Js6hq2 z@5}~!B}(cNej#a(51-gsEa7!&i}Jc{_3zunI%8)LXq!c+Ufj-p%`)HKf)WZsKGBwa zMke6EJ`6>DSan8ZU}R9?@@ZIbjyxC9Dny`2U_T0quw_tq#N+pASd8m5oeL1zYCc`O z&XKsjkM0ce*W32b4C(Z1#D{NW?LD4b-^iS{P@jk~NghjLi_9#8p1NKr+TN|mT+KO4 z`PG=4pZ_M{;Wp=da^)q0GjY;2M>*!1#MiG=Roh_BHjHaA@jP zBP;X4W%{nuumh=>1>b)8gNb+~pK|nGs4vSTA%D2yO^~$JW9zB+n-8NSDqg?eDcK7I z!uE@DKZjY`Pj(%?^gBcy*4B#O3S5dfj3E&>;x8_umMCa+HVO?Sq6!5PNQj8Pf^?r7 zEB*DBZ=U5@r8TDsJ#AFpR$VtaWagfglY@vw&b*{W7vzEQ&QXsgX~=ffg^Xc3IkzVXd>I-H;EEr=H^5%n$%s!Wes_q(Y^pxKgLVv@sD#DMs!Hnv<$jAB2 z*4NOtI>=2&++?N8k0j1K$;yY&JAU%ZDxH;;1%hD88=#Ag<(^PP|W%V5S>#K*=w_np{r0b%^8B->_A;njvpu zZ99Jh%pstQJK(1jaIf`m1CyrO*oNmea$#4K)iedUHwFX(p>ay6_$({x#z)jMpk=r+ zlg7M|IN6`)3y z)^L5=L7xc0o=%4fSMI7-zE-t>g4g9uY^ZMj1$Xh(UaNe*h16B0F0>=+MmFWmSpAcj zB+z55CO!CrvPIM6MSHgC4;zgZ!Ob2SO-jK<_?v2Ulj3+fDblOF7xT27+J)?2OS3M( zU{0gb^4+z2g*%mU9Dk46{U<1+BmSG{9HO0D-XHCDZW*x}dCimh96> zaxeP&S7BE9&{wLHU>8=(yZ>=)54~g@?BU;3@O4ncja5d@XcPqKuC|1lA(#Hzb(PK=4N#NgDHnN@KV7ppEq4JKs*UDbf z0l#ni&4AD0I3u*m`9RVq>9rg-FsVmE8do368Z}5;b`0%DYc}q)$1gFqcc++FM`!H?pQ;EZ?2ONiE>_=X#>A)f5cAL?Soj+yJgI!K3|@uSma9S#So=`^??$ zj?0&teg)^`U2dbs?+Q5X`w%s4`7yojFm3j~+3_%XvLAj)Wh#h0eS`{gXIAw;ZJdNZ z)iT2a#UeLh`c2izrv^^?0XLL3a6|2%VebhFZWFJ}HBHEnD&J>Q4CDy4Nq4ufF*m z@bss81>SKz9qNyt+fdm8ivAg)o99_YYH-3Mdh4{`u)d52Enqxuep5P68r1#_w%}Fb zX&4w&+V+bw*2U-;;HF$@gmD*)RGP6t+;f;WuTVE!sM?36&$TsJXUqM}&35^L%y#g%`7pi)eQ(&jML*Nn7IYmTB|Mu5aoiE-s4swyS zFL4HxHHnfF!0*YCG zzs(qyE5~Ev^wq74sU}_YD;=?WErkQG)w#9bgK#@eYC2arWJhlb0JTlgl!{)I6bzhE zNI7vqaaA75ZFL9f^@KOOhX8lpM^yU{E2JQIqF)bdkqPx@*&VMKt;DR_nt4;ZCq3lw ze@K>A1Mj?D_I$gza_QPhCJJ_ukjfHEFa#s&)xk(BAakGdhm-{ggTdYE6`oMn90fEp z^N5GkVYfrwYUGBlBMX!>b|P8iX})zzf&WO+xm`do3}4RFbfkAKekiaeJH;43zRz<@$-Lo2c}*qB{OqU#vl@1|s-HgX)$ zz9{?2(*lmpty&;NspoL6{NlV96){Q=13<7Yq}%RyGd%%BW=*Sct;Nmd z5kBBND5c|R)bItPre>EyczZAo*Tl-tuYHkMEIl7%1Qvo}P2ZaIt)cawspLyXqBc&k z+s_KSVT~yuKwJ^%?=NEqTLru{30gtleU_a5^aj<;^yq-< z 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 diff --git a/bitmart/回测图表_交互式.html b/bitmart/回测图表_交互式.html deleted file mode 100644 index b67088e..0000000 --- a/bitmart/回测图表_交互式.html +++ /dev/null @@ -1,3888 +0,0 @@ - - - -

-
- - \ No newline at end of file diff --git a/bitmart/均线回归.py b/bitmart/均线回归.py deleted file mode 100644 index 9616cce..0000000 --- a/bitmart/均线回归.py +++ /dev/null @@ -1,866 +0,0 @@ -import os -import time -import uuid -import datetime -from dataclasses import dataclass - -from tqdm import tqdm -from loguru import logger - -from bitmart.api_contract import APIContract -from bitmart.lib.cloud_exceptions import APIException - -from 交易.tools import send_dingtalk_message - - -@dataclass -class StrategyConfig: - # ============================= - # 1m | ETH 永续 | 控止损≤5/日 - # ============================= - - # ===== 合约 ===== - contract_symbol: str = "ETHUSDT" - open_type: str = "cross" - leverage: str = "30" - - # ===== K线与指标 ===== - step_min: int = 1 - lookback_min: int = 240 - ema_len: int = 36 - atr_len: int = 14 - - # ========================================================= - # ✅ 自动阈值:ATR/Price 分位数基准(更稳,不被短时噪声带跑) - # ========================================================= - vol_baseline_window: int = 60 - vol_baseline_quantile: float = 0.65 - vol_scale_min: float = 0.80 - vol_scale_max: float = 1.60 - - # ✅ baseline 每 60 秒刷新一次(体感更明显、也省CPU) - base_ratio_refresh_sec: int = 60 - - # ========================================================= - # ✅ 动态 floor(方案一) - # floor = clamp(min, base_k * base_ratio, max) - # 目的:跟着典型波动变,过滤小噪声;tp/sl 也随环境自适应 - # ========================================================= - # entry_dev_floor 动态 - entry_dev_floor_min: float = 0.0012 # 0.12% - entry_dev_floor_max: float = 0.0030 # 0.30%(可按你偏好调) - entry_dev_floor_base_k: float = 1.10 # entry_floor = 1.10 * base_ratio - - # tp_floor 动态 - tp_floor_min: float = 0.0006 # 0.06% - tp_floor_max: float = 0.0020 # 0.20% - tp_floor_base_k: float = 0.55 # tp_floor = 0.55 * base_ratio(止盈别太大,1m回归更实际) - - # sl_floor 动态 - sl_floor_min: float = 0.0018 # 0.18% - sl_floor_max: float = 0.0060 # 0.60% - sl_floor_base_k: float = 1.35 # sl_floor = 1.35 * base_ratio(ETH 1m 插针多,止损下限可更稳) - - # ========================================================= - # ✅ 动态阈值倍率(仍然保留你原来思路) - # ========================================================= - entry_k: float = 1.45 - tp_k: float = 0.65 - sl_k: float = 1.05 - - # ===== 时间/冷却 ===== - max_hold_sec: int = 75 - cooldown_sec_after_exit: int = 20 - - # ===== 下单/仓位 ===== - risk_percent: float = 0.004 - min_size: int = 1 - max_size: int = 5000 - - # ===== 日内风控 ===== - daily_loss_limit: float = 0.02 - daily_profit_cap: float = 0.01 - - # ===== 危险模式过滤 ===== - atr_ratio_kill: float = 0.0038 - big_body_kill: float = 0.010 - - # ===== 轮询节奏 ===== - klines_refresh_sec: int = 10 - tick_refresh_sec: int = 1 - status_notify_sec: int = 60 - - # ========================================================= - # ✅ 止损后同向入场加门槛(但不禁止同向重入) - # ========================================================= - reentry_penalty_mult: float = 1.55 - reentry_penalty_max_sec: int = 180 - reset_band_k: float = 0.45 - reset_band_floor: float = 0.0006 - - # ========================================================= - # ✅ 止损后同方向 SL 放宽幅度与"止损时 vol_scale"联动 - # ========================================================= - post_sl_sl_max_sec: int = 90 - post_sl_mult_min: float = 1.02 - post_sl_mult_max: float = 1.16 - post_sl_vol_alpha: float = 0.20 - - -class BitmartFuturesMeanReversionBot: - def __init__(self, cfg: StrategyConfig): - self.cfg = cfg - - self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" - self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - self.memo = "合约交易" - - if not self.api_key or not self.secret_key: - raise RuntimeError("请先设置环境变量 BITMART_API_KEY / BITMART_SECRET_KEY / BITMART_MEMO(可选)") - - self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15)) - - # 持仓状态: -1 空, 0 无, 1 多 - self.pos = 0 - self.entry_price = None - self.entry_ts = None - self.last_exit_ts = 0 - - # 日内权益基准 - self.day_start_equity = None - self.trading_enabled = True - self.day_tag = datetime.date.today() - - # 缓存 - self._klines_cache = None - self._klines_cache_ts = 0 - self._last_status_notify_ts = 0 - - # ✅ base_ratio 缓存 - self._base_ratio_cached = 0.0015 # 初始化默认值 0.15% - self._base_ratio_ts = 0.0 - - # ✅ 止损后"同向入场加门槛"状态 - self.last_sl_dir = 0 # 1=多止损,-1=空止损,0=无 - self.last_sl_ts = 0.0 - - # ✅ 止损后"同方向 SL 联动放宽"状态 - self.post_sl_dir = 0 - self.post_sl_ts = 0.0 - self.post_sl_vol_scale = 1.0 # 记录止损时的 vol_scale - - self.pbar = tqdm(total=60, desc="运行中(秒)", ncols=90) - - logger.info(f"初始化完成,基准波动率默认值: {self._base_ratio_cached * 100:.4f}%") - - # ----------------- 通用工具 ----------------- - def ding(self, msg, error=False): - prefix = "❌bitmart:" if error else "🔔bitmart:" - if error: - for _ in range(3): - send_dingtalk_message(f"{prefix}{msg}") - else: - send_dingtalk_message(f"{prefix}{msg}") - - def set_leverage(self) -> bool: - try: - resp = self.contractAPI.post_submit_leverage( - contract_symbol=self.cfg.contract_symbol, - leverage=self.cfg.leverage, - open_type=self.cfg.open_type - )[0] - if resp.get("code") == 1000: - logger.success(f"设置杠杆成功:{self.cfg.open_type} + {self.cfg.leverage}x") - return True - logger.error(f"设置杠杆失败: {resp}") - self.ding(f"设置杠杆失败: {resp}", error=True) - return False - except Exception as e: - logger.error(f"设置杠杆异常: {e}") - self.ding(f"设置杠杆异常: {e}", error=True) - return False - - # ----------------- 行情/指标 ----------------- - def get_klines_cached(self): - now = time.time() - if self._klines_cache is not None and (now - self._klines_cache_ts) < self.cfg.klines_refresh_sec: - return self._klines_cache - - kl = self.get_klines() - if kl: - self._klines_cache = kl - self._klines_cache_ts = now - return self._klines_cache - - def get_klines(self): - try: - end_time = int(time.time()) - start_time = end_time - 60 * self.cfg.lookback_min - - resp = self.contractAPI.get_kline( - contract_symbol=self.cfg.contract_symbol, - step=self.cfg.step_min, - start_time=start_time, - end_time=end_time - )[0] - - if resp.get("code") != 1000: - logger.error(f"获取K线失败: {resp}") - return None - - data = resp.get("data", []) - formatted = [] - for k in data: - formatted.append({ - "id": int(k["timestamp"]), - "open": float(k["open_price"]), - "high": float(k["high_price"]), - "low": float(k["low_price"]), - "close": float(k["close_price"]), - }) - formatted.sort(key=lambda x: x["id"]) - return formatted - except Exception as e: - logger.error(f"获取K线异常: {e}") - self.ding(f"获取K线异常: {e}", error=True) - return None - - def get_last_price(self, fallback_close: float) -> float: - try: - if hasattr(self.contractAPI, "get_contract_details"): - r = self.contractAPI.get_contract_details(contract_symbol=self.cfg.contract_symbol)[0] - d = r.get("data") if isinstance(r, dict) else None - if isinstance(d, dict): - for key in ("last_price", "mark_price", "index_price"): - if key in d and d[key] is not None: - return float(d[key]) - - if hasattr(self.contractAPI, "get_ticker"): - r = self.contractAPI.get_ticker(contract_symbol=self.cfg.contract_symbol)[0] - d = r.get("data") if isinstance(r, dict) else None - if isinstance(d, dict): - for key in ("last_price", "price", "last", "close"): - if key in d and d[key] is not None: - return float(d[key]) - except Exception: - pass - - return float(fallback_close) - - @staticmethod - def ema(values, n: int) -> float: - k = 2 / (n + 1) - e = values[0] - for v in values[1:]: - e = v * k + e * (1 - k) - return e - - @staticmethod - def atr(klines, n: int) -> float: - if len(klines) < n + 1: - return 0.0 - trs = [] - for i in range(-n, 0): - cur = klines[i] - prev = klines[i - 1] - tr = max( - cur["high"] - cur["low"], - abs(cur["high"] - prev["close"]), - abs(cur["low"] - prev["close"]), - ) - trs.append(tr) - return sum(trs) / len(trs) - - def is_danger_market(self, klines, price: float) -> bool: - last = klines[-1] - body = abs(last["close"] - last["open"]) / last["open"] if last["open"] else 0.0 - if body >= self.cfg.big_body_kill: - return True - - a = self.atr(klines, self.cfg.atr_len) - atr_ratio = (a / price) if price > 0 else 0.0 - if atr_ratio >= self.cfg.atr_ratio_kill: - return True - - return False - - def atr_ratio_baseline(self, klines) -> float: - """简化版ATR基准计算""" - window = min(self.cfg.vol_baseline_window, len(klines) - self.cfg.atr_len - 1) - if window <= 10: # 数据太少 - logger.warning(f"数据不足计算基准: {len(klines)}根K线") - return 0.0 - - ratios = [] - - # 简化计算:每隔3根K线计算一个ATR比率(减少计算量) - step = 3 - for i in range(-window, 0, step): - if len(klines) + i < self.cfg.atr_len + 1: - continue - - # 计算当前位置的ATR - start_idx = len(klines) + i - self.cfg.atr_len - end_idx = len(klines) + i - - if start_idx < 0 or end_idx <= start_idx: - continue - - sub_klines = klines[start_idx:end_idx] - - # 确保有足够数据计算ATR - if len(sub_klines) >= self.cfg.atr_len + 1: - a = self.atr(sub_klines, self.cfg.atr_len) - price = klines[end_idx - 1]["close"] - - if a > 0 and price > 0: - ratio = a / price - if 0.0001 < ratio < 0.01: # 过滤异常值 - ratios.append(ratio) - - if len(ratios) < 5: # 样本太少 - # 尝试直接使用整个数据计算一个ATR比率 - a = self.atr(klines[-60:], self.cfg.atr_len) # 使用最近60根K线 - price = klines[-1]["close"] - if a > 0 and price > 0: - baseline = a / price - logger.debug(f"使用全量数据计算基准: {baseline * 100:.4f}%") - return baseline - else: - return 0.0 - - # 计算分位数 - ratios.sort() - idx = min(len(ratios) - 1, - max(0, int(self.cfg.vol_baseline_quantile * (len(ratios) - 1)))) - baseline = ratios[idx] - - logger.debug(f"基准计算: 样本数={len(ratios)}, 基准={baseline * 100:.4f}%, " - f"范围=[{ratios[0] * 100:.4f}%, {ratios[-1] * 100:.4f}%]") - - return baseline - - def get_base_ratio_cached(self, klines) -> float: - """获取缓存的基准波动率,定期刷新""" - now = time.time() - refresh_sec = self.cfg.base_ratio_refresh_sec - - if (self._base_ratio_cached is None or - (now - self._base_ratio_ts) >= refresh_sec): - - # 使用简单版本的基准计算 - baseline = self.atr_ratio_baseline(klines) - - if baseline > 0.0001: # 大于0.01%才认为是有效值 - self._base_ratio_cached = baseline - self._base_ratio_ts = now - logger.info(f"基准波动率更新: {baseline * 100:.4f}%") - else: - # 使用基于价格的动态默认值 - current_price = klines[-1]["close"] if klines else 3000 - # ETH价格越高,基准波动率越小(百分比) - if current_price > 4000: - default_baseline = 0.0010 # 0.10% - elif current_price > 3500: - default_baseline = 0.0012 # 0.12% - elif current_price > 3000: - default_baseline = 0.0015 # 0.15% - elif current_price > 2500: - default_baseline = 0.0018 # 0.18% - else: - default_baseline = 0.0020 # 0.20% - - self._base_ratio_cached = default_baseline - self._base_ratio_ts = now - logger.warning(f"使用价格动态默认基准: {default_baseline * 100:.4f}% " - f"(价格=${current_price:.0f})") - - return self._base_ratio_cached - - @staticmethod - def _clamp(x: float, lo: float, hi: float) -> float: - """限制数值在指定范围内""" - return max(lo, min(hi, x)) - - def dynamic_thresholds(self, atr_ratio: float, base_ratio: float): - """ - ✅ entry/tp/sl 全部动态(修复版): - - vol_scale:atr_ratio/base_ratio 限幅 - - floor:方案一 (floor = clamp(min, k*base_ratio, max)) - - 最终阈值:max(floor, k * vol_scale * atr_ratio) - """ - # 1) 检查输入有效性 - if atr_ratio <= 0: - logger.warning(f"ATR比率异常: {atr_ratio}") - atr_ratio = 0.001 # 默认值 0.1% - - # 2) 如果base_ratio太小或无效,使用调整后的atr_ratio - if base_ratio < 0.0005: # 小于0.05%视为无效 - base_ratio = max(0.001, atr_ratio * 1.2) # 比当前ATR比率稍大 - logger.debug(f"基准太小,使用调整后的atr_ratio: {base_ratio * 100:.4f}%") - - # 3) vol_scale计算 - if base_ratio > 0: - raw_scale = atr_ratio / base_ratio - vol_scale = self._clamp(raw_scale, self.cfg.vol_scale_min, self.cfg.vol_scale_max) - logger.debug( - f"vol_scale: {raw_scale:.2f} → {vol_scale:.2f} (atr={atr_ratio * 100:.3f}%, base={base_ratio * 100:.3f}%)") - else: - vol_scale = 1.0 - logger.warning(f"基准无效,使用默认vol_scale=1.0") - - # 4) 动态floor计算 - # Entry floor - entry_floor_raw = self.cfg.entry_dev_floor_base_k * base_ratio - entry_floor = self._clamp( - entry_floor_raw, - self.cfg.entry_dev_floor_min, - self.cfg.entry_dev_floor_max, - ) - - # TP floor - tp_floor_raw = self.cfg.tp_floor_base_k * base_ratio - tp_floor = self._clamp( - tp_floor_raw, - self.cfg.tp_floor_min, - self.cfg.tp_floor_max, - ) - - # SL floor - sl_floor_raw = self.cfg.sl_floor_base_k * base_ratio - sl_floor = self._clamp( - sl_floor_raw, - self.cfg.sl_floor_min, - self.cfg.sl_floor_max, - ) - - # 5) 最终阈值计算 - entry_dev_atr_part = self.cfg.entry_k * vol_scale * atr_ratio - entry_dev = max(entry_floor, entry_dev_atr_part) - - tp_atr_part = self.cfg.tp_k * vol_scale * atr_ratio - tp = max(tp_floor, tp_atr_part) - - sl_atr_part = self.cfg.sl_k * vol_scale * atr_ratio - sl = max(sl_floor, sl_atr_part) - - # 6) 确保entry_dev不会太小 - entry_dev = max(entry_dev, self.cfg.entry_dev_floor_min) - - # 7) 输出详细信息 - logger.info( - f"动态阈值: atr={atr_ratio * 100:.4f}%, base={base_ratio * 100:.4f}%, " - f"vol_scale={vol_scale:.2f}, floor={entry_floor * 100:.4f}%, " - f"atr_part={entry_dev_atr_part * 100:.4f}%, 最终entry_dev={entry_dev * 100:.4f}%" - ) - - return entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor - - # ----------------- 账户/仓位 ----------------- - def get_assets_available(self) -> float: - try: - resp = self.contractAPI.get_assets_detail()[0] - if resp.get("code") != 1000: - return 0.0 - data = resp.get("data") - if isinstance(data, dict): - return float(data.get("available_balance", 0)) - if isinstance(data, list): - for asset in data: - if asset.get("currency") == "USDT": - return float(asset.get("available_balance", 0)) - return 0.0 - except Exception as e: - logger.error(f"余额查询异常: {e}") - return 0.0 - - def get_position_status(self) -> bool: - try: - resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0] - if resp.get("code") != 1000: - return False - - positions = resp.get("data", []) - if not positions: - self.pos = 0 - return True - - p = positions[0] - self.pos = 1 if p["position_type"] == 1 else -1 - return True - except Exception as e: - logger.error(f"持仓查询异常: {e}") - self.ding(f"持仓查询异常: {e}", error=True) - return False - - def get_equity_proxy(self) -> float: - return self.get_assets_available() - - def refresh_daily_baseline(self): - today = datetime.date.today() - if today != self.day_tag: - self.day_tag = today - self.day_start_equity = None - self.trading_enabled = True - self.ding(f"新的一天({today}):重置日内风控基准") - - def risk_kill_switch(self): - self.refresh_daily_baseline() - equity = self.get_equity_proxy() - if equity <= 0: - return - - if self.day_start_equity is None: - self.day_start_equity = equity - logger.info(f"日内权益基准设定:{equity:.2f} USDT") - return - - pnl = (equity - self.day_start_equity) / self.day_start_equity - if pnl <= -self.cfg.daily_loss_limit: - self.trading_enabled = False - self.ding(f"触发日止损:{pnl * 100:.2f}% -> 停机", error=True) - - if pnl >= self.cfg.daily_profit_cap: - self.trading_enabled = False - self.ding(f"达到日盈利封顶:{pnl * 100:.2f}% -> 停机") - - # ----------------- 下单 ----------------- - def calculate_size(self, price: float) -> int: - bal = self.get_assets_available() - if bal < 10: - return 0 - - margin = bal * self.cfg.risk_percent - lev = int(self.cfg.leverage) - - # ⚠️ 沿用你的原假设:1张≈0.001ETH - size = int((margin * lev) / (price * 0.001)) - size = max(self.cfg.min_size, size) - size = min(self.cfg.max_size, size) - return size - - def place_market_order(self, side: int, size: int) -> bool: - if size <= 0: - return False - - client_order_id = f"mr_{int(time.time())}_{uuid.uuid4().hex[:8]}" - try: - resp = self.contractAPI.post_submit_order( - contract_symbol=self.cfg.contract_symbol, - client_order_id=client_order_id, - side=side, - mode=1, - type="market", - leverage=self.cfg.leverage, - open_type=self.cfg.open_type, - size=size - )[0] - - logger.info(f"order_resp: {resp}") - - if resp.get("code") == 1000: - return True - - self.ding(f"下单失败: {resp}", error=True) - return False - - except APIException as e: - logger.error(f"API下单异常: {e}") - self.ding(f"API下单异常: {e}", error=True) - return False - - except Exception as e: - logger.error(f"下单未知异常: {e}") - self.ding(f"下单未知异常: {e}", error=True) - return False - - def close_position_all(self): - if self.pos == 1: - ok = self.place_market_order(3, 999999) - if ok: - self.pos = 0 - elif self.pos == -1: - ok = self.place_market_order(2, 999999) - if ok: - self.pos = 0 - - # ----------------- 止损后机制 ----------------- - def _reentry_penalty_active(self, dev: float, entry_dev: float) -> bool: - """检查是否需要应用重新入场惩罚""" - if self.last_sl_dir == 0: - return False - - if (time.time() - self.last_sl_ts) > self.cfg.reentry_penalty_max_sec: - self.last_sl_dir = 0 - return False - - reset_band = max(self.cfg.reset_band_floor, self.cfg.reset_band_k * entry_dev) - if abs(dev) <= reset_band: - self.last_sl_dir = 0 - return False - - return True - - def _post_sl_dynamic_mult(self) -> float: - """计算止损后SL放宽倍数""" - if self.post_sl_dir == 0: - return 1.0 - - if (time.time() - self.post_sl_ts) > self.cfg.post_sl_sl_max_sec: - self.post_sl_dir = 0 - self.post_sl_vol_scale = 1.0 - return 1.0 - - raw = 1.0 + self.cfg.post_sl_vol_alpha * (self.post_sl_vol_scale - 1.0) - raw = max(1.0, raw) # 不缩小止损,只放宽 - return max(self.cfg.post_sl_mult_min, min(self.cfg.post_sl_mult_max, raw)) - - # ----------------- 交易逻辑 ----------------- - def in_cooldown(self) -> bool: - """检查是否在冷却期内""" - return (time.time() - self.last_exit_ts) < self.cfg.cooldown_sec_after_exit - - def maybe_enter(self, price: float, ema_value: float, entry_dev: float): - """检查并执行入场""" - if self.pos != 0: - return - if self.in_cooldown(): - return - - dev = (price - ema_value) / ema_value if ema_value else 0.0 - size = self.calculate_size(price) - if size <= 0: - return - - penalty_active = self._reentry_penalty_active(dev, entry_dev) - - long_th = -entry_dev - short_th = entry_dev - - if penalty_active: - if self.last_sl_dir == 1: - long_th = -entry_dev * self.cfg.reentry_penalty_mult - logger.info( - f"多头止损后惩罚生效: 入场阈值从 {long_th * 100:.3f}% 调整为 {(-entry_dev * self.cfg.reentry_penalty_mult) * 100:.3f}%") - elif self.last_sl_dir == -1: - short_th = entry_dev * self.cfg.reentry_penalty_mult - logger.info( - f"空头止损后惩罚生效: 入场阈值从 {short_th * 100:.3f}% 调整为 {(entry_dev * self.cfg.reentry_penalty_mult) * 100:.3f}%") - - logger.info( - f"入场检查: price={price:.2f}, ema={ema_value:.2f}, dev={dev * 100:.3f}% " - f"(entry_dev={entry_dev * 100:.3f}%, long_th={long_th * 100:.3f}%, short_th={short_th * 100:.3f}%) " - f"size={size}, penalty={penalty_active}, last_sl_dir={self.last_sl_dir}" - ) - - if dev <= long_th: - if self.place_market_order(1, size): - self.pos = 1 - self.entry_price = price - self.entry_ts = time.time() - self.ding(f"✅开多:dev={dev * 100:.3f}% size={size} entry={price:.2f}") - - elif dev >= short_th: - if self.place_market_order(4, size): - self.pos = -1 - self.entry_price = price - self.entry_ts = time.time() - self.ding(f"✅开空:dev={dev * 100:.3f}% size={size} entry={price:.2f}") - - def maybe_exit(self, price: float, tp: float, sl: float, vol_scale: float): - """检查并执行出场""" - if self.pos == 0 or self.entry_price is None or self.entry_ts is None: - return - - hold = time.time() - self.entry_ts - - if self.pos == 1: - pnl = (price - self.entry_price) / self.entry_price - else: - pnl = (self.entry_price - price) / self.entry_price - - sl_mult = 1.0 - if self.post_sl_dir == self.pos and self.post_sl_dir != 0: - sl_mult = self._post_sl_dynamic_mult() - effective_sl = sl * sl_mult - - if pnl >= tp: - self.close_position_all() - self.ding(f"🎯止盈:pnl={pnl * 100:.3f}% price={price:.2f} tp={tp * 100:.3f}%") - self.entry_price, self.entry_ts = None, None - self.last_exit_ts = time.time() - - elif pnl <= -effective_sl: - sl_dir = self.pos - - self.close_position_all() - self.ding( - f"🛑止损:pnl={pnl * 100:.3f}% price={price:.2f} " - f"sl={sl * 100:.3f}% effective_sl={effective_sl * 100:.3f}%(×{sl_mult:.2f})", - error=True - ) - - self.last_sl_dir = sl_dir - self.last_sl_ts = time.time() - - self.post_sl_dir = sl_dir - self.post_sl_ts = time.time() - self.post_sl_vol_scale = float(vol_scale) - - self.entry_price, self.entry_ts = None, None - self.last_exit_ts = time.time() - - elif hold >= self.cfg.max_hold_sec: - self.close_position_all() - self.ding(f"⏱超时:hold={int(hold)}s pnl={pnl * 100:.3f}% price={price:.2f}") - self.entry_price, self.entry_ts = None, None - self.last_exit_ts = time.time() - - def notify_status_throttled(self, price: float, ema_value: float, dev: float, bal: float, - atr_ratio: float, base_ratio: float, vol_scale: float, - entry_dev: float, tp: float, sl: float, - entry_floor: float, tp_floor: float, sl_floor: float): - """限频状态通知""" - now = time.time() - if (now - self._last_status_notify_ts) < self.cfg.status_notify_sec: - return - self._last_status_notify_ts = now - - direction_str = "多" if self.pos == 1 else ("空" if self.pos == -1 else "无") - penalty_active = self._reentry_penalty_active(dev, entry_dev) - - sl_mult = 1.0 - if self.pos != 0 and self.post_sl_dir == self.pos: - sl_mult = self._post_sl_dynamic_mult() - - base_age = int(now - self._base_ratio_ts) if self._base_ratio_ts else -1 - - msg = ( - f"【BitMart {self.cfg.contract_symbol}|1m均值回归(动态阈值)】\n" - f"📊 状态:{direction_str}\n" - f"💰 现价:{price:.2f} | EMA{self.cfg.ema_len}:{ema_value:.2f}\n" - f"📈 偏离:{dev * 100:.3f}% (入场阈值:±{entry_dev * 100:.3f}%)\n" - f"🌊 波动率:ATR比={atr_ratio * 100:.3f}% | 基准={base_ratio * 100:.3f}% | 缩放={vol_scale:.2f}\n" - f"🎯 动态Floor:入场={entry_floor * 100:.3f}% | 止盈={tp_floor * 100:.3f}% | 止损={sl_floor * 100:.3f}%\n" - f"💰 止盈/止损:{tp * 100:.3f}% / {sl * 100:.3f}% (盈亏比:{tp / sl:.2f})\n" - f"🔄 基准刷新:{self.cfg.base_ratio_refresh_sec}s (已过={base_age}s)\n" - f"⚠️ 止损同向加门槛:{'开启' if penalty_active else '关闭'} (方向={self.last_sl_dir})\n" - f"💳 可用余额:{bal:.2f} USDT | 杠杆:{self.cfg.leverage}x\n" - f"⏱️ 持仓限制:{self.cfg.max_hold_sec}s | 冷却:{self.cfg.cooldown_sec_after_exit}s" - ) - self.ding(msg) - - def action(self): - """主循环""" - if not self.set_leverage(): - self.ding("杠杆设置失败,停止运行", error=True) - return - - while True: - now_dt = datetime.datetime.now() - self.pbar.n = now_dt.second - self.pbar.refresh() - - # 1. 获取K线数据 - klines = self.get_klines_cached() - if not klines or len(klines) < (self.cfg.ema_len + 5): - logger.warning("K线数据不足,等待...") - time.sleep(1) - continue - - # 2. 计算技术指标 - last_k = klines[-1] - closes = [k["close"] for k in klines[-(self.cfg.ema_len + 1):]] - ema_value = self.ema(closes, self.cfg.ema_len) - - price = self.get_last_price(fallback_close=float(last_k["close"])) - dev = (price - ema_value) / ema_value if ema_value else 0.0 - - # 3. 计算波动率相关指标 - a = self.atr(klines, self.cfg.atr_len) - atr_ratio = (a / price) if price > 0 else 0.0 - - base_ratio = self.get_base_ratio_cached(klines) - - # 4. 计算动态阈值 - entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor = self.dynamic_thresholds( - atr_ratio, base_ratio - ) - - # 记录调试信息 - logger.debug( - f"循环数据: price={price:.2f}, ema={ema_value:.2f}, dev={dev * 100:.3f}%, " - f"atr_ratio={atr_ratio * 100:.3f}%, base_ratio={base_ratio * 100:.3f}%, " - f"entry_dev={entry_dev * 100:.3f}%" - ) - - # 5. 风控检查 - self.risk_kill_switch() - - # 6. 获取持仓状态 - if not self.get_position_status(): - time.sleep(1) - continue - - # 7. 检查交易是否启用 - if not self.trading_enabled: - if self.pos != 0: - self.close_position_all() - logger.warning("交易被禁用(风控触发),等待...") - time.sleep(5) - continue - - # 8. 检查危险市场 - if self.is_danger_market(klines, price): - logger.warning("危险模式:高波动/大实体K,暂停开仓") - self.maybe_exit(price, tp, sl, vol_scale) - time.sleep(self.cfg.tick_refresh_sec) - continue - - # 9. 执行交易逻辑 - self.maybe_exit(price, tp, sl, vol_scale) - self.maybe_enter(price, ema_value, entry_dev) - - # 10. 状态通知 - bal = self.get_assets_available() - self.notify_status_throttled( - price, ema_value, dev, bal, - atr_ratio, base_ratio, vol_scale, - entry_dev, tp, sl, - entry_floor, tp_floor, sl_floor - ) - - time.sleep(self.cfg.tick_refresh_sec) - - -if __name__ == "__main__": - """ - Windows PowerShell: - setx BITMART_API_KEY "你的key" - setx BITMART_SECRET_KEY "你的secret" - setx BITMART_MEMO "合约交易" - 重新打开终端再运行。 - - Linux/macOS: - export BITMART_API_KEY="你的key" - export BITMART_SECRET_KEY="你的secret" - export BITMART_MEMO "合约交易" - """ - cfg = StrategyConfig() - bot = BitmartFuturesMeanReversionBot(cfg) - - # 设置日志级别为INFO以便查看详细计算过程 - logger.remove() - logger.add(lambda msg: tqdm.write(msg, end=""), level="INFO") - - try: - bot.action() - except KeyboardInterrupt: - logger.info("程序被用户中断") - bot.ding("🤖 策略已手动停止") - except Exception as e: - logger.error(f"程序异常退出: {e}") - bot.ding(f"❌ 策略异常退出: {e}", error=True) - raise - -# 目前动态计算阀值的速度是多少 \ No newline at end of file diff --git a/bitmart/均线自动化开单.py b/bitmart/均线自动化开单.py deleted file mode 100644 index 3e896f5..0000000 --- a/bitmart/均线自动化开单.py +++ /dev/null @@ -1,1165 +0,0 @@ -import time -import datetime -from typing import Tuple, Optional, List -from dataclasses import dataclass, field - -from tqdm import tqdm -from loguru import logger -from bitmart.api_contract import APIContract -from DrissionPage import ChromiumPage, ChromiumOptions - -from bit_tools import openBrowser -from 交易.tools import send_dingtalk_message - - -@dataclass -class StrategyConfig: - """针对90%返佣优化的均值回归策略配置""" - - # ============================= - # ETH 永续 | 1分钟K线 | 90%返佣优化 - # ============================= - - # ===== 合约配置 ===== - contract_symbol: str = "ETHUSDT" - open_type: str = "cross" - leverage: str = "30" - - # ===== K线与指标 ===== - step_min: int = 1 - lookback_min: int = 240 - ema_len: int = 36 - atr_len: int = 14 - - # ===== 动态阈值基准 ===== - vol_baseline_window: int = 60 - vol_baseline_quantile: float = 0.65 - vol_scale_min: float = 0.80 - vol_scale_max: float = 1.60 - base_ratio_refresh_sec: int = 60 - - # ===== 动态floor配置(针对低手续费优化) ===== - entry_dev_floor_min: float = 0.0008 # 0.08%(原0.12%) - entry_dev_floor_max: float = 0.0020 # 0.20%(原0.30%) - entry_dev_floor_base_k: float = 1.10 - - tp_floor_min: float = 0.0004 # 0.04%(原0.06%) - tp_floor_max: float = 0.0015 # 0.15%(原0.20%) - tp_floor_base_k: float = 0.55 - - sl_floor_min: float = 0.0012 # 0.12%(原0.18%) - sl_floor_max: float = 0.0040 # 0.40%(原0.60%) - sl_floor_base_k: float = 1.35 - - # ===== 阈值倍率(针对低手续费优化) ===== - entry_k: float = 1.30 # 原1.45 - tp_k: float = 0.55 # 原0.65 - sl_k: float = 0.90 # 原1.05 - - # ===== 时间/冷却(降低以增加频率) ===== - max_hold_sec: int = 60 # 原75秒 - cooldown_sec_after_exit: int = 5 # 原20秒 - opposite_direction_cooldown: int = 30 # 平仓后同向冷却30秒 - - # ===== 仓位管理 ===== - fixed_margin: float = 10.0 # 固定保证金 - min_size: int = 1 - max_size: int = 10 - max_daily_trades: int = 50 # 每日最大交易次数 - - # ===== 日内风控 ===== - daily_loss_limit: float = 0.02 - daily_profit_cap: float = 0.01 - - # ===== 危险模式过滤 ===== - atr_ratio_kill: float = 0.0038 - big_body_kill: float = 0.010 - - # ===== 轮询节奏 ===== - klines_refresh_sec: int = 10 - tick_refresh_sec: int = 1 - status_notify_sec: int = 60 - - # ===== 止损后机制 ===== - reentry_penalty_mult: float = 1.55 - reentry_penalty_max_sec: int = 180 - reset_band_k: float = 0.45 - reset_band_floor: float = 0.0006 - - post_sl_sl_max_sec: int = 90 - post_sl_mult_min: float = 1.02 - post_sl_mult_max: float = 1.16 - post_sl_vol_alpha: float = 0.20 - - # ===== 正常平仓条件(针对低手续费优化) ===== - normal_exit_threshold: float = 0.0002 # 0.02%(原0.03%) - normal_exit_min_profit: float = 0.0001 # 0.01%(原0.02%) - - # ===== 90%返佣配置 ===== - platform_fee_rate: float = 0.0005 # 平台手续费:开仓价值的万分之五 - rebate_rate: float = 0.90 # 返佣比例:90% - - # ===== 新增:趋势过滤 ===== - use_trend_filter: bool = True - trend_ema_len: int = 50 - trend_threshold: float = 0.0005 # 0.05% - - # ===== 新增:成交量确认 ===== - require_volume_confirmation: bool = True - volume_ma_len: int = 20 - volume_threshold: float = 1.2 - - # ===== 新增:连续亏损控制 ===== - max_consecutive_losses: int = 3 - recovery_wait_sec: int = 60 - - -class BitmartFuturesMeanReversionBot: - def __init__(self, cfg: StrategyConfig, bit_id=None): - self.bit_id = bit_id - self.page: Optional[ChromiumPage] = None - self.cfg = cfg - - self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" - self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - self.memo = "合约交易" - - if not self.api_key or not self.secret_key: - raise RuntimeError("请先设置API密钥") - - self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15)) - - # 持仓状态 - self.pos = 0 # -1 空, 0 无, 1 多 - self.entry_price: Optional[float] = None - self.entry_ts: Optional[float] = None - self.last_exit_ts = 0.0 - - # 新增:记录上次平仓方向 - self.last_exit_direction = 0 # 1=多平, -1=空平 - self.last_exit_price = 0.0 - - # 开仓信息 - self.entry_margin: Optional[float] = None - self.entry_position_value: Optional[float] = None - - # 日内权益基准 - self.day_start_equity: Optional[float] = None - self.trading_enabled = True - self.day_tag = datetime.date.today() - self.today_trade_count = 0 - self.consecutive_losses = 0 - - # 缓存 - self._klines_cache: Optional[List] = None - self._klines_cache_ts = 0.0 - self._last_status_notify_ts = 0.0 - - # 基准波动率缓存 - self._base_ratio_cached = 0.0015 - self._base_ratio_ts = 0.0 - - # 止损后机制状态 - self.last_sl_dir = 0 - self.last_sl_ts = 0.0 - self.post_sl_dir = 0 - self.post_sl_ts = 0.0 - self.post_sl_vol_scale = 1.0 - - self.pbar = tqdm(total=60, desc="运行中(秒)", ncols=90) - - logger.info(f"初始化完成 - 合约:{cfg.contract_symbol}, 杠杆:{cfg.leverage}x, 返佣:{cfg.rebate_rate * 100}%") - - # ----------------- 工具函数 ----------------- - def ding(self, msg: str, error: bool = False): - """发送钉钉通知""" - prefix = "❌bitmart:" if error else "🔔bitmart:" - if error: - for _ in range(1): - send_dingtalk_message(f"{prefix}{msg}") - else: - send_dingtalk_message(f"{prefix}{msg}") - - def set_leverage(self) -> bool: - """设置杠杆""" - try: - resp = self.contractAPI.post_submit_leverage( - contract_symbol=self.cfg.contract_symbol, - leverage=self.cfg.leverage, - open_type=self.cfg.open_type - )[0] - if resp.get("code") == 1000: - logger.success(f"设置杠杆成功:{self.cfg.open_type} + {self.cfg.leverage}x") - return True - logger.error(f"设置杠杆失败: {resp}") - return False - except Exception as e: - logger.error(f"设置杠杆异常: {e}") - return False - - # ----------------- 行情数据 ----------------- - def get_klines_cached(self) -> Optional[List]: - """获取缓存的K线数据""" - now = time.time() - if self._klines_cache is not None and (now - self._klines_cache_ts) < self.cfg.klines_refresh_sec: - return self._klines_cache - - kl = self.get_klines() - if kl: - self._klines_cache = kl - self._klines_cache_ts = now - return self._klines_cache - - def get_klines(self) -> Optional[List]: - """获取K线数据""" - try: - end_time = int(time.time()) - start_time = end_time - 60 * self.cfg.lookback_min - - resp = self.contractAPI.get_kline( - contract_symbol=self.cfg.contract_symbol, - step=self.cfg.step_min, - start_time=start_time, - end_time=end_time - )[0] - - if resp.get("code") != 1000: - logger.error(f"获取K线失败: {resp}") - return None - - data = resp.get("data", []) - formatted = [] - for k in data: - formatted.append({ - "id": int(k["timestamp"]), - "open": float(k["open_price"]), - "high": float(k["high_price"]), - "low": float(k["low_price"]), - "close": float(k["close_price"]), - }) - formatted.sort(key=lambda x: x["id"]) - return formatted - except Exception as e: - logger.error(f"获取K线异常: {e}") - return None - - def get_last_price(self, fallback_close: float) -> float: - """获取最新价格""" - try: - if hasattr(self.contractAPI, "get_contract_details"): - r = self.contractAPI.get_contract_details(contract_symbol=self.cfg.contract_symbol)[0] - d = r.get("data") if isinstance(r, dict) else None - if isinstance(d, dict): - for key in ("last_price", "mark_price", "index_price"): - if key in d and d[key] is not None: - return float(d[key]) - - if hasattr(self.contractAPI, "get_ticker"): - r = self.contractAPI.get_ticker(contract_symbol=self.cfg.contract_symbol)[0] - d = r.get("data") if isinstance(r, dict) else None - if isinstance(d, dict): - for key in ("last_price", "price", "last", "close"): - if key in d and d[key] is not None: - return float(d[key]) - except Exception: - pass - - return float(fallback_close) - - @staticmethod - def ema(values: List[float], n: int) -> float: - """计算EMA""" - k = 2 / (n + 1) - e = values[0] - for v in values[1:]: - e = v * k + e * (1 - k) - return e - - @staticmethod - def atr(klines: List[dict], n: int) -> float: - """计算ATR""" - if len(klines) < n + 1: - return 0.0 - trs = [] - for i in range(-n, 0): - cur = klines[i] - prev = klines[i - 1] - tr = max( - cur["high"] - cur["low"], - abs(cur["high"] - prev["close"]), - abs(cur["low"] - prev["close"]), - ) - trs.append(tr) - return sum(trs) / len(trs) - - # ----------------- 风控检查 ----------------- - def is_danger_market(self, klines: List[dict], price: float) -> bool: - """检查是否危险市场""" - last = klines[-1] - body = abs(last["close"] - last["open"]) / last["open"] if last["open"] else 0.0 - if body >= self.cfg.big_body_kill: - logger.warning(f"大实体K线: {body * 100:.2f}%") - return True - - a = self.atr(klines, self.cfg.atr_len) - atr_ratio = (a / price) if price > 0 else 0.0 - if atr_ratio >= self.cfg.atr_ratio_kill: - logger.warning(f"高ATR比率: {atr_ratio * 100:.2f}%") - return True - - return False - - def check_trend_filter(self, price: float) -> bool: - """趋势过滤器""" - if not self.cfg.use_trend_filter: - return True - - klines = self.get_klines_cached() - if not klines or len(klines) < self.cfg.trend_ema_len: - return True - - try: - closes = [k["close"] for k in klines[-(self.cfg.trend_ema_len + 1):]] - trend_ema = self.ema(closes, self.cfg.trend_ema_len) - trend_dev = (price - trend_ema) / trend_ema if trend_ema else 0.0 - - if abs(trend_dev) > self.cfg.trend_threshold: - logger.debug(f"趋势过滤触发: 偏差={trend_dev * 100:.3f}%, 阈值={self.cfg.trend_threshold * 100:.3f}%") - return False - - return True - except Exception as e: - logger.error(f"趋势过滤异常: {e}") - return True - - # ----------------- 波动率计算 ----------------- - def atr_ratio_baseline(self, klines: List[dict]) -> float: - """计算ATR比率基准""" - window = min(self.cfg.vol_baseline_window, len(klines) - self.cfg.atr_len - 1) - if window <= 10: - logger.warning(f"数据不足计算基准: {len(klines)}根K线") - return 0.0 - - ratios = [] - step = 3 - - for i in range(-window, 0, step): - if len(klines) + i < self.cfg.atr_len + 1: - continue - - start_idx = len(klines) + i - self.cfg.atr_len - end_idx = len(klines) + i - - if start_idx < 0 or end_idx <= start_idx: - continue - - sub_klines = klines[start_idx:end_idx] - if len(sub_klines) >= self.cfg.atr_len + 1: - a = self.atr(sub_klines, self.cfg.atr_len) - price = klines[end_idx - 1]["close"] - if a > 0 and price > 0: - ratio = a / price - if 0.0001 < ratio < 0.01: - ratios.append(ratio) - - if len(ratios) < 5: - a = self.atr(klines[-60:], self.cfg.atr_len) - price = klines[-1]["close"] - if a > 0 and price > 0: - baseline = a / price - logger.debug(f"使用全量数据计算基准: {baseline * 100:.4f}%") - return baseline - else: - return 0.0 - - ratios.sort() - idx = min(len(ratios) - 1, - max(0, int(self.cfg.vol_baseline_quantile * (len(ratios) - 1)))) - baseline = ratios[idx] - - logger.debug(f"基准计算: 样本数={len(ratios)}, 基准={baseline * 100:.4f}%") - return baseline - - def get_base_ratio_cached(self, klines: List[dict]) -> float: - """获取缓存的基准波动率""" - now = time.time() - refresh_sec = self.cfg.base_ratio_refresh_sec - - if (self._base_ratio_cached is None or - (now - self._base_ratio_ts) >= refresh_sec): - - baseline = self.atr_ratio_baseline(klines) - - if baseline > 0.0001: - self._base_ratio_cached = baseline - self._base_ratio_ts = now - logger.info(f"基准波动率更新: {baseline * 100:.4f}%") - else: - current_price = klines[-1]["close"] if klines else 3000 - if current_price > 4000: - default_baseline = 0.0010 - elif current_price > 3500: - default_baseline = 0.0012 - elif current_price > 3000: - default_baseline = 0.0015 - elif current_price > 2500: - default_baseline = 0.0018 - else: - default_baseline = 0.0020 - - self._base_ratio_cached = default_baseline - self._base_ratio_ts = now - logger.warning(f"使用价格动态默认基准: {default_baseline * 100:.4f}%") - - return self._base_ratio_cached - - @staticmethod - def _clamp(x: float, lo: float, hi: float) -> float: - """限制数值在指定范围内""" - return max(lo, min(hi, x)) - - # ----------------- 动态阈值计算 ----------------- - def dynamic_thresholds(self, atr_ratio: float, base_ratio: float) -> Tuple[ - float, float, float, float, float, float, float]: - """计算动态阈值""" - if atr_ratio <= 0: - logger.warning(f"ATR比率异常: {atr_ratio}") - atr_ratio = 0.001 - - if base_ratio < 0.0005: - base_ratio = max(0.001, atr_ratio * 1.2) - logger.debug(f"基准太小,使用调整后的atr_ratio: {base_ratio * 100:.4f}%") - - # vol_scale计算 - if base_ratio > 0: - raw_scale = atr_ratio / base_ratio - vol_scale = self._clamp(raw_scale, self.cfg.vol_scale_min, self.cfg.vol_scale_max) - else: - vol_scale = 1.0 - - # 动态floor计算 - entry_floor_raw = self.cfg.entry_dev_floor_base_k * base_ratio - entry_floor = self._clamp( - entry_floor_raw, - self.cfg.entry_dev_floor_min, - self.cfg.entry_dev_floor_max, - ) - - tp_floor_raw = self.cfg.tp_floor_base_k * base_ratio - tp_floor = self._clamp( - tp_floor_raw, - self.cfg.tp_floor_min, - self.cfg.tp_floor_max, - ) - - sl_floor_raw = self.cfg.sl_floor_base_k * base_ratio - sl_floor = self._clamp( - sl_floor_raw, - self.cfg.sl_floor_min, - self.cfg.sl_floor_max, - ) - - # 最终阈值 - entry_dev_atr_part = self.cfg.entry_k * vol_scale * atr_ratio - entry_dev = max(entry_floor, entry_dev_atr_part) - - tp_atr_part = self.cfg.tp_k * vol_scale * atr_ratio - tp = max(tp_floor, tp_atr_part) - - sl_atr_part = self.cfg.sl_k * vol_scale * atr_ratio - sl = max(sl_floor, sl_atr_part) - - entry_dev = max(entry_dev, self.cfg.entry_dev_floor_min) - - logger.info( - f"动态阈值: entry={entry_dev * 100:.4f}%, tp={tp * 100:.4f}%, sl={sl * 100:.4f}%, " - f"vol_scale={vol_scale:.2f}" - ) - - return entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor - - # ----------------- 账户仓位管理 ----------------- - def get_assets_available(self) -> float: - """获取可用余额""" - try: - resp = self.contractAPI.get_assets_detail()[0] - if resp.get("code") != 1000: - return 0.0 - data = resp.get("data") - if isinstance(data, dict): - return float(data.get("available_balance", 0)) - if isinstance(data, list): - for asset in data: - if asset.get("currency") == "USDT": - return float(asset.get("available_balance", 0)) - return 0.0 - except Exception as e: - logger.error(f"余额查询异常: {e}") - return 0.0 - - def get_position_status(self) -> bool: - """获取持仓状态""" - try: - resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0] - if resp.get("code") != 1000: - return False - - positions = resp.get("data", []) - if not positions: - self.pos = 0 - return True - - p = positions[0] - self.pos = 1 if p["position_type"] == 1 else -1 - return True - except Exception as e: - logger.error(f"持仓查询异常: {e}") - return False - - def get_equity_proxy(self) -> float: - """获取权益""" - return self.get_assets_available() - - def refresh_daily_baseline(self): - """刷新日内基准""" - today = datetime.date.today() - if today != self.day_tag: - self.day_tag = today - self.day_start_equity = None - self.trading_enabled = True - self.today_trade_count = 0 - self.consecutive_losses = 0 - logger.info(f"新的一天({today}):重置日内风控基准") - - def risk_kill_switch(self): - """风险控制开关""" - self.refresh_daily_baseline() - equity = self.get_equity_proxy() - if equity <= 0: - return - - if self.day_start_equity is None: - self.day_start_equity = equity - logger.info(f"日内权益基准设定:{equity:.2f} USDT") - return - - pnl = (equity - self.day_start_equity) / self.day_start_equity - if pnl <= -self.cfg.daily_loss_limit: - self.trading_enabled = False - self.ding(f"触发日止损:{pnl * 100:.2f}% -> 停机", error=True) - - if pnl >= self.cfg.daily_profit_cap: - self.trading_enabled = False - self.ding(f"达到日盈利封顶:{pnl * 100:.2f}% -> 停机") - - def calculate_size(self, price: float) -> int: - """计算仓位大小""" - bal = self.get_assets_available() - if bal < self.cfg.fixed_margin: - logger.warning(f"余额不足:{bal:.2f} USDT < {self.cfg.fixed_margin} USDT") - return 0 - - margin = self.cfg.fixed_margin - lev = int(self.cfg.leverage) - size = int((margin * lev) / (price * 0.001)) - size = max(self.cfg.min_size, size) - size = min(self.cfg.max_size, size) - - logger.debug(f"计算仓位:保证金={margin}u, 杠杆={lev}x, size={size}") - return size - - # ----------------- 浏览器操作 ----------------- - def openBrowser(self) -> bool: - """打开浏览器""" - try: - bit_port = openBrowser(id=self.bit_id) - co = ChromiumOptions() - co.set_local_port(port=bit_port) - self.page = ChromiumPage(addr_or_opts=co) - return True - except Exception as e: - logger.error(f"打开浏览器失败: {e}") - return False - - def click_safe(self, xpath: str, sleep: float = 0.5) -> bool: - """安全点击""" - try: - ele = self.page.ele(xpath) - if not ele: - return False - ele.scroll.to_see(center=True) - time.sleep(sleep) - ele.click() - return True - except Exception as e: - logger.error(f"点击失败: {e}") - return False - - # ----------------- 订单执行 ----------------- - def place_market_order(self, side: int, size: int) -> bool: - """下市价单""" - if size <= 0: - return False - size = 10 - try: - # 确保size在合理范围内 - size = max(self.cfg.min_size, min(self.cfg.max_size, size)) - - # 开多单 - if side == 1: - self.click_safe('x://button[normalize-space(text()) ="市价"]') - self.page.ele('x://*[@id="size_0"]').input(str(size), clear=True) - self.click_safe('x://span[normalize-space(text()) ="买入/做多"]') - logger.info(f"✅ 开多单: size={size}") - return True - - # 开空单 - elif side == 4: - self.click_safe('x://button[normalize-space(text()) ="市价"]') - self.page.ele('x://*[@id="size_0"]').input(str(size), clear=True) - self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]') - logger.info(f"✅ 开空单: size={size}") - return True - - # 平多单 - elif side == 2: - self.click_safe('x://span[normalize-space(text()) ="市价"]') - time.sleep(0.3) - self.page.ele('x://*[@id="size_0"]').input(str(size), clear=True) - time.sleep(0.3) - self.click_safe('x://span[normalize-space(text()) ="卖出/做空"]') - logger.info(f"✅ 平多单: size={size}") - return True - - # 平空单 - elif side == 3: - self.click_safe('x://span[normalize-space(text()) ="市价"]') - time.sleep(0.3) - self.page.ele('x://*[@id="size_0"]').input(str(size), clear=True) - time.sleep(0.3) - self.click_safe('x://span[normalize-space(text()) ="买入/做多"]') - logger.info(f"✅ 平空单: size={size}") - return True - - else: - logger.error(f"未知的订单方向: side={side}") - return False - - except Exception as e: - logger.error(f"下单异常: {e}") - return False - - def calculate_fee(self, position_value: float) -> float: - """计算手续费(考虑90%返佣)""" - platform_fee = position_value * self.cfg.platform_fee_rate - actual_fee = platform_fee * (1 - self.cfg.rebate_rate) - return actual_fee - - def calculate_net_pnl(self, price: float) -> Tuple[float, float, float]: - """计算净盈亏""" - if self.pos == 0 or self.entry_price is None or self.entry_margin is None or self.entry_position_value is None: - return 0.0, 0.0, 0.0 - - leverage = int(self.cfg.leverage) - - # 计算价格变动比例 - if self.pos == 1: - price_change_ratio = (price - self.entry_price) / self.entry_price - else: - price_change_ratio = (self.entry_price - price) / self.entry_price - - # 计算毛盈亏比例(考虑杠杆) - gross_pnl_ratio = leverage * price_change_ratio - - # 计算手续费 - entry_fee = self.calculate_fee(self.entry_position_value) - exit_position_value = self.entry_position_value - exit_fee = self.calculate_fee(exit_position_value) - total_fee = entry_fee + exit_fee - - # 手续费相对于保证金的比率 - fee_ratio = total_fee / self.entry_margin - - # 净盈亏 = 毛盈亏 - 手续费比率 - net_pnl_ratio = gross_pnl_ratio - fee_ratio - - return net_pnl_ratio, total_fee, gross_pnl_ratio - - def close_position_all(self) -> bool: - """平掉所有持仓""" - if self.pos == 0: - logger.info("当前无持仓,无需平仓") - return True - - max_retries = 3 - retry_delay = 1.0 - - for attempt in range(1, max_retries + 1): - logger.info(f"平仓尝试 {attempt}/{max_retries}...") - old_pos = self.pos - - # 执行平仓操作 - if self.pos == 1: - ok = self.place_market_order(2, 999999) - if not ok: - logger.warning(f"平多单操作失败 (尝试 {attempt}/{max_retries})") - if attempt < max_retries: - time.sleep(retry_delay) - continue - else: - self.ding(f"平多单失败:已重试{max_retries}次仍失败", error=True) - return False - elif self.pos == -1: - ok = self.place_market_order(3, 999999) - if not ok: - logger.warning(f"平空单操作失败 (尝试 {attempt}/{max_retries})") - if attempt < max_retries: - time.sleep(retry_delay) - continue - else: - self.ding(f"平空单失败:已重试{max_retries}次仍失败", error=True) - return False - else: - return True - - # 等待订单执行 - time.sleep(1.5) - - # 验证是否平仓成功 - verify_success = self._verify_position_closed(old_pos) - if verify_success: - self.pos = 0 - logger.success(f"✅ 平仓成功 (尝试 {attempt}/{max_retries})") - return True - else: - logger.warning(f"平仓验证失败 (尝试 {attempt}/{max_retries})") - if attempt < max_retries: - time.sleep(retry_delay) - self.get_position_status() - else: - self.ding(f"平仓失败:已重试{max_retries}次", error=True) - return False - - return False - - def _verify_position_closed(self, expected_old_pos: int) -> bool: - """验证持仓是否已平仓""" - try: - resp = self.contractAPI.get_position(contract_symbol=self.cfg.contract_symbol)[0] - - if resp.get("code") != 1000: - logger.warning(f"查询持仓状态失败: {resp}") - return False - - positions = resp.get("data", []) - if not positions or len(positions) == 0: - logger.info("✅ SDK验证:持仓已平仓") - return True - - for p in positions: - position_type = p.get("position_type", 0) - current_pos = 1 if position_type == 1 else -1 - if current_pos == expected_old_pos: - logger.warning(f"⚠️ SDK验证:持仓仍存在") - return False - - logger.info("✅ SDK验证:持仓已平仓或已改变") - return True - - except Exception as e: - logger.error(f"验证持仓状态异常: {e}") - return False - - # ----------------- 止损后机制 ----------------- - def _reentry_penalty_active(self, dev: float, entry_dev: float) -> bool: - """检查是否需要应用重新入场惩罚""" - if self.last_sl_dir == 0: - return False - - if (time.time() - self.last_sl_ts) > self.cfg.reentry_penalty_max_sec: - self.last_sl_dir = 0 - return False - - reset_band = max(self.cfg.reset_band_floor, self.cfg.reset_band_k * entry_dev) - if abs(dev) <= reset_band: - self.last_sl_dir = 0 - return False - - return True - - def _post_sl_dynamic_mult(self) -> float: - """计算止损后SL放宽倍数""" - if self.post_sl_dir == 0: - return 1.0 - - if (time.time() - self.post_sl_ts) > self.cfg.post_sl_sl_max_sec: - self.post_sl_dir = 0 - self.post_sl_vol_scale = 1.0 - return 1.0 - - raw = 1.0 + self.cfg.post_sl_vol_alpha * (self.post_sl_vol_scale - 1.0) - raw = max(1.0, raw) - return max(self.cfg.post_sl_mult_min, min(self.cfg.post_sl_mult_max, raw)) - - # ----------------- 交易逻辑(核心修复) ----------------- - def in_cooldown(self) -> bool: - """检查是否在冷却期内""" - return (time.time() - self.last_exit_ts) < self.cfg.cooldown_sec_after_exit - - def maybe_enter(self, price: float, ema_value: float, entry_dev: float): - """ - 检查并执行入场 - 修复版 - 修复问题:避免平仓后立即开反向仓位 - """ - # 基础检查 - if self.pos != 0: - return - - if self.in_cooldown(): - return - - if not self.trading_enabled: - return - - # 连续亏损控制 - if self.consecutive_losses >= self.cfg.max_consecutive_losses: - logger.warning(f"连续亏损{self.consecutive_losses}次,暂停交易{self.cfg.recovery_wait_sec}秒") - time.sleep(self.cfg.recovery_wait_sec) - return - - # 日内交易次数限制 - if self.today_trade_count >= self.cfg.max_daily_trades: - logger.info(f"达到日交易次数上限: {self.today_trade_count}") - return - - # 趋势过滤 - if not self.check_trend_filter(price): - return - - dev = (price - ema_value) / ema_value if ema_value else 0.0 - size = self.calculate_size(price) - if size <= 0: - return - - # 方向过滤:避免平仓后立即反向开仓 - if self.last_exit_direction != 0: - time_since_exit = time.time() - self.last_exit_ts - - # 如果是平多后(做空方向),且在冷却期内,避免立即开多 - if (self.last_exit_direction == 1 and # 上次是多平 - time_since_exit < self.cfg.opposite_direction_cooldown and - dev <= 0): # 当前适合开多 - logger.info(f"平多后{int(time_since_exit)}秒内,避免开多") - return - - # 如果是平空后(做多方向),且在冷却期内,避免立即开空 - if (self.last_exit_direction == -1 and # 上次是空平 - time_since_exit < self.cfg.opposite_direction_cooldown and - dev >= 0): # 当前适合开空 - logger.info(f"平空后{int(time_since_exit)}秒内,避免开空") - return - - penalty_active = self._reentry_penalty_active(dev, entry_dev) - long_th = -entry_dev - short_th = entry_dev - - if penalty_active: - if self.last_sl_dir == 1: - long_th = -entry_dev * self.cfg.reentry_penalty_mult - elif self.last_sl_dir == -1: - short_th = entry_dev * self.cfg.reentry_penalty_mult - - logger.info( - f"入场检查: 偏离={dev * 100:.3f}%, 阈值=[{long_th * 100:.3f}%, {short_th * 100:.3f}%], " - f"size={size}, 连续亏损={self.consecutive_losses}" - ) - - # 开多条件:价格显著低于EMA - if dev <= long_th: - if self.place_market_order(1, size): - self.pos = 1 - self.entry_price = price - self.entry_ts = time.time() - self.entry_margin = self.cfg.fixed_margin - self.entry_position_value = self.entry_margin * int(self.cfg.leverage) - self.ding(f"✅开多:偏离={dev * 100:.3f}%, 价格={price:.2f}") - - # 开空条件:价格显著高于EMA - elif dev >= short_th: - if self.place_market_order(4, size): - self.pos = -1 - self.entry_price = price - self.entry_ts = time.time() - self.entry_margin = self.cfg.fixed_margin - self.entry_position_value = self.entry_margin * int(self.cfg.leverage) - self.ding(f"✅开空:偏离={dev * 100:.3f}%, 价格={price:.2f}") - - def maybe_exit(self, price: float, ema_value: float, tp: float, sl: float, vol_scale: float): - """ - 检查并执行出场 - 记录平仓方向,用于后续入场过滤 - """ - if self.pos == 0 or self.entry_price is None or self.entry_ts is None: - return - - hold = time.time() - self.entry_ts - net_pnl, total_fee, gross_pnl = self.calculate_net_pnl(price) - dev = (price - ema_value) / ema_value if ema_value else 0.0 - - # 计算止损倍数 - sl_mult = 1.0 - if self.post_sl_dir == self.pos and self.post_sl_dir != 0: - sl_mult = self._post_sl_dynamic_mult() - effective_sl = sl * sl_mult - - # 记录平仓前的方向 - old_pos = self.pos - - # 条件1:正常平仓(价格回归EMA) - if abs(dev) <= self.cfg.normal_exit_threshold: - if net_pnl >= self.cfg.normal_exit_min_profit: # 净盈利达到最小要求 - if self.close_position_all(): - self._record_exit(price, old_pos, net_pnl, total_fee, gross_pnl, "正常平仓") - return - else: - logger.error("正常平仓失败") - return - - # 条件2:止盈 - if gross_pnl >= tp: - if net_pnl > 0: # 扣除手续费后仍有盈利 - if self.close_position_all(): - self._record_exit(price, old_pos, net_pnl, total_fee, gross_pnl, "止盈") - return - else: - logger.error("止盈平仓失败") - return - - # 条件3:止损 - if gross_pnl <= -effective_sl: - if self.close_position_all(): - self._record_exit(price, old_pos, net_pnl, total_fee, gross_pnl, "止损") - - # 记录止损状态 - sl_dir = old_pos - self.last_sl_dir = sl_dir - self.last_sl_ts = time.time() - self.post_sl_dir = sl_dir - self.post_sl_ts = time.time() - self.post_sl_vol_scale = float(vol_scale) - return - else: - logger.error("止损平仓失败") - return - - # 条件4:超时平仓 - if hold >= self.cfg.max_hold_sec: - if net_pnl > 0: # 扣除手续费后仍有盈利 - if self.close_position_all(): - self._record_exit(price, old_pos, net_pnl, total_fee, gross_pnl, "超时平仓") - return - else: - logger.error("超时平仓失败") - return - else: - logger.debug(f"超时但净盈亏为负,继续持有") - - def _record_exit(self, price: float, old_pos: int, net_pnl: float, - total_fee: float, gross_pnl: float, exit_type: str): - """记录平仓信息""" - self.entry_price = None - self.entry_ts = None - self.entry_margin = None - self.entry_position_value = None - self.last_exit_ts = time.time() - self.last_exit_direction = old_pos - self.last_exit_price = price - self.today_trade_count += 1 - - # 更新连续亏损计数 - if net_pnl < 0: - self.consecutive_losses += 1 - else: - self.consecutive_losses = 0 - - self.ding( - f"📊{exit_type}:方向={'多' if old_pos == 1 else '空'}, " - f"毛盈亏={gross_pnl * 100:.3f}%, 净盈亏={net_pnl * 100:.3f}%, " - f"手续费={total_fee:.4f}u, 价格={price:.2f}" - ) - - # ----------------- 状态通知 ----------------- - def notify_status_throttled(self, price: float, ema_value: float, dev: float, bal: float, - atr_ratio: float, base_ratio: float, vol_scale: float, - entry_dev: float, tp: float, sl: float, - entry_floor: float, tp_floor: float, sl_floor: float): - """限频状态通知""" - now = time.time() - if (now - self._last_status_notify_ts) < self.cfg.status_notify_sec: - return - self._last_status_notify_ts = now - - direction_str = "多" if self.pos == 1 else ("空" if self.pos == -1 else "无") - - # 计算当前盈亏 - current_net_pnl, current_fee, current_gross_pnl = 0.0, 0.0, 0.0 - if self.pos != 0 and self.entry_price: - current_net_pnl, current_fee, current_gross_pnl = self.calculate_net_pnl(price) - - msg = ( - f"【BitMart {self.cfg.contract_symbol}|1m均值回归(90%返佣优化)】\n" - f"📊 状态:{direction_str} | 今日交易:{self.today_trade_count}/{self.cfg.max_daily_trades}\n" - f"💰 现价:{price:.2f} | EMA{self.cfg.ema_len}:{ema_value:.2f} | 偏离:{dev * 100:.3f}%\n" - f"🎯 阈值:入场±{entry_dev * 100:.3f}% | 止盈{tp * 100:.3f}% | 止损{sl * 100:.3f}%\n" - f"🌊 波动率:ATR={atr_ratio * 100:.3f}% | 基准={base_ratio * 100:.3f}% | 缩放={vol_scale:.2f}\n" - f"💳 余额:{bal:.2f} USDT | 杠杆:{self.cfg.leverage}x | 保证金:{self.cfg.fixed_margin}u\n" - ) - - if self.pos != 0: - msg += ( - f"📈 当前盈亏:毛={current_gross_pnl * 100:.3f}% | 净={current_net_pnl * 100:.3f}% | " - f"手续费={current_fee:.4f}u\n" - f"⚠️ 连续亏损:{self.consecutive_losses}次\n" - ) - - self.ding(msg) - - # ----------------- 主循环 ----------------- - def action(self): - """主循环""" - if not self.set_leverage(): - self.ding("杠杆设置失败,停止运行", error=True) - return - - # 打开浏览器 - if not self.openBrowser(): - self.ding("打开 TGE 失败!", error=True) - return - logger.info("TGE 端口获取成功") - - self.page.get("https://derivatives.bitmart.com/zh-CN/futures/ETHUSDT") - time.sleep(3) # 等待页面加载 - - logger.info("策略启动 - 90%返佣优化版") - logger.info("策略特点:低阈值、高频次、严格方向过滤") - - while True: - now_dt = datetime.datetime.now() - self.pbar.n = now_dt.second - self.pbar.refresh() - - # 1. 获取K线数据 - klines = self.get_klines_cached() - if not klines or len(klines) < (self.cfg.ema_len + 5): - logger.warning("K线数据不足,等待...") - time.sleep(1) - continue - - # 2. 计算技术指标 - last_k = klines[-1] - closes = [k["close"] for k in klines[-(self.cfg.ema_len + 1):]] - ema_value = self.ema(closes, self.cfg.ema_len) - price = self.get_last_price(fallback_close=float(last_k["close"])) - dev = (price - ema_value) / ema_value if ema_value else 0.0 - - # 3. 计算波动率指标 - a = self.atr(klines, self.cfg.atr_len) - atr_ratio = (a / price) if price > 0 else 0.0 - base_ratio = self.get_base_ratio_cached(klines) - - # 4. 计算动态阈值 - entry_dev, tp, sl, vol_scale, entry_floor, tp_floor, sl_floor = self.dynamic_thresholds( - atr_ratio, base_ratio - ) - - # 5. 风控检查 - self.risk_kill_switch() - - # 6. 获取持仓状态 - if not self.get_position_status(): - time.sleep(1) - continue - - # 7. 检查交易是否启用 - if not self.trading_enabled: - if self.pos != 0: - if self.close_position_all(): - logger.info("交易被禁用,已平仓") - else: - logger.error("交易被禁用,但平仓失败") - logger.warning("交易被禁用,等待...") - time.sleep(5) - continue - - # 8. 检查危险市场 - if self.is_danger_market(klines, price): - logger.warning("危险模式,暂停开仓") - self.maybe_exit(price, ema_value, tp, sl, vol_scale) - time.sleep(self.cfg.tick_refresh_sec) - continue - - # 9. 执行交易逻辑 - # 先检查平仓 - self.maybe_exit(price, ema_value, tp, sl, vol_scale) - # 再检查开仓 - self.maybe_enter(price, ema_value, entry_dev) - - # 10. 状态通知 - bal = self.get_assets_available() - self.notify_status_throttled( - price, ema_value, dev, bal, - atr_ratio, base_ratio, vol_scale, - entry_dev, tp, sl, - entry_floor, tp_floor, sl_floor - ) - - time.sleep(self.cfg.tick_refresh_sec) - - -if __name__ == "__main__": - """ - Windows PowerShell: - setx BITMART_API_KEY "你的key" - setx BITMART_SECRET_KEY "你的secret" - setx BITMART_MEMO "合约交易" - 重新打开终端再运行。 - - Linux/macOS: - export BITMART_API_KEY="你的key" - export BITMART_SECRET_KEY="你的secret" - export BITMART_MEMO "合约交易" - """ - cfg = StrategyConfig() - bot = BitmartFuturesMeanReversionBot(cfg, bit_id="f2320f57e24c45529a009e1541e25961") - - # 设置日志级别 - logger.remove() - logger.add(lambda msg: tqdm.write(msg, end=""), level="INFO") - - logger.info(""" - ==================================================== - 90%返佣优化策略说明: - - 策略特点: - 1. 针对90%返佣优化,阈值降低30-50% - 2. 增加方向过滤,避免平仓后立即反向开仓 - 3. 冷却时间从20秒减至5秒,提高交易频率 - 4. 添加连续亏损控制和趋势过滤 - 5. 日内交易次数限制:50次 - - 核心修复: - 1. 避免平仓后立即反向开仓(原策略最大问题) - 2. 修复平仓后可能立即开反向仓位的问题 - 3. 增加对同方向冷却的控制 - ==================================================== - """) - - try: - bot.action() - except KeyboardInterrupt: - logger.info("程序被用户中断") - bot.ding("🤖 策略已手动停止") - except Exception as e: - logger.error(f"程序异常退出: {e}") - bot.ding(f"❌ 策略异常退出: {e}", error=True) - raise diff --git a/bitmart/抓取数据_30分钟.py b/bitmart/抓取数据_30分钟.py deleted file mode 100644 index b1c52f2..0000000 --- a/bitmart/抓取数据_30分钟.py +++ /dev/null @@ -1,169 +0,0 @@ -""" -BitMart 15分钟K线数据抓取脚本 -从 BitMart API 获取15分钟K线数据并存储到数据库 -""" - -import time -from loguru import logger -from bitmart.api_contract import APIContract -from models.bitmart_15 import BitMart15 - - -class BitMartDataCollector: - def __init__(self): - self.api_key = "a0fb7b98464fd9bcce67e7c519d58ec10d0c38a8" - self.secret_key = "4eaeba78e77aeaab1c2027f846a276d164f264a44c2c1bb1c5f3be50c8de1ca5" - self.memo = "数据抓取" - self.contract_symbol = "ETHUSDT" - self.contractAPI = APIContract(self.api_key, self.secret_key, self.memo, timeout=(5, 15)) - - def get_klines(self, start_time=None, end_time=None, limit=200): - """ - 获取K线数据 - :param start_time: 开始时间戳(秒级) - :param end_time: 结束时间戳(秒级) - :param limit: 获取数量限制 - :return: K线数据列表 - """ - try: - if not end_time: - end_time = int(time.time()) - if not start_time: - start_time = end_time - 3600 * 24 * 1 # 默认获取最近7天 - - response = self.contractAPI.get_kline( - contract_symbol=self.contract_symbol, - step=15, # 15分钟 - start_time=start_time, - end_time=end_time - )[0] - - if response['code'] != 1000: - logger.error(f"获取K线失败: {response}") - return [] - - klines = response.get('data', []) - formatted = [] - for k in klines: - # BitMart API 返回的时间戳是秒级,需要转换为毫秒级 - # 根据 bitmart/框架.py 中的使用方式,API返回的是秒级时间戳 - timestamp_ms = int(k["timestamp"]) * 1000 - - formatted.append({ - 'id': timestamp_ms, - 'open': float(k["open_price"]), - 'high': float(k["high_price"]), - 'low': float(k["low_price"]), - 'close': float(k["close_price"]) - }) - - # 按时间戳排序 - formatted.sort(key=lambda x: x['id']) - return formatted - except Exception as e: - logger.error(f"获取K线异常: {e}") - return [] - - def save_klines(self, klines): - """ - 保存K线数据到数据库 - :param klines: K线数据列表 - :return: 保存的数量 - """ - saved_count = 0 - for kline in klines: - try: - BitMart15.get_or_create( - id=kline['id'], - defaults={ - 'open': kline['open'], - 'high': kline['high'], - 'low': kline['low'], - 'close': kline['close'], - } - ) - saved_count += 1 - except Exception as e: - logger.error(f"保存K线数据失败 {kline['id']}: {e}") - - return saved_count - - def collect_historical_data(self, start_date=None, days=None): - """ - 抓取历史数据(从指定日期到现在) - :param start_date: 起始日期字符串,格式 'YYYY-MM-DD',如 '2025-01-01' - :param days: 如果不指定 start_date,则抓取最近多少天的数据 - """ - import datetime - - now = int(time.time()) - - if start_date: - # 解析起始日期 - start_dt = datetime.datetime.strptime(start_date, '%Y-%m-%d') - target_start_time = int(start_dt.timestamp()) - logger.info(f"开始抓取 BitMart {self.contract_symbol} 从 {start_date} 到现在的15分钟K线数据") - elif days: - target_start_time = now - 3600 * 24 * days - logger.info(f"开始抓取 BitMart {self.contract_symbol} 最近 {days} 天的15分钟K线数据") - else: - target_start_time = now - 3600 * 24 * 30 # 默认30天 - logger.info(f"开始抓取 BitMart {self.contract_symbol} 最近 30 天的15分钟K线数据") - - # 分批获取,每次获取5天的数据(15分钟K线数据量较大) - batch_days = 5 - total_saved = 0 - fail_count = 0 - max_fail = 3 # 连续失败超过3次则停止 - - current_end = now - while current_end > target_start_time: - current_start = max(current_end - 3600 * 24 * batch_days, target_start_time) - - logger.info(f"抓取时间段: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(current_start))} " - f"到 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(current_end))}") - - klines = self.get_klines(start_time=current_start, end_time=current_end) - if klines: - saved = self.save_klines(klines) - total_saved += saved - logger.info(f"本批次保存 {saved} 条数据,累计 {total_saved} 条") - fail_count = 0 # 重置失败计数 - else: - fail_count += 1 - logger.warning(f"本批次未获取到数据 (连续失败 {fail_count} 次)") - if fail_count >= max_fail: - logger.error(f"连续 {max_fail} 次获取数据失败,可能已达到 API 历史数据限制,停止抓取") - break - - current_end = current_start - time.sleep(1) # 避免请求过快 - - logger.success(f"数据抓取完成,共保存 {total_saved} 条K线数据") - - def collect_realtime_data(self): - """ - 实时抓取最新数据(用于定时任务) - """ - logger.info("开始抓取 BitMart 最新15分钟K线数据") - - # 获取最近1小时的数据(确保能获取到最新的K线) - end_time = int(time.time()) - start_time = end_time - 3600 * 2 # 最近2小时 - - klines = self.get_klines(start_time=start_time, end_time=end_time) - if klines: - saved = self.save_klines(klines) - logger.success(f"保存 {saved} 条最新K线数据") - else: - logger.warning("未获取到最新数据") - - -if __name__ == '__main__': - collector = BitMartDataCollector() - - # 抓取从 2025-01-01 到现在的15分钟K线历史数据 - collector.collect_historical_data(start_date='2025-01-01') - - # 如果需要实时抓取,可以取消下面的注释 - # collector.collect_realtime_data() diff --git a/mexc/30分钟.py b/mexc/30分钟.py index 217b511..7c6fa4c 100644 --- a/mexc/30分钟.py +++ b/mexc/30分钟.py @@ -41,6 +41,57 @@ from models.mexc import Mexc30 # ========================= 工具函数 ========================= +# 交易对的最小价格单位(tick size)配置 +# 格式:交易对符号 -> 最小单位 +TICK_SIZE_MAP = { + 'SOLUSDT': 0.01, + 'BTCUSDT': 0.1, + 'ETHUSDT': 0.01, + 'BNBUSDT': 0.01, + # 可以根据需要添加更多交易对 +} + +# 默认最小单位(如果交易对不在配置中) +DEFAULT_TICK_SIZE = 0.01 + + +def get_tick_size(symbol: str) -> float: + """ + 获取交易对的最小价格单位 + :param symbol: 交易对符号,如 'SOLUSDT' + :return: 最小单位,如 0.01 + """ + return TICK_SIZE_MAP.get(symbol.upper(), DEFAULT_TICK_SIZE) + + +def adjust_price_for_trade(price: float, direction: str, symbol: str = 'SOLUSDT') -> float: + """ + 根据交易方向调整价格,考虑买卖价差 + + 买入(开多/平空):价格 + tick_size + 卖出(开空/平多):价格 - tick_size + + :param price: 原始价格(K线开盘价或收盘价) + :param direction: 交易方向,'long' 表示买入,'short' 表示卖出 + :param symbol: 交易对符号,用于获取最小单位 + :return: 调整后的价格 + """ + tick_size = get_tick_size(symbol) + + if direction == 'long': + # 买入:价格 + tick_size + adjusted = price + tick_size + elif direction == 'short': + # 卖出:价格 - tick_size + adjusted = price - tick_size + else: + # 未知方向,返回原价 + adjusted = price + + # 确保价格不会为负 + return max(adjusted, tick_size) + + def is_bullish(c): # 阳线 return float(c['close']) > float(c['open']) @@ -109,7 +160,7 @@ def get_data_by_date(model, date_str: str): # ========================= 回测逻辑 ========================= -def backtest_15m_trend_optimized(dates: List[str]): +def backtest_15m_trend_optimized(dates: List[str], symbol: str = 'SOLUSDT'): """ 回测策略逻辑: 1. 开仓条件(信号出现时,下一根K线开盘价开仓): @@ -182,7 +233,9 @@ def backtest_15m_trend_optimized(dates: List[str]): if current_position is None: if direction: # 信号出现(prev和curr形成信号),在下一根K线(next_bar)的开盘价开仓 - entry_price = float(next_bar['open']) + # 应用买卖价差:买入(开多)时价格+0.01,卖出(开空)时价格-0.01 + raw_price = float(next_bar['open']) + entry_price = adjust_price_for_trade(raw_price, direction, symbol) current_position = { 'direction': direction, 'signal': stats[signal_key]['name'], @@ -192,7 +245,7 @@ def backtest_15m_trend_optimized(dates: List[str]): } consecutive_opposite_count = 0 # 重置连续反色计数 stats[signal_key]['count'] += 1 - logger.debug(f"开仓: {stats[signal_key]['name']} {'做多' if direction == 'long' else '做空'} @ {entry_price:.2f}") + logger.debug(f"开仓: {stats[signal_key]['name']} {'做多' if direction == 'long' else '做空'} @ {entry_price:.2f} (原始价格: {raw_price:.2f})") idx += 1 continue @@ -203,7 +256,11 @@ def backtest_15m_trend_optimized(dates: List[str]): # 1. 反向信号 -> 下一根K线开盘价平仓并反手开仓 # 策略:遇到反向信号(如持有多单时遇到阴包阳),平仓并反手开仓 if direction and direction != pos_dir: - exit_price = float(next_bar['open']) + # 平仓:持有多单时卖出(价格-0.01),持有空单时买入(价格+0.01) + raw_exit_price = float(next_bar['open']) + exit_direction = 'short' if pos_dir == 'long' else 'long' # 平仓方向与持仓方向相反 + exit_price = adjust_price_for_trade(raw_exit_price, exit_direction, symbol) + diff = (exit_price - current_position['entry_price']) if pos_dir == 'long' else ( current_position['entry_price'] - exit_price) trades.append({ @@ -218,17 +275,19 @@ def backtest_15m_trend_optimized(dates: List[str]): stats[pos_sig_key]['total_profit'] += diff if diff > 0: stats[pos_sig_key]['wins'] += 1 - # 反手开仓(下一根K线开盘价) + # 反手开仓(下一根K线开盘价,应用买卖价差) + raw_entry_price = float(next_bar['open']) + entry_price = adjust_price_for_trade(raw_entry_price, direction, symbol) current_position = { 'direction': direction, 'signal': stats[signal_key]['name'], 'signal_key': signal_key, - 'entry_price': exit_price, + 'entry_price': entry_price, 'entry_time': next_bar['id'] } consecutive_opposite_count = 0 # 重置连续反色计数 stats[signal_key]['count'] += 1 - logger.debug(f"反向信号反手: 平{'做多' if pos_dir == 'long' else '做空'} @ {exit_price:.2f}, 开{'做多' if direction == 'long' else '做空'}") + logger.debug(f"反向信号反手: 平{'做多' if pos_dir == 'long' else '做空'} @ {exit_price:.2f} (原始: {raw_exit_price:.2f}), 开{'做多' if direction == 'long' else '做空'} @ {entry_price:.2f} (原始: {raw_entry_price:.2f})") idx += 1 continue @@ -240,7 +299,9 @@ def backtest_15m_trend_optimized(dates: List[str]): # 如果已经连续两根阴线,下一根K线开盘价平仓 if consecutive_opposite_count >= 2: logger.debug(f"平仓: 做多遇到连续两根阴线") - exit_price = float(next_bar['open']) + # 平多单:卖出,价格 - 0.01 + raw_exit_price = float(next_bar['open']) + exit_price = adjust_price_for_trade(raw_exit_price, 'short', symbol) diff = exit_price - current_position['entry_price'] trades.append({ 'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000), @@ -268,7 +329,9 @@ def backtest_15m_trend_optimized(dates: List[str]): # 如果已经连续两根阳线,下一根K线开盘价平仓 if consecutive_opposite_count >= 2: logger.debug(f"平仓: 做空遇到连续两根阳线") - exit_price = float(next_bar['open']) + # 平空单:买入,价格 + 0.01 + raw_exit_price = float(next_bar['open']) + exit_price = adjust_price_for_trade(raw_exit_price, 'long', symbol) diff = current_position['entry_price'] - exit_price trades.append({ 'entry_time': datetime.datetime.fromtimestamp(current_position['entry_time'] / 1000), @@ -305,8 +368,12 @@ def backtest_15m_trend_optimized(dates: List[str]): # 尾仓:最后一根收盘价平仓 if current_position: last = all_data[-1] - exit_price = float(last['close']) pos_dir = current_position['direction'] + # 平仓:持有多单时卖出(价格-0.01),持有空单时买入(价格+0.01) + raw_exit_price = float(last['close']) + exit_direction = 'short' if pos_dir == 'long' else 'long' # 平仓方向与持仓方向相反 + exit_price = adjust_price_for_trade(raw_exit_price, exit_direction, symbol) + diff = (exit_price - current_position['entry_price']) if pos_dir == 'long' else ( current_position['entry_price'] - exit_price) trades.append({ @@ -346,7 +413,9 @@ if __name__ == '__main__': print(dates) # dates = [f"2025-09-{i}" for i in range(1, 32)] - trades, stats = backtest_15m_trend_optimized(dates) + # 指定交易对符号,用于获取正确的最小价格单位 + symbol = 'SOLUSDT' + trades, stats = backtest_15m_trend_optimized(dates, symbol=symbol) logger.info("===== 每笔交易详情 =====")