Add analytics risk guard and redesigned dashboard
This commit is contained in:
+7
-2
@@ -66,14 +66,19 @@ def make_settings():
|
||||
kelly_fraction=0.25,
|
||||
kelly_max_fraction=0.20,
|
||||
risk_per_trade_percent=0.01,
|
||||
risk_guard_enabled=True,
|
||||
risk_recent_trade_window=20,
|
||||
risk_max_consecutive_losses=4,
|
||||
risk_min_recent_profit_factor=0.85,
|
||||
risk_reduce_multiplier=0.50,
|
||||
atr_trailing_multiplier=2.2,
|
||||
trend_rsi_min=45.0,
|
||||
trend_rsi_max=65.0,
|
||||
time_series_forecast_enabled=True,
|
||||
time_series_min_candles=120,
|
||||
time_series_forecast_horizon=3,
|
||||
time_series_min_edge_percent=0.10,
|
||||
time_series_min_probability_up=0.64,
|
||||
time_series_min_edge_percent=0.08,
|
||||
time_series_min_probability_up=0.58,
|
||||
time_series_min_confidence=0.72,
|
||||
time_series_max_adjustment=0.08,
|
||||
time_series_lstm_enabled=True,
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from crypto_spot_bot.analytics import risk_guard_snapshot
|
||||
from crypto_spot_bot.data_quality import analyze_symbol_quality
|
||||
from crypto_spot_bot.models import Candle, Ticker, Trade, utc_now
|
||||
from crypto_spot_bot.storage import Storage
|
||||
|
||||
|
||||
def test_risk_guard_blocks_after_consecutive_losses(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, risk_max_consecutive_losses=2)
|
||||
storage = Storage(settings.database_path)
|
||||
now = utc_now()
|
||||
for _ in range(2):
|
||||
storage.insert_trade(
|
||||
Trade(
|
||||
id=None,
|
||||
symbol="BTCUSDT",
|
||||
side="SELL",
|
||||
qty=1.0,
|
||||
entry_price=100.0,
|
||||
exit_price=99.0,
|
||||
net_pnl=-1.0,
|
||||
opened_at=now,
|
||||
closed_at=now,
|
||||
entry_diagnostics={"forecast": {"probability_up": 0.64, "model": "torch_gru"}},
|
||||
)
|
||||
)
|
||||
|
||||
guard = risk_guard_snapshot(settings, storage.closed_trades(), storage.latest_equity())
|
||||
|
||||
assert guard["block_new_entries"] is True
|
||||
assert "consecutive_losses" in guard["reasons"]
|
||||
assert guard["position_size_multiplier"] == 0.0
|
||||
|
||||
|
||||
def test_data_quality_flags_missing_candle_gap() -> None:
|
||||
candles = [
|
||||
Candle(1_000_000, 100, 101, 99, 100.5, 10),
|
||||
Candle(1_000_000 + 60 * 60 * 1000 * 3, 100.5, 102, 100, 101, 12),
|
||||
]
|
||||
ticker = Ticker("BTCUSDT", 101, 100.99, 101.01, 1_000_000, 100, 0)
|
||||
|
||||
row = analyze_symbol_quality(symbol="BTCUSDT", candles=candles, ticker=ticker, interval="60")
|
||||
|
||||
assert row["status"] == "warn"
|
||||
assert any(issue["code"] == "missing_candles" for issue in row["issues"])
|
||||
@@ -58,6 +58,34 @@ def test_live_spot_order_explicitly_disables_leverage(make_settings, tmp_path) -
|
||||
assert captured["payload"]["orderFilter"] == "Order"
|
||||
|
||||
|
||||
def test_private_get_signs_the_same_query_it_sends(make_settings, tmp_path) -> None:
|
||||
client = BybitClient(make_settings(tmp_path))
|
||||
captured = {}
|
||||
|
||||
class Response:
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
def json(self):
|
||||
return {"retCode": 0, "result": {"ok": True}}
|
||||
|
||||
class Session:
|
||||
def get(self, url, params, headers, timeout):
|
||||
captured["url"] = url
|
||||
captured["params"] = params
|
||||
captured["headers"] = headers
|
||||
captured["timeout"] = timeout
|
||||
return Response()
|
||||
|
||||
client.session = Session()
|
||||
|
||||
assert client.wallet_balance(coin=None) == {"ok": True}
|
||||
|
||||
assert captured["params"] == [("accountType", "UNIFIED")]
|
||||
assert "coin" not in dict(captured["params"])
|
||||
assert captured["headers"]["X-BAPI-SIGN"]
|
||||
|
||||
|
||||
def test_websocket_subscribe_uses_configured_kline_interval() -> None:
|
||||
payload = websocket_subscribe_message(["BTCUSDT"], interval="60")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user