import json import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) from app.core.opportunity_lifecycle import apply_entry_quality_gate from app.core.strategy_registry import LONG_MOMENTUM_BREAKOUT_STRATEGY, LONG_SECOND_WAVE_PULLBACK_STRATEGY from app.services.price_tracker import reconcile_buy_signals_after_gate def test_risk_reward_false_blocks_buy_now(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.072, 'current_price': 0.0758, 'risk_reward_ok': False, 'rr1': 0.4, }, signals=['1H 起爆点↑(强度56×)', '⚠️ 等回踩降权(-3分)'], current_price=0.0758, market_context={'change_24h': 9.0}, ) assert action in ('等回踩', '观察') assert action != '可即刻买入' assert plan['entry_quality_gate']['blocked_action'] == '可即刻买入' assert any('risk_reward_ok=false' in r for r in reasons) def test_buy_now_with_bad_rr_sets_real_pullback_price(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '即刻买入', 'entry_price': 0.11455, 'current_price': 0.11455, 'stop_loss': 0.107457, 'tp1': 0.120089, 'risk_reward_ok': False, 'rr1': 0.83, }, signals=['🟢 15min即刻入场信号', '日线 站稳突破位+19.2%'], current_price=0.11455, market_context={'change_24h': 3.1}, ) assert action == '等回踩' assert plan['entry_price'] < 0.11455 assert round(plan['entry_price'], 6) == 0.113071 assert plan['rr_target_entry'] == plan['entry_price'] assert any('现价不买' in r for r in reasons) def test_buy_now_with_bad_rr_can_use_stricter_rr_override(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '即刻买入', 'entry_price': 0.11455, 'current_price': 0.11455, 'stop_loss': 0.107457, 'tp1': 0.120089, 'risk_reward_ok': False, 'rr1': 0.83, }, signals=['🟢 15min即刻入场信号', '日线 站稳突破位+19.2%'], current_price=0.11455, market_context={'change_24h': 3.1}, cfg={'min_rr_buy_now': 1.5}, ) assert action == '等回踩' assert round(plan['entry_price'], 6) == 0.11251 assert any('现价不买' in r for r in reasons) def test_low_entry_score_blocks_buy_now_and_weak_pullback(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '可即刻买入', 'entry_price': 1.0, 'current_price': 1.0, 'stop_loss': 0.95, 'tp1': 1.12, 'risk_reward_ok': True, 'rr1': 2.4, 'entry_trigger_confirmed': True, 'score_components': {'opportunity_score': 12, 'entry_score': 0, 'risk_score': 1}, }, signals=['当前15min即刻入场信号'], current_price=1.0, market_context={'change_24h': 2.0}, cfg={'min_entry_score_buy_now': 3, 'min_entry_score_wait_pullback': 1}, ) assert action == '观察' assert any('买点分' in r for r in reasons) assert plan['entry_quality_gate']['entry_score'] == 0 def test_entry_gate_uses_strategy_specific_thresholds(): base_plan = { 'entry_action': '可即刻买入', 'entry_price': 1.0, 'current_price': 1.0, 'stop_loss': 0.95, 'tp1': 1.12, 'risk_reward_ok': True, 'rr1': 2.4, 'entry_trigger_confirmed': True, 'score_components': {'opportunity_score': 12, 'entry_score': 2, 'risk_score': 1}, } momentum_action, momentum_plan, _ = apply_entry_quality_gate( action_status='可即刻买入', entry_plan=dict(base_plan), signals=['当前15min即刻入场信号'], current_price=1.0, market_context={'change_24h': 2.0}, strategy_code=LONG_MOMENTUM_BREAKOUT_STRATEGY, ) second_wave_action, second_wave_plan, _ = apply_entry_quality_gate( action_status='可即刻买入', entry_plan=dict(base_plan), signals=['当前15min即刻入场信号'], current_price=1.0, market_context={'change_24h': 2.0}, strategy_code=LONG_SECOND_WAVE_PULLBACK_STRATEGY, ) assert momentum_action != '可即刻买入' assert momentum_plan['entry_quality_gate']['strategy_code'] == LONG_MOMENTUM_BREAKOUT_STRATEGY assert second_wave_action == '可即刻买入' assert second_wave_plan['strategy_code'] == LONG_SECOND_WAVE_PULLBACK_STRATEGY def test_buy_now_requires_current_trigger_and_no_bearish_flow_risk(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '即刻买入', 'entry_price': 1.0, 'current_price': 1.0, 'stop_loss': 0.95, 'tp1': 1.12, 'risk_reward_ok': True, 'rr1': 2.4, }, signals=['1H历史起爆点已过期(12根前)', '⚠️ 1H连续3K空头加速'], current_price=1.0, market_context={'change_24h': 2.0}, ) assert action != '可即刻买入' assert any('缺少当前15min触发' in r for r in reasons) assert any('空头加速' in r for r in reasons) def test_structure_watch_pullback_touch_does_not_upgrade_to_buy_now(): action, plan, reasons = apply_entry_quality_gate( action_status='等回踩', entry_plan={ 'entry_action': '等回踩', 'entry_price': 9.74, 'current_price': 9.74, 'stop_loss': 9.253, 'tp1': 10.5192, 'risk_reward_ok': True, 'rr1': 1.6, 'opportunity_level': 'structure_watch', 'opportunity_level_label': '结构观察', 'max_action': 'wait_pullback', }, signals=['1H放量(4.4x)但无量价齐飞(量价背离)', '🟢 15min即刻入场信号'], current_price=9.74, market_context={'change_24h': 0.3}, ) assert action == '等回踩' assert action != '可即刻买入' assert any('空头加速' in r for r in reasons) def test_structure_watch_with_current_trigger_and_good_rr_can_upgrade_to_buy_now(): action, plan, reasons = apply_entry_quality_gate( action_status='等回踩', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.114, 'current_price': 0.114, 'stop_loss': 0.1026, 'tp1': 0.140743, 'risk_reward_ok': True, 'rr1': 2.35, 'opportunity_level': 'structure_watch', 'opportunity_level_label': '结构观察', 'max_action': 'wait_pullback', }, signals=['4H需求区反弹', '🟢 15min即刻入场信号'], current_price=0.114, market_context={'change_24h': -6.9}, ) assert action == '可即刻买入' assert plan['entry_action'] == '可即刻买入' assert plan['entry_trigger_confirmed'] is True assert any('回踩参考已到或更优' in r for r in reasons) def test_tracker_pullback_confirmation_signal_counts_as_current_trigger(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.114, 'current_price': 0.114, 'stop_loss': 0.1026, 'tp1': 0.140743, 'risk_reward_ok': True, 'rr1': 2.35, 'opportunity_level': 'structure_watch', 'opportunity_level_label': '结构观察', 'max_action': 'wait_pullback', }, signals=['🟢 回踩确认完毕!可即刻入场(15min动K确认)'], current_price=0.114, market_context={'change_24h': -6.9}, ) assert action == '可即刻买入' assert all('缺少当前15min触发' not in r for r in reasons) def test_live_rr_recheck_overrides_stale_false_risk_reward_flag(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '即刻买入', 'entry_price': 1.0, 'current_price': 1.0, 'stop_loss': 0.92, 'tp1': 1.12, 'risk_reward_ok': False, 'rr1': 0.8, 'opportunity_level': 'short_swing', 'opportunity_level_label': '短波段', 'max_action': 'buy_now', }, signals=['🟢 15min即刻入场信号', '1H 量价齐飞K(量3.2x)'], current_price=0.97, market_context={'change_24h': 1.5}, ) assert plan['risk_reward_ok_live'] is True assert action == '可即刻买入' assert all('risk_reward_ok=false' not in r for r in reasons) assert all('rr1=0.8' not in r for r in reasons) def test_tracker_gate_downgrade_removes_provisional_buy_signal(): signals = reconcile_buy_signals_after_gate( [ '🟢 回踩确认完毕!可即刻入场(15min动K确认)', '其他背景信号', ], '等回踩', {'rr_target_entry': 0.11322245, 'entry_price': 0.11322245}, ['rr1=0.82 < 1.2,禁止现价买入', '现价不买,等回落到0.11322245附近再评估'], ) assert all('可即刻入场' not in signal for signal in signals) assert all('回踩确认完毕' not in signal for signal in signals) assert any('现价不买' in signal and '$0.1132' in signal for signal in signals) def test_breakout_distance_over_60_forces_observe(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={'entry_action': '即刻买入', 'risk_reward_ok': True, 'rr1': 2.0, 'entry_price': 0.164}, signals=['日线站稳突破位 +66.7%', '日线站稳突破位 +71.7%'], current_price=0.168, market_context={'change_24h': 5.0}, ) assert action == '观察' assert plan['entry_quality_gate']['breakout_distance_pct'] == 71.7 assert any('严禁现价追' in r for r in reasons) def test_low_static_accumulation_builds_ambush_plan(): action, plan, reasons = apply_entry_quality_gate( action_status='等回踩', entry_plan={'entry_action': '等回踩', 'entry_price': 2.393, 'risk_reward_ok': True, 'rr1': 1.6, 'support': 2.2, 'resistance': 3.0}, signals=['4H静K蓄力观察(3静K,量比1.4x)', '大户偏多 62%'], current_price=2.393, market_context={'change_24h': 3.0}, derivatives_context={'top_trader_long_pct': 62}, ) assert action == '等回踩' lifecycle = plan.get('opportunity_lifecycle') assert lifecycle['stage'] == '低位潜伏' assert lifecycle['plan_type'] == 'ambush' assert lifecycle['static_count'] >= 3 def test_invalid_long_geometry_degrades_to_observe(): action, plan, reasons = apply_entry_quality_gate( action_status='等回踩', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.109065, 'stop_loss': 0.118914, 'tp1': 0.135879, 'risk_reward_ok': True, 'rr1': 1.5, }, signals=['1H 量价齐飞K(量6.5x)', '15min 强突破K线(ATR×2.1)'], current_price=0.1257, market_context={'change_24h': 13.8}, ) assert action == '观察' assert any('止损价不低于计划入场价' in r for r in reasons) def test_wait_pullback_too_far_above_breakout_degrades_to_observe(): action, plan, reasons = apply_entry_quality_gate( action_status='等回踩', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.109065, 'stop_loss': 0.104, 'tp1': 0.135879, 'risk_reward_ok': True, 'rr1': 2.0, }, signals=['1H 量价齐飞K(量6.5x)', '15min 强突破K线(ATR×2.1)'], current_price=0.1257, market_context={'change_24h': 13.8}, ) assert action == '观察' assert any('突破已走远' in r for r in reasons) def test_ws_tracker_does_not_push_when_gate_downgrades_buy_now(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'entry_action': '等回踩', 'entry_price': 0.072, 'risk_reward_ok': False, 'rr1': 0.4, 'stop_loss': 0.07, 'tp1': 0.08, 'tp2': 0.085, }, signals=['1H 起爆点↑(强度56×)', '⚠️ 等回踩降权(-3分)'], current_price=0.0719, market_context={'change_24h': 9.0}, ) assert action in ('等回踩', '观察') assert action != '可即刻买入' assert plan['risk_reward_ok_live'] is True assert any('缺少当前15min触发' in r for r in reasons) def test_intraday_momentum_uses_strategy_rr_threshold_not_legacy_1_5(): action, plan, reasons = apply_entry_quality_gate( action_status='可即刻买入', entry_plan={ 'strategy_code': LONG_MOMENTUM_BREAKOUT_STRATEGY, 'side': 'long', 'entry_action': '可即刻买入', 'entry_price': 100, 'current_price': 100, 'stop_loss': 92, 'tp1': 110, 'tp2': 116, 'risk_reward_ok': True, 'rr1': 1.25, 'entry_trigger_confirmed': True, 'opportunity_level': 'intraday_breakout', 'score_components': {'entry_score': 3}, }, signals=['15min即刻入场信号', '1H 量价齐飞K(量2.2x)'], current_price=100, market_context={'change_24h': 4.0}, strategy_code=LONG_MOMENTUM_BREAKOUT_STRATEGY, ) assert action == '可即刻买入' assert plan['risk_reward_ok_live'] is True assert not any('rr1=' in r and '< 1.5' in r for r in reasons)