This commit is contained in:
2026-06-12 16:25:41 +08:00
parent ef4c1cca32
commit 2d8a0c3bca
23 changed files with 2904 additions and 525 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ class SFGridTradeTarget(BaseModel):
init_price = FloatField(null=True) # 建仓成本
grid_match_count = IntegerField(default=0)
grid_total_profit = FloatField(default=0.0)
status = IntegerField(default=0) # -1表示新标的,未完成交易配置,0表示新标的,已完成交易配置,1表示已建初始仓,正常交易中
status = IntegerField(default=0) # 已废弃,改用 strategy_type + grid_index
enabled = BooleanField(default=False) # 是否启动交易线程
strategy_type = IntegerField(default=0) # 0=未分类, 1=网格策略
+58 -117
View File
@@ -25,7 +25,7 @@ from core.sfgrid import bus_events
from core.sfgrid.bus_events import EventTradeTargetUpdate
import core.sfgrid.model as model
from core.eventbus import event_bus
from core.constants import OrderTypeBuy, OrderTypeSell, OrderTypeInit
from core.constants import OrderTypeBuy, OrderTypeSell
from xtquant import xtconstant
from xtquant.xttype import XtOrderError, XtOrderResponse, XtTrade
@@ -65,8 +65,7 @@ class SFGridStrategy:
PrintLog(LogLevel.INFO,
f'|- [DEBUG] 标的{tradeTarget.targetName()} 构造开始: '
f'grid_index={tradeTarget.grid_index}, status={tradeTarget.status}, '
f'enabled={tradeTarget.enabled}')
f'网格={tradeTarget.grid_index}, 启用={tradeTarget.enabled}')
# orderGrid: 网格索引 → 订单编号(seq 或 order_id)的映射
# seq 是 xtquant 返回的下单序号(下单瞬间),order_id 是交易所返回的正式订单号(异步回调后更新)
@@ -149,30 +148,10 @@ class SFGridStrategy:
# 获取当前该标的所有未成交订单
orders = qmtv.queryPendingOrder(self.tradeTarget.stock_code, self.getName()) # type: ignore
# ── 分支1: status=0 未建仓 → 下建仓单 ──
# 条件: 标的尚未建仓 且 不存在正在执行中的建仓单(防止重复建仓)
init_remark = self._make_remark(OrderTypeInit, 1)
if self.tradeTarget.status == 0 and not any(
o.order_remark == init_remark for o in orders
):
# 建仓价取价格网格中最高价(grid_index=0 即列表第一个元素)
price = self.tradeTarget.getPriceGrid()[0]
tmpOrderSeq = qmtv.orderAsync(
str(self.tradeTarget.stock_code),
self.tradeTarget.grid_volume,
xtconstant.STOCK_BUY, # 建仓 = 买入
price,
xtconstant.FIX_PRICE, # 限价单
init_remark,
self.getName(),
)
self.orderGrid[1] = tmpOrderSeq # 建仓单固定在网格索引 1
PrintLog(LogLevel.INFO,
f'|- 标的[{self.tradeTarget.targetName()}] 初始化: '
f'建仓单,建仓价: {price:.3f}')
# ── 分支2: status=1 已建仓 → 下网格买卖单 ──
elif self.tradeTarget.status == 1:
# ── 统一网格逻辑 ──
# grid_index=0 空仓: 只挂买单 @ grid[1],无持仓可卖
# grid_index>0 有仓: 上方挂卖单 @ grid[idx-1],下方挂买单 @ grid[idx+1]
if self.tradeTarget.grid_index >= 0:
currentIdx = self.tradeTarget.grid_index # type: ignore
# --- 上方挂卖出单(空单)---
@@ -272,19 +251,16 @@ class SFGridStrategy:
启用或停用该标的的网格交易
启用时 (enabled=True):
- status=0: 初始化网格索引后调用 refreshGridOrder 下建仓单
- status=1: 检查持仓是否满足当前网格位置要求,满足则刷新网格订单
不满足则回退 enabled=False(风控保护)
- grid_index=0 空仓: 直接调用 refreshGridOrder(只挂买单)
- grid_index>0 有仓: 检查持仓是否满足 grid_volume × grid_index
满足则刷新网格单,不满足则回退 enabled=False(风控保护)
停用时 (enabled=False):
- 取消该标的所有未成交订单,停止交易监控
返回:
更新后的 tradeTarget 对象
"""
PrintLog(LogLevel.INFO,
f" |- [DEBUG] enabledTrading({enabled}) 调用前: "
f"grid_index={self.tradeTarget.grid_index}, status={self.tradeTarget.status}")
f"grid_index={self.tradeTarget.grid_index}")
self.tradeTarget.enabled = enabled # type: ignore
@@ -294,38 +270,23 @@ class SFGridStrategy:
f" |- 标的{self.tradeTarget.targetName()}交易启动, "
f"持仓量:{self.tradeTarget.current_position}")
if self.tradeTarget.status == 0:
# 未建仓状态: 初始化网格索引
if self.tradeTarget.grid_index == 0:
# grid_index=0 表示从未初始化过,设为 1(价格网格最高点建仓)
self.tradeTarget.grid_index = 1 # pyright: ignore[reportAttributeAccessIssue]
PrintLog(LogLevel.INFO,
f" |- 标的{self.tradeTarget.targetName()}初始状态, "
f"设置网格序号 1,")
else:
# grid_index 非零,保留之前设置的值(可能是手动修改的)
PrintLog(LogLevel.INFO,
f" |- 标的{self.tradeTarget.targetName()}初始状态, "
f"保留网格序号 {self.tradeTarget.grid_index},")
if self.tradeTarget.grid_index == 0:
# 空仓: refreshGridOrder 会在 grid[1] 挂第一笔买单
PrintLog(LogLevel.INFO,
f" |- 标的{self.tradeTarget.targetName()}空仓, "
f"等待首次买入建仓")
else:
# 已建仓状态: 检查现有持仓是否满足当前网格位置的仓位需求
# 有仓: 检查现有持仓是否满足当前网格位置的仓位需求
# 最小需求仓位 = 每格股数 × 当前网格索引
# 例: grid_volume=100, grid_index=3 → 需持股 300 股
PrintLog(LogLevel.INFO,
f" |- 标的{self.tradeTarget.targetName()}已有仓位或非初始状态 "
f"无需建初始仓 当前仓位: {self.tradeTarget.current_position} "
f"状态: {self.tradeTarget.status}")
minRequirePosition: int = self.tradeTarget.grid_volume * int(self.tradeTarget.grid_index) # type: ignore
if minRequirePosition <= int(self.tradeTarget.current_position): # type: ignore
# 持仓充足,可以继续网格交易
PrintLog(LogLevel.INFO,
f' |- 仓位检查: 持仓需求充足, '
f'(gridVolume*gridIndex)={minRequirePosition}, '
f'当前持仓:{self.tradeTarget.current_position}')
else:
# 持仓不足(可能是之前部分成交或手动减仓),风控:拒绝启用
PrintLog(LogLevel.INFO,
f' |- 仓位检查: 持仓需求不足, '
f'(gridVolume*gridIndex)={minRequirePosition}, '
@@ -333,7 +294,7 @@ class SFGridStrategy:
f'交易启动失败')
self.tradeTarget.enabled = False # type: ignore
# 无论 status=0 还是 status=1,最终都调用 refreshGridOrder 下对应的单
# 刷新网格订单(空仓只挂买单,有仓买卖对冲)
self.refreshGridOrder()
else:
@@ -429,20 +390,17 @@ class SFGridStrategy:
"""
QMT 委托成交通知回调
成交后:
1. 更新网格索引(卖出上移 / 买入下移)
2. 如果是建仓单成交: status 0→1, 记录建仓价
3. 如果是网格单成交: 累计网格匹配次数和总利润
4. orderGrid 删除已成交订
5. 持久化状态到数据库
6. 调用 refreshGridOrder 挂新的网格单
trade.order_remark 格式: "{type},{gridIdx},{stockCode}"
收到成交后:
1. 判断成交方向(买入下移 / 卖出上移)→ 更新 grid_index
2. 首次建仓(grid_index==0 时成交)→ 记录 init_price
3. 卖出成交 → 累计 grid_match_count 和 grid_total_profit
4. 清理 orderGrid → 持久化 → 刷新网格挂
"""
# ── 过滤:只处理本策略本标的的成交 ──
parsed = self._filter_event(trade.order_remark, trade.strategy_name)
if parsed is None:
return
orderType, gridIdx, _ = parsed
_, gridIdx, _ = parsed # gridIdx: 成交订单对应的网格索引(int)
PrintLog(LogLevel.INFO,
f'|- 委托成交通知'
@@ -451,61 +409,45 @@ class SFGridStrategy:
self.dataUpdateLock.acquire()
try:
desc: str = "" # 用于日志展示的成交类型描述
# ── 分支1: 建仓单成交 ──
if orderType == OrderTypeInit:
PrintLog(LogLevel.INFO,
f'|- 委托成交通知[{self.tradeTarget.targetName()}-{trade.order_id}] '
f'- 建仓单成交')
# 状态切换: 未建仓(0) → 已建仓(1)
self.tradeTarget.status = 1 # type: ignore
# 记录建仓价格
# ── 首次建仓:记录建仓价 ──
# grid_index==0 表示成交前处于空仓状态,这笔成交就是首次建仓
if self.tradeTarget.grid_index == 0:
self.tradeTarget.init_price = trade.traded_price # type: ignore
PrintLog(LogLevel.INFO,
f'|- [DEBUG] 建仓单成交: '
f'grid_index {self.tradeTarget.grid_index} → 1')
# 建仓后网格索引固定为 1(价格网格最高点)
self.tradeTarget.grid_index = 1 # type: ignore
desc = "建仓单"
# ── 分支2: 网格单成交 ──
# ── 网格方向判断 ──
# 比较成交单的网格索引 vs 当前网格索引,判断价格移动方向
oriIdx = self.tradeTarget.grid_index # 成交前的网格位置
if gridIdx > self.tradeTarget.grid_index:
# 成交单在下方(更大索引 = 更低价格)→ 买入成交,持仓下移
self.tradeTarget.grid_index += 1 # type: ignore
# 首次建仓时 oriIdx==0,加上"建仓单"前缀便于识别
desc = "建仓单(下移)" if oriIdx == 0 else "下移一格"
elif gridIdx < self.tradeTarget.grid_index:
# 成交单在上方(更小索引 = 更高价格)→ 卖出成交,持仓上移
self.tradeTarget.grid_index -= 1 # type: ignore
# 卖出获利:累计匹配次数和利润
self.tradeTarget.grid_match_count += 1 # type: ignore
# 单格利润 = grid_size × 成交量
self.tradeTarget.grid_total_profit += ( # type: ignore
self.tradeTarget.grid_size * trade.traded_volume)
desc = "上移一格"
else:
PrintLog(LogLevel.INFO,
f'|- 委托成交通知[{self.tradeTarget.targetName()}-{trade.order_id}] '
f'- 网格单成交')
oriIdx = self.tradeTarget.grid_index # 记录原网格位置(用于日志)
# gridIdx == grid_index: 同格成交,正常情况下不会出现
desc = "同格(异常)"
# 判断成交方向: gridIdx > currentIdx → 买入成交(下移)
if gridIdx > self.tradeTarget.grid_index:
desc = "下移一格"
self.tradeTarget.grid_index += 1
PrintLog(LogLevel.INFO,
f'|- [{self.tradeTarget.targetName()}] '
f'原网格 {oriIdx} → 现网格 {self.tradeTarget.grid_index}'
f'{desc}')
# 判断成交方向: gridIdx < currentIdx → 卖出成交(上移)
elif gridIdx < self.tradeTarget.grid_index:
desc = "上移一格"
# 累计统计
self.tradeTarget.grid_match_count += 1 # 网格匹配次数+1
self.tradeTarget.grid_total_profit += (
self.tradeTarget.grid_size * trade.traded_volume
) # 累计利润 = 网格间距 × 成交量
self.tradeTarget.grid_index -= 1
# gridIdx == currentIdx: 理论上不应出现(同一个位置不会挂单给自己)
else:
desc = "保持格, 理论上不应该输出"
PrintLog(LogLevel.INFO,
f'|- 委托成交通知'
f'[{self.tradeTarget.stock_code}-{self.tradeTarget.stock_name} - '
f'原网格位置 {oriIdx}, 现网格位置 {self.tradeTarget.grid_index}')
# ── 成交后处理 ──
# 1. 持久化状态到数据库
# ── 成交后统一处理 ──
# 1. 持久化状态到数据库(grid_index、持仓量等已变更)
self.saveProxy()
# 2. 从 orderGrid 中删除已成交订单(pop 防重复推送 KeyError
# 2. 从 orderGrid 清理已成交订单(pop 防 xtquant 重复推送 KeyError
self.orderGrid.pop(gridIdx, None)
# 3. 打印成交报告
PrintLog(LogLevel.INFO,
f"|- 成交报告[{self.tradeTarget.targetName()}] : "
@@ -517,8 +459,7 @@ class SFGridStrategy:
f' 成交价: {trade.traded_price} 成交量: {trade.traded_volume}')
PrintLog(LogLevel.INFO,
f' 手续费 : {trade.commission:.3f}')
# 4. 刷新网格订单:在新的 grid_index 上下重新挂买卖单
# 4. 刷新网格订单:在新的 grid_index 位置重新挂买卖单
self.refreshGridOrder()
finally:
@@ -571,7 +512,7 @@ class SFGridStrategy:
"""
PrintLog(LogLevel.DEBUG,
f'|- [DEBUG] saveProxy: {self.tradeTarget.targetName()} '
f'grid_index={self.tradeTarget.grid_index}, status={self.tradeTarget.status}')
f'网格={self.tradeTarget.grid_index}')
rc = self.tradeTarget.save()
event_bus.publish(EventTradeTargetUpdate, self.tradeTarget)
return rc
File diff suppressed because it is too large Load Diff