feat: enforce profit-only spot exits

This commit is contained in:
Курнат Андрей
2026-07-15 20:23:31 +03:00
parent 5082be2e5a
commit 991b77351c
8 changed files with 224 additions and 11 deletions
+28 -8
View File
@@ -13,7 +13,11 @@ from crypto_spot_bot.learning import TradeLearner
from crypto_spot_bot.market_data import MarketData
from crypto_spot_bot.models import BotStatus, Signal, Ticker, utc_now
from crypto_spot_bot.patterns import PatternAnalyzer
from crypto_spot_bot.strategy import SpotStrategy, torch_model_readiness_reasons
from crypto_spot_bot.strategy import (
SpotStrategy,
apply_profit_only_exit_policy,
torch_model_readiness_reasons,
)
from crypto_spot_bot.storage import Storage
from crypto_spot_bot.time_series import TimeSeriesForecaster, _barrier_outcome
@@ -165,6 +169,8 @@ class CryptoSpotBot:
adaptive_rules["reduce_now"] = position.id is not None and position.id == reduction_candidate_id
learning = {"adaptive_rules": adaptive_rules}
signal = self.strategy.exit_signal(position, candles, ticker, learning, forecast)
if ticker is not None:
signal = apply_profit_only_exit_policy(self.settings, position, ticker, signal)
self._record_signal(signal)
if signal.action == "SELL" and ticker is not None:
await asyncio.to_thread(self.broker.sell, position, ticker, signal.reason)
@@ -371,14 +377,28 @@ class CryptoSpotBot:
volume_24h=0.0,
change_24h=0.0,
)
self.broker.sell(
position,
synthetic_ticker,
f"{self.settings.strategy_mode}: закрыта старая paper-позиция вне списка разрешенных пар",
)
self.storage.event(
f"{position.symbol}: старая paper-позиция закрыта при переходе на {self.settings.strategy_mode}"
candidate = Signal(
position.symbol,
"SELL",
0.5,
f"{self.settings.strategy_mode}: старая paper-позиция вне списка разрешенных пар",
{
"emergency_exit": True,
"emergency_exit_type": "symbol_removed_from_universe",
},
)
decision = apply_profit_only_exit_policy(self.settings, position, synthetic_ticker, candidate)
self._record_signal(decision)
if decision.action == "SELL":
self.broker.sell(position, synthetic_ticker, decision.reason)
self.storage.event(
f"{position.symbol}: старая paper-позиция закрыта при переходе на {self.settings.strategy_mode}"
)
else:
self.storage.event(
f"{position.symbol}: старая paper-позиция сохранена политикой profit-only",
"WARN",
)
def _reduction_candidate_id(self, prices: dict[str, float]) -> int | None:
rules = self._with_exposure_context(self.learner.state.adaptive_rules or {})