添加交易逻辑

This commit is contained in:
zlt 2026-08-25 14:07:42 +08:00
parent e4c23ec238
commit f6aac0ca17
6 changed files with 19 additions and 19 deletions

View File

@ -30,13 +30,13 @@
---
## 2026-08-25· 策略自动挂载:个股打法状态机(吸筹挂网格 / 高热挂止盈 / 派发停买腿 / 接力切换)
## 2026-08-25· 策略自动挂载:个股打法状态机(吸筹挂网格 / 高热挂止盈 / 转派发暂停买入 / 接力切换)
**背景**
用户要把个股策略从「手工挂」推进到「默认自动用」:有吸筹标志就开始网格建仓,热度很高就挂跟踪止盈。方案文件 STRATEGY_AUTO_ATTACH_PLAN.mdV2 定稿)已入库,五处拍板均已当面定下:只认「明确吸筹」档触发;建仓资格不放宽(吸筹只影响候选排序,不给入场资格);不设观察期直接全自动;实现骨架取「状态机含接力切换」(网格票站上区间上界且热度达标时自动换挂止盈);判分闭环进 V1。上线前在桥机跑过一轮只读探测scripts/probe_strategy_signals.py最新结论日吸筹定性 420 只(明确 164热度全市场 5195 只、不低于 0.80 的共 194 只(约前 4%,阈值 0.80 因此保留),候选池 31 只中 17 只带明确吸筹(拍板②成立),两信号源日龄 1 天。探测同时暴露一个问题62 只「词表外」定性,根因是首版归类用前缀匹配,而 state 常带前后缀修饰——数据底座 feed.py 用的是子串包含,本次全部改为与它同法(子串 + 派发优先的保守次序)。
**做了什么**
一,新模块 app/services/strategy_advisor.py每交易日 09:40 对每只持仓票判阶段、走四条边——明确吸筹且新鲜挂网格(区间锚支撑压力、锚不住退百分比带,锚不出合法区间就放弃不硬凑);热度超阈值且安全垫为正挂跟踪止盈(双命中也取止盈,负垫永不挂止盈,把位置留给补仓评估);定性转派发或标志失效只暂停网格买入(来源标 accum回到明确吸筹自动解除不动卖出侧);自动网格站上区间上界且热度达标撤网格换挂止盈(接力),高水位从当日现价起算。挂载与切换只做配置动作,真正买卖仍由 strategy_runner 逐笔过规则闸与全部熔断。自动策略的身份全靠 note 约定承载(「自动挂载: 」「自动挂载(接力): 」「[接力撤下]」),每日新挂上限、人工撤下冷却(同票同规则 10 个交易日)、接力冷却(同票网格边 10 个交易日)全部从策略表推导,不加新表不加状态参数。排除项:人工策略不碰、冻结与黑名单不挂网格、有在途不挂、热度表停更时热度边整轮不动、词表外定性按无标志处理并整轮报样本。
一,新模块 app/services/strategy_advisor.py每交易日 09:40 对每只持仓票判阶段、走四条边——明确吸筹且新鲜挂网格(区间锚支撑压力、锚不住退百分比带,锚不出合法区间就放弃不硬凑);热度超阈值且安全垫为正挂跟踪止盈(双命中也取止盈,负垫永不挂止盈,把位置留给补仓评估);定性转派发或标志失效只暂停网格买入(来源标 accum回到明确吸筹自动解除卖出照常);自动网格站上区间上界且热度达标撤网格换挂止盈(接力),高水位从当日现价起算。挂载与切换只做配置动作,真正买卖仍由 strategy_runner 逐笔过规则闸与全部熔断。自动策略的身份全靠 note 约定承载(「自动挂载: 」「自动挂载(接力): 」「[接力撤下]」),每日新挂上限、人工撤下冷却(同票同规则 10 个交易日)、接力冷却(同票网格边 10 个交易日)全部从策略表推导,不加新表不加状态参数。排除项:人工策略不碰、冻结与黑名单不挂网格、有在途不挂、热度表停更时热度边整轮不动、词表外定性按无标志处理并整轮报样本。
strategy_service 补 clear_buypause按来源解除买入暂停advisor 只清自己 accum 停的不放开风控停的scheduler 新任务 pms.strategy_attach09:40在宏观扫描 09:35 之后宏观要降仓时在途票自动让路web 补试算端点 POST /api/ops/strategy-attach-scan?dry_run=true只判不写供判收与日常复核
13 个 PMS_AUTO_* 参数进 settings 与参数页(总开关、边清单、日上限 2、吸筹日龄 3、网格带宽/档距/投入比例、热度阈值 0.80、止盈回撤/卖出比例、接力开关与两种冷却),全带中文说明与范围校验,边清单在写入口拦未知边名;总开关进 param_store.FAIL_CLOSED——参数表读不到时按关处理故障期间宁可不挂。
四,探测脚本升 v1.1(归类改子串包含并打印词表外原文样本;对读注释改口径说明:探测是单日分布、前端清单是 30 天窗口,比结构不比绝对数)。
@ -51,10 +51,10 @@ app/services/strategy_advisor.py新增app/services/strategy_service.py
部分判收2026-08-25 盘中)。桥机 make deploy + make test 见 ALL SUITES PASSdry-run 试算真机四票读数全部合理688802.SH 想挂网格但「按投入比例折出 47,250 元买不起一手」被挡科创板高价票判定正确002179.SZ 有在途指令主动让路其余两票无信号静默跳过unknown_states 为空(子串归类修对了)。**尚未判收的**:真挂一条(等在途清了重扫或次日 09:40、边三/边四真机走一遍、strategy_runner 对自动网格的逐档发单。
**当日盘中补两处(第二次交付)**
科创板一手口径dry-run 里 688802.SH 暴露全库按 100 股一手,而 688/689 最小申报 200 股——自动网格若生成 per_lot=100 会被券商拒单。advisor 补 lot_of()688/689→200买得起一手与 per_lot 下限都按它算200 仍是 100 的整数倍runner 的取整不会磨掉。runner 侧卖出对科创板的整百取整仍不完美,见欠账。二,判分读数脚本 scripts/report_strategy_score.py 交付:四段只读统计(样本盘点 / 网格差价按策略内均价配对 / 止盈「卖点之后又跌多少=保住的钱」/ 挂上 vs 名额挡下的对照组涨跌样本不足三条只报数不下结论连不上库或取不到现价都说人话。batch17 扩到 36 例,总数 555。
科创板一手口径dry-run 里 688802.SH 暴露全库按 100 股一手,而 688/689 最小申报 200 股——自动网格若生成 per_lot=100 会被券商拒单。advisor 补 lot_of()688/689→200买得起一手与 per_lot 下限都按它算200 仍是 100 的整数倍runner 的取整不会磨掉。runner 侧卖出数量对科创板的整百取整仍不完美,见欠账。二,判分读数脚本 scripts/report_strategy_score.py 交付:四段只读统计(样本盘点 / 网格差价按策略内均价配对 / 止盈「卖点之后又跌多少=保住的钱」/ 挂上 vs 名额挡下的对照组涨跌样本不足三条只报数不下结论连不上库或取不到现价都说人话。batch17 扩到 36 例,总数 555。
**还欠着什么**
一,判分脚本已交,但**有意义的读数要等两周样本**——现在跑是空跑冒烟,别拿首周数字调阈值(热度 0.80 / 日上限 2 等数据回调)。二,探测顺带发现候选池计划日停在 2026-08-21探测日 08-25日龄已 2 个交易日以上),疑似上游桥的日更链又停了,且 PMS_PLAN_STALE_TDAYS 可能被调宽过——与本特性无关,单独排查。三,科创板卖出腿strategy_runner 的 _round_lot 全库按 100 股取整688 票的止盈卖出若折出 100 股会被拒单(低于 200 股只能一次性清)——影响面小(当前仅一只 688 且它连网格都挂不上),排查后单独修。
一,判分脚本已交,但**有意义的读数要等两周样本**——现在跑是空跑冒烟,别拿首周数字调阈值(热度 0.80 / 日上限 2 等数据回调)。二,探测顺带发现候选池计划日停在 2026-08-21探测日 08-25日龄已 2 个交易日以上),疑似上游桥的日更链又停了,且 PMS_PLAN_STALE_TDAYS 可能被调宽过——与本特性无关,单独排查。三,科创板的卖出数量取整strategy_runner 的 _round_lot 全库按 100 股取整688 开头的票止盈卖出若折出 100 股会被拒单(科创板低于 200 股只能一次性全卖)——影响面小(当前仅一只 688 且它连网格都挂不上),排查后单独修。
---

View File

@ -199,7 +199,7 @@ def macro_scan():
@celery_app.task(name="pms.strategy_attach")
@guard(trade_day=True, respect_exec_halt=True)
def strategy_attach():
"""策略自动挂载 (09:40): 个股打法状态机 —— 吸筹挂网格 / 高热挂止盈 / 派发停买腿 / 接力。
"""策略自动挂载 (09:40): 个股打法状态机 —— 吸筹挂网格 / 高热挂止盈 / 转派发暂停买入 / 接力。
定在 09:40: 在宏观扫描 (09:35) 之后 若宏观当天要降仓, 先让降仓命令占住在途,
有在途的票本轮自动挂载会主动让路 (缓到下一个扫描日); 也避开开盘前 15 分钟的

View File

@ -155,7 +155,7 @@ DESC = {
"PMS_T0_GLOBAL_DAY_LOSS": "全局当日T亏熔断",
"PMS_STRATEGY_ENABLED": "个股交易方案(策略)层总开关 (关=strategy_runner 空转)",
"PMS_AUTO_STRATEGY_ENABLED": "策略自动挂载总开关 (个股打法状态机; 关=09:40 调度位空转不挂不撤)",
"PMS_AUTO_STRATEGY_RULES": "启用的边 (逗号分隔): accum_grid 吸筹挂网格 / heat_trail 热度挂止盈 / accum_exit 派发暂停买腿 / handoff 网格换止盈接力",
"PMS_AUTO_STRATEGY_RULES": "启用的规则 (逗号分隔): accum_grid 吸筹挂网格 / heat_trail 热度挂止盈 / accum_exit 转派发暂停买入 / handoff 网格换止盈接力",
"PMS_AUTO_STRATEGY_DAILY_MAX": "每日自动新挂上限 (只数; 接力换挂是换不是增, 不计入)",
"PMS_AUTO_ACCUM_STALE_TDAYS": "吸筹结论日龄超此 (交易日) 视为过期不触发",
"PMS_AUTO_GRID_BAND": "网格区间退化带宽: 支撑/压力锚不住时用 现价×(1±带宽)",

View File

@ -7,7 +7,7 @@
阶段: 吸筹震荡 / 启动拉升 / 高位派发 / 深亏修复 / 中性
边一 中性吸筹震荡 明确吸筹且结论新鲜 挂网格
边二 中性启动拉升 热度超阈值且安全垫为正 挂跟踪止盈 (双命中也取止盈)
边三 吸筹震荡高位派发 定性转派发或标志失效 暂停网格买入 (来源 accum, 回明确自动解除)
边三 吸筹震荡高位派发 定性转派发或标志失效 暂停网格买入 (来源 accum, 回明确自动解除)
边四 吸筹震荡启动拉升 自动网格票站上区间上界且热度超阈值 撤网格换挂止盈 (接力)
**挂载和切换本身不下单** 真正的买卖仍由 strategy_runner 每分钟评估每一笔过规则闸与
@ -74,7 +74,7 @@ def lot_of(ts_code) -> int:
(strategy_runner._round_lot / sizer), 科创板 200 股起买这条只在这里兜 自动网格
per_lot 若生成 100, runner 发单会被券商按无效数量拒掉买得起一手的判断与
per_lot 下限都按这个数; 200 也是 100 的整数倍, 不会被 _round_lot 磨掉
runner 侧卖出的整百取整对科创板仍不完美 ( DEVLOG 欠账), 不在本模块修"""
runner 侧卖出数量的整百取整对科创板仍不完美 ( DEVLOG 欠账), 不在本模块修"""
s = str(ts_code or "")
return 200 if s.startswith(("688", "689")) else 100
@ -368,7 +368,7 @@ def scan(*, dry_run: bool = False, now=None) -> dict:
accum = accum_of(codes)
except Exception as e: # noqa: BLE001
accum = {}
out["errors"].append(f"吸筹取数失败 (边一/三/四本轮不动): {type(e).__name__}: {e}")
out["errors"].append(f"吸筹取数失败, 依赖它的判断本轮全部不做: {type(e).__name__}: {e}")
try:
heat, heat_meta = heat_of(codes)
if heat_meta.get("stale"):
@ -377,7 +377,7 @@ def scan(*, dry_run: bool = False, now=None) -> dict:
f"热度相关的边本轮不动"})
except Exception as e: # noqa: BLE001
heat = {}
out["errors"].append(f"热度取数失败 (边二/四本轮不动): {type(e).__name__}: {e}")
out["errors"].append(f"热度取数失败, 依赖它的判断本轮全部不做: {type(e).__name__}: {e}")
for c in codes:
a = accum.get(c)
if a and a["cls"] == CLS_UNKNOWN:
@ -487,7 +487,7 @@ def _tend_existing(st, p, a, fresh, hv, prm, buypause, handoff_cool, today,
return
if why_h:
out["skipped"].append({"ts_code": code, "why": why_h})
# ---- 边三: 派发/失效 → 停买腿; 回明确 → 解除 accum 来源的暂停 ----
# ---- 边三: 派发/失效 → 暂停买入; 回明确 → 解除 accum 来源的暂停 ----
if R_EXIT not in prm["rules"]:
return
cls = a.get("cls")
@ -501,10 +501,10 @@ def _tend_existing(st, p, a, fresh, hv, prm, buypause, handoff_cool, today,
if dry_run:
out["paused"].append({"ts_code": code, "dry_run": True, "why": why})
return
ids = strategy_service.pause_buy(code, reason=f"{why}, 网格买入腿暂停 (卖出照常)",
ids = strategy_service.pause_buy(code, reason=f"{why}, 网格暂停买入 (卖出照常)",
source="accum")
out["paused"].append({"ts_code": code, "strategies": ids, "why": why})
_ledger(code, "NOTE", f"边三: {why} —— 网格买入腿已暂停, 卖出与已买档位照常",
_ledger(code, "NOTE", f"{why} —— 网格已暂停买入, 卖出与已买档位照常",
{"accum_state": a.get("state"), "accum_ymd": a.get("ymd")},
st["strategy_id"], out, action="NOTE")
elif cls == CLS_CLEAR and fresh and pause_ent.get("source") == "accum":
@ -514,7 +514,7 @@ def _tend_existing(st, p, a, fresh, hv, prm, buypause, handoff_cool, today,
r = strategy_service.clear_buypause(code, only_source="accum") or {}
if r.get("cleared"):
out["resumed"].append({"ts_code": code})
_ledger(code, "NOTE", "边三解除: 定性回到明确吸筹, 网格买入腿恢复",
_ledger(code, "NOTE", "定性回到明确吸筹, 网格恢复买入",
{"accum_state": a.get("state")}, st["strategy_id"], out,
action="NOTE")
elif r.get("error"):
@ -538,10 +538,10 @@ def _attach_one(edge, why, p, a, hv, prm, view, black, live_codes, optout,
return
if edge == R_GRID:
if (p.get("frozen_reason") or "NONE") != "NONE":
out["skipped"].append({"ts_code": code, "why": "该股被冻结 (禁增持), 网格只剩半条腿, 不挂"})
out["skipped"].append({"ts_code": code, "why": "该股被冻结禁止增持, 网格买不进只剩卖出, 不挂"})
return
if code in black:
out["skipped"].append({"ts_code": code, "why": "该股在黑名单, 网格买腿必被闸拦, 不挂"})
out["skipped"].append({"ts_code": code, "why": "该股在黑名单, 网格的买入必被规则闸拦下, 不挂"})
return
if attached_today >= prm["daily_max"]:
out["blocked"].append({"ts_code": code, "edge": edge,

View File

@ -239,12 +239,12 @@ class Settings(BaseSettings):
PMS_STRATEGY_ENABLED: bool = False # 个股交易方案(策略)层总开关; False=strategy_runner 空转不评估
# --- 策略自动挂载 (个股打法状态机, STRATEGY_AUTO_ATTACH_PLAN.md) ---
# 每交易日 09:40 由 scheduler.strategy_attach 跑一次, 只做「配置动作」(挂/换/暂停买腿),
# 每交易日 09:40 由 scheduler.strategy_attach 跑一次, 只做「配置动作」(挂载/换挂/暂停买入),
# 真正的买卖仍由 strategy_runner 逐分钟评估、逐笔过规则闸与全部熔断。
# 模拟仓拍板 (2026-08-25): 全自动上线不设观察期; ENABLED 已进 param_store.FAIL_CLOSED
# (参数表读不到时按关处理, 基础设施故障期间宁可不挂)。
PMS_AUTO_STRATEGY_ENABLED: bool = True # 总开关; 关 = 09:40 调度位空转不挂不撤
PMS_AUTO_STRATEGY_RULES: str = "accum_grid,heat_trail,accum_exit,handoff" # 启用的
PMS_AUTO_STRATEGY_RULES: str = "accum_grid,heat_trail,accum_exit,handoff" # 启用的规则
PMS_AUTO_STRATEGY_DAILY_MAX: int = 2 # 每日自动新挂上限 (接力换挂不计入)
PMS_AUTO_ACCUM_STALE_TDAYS: int = 3 # 吸筹结论日龄超此 (交易日) 视为过期不触发
PMS_AUTO_GRID_BAND: float = 0.08 # 支撑压力锚不住时网格区间退化为 现价×(1±带宽)

View File

@ -8,7 +8,7 @@
2. note 约定字面量钉死 冷却与计数全靠它, 改一个字就把无状态推导改坏;
3. strategy_service.clear_buypause 的来源匹配 (只清自己停的, 不放开风控停的);
4. 编排冒烟 (全打桩): dry_run 滴水不写 / 真挂走 attach+留痕 / 名额满挡下 /
边三停买腿与恢复 / 边四接力全链 (撤网格打标记挂止盈初始化高水位)
转派发暂停买入与恢复 / 接力全链 (撤网格打标记挂止盈初始化高水位)
运行: python scripts/test_batch17_units.py
"""
import os