feat: v_combo落地Step1-2——v71_gate加B动量通道+策略记录exit_mode(swing/momentum)
This commit is contained in:
@@ -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, # 动量基因标记
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user