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

@@ -58,7 +58,13 @@ QMT 模块是 NewStock 量化交易系统的实盘交易执行模块,通过 `x
交易信号通过 Redis 消息队列传递,每个策略对应一个独立的队列。消息格式为 JSON 对象,包含股票代码、操作类型、价格、时间戳等字段。系统对每条消息进行严格校验,包括日期校验、时间戳校验、必填字段校验等,确保只有当天的有效指令才会被执行。
买入逻辑支持槽位控制,通过 `total_slots` 参数限制同时持有的股票数量。系统根据可用资金和目标槽位自动计算每只股票的买入数量,金额过小或股数不足的请求会被拦截。卖出逻辑则根据 Redis 中的虚拟持仓和实盘可用持仓计算实际卖出数量,确保不会超卖。
买入逻辑支持两种模式:
1. **槽位控制模式**:通过 `total_slots` 参数限制同时持有的股票数量。系统根据可用资金和目标槽位自动计算每只股票的买入数量,金额过小或股数不足的请求会被拦截。
2. **仓位百分比模式**:通过 `position_pct` 参数指定目标持仓占账户总资产的比例。系统根据账户总资产计算目标金额,然后转换为具体股数进行下单。该模式无持仓数量限制。
卖出逻辑根据策略配置模式有所不同:槽位模式下根据 Redis 中的虚拟持仓和实盘可用持仓计算实际卖出数量;百分比模式下执行清仓操作。
### 3.3 持仓管理
@@ -193,9 +199,41 @@ QMT 模块是 NewStock 量化交易系统的实盘交易执行模块,通过 `x
| `redis` | Object | Redis 连接配置,支持 host、port、db、password 等参数 |
| `qmt_terminals` | Array | QMT 终端列表,每个终端包含唯一标识、别名、路径、账户信息 |
| `strategies` | Object | 策略配置,键为策略名,值包含使用的终端 ID 和执行参数 |
| `total_slots` | Integer | 策略的最大持仓股票数量 |
| `total_slots` | Integer | 策略的最大持仓股票数量(槽位模式) |
| `order_mode` | String | 下单模式,可选 `slots`(槽位)或 `percentage`(百分比),默认为 `slots` |
| `auto_reconnect` | Object | 自动重连配置,包含启用状态和执行时间 |
**槽位模式配置示例:**
```json
{
"strategies": {
"strategy_a": {
"qmt_id": "terminal_001",
"order_mode": "slots",
"total_slots": 5,
"execution": {
"buy_amount_per_stock": 20000,
"min_buy_amount": 2000
}
}
}
}
```
**百分比模式配置示例:**
```json
{
"strategies": {
"strategy_b": {
"qmt_id": "terminal_001",
"order_mode": "percentage"
}
}
}
```
## 6. Web 仪表盘功能
### 6.1 功能概览
@@ -317,7 +355,152 @@ Web 仪表盘基于 Vue 3 和 Naive UI 组件库开发,提供可视化的系
| 聚宽 | `.XSHE` / `.XSHG` | `000001.XSHE``600519.XSHG` |
| QMT | `.SZ` / `.SH` | `000001.SZ``600519.SH` |
## 8. 系统架构
## 8. 百分比下单信号发送端qmt_percentage_sender.py
### 8.1 模块定位
`qmt_percentage_sender.py` 是基于**仓位百分比**的 QMT 信号发送端,与槽位模式的 `qmt_signal_sender.py` 并行存在。该模块用于配置为 `order_mode: "percentage"` 的策略,通过指定目标持仓占账户总资产的比例来触发交易。
### 8.2 核心函数
#### `send_qmt_percentage_signal(code, position_pct, action, price, is_backtest, timestamp, redis_config)`
| 参数 | 类型 | 说明 |
|------|------|------|
| `code` | str | 股票代码,聚宽格式(如 `000001.XSHE``600519.XSHG` |
| `position_pct` | float | 目标持仓占总资产的比例,范围 0.0 ~ 1.0(如 0.2 表示 20% |
| `action` | str | 交易动作,固定为 `"BUY"``"SELL"` |
| `price` | float | 当前最新价格,用于实盘限价单参考 |
| `is_backtest` | bool | 是否为回测模式True/False |
| `timestamp` | str | 时间戳字符串,格式 `"YYYY-MM-DD HH:MM:SS"` |
| `redis_config` | dict | Redis 连接配置,包含 `host``port``password``db``strategy_name` 等字段 |
### 8.3 处理流程
```
策略触发信号
1. 环境判断与流量控制
├─ 实盘模式 → 直接通过
└─ 回测模式 → 限制最多发送 10 条(防止回测刷爆队列)
2. 建立 Redis 连接socket_timeout=1s
3. 数据转换与规范化
└─ 股票代码格式转换:.XSHE → .SZ.XSHG → .SH
4. 构建 JSON 消息体(包含 position_pct 字段)
5. 队列路由
├─ 回测 → {strategy_name}_backtestTTL: 1 小时)
└─ 实盘 → {strategy_name}_realTTL: 7 天)
6. 控制台日志输出
```
### 8.4 消息格式
发送到 Redis 队列的 JSON 消息结构:
```json
{
"strategy_name": "my_strategy",
"stock_code": "000001.SZ",
"action": "BUY",
"price": 15.50,
"position_pct": 0.2,
"timestamp": "2026-02-17 14:30:00",
"is_backtest": false
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `strategy_name` | str | 策略名称,来自 `redis_config['strategy_name']`,用于队列路由和持仓管理 |
| `stock_code` | str | QMT 格式的股票代码(`.SZ` / `.SH` |
| `action` | str | 交易动作,`BUY``SELL` |
| `price` | float | 信号触发时的最新价格 |
| `position_pct` | float | 目标持仓占账户总资产的比例,范围 0.0 ~ 1.0 |
| `timestamp` | str | 信号生成时间,格式 `YYYY-MM-DD HH:MM:SS` |
| `is_backtest` | bool | 是否为回测环境发出的信号 |
### 8.5 买卖意图判定逻辑
与槽位模式不同,百分比模式需要**显式指定**交易动作:
- **`action = "BUY"`**:策略意向买入该股票,目标持仓占比为 `position_pct`。交易引擎根据账户总资产计算目标金额,然后转换为具体股数下单。
- **`action = "SELL"`**:策略意向清仓该股票。百分比模式下卖出采用简化逻辑,直接执行清仓操作。
### 8.6 买入计算公式
当 QMT 端接收到百分比模式的买入信号时,按以下公式计算买入股数:
```
目标金额 = 账户总资产 × position_pct
可用金额 = min(目标金额, 可用资金)
买入股数 = int(可用金额 / 价格 / 100) × 100
```
**边界处理:**
- 单笔金额 < 2000 元 → 拦截不下单
- 计算股数 < 100 股 → 拦截不下单
- 价格 ≤ 0 → 强制设为 1.0(仅测试用)
### 8.7 与槽位模式的对比
| 特性 | 槽位模式 (slots) | 百分比模式 (percentage) |
|------|------------------|------------------------|
| 核心参数 | `total_slots` | `position_pct` |
| 持仓限制 | 有(同时持仓数量限制) | 无 |
| 资金分配 | 按剩余槽位均分资金 | 按总资产比例计算 |
| 卖出逻辑 | 按持仓数量计算 | 清仓 |
| 配置方式 | 配置文件设置 `order_mode: "slots"` | 配置文件设置 `order_mode: "percentage"` |
| 信号发送 | `send_qmt_signal()` | `send_qmt_percentage_signal()` |
### 8.8 使用示例
```python
from qmt_percentage_sender import send_qmt_percentage_signal
# Redis 配置
redis_config = {
"host": "localhost",
"port": 6379,
"password": None,
"db": 0,
"strategy_name": "MyPercentageStrategy"
}
# 买入信号:目标持仓占账户总资产的 20%
send_qmt_percentage_signal(
code="000001.XSHE",
position_pct=0.2,
action="BUY",
price=15.5,
is_backtest=False,
timestamp="2026-02-17 14:30:00",
redis_config=redis_config
)
# 卖出信号:清仓
send_qmt_percentage_signal(
code="000001.XSHE",
position_pct=0,
action="SELL",
price=15.8,
is_backtest=False,
timestamp="2026-02-17 14:35:00",
redis_config=redis_config
)
```
## 9. 系统架构
### 8.1 组件关系图
@@ -400,7 +583,7 @@ Web 仪表盘基于 Vue 3 和 Naive UI 组件库开发,提供可视化的系
7. **订单执行**:调用 QMT API 下单,成功则缓存订单信息
8. **状态更新**:标记虚拟持仓,异步等待成交回调
## 9. 启动与停止
## 10. 启动与停止
### 9.1 Windows 启动
@@ -427,7 +610,7 @@ python run.py
|------|----------|------|
| API 服务 | 8001 | Web 仪表盘和 RESTful API 监听端口 |
## 10. 注意事项
## 11. 注意事项
1. **QMT 终端要求**:确保 QMT 终端已登录且路径配置正确
2. **Redis 服务**:系统依赖 Redis 运行,请确保 Redis 服务可用

View File

@@ -0,0 +1,102 @@
import redis
import json
# --- 模块级全局变量 ---
_BACKTEST_SEND_COUNT = 0
def send_qmt_percentage_signal(
code, position_pct, action, price, is_backtest, timestamp, redis_config
):
"""
发送基于仓位百分比的信号到 Redis
参数:
- code: 股票代码 (聚宽格式: 000001.XSHE)
- position_pct: 目标持仓占总资产的比例 (0.0 ~ 1.0,如 0.2 表示 20%)
- action: 交易动作,"BUY""SELL"
- price: 当前最新价格 (用于实盘限价单参考)
- is_backtest: 是否为回测模式 (True/False)
- timestamp: 时间戳字符串,格式 "YYYY-MM-DD HH:MM:SS"
- redis_config: Redis配置字典包含 host, port, password, db, strategy_name
"""
global _BACKTEST_SEND_COUNT
try:
# ---------------------------------------------------------
# 1. 环境判断与流量控制
# ---------------------------------------------------------
if is_backtest:
if _BACKTEST_SEND_COUNT >= 10:
return
_BACKTEST_SEND_COUNT += 1
# ---------------------------------------------------------
# 2. 建立 Redis 连接
# ---------------------------------------------------------
r = redis.Redis(
host=redis_config["host"],
port=redis_config["port"],
password=redis_config.get("password"),
db=redis_config.get("db", 0),
decode_responses=True,
socket_timeout=1,
)
# ---------------------------------------------------------
# 3. 数据转换与规范化
# ---------------------------------------------------------
# 股票代码格式转换: 聚宽(.XSHE/.XSHG) -> QMT(.SZ/.SH)
qmt_code = code
if code.endswith(".XSHE"):
qmt_code = code.replace(".XSHE", ".SZ")
elif code.endswith(".XSHG"):
qmt_code = code.replace(".XSHG", ".SH")
# 校验 action 参数
if action not in ["BUY", "SELL"]:
print(f"[Error] 无效的 action 参数: {action},必须是 'BUY''SELL'")
return
# ---------------------------------------------------------
# 4. 构建消息体
# ---------------------------------------------------------
base_strategy_name = redis_config.get("strategy_name", "default_strategy")
msg = {
"strategy_name": base_strategy_name,
"stock_code": qmt_code,
"action": action,
"price": price,
"position_pct": float(position_pct),
"timestamp": timestamp,
"is_backtest": is_backtest,
}
json_payload = json.dumps(msg)
# ---------------------------------------------------------
# 5. 队列路由
# ---------------------------------------------------------
queue_key = (
f"{base_strategy_name}_backtest"
if is_backtest
else f"{base_strategy_name}_real"
)
expire_seconds = 3600 if is_backtest else 604800
r.rpush(queue_key, json_payload)
r.expire(queue_key, expire_seconds)
# ---------------------------------------------------------
# 6. 控制台输出
# ---------------------------------------------------------
log_prefix = "【回测】" if is_backtest else "【实盘】"
pct_display = f"{position_pct * 100:.1f}%"
desc = f"目标仓位:{pct_display}" if action == "BUY" else "清仓"
print(
f"{log_prefix} 百分比信号 -> {qmt_code} | 动作:{action} | {desc} | 价格:{price} | 时间:{timestamp}"
)
except Exception as e:
print(f"[Error] 发送QMT百分比信号失败: {e}")

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":