From f6aac0ca17ae53469d7952f01dea3965307b0dc0 Mon Sep 17 00:00:00 2001 From: zlt Date: Tue, 25 Aug 2026 14:07:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=A4=E6=98=93=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEVLOG.md | 8 ++++---- app/scheduler.py | 2 +- app/services/param_store.py | 2 +- app/services/strategy_advisor.py | 20 ++++++++++---------- config/settings.py | 4 ++-- scripts/test_batch17_units.py | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index ea90030..84b4f3f 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -30,13 +30,13 @@ --- -## 2026-08-25(续)· 策略自动挂载:个股打法状态机(吸筹挂网格 / 高热挂止盈 / 派发停买腿 / 接力切换) +## 2026-08-25(续)· 策略自动挂载:个股打法状态机(吸筹挂网格 / 高热挂止盈 / 转派发暂停买入 / 接力切换) **背景** 用户要把个股策略从「手工挂」推进到「默认自动用」:有吸筹标志就开始网格建仓,热度很高就挂跟踪止盈。方案文件 STRATEGY_AUTO_ATTACH_PLAN.md(V2 定稿)已入库,五处拍板均已当面定下:只认「明确吸筹」档触发;建仓资格不放宽(吸筹只影响候选排序,不给入场资格);不设观察期直接全自动;实现骨架取「状态机含接力切换」(网格票站上区间上界且热度达标时自动换挂止盈);判分闭环进 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_attach(09: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 PASS;dry-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 且它连网格都挂不上),排查后单独修。 --- diff --git a/app/scheduler.py b/app/scheduler.py index f8e6ea3..fd57841 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -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 分钟的 diff --git a/app/services/param_store.py b/app/services/param_store.py index 7f95bc3..e2e65b9 100644 --- a/app/services/param_store.py +++ b/app/services/param_store.py @@ -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±带宽)", diff --git a/app/services/strategy_advisor.py b/app/services/strategy_advisor.py index 3e4539f..411ffa8 100644 --- a/app/services/strategy_advisor.py +++ b/app/services/strategy_advisor.py @@ -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, diff --git a/config/settings.py b/config/settings.py index b23d130..b23df86 100644 --- a/config/settings.py +++ b/config/settings.py @@ -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±带宽) diff --git a/scripts/test_batch17_units.py b/scripts/test_batch17_units.py index a09a4cf..489103f 100644 --- a/scripts/test_batch17_units.py +++ b/scripts/test_batch17_units.py @@ -8,7 +8,7 @@ 2. note 约定字面量钉死 —— 冷却与计数全靠它, 改一个字就把无状态推导改坏; 3. strategy_service.clear_buypause 的来源匹配 (只清自己停的, 不放开风控停的); 4. 编排冒烟 (全打桩): dry_run 滴水不写 / 真挂走 attach+留痕 / 名额满挡下 / - 边三停买腿与恢复 / 边四接力全链 (撤网格→打标记→挂止盈→初始化高水位)。 + 转派发暂停买入与恢复 / 接力全链 (撤网格→打标记→挂止盈→初始化高水位)。 运行: python scripts/test_batch17_units.py """ import os