refactor(qmt): 优化配置模型和百分比交易逻辑

- 统一使用配置模型属性访问替代字典下标
- 完善百分比模式买卖的日志记录和错误处理
- 代码格式化和死代码清理
- 更新 notebook 数据及测试脚本
This commit is contained in:
2026-03-03 15:16:37 +08:00
parent 7bb0a0537b
commit 4090b3c5be
5 changed files with 309 additions and 188 deletions

View File

@@ -137,7 +137,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.11"
"version": "3.13.2"
}
},
"nbformat": 4,

View File

@@ -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",
"<class 'pandas.core.frame.DataFrame'>\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,

View File

@@ -2508,7 +2508,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.2"
"version": "3.12.11"
}
},
"nbformat": 4,

View File

@@ -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)

View File

@@ -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
@@ -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")
@@ -839,7 +850,7 @@ class MultiEngineManager:
"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)
)
@@ -1131,31 +1148,43 @@ class MultiEngineManager:
# 获取目标持仓百分比
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
# 价格校验
@@ -1164,15 +1193,21 @@ class MultiEngineManager:
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
# 记录订单执行请求
@@ -1197,7 +1232,9 @@ class MultiEngineManager:
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,22 +1264,30 @@ 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
# 执行清仓
@@ -1250,7 +1295,9 @@ class MultiEngineManager:
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(
@@ -1274,7 +1321,9 @@ class MultiEngineManager:
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)
@@ -1695,31 +1742,43 @@ class MultiEngineManager:
# 获取目标持仓百分比
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
# 价格校验
@@ -1728,15 +1787,21 @@ class MultiEngineManager:
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
# 记录订单执行请求
@@ -1761,7 +1826,9 @@ class MultiEngineManager:
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,22 +1858,30 @@ 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
# 执行清仓
@@ -1814,7 +1889,9 @@ class MultiEngineManager:
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(
@@ -1838,7 +1915,9 @@ class MultiEngineManager:
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,53 +1941,71 @@ 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
# 记录订单执行请求
@@ -1933,7 +2030,9 @@ class MultiEngineManager:
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,30 +2058,43 @@ 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(
@@ -2006,7 +2118,9 @@ class MultiEngineManager:
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: