Close stale paper positions outside trend universe

This commit is contained in:
Codex
2026-06-21 23:30:10 +03:00
parent 8a211acf98
commit 6b9bddeb0a
2 changed files with 75 additions and 1 deletions
+27 -1
View File
@@ -7,7 +7,7 @@ from crypto_spot_bot.config import Settings
from crypto_spot_bot.execution import LiveBroker, PaperBroker
from crypto_spot_bot.learning import TradeLearner
from crypto_spot_bot.market_data import MarketData
from crypto_spot_bot.models import BotStatus, Signal, utc_now
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
from crypto_spot_bot.storage import Storage
@@ -50,6 +50,7 @@ class CryptoSpotBot:
self.market.reset_stop()
if not self.market.symbols:
await self.market.bootstrap()
self._close_paper_positions_outside_symbol_universe()
self._update_patterns()
self._update_forecasts()
self.learner.refresh()
@@ -198,6 +199,31 @@ class CryptoSpotBot:
enriched["over_target_exposure"] = current_exposure > target_total + self.settings.min_position_usdt
return enriched
def _close_paper_positions_outside_symbol_universe(self) -> None:
if self.settings.strategy_mode != "trend_macd" or self.settings.trading_mode != "paper":
return
allowed_symbols = set(self.market.symbols or self.settings.symbols)
for position in list(self.broker.open_positions()):
if position.symbol in allowed_symbols:
continue
synthetic_ticker = Ticker(
symbol=position.symbol,
last_price=position.entry_price,
bid=position.entry_price,
ask=position.entry_price,
turnover_24h=0.0,
volume_24h=0.0,
change_24h=0.0,
)
self.broker.sell(
position,
synthetic_ticker,
"trend_macd: закрыта старая paper-позиция вне списка разрешенных пар",
)
self.storage.event(
f"{position.symbol}: старая paper-позиция закрыта при переходе на trend_macd"
)
def _reduction_candidate_id(self, prices: dict[str, float]) -> int | None:
rules = self._with_exposure_context(self.learner.state.adaptive_rules or {})
if not rules.get("reduce_exposure") or not rules.get("over_target_exposure"):
+48
View File
@@ -1,9 +1,13 @@
from __future__ import annotations
from types import SimpleNamespace
from crypto_spot_bot.bybit import Instrument
from crypto_spot_bot.bot import CryptoSpotBot
from crypto_spot_bot.execution import PaperBroker
from crypto_spot_bot.models import Signal, Ticker
from crypto_spot_bot.storage import Storage
from crypto_spot_bot.strategy import SpotStrategy
def test_paper_broker_buy_and_sell_records_trade(make_settings, tmp_path) -> None:
@@ -154,3 +158,47 @@ def test_trend_macd_broker_blocks_dca_for_same_symbol(make_settings, tmp_path) -
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,
strategy_mode="trend_macd",
trading_mode="paper",
symbols=("BTCUSDT", "ETHUSDT", "SOLUSDT"),
min_position_usdt=1,
max_position_usdt=20,
max_symbol_exposure_usdt=20,
max_total_exposure_usdt=80,
max_open_positions=3,
max_entries_per_minute=0,
)
storage = Storage(settings.database_path)
broker = PaperBroker(settings, storage)
ticker = Ticker("HYPEUSDT", 10, 9.99, 10.01, 10_000_000, 100, 0)
instrument = Instrument("HYPEUSDT", "HYPE", "USDT", "Trading", 0.001, 0.001, 0.001, 1)
position = broker.buy(
Signal("HYPEUSDT", "BUY", 0.8, "old", {"position_notional_usdt": 5}),
ticker,
instrument,
{"HYPEUSDT": 10},
)
assert position is not None
bot = CryptoSpotBot(
settings,
storage,
SimpleNamespace(symbols=["BTCUSDT", "ETHUSDT", "SOLUSDT"]),
broker,
SpotStrategy(settings),
object(),
object(),
)
bot._close_paper_positions_outside_symbol_universe()
assert broker.open_positions() == []
assert storage.open_positions() == []
trade = storage.recent_trades(limit=1)[0]
assert trade["side"] == "SELL"
assert trade["symbol"] == "HYPEUSDT"
assert "trend_macd" in trade["reason"]