feat(qmt): 新增 Pydantic 配置模型并重构引擎架构

- 新增 config_models.py: 使用 Pydantic 提供强类型配置校验
  - QMTConfig, QMTTerminalConfig, StrategyConfig 等数据模型
  - 支持 slots/percentage 两种下单模式
  - 兼容旧版配置格式迁移
- 新增 validate_config.py: 配置检测 CLI 工具
- 重构 TradingUnit 和 MultiEngineManager 使用新配置模型
- 新增百分比模式买卖逻辑 (_execute_percentage_buy/sell)
- 完善日志记录和错误处理
- 删除 TODO_FIX.md: 清理已完成的缺陷修复任务清单
This commit is contained in:
2026-02-25 21:48:10 +08:00
parent 7b4112b70b
commit 555e7ebb43
3 changed files with 955 additions and 13 deletions

View File

@@ -2,6 +2,42 @@
"""
QMT (Quantitative Trading) Module
提供量化交易相关的功能:
- Redis Stream 消息处理
- 交易信号发送
- 回测消息消费
- 细粒度日志记录
- 强类型配置校验
"""
from .message_processor import StreamMessageProcessor, send_qmt_signal_to_stream
from .logger import QMTLogger, get_qmt_logger
from .config_models import (
QMTConfig,
QMTTerminalConfig,
StrategyConfig,
RedisConfig,
AutoReconnectConfig,
load_config,
ConfigError,
)
__all__ = [
"StreamMessageProcessor",
"send_qmt_signal_to_stream",
"QMTLogger",
"get_qmt_logger",
"QMTConfig",
"QMTTerminalConfig",
"StrategyConfig",
"RedisConfig",
"AutoReconnectConfig",
"load_config",
"ConfigError",
]
"""
QMT (Quantitative Trading) Module
提供量化交易相关的功能:
- Redis Stream 消息处理
- 交易信号发送

View File

@@ -6,6 +6,101 @@ import logging
import datetime
import uvicorn
from .qmt_engine import QMTEngine, ConfigError
from .api_server import create_api_server
def setup_logger():
"""配置日志系统"""
log_dir = "logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
log_file = os.path.join(log_dir, f"{datetime.date.today().strftime('%Y-%m-%d')}.log")
logger = logging.getLogger("QMT_Main")
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8')
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger
def main():
"""主函数 - 启动QMT交易引擎和API服务器"""
logger = setup_logger()
logger.info("="*50)
logger.info(">>> QMT交易系统启动中...")
logger.info("="*50)
# 创建QMT引擎实例
engine = QMTEngine()
logger.info("QMT引擎实例创建成功")
try:
# 初始化引擎
engine.initialize('config.json')
logger.info("✅ QMT引擎初始化成功")
except ConfigError as e:
logger.error(f"❌ 配置校验失败: {str(e)}")
logger.error("请检查 config.json 配置文件")
sys.exit(1)
except Exception as e:
logger.error(f"❌ QMT引擎初始化失败: {str(e)}", exc_info=True)
sys.exit(1)
# 启动交易线程
trading_thread = threading.Thread(target=engine.run_trading_loop, daemon=True)
trading_thread.start()
logger.info("✅ 交易线程启动成功")
# 创建API服务器
app = create_api_server(engine)
logger.info("✅ API服务器创建成功")
# 启动Web服务
logger.info(">>> Web服务启动: http://localhost:8001")
try:
uvicorn.run(
app,
host="0.0.0.0",
port=8001,
log_level="warning",
access_log=False
)
except KeyboardInterrupt:
logger.info(">>> 正在关闭系统...")
engine.stop()
logger.info(">>> 系统已关闭")
except Exception as e:
logger.error(f">>> 系统异常: {str(e)}", exc_info=True)
engine.stop()
logger.info(">>> 系统已关闭")
if __name__ == '__main__':
# 使用 -u 参数运行是最佳实践: python -u main.py
# 但这里也在代码里强制 flush 了
main()
import threading
import sys
import os
import logging
import datetime
import uvicorn
from .qmt_engine import QMTEngine
from .api_server import create_api_server

View File

@@ -17,6 +17,34 @@ from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from xtquant.xttype import StockAccount
from xtquant import xtconstant
# 导入 Redis Stream 消息处理器和日志模块
try:
from .message_processor import StreamMessageProcessor
from .logger import QMTLogger
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
import time
import datetime
import traceback
import sys
import json
import os
import threading
import logging
from typing import Optional, Dict, Any, List
from dataclasses import dataclass
from dateutil.parser import parse as parse_time
import redis
from xtquant import xtdata
from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from xtquant.xttype import StockAccount
from xtquant import xtconstant
# 导入 Redis Stream 消息处理器和日志模块
try:
from .message_processor import StreamMessageProcessor
@@ -394,12 +422,13 @@ class UnitCallback(XtQuantTraderCallback):
class TradingUnit:
"""终端实例执行单元,负责管理单个 QMT 进程"""
def __init__(self, t_cfg):
self.qmt_id = t_cfg["qmt_id"]
self.alias = t_cfg.get("alias", self.qmt_id)
self.path = t_cfg["path"]
self.account_id = t_cfg["account_id"]
self.account_type = t_cfg["account_type"]
def __init__(self, t_cfg: QMTTerminalConfig):
self.qmt_id = t_cfg.qmt_id
self.alias = t_cfg.alias or t_cfg.qmt_id
self.path = t_cfg.path
self.account_id = t_cfg.account_id
self.account_type = t_cfg.account_type
self.xt_trader = None
self.acc_obj = None
@@ -474,6 +503,19 @@ class MultiEngineManager:
return cls._instance
def __init__(self):
if hasattr(self, "_initialized"):
return
self.units: Dict[str, TradingUnit] = {}
self.config: Optional[QMTConfig] = None
self.is_running = True
self.start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.is_scheduled_reconnecting = False # 定时重连调度器正在执行标志
self._initialized = True
# Stream 处理器和日志器将在 initialize 中创建
self.stream_processor: Optional[StreamMessageProcessor] = None
self.qmt_logger: Optional[QMTLogger] = None
self.r = None
self.pos_manager = None
if hasattr(self, "_initialized"):
return
self.units: Dict[str, TradingUnit] = {}
@@ -488,8 +530,19 @@ class MultiEngineManager:
def initialize(self, config_file="config.json"):
self._setup_logger() # 先初始化 logger
with open(config_file, "r", encoding="utf-8") as f:
self.config = json.load(f)
# 使用新的配置模型加载配置
try:
self.config = load_config(config_file)
self.logger.info(f"✅ 配置加载成功: {config_file}")
self.logger.info(f" - 终端数量: {len(self.config.qmt_terminals)}")
self.logger.info(f" - 策略数量: {len(self.config.strategies)}")
except ConfigError as e:
self.logger.error(f"❌ 配置错误: {e}")
raise
except Exception as e:
self.logger.error(f"❌ 配置加载失败: {e}")
raise
# 从 .env.local 加载 Redis 配置
redis_config = self._load_redis_config()
@@ -501,10 +554,11 @@ class MultiEngineManager:
self.qmt_logger = QMTLogger(name="QMT_Engine_Stream")
self.logger.info("Redis Stream 处理器初始化完成")
for t_cfg in self.config.get("qmt_terminals", []):
for t_cfg in self.config.qmt_terminals:
unit = TradingUnit(t_cfg)
unit.connect()
self.units[unit.qmt_id] = unit
def _load_redis_config(self) -> Dict[str, Any]:
"""从 .env.local 加载 Redis 配置"""
# 尝试加载 python-dotenv
@@ -563,6 +617,9 @@ class MultiEngineManager:
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()
@@ -667,7 +724,8 @@ class MultiEngineManager:
"130000" <= curr_hms <= "150030"
)
if is_trading:
for s_name in self.config["strategies"].keys():
for s_name in self.config.strategies.keys():
self.process_route(s_name)
self.process_route(s_name)
# --- 收盘结算与标志位重置 ---
@@ -691,7 +749,13 @@ class MultiEngineManager:
从 Redis Stream 消费消息,处理成功后 ACK失败则进入失败队列。
"""
strat_cfg = self.config["strategies"].get(strategy_name)
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.warning(f"[{strategy_name}] 策略配置不存在")
return
unit = self.units.get(strat_cfg.qmt_id)
if not unit or not unit.callback or not unit.callback.is_connected:
return
unit = self.units.get(strat_cfg.get("qmt_id"))
if not unit or not unit.callback or not unit.callback.is_connected:
return
@@ -748,7 +812,12 @@ class MultiEngineManager:
action = data.get("action")
# 获取策略配置,确定下单模式
strat_cfg = self.config["strategies"].get(strategy_name, {})
# 获取策略配置,确定下单模式
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.warning(f"[{strategy_name}] 策略配置不存在")
order_mode = strat_cfg.order_mode
order_mode = strat_cfg.order_mode
order_mode = strat_cfg.get("order_mode", "slots")
if action == "BUY":
@@ -830,7 +899,482 @@ class MultiEngineManager:
self.logger.error(f"[process_route] 消费消息异常: {str(e)}", exc_info=True)
def _execute_buy(self, unit, strategy_name, data):
strat_cfg = self.config["strategies"][strategy_name]
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
# 1. 槽位校验
if data["total_slots"] != strat_cfg.total_slots:
self.logger.error(
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
):
return
try:
asset = unit.xt_trader.query_stock_asset(unit.acc_obj)
# 计算该终端的总槽位之和
terminal_strategies = self.get_strategies_by_terminal(unit.qmt_id)
# 计算加权槽位总和(支持策略权重配置)
# 权重默认为 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
for s in terminal_strategies
if self.config.get_strategy(s)
)
if not asset or total_weighted_slots <= 0:
return
# 获取当前策略的权重
weight = strat_cfg.weight
# 4. 资金加权分配 (基于该终端总资产)
total_equity = asset.cash + asset.market_value
target_amt = total_equity * weight / total_weighted_slots
actual_amt = min(target_amt, asset.cash * 0.98) # 预留手续费滑点
if actual_amt < 2000:
self.logger.warning(
f"[{strategy_name}] 单笔预算 {actual_amt:.2f} 不足 2000 元,取消买入"
)
return
# 4. 价格与股数
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
if vol < 100:
return
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PyBuy",
)
if oid != -1:
unit.order_cache[oid] = (strategy_name, data["stock_code"], "BUY")
self.pos_manager.mark_holding(strategy_name, data["stock_code"])
self.logger.info(
f"√√√ [{unit.alias}] {strategy_name} 下单买入: {data['stock_code']} {vol}股 @ {price}"
)
# 记录订单执行成功
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
def _execute_sell(self, unit, strategy_name, data):
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
# 1. 查询实盘持仓(一切以实盘为准)
real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj)
rp = (
next((p for p in real_pos if p.stock_code == data["stock_code"]), None)
if real_pos
else None
)
can_use = rp.can_use_volume if rp else 0
# 2. 检查虚拟持仓
v_vol = self.pos_manager.get_position(strategy_name, data["stock_code"])
# 3. 实盘无持仓 -> 拒绝卖出(清理幽灵持仓)
if can_use <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 实盘无可用持仓"
)
# 如果虚拟持仓存在但实盘已清仓,清理幽灵持仓
if v_vol > 0:
self.pos_manager.force_delete(strategy_name, data["stock_code"])
self.logger.info(
f"[{strategy_name}] 已清理幽灵持仓: {data['stock_code']} 虚拟{v_vol}"
)
return
# 4. 实盘有持仓 -> 必须卖出(取虚拟和实盘的最小值,虚拟无持仓则取实盘)
if v_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出提醒: {data['stock_code']} 虚拟无持仓但实盘有{can_use}股,以实盘为准执行卖出"
)
final_vol = min(v_vol, can_use) if v_vol > 0 else can_use
if final_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 计算后卖出量为0"
)
return
try:
offset = strat_cfg.execution.sell_price_offset
price = round(float(data["price"]) + offset, 3)
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_SELL,
final_vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySell",
)
if oid != -1:
unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL")
self.logger.info(
f"√√√ [{unit.alias}] {strategy_name} 下单卖出: {data['stock_code']} {final_vol}股 @ {price}"
)
# 记录订单执行成功
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
def _execute_percentage_buy(self, unit, strategy_name, data):
"""处理百分比模式的买入逻辑"""
strat_cfg = self.config.get_strategy(strategy_name)
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})")
return
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")
return
total_asset = asset.total_asset
available_cash = asset.cash
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}")
# 检查最小金额限制
if actual_amount < 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")
price = 1.0
# 计算股数
vol = int(actual_amount / price / 100) * 100
self.logger.info(f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}")
if vol < 100:
self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})")
return
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] [百分比模式] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
def _execute_percentage_sell(self, unit, strategy_name, data):
"""处理百分比模式的卖出逻辑(清仓)"""
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
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")
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}")
if can_use <= 0:
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_SELL,
can_use,
xtconstant.FIX_PRICE,
price,
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] [百分比模式] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
def _execute_sell(self, unit, strategy_name, data):
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
# 1. 查询实盘持仓(一切以实盘为准)
real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj)
rp = (
next((p for p in real_pos if p.stock_code == data["stock_code"]), None)
if real_pos
else None
)
can_use = rp.can_use_volume if rp else 0
# 2. 检查虚拟持仓
v_vol = self.pos_manager.get_position(strategy_name, data["stock_code"])
# 3. 实盘无持仓 -> 拒绝卖出(清理幽灵持仓)
if can_use <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 实盘无可用持仓"
)
# 如果虚拟持仓存在但实盘已清仓,清理幽灵持仓
if v_vol > 0:
self.pos_manager.force_delete(strategy_name, data["stock_code"])
self.logger.info(
f"[{strategy_name}] 已清理幽灵持仓: {data['stock_code']} 虚拟{v_vol}"
)
return
# 4. 实盘有持仓 -> 必须卖出(取虚拟和实盘的最小值,虚拟无持仓则取实盘)
if v_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出提醒: {data['stock_code']} 虚拟无持仓但实盘有{can_use}股,以实盘为准执行卖出"
)
final_vol = min(v_vol, can_use) if v_vol > 0 else can_use
if final_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 计算后卖出量为0"
)
return
try:
offset = strat_cfg.execution.sell_price_offset
price = round(float(data["price"]) + offset, 3)
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_SELL,
final_vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySell",
)
if oid != -1:
unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL")
self.logger.info(
f"√√√ [{unit.alias}] {strategy_name} 下单卖出: {data['stock_code']} {final_vol}股 @ {price}"
)
# 记录订单执行成功
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
"""处理百分比模式的卖出逻辑(清仓)"""
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
self.logger.info(f"[{strategy_name}] [百分比模式] 处理卖出: {data['stock_code']} (清仓)")
# 1. 槽位校验
if data["total_slots"] != strat_cfg["total_slots"]:
self.logger.error(
@@ -933,6 +1477,98 @@ class MultiEngineManager:
self.logger.error(traceback.format_exc())
def _execute_sell(self, unit, strategy_name, data):
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
# 1. 查询实盘持仓(一切以实盘为准)
real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj)
rp = (
next((p for p in real_pos if p.stock_code == data["stock_code"]), None)
if real_pos
else None
)
can_use = rp.can_use_volume if rp else 0
# 2. 检查虚拟持仓
v_vol = self.pos_manager.get_position(strategy_name, data["stock_code"])
# 3. 实盘无持仓 -> 拒绝卖出(清理幽灵持仓)
if can_use <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 实盘无可用持仓"
)
# 如果虚拟持仓存在但实盘已清仓,清理幽灵持仓
if v_vol > 0:
self.pos_manager.force_delete(strategy_name, data["stock_code"])
self.logger.info(
f"[{strategy_name}] 已清理幽灵持仓: {data['stock_code']} 虚拟{v_vol}"
)
return
# 4. 实盘有持仓 -> 必须卖出(取虚拟和实盘的最小值,虚拟无持仓则取实盘)
if v_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出提醒: {data['stock_code']} 虚拟无持仓但实盘有{can_use}股,以实盘为准执行卖出"
)
final_vol = min(v_vol, can_use) if v_vol > 0 else can_use
if final_vol <= 0:
self.logger.warning(
f"[{strategy_name}] 卖出拦截: {data['stock_code']} 计算后卖出量为0"
)
return
try:
offset = strat_cfg.execution.sell_price_offset
price = round(float(data["price"]) + offset, 3)
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_SELL,
final_vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySell",
)
if oid != -1:
unit.order_cache[oid] = (strategy_name, data["stock_code"], "SELL")
self.logger.info(
f"√√√ [{unit.alias}] {strategy_name} 下单卖出: {data['stock_code']} {final_vol}股 @ {price}"
)
# 记录订单执行成功
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=final_vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
# 1. 查询实盘持仓(一切以实盘为准)
real_pos = unit.xt_trader.query_stock_positions(unit.acc_obj)
rp = (
@@ -1028,6 +1664,181 @@ class MultiEngineManager:
def _execute_percentage_buy(self, unit, strategy_name, data):
"""处理百分比模式的买入逻辑"""
strat_cfg = self.config.get_strategy(strategy_name)
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})")
return
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")
return
total_asset = asset.total_asset
available_cash = asset.cash
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}")
# 检查最小金额限制
if actual_amount < 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")
price = 1.0
# 计算股数
vol = int(actual_amount / price / 100) * 100
self.logger.info(f"[{strategy_name}] [百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}")
if vol < 100:
self.logger.warning(f"[{strategy_name}] [百分比模式] 拦截买入: 股数不足 100 ({vol})")
return
# 记录订单执行请求
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] [百分比模式] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="BUY",
volume=vol,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
def _execute_percentage_sell(self, unit, strategy_name, data):
"""处理百分比模式的卖出逻辑(清仓)"""
strat_cfg = self.config.get_strategy(strategy_name)
if not strat_cfg:
self.logger.error(f"[{strategy_name}] 策略配置不存在")
return
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")
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}")
if can_use <= 0:
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
)
oid = unit.xt_trader.order_stock(
unit.acc_obj,
data["stock_code"],
xtconstant.STOCK_SELL,
can_use,
xtconstant.FIX_PRICE,
price,
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.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
order_id=oid,
)
else:
error_msg = "下单请求被拒绝 (Result=-1)"
self.logger.error(f"[{strategy_name}] [百分比模式] {error_msg}")
self.qmt_logger.log_order_execution(
strategy_name=strategy_name,
stock_code=data["stock_code"],
action="SELL",
volume=can_use,
price=price,
error=error_msg,
)
except:
self.logger.error(traceback.format_exc())
"""处理百分比模式的买入逻辑"""
strat_cfg = self.config["strategies"][strategy_name]
# 获取目标持仓百分比