107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
import pytest
|
|
|
|
from app.db import altcoin_db
|
|
from app.db.paper_trading import list_paper_trades, sync_recommendation
|
|
from app.db.runtime_config_db import set_config
|
|
from app.services import price_streamer
|
|
|
|
|
|
@pytest.fixture
|
|
def buy_now_rec(monkeypatch):
|
|
set_config("system", "paper_trading", {
|
|
"enabled": True,
|
|
"trade_notional_usdt": 5000,
|
|
"trade_leverage": 5,
|
|
"fee_rate": 0,
|
|
"slippage_pct": 0,
|
|
})
|
|
set_config("system", "price_streamer", {
|
|
"enabled": True,
|
|
"update_latest_price_cache": True,
|
|
"sync_paper_trading": True,
|
|
"include_actionable_recommendations": True,
|
|
"include_open_paper_trades": True,
|
|
})
|
|
altcoin_db.init_db()
|
|
rec_id = altcoin_db.create_recommendation(
|
|
symbol="WS/USDT",
|
|
rec_state="爆发",
|
|
rec_score=28,
|
|
entry_price=100,
|
|
stop_loss=95,
|
|
tp1=106,
|
|
tp2=112,
|
|
signals=["当前15min即刻入场信号"],
|
|
entry_plan={
|
|
"entry_action": "可即刻买入",
|
|
"entry_price": 100,
|
|
"stop_loss": 95,
|
|
"tp1": 106,
|
|
"tp2": 112,
|
|
"risk_reward_ok": True,
|
|
"entry_trigger_confirmed": True,
|
|
},
|
|
)
|
|
return next(r for r in altcoin_db.get_active_recommendations_deduped(actionable_only=False) if r["id"] == rec_id)
|
|
|
|
|
|
def test_price_streamer_loads_actionable_targets(buy_now_rec):
|
|
targets = price_streamer.load_stream_targets()
|
|
|
|
assert "WS/USDT" in targets
|
|
assert targets["WS/USDT"]["id"] == buy_now_rec["id"]
|
|
|
|
|
|
def test_price_streamer_tick_opens_and_closes_paper_trade(buy_now_rec):
|
|
targets = {"WS/USDT": buy_now_rec}
|
|
|
|
opened = price_streamer.handle_price_tick("WS/USDT", 100, targets, event_time="2026-05-16T10:00:00")
|
|
targets = price_streamer.load_stream_targets()
|
|
closed = price_streamer.handle_price_tick("WS/USDT", 106, targets, event_time="2026-05-16T10:01:00")
|
|
|
|
assert opened["paper_trading"]["opened"] is True
|
|
assert closed["paper_trading"]["closed"] is True
|
|
assert closed["paper_trading"]["exit_reason"] == "tp1"
|
|
trade = list_paper_trades()["items"][0]
|
|
assert trade["status"] == "closed"
|
|
assert trade["notional_usdt"] == pytest.approx(5000.0)
|
|
|
|
|
|
def test_price_streamer_tick_drives_paper_trailing_stop(monkeypatch, buy_now_rec):
|
|
monkeypatch.setenv("ALPHAX_PAPER_TRAILING_STOP_ENABLED", "1")
|
|
monkeypatch.setenv("ALPHAX_PAPER_TRAILING_ACTIVATE_PNL_PCT", "3")
|
|
monkeypatch.setenv("ALPHAX_PAPER_TRAILING_MIN_LOCK_PROFIT_PCT", "0.5")
|
|
monkeypatch.setenv("ALPHAX_PAPER_TRAILING_DISTANCE_PCT", "1.5")
|
|
targets = {"WS/USDT": buy_now_rec}
|
|
|
|
price_streamer.handle_price_tick("WS/USDT", 100, targets, event_time="2026-05-16T10:00:00")
|
|
activated = price_streamer.handle_price_tick("WS/USDT", 105, targets, event_time="2026-05-16T10:01:00")
|
|
trailing_stop = activated["paper_trading"]["trailing_stop"]
|
|
closed = price_streamer.handle_price_tick("WS/USDT", trailing_stop * 0.999, targets, event_time="2026-05-16T10:02:00")
|
|
|
|
assert activated["paper_trading"]["activated"] is True
|
|
assert closed["paper_trading"]["closed"] is True
|
|
assert closed["paper_trading"]["exit_reason"] == "trailing_stop"
|
|
|
|
|
|
def test_price_streamer_tracks_open_paper_trade_without_active_rec(buy_now_rec):
|
|
sync_recommendation(buy_now_rec, 100, event_time="2026-05-16T10:00:00")
|
|
|
|
targets = price_streamer.load_stream_targets()
|
|
|
|
assert "WS/USDT" in targets
|
|
assert targets["WS/USDT"]["id"] == buy_now_rec["id"]
|
|
|
|
|
|
def test_price_streamer_builds_binance_combined_stream_url():
|
|
url = price_streamer._stream_url(["BTC/USDT", "ETH/USDT"], {"stream_url": "wss://example.test/stream"})
|
|
|
|
assert url == "wss://example.test/stream?streams=btcusdt@ticker/ethusdt@ticker"
|
|
|
|
|
|
def test_price_streamer_parse_ticker_message():
|
|
symbol, price = price_streamer._parse_ticker_message('{"stream":"wsusdt@ticker","data":{"s":"WSUSDT","c":"101.5"}}')
|
|
|
|
assert symbol == "WS/USDT"
|
|
assert price == pytest.approx(101.5)
|