feat: 完善 QMT 交易模块

This commit is contained in:
2026-02-24 13:06:14 +08:00
parent 29706da299
commit 5628fbb34c
13 changed files with 1249 additions and 5368 deletions

View File

@@ -285,7 +285,251 @@ class MyXtQuantTraderCallback(XtQuantTraderCallback):
# ================= 5. 核心消息处理 (重写版:拒绝静默失败) =================
def process_strategy_queue(strategy_name, r_client, xt_trader, acc, pos_manager):
def get_strategy_config(strategy_name, config):
"""获取策略配置,支持新旧配置格式兼容"""
strategies = config.get("strategies", {})
# 如果 strategies 是列表(旧格式),转换为默认配置
if isinstance(strategies, list):
return {"order_mode": "slots", "total_slots": 5}
# 获取策略配置
strategy_config = strategies.get(strategy_name, {})
# 设置默认值
result = {
"order_mode": strategy_config.get("order_mode", "slots"),
"total_slots": strategy_config.get("total_slots", 5),
}
return result
def process_percentage_buy(
strategy_name, stock_code, price, position_pct, xt_trader, acc
):
"""处理百分比模式的买入逻辑"""
logger.info(f"[百分比模式] 处理买入: {stock_code}, 目标占比: {position_pct}")
# 查询资产
asset = xt_trader.query_stock_asset(acc)
if not asset:
logger.error("API 错误: query_stock_asset 返回 None可能是 QMT 断连或未同步")
return
total_asset = asset.total_asset
available_cash = asset.cash
logger.info(
f"[百分比模式] 账户总资产: {total_asset:.2f}, 可用资金: {available_cash:.2f}"
)
# 计算目标金额
target_amount = total_asset * position_pct
actual_amount = min(target_amount, available_cash)
logger.info(
f"[百分比模式] 目标金额: {target_amount:.2f}, 实际可用: {actual_amount:.2f}"
)
# 检查最小金额限制
if actual_amount < 2000:
logger.warning(f"[百分比模式] 拦截买入: 金额过小 ({actual_amount:.2f} < 2000)")
return
# 价格校验
if price <= 0:
logger.warning(
f"[百分比模式] 价格异常: {price}强制设为1.0以计算股数(仅测试用)"
)
price = 1.0
# 计算股数
vol = int(actual_amount / price / 100) * 100
logger.info(
f"[百分比模式] 计算股数: 资金{actual_amount:.2f} / 价格{price} -> {vol}"
)
if vol < 100:
logger.warning(f"[百分比模式] 拦截买入: 股数不足 100 ({vol})")
return
# 执行下单
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PyBuyPct",
)
if oid != -1:
logger.info(f"[百分比模式] √√√ 下单成功: ID={oid} {stock_code} 买入 {vol}")
ORDER_CACHE[oid] = (strategy_name, stock_code, "BUY")
else:
logger.error(
f"[百分比模式] XXX 下单请求被拒绝 (Result=-1),请检查 QMT 终端报错"
)
def process_percentage_sell(strategy_name, stock_code, price, xt_trader, acc):
"""处理百分比模式的卖出逻辑(清仓)"""
logger.info(f"[百分比模式] 处理卖出: {stock_code} (清仓)")
# 查询实盘持仓
real_pos = xt_trader.query_stock_positions(acc)
if real_pos is None:
logger.error("[百分比模式] API 错误: query_stock_positions 返回 None")
return
rp = next((p for p in real_pos if p.stock_code == stock_code), None)
can_use = rp.can_use_volume if rp else 0
logger.info(f"[百分比模式] 股票 {stock_code} 实盘可用持仓: {can_use}")
if can_use <= 0:
logger.warning(f"[百分比模式] 拦截卖出: 无可用持仓")
return
# 执行清仓
logger.info(f"[百分比模式] 执行清仓: {stock_code} @ {price}, 数量: {can_use}")
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_SELL,
can_use,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySellPct",
)
if oid != -1:
logger.info(f"[百分比模式] √√√ 下单成功: ID={oid} {stock_code} 卖出 {can_use}")
ORDER_CACHE[oid] = (strategy_name, stock_code, "SELL")
else:
logger.error(f"[百分比模式] XXX 下单请求被拒绝 (Result=-1)")
def process_slots_buy(
strategy_name, stock_code, price, total_slots, xt_trader, acc, pos_manager
):
"""处理槽位模式的买入逻辑(原有逻辑保持不变)"""
holding = pos_manager.get_holding_count(strategy_name)
empty = total_slots - holding
logger.info(
f"[槽位模式] 检查持仓: 当前占用 {holding} / 总槽位 {total_slots} -> 剩余 {empty}"
)
if empty <= 0:
logger.warning(f"[槽位模式] 拦截买入: 槽位已满,不执行下单")
return
# 查询资金
asset = xt_trader.query_stock_asset(acc)
if not asset:
logger.error(
"[槽位模式] API 错误: query_stock_asset 返回 None可能是 QMT 断连或未同步"
)
return
logger.info(f"[槽位模式] 当前可用资金: {asset.cash:.2f}")
amt = asset.cash / empty
if amt < 2000:
logger.warning(f"[槽位模式] 拦截买入: 单笔金额过小 ({amt:.2f} < 2000)")
return
if price <= 0:
logger.warning(f"[槽位模式] 价格异常: {price}强制设为1.0以计算股数(仅测试用)")
price = 1.0
vol = int(amt / price / 100) * 100
logger.info(f"[槽位模式] 计算股数: 资金{amt:.2f} / 价格{price} -> {vol}")
if vol < 100:
logger.warning(f"[槽位模式] 拦截买入: 股数不足 100 ({vol})")
return
# 执行下单
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PyBuy",
)
if oid != -1:
logger.info(f"[槽位模式] √√√ 下单成功: ID={oid} {stock_code} 买入 {vol}")
ORDER_CACHE[oid] = (strategy_name, stock_code, "BUY")
pos_manager.mark_holding(strategy_name, stock_code)
else:
logger.error(f"[槽位模式] XXX 下单请求被拒绝 (Result=-1),请检查 QMT 终端报错")
def process_slots_sell(strategy_name, stock_code, price, xt_trader, acc, pos_manager):
"""处理槽位模式的卖出逻辑(原有逻辑保持不变)"""
v_vol = pos_manager.get_position(strategy_name, stock_code)
logger.info(f"[槽位模式] 卖出 - Redis 记录虚拟持仓: {v_vol}")
if v_vol > 0:
logger.info(f"[槽位模式] 卖出 - 正在查询实盘持仓: {stock_code}")
real_pos = xt_trader.query_stock_positions(acc)
logger.info(
f"[槽位模式] 卖出 - 实盘持仓查询完成,获取到 {len(real_pos) if real_pos else 0} 条记录"
)
if real_pos is None:
logger.error("[槽位模式] API 错误: query_stock_positions 返回 None")
return
rp = next((p for p in real_pos if p.stock_code == stock_code), None)
can_use = rp.can_use_volume if rp else 0
logger.info(f"[槽位模式] 卖出 - 股票 {stock_code} 实盘可用持仓: {can_use}")
final = min(v_vol, can_use)
logger.info(f"[槽位模式] 卖出 - 计算卖出量: min({v_vol}, {can_use}) = {final}")
if final > 0:
logger.info(
f"[槽位模式] 卖出 - 执行卖出订单: {stock_code} @ {price}, 数量: {final}"
)
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_SELL,
final,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySell",
)
if oid != -1:
logger.info(
f"[槽位模式] √√√ 下单成功: ID={oid} {stock_code} 卖出 {final}"
)
ORDER_CACHE[oid] = (strategy_name, stock_code, "SELL")
else:
logger.error(f"[槽位模式] XXX 下单请求被拒绝 (Result=-1)")
else:
logger.warning(
f"[槽位模式] 拦截卖出: 最终计算卖出量为 0 (虚拟:{v_vol}, 实盘:{can_use})"
)
else:
logger.warning(f"[槽位模式] 拦截卖出: Redis 中无此持仓记录,忽略")
def process_strategy_queue(
strategy_name, r_client, xt_trader, acc, pos_manager, config
):
queue_key = f"{strategy_name}_real"
# 1. 获取消息
@@ -326,130 +570,62 @@ def process_strategy_queue(strategy_name, r_client, xt_trader, acc, pos_manager)
stock_code = data.get("stock_code")
action = data.get("action")
price = float(data.get("price", 0))
total_slots = int(data.get("total_slots", 1))
if not stock_code or not action:
logger.error(f"缺少关键字段: Code={stock_code}, Action={action}")
return
# 5. 获取策略配置,确定下单模式
strategy_config = get_strategy_config(strategy_name, config)
order_mode = strategy_config.get("order_mode", "slots")
logger.info(
f"解析成功: {action} {stock_code} @ {price}, 目标槽位: {total_slots}"
f"解析成功: {action} {stock_code} @ {price}, 下单模式: {order_mode}"
)
# 5. QMT 存活检查
# 6. QMT 存活检查
if xt_trader is None or acc is None:
logger.error("严重错误: QMT 对象未初始化 (xt_trader is None)")
return
# 6. 买入逻辑
if action == "BUY":
holding = pos_manager.get_holding_count(strategy_name)
empty = total_slots - holding
# 7. 根据下单模式执行相应逻辑
if order_mode == "percentage":
# 百分比模式
position_pct = float(data.get("position_pct", 0))
logger.info(
f"检查持仓: 当前占用 {holding} / 总槽位 {total_slots} -> 剩余 {empty}"
)
if empty <= 0:
logger.warning(f"拦截买入: 槽位已满,不执行下单")
return
# 查询资金
asset = xt_trader.query_stock_asset(acc)
if not asset:
logger.error(
"API 错误: query_stock_asset 返回 None可能是 QMT 断连或未同步"
if action == "BUY":
process_percentage_buy(
strategy_name, stock_code, price, position_pct, xt_trader, acc
)
return
logger.info(f"当前可用资金: {asset.cash:.2f}")
amt = asset.cash / empty
if amt < 2000:
logger.warning(f"拦截买入: 单笔金额过小 ({amt:.2f} < 2000)")
return
if price <= 0:
logger.warning(f"价格异常: {price}强制设为1.0以计算股数(仅测试用)")
price = 1.0
vol = int(amt / price / 100) * 100
logger.info(f"计算股数: 资金{amt:.2f} / 价格{price} -> {vol}")
if vol < 100:
logger.warning(f"拦截买入: 股数不足 100 ({vol})")
return
# 执行下单
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_BUY,
vol,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PyBuy",
)
if oid != -1:
logger.info(f"√√√ 下单成功: ID={oid} {stock_code} 买入 {vol}")
ORDER_CACHE[oid] = (strategy_name, stock_code, "BUY")
pos_manager.mark_holding(strategy_name, stock_code)
else:
logger.error(f"XXX 下单请求被拒绝 (Result=-1),请检查 QMT 终端报错")
# 7. 卖出逻辑
elif action == "SELL":
v_vol = pos_manager.get_position(strategy_name, stock_code)
logger.info(f"卖出 - Redis 记录虚拟持仓: {v_vol}")
if v_vol > 0:
logger.info(f"卖出 - 正在查询实盘持仓: {stock_code}")
real_pos = xt_trader.query_stock_positions(acc)
logger.info(
f"卖出 - 实盘持仓查询完成,获取到 {len(real_pos) if real_pos else 0} 条记录"
elif action == "SELL":
process_percentage_sell(
strategy_name, stock_code, price, xt_trader, acc
)
if real_pos is None:
logger.error("API 错误: query_stock_positions 返回 None")
return
rp = next((p for p in real_pos if p.stock_code == stock_code), None)
can_use = rp.can_use_volume if rp else 0
logger.info(f"卖出 - 股票 {stock_code} 实盘可用持仓: {can_use}")
final = min(v_vol, can_use)
logger.info(f"卖出 - 计算卖出量: min({v_vol}, {can_use}) = {final}")
if final > 0:
logger.info(
f"卖出 - 执行卖出订单: {stock_code} @ {price}, 数量: {final}"
)
oid = xt_trader.order_stock(
acc,
stock_code,
xtconstant.STOCK_SELL,
final,
xtconstant.FIX_PRICE,
price,
strategy_name,
"PySell",
)
if oid != -1:
logger.info(f"√√√ 下单成功: ID={oid} {stock_code} 卖出 {final}")
ORDER_CACHE[oid] = (strategy_name, stock_code, "SELL")
else:
logger.error(f"XXX 下单请求被拒绝 (Result=-1)")
else:
logger.warning(
f"拦截卖出: 最终计算卖出量为 0 (虚拟:{v_vol}, 实盘:{can_use})"
)
else:
logger.warning(f"拦截卖出: Redis 中无此持仓记录,忽略")
logger.error(f"未知的 Action: {action}")
else:
logger.error(f"未知的 Action: {action}")
# 槽位模式(默认)
total_slots = int(
data.get("total_slots", strategy_config.get("total_slots", 5))
)
if action == "BUY":
process_slots_buy(
strategy_name,
stock_code,
price,
total_slots,
xt_trader,
acc,
pos_manager,
)
elif action == "SELL":
process_slots_sell(
strategy_name, stock_code, price, xt_trader, acc, pos_manager
)
else:
logger.error(f"未知的 Action: {action}")
except Exception as e:
logger.error(f"消息处理发生未捕获异常: {str(e)}", exc_info=True)
@@ -662,7 +838,12 @@ def trading_loop():
settler.reset_flag()
for s in watch_list:
process_strategy_queue(
s, r, GLOBAL_STATE.xt_trader, GLOBAL_STATE.acc, pos_manager
s,
r,
GLOBAL_STATE.xt_trader,
GLOBAL_STATE.acc,
pos_manager,
CONFIG,
)
elif "150500" <= current_time_str <= "151000":