Use Torch forecast as primary strategy
This commit is contained in:
+34
-1
@@ -77,7 +77,40 @@ def test_default_symbols_are_fixed_trend_pairs(tmp_path, monkeypatch) -> None:
|
||||
assert settings.auto_select_symbols is False
|
||||
assert settings.top_symbols_count == 3
|
||||
assert settings.symbols == FIXED_SPOT_SYMBOLS
|
||||
assert settings.strategy_mode == "trend_macd"
|
||||
assert settings.strategy_mode == "torch_forecast"
|
||||
assert settings.base_interval == "60"
|
||||
assert settings.trend_interval == "D"
|
||||
assert settings.risk_per_trade_percent == 0.01
|
||||
assert settings.time_series_forecast_enabled is True
|
||||
|
||||
|
||||
def test_torch_forecast_forces_fixed_three_symbols(tmp_path, monkeypatch) -> None:
|
||||
for key in (
|
||||
"AUTO_SELECT_SYMBOLS",
|
||||
"TOP_SYMBOLS_COUNT",
|
||||
"SYMBOLS",
|
||||
"STRATEGY_MODE",
|
||||
"TIME_SERIES_FORECAST_ENABLED",
|
||||
):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
monkeypatch.setenv("TRADING_MODE", "paper")
|
||||
env_file = tmp_path / ".env"
|
||||
env_file.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"TRADING_MODE=paper",
|
||||
"STRATEGY_MODE=torch_forecast",
|
||||
"AUTO_SELECT_SYMBOLS=true",
|
||||
"TOP_SYMBOLS_COUNT=9",
|
||||
"SYMBOLS=DOGEUSDT,XRPUSDT",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
settings = load_settings(env_file)
|
||||
|
||||
assert settings.auto_select_symbols is False
|
||||
assert settings.top_symbols_count == 3
|
||||
assert settings.symbols == FIXED_SPOT_SYMBOLS
|
||||
assert settings.time_series_forecast_enabled is True
|
||||
|
||||
@@ -160,6 +160,31 @@ def test_trend_macd_broker_blocks_dca_for_same_symbol(make_settings, tmp_path) -
|
||||
assert len(broker.open_positions()) == 1
|
||||
|
||||
|
||||
def test_torch_forecast_broker_blocks_dca_for_same_symbol(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
strategy_mode="torch_forecast",
|
||||
min_position_usdt=1,
|
||||
max_position_usdt=20,
|
||||
max_symbol_exposure_usdt=20,
|
||||
max_total_exposure_usdt=80,
|
||||
max_open_positions=3,
|
||||
max_positions_per_symbol=20,
|
||||
max_entries_per_minute=0,
|
||||
)
|
||||
storage = Storage(settings.database_path)
|
||||
broker = PaperBroker(settings, storage)
|
||||
ticker = Ticker("BTCUSDT", 100, 99.9, 100.1, 10_000_000, 100, 0)
|
||||
instrument = Instrument("BTCUSDT", "BTC", "USDT", "Trading", 0.01, 0.000001, 0.000001, 1)
|
||||
|
||||
first = broker.buy(Signal("BTCUSDT", "BUY", 0.8, "first", {"position_notional_usdt": 2}), ticker, instrument, {"BTCUSDT": 100})
|
||||
second = broker.buy(Signal("BTCUSDT", "BUY", 0.8, "second", {"position_notional_usdt": 2}), ticker, instrument, {"BTCUSDT": 100})
|
||||
|
||||
assert first is not None
|
||||
assert second is None
|
||||
assert len(broker.open_positions()) == 1
|
||||
|
||||
|
||||
def test_trend_macd_closes_old_paper_positions_outside_symbol_universe(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
|
||||
@@ -233,6 +233,81 @@ def test_trend_macd_exits_on_atr_trailing_stop(make_settings, tmp_path) -> None:
|
||||
assert "ATR trailing" in signal.reason
|
||||
|
||||
|
||||
def test_torch_forecast_buys_only_from_positive_torch_edge(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(
|
||||
tmp_path,
|
||||
strategy_mode="torch_forecast",
|
||||
max_position_usdt=25,
|
||||
stop_loss_percent=0.04,
|
||||
risk_per_trade_percent=0.01,
|
||||
)
|
||||
strategy = SpotStrategy(settings)
|
||||
ticker = Ticker("BTCUSDT", 105, 104.99, 105.01, 10_000_000, 1000, 1.0)
|
||||
|
||||
signal = strategy.entry_signal(
|
||||
"BTCUSDT",
|
||||
[],
|
||||
ticker,
|
||||
open_positions_for_symbol=0,
|
||||
forecast={
|
||||
"usable": True,
|
||||
"model": "torch_gru",
|
||||
"expected_return_percent": 0.24,
|
||||
"probability_up": 0.63,
|
||||
"skill": 0.22,
|
||||
"block_entry": False,
|
||||
},
|
||||
account={"equity": 100.0},
|
||||
)
|
||||
|
||||
assert signal.action == "BUY"
|
||||
assert signal.diagnostics["strategy_mode"] == "torch_forecast"
|
||||
assert signal.diagnostics["checks"]["torch_model_ok"] is True
|
||||
assert signal.diagnostics["position_notional_usdt"] == 25.0
|
||||
|
||||
|
||||
def test_torch_forecast_blocks_without_valid_torch_model(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, strategy_mode="torch_forecast")
|
||||
strategy = SpotStrategy(settings)
|
||||
ticker = Ticker("ETHUSDT", 105, 104.99, 105.01, 10_000_000, 1000, 1.0)
|
||||
|
||||
signal = strategy.entry_signal(
|
||||
"ETHUSDT",
|
||||
_trend_entry_candles(),
|
||||
ticker,
|
||||
open_positions_for_symbol=0,
|
||||
forecast={"usable": True, "model": "none", "expected_return_percent": 0.5, "probability_up": 0.7},
|
||||
account={"equity": 100.0},
|
||||
)
|
||||
|
||||
assert signal.action == "HOLD"
|
||||
assert signal.diagnostics["checks"]["torch_model_ok"] is False
|
||||
|
||||
|
||||
def test_torch_forecast_exits_when_forecast_turns_negative(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path, strategy_mode="torch_forecast", stop_loss_percent=0.04)
|
||||
strategy = SpotStrategy(settings)
|
||||
position = Position(1, "SOLUSDT", 1, 100, 100, 0.1, 96, 120, 103)
|
||||
ticker = Ticker("SOLUSDT", 101, 100.99, 101.01, 10_000_000, 1000, 1.0)
|
||||
|
||||
signal = strategy.exit_signal(
|
||||
position,
|
||||
_trend_entry_candles(),
|
||||
ticker,
|
||||
forecast={
|
||||
"usable": True,
|
||||
"model": "torch_lstm",
|
||||
"expected_return_percent": -0.08,
|
||||
"probability_up": 0.43,
|
||||
"skill": 0.18,
|
||||
"block_entry": True,
|
||||
},
|
||||
)
|
||||
|
||||
assert signal.action == "SELL"
|
||||
assert "torch_forecast" in signal.reason
|
||||
|
||||
|
||||
def test_strategy_emits_buy_when_score_passes_threshold(make_settings, tmp_path) -> None:
|
||||
settings = make_settings(tmp_path)
|
||||
strategy = SpotStrategy(settings)
|
||||
|
||||
Reference in New Issue
Block a user