173 lines
6.2 KiB
Python
173 lines
6.2 KiB
Python
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.services.price_tracker import reconcile_buy_signals_after_gate
|
||
from legacy import price_tracker_ws
|
||
|
||
|
||
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.113199
|
||
assert plan['rr_target_entry'] == plan['entry_price']
|
||
assert any('现价不买' in r for r in reasons)
|
||
|
||
|
||
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_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_ws_tracker_does_not_push_when_gate_downgrades_buy_now():
|
||
rec = {
|
||
'id': 1,
|
||
'symbol': 'WLFI/USDT',
|
||
'status': 'active',
|
||
'entry_price': 0.0758,
|
||
'stop_loss': 0.070,
|
||
'tp1': 0.080,
|
||
'tp2': 0.085,
|
||
'entry_plan': {
|
||
'entry_action': '等回踩',
|
||
'entry_price': 0.072,
|
||
'risk_reward_ok': False,
|
||
'rr1': 0.4,
|
||
},
|
||
'signals': json.dumps(['1H 起爆点↑(强度56×)', '⚠️ 等回踩降权(-3分)'], ensure_ascii=False),
|
||
'market_context': {'change_24h': 9.0},
|
||
'derivatives_context': {},
|
||
'sector_context': {},
|
||
'action_status': '持有',
|
||
}
|
||
trigger = price_tracker_ws.check_triggers('WLFI/USDT', rec, 0.0719)
|
||
assert trigger is not None
|
||
assert trigger['action_status'] != '可即刻买入'
|
||
assert trigger['pushable'] is False
|