Allow disabling stop loss exits
This commit is contained in:
@@ -138,6 +138,7 @@ class Settings:
|
||||
time_series_probe_size_multiplier: float
|
||||
time_series_rebound_fallback_enabled: bool
|
||||
stop_loss_percent: float
|
||||
stop_loss_exit_enabled: bool
|
||||
take_profit_percent: float
|
||||
trailing_stop_percent: float
|
||||
min_hold_seconds: int
|
||||
@@ -289,6 +290,7 @@ def load_settings(env_file: str | Path | None = None) -> Settings:
|
||||
time_series_probe_size_multiplier=_float_env("TIME_SERIES_PROBE_SIZE_MULTIPLIER", 0.40),
|
||||
time_series_rebound_fallback_enabled=_bool_env("TIME_SERIES_REBOUND_FALLBACK_ENABLED", True),
|
||||
stop_loss_percent=_float_env("STOP_LOSS_PERCENT", 0.04),
|
||||
stop_loss_exit_enabled=_bool_env("STOP_LOSS_EXIT_ENABLED", True),
|
||||
take_profit_percent=_float_env("TAKE_PROFIT_PERCENT", 0.035),
|
||||
trailing_stop_percent=_float_env("TRAILING_STOP_PERCENT", 0.015),
|
||||
min_hold_seconds=_int_env("MIN_HOLD_SECONDS", 180),
|
||||
|
||||
@@ -314,6 +314,7 @@ def _safe_config(settings: Settings) -> dict[str, Any]:
|
||||
"time_series_rebound_fallback_enabled": settings.time_series_rebound_fallback_enabled,
|
||||
"time_series_model_artifact": _time_series_model_artifact(settings),
|
||||
"stop_loss_percent": settings.stop_loss_percent,
|
||||
"stop_loss_exit_enabled": settings.stop_loss_exit_enabled,
|
||||
"take_profit_percent": settings.take_profit_percent,
|
||||
"trailing_stop_percent": settings.trailing_stop_percent,
|
||||
"min_hold_seconds": settings.min_hold_seconds,
|
||||
|
||||
@@ -299,6 +299,7 @@ def _neutral_rules(settings: Settings, reason: str | None = None) -> dict[str, A
|
||||
"ema_exit_mode": "normal",
|
||||
"rsi_exit_mode": "normal",
|
||||
"stop_loss_percent": settings.stop_loss_percent,
|
||||
"stop_loss_exit_enabled": settings.stop_loss_exit_enabled,
|
||||
"take_profit_percent": settings.take_profit_percent,
|
||||
"trailing_stop_percent": settings.trailing_stop_percent,
|
||||
"symbol_threshold_adjustments": {},
|
||||
|
||||
+41
-14
@@ -320,7 +320,8 @@ class SpotStrategy:
|
||||
diagnostics = {
|
||||
"price": price,
|
||||
"entry_price": position.entry_price,
|
||||
"stop_loss": position.stop_loss,
|
||||
"stop_loss": position.stop_loss if self.settings.stop_loss_exit_enabled else None,
|
||||
"stop_loss_exit_enabled": self.settings.stop_loss_exit_enabled,
|
||||
"take_profit": position.take_profit,
|
||||
"highest_price": position.highest_price,
|
||||
"trailing_stop": trailing,
|
||||
@@ -328,7 +329,7 @@ class SpotStrategy:
|
||||
"ema_20": latest.ema_20,
|
||||
"ema_50": latest.ema_50,
|
||||
}
|
||||
if price <= position.stop_loss:
|
||||
if self.settings.stop_loss_exit_enabled and price <= position.stop_loss:
|
||||
return Signal(position.symbol, "SELL", 1.0, "сработал стоп-лосс", diagnostics)
|
||||
if price >= position.take_profit:
|
||||
return Signal(position.symbol, "SELL", 0.96, "сработал тейк-профит", diagnostics)
|
||||
@@ -389,7 +390,7 @@ class SpotStrategy:
|
||||
trailing_percent = _adaptive_percent(
|
||||
adaptive, "trailing_stop_percent", self.settings.trailing_stop_percent, 0.003, 0.08
|
||||
)
|
||||
effective_stop_loss = max(position.stop_loss, position.entry_price * (1 - stop_loss_percent))
|
||||
effective_stop_loss = _effective_stop_loss(self.settings, position, stop_loss_percent)
|
||||
effective_take_profit = position.entry_price * (1 + take_profit_percent)
|
||||
trailing = position.trailing_stop(trailing_percent)
|
||||
estimated_exit_net_percent = _estimated_exit_net_percent(position, price, self.settings)
|
||||
@@ -397,6 +398,7 @@ class SpotStrategy:
|
||||
"price": price,
|
||||
"entry_price": position.entry_price,
|
||||
"stop_loss": effective_stop_loss,
|
||||
"stop_loss_exit_enabled": self.settings.stop_loss_exit_enabled,
|
||||
"take_profit": effective_take_profit,
|
||||
"highest_price": position.highest_price,
|
||||
"trailing_stop": trailing,
|
||||
@@ -408,7 +410,7 @@ class SpotStrategy:
|
||||
"estimated_exit_net_percent": round(estimated_exit_net_percent, 4),
|
||||
"min_exit_profit_percent": float(adaptive.get("min_exit_profit_percent", 0.0) or 0.0),
|
||||
}
|
||||
if price <= effective_stop_loss:
|
||||
if effective_stop_loss is not None and price <= effective_stop_loss:
|
||||
return Signal(position.symbol, "SELL", 1.0, "сработал стоп-лосс", diagnostics)
|
||||
if price >= effective_take_profit:
|
||||
return Signal(position.symbol, "SELL", 0.96, "сработал тейк-профит", diagnostics)
|
||||
@@ -580,11 +582,9 @@ def _trend_macd_exit_signal(
|
||||
previous = candles[-2]
|
||||
price = ticker.last_price
|
||||
stop_loss_percent = _clamp(settings.stop_loss_percent, 0.003, 0.08)
|
||||
effective_stop_loss = max(position.stop_loss, position.entry_price * (1 - stop_loss_percent))
|
||||
effective_stop_loss = _effective_stop_loss(settings, position, stop_loss_percent)
|
||||
atr_multiplier = _clamp(settings.atr_trailing_multiplier, 0.5, 10.0)
|
||||
atr_trailing_stop = None
|
||||
if latest.atr_14 is not None and position.highest_price > position.entry_price:
|
||||
atr_trailing_stop = max(effective_stop_loss, position.highest_price - latest.atr_14 * atr_multiplier)
|
||||
atr_trailing_stop = _atr_trailing_stop(settings, position, latest.atr_14, atr_multiplier, effective_stop_loss)
|
||||
macd_cross_down = _macd_crossed_down(previous, latest)
|
||||
close_below_ema50 = latest.ema_50 is not None and latest.close < latest.ema_50
|
||||
diagnostics = {
|
||||
@@ -592,6 +592,7 @@ def _trend_macd_exit_signal(
|
||||
"price": price,
|
||||
"entry_price": position.entry_price,
|
||||
"stop_loss": effective_stop_loss,
|
||||
"stop_loss_exit_enabled": settings.stop_loss_exit_enabled,
|
||||
"atr_trailing_stop": atr_trailing_stop,
|
||||
"atr_trailing_multiplier": atr_multiplier,
|
||||
"highest_price": position.highest_price,
|
||||
@@ -603,7 +604,7 @@ def _trend_macd_exit_signal(
|
||||
"macd_cross_down": macd_cross_down,
|
||||
"close_below_ema50": close_below_ema50,
|
||||
}
|
||||
if price <= effective_stop_loss:
|
||||
if effective_stop_loss is not None and price <= effective_stop_loss:
|
||||
return Signal(position.symbol, "SELL", 1.0, "trend_macd: сработал стоп-лосс", diagnostics)
|
||||
if atr_trailing_stop is not None and price <= atr_trailing_stop:
|
||||
return Signal(position.symbol, "SELL", 0.94, "trend_macd: сработал ATR trailing stop", diagnostics)
|
||||
@@ -859,11 +860,15 @@ def _torch_forecast_exit_signal(
|
||||
latest = candles[-1] if candles else None
|
||||
price = ticker.last_price
|
||||
stop_loss_percent = _clamp(settings.stop_loss_percent, 0.003, 0.08)
|
||||
effective_stop_loss = max(position.stop_loss, position.entry_price * (1 - stop_loss_percent))
|
||||
effective_stop_loss = _effective_stop_loss(settings, position, stop_loss_percent)
|
||||
atr_multiplier = _clamp(settings.atr_trailing_multiplier, 0.5, 10.0)
|
||||
atr_trailing_stop = None
|
||||
if latest and latest.atr_14 is not None and position.highest_price > position.entry_price:
|
||||
atr_trailing_stop = max(effective_stop_loss, position.highest_price - latest.atr_14 * atr_multiplier)
|
||||
atr_trailing_stop = _atr_trailing_stop(
|
||||
settings,
|
||||
position,
|
||||
latest.atr_14 if latest else None,
|
||||
atr_multiplier,
|
||||
effective_stop_loss,
|
||||
)
|
||||
|
||||
expected_return = _safe_float(forecast.get("expected_return_percent"), 0.0)
|
||||
probability_up = _safe_float(forecast.get("probability_up"), 0.5)
|
||||
@@ -879,6 +884,7 @@ def _torch_forecast_exit_signal(
|
||||
"price": price,
|
||||
"entry_price": position.entry_price,
|
||||
"stop_loss": effective_stop_loss,
|
||||
"stop_loss_exit_enabled": settings.stop_loss_exit_enabled,
|
||||
"take_profit": position.take_profit,
|
||||
"atr_trailing_stop": atr_trailing_stop,
|
||||
"atr_trailing_multiplier": atr_multiplier,
|
||||
@@ -895,7 +901,7 @@ def _torch_forecast_exit_signal(
|
||||
"estimated_exit_net_percent": round(estimated_exit_net_percent, 4),
|
||||
"atr_14": latest.atr_14 if latest else None,
|
||||
}
|
||||
if price <= effective_stop_loss:
|
||||
if effective_stop_loss is not None and price <= effective_stop_loss:
|
||||
return Signal(position.symbol, "SELL", 1.0, "torch_forecast: stop-loss hit", diagnostics)
|
||||
if price >= position.take_profit:
|
||||
return Signal(position.symbol, "SELL", 0.96, "torch_forecast: take-profit hit", diagnostics)
|
||||
@@ -1565,6 +1571,27 @@ def _adaptive_percent(adaptive: dict, key: str, default: float, low: float, high
|
||||
return _clamp(_safe_float(adaptive.get(key), default), low, high)
|
||||
|
||||
|
||||
def _effective_stop_loss(settings: Settings, position: Position, stop_loss_percent: float) -> float | None:
|
||||
if not settings.stop_loss_exit_enabled:
|
||||
return None
|
||||
return max(position.stop_loss, position.entry_price * (1 - stop_loss_percent))
|
||||
|
||||
|
||||
def _atr_trailing_stop(
|
||||
settings: Settings,
|
||||
position: Position,
|
||||
atr: float | None,
|
||||
atr_multiplier: float,
|
||||
effective_stop_loss: float | None,
|
||||
) -> float | None:
|
||||
if atr is None or atr <= 0 or position.highest_price <= position.entry_price:
|
||||
return None
|
||||
raw_stop = position.highest_price - atr * atr_multiplier
|
||||
if settings.stop_loss_exit_enabled and effective_stop_loss is not None:
|
||||
return max(effective_stop_loss, raw_stop)
|
||||
return raw_stop if raw_stop > position.entry_price else None
|
||||
|
||||
|
||||
def _estimated_exit_net_percent(position: Position, price: float, settings: Settings) -> float:
|
||||
if position.entry_price <= 0:
|
||||
return 0.0
|
||||
|
||||
Reference in New Issue
Block a user