alphax/tests/test_trailing_stop.py
2026-05-31 19:00:46 +08:00

98 lines
3.4 KiB
Python

import pytest
from app.core.trailing_stop import evaluate_trailing_stop, trailing_profile
def test_fixed_trailing_stop_activates_with_profit_floor():
decision = evaluate_trailing_stop(
position={"entry_price": 100, "max_price": 100, "min_price": 100, "trailing_stop": 0},
current_price=104,
pnl_pct=4,
config={
"trailing_stop_enabled": True,
"trailing_mode": "fixed",
"trailing_activate_pnl_pct": 3,
"trailing_min_lock_profit_pct": 0.5,
"trailing_distance_pct": 1.5,
},
).as_dict()
assert decision["action"] == "activate"
assert decision["event_type"] == "trailing_activate"
assert decision["activated"] is True
assert decision["trailing_mode"] == "fixed"
assert decision["trailing_stop"] == pytest.approx(102.44)
def test_trailing_stop_never_moves_down():
decision = evaluate_trailing_stop(
position={"entry_price": 100, "max_price": 106, "min_price": 100, "trailing_stop": 104},
current_price=104.5,
pnl_pct=4.5,
config={
"trailing_stop_enabled": True,
"trailing_mode": "fixed",
"trailing_activate_pnl_pct": 3,
"trailing_min_lock_profit_pct": 0.5,
"trailing_distance_pct": 1.5,
},
).as_dict()
assert decision["action"] == "hold"
assert decision["reason"] == "unchanged"
assert decision["trailing_stop"] == pytest.approx(104)
def test_short_trailing_stop_activates_and_moves_down():
activated = evaluate_trailing_stop(
position={"side": "short", "entry_price": 100, "max_price": 100, "min_price": 100, "trailing_stop": 0},
current_price=94,
pnl_pct=6,
config={
"trailing_stop_enabled": True,
"trailing_mode": "fixed",
"trailing_activate_pnl_pct": 3,
"trailing_min_lock_profit_pct": 0.5,
"trailing_distance_pct": 1.5,
},
).as_dict()
moved = evaluate_trailing_stop(
position={"side": "short", "entry_price": 100, "max_price": 100, "min_price": 94, "trailing_stop": activated["trailing_stop"]},
current_price=92,
pnl_pct=8,
config={
"trailing_stop_enabled": True,
"trailing_mode": "fixed",
"trailing_activate_pnl_pct": 3,
"trailing_min_lock_profit_pct": 0.5,
"trailing_distance_pct": 1.5,
},
).as_dict()
assert activated["action"] == "activate"
assert activated["trailing_stop"] == pytest.approx(95.41)
assert moved["action"] == "move"
assert moved["trailing_stop"] < activated["trailing_stop"]
def test_volatility_profile_widens_activation_and_distance():
profile = trailing_profile(
{"entry_price": 100, "max_price": 100, "min_price": 98},
current_price=110,
pnl_pct=10,
config={
"trailing_mode": "volatility",
"trailing_activate_pnl_pct": 3,
"trailing_distance_pct": 1.5,
"trailing_volatility_activation_mult": 0.6,
"trailing_volatility_distance_mult": 0.7,
"trailing_volatility_max_activation_pct": 8,
"trailing_volatility_max_distance_pct": 8,
},
)
assert profile["trailing_mode"] == "volatility"
assert profile["volatility_pct"] == pytest.approx(12)
assert profile["activate_pnl_pct"] == pytest.approx(7.2)
assert profile["distance_pct"] == pytest.approx(8.0)