fix: 策略参数权威保护改白名单机制——只有per_stock_12d/batch_12d/promote能写参数,辅助模块(clean_watchlist/branch_scanner等)无法覆写(根治00020等无快照票被反复覆写参数)

This commit is contained in:
xxm
2026-08-20 00:42:52 +08:00
parent 5674d3e66a
commit a5dcb9136d
+19 -64
View File
@@ -2083,73 +2083,28 @@ def write_holding_strategy(conn, code: str, name: str, data: dict,
data['timing_signal'] = _old_sig
except Exception:
pass
# ── 策略参数权威保护(2026-07-27 老爸:技术路径每2分钟覆写12维的Zone/SL/TP/Position→RR波动→盯盘和XMPP不一致)──
# 新鲜(<20h)12维分析的技术参数+仓位不允许被技术路径覆写
# 2026-07-27 坑:_old_ra=None 时权威保护永不触发(很多股票的reassessed_at为空),
# 导致系统自动计算的仓位被反复踩回"中等仓位"。加入兜底:仓位含"%(系统按"即永保
if source_trigger in _TECHNICAL_PATHS:
# 兜底:系统自动计算的仓位永久保护(不含 %(系统按 的不保护,即只有 LLM 仓和系统仓被保护)
_old_pos = conn.execute(
"SELECT position_advice FROM holding_strategies WHERE code=? AND status='active'",
(code,)).fetchone()
_old_pos_val = (_old_pos[0] or '') if _old_pos else ''
if _old_pos_val and '系统按' in str(_old_pos_val):
data['position_advice'] = _old_pos_val
# 被保护仓位触发时顺便保护参数(无论 _old_ra 是否空)
_op = conn.execute(
"SELECT entry_low, entry_high, stop_loss, take_profit FROM holding_strategies WHERE code=? AND status='active'",
(code,)).fetchone()
# ── per_stock 重评保护(2026-08-19 老莫:参数覆写保护)──
# per_stock_reassess 重评后 24h 内,其他 source_trigger 不得覆写参数
# 判断:最近的 per_stock_12d 快照时间戳(source_trigger='per_stock_12d'
_ps = conn.execute(
"SELECT snapshotted_at FROM strategy_history WHERE code=? AND source_trigger='per_stock_12d' ORDER BY snapshotted_at DESC LIMIT 1",
(code,)).fetchone()
if _ps and _ps[0]:
# ── 策略参数权威保护(2026-08-19 重写:白名单机制,根治交叉覆写)──
# 只有 LLM 重评路径(per_stock_12d/batch_12d) 和 提拔(promote) 能写策略参数
# 其他调用方(辅助模块/默认write_holding_strategy):保留 DB 当前参数,不覆写。
# 解决:无 per_stock_12d 快照的票(如00020)参数被辅助模块(clean_watchlist/branch_scanner等)反复覆写
_PARAM_WHITELIST = ('per_stock_12d', 'batch_12d', 'promote')
if source_trigger not in _PARAM_WHITELIST:
try:
from datetime import datetime as _ddt, timedelta as _dtd
_ps_dt = _ddt.fromisoformat(str(_ps[0])[:19])
if (_ddt.now() - _ps_dt) < _dtd(hours=24):
# 24h 内重评过 → 保护参数不被覆写
_op2 = conn.execute(
"SELECT entry_low, entry_high, stop_loss, take_profit, rr_ratio FROM holding_strategies WHERE code=? AND status='active'",
(code,)).fetchone()
if _op2 and _op2[0] and _op2[0] > 0:
# 参数保护:当前值保留(除非调用方是 per_stock_12d 本身)
if source_trigger != 'per_stock_12d':
data['entry_low'] = float(_op2[0])
data['entry_high'] = float(_op2[1]) if _op2[1] else _op2[0]
data['stop_loss'] = float(_op2[2]) if _op2[2] else data.get('stop_loss')
data['take_profit'] = float(_op2[3]) if _op2[3] else data.get('take_profit')
data['rr_ratio'] = float(_op2[4]) if _op2[4] else data.get('rr_ratio')
print(f" [AUTHORITY-24H] {code} per_stock重评24h内,保留参数(区{_op2[0]}~{_op2[1]}{_op2[2]})", flush=True)
_cur = conn.execute(
"SELECT entry_low, entry_high, stop_loss, take_profit, rr_ratio, position_advice "
"FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
if _cur and _cur[0] and float(_cur[0] or 0) > 0:
data['entry_low'] = float(_cur[0])
data['entry_high'] = float(_cur[1]) if _cur[1] else _cur[0]
data['stop_loss'] = float(_cur[2]) if _cur[2] else data.get('stop_loss')
data['take_profit'] = float(_cur[3]) if _cur[3] else data.get('take_profit')
data['rr_ratio'] = float(_cur[4]) if _cur[4] else data.get('rr_ratio')
# 仓位保护:LLM/系统算的仓位不被辅助覆盖
if _cur[5] and str(_cur[5]).strip():
data['position_advice'] = str(_cur[5])
print(f" [AUTHORITY-PARAM] {code} 保护策略参数(区{_cur[0]}~{_cur[1]}{_cur[2]}) source={source_trigger}", flush=True)
except Exception:
pass
if _op and float(_op[0] or 0) > 0:
data['entry_low'] = float(_op[0])
data['entry_high'] = float(_op[1])
data['stop_loss'] = float(_op[2])
data['take_profit'] = float(_op[3])
print(f" [AUTHORITY-POS] {code} 保护系统仓位'{_old_pos_val[:30]}'", flush=True)
elif _old_ra:
try:
from datetime import datetime as _ddt3, timedelta as _dtd3
_ra_dt3 = _ddt3.fromisoformat(str(_old_ra)[:19])
if (_ddt3.now() - _ra_dt3) < _dtd3(hours=20):
_old_params = conn.execute(
"SELECT entry_low, entry_high, stop_loss, take_profit, position_advice FROM holding_strategies WHERE code=? AND status='active'",
(code,)).fetchone()
if _old_params:
_keys = ['entry_low','entry_high','stop_loss','take_profit','position_advice']
_vals = [v if v else '' for v in _old_params]
for i, k in enumerate(_keys):
if i < 4 and float(_vals[i] or 0) > 0:
data[k] = float(_vals[i])
elif i == 4 and str(_vals[i]).strip():
data[k] = str(_vals[i])
print(f" [AUTHORITY-PARAM] {code} 保留12维参数(区{_vals[0]}~{_vals[1]}{_vals[2]}{_vals[3]} pos={_vals[4]})", flush=True)
except Exception:
pass
# ── action 权限保护(与信号同一权威层级,2026-07-22)──
# 技术路径不得覆盖新鲜(<20h)12维 action。
# 根治:技术路径写的"盈亏比不足1:1.5不建议买入"旧 action 与12维买入分析同框矛盾。