07.4.3.19.2 — Journal Noise Reduction & Position-aware Signal Logg
This commit is contained in:
@@ -36,6 +36,9 @@ class AutoTradeRunner:
|
|||||||
_last_event_version: int = 0
|
_last_event_version: int = 0
|
||||||
_retry_after_until: float = 0.0
|
_retry_after_until: float = 0.0
|
||||||
|
|
||||||
|
_position_aligned_signal_log_interval_seconds = 900
|
||||||
|
_last_position_aligned_signal_log_at_by_key: dict[str, float] = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_screen(
|
def register_screen(
|
||||||
cls,
|
cls,
|
||||||
@@ -206,6 +209,14 @@ class AutoTradeRunner:
|
|||||||
if signal not in {"BUY", "SELL"}:
|
if signal not in {"BUY", "SELL"}:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if cls._is_position_aligned_signal(state=state, signal=signal):
|
||||||
|
cls._log_position_aligned_signal_suppressed(
|
||||||
|
state=state,
|
||||||
|
payload=payload,
|
||||||
|
signal=signal,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
cls._publish_strong_signal_event(state=state, payload=payload)
|
cls._publish_strong_signal_event(state=state, payload=payload)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -221,6 +232,73 @@ class AutoTradeRunner:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _is_position_aligned_signal(cls, *, state, signal: str) -> bool:
|
||||||
|
position_side = str(getattr(state, "position_side", "NONE") or "NONE").upper()
|
||||||
|
|
||||||
|
if position_side == "LONG" and signal == "BUY":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if position_side == "SHORT" and signal == "SELL":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _log_position_aligned_signal_suppressed(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
state,
|
||||||
|
payload: dict,
|
||||||
|
signal: str,
|
||||||
|
) -> None:
|
||||||
|
position_side = str(getattr(state, "position_side", "NONE") or "NONE").upper()
|
||||||
|
symbol = str(payload.get("symbol") or state.symbol or "—")
|
||||||
|
strategy = str(payload.get("strategy") or state.strategy or "—")
|
||||||
|
confidence = float(payload.get("confidence") or state.last_signal_confidence or 0.0)
|
||||||
|
repeat_count = int(payload.get("repeat_count") or state.last_signal_repeat_count or 0)
|
||||||
|
|
||||||
|
log_key = (
|
||||||
|
f"{position_side}:"
|
||||||
|
f"{symbol}:"
|
||||||
|
f"{strategy}:"
|
||||||
|
f"{signal}"
|
||||||
|
)
|
||||||
|
|
||||||
|
now = time.monotonic()
|
||||||
|
last_logged_at = cls._last_position_aligned_signal_log_at_by_key.get(log_key)
|
||||||
|
|
||||||
|
if (
|
||||||
|
last_logged_at is not None
|
||||||
|
and now - last_logged_at < cls._position_aligned_signal_log_interval_seconds
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
cls._last_position_aligned_signal_log_at_by_key[log_key] = now
|
||||||
|
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="auto_position_aligned_signal_suppressed",
|
||||||
|
message=(
|
||||||
|
f"Сигнал {signal} совпадает с открытой позицией "
|
||||||
|
f"{position_side}; уведомление подавлено."
|
||||||
|
),
|
||||||
|
screen="auto",
|
||||||
|
action="signal_notification",
|
||||||
|
payload={
|
||||||
|
"symbol": symbol,
|
||||||
|
"strategy": strategy,
|
||||||
|
"signal": signal,
|
||||||
|
"position_side": position_side,
|
||||||
|
"confidence": confidence,
|
||||||
|
"repeat_count": repeat_count,
|
||||||
|
"reason": payload.get("reason") or state.last_signal_reason,
|
||||||
|
"suppression_interval_seconds": cls._position_aligned_signal_log_interval_seconds,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _publish_strong_signal_event(cls, *, state, payload: dict) -> None:
|
def _publish_strong_signal_event(cls, *, state, payload: dict) -> None:
|
||||||
signal = str(payload.get("signal", "")).upper()
|
signal = str(payload.get("signal", "")).upper()
|
||||||
@@ -366,29 +444,11 @@ class AutoTradeRunner:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _log_refresh_skip(cls, reason: str, payload: dict | None = None) -> None:
|
def _log_refresh_skip(cls, reason: str, payload: dict | None = None) -> None:
|
||||||
try:
|
return
|
||||||
JournalService().log_ui_info(
|
|
||||||
event_type="auto_screen_refresh_skipped",
|
|
||||||
message=f"Auto screen refresh skipped: {reason}",
|
|
||||||
screen="auto",
|
|
||||||
action="refresh_screen",
|
|
||||||
payload=payload or {},
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _log_refresh_success(cls, payload: dict | None = None) -> None:
|
def _log_refresh_success(cls, payload: dict | None = None) -> None:
|
||||||
try:
|
return
|
||||||
JournalService().log_ui_info(
|
|
||||||
event_type="auto_screen_refreshed",
|
|
||||||
message="Auto screen refreshed.",
|
|
||||||
screen="auto",
|
|
||||||
action="refresh_screen",
|
|
||||||
payload=payload or {},
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _log_refresh_error(cls, reason: str, payload: dict | None = None) -> None:
|
def _log_refresh_error(cls, reason: str, payload: dict | None = None) -> None:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from src.storage.repositories.journal import JournalRepository
|
|||||||
from src.storage.session import check_database_health
|
from src.storage.session import check_database_health
|
||||||
from src.trading.journal.exporter import build_csv, build_xlsx
|
from src.trading.journal.exporter import build_csv, build_xlsx
|
||||||
|
|
||||||
EXPORT_LIMIT = 5000
|
EXPORT_LIMIT = 10000
|
||||||
|
|
||||||
|
|
||||||
class JournalService:
|
class JournalService:
|
||||||
|
|||||||
@@ -363,6 +363,17 @@
|
|||||||
- исправлена очистка ghost-позиций после forced exit
|
- исправлена очистка ghost-позиций после forced exit
|
||||||
- стабилизирован lifecycle paper execution во время ночного runtime-тестирования
|
- стабилизирован lifecycle paper execution во время ночного runtime-тестирования
|
||||||
|
|
||||||
|
#### 07.4.3.19.2 — Journal Noise Reduction & Position-aware Signal Logg
|
||||||
|
Снижение шума журнала и position-aware signal logging ✅
|
||||||
|
- отключено журналирование auto_screen_refresh_skipped
|
||||||
|
- отключено журналирование auto_screen_refreshed
|
||||||
|
- сохранено журналирование ошибок refresh-цикла
|
||||||
|
- добавлено подавление сигналов, совпадающих с открытой позицией
|
||||||
|
- добавлен throttled summary auto_position_aligned_signal_suppressed
|
||||||
|
- снижено количество повторных strong signal уведомлений
|
||||||
|
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
||||||
|
- подготовлена база для Signal Intent Layer в следующем этапе
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid Strategy
|
⏳ Grid Strategy
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,17 @@
|
|||||||
- исправлена очистка ghost-позиций после forced exit
|
- исправлена очистка ghost-позиций после forced exit
|
||||||
- стабилизирован lifecycle paper execution во время ночного runtime-тестирования
|
- стабилизирован lifecycle paper execution во время ночного runtime-тестирования
|
||||||
|
|
||||||
|
#### 07.4.3.19.2 — Journal Noise Reduction & Position-aware Signal Logg
|
||||||
|
Снижение шума журнала и position-aware signal logging ✅
|
||||||
|
- отключено журналирование auto_screen_refresh_skipped
|
||||||
|
- отключено журналирование auto_screen_refreshed
|
||||||
|
- сохранено журналирование ошибок refresh-цикла
|
||||||
|
- добавлено подавление сигналов, совпадающих с открытой позицией
|
||||||
|
- добавлен throttled summary auto_position_aligned_signal_suppressed
|
||||||
|
- снижено количество повторных strong signal уведомлений
|
||||||
|
- сохранена обработка противоположных сигналов как reversal / flip candidates
|
||||||
|
- подготовлена база для Signal Intent Layer в следующем этапе
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# 07.4.3.19.2 — Journal Noise Reduction & Position-aware Signal Logging
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
Этап: `07.4.3.19.2`
|
||||||
|
Название: `Journal Noise Reduction & Position-aware Signal Logging`
|
||||||
|
Состояние: подготовлено к runtime-проверке после внедрения правок.
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Сократить технический шум в журнале и убрать повторяющиеся уведомления о сильных сигналах, которые совпадают с уже открытой позицией.
|
||||||
|
|
||||||
|
До этапа журнал активно заполнялся техническими событиями автообновления экрана и повторными сигналами вида:
|
||||||
|
|
||||||
|
- `auto_screen_refresh_skipped`
|
||||||
|
- `auto_screen_refreshed`
|
||||||
|
- повторные strong signal alerts при уже открытой позиции
|
||||||
|
|
||||||
|
После этапа журнал должен содержать меньше технических записей и больше полезных runtime-событий.
|
||||||
|
|
||||||
|
## Что изменено
|
||||||
|
|
||||||
|
### 1. Отключены шумные UI refresh-логи
|
||||||
|
|
||||||
|
В `AutoTradeRunner` отключено журналирование обычных успешных и пропущенных обновлений экрана:
|
||||||
|
|
||||||
|
- `auto_screen_refresh_skipped`
|
||||||
|
- `auto_screen_refreshed`
|
||||||
|
|
||||||
|
Логика обновления экрана не меняется. Экран продолжает обновляться, но journal больше не заполняется техническими refresh-событиями.
|
||||||
|
|
||||||
|
Ошибки refresh остаются в журнале:
|
||||||
|
|
||||||
|
- `auto_screen_refresh_error`
|
||||||
|
- `telegram_retry_after`
|
||||||
|
- `telegram_bad_request`
|
||||||
|
- `unexpected_refresh_error`
|
||||||
|
|
||||||
|
### 2. Добавлено подавление сигналов, совпадающих с открытой позицией
|
||||||
|
|
||||||
|
Добавлена position-aware проверка перед публикацией strong signal RuntimeEvent.
|
||||||
|
|
||||||
|
Теперь:
|
||||||
|
|
||||||
|
- если открыта `LONG` позиция и приходит `BUY`, уведомление подавляется;
|
||||||
|
- если открыта `SHORT` позиция и приходит `SELL`, уведомление подавляется;
|
||||||
|
- противоположные сигналы не подавляются, так как они могут быть reversal / flip candidate.
|
||||||
|
|
||||||
|
Это снижает Telegram spam и уменьшает количество повторяющихся записей в journal.
|
||||||
|
|
||||||
|
### 3. Добавлен throttled summary в journal
|
||||||
|
|
||||||
|
Для подавленных aligned-сигналов добавлено редкое summary-событие:
|
||||||
|
|
||||||
|
- `auto_position_aligned_signal_suppressed`
|
||||||
|
|
||||||
|
Оно пишется не чаще одного раза в 15 минут для связки:
|
||||||
|
|
||||||
|
- position side
|
||||||
|
- symbol
|
||||||
|
- strategy
|
||||||
|
- signal
|
||||||
|
|
||||||
|
Это позволяет видеть, что стратегия продолжает выдавать сигналы по направлению позиции, но без лавины одинаковых записей.
|
||||||
|
|
||||||
|
## Новое поведение
|
||||||
|
|
||||||
|
### Если позиции нет
|
||||||
|
|
||||||
|
Сильный сигнал продолжает проходить в RuntimeEvent pipeline:
|
||||||
|
|
||||||
|
- `AUTO_SIGNAL_READY`
|
||||||
|
- Telegram notification
|
||||||
|
- journal notification logs
|
||||||
|
|
||||||
|
### Если открыта LONG позиция
|
||||||
|
|
||||||
|
`BUY` сигнал:
|
||||||
|
|
||||||
|
- не отправляет повторное strong signal уведомление;
|
||||||
|
- не создаёт лишний RuntimeEvent notification;
|
||||||
|
- пишет summary `auto_position_aligned_signal_suppressed` максимум раз в 15 минут.
|
||||||
|
|
||||||
|
`SELL` сигнал:
|
||||||
|
|
||||||
|
- не подавляется;
|
||||||
|
- может быть обработан как reversal / flip candidate;
|
||||||
|
- дальше проверяется execution-защитами из `07.4.3.19.1`.
|
||||||
|
|
||||||
|
### Если открыта SHORT позиция
|
||||||
|
|
||||||
|
`SELL` сигнал:
|
||||||
|
|
||||||
|
- не отправляет повторное strong signal уведомление;
|
||||||
|
- не создаёт лишний RuntimeEvent notification;
|
||||||
|
- пишет summary `auto_position_aligned_signal_suppressed` максимум раз в 15 минут.
|
||||||
|
|
||||||
|
`BUY` сигнал:
|
||||||
|
|
||||||
|
- не подавляется;
|
||||||
|
- может быть обработан как reversal / flip candidate;
|
||||||
|
- дальше проверяется execution-защитами из `07.4.3.19.1`.
|
||||||
|
|
||||||
|
## Какие файлы затронуты
|
||||||
|
|
||||||
|
Основной файл:
|
||||||
|
|
||||||
|
- `app/src/trading/auto/runner.py`
|
||||||
|
|
||||||
|
Используется существующая инфраструктура:
|
||||||
|
|
||||||
|
- `RuntimeEventPublisher`
|
||||||
|
- `RuntimeEventType.AUTO_SIGNAL_READY`
|
||||||
|
- `JournalService`
|
||||||
|
- `NotificationTargetRegistry`
|
||||||
|
|
||||||
|
## Что проверить после запуска
|
||||||
|
|
||||||
|
### 1. Compile check
|
||||||
|
|
||||||
|
`python -m compileall src`
|
||||||
|
|
||||||
|
### 2. Проверить отсутствие refresh-spam
|
||||||
|
|
||||||
|
После запуска в journal больше не должны появляться:
|
||||||
|
|
||||||
|
- `auto_screen_refresh_skipped`
|
||||||
|
- `auto_screen_refreshed`
|
||||||
|
|
||||||
|
### 3. Проверить position-aligned suppression
|
||||||
|
|
||||||
|
После открытия позиции:
|
||||||
|
|
||||||
|
- `LONG + BUY` не должен слать повторные Telegram strong signal alerts;
|
||||||
|
- `SHORT + SELL` не должен слать повторные Telegram strong signal alerts;
|
||||||
|
- в journal должен появляться `auto_position_aligned_signal_suppressed` не чаще одного раза в 15 минут.
|
||||||
|
|
||||||
|
### 4. Проверить reversal-кандидаты
|
||||||
|
|
||||||
|
Противоположные сигналы должны продолжать проходить:
|
||||||
|
|
||||||
|
- `LONG + SELL`
|
||||||
|
- `SHORT + BUY`
|
||||||
|
|
||||||
|
Они должны идти дальше в execution pipeline и проверяться flip-защитами.
|
||||||
|
|
||||||
|
## Ожидаемый эффект
|
||||||
|
|
||||||
|
После этапа journal должен стать заметно чище:
|
||||||
|
|
||||||
|
- меньше технического UI noise;
|
||||||
|
- меньше повторных signal notification events;
|
||||||
|
- меньше Telegram spam;
|
||||||
|
- лучше видны реальные execution-события:
|
||||||
|
- `paper_position_opened`
|
||||||
|
- `paper_position_closed`
|
||||||
|
- `paper_position_flipped`
|
||||||
|
- `paper_flip_blocked`
|
||||||
|
- `auto_position_aligned_signal_suppressed`
|
||||||
|
|
||||||
|
## Следующий рекомендуемый этап
|
||||||
|
|
||||||
|
`07.4.3.19.3 — Strategy Noise Filter & Signal Intent Layer`
|
||||||
|
|
||||||
|
Цель следующего этапа — не только подавлять лишние уведомления, но и классифицировать смысл сигнала относительно позиции:
|
||||||
|
|
||||||
|
- `ENTRY`
|
||||||
|
- `REINFORCE`
|
||||||
|
- `REVERSAL_CANDIDATE`
|
||||||
|
- `FLIP_ALLOWED`
|
||||||
|
- `IGNORE`
|
||||||
Reference in New Issue
Block a user