diff --git a/main/data/index_and_industry.ipynb b/main/data/index_and_industry.ipynb index 735600e..a77f841 100644 --- a/main/data/index_and_industry.ipynb +++ b/main/data/index_and_industry.ipynb @@ -137,7 +137,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.11" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/main/train/Classify/Classify2.ipynb b/main/train/Classify/Classify2.ipynb index 0b5b186..8163dcf 100644 --- a/main/train/Classify/Classify2.ipynb +++ b/main/train/Classify/Classify2.ipynb @@ -18,10 +18,10 @@ "Please upgrade to Gymnasium, the maintained drop-in replacement of Gym, or contact the authors of your software and request that they upgrade.\n", "Users of this version of Gym should be able to simply replace 'import gym' with 'import gymnasium as gym' in the vast majority of cases.\n", "See the migration guide at https://gymnasium.farama.org/introduction/migration_guide/ for additional information.\n", - "[177513:MainThread](2026-02-25 22:33:43,460) INFO - qlib.Initialization - [config.py:452] - default_conf: client.\n", - "[177513:MainThread](2026-02-25 22:33:43,461) WARNING - qlib.Initialization - [config.py:459] - Unrecognized config freq\n", - "[177513:MainThread](2026-02-25 22:33:43,467) INFO - qlib.Initialization - [__init__.py:75] - qlib successfully initialized based on client settings.\n", - "[177513:MainThread](2026-02-25 22:33:43,469) INFO - qlib.Initialization - [__init__.py:77] - data_path={'__DEFAULT_FREQ': PosixPath('/mnt/d/PyProject/NewStock/data/qlib')}\n" + "[65879:MainThread](2026-03-01 22:39:22,291) INFO - qlib.Initialization - [config.py:452] - default_conf: client.\n", + "[65879:MainThread](2026-03-01 22:39:22,291) WARNING - qlib.Initialization - [config.py:459] - Unrecognized config freq\n", + "[65879:MainThread](2026-03-01 22:39:22,297) INFO - qlib.Initialization - [__init__.py:75] - qlib successfully initialized based on client settings.\n", + "[65879:MainThread](2026-03-01 22:39:22,298) INFO - qlib.Initialization - [__init__.py:77] - data_path={'__DEFAULT_FREQ': PosixPath('/mnt/d/PyProject/NewStock/data/qlib')}\n" ] }, { @@ -96,7 +96,7 @@ "cyq perf\n", "left merge on ['ts_code', 'trade_date']\n", "\n", - "RangeIndex: 9436343 entries, 0 to 9436342\n", + "RangeIndex: 9456764 entries, 0 to 9456763\n", "Data columns (total 33 columns):\n", " # Column Dtype \n", "--- ------ ----- \n", @@ -174,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "id": "5f3847ec", "metadata": {}, "outputs": [ @@ -182,15 +182,17 @@ "name": "stdout", "output_type": "stream", "text": [ - " trade_date return_5_rank\n", - "6527870 2024-06-03 0.523969\n", - " trade_date ma5 ma10 close\n", - "6527870 2024-06-03 10.526 10.216 10.37\n" + " trade_date ma5 ma10 return_5 close\n", + "6527953 2024-01-23 7.358 7.500 -0.043364 7.28\n", + "6527952 2024-01-24 7.412 7.507 0.036339 7.70\n", + "6527951 2024-01-25 7.552 7.556 0.094213 8.13\n", + "6527950 2024-01-26 7.692 7.609 0.093583 8.18\n", + "6527949 2024-01-29 7.910 7.667 0.152022 8.26\n" ] } ], "source": [ - "\n", + "df = df.sort_values(by=['trade_date'])\n", "df['return_5'] = df.groupby('ts_code')['close'].pct_change(periods=5)\n", "\n", "df['return_5_rank'] = df.groupby('trade_date')['return_5'].transform(\n", @@ -198,10 +200,19 @@ " )\n", "\n", "df['ma5'] = df.groupby('ts_code')['close'].transform(lambda x: x.rolling(window=5, min_periods=1).mean())\n", - "df['ma10'] = df.groupby('ts_code')['close'].transform(lambda x: x.rolling(window=10, min_periods=1).mean())\n", + "df['ma10'] = df.groupby('ts_code')['close'].transform(lambda x: x.rolling(window=10, min_periods=1).mean())\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6de3c12", + "metadata": {}, + "outputs": [], + "source": [ "\n", - "print(df[(df['ts_code'] == '601117.SH') & (df['trade_date'] == '2024-06-03')][['trade_date', 'return_5_rank']])\n", - "print(df[(df['ts_code'] == '601117.SH') & (df['trade_date'] == '2024-06-03')][['trade_date', 'ma5', 'ma10', 'close']])" + "# print(df[(df['ts_code'] == '601117.SH') & (df['trade_date'] >= '2024-01-23')][['trade_date', 'return_5_rank']])\n", + "print(df[(df['ts_code'] == '601117.SH') & (df['trade_date'] >= '2024-01-23') & (df['trade_date'] <= '2024-01-29')][['trade_date', 'ma5', 'ma10', 'return_5', 'close']])" ] }, { @@ -9370,7 +9381,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.12.11" } }, "nbformat": 4, diff --git a/main/train/Classify2.ipynb b/main/train/Classify2.ipynb index 587a027..154af22 100644 --- a/main/train/Classify2.ipynb +++ b/main/train/Classify2.ipynb @@ -2508,7 +2508,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.2" + "version": "3.12.11" } }, "nbformat": 4, diff --git a/main/train/test.py b/main/train/test.py index c91674d..17fd3f7 100644 --- a/main/train/test.py +++ b/main/train/test.py @@ -2,14 +2,9 @@ from operator import index import tushare as ts -import sys -print(sys.path) - -from main.factor.factor import calculate_arbr - ts.set_token('3a0741c702ee7e5e5f2bf1f0846bafaafe4e320833240b2a7e4a685f') pro = ts.pro_api() -df = pro.dc_member(trade_date='20190105') +df = ts.pro_bar(ts_code='601117.SH', adj='qfq', start_date='20240129', end_date='20240129', fields='trade_date,ts_code,close,tor') print(df) \ No newline at end of file diff --git a/qmt/qmt_engine.py b/qmt/qmt_engine.py index 016c55e..6d5ca84 100644 --- a/qmt/qmt_engine.py +++ b/qmt/qmt_engine.py @@ -21,12 +21,24 @@ from xtquant import xtconstant try: from .message_processor import StreamMessageProcessor from .logger import QMTLogger - from .config_models import QMTConfig, QMTTerminalConfig, StrategyConfig, load_config, ConfigError + from .config_models import ( + QMTConfig, + QMTTerminalConfig, + StrategyConfig, + load_config, + ConfigError, + ) except ImportError: # 当作为脚本直接运行时 from message_processor import StreamMessageProcessor from logger import QMTLogger - from config_models import QMTConfig, QMTTerminalConfig, StrategyConfig, load_config, ConfigError + from config_models import ( + QMTConfig, + QMTTerminalConfig, + StrategyConfig, + load_config, + ConfigError, + ) import time import datetime import traceback @@ -429,7 +441,6 @@ class TradingUnit: self.account_id = t_cfg.account_id self.account_type = t_cfg.account_type - self.xt_trader = None self.acc_obj = None self.callback = None @@ -530,7 +541,7 @@ class MultiEngineManager: def initialize(self, config_file="config.json"): self._setup_logger() # 先初始化 logger - + # 使用新的配置模型加载配置 try: self.config = load_config(config_file) @@ -567,7 +578,9 @@ class MultiEngineManager: # 尝试多个路径 env_paths = [ - os.path.join(os.path.dirname(os.path.abspath(__file__)), "config", ".env.local"), + os.path.join( + os.path.dirname(os.path.abspath(__file__)), "config", ".env.local" + ), os.path.join(os.path.dirname(__file__), "config", ".env.local"), os.path.join(os.path.dirname(__file__), "..", "config", ".env.local"), "/qmt/config/.env.local", @@ -583,7 +596,9 @@ class MultiEngineManager: break if not loaded: - self.logger.warning("[Config] 警告: 未找到 .env.local 文件,使用默认配置") + self.logger.warning( + "[Config] 警告: 未找到 .env.local 文件,使用默认配置" + ) except ImportError: self.logger.warning("[Config] 警告: 未安装 python-dotenv,使用默认配置") @@ -616,15 +631,11 @@ class MultiEngineManager: sh.setFormatter(fmt) self.logger.addHandler(fh) self.logger.addHandler(sh) + def get_strategies_by_terminal(self, qmt_id): if not self.config: return [] return self.config.get_strategies_by_terminal(qmt_id) - return [ - s - for s, cfg in self.config["strategies"].items() - if cfg.get("qmt_id") == qmt_id - ] def run_trading_loop(self): self.logger = logging.getLogger("QMT_Engine") @@ -752,7 +763,7 @@ class MultiEngineManager: """处理策略消息路由 - 使用 Redis Stream 从 Redis Stream 消费消息,处理成功后 ACK,失败则进入失败队列。 - + Args: strategy_name: 策略名称 is_trading_hours: 是否在交易时间内,False 表示休盘后只消费消息不下单 @@ -815,14 +826,14 @@ class MultiEngineManager: # 3. 执行交易动作(仅在交易时间内执行实际下单) action = data.get("action") - + # 获取策略配置,确定下单模式 strat_cfg = self.config.get_strategy(strategy_name) if not strat_cfg: self.logger.warning(f"[{strategy_name}] 策略配置不存在") continue order_mode = strat_cfg.order_mode - + if not is_trading_hours: # 休盘后:只记录日志,不下单 self.logger.info( @@ -835,11 +846,11 @@ class MultiEngineManager: validation_type="market_hours_check", strategy_name=strategy_name, details={ - "action": action, + "action": action, "code": data.get("stock_code"), "order_mode": order_mode, "msg_time": data.get("timestamp"), - "skip_reason": "休盘后只消费消息不下单" + "skip_reason": "休盘后只消费消息不下单", }, result=True, ) @@ -847,7 +858,11 @@ class MultiEngineManager: self.qmt_logger.log_validation( validation_type="action_check", strategy_name=strategy_name, - details={"action": "BUY", "code": data.get("stock_code"), "order_mode": order_mode}, + details={ + "action": "BUY", + "code": data.get("stock_code"), + "order_mode": order_mode, + }, result=True, ) # 根据下单模式执行相应逻辑 @@ -859,7 +874,11 @@ class MultiEngineManager: self.qmt_logger.log_validation( validation_type="action_check", strategy_name=strategy_name, - details={"action": "SELL", "code": data.get("stock_code"), "order_mode": order_mode}, + details={ + "action": "SELL", + "code": data.get("stock_code"), + "order_mode": order_mode, + }, result=True, ) # 根据下单模式执行相应逻辑 @@ -934,10 +953,7 @@ class MultiEngineManager: return # 2. 持仓数检查 - if ( - self.pos_manager.get_holding_count(strategy_name) - >= strat_cfg.total_slots - ): + if self.pos_manager.get_holding_count(strategy_name) >= strat_cfg.total_slots: return try: @@ -949,7 +965,8 @@ class MultiEngineManager: # 权重默认为 1,支持通过 weight 字段调整资金分配比例 # 示例:strategies = {"strategy_a": {"total_slots": 5, "weight": 1}, "strategy_b": {"total_slots": 5, "weight": 2}} total_weighted_slots = sum( - self.config.get_strategy(s).total_slots * self.config.get_strategy(s).weight + self.config.get_strategy(s).total_slots + * self.config.get_strategy(s).weight for s in terminal_strategies if self.config.get_strategy(s) ) @@ -1127,54 +1144,72 @@ class MultiEngineManager: if not strat_cfg: self.logger.error(f"[{strategy_name}] 策略配置不存在") return - + # 获取目标持仓百分比 position_pct = float(data.get("position_pct", 0)) if position_pct <= 0: - self.logger.warning(f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})") + self.logger.warning( + f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})" + ) return - - self.logger.info(f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}" + ) + try: asset = unit.xt_trader.query_stock_asset(unit.acc_obj) if not asset: - self.logger.error(f"[{strategy_name}] API 错误: query_stock_asset 返回 None") + self.logger.error( + f"[{strategy_name}] API 错误: query_stock_asset 返回 None" + ) return - + total_asset = asset.total_asset available_cash = asset.cash - - self.logger.info(f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}" + ) + # 计算目标金额 target_amount = total_asset * position_pct actual_amount = min(target_amount, available_cash * 0.98) # 预留手续费滑点 - - self.logger.info(f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}" + ) + # 检查最小金额限制 if actual_amount < 2000: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)" + ) return - + # 价格校验 price = float(data.get("price", 0)) offset = strat_cfg.execution.buy_price_offset price = round(price + offset, 3) - + if price <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0" + ) price = 1.0 - + # 计算股数 vol = int(actual_amount / price / 100) * 100 - self.logger.info(f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股" + ) + if vol < 100: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})" + ) return - + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1183,7 +1218,7 @@ class MultiEngineManager: volume=vol, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -1194,10 +1229,12 @@ class MultiEngineManager: strategy_name, "PyBuyPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "BUY") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1227,31 +1264,41 @@ class MultiEngineManager: if not strat_cfg: self.logger.error(f"[{strategy_name}] 策略配置不存在") return - self.logger.info(f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)" + ) + try: # 查询实盘持仓 real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj) if real_pos is None: - self.logger.error(f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None") + self.logger.error( + f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None" + ) return - + rp = next((p for p in real_pos if p.stock_code == data["stock_code"]), None) can_use = rp.can_use_volume if rp else 0 - - self.logger.info(f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}" + ) + if can_use <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓" + ) return - + # 执行清仓 price = float(data.get("price", 0)) offset = strat_cfg.execution.sell_price_offset price = round(price + offset, 3) - - self.logger.info(f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}" + ) + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1260,7 +1307,7 @@ class MultiEngineManager: volume=can_use, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -1271,10 +1318,12 @@ class MultiEngineManager: strategy_name, "PySellPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1396,20 +1445,19 @@ class MultiEngineManager: if not strat_cfg: self.logger.error(f"[{strategy_name}] 策略配置不存在") return - self.logger.info(f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)" + ) + # 1. 槽位校验 - if data["total_slots"] != strat_cfg["total_slots"]: + if data["total_slots"] != strat_cfg.total_slots: self.logger.error( - f"[{strategy_name}] 信号槽位({data['total_slots']})与配置({strat_cfg['total_slots']})不符" + f"[{strategy_name}] 信号槽位({data['total_slots']})与配置({strat_cfg.total_slots})不符" ) return # 2. 持仓数检查 - if ( - self.pos_manager.get_holding_count(strategy_name) - >= strat_cfg["total_slots"] - ): + if self.pos_manager.get_holding_count(strategy_name) >= strat_cfg.total_slots: return try: @@ -1419,18 +1467,18 @@ class MultiEngineManager: # 计算加权槽位总和(支持策略权重配置) # 权重默认为 1,支持通过 weight 字段调整资金分配比例 - # 示例:strategies = {"strategy_a": {"total_slots": 5, "weight": 1}, "strategy_b": {"total_slots": 5, "weight": 2}} - total_weighted_slots = sum( - self.config["strategies"][s].get("total_slots", 1) - * self.config["strategies"][s].get("weight", 1) - for s in terminal_strategies - ) + # 计算加权槽位总和 + total_weighted_slots = 0 + for s in terminal_strategies: + s_cfg = self.config.strategies.get(s) + if s_cfg: + total_weighted_slots += s_cfg.total_slots * s_cfg.weight if not asset or total_weighted_slots <= 0: return # 获取当前策略的权重 - weight = strat_cfg.get("weight", 1) + weight = strat_cfg.weight # 4. 资金加权分配 (基于该终端总资产) total_equity = asset.cash + asset.market_value @@ -1444,7 +1492,7 @@ class MultiEngineManager: return # 4. 价格与股数 - offset = strat_cfg.get("execution", {}).get("buy_price_offset", 0.0) + offset = strat_cfg.execution.buy_price_offset price = round(float(data["price"]) + offset, 3) vol = int(actual_amt / (price if price > 0 else 1.0) / 100) * 100 @@ -1631,10 +1679,9 @@ class MultiEngineManager: return try: + strategy_config = self.config.strategies.get(strategy_name) offset = ( - self.config["strategies"][strategy_name] - .get("execution", {}) - .get("sell_price_offset", 0.0) + strategy_config.execution.sell_price_offset if strategy_config else 0.0 ) price = round(float(data["price"]) + offset, 3) @@ -1691,54 +1738,72 @@ class MultiEngineManager: if not strat_cfg: self.logger.error(f"[{strategy_name}] 策略配置不存在") return - + # 获取目标持仓百分比 position_pct = float(data.get("position_pct", 0)) if position_pct <= 0: - self.logger.warning(f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})") + self.logger.warning( + f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})" + ) return - - self.logger.info(f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}" + ) + try: asset = unit.xt_trader.query_stock_asset(unit.acc_obj) if not asset: - self.logger.error(f"[{strategy_name}] API 错误: query_stock_asset 返回 None") + self.logger.error( + f"[{strategy_name}] API 错误: query_stock_asset 返回 None" + ) return - + total_asset = asset.total_asset available_cash = asset.cash - - self.logger.info(f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}" + ) + # 计算目标金额 target_amount = total_asset * position_pct actual_amount = min(target_amount, available_cash * 0.98) # 预留手续费滑点 - - self.logger.info(f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}" + ) + # 检查最小金额限制 if actual_amount < 2000: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)" + ) return - + # 价格校验 price = float(data.get("price", 0)) offset = strat_cfg.execution.buy_price_offset price = round(price + offset, 3) - + if price <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0" + ) price = 1.0 - + # 计算股数 vol = int(actual_amount / price / 100) * 100 - self.logger.info(f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股" + ) + if vol < 100: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})" + ) return - + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1747,7 +1812,7 @@ class MultiEngineManager: volume=vol, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -1758,10 +1823,12 @@ class MultiEngineManager: strategy_name, "PyBuyPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "BUY") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1791,31 +1858,41 @@ class MultiEngineManager: if not strat_cfg: self.logger.error(f"[{strategy_name}] 策略配置不存在") return - self.logger.info(f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)" + ) + try: # 查询实盘持仓 real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj) if real_pos is None: - self.logger.error(f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None") + self.logger.error( + f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None" + ) return - + rp = next((p for p in real_pos if p.stock_code == data["stock_code"]), None) can_use = rp.can_use_volume if rp else 0 - - self.logger.info(f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}" + ) + if can_use <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓" + ) return - + # 执行清仓 price = float(data.get("price", 0)) offset = strat_cfg.execution.sell_price_offset price = round(price + offset, 3) - - self.logger.info(f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}" + ) + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1824,7 +1901,7 @@ class MultiEngineManager: volume=can_use, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -1835,10 +1912,12 @@ class MultiEngineManager: strategy_name, "PySellPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1862,55 +1941,73 @@ class MultiEngineManager: except: self.logger.error(traceback.format_exc()) """处理百分比模式的买入逻辑""" - strat_cfg = self.config["strategies"][strategy_name] - + strat_cfg = self.config.get_strategy(strategy_name) + # 获取目标持仓百分比 position_pct = float(data.get("position_pct", 0)) if position_pct <= 0: - self.logger.warning(f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})") + self.logger.warning( + f"[{strategy_name}] 百分比模式买入: position_pct 无效 ({position_pct})" + ) return - - self.logger.info(f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理买入: {data['stock_code']}, 目标占比: {position_pct}" + ) + try: asset = unit.xt_trader.query_stock_asset(unit.acc_obj) if not asset: - self.logger.error(f"[{strategy_name}] API 错误: query_stock_asset 返回 None") + self.logger.error( + f"[{strategy_name}] API 错误: query_stock_asset 返回 None" + ) return - + total_asset = asset.total_asset available_cash = asset.cash - - self.logger.info(f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}" + ) + # 计算目标金额 target_amount = total_asset * position_pct actual_amount = min(target_amount, available_cash * 0.98) # 预留手续费滑点 - - self.logger.info(f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}" + ) + # 检查最小金额限制 if actual_amount < 2000: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)" + ) return - + # 价格校验 price = float(data.get("price", 0)) - offset = strat_cfg.get("execution", {}).get("buy_price_offset", 0.0) + offset = strat_cfg.execution.buy_price_offset price = round(price + offset, 3) - + if price <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 价格异常: {price},强制设为1.0" + ) price = 1.0 - + # 计算股数 vol = int(actual_amount / price / 100) * 100 - self.logger.info(f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}股" + ) + if vol < 100: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})" + ) return - + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1919,7 +2016,7 @@ class MultiEngineManager: volume=vol, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -1930,10 +2027,12 @@ class MultiEngineManager: strategy_name, "PyBuyPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "BUY") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单买入: {data['stock_code']} {vol}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1959,31 +2058,44 @@ class MultiEngineManager: def _execute_percentage_sell(self, unit, strategy_name, data): """处理百分比模式的卖出逻辑(清仓)""" - self.logger.info(f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)") - + self.logger.info( + f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)" + ) + try: # 查询实盘持仓 real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj) if real_pos is None: - self.logger.error(f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None") + self.logger.error( + f"[{strategy_name}] [百分比模式] API 错误: query_stock_positions 返回 None" + ) return - + rp = next((p for p in real_pos if p.stock_code == data["stock_code"]), None) can_use = rp.can_use_volume if rp else 0 - - self.logger.info(f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 股票 {data['stock_code']} 实盘可用持仓: {can_use}" + ) + if can_use <= 0: - self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓") + self.logger.warning( + f"[{strategy_name}] [百分比模式] 拦截卖出: 无可用持仓" + ) return - + # 执行清仓 price = float(data.get("price", 0)) - offset = self.config["strategies"][strategy_name].get("execution", {}).get("sell_price_offset", 0.0) + strategy_config = self.config.get_strategy(strategy_name) + offset = ( + strategy_config.execution.sell_price_offset if strategy_config else 0.0 + ) price = round(price + offset, 3) - - self.logger.info(f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}") - + + self.logger.info( + f"[{strategy_name}] [百分比模式] 执行清仓: {data['stock_code']} @ {price}, 数量: {can_use}" + ) + # 记录订单执行请求 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -1992,7 +2104,7 @@ class MultiEngineManager: volume=can_use, price=price, ) - + oid = unit.xt_trader.order_stock( unit.acc_obj, data["stock_code"], @@ -2003,10 +2115,12 @@ class MultiEngineManager: strategy_name, "PySellPct", ) - + if oid != -1: unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL") - self.logger.info(f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}") + self.logger.info( + f"√√√ [{unit.alias}] [{strategy_name}] [百分比模式] 下单卖出: {data['stock_code']} {can_use}股 @ {price}" + ) # 记录订单执行成功 self.qmt_logger.log_order_execution( strategy_name=strategy_name, @@ -2029,6 +2143,7 @@ class MultiEngineManager: ) except: self.logger.error(traceback.format_exc()) + def verify_connection(self, timeout=5): """验证物理连接是否有效""" try: