alphax/tests/test_price_tracker_trailing_stop.py
2026-05-15 00:23:04 +08:00

55 lines
2.0 KiB
Python

import os
import sys
import pandas as pd
PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if PROJECT_DIR not in sys.path:
sys.path.insert(0, PROJECT_DIR)
from app.services import price_tracker
def test_trailing_stop_activation_uses_profit_floor_when_atr_is_too_wide(monkeypatch):
"""MLN-style case: +5% float profit with huge ATR must still protect above entry."""
monkeypatch.setattr(price_tracker, "calc_atr", lambda df, period=14: 0.447143)
monkeypatch.setattr(price_tracker, "detect_trend_exhaustion", lambda df, atr: {"exhausted": False, "signals": [], "severity": "low"})
monkeypatch.setattr(price_tracker, "full_pa_analysis", lambda df, timeframe: {"candles_class": []})
monkeypatch.setattr(price_tracker, "load_rules", lambda: {
"tracker": {
"trailing_stop": {
"enabled": True,
"activate_pnl_pct": 3,
"min_lock_profit_pct": 0.5,
"breakeven_buffer_pct": 0.5,
"tiers": [
{"min_pnl_pct": 0, "atr_mult": 3.0, "label": "防震"},
{"min_pnl_pct": 5, "atr_mult": 2.0, "label": "锁利"},
],
}
}
})
df = pd.DataFrame(
[
{"open": 3.5, "high": 3.8, "low": 3.4, "close": 3.7, "volume": 100},
{"open": 3.7, "high": 3.9, "low": 3.6, "close": 3.8, "volume": 120},
]
* 20
)
monkeypatch.setattr(price_tracker, "fetch_klines", lambda symbol, timeframe, limit=100: df)
rec = {
"entry_price": 3.61,
"stop_loss": 3.249,
"tp1": 4.822857,
"tp2": 5.631429,
"entry_plan": {"trailing_stop_level": 0.0},
}
result = price_tracker.analyze_tracking_signals("MLN/USDT", rec, 3.8)
assert result["trailing_stop_activated"] is True
assert result["trailing_stop_level"] == max(3.249, 3.61 * 1.005)
assert result["trailing_stop_level"] > rec["entry_price"]
assert any("跟踪止盈激活" in signal for signal in result["sell_signals"])