Stage 07.4.3.1 — trend strategy stabilization
This commit is contained in:
@@ -16,8 +16,18 @@ class AutoTradeService:
|
||||
_state = AutoTradeState()
|
||||
_loop_task: asyncio.Task | None = None
|
||||
_loop_interval_seconds = 5
|
||||
|
||||
# минимальное количество повторов BUY / SELL для подтверждения сигнала
|
||||
_confirm_repeats = 3
|
||||
|
||||
# минимальная уверенность для готовности к будущему execution
|
||||
_ready_confidence = 0.7
|
||||
|
||||
_last_signal_key: str | None = None
|
||||
_last_signal_value: str | None = None
|
||||
_last_signal_reason: str = ""
|
||||
_last_signal_confidence: float = 0.0
|
||||
_last_signal_payload: dict | None = None
|
||||
_same_signal_count = 0
|
||||
|
||||
# получить текущее состояние автоторговли
|
||||
@@ -106,15 +116,14 @@ class AutoTradeService:
|
||||
def set_symbol(self, symbol: str) -> AutoTradeState:
|
||||
state = self.get_state()
|
||||
state.symbol = symbol
|
||||
self._reset_signal_tracking()
|
||||
return state
|
||||
|
||||
# установить стратегию
|
||||
def set_strategy(self, strategy: str) -> AutoTradeState:
|
||||
state = self.get_state()
|
||||
state.strategy = strategy.strip().upper()
|
||||
self._last_signal_key = None
|
||||
self._last_signal_value = None
|
||||
self._same_signal_count = 0
|
||||
self._reset_signal_tracking()
|
||||
return state
|
||||
|
||||
# установить риск
|
||||
@@ -122,6 +131,30 @@ class AutoTradeService:
|
||||
state = self.get_state()
|
||||
state.risk_percent = risk_percent
|
||||
return state
|
||||
|
||||
# установить плечо
|
||||
def set_leverage(self, leverage: float) -> AutoTradeState:
|
||||
state = self.get_state()
|
||||
state.leverage = leverage
|
||||
return state
|
||||
|
||||
# сбросить внутренний трекинг сигналов
|
||||
def _reset_signal_tracking(self) -> None:
|
||||
self._last_signal_key = None
|
||||
self._last_signal_value = None
|
||||
self._last_signal_reason = ""
|
||||
self._last_signal_confidence = 0.0
|
||||
self._last_signal_payload = None
|
||||
self._same_signal_count = 0
|
||||
|
||||
state = self.get_state()
|
||||
state.last_signal_repeat_count = 0
|
||||
state.last_signal_confidence = 0.0
|
||||
state.last_signal_reason = None
|
||||
state.decision_status = "WAITING"
|
||||
state.decision_reason = None
|
||||
state.is_signal_confirmed = False
|
||||
state.is_signal_ready = False
|
||||
|
||||
# собрать контекст для стратегии
|
||||
def _build_strategy_context(self) -> StrategyContext:
|
||||
@@ -138,6 +171,46 @@ class AutoTradeService:
|
||||
state = self.get_state()
|
||||
return StrategyRegistry.get(state.strategy)
|
||||
|
||||
# обновить статус решения по текущему сигналу
|
||||
def _update_decision_state(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
signal: str,
|
||||
confidence: float,
|
||||
) -> None:
|
||||
state.is_signal_confirmed = False
|
||||
state.is_signal_ready = False
|
||||
|
||||
if signal == "HOLD":
|
||||
state.decision_status = "WAITING"
|
||||
state.decision_reason = "Нет торгового направления."
|
||||
return
|
||||
|
||||
if self._same_signal_count < self._confirm_repeats:
|
||||
state.decision_status = "CONFIRMING"
|
||||
state.decision_reason = (
|
||||
f"Сигнал {signal} подтверждается: "
|
||||
f"{self._same_signal_count}/{self._confirm_repeats} повторов."
|
||||
)
|
||||
return
|
||||
|
||||
state.is_signal_confirmed = True
|
||||
|
||||
if confidence < self._ready_confidence:
|
||||
state.decision_status = "BLOCKED"
|
||||
state.decision_reason = (
|
||||
f"Сигнал {signal} подтверждён, но уверенность низкая: "
|
||||
f"{confidence:.2f} < {self._ready_confidence:.2f}."
|
||||
)
|
||||
return
|
||||
|
||||
state.is_signal_ready = True
|
||||
state.decision_status = "READY"
|
||||
state.decision_reason = (
|
||||
f"Сигнал {signal} подтверждён и готов к будущему execution."
|
||||
)
|
||||
|
||||
# записать новый сигнал и итог предыдущей серии при смене сигнала
|
||||
def _log_signal_if_changed(
|
||||
self,
|
||||
@@ -156,6 +229,12 @@ class AutoTradeService:
|
||||
|
||||
if is_same_signal:
|
||||
self._same_signal_count += 1
|
||||
self._update_signal_state_fields(
|
||||
state=state,
|
||||
signal=signal,
|
||||
reason=reason,
|
||||
confidence=confidence,
|
||||
)
|
||||
return
|
||||
|
||||
if previous_signal is not None:
|
||||
@@ -166,6 +245,9 @@ class AutoTradeService:
|
||||
previous_signal=previous_signal,
|
||||
previous_count=previous_count,
|
||||
next_signal=signal,
|
||||
reason=self._last_signal_reason,
|
||||
confidence=self._last_signal_confidence,
|
||||
payload=self._last_signal_payload,
|
||||
)
|
||||
else:
|
||||
self._log_signal_event(
|
||||
@@ -173,7 +255,7 @@ class AutoTradeService:
|
||||
state=state,
|
||||
signal=previous_signal,
|
||||
reason=f"{previous_signal} завершился без серии.",
|
||||
confidence=0.0,
|
||||
confidence=self._last_signal_confidence,
|
||||
payload={
|
||||
"previous_signal": previous_signal,
|
||||
"next_signal": signal,
|
||||
@@ -182,13 +264,39 @@ class AutoTradeService:
|
||||
|
||||
self._last_signal_key = signal_key
|
||||
self._last_signal_value = signal
|
||||
self._last_signal_reason = reason
|
||||
self._last_signal_confidence = confidence
|
||||
self._last_signal_payload = payload
|
||||
self._same_signal_count = 1
|
||||
|
||||
# Новый сигнал не пишем сразу.
|
||||
# Он попадёт в журнал при следующей смене сигнала:
|
||||
# либо как одиночный сигнал, либо как серия.
|
||||
|
||||
# записать сам сигнал в журнал
|
||||
self._update_signal_state_fields(
|
||||
state=state,
|
||||
signal=signal,
|
||||
reason=reason,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
# обновить поля state для экрана автоторговли
|
||||
def _update_signal_state_fields(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
signal: str,
|
||||
reason: str,
|
||||
confidence: float,
|
||||
) -> None:
|
||||
state.last_signal = signal
|
||||
state.last_signal_repeat_count = self._same_signal_count
|
||||
state.last_signal_confidence = confidence
|
||||
state.last_signal_reason = reason
|
||||
|
||||
self._update_decision_state(
|
||||
state=state,
|
||||
signal=signal,
|
||||
confidence=confidence,
|
||||
)
|
||||
|
||||
# записать одиночный сигнал в журнал
|
||||
def _log_signal_event(
|
||||
self,
|
||||
*,
|
||||
@@ -220,7 +328,7 @@ class AutoTradeService:
|
||||
"confidence": confidence,
|
||||
"reason": reason,
|
||||
"repeat_count": 1,
|
||||
"is_strong_signal": confidence > 0.7,
|
||||
"is_strong_signal": confidence > self._ready_confidence,
|
||||
"is_aggregated": False,
|
||||
"payload": payload or {},
|
||||
},
|
||||
@@ -237,6 +345,9 @@ class AutoTradeService:
|
||||
previous_signal: str,
|
||||
previous_count: int,
|
||||
next_signal: str,
|
||||
reason: str,
|
||||
confidence: float,
|
||||
payload: dict | None,
|
||||
) -> None:
|
||||
emoji_map = {
|
||||
"BUY": "🟢",
|
||||
@@ -261,7 +372,11 @@ class AutoTradeService:
|
||||
"signal": previous_signal,
|
||||
"next_signal": next_signal,
|
||||
"repeat_count": previous_count,
|
||||
"confidence": confidence,
|
||||
"reason": reason,
|
||||
"is_strong_signal": confidence > self._ready_confidence,
|
||||
"is_aggregated": True,
|
||||
"payload": payload or {},
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
@@ -279,7 +394,6 @@ class AutoTradeService:
|
||||
result = strategy.analyze(context)
|
||||
|
||||
state.last_check_at = datetime.now().strftime("%H:%M:%S")
|
||||
state.last_signal = result.signal.value
|
||||
|
||||
self._log_signal_if_changed(
|
||||
strategy_name=strategy.name,
|
||||
@@ -290,8 +404,4 @@ class AutoTradeService:
|
||||
payload=result.payload,
|
||||
)
|
||||
|
||||
state.last_signal_repeat_count = self._same_signal_count
|
||||
state.last_signal_confidence = result.confidence
|
||||
state.last_signal_reason = result.reason
|
||||
|
||||
return state
|
||||
Reference in New Issue
Block a user