alphax/tests/test_opportunity_lifecycle.py
2026-06-07 20:29:45 +08:00

394 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)