From 0d68ced3c0a37aa890da835a5863dc79c13e364e Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 30 Jul 2026 03:43:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v=5Fcombo=E8=90=BD=E5=9C=B0Step1-2?= =?UTF-8?q?=E2=80=94=E2=80=94v71=5Fgate=E5=8A=A0B=E5=8A=A8=E9=87=8F?= =?UTF-8?q?=E9=80=9A=E9=81=93+=E7=AD=96=E7=95=A5=E8=AE=B0=E5=BD=95exit=5Fm?= =?UTF-8?q?ode(swing/momentum)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/profile-scripts/strategy_lifecycle.py | 16 +++++++++ deploy/profile-scripts/v71_gate.py | 37 ++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/deploy/profile-scripts/strategy_lifecycle.py b/deploy/profile-scripts/strategy_lifecycle.py index b90ffe56..7590a3d7 100644 --- a/deploy/profile-scripts/strategy_lifecycle.py +++ b/deploy/profile-scripts/strategy_lifecycle.py @@ -1474,6 +1474,20 @@ def reassess_strategy(code, name, price, cost, shares, current_action, except Exception as _e: print(f" [v7.1闸门] 评估异常(放行): {_e}", flush=True) + # ----- 【B动量通道】v7.1未过时试收缩突破(v_combo动量族,2026-07-29落地) ----- + _entry_source = 'pullback' # 默认回调源 + if is_new_entry and not any(s in timing_signal for s in ("买入", "加仓", "可追")): + try: + from v71_gate import check_breakout_gate + _bg = check_breakout_gate(code, price) + if _bg.get("pass"): + timing_signal = "买入" + _entry_source = 'momentum' + action_note = (action_note + " | B动量突破: " + _bg["reason"]) if action_note else ("B动量突破: " + _bg["reason"]) + print(f" [B通道] 动量突破→买入: {_bg['reason']}", flush=True) + except Exception as _e: + print(f" [B通道] 评估异常(跳过): {_e}", flush=True) + # ----- 【三维共振层】技术×资金×消息合成判断(2026-07-29 老爸批准,全程记录) ----- _res_decision = None if is_new_entry and any(s in timing_signal for s in ("买入", "加仓", "可追")): @@ -1603,6 +1617,8 @@ def reassess_strategy(code, name, price, cost, shares, current_action, 'stock_category': stock_category, # 股票分类:短炒/中短线/中长线/弱势/深套 'time_horizon': time_horizon, # 时间跨度 'position_advice': position_advice, # 仓位建议 + 'exit_mode': ('momentum' if '_entry_source' in dir() and _entry_source == 'momentum' else 'swing'), # v_combo: 回调源用波段,动量源用固定 + 'dna': bool(_gate.get('factors', {}).get('hh_structure')) if '_gate' in dir() and isinstance(_gate, dict) else False, # 动量基因标记 } diff --git a/deploy/profile-scripts/v71_gate.py b/deploy/profile-scripts/v71_gate.py index 6a0ba49c..756ed9e9 100644 --- a/deploy/profile-scripts/v71_gate.py +++ b/deploy/profile-scripts/v71_gate.py @@ -110,6 +110,43 @@ def check_entry_gate(code, price=None): } +def check_breakout_gate(code, price=None): + """B严格动量入场通道(v_combo 动量族,2026-07-29 落地): + 收缩突破签名 = ATR处20日最低1/4位 + 收盘破20日新高 + 量比>1.5 + 评分≥50 + ROC>4 + 大盘MA20上 + 返回 {'pass','reason','source'} source='momentum' 供出场分派""" + from backtest_framework import prepare_bars, compute_single_score + from strategy_lab import mkt_ctx + end = datetime.now().strftime('%Y-%m-%d') + start = (datetime.now() - timedelta(days=220)).strftime('%Y-%m-%d') + bars = prepare_bars(code, start, end) + if not bars or len(bars) < 25: + return {'pass': False, 'reason': '数据不足', 'source': 'momentum'} + b = bars[-1] + closes = [x['close'] for x in bars] + atr_now = b.get('atr') or 0 + atrs = [x.get('atr') or 0 for x in bars[-21:-1]] + if not atrs: + return {'pass': False, 'reason': 'ATR数据不足', 'source': 'momentum'} + atr_low = sorted(atrs)[len(atrs)//4] + high20 = max(x['high'] for x in bars[-21:-1]) + vols = [x['volume'] for x in bars[-6:-1]] + vm = sum(vols)/len(vols) if vols else 0 + vr = (b['volume']/vm) if vm > 0 else 1 + if not (atr_now > 0 and atr_now <= atr_low * 1.05 and closes[-1] > high20 and vr > 1.5): + return {'pass': False, 'reason': f'收缩突破不满足(ATR{atr_now:.2f}vs{atr_low:.2f},量比{vr:.1f})', 'source': 'momentum'} + sc = compute_single_score(bars) + if sc is None or sc[0] < 50: + return {'pass': False, 'reason': f'评分{sc[0] if sc else 0}<50', 'source': 'momentum'} + if (b.get('roc') or 0) < 4: + return {'pass': False, 'reason': f"ROC{b.get('roc')}<4", 'source': 'momentum'} + date = b['date'] + mk = mkt_ctx(date, code) + if mk.get('above_ma20') is not True: + return {'pass': False, 'reason': '大盘未在MA20上', 'source': 'momentum'} + return {'pass': True, 'reason': f'收缩突破通过(评分{sc[0]},ROC{b.get("roc")},量比{vr:.1f})', 'source': 'momentum', + 'score': sc[0], 'atr': atr_now} + + if __name__ == '__main__': for c in sys.argv[1:] or ['688002', '603599']: r = check_entry_gate(c)