feat: 完善 QMT 交易模块
This commit is contained in:
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user