07.4.3.19.3 — Strategy Noise Filter & Signal Intent Layer
This commit is contained in:
@@ -7,12 +7,12 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from src.core.config import load_settings
|
from src.core.config import load_settings
|
||||||
|
from src.core.event_bus import EventBus
|
||||||
from src.trading.auto.state import AutoTradeState
|
from src.trading.auto.state import AutoTradeState
|
||||||
|
from src.trading.execution.engine import ExecutionEngine
|
||||||
from src.trading.journal.service import JournalService
|
from src.trading.journal.service import JournalService
|
||||||
from src.trading.strategies.base import BaseStrategy, StrategyContext
|
from src.trading.strategies.base import BaseStrategy, StrategyContext
|
||||||
from src.trading.strategies.registry import StrategyRegistry
|
from src.trading.strategies.registry import StrategyRegistry
|
||||||
from src.core.event_bus import EventBus
|
|
||||||
from src.trading.execution.engine import ExecutionEngine
|
|
||||||
|
|
||||||
|
|
||||||
class AutoTradeService:
|
class AutoTradeService:
|
||||||
@@ -31,9 +31,10 @@ class AutoTradeService:
|
|||||||
_last_signal_reason: str = ""
|
_last_signal_reason: str = ""
|
||||||
_last_signal_confidence: float = 0.0
|
_last_signal_confidence: float = 0.0
|
||||||
_last_signal_payload: dict | None = None
|
_last_signal_payload: dict | None = None
|
||||||
|
_last_signal_started_at: float | None = None
|
||||||
_same_signal_count = 0
|
_same_signal_count = 0
|
||||||
|
|
||||||
# debug: принудительно выставить сигнал и decision
|
# debug: принудительно выставить сигнал и decision
|
||||||
def debug_force_signal(
|
def debug_force_signal(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -50,7 +51,7 @@ class AutoTradeService:
|
|||||||
|
|
||||||
previous_signal = state.last_signal
|
previous_signal = state.last_signal
|
||||||
previous_decision_status = state.decision_status
|
previous_decision_status = state.decision_status
|
||||||
|
|
||||||
if previous_signal != normalized_signal or state.signal_started_at is None:
|
if previous_signal != normalized_signal or state.signal_started_at is None:
|
||||||
state.signal_started_at = time.monotonic()
|
state.signal_started_at = time.monotonic()
|
||||||
|
|
||||||
@@ -70,6 +71,11 @@ class AutoTradeService:
|
|||||||
state.is_signal_confirmed = True
|
state.is_signal_confirmed = True
|
||||||
state.is_signal_ready = True
|
state.is_signal_ready = True
|
||||||
|
|
||||||
|
signal_intent = self._signal_intent(
|
||||||
|
state=state,
|
||||||
|
signal=state.last_signal,
|
||||||
|
)
|
||||||
|
|
||||||
EventBus.emit(
|
EventBus.emit(
|
||||||
"auto_decision_changed",
|
"auto_decision_changed",
|
||||||
{
|
{
|
||||||
@@ -77,6 +83,7 @@ class AutoTradeService:
|
|||||||
"previous_decision_status": previous_decision_status,
|
"previous_decision_status": previous_decision_status,
|
||||||
"decision_status": state.decision_status,
|
"decision_status": state.decision_status,
|
||||||
"signal": state.last_signal,
|
"signal": state.last_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
"repeat_count": state.last_signal_repeat_count,
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
"confidence": state.last_signal_confidence,
|
"confidence": state.last_signal_confidence,
|
||||||
"symbol": state.symbol,
|
"symbol": state.symbol,
|
||||||
@@ -88,7 +95,7 @@ class AutoTradeService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# установить капитал, выделенный под автоторговлю
|
# установить капитал, выделенный под автоторговлю
|
||||||
def set_allocated_balance_usd(self, value: float) -> AutoTradeState:
|
def set_allocated_balance_usd(self, value: float) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
@@ -100,7 +107,7 @@ class AutoTradeService:
|
|||||||
state.execution_block_reason = None
|
state.execution_block_reason = None
|
||||||
state.execution_size_adjustment_reason = None
|
state.execution_size_adjustment_reason = None
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# получить текущее состояние автоторговли
|
# получить текущее состояние автоторговли
|
||||||
def get_state(self) -> AutoTradeState:
|
def get_state(self) -> AutoTradeState:
|
||||||
if not self._state.symbol:
|
if not self._state.symbol:
|
||||||
@@ -160,6 +167,7 @@ class AutoTradeService:
|
|||||||
self._reset_signal_tracking()
|
self._reset_signal_tracking()
|
||||||
state.last_signal = "HOLD"
|
state.last_signal = "HOLD"
|
||||||
state.signal_started_at = time.monotonic()
|
state.signal_started_at = time.monotonic()
|
||||||
|
|
||||||
EventBus.emit(
|
EventBus.emit(
|
||||||
"auto_status_changed",
|
"auto_status_changed",
|
||||||
{
|
{
|
||||||
@@ -233,14 +241,14 @@ class AutoTradeService:
|
|||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.risk_percent = risk_percent
|
state.risk_percent = risk_percent
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# установить плечо
|
# установить плечо
|
||||||
def set_leverage(self, leverage: float) -> AutoTradeState:
|
def set_leverage(self, leverage: float) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.leverage = leverage
|
state.leverage = leverage
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# установить stop loss в %
|
# установить stop loss в %
|
||||||
def set_stop_loss_percent(self, value: float | None) -> AutoTradeState:
|
def set_stop_loss_percent(self, value: float | None) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.stop_loss_percent = value
|
state.stop_loss_percent = value
|
||||||
@@ -257,7 +265,7 @@ class AutoTradeService:
|
|||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.max_loss_usd = value
|
state.max_loss_usd = value
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# установить максимальное использование баланса под маржу
|
# установить максимальное использование баланса под маржу
|
||||||
def set_max_reserved_balance_percent(self, value: float | None) -> AutoTradeState:
|
def set_max_reserved_balance_percent(self, value: float | None) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
@@ -272,6 +280,7 @@ class AutoTradeService:
|
|||||||
self._last_signal_reason = ""
|
self._last_signal_reason = ""
|
||||||
self._last_signal_confidence = 0.0
|
self._last_signal_confidence = 0.0
|
||||||
self._last_signal_payload = None
|
self._last_signal_payload = None
|
||||||
|
self._last_signal_started_at = None
|
||||||
self._same_signal_count = 0
|
self._same_signal_count = 0
|
||||||
|
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
@@ -300,6 +309,34 @@ class AutoTradeService:
|
|||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
return StrategyRegistry.get(state.strategy)
|
return StrategyRegistry.get(state.strategy)
|
||||||
|
|
||||||
|
# определить смысл сигнала с учетом открытой позиции
|
||||||
|
def _signal_intent(self, *, state: AutoTradeState, signal: str | None) -> str:
|
||||||
|
normalized_signal = (signal or "HOLD").upper()
|
||||||
|
position_side = str(getattr(state, "position_side", "NONE") or "NONE").upper()
|
||||||
|
|
||||||
|
if normalized_signal == "HOLD":
|
||||||
|
return "HOLD_MARKET"
|
||||||
|
|
||||||
|
if normalized_signal not in {"BUY", "SELL"}:
|
||||||
|
return "NOISE"
|
||||||
|
|
||||||
|
if position_side == "NONE":
|
||||||
|
return "ENTRY_CANDIDATE"
|
||||||
|
|
||||||
|
if position_side == "LONG" and normalized_signal == "BUY":
|
||||||
|
return "REINFORCE_POSITION"
|
||||||
|
|
||||||
|
if position_side == "SHORT" and normalized_signal == "SELL":
|
||||||
|
return "REINFORCE_POSITION"
|
||||||
|
|
||||||
|
if position_side == "LONG" and normalized_signal == "SELL":
|
||||||
|
return "REVERSAL_CANDIDATE"
|
||||||
|
|
||||||
|
if position_side == "SHORT" and normalized_signal == "BUY":
|
||||||
|
return "REVERSAL_CANDIDATE"
|
||||||
|
|
||||||
|
return "NOISE"
|
||||||
|
|
||||||
# обновить статус решения по текущему сигналу
|
# обновить статус решения по текущему сигналу
|
||||||
def _update_decision_state(
|
def _update_decision_state(
|
||||||
self,
|
self,
|
||||||
@@ -355,6 +392,7 @@ class AutoTradeService:
|
|||||||
previous_signal = self._last_signal_value
|
previous_signal = self._last_signal_value
|
||||||
previous_count = self._same_signal_count
|
previous_count = self._same_signal_count
|
||||||
is_same_signal = signal_key == self._last_signal_key
|
is_same_signal = signal_key == self._last_signal_key
|
||||||
|
now = time.monotonic()
|
||||||
|
|
||||||
if is_same_signal:
|
if is_same_signal:
|
||||||
self._same_signal_count += 1
|
self._same_signal_count += 1
|
||||||
@@ -377,6 +415,7 @@ class AutoTradeService:
|
|||||||
reason=self._last_signal_reason,
|
reason=self._last_signal_reason,
|
||||||
confidence=self._last_signal_confidence,
|
confidence=self._last_signal_confidence,
|
||||||
payload=self._last_signal_payload,
|
payload=self._last_signal_payload,
|
||||||
|
duration_seconds=self._signal_duration_seconds(now=now),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._log_signal_event(
|
self._log_signal_event(
|
||||||
@@ -396,6 +435,7 @@ class AutoTradeService:
|
|||||||
self._last_signal_reason = reason
|
self._last_signal_reason = reason
|
||||||
self._last_signal_confidence = confidence
|
self._last_signal_confidence = confidence
|
||||||
self._last_signal_payload = payload
|
self._last_signal_payload = payload
|
||||||
|
self._last_signal_started_at = now
|
||||||
self._same_signal_count = 1
|
self._same_signal_count = 1
|
||||||
|
|
||||||
self._update_signal_state_fields(
|
self._update_signal_state_fields(
|
||||||
@@ -405,6 +445,27 @@ class AutoTradeService:
|
|||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _signal_duration_seconds(self, *, now: float) -> int:
|
||||||
|
if self._last_signal_started_at is None:
|
||||||
|
return max(0, int(self._same_signal_count * self._loop_interval_seconds))
|
||||||
|
|
||||||
|
return max(0, int(now - self._last_signal_started_at))
|
||||||
|
|
||||||
|
def _format_duration(self, total_seconds: int) -> str:
|
||||||
|
total_seconds = max(0, int(total_seconds))
|
||||||
|
|
||||||
|
hours = total_seconds // 3600
|
||||||
|
minutes = (total_seconds % 3600) // 60
|
||||||
|
seconds = total_seconds % 60
|
||||||
|
|
||||||
|
if hours > 0:
|
||||||
|
return f"{hours}ч {minutes:02d}м {seconds:02d}с"
|
||||||
|
|
||||||
|
if minutes > 0:
|
||||||
|
return f"{minutes}м {seconds:02d}с"
|
||||||
|
|
||||||
|
return f"{seconds}с"
|
||||||
|
|
||||||
# обновить поля state для экрана автоторговли
|
# обновить поля state для экрана автоторговли
|
||||||
def _update_signal_state_fields(
|
def _update_signal_state_fields(
|
||||||
self,
|
self,
|
||||||
@@ -431,12 +492,30 @@ class AutoTradeService:
|
|||||||
confidence=confidence,
|
confidence=confidence,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
signal_intent = self._signal_intent(
|
||||||
|
state=state,
|
||||||
|
signal=state.last_signal,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
previous_decision_status != state.decision_status
|
||||||
|
and state.decision_status == "READY"
|
||||||
|
):
|
||||||
|
self._log_ready_signal(
|
||||||
|
state=state,
|
||||||
|
signal=state.last_signal,
|
||||||
|
reason=state.last_signal_reason or reason,
|
||||||
|
confidence=state.last_signal_confidence,
|
||||||
|
signal_intent=signal_intent,
|
||||||
|
)
|
||||||
|
|
||||||
if previous_signal != state.last_signal:
|
if previous_signal != state.last_signal:
|
||||||
EventBus.emit(
|
EventBus.emit(
|
||||||
"auto_signal_changed",
|
"auto_signal_changed",
|
||||||
{
|
{
|
||||||
"previous_signal": previous_signal,
|
"previous_signal": previous_signal,
|
||||||
"signal": state.last_signal,
|
"signal": state.last_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
"repeat_count": state.last_signal_repeat_count,
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
"confidence": state.last_signal_confidence,
|
"confidence": state.last_signal_confidence,
|
||||||
},
|
},
|
||||||
@@ -449,6 +528,7 @@ class AutoTradeService:
|
|||||||
"previous_decision_status": previous_decision_status,
|
"previous_decision_status": previous_decision_status,
|
||||||
"decision_status": state.decision_status,
|
"decision_status": state.decision_status,
|
||||||
"signal": state.last_signal,
|
"signal": state.last_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
"repeat_count": state.last_signal_repeat_count,
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
"confidence": state.last_signal_confidence,
|
"confidence": state.last_signal_confidence,
|
||||||
"symbol": state.symbol,
|
"symbol": state.symbol,
|
||||||
@@ -458,7 +538,7 @@ class AutoTradeService:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# записать одиночный сигнал в журнал
|
# одиночные BUY / SELL больше не пишем в журнал как полезные события
|
||||||
def _log_signal_event(
|
def _log_signal_event(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -469,34 +549,7 @@ class AutoTradeService:
|
|||||||
confidence: float,
|
confidence: float,
|
||||||
payload: dict | None,
|
payload: dict | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
emoji_map = {
|
return
|
||||||
"BUY": "🟢",
|
|
||||||
"SELL": "🔴",
|
|
||||||
"HOLD": "🟡",
|
|
||||||
}
|
|
||||||
emoji = emoji_map.get(signal, "•")
|
|
||||||
|
|
||||||
try:
|
|
||||||
JournalService().log_ui_info(
|
|
||||||
event_type="auto_signal_generated",
|
|
||||||
message=f"{emoji} Сигнал автоторговли {signal}: {reason}",
|
|
||||||
screen="auto",
|
|
||||||
action="run_cycle",
|
|
||||||
payload={
|
|
||||||
"strategy": strategy_name,
|
|
||||||
"status": state.status,
|
|
||||||
"symbol": state.symbol,
|
|
||||||
"signal": signal,
|
|
||||||
"confidence": confidence,
|
|
||||||
"reason": reason,
|
|
||||||
"repeat_count": 1,
|
|
||||||
"is_strong_signal": confidence > self._ready_confidence,
|
|
||||||
"is_aggregated": False,
|
|
||||||
"payload": payload or {},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# записать итог серии одинаковых сигналов при смене сигнала
|
# записать итог серии одинаковых сигналов при смене сигнала
|
||||||
def _log_signal_summary(
|
def _log_signal_summary(
|
||||||
@@ -510,20 +563,19 @@ class AutoTradeService:
|
|||||||
reason: str,
|
reason: str,
|
||||||
confidence: float,
|
confidence: float,
|
||||||
payload: dict | None,
|
payload: dict | None,
|
||||||
|
duration_seconds: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
emoji_map = {
|
if previous_signal != "HOLD":
|
||||||
"BUY": "🟢",
|
return
|
||||||
"SELL": "🔴",
|
|
||||||
"HOLD": "🟡",
|
duration_text = self._format_duration(duration_seconds)
|
||||||
}
|
signal_intent = "HOLD_MARKET"
|
||||||
emoji = emoji_map.get(previous_signal, "•")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
JournalService().log_ui_info(
|
JournalService().log_ui_info(
|
||||||
event_type="auto_signal_summary",
|
event_type="auto_signal_summary",
|
||||||
message=(
|
message=(
|
||||||
f"{emoji} {previous_count} {previous_signal} подряд "
|
f"🟡 HOLD {duration_text} завершён сигналом {next_signal}"
|
||||||
f"до смены на {next_signal}"
|
|
||||||
),
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="signal_summary",
|
action="signal_summary",
|
||||||
@@ -533,10 +585,13 @@ class AutoTradeService:
|
|||||||
"symbol": state.symbol,
|
"symbol": state.symbol,
|
||||||
"signal": previous_signal,
|
"signal": previous_signal,
|
||||||
"next_signal": next_signal,
|
"next_signal": next_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
"repeat_count": previous_count,
|
"repeat_count": previous_count,
|
||||||
|
"duration_seconds": duration_seconds,
|
||||||
|
"duration_text": duration_text,
|
||||||
"confidence": confidence,
|
"confidence": confidence,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"is_strong_signal": confidence > self._ready_confidence,
|
"is_strong_signal": False,
|
||||||
"is_aggregated": True,
|
"is_aggregated": True,
|
||||||
"payload": payload or {},
|
"payload": payload or {},
|
||||||
},
|
},
|
||||||
@@ -544,6 +599,47 @@ class AutoTradeService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _log_ready_signal(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str | None,
|
||||||
|
reason: str,
|
||||||
|
confidence: float,
|
||||||
|
signal_intent: str,
|
||||||
|
) -> None:
|
||||||
|
normalized_signal = (signal or "HOLD").upper()
|
||||||
|
if normalized_signal not in {"BUY", "SELL"}:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="auto_signal_ready",
|
||||||
|
message=(
|
||||||
|
f"Сигнал {normalized_signal} готов: "
|
||||||
|
f"{signal_intent}, confidence={confidence:.2f}, "
|
||||||
|
f"repeats={state.last_signal_repeat_count}"
|
||||||
|
),
|
||||||
|
screen="auto",
|
||||||
|
action="signal_ready",
|
||||||
|
payload={
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"status": state.status,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"signal": normalized_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
|
"confidence": confidence,
|
||||||
|
"reason": reason,
|
||||||
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
|
"position_side": state.position_side,
|
||||||
|
"decision_status": state.decision_status,
|
||||||
|
"is_strong_signal": confidence > self._ready_confidence,
|
||||||
|
"is_aggregated": False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# выполнить один цикл анализа рынка
|
# выполнить один цикл анализа рынка
|
||||||
def run_cycle(self) -> AutoTradeState:
|
def run_cycle(self) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|||||||
@@ -374,6 +374,16 @@
|
|||||||
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
||||||
- подготовлена база для Signal Intent Layer в следующем этапе
|
- подготовлена база для Signal Intent Layer в следующем этапе
|
||||||
|
|
||||||
|
#### 07.4.3.19.3 ✅ Strategy Noise Filter & Signal Intent Layer
|
||||||
|
- убрано журналирование одиночных BUY / SELL без серии
|
||||||
|
- HOLD-серии переведены с repeat-count на duration формат
|
||||||
|
- добавлен формат 🟡 HOLD 5м 36с завершён сигналом SELL
|
||||||
|
- добавлен signal_intent в payload сигналов
|
||||||
|
- добавлены intent-типы ENTRY_CANDIDATE, REVERSAL_CANDIDATE, REINFORCE_POSITION, HOLD_MARKET, NOISE
|
||||||
|
- добавлена position-aware интерпретация сигналов
|
||||||
|
- добавлено отдельное событие готового сигнала
|
||||||
|
- подготовлена база для стандартизации журнала в 07.4.3.19.4
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid Strategy
|
⏳ Grid Strategy
|
||||||
|
|
||||||
|
|||||||
@@ -350,6 +350,16 @@
|
|||||||
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
||||||
- подготовлена база для Signal Intent Layer в следующем этапе
|
- подготовлена база для Signal Intent Layer в следующем этапе
|
||||||
|
|
||||||
|
#### 07.4.3.19.3 ✅ Strategy Noise Filter & Signal Intent Layer
|
||||||
|
- убрано журналирование одиночных BUY / SELL без серии
|
||||||
|
- HOLD-серии переведены с repeat-count на duration формат
|
||||||
|
- добавлен формат 🟡 HOLD 5м 36с завершён сигналом SELL
|
||||||
|
- добавлен signal_intent в payload сигналов
|
||||||
|
- добавлены intent-типы ENTRY_CANDIDATE, REVERSAL_CANDIDATE, REINFORCE_POSITION, HOLD_MARKET, NOISE
|
||||||
|
- добавлена position-aware интерпретация сигналов
|
||||||
|
- добавлено отдельное событие готового сигнала
|
||||||
|
- подготовлена база для стандартизации журнала в 07.4.3.19.4
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# 07.4.3.19.3 — Strategy Noise Filter & Signal Intent Layer
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Этап завершён.
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Цель этапа — снизить шум от краткосрочных стратегических сигналов и добавить смысловой слой signal intent, чтобы журнал фиксировал не каждый сырой BUY / SELL / HOLD, а только полезные runtime-переходы.
|
||||||
|
|
||||||
|
До этого этапа журнал уже был очищен от UI refresh spam, но оставались частые записи вида:
|
||||||
|
|
||||||
|
- BUY завершился без серии
|
||||||
|
- SELL завершился без серии
|
||||||
|
- 21 HOLD подряд до смены на SELL
|
||||||
|
|
||||||
|
Такие записи были полезны на этапе разработки, но плохо подходили для runtime-аудита.
|
||||||
|
|
||||||
|
## Что изменено
|
||||||
|
|
||||||
|
### 1. Убрано журналирование одиночных BUY / SELL
|
||||||
|
|
||||||
|
Одиночные BUY / SELL, которые не дошли до серии и не стали READY-сигналом, больше не пишутся в журнал как полезные события.
|
||||||
|
|
||||||
|
Это снижает шум от микросигналов SCALP-стратегии, когда стратегия на один цикл выдаёт BUY или SELL, а затем возвращается в HOLD.
|
||||||
|
|
||||||
|
### 2. HOLD-серии теперь логируются по длительности
|
||||||
|
|
||||||
|
Сводка HOLD теперь формируется по времени, а не по количеству повторов.
|
||||||
|
|
||||||
|
Новый формат сообщения:
|
||||||
|
|
||||||
|
- 🟡 HOLD 5м 36с завершён сигналом SELL
|
||||||
|
|
||||||
|
Такой формат понятнее, потому что не зависит напрямую от внутреннего интервала цикла.
|
||||||
|
|
||||||
|
### 3. Добавлен signal intent
|
||||||
|
|
||||||
|
В payload сигнальных событий добавлен смысловой intent:
|
||||||
|
|
||||||
|
- ENTRY_CANDIDATE
|
||||||
|
- REVERSAL_CANDIDATE
|
||||||
|
- REINFORCE_POSITION
|
||||||
|
- HOLD_MARKET
|
||||||
|
- NOISE
|
||||||
|
|
||||||
|
Intent рассчитывается с учётом текущей позиции.
|
||||||
|
|
||||||
|
### 4. Добавлена position-aware интерпретация сигналов
|
||||||
|
|
||||||
|
Новая логика интерпретации:
|
||||||
|
|
||||||
|
- LONG + BUY → REINFORCE_POSITION
|
||||||
|
- SHORT + SELL → REINFORCE_POSITION
|
||||||
|
- LONG + SELL → REVERSAL_CANDIDATE
|
||||||
|
- SHORT + BUY → REVERSAL_CANDIDATE
|
||||||
|
- NONE + BUY / SELL → ENTRY_CANDIDATE
|
||||||
|
- HOLD → HOLD_MARKET
|
||||||
|
|
||||||
|
Это подготавливает систему к следующему уровню фильтрации стратегий и execution-логики.
|
||||||
|
|
||||||
|
### 5. Добавлено отдельное событие готового сигнала
|
||||||
|
|
||||||
|
Когда сигнал доходит до READY, в журнал пишется отдельное событие:
|
||||||
|
|
||||||
|
- auto_signal_ready
|
||||||
|
|
||||||
|
В payload добавляются:
|
||||||
|
|
||||||
|
- signal
|
||||||
|
- signal_intent
|
||||||
|
- confidence
|
||||||
|
- repeat_count
|
||||||
|
- position_side
|
||||||
|
- decision_status
|
||||||
|
- strategy
|
||||||
|
- symbol
|
||||||
|
|
||||||
|
## Что больше не пишется в журнал
|
||||||
|
|
||||||
|
Из журнала убраны как регулярные runtime-события:
|
||||||
|
|
||||||
|
- одиночный BUY без серии
|
||||||
|
- одиночный SELL без серии
|
||||||
|
- BUY завершился без серии
|
||||||
|
- SELL завершился без серии
|
||||||
|
- HOLD → HOLD
|
||||||
|
|
||||||
|
Эти события могут быть возвращены позже как debug-level telemetry, но не должны попадать в основной journal-аудит.
|
||||||
|
|
||||||
|
## Что остаётся в журнале
|
||||||
|
|
||||||
|
После этапа журнал должен содержать только полезные signal/runtime события:
|
||||||
|
|
||||||
|
- HOLD duration summary
|
||||||
|
- READY signal
|
||||||
|
- execution events
|
||||||
|
- blocked flip
|
||||||
|
- notification errors
|
||||||
|
- market/runtime events
|
||||||
|
- journal/system critical events
|
||||||
|
|
||||||
|
## Основной изменённый файл
|
||||||
|
|
||||||
|
- app/src/trading/auto/service.py
|
||||||
|
|
||||||
|
## Проверка
|
||||||
|
|
||||||
|
После правок нужно выполнить:
|
||||||
|
|
||||||
|
python -m compileall src
|
||||||
|
python -m src.main
|
||||||
|
|
||||||
|
После запуска проверить журнал:
|
||||||
|
|
||||||
|
1. В журнале больше нет частых записей «BUY завершился без серии» и «SELL завершился без серии».
|
||||||
|
2. HOLD-серии отображаются по времени.
|
||||||
|
3. READY-сигналы содержат signal_intent в payload.
|
||||||
|
4. Execution events продолжают приходить как раньше.
|
||||||
|
5. Flip protection из этапа 07.4.3.19.1 не сломан.
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
Этап подготовил основу для дальнейшей стандартизации журнала и будущего Signal Intent Layer. Система стала меньше реагировать на шумовые микросигналы и начала фиксировать не только сырой сигнал, но и его смысл относительно текущей позиции.
|
||||||
Reference in New Issue
Block a user