Avoid fee-negative ATR exits
This commit is contained in:
@@ -909,6 +909,15 @@ def _torch_forecast_exit_signal(
|
|||||||
if price >= position.take_profit:
|
if price >= position.take_profit:
|
||||||
return Signal(position.symbol, "SELL", 0.96, "torch_forecast: take-profit hit", diagnostics)
|
return Signal(position.symbol, "SELL", 0.96, "torch_forecast: take-profit hit", diagnostics)
|
||||||
if atr_trailing_stop is not None and price <= atr_trailing_stop:
|
if atr_trailing_stop is not None and price <= atr_trailing_stop:
|
||||||
|
if estimated_exit_net_percent < 0:
|
||||||
|
diagnostics["atr_exit_blocked_by_cost"] = True
|
||||||
|
return Signal(
|
||||||
|
position.symbol,
|
||||||
|
"HOLD",
|
||||||
|
0.45,
|
||||||
|
"torch_forecast: ATR trailing touched, but exit is not worth fees",
|
||||||
|
diagnostics,
|
||||||
|
)
|
||||||
return Signal(position.symbol, "SELL", 0.94, "torch_forecast: ATR trailing stop hit", diagnostics)
|
return Signal(position.symbol, "SELL", 0.94, "torch_forecast: ATR trailing stop hit", diagnostics)
|
||||||
if not _is_torch_forecast(forecast):
|
if not _is_torch_forecast(forecast):
|
||||||
if rebound_fallback_position:
|
if rebound_fallback_position:
|
||||||
|
|||||||
@@ -917,6 +917,43 @@ def test_torch_forecast_holds_fee_churn_exit_after_min_hold(make_settings, tmp_p
|
|||||||
assert signal.diagnostics["forecast_exit_blocked_by_cost"] is True
|
assert signal.diagnostics["forecast_exit_blocked_by_cost"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_torch_forecast_holds_atr_trailing_exit_that_does_not_cover_fees(make_settings, tmp_path) -> None:
|
||||||
|
settings = make_settings(tmp_path, strategy_mode="torch_forecast", min_hold_seconds=60)
|
||||||
|
strategy = SpotStrategy(settings)
|
||||||
|
candles = _trend_entry_candles(close=100.2)
|
||||||
|
candles[-1].atr_14 = 0.8
|
||||||
|
position = Position(
|
||||||
|
1,
|
||||||
|
"MNTUSDT",
|
||||||
|
1,
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
0.1,
|
||||||
|
96,
|
||||||
|
120,
|
||||||
|
102,
|
||||||
|
opened_at=utc_now() - timedelta(seconds=600),
|
||||||
|
)
|
||||||
|
ticker = Ticker("MNTUSDT", 100.2, 100.19, 100.21, 10_000_000, 1000, 1.0)
|
||||||
|
|
||||||
|
signal = strategy.exit_signal(
|
||||||
|
position,
|
||||||
|
candles,
|
||||||
|
ticker,
|
||||||
|
forecast={
|
||||||
|
"usable": True,
|
||||||
|
"model": "torch_lstm",
|
||||||
|
"expected_return_percent": 0.4,
|
||||||
|
"probability_up": 0.58,
|
||||||
|
"skill": 0.18,
|
||||||
|
"block_entry": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert signal.action == "HOLD"
|
||||||
|
assert signal.diagnostics["atr_exit_blocked_by_cost"] is True
|
||||||
|
|
||||||
|
|
||||||
def test_torch_forecast_rebound_fallback_holds_without_model(make_settings, tmp_path) -> None:
|
def test_torch_forecast_rebound_fallback_holds_without_model(make_settings, tmp_path) -> None:
|
||||||
settings = make_settings(tmp_path, strategy_mode="torch_forecast", min_hold_seconds=180)
|
settings = make_settings(tmp_path, strategy_mode="torch_forecast", min_hold_seconds=180)
|
||||||
strategy = SpotStrategy(settings)
|
strategy = SpotStrategy(settings)
|
||||||
|
|||||||
Reference in New Issue
Block a user