07.4.4.1.2 — Market State Journal Events
This commit is contained in:
@@ -32,6 +32,9 @@ class AutoTradeService:
|
||||
_last_signal_confidence: float = 0.0
|
||||
_last_signal_payload: dict | None = None
|
||||
_last_signal_started_at: float | None = None
|
||||
_last_logged_market_state: str | None = None
|
||||
_last_logged_market_trend: str | None = None
|
||||
_last_logged_market_volatility: str | None = None
|
||||
_same_signal_count = 0
|
||||
|
||||
# debug: принудительно выставить сигнал и decision
|
||||
@@ -649,12 +652,115 @@ class AutoTradeService:
|
||||
if not isinstance(payload, dict):
|
||||
return
|
||||
|
||||
previous_market_state = state.market_state
|
||||
previous_market_trend = state.market_trend
|
||||
previous_market_volatility = state.market_volatility
|
||||
|
||||
state.market_state = payload.get("market_state")
|
||||
state.market_trend = payload.get("market_trend")
|
||||
state.market_volatility = payload.get("market_volatility")
|
||||
state.market_analysis_interval = payload.get("market_analysis_interval")
|
||||
state.market_analysis_reason = payload.get("market_analysis_reason")
|
||||
|
||||
self._log_market_state_if_changed(
|
||||
state=state,
|
||||
payload=payload,
|
||||
previous_market_state=previous_market_state,
|
||||
previous_market_trend=previous_market_trend,
|
||||
previous_market_volatility=previous_market_volatility,
|
||||
)
|
||||
|
||||
def _log_market_state_if_changed(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
payload: dict,
|
||||
previous_market_state: str | None,
|
||||
previous_market_trend: str | None,
|
||||
previous_market_volatility: str | None,
|
||||
) -> None:
|
||||
market_state = state.market_state
|
||||
market_trend = state.market_trend
|
||||
market_volatility = state.market_volatility
|
||||
|
||||
if not market_state or market_state == "UNKNOWN":
|
||||
return
|
||||
|
||||
state_changed = (
|
||||
market_state != previous_market_state
|
||||
and market_state != type(self)._last_logged_market_state
|
||||
)
|
||||
|
||||
trend_changed = (
|
||||
market_trend is not None
|
||||
and market_trend != previous_market_trend
|
||||
and market_trend != type(self)._last_logged_market_trend
|
||||
)
|
||||
|
||||
volatility_changed = (
|
||||
market_volatility is not None
|
||||
and market_volatility != previous_market_volatility
|
||||
and market_volatility != type(self)._last_logged_market_volatility
|
||||
)
|
||||
|
||||
if not state_changed and not trend_changed and not volatility_changed:
|
||||
return
|
||||
|
||||
type(self)._last_logged_market_state = market_state
|
||||
type(self)._last_logged_market_trend = market_trend
|
||||
type(self)._last_logged_market_volatility = market_volatility
|
||||
|
||||
level = self._market_journal_level(market_state)
|
||||
message = self._market_state_message(market_state)
|
||||
|
||||
journal_payload = {
|
||||
**payload,
|
||||
"previous_market_state": previous_market_state,
|
||||
"previous_market_trend": previous_market_trend,
|
||||
"previous_market_volatility": previous_market_volatility,
|
||||
"current_market_state": market_state,
|
||||
"current_market_trend": market_trend,
|
||||
"current_market_volatility": market_volatility,
|
||||
}
|
||||
|
||||
try:
|
||||
if level == "WARNING":
|
||||
JournalService().log_ui_warning(
|
||||
event_type="market_state_changed",
|
||||
message=message,
|
||||
screen="auto",
|
||||
action="market_analysis",
|
||||
payload=journal_payload,
|
||||
)
|
||||
return
|
||||
|
||||
JournalService().log_ui_info(
|
||||
event_type="market_state_changed",
|
||||
message=message,
|
||||
screen="auto",
|
||||
action="market_analysis",
|
||||
payload=journal_payload,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _market_journal_level(self, market_state: str) -> str:
|
||||
if market_state == "HIGH_VOLATILITY":
|
||||
return "WARNING"
|
||||
|
||||
return "INFO"
|
||||
|
||||
def _market_state_message(self, market_state: str) -> str:
|
||||
messages = {
|
||||
"TREND_UP": "📈 Рынок перешёл в рост.",
|
||||
"TREND_DOWN": "📉 Рынок перешёл в снижение.",
|
||||
"RANGE": "🟰 На рынке нет выраженного направления.",
|
||||
"HIGH_VOLATILITY": "⚠️ Рынок стал слишком волатильным.",
|
||||
"LOW_VOLATILITY": "💤 Рынок почти не движется.",
|
||||
}
|
||||
|
||||
return messages.get(market_state, "⏳ Состояние рынка анализируется.")
|
||||
|
||||
def run_cycle(self) -> AutoTradeState:
|
||||
state = self.get_state()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime
|
||||
from io import BytesIO, StringIO
|
||||
from zoneinfo import ZoneInfo
|
||||
@@ -38,6 +39,22 @@ EVENT_TITLES = {
|
||||
}
|
||||
|
||||
|
||||
_EMOJI_RE = re.compile(
|
||||
"["
|
||||
"\U0001F300-\U0001FAFF"
|
||||
"\U00002700-\U000027BF"
|
||||
"\U00002600-\U000026FF"
|
||||
"\U0001F1E6-\U0001F1FF"
|
||||
"]+",
|
||||
flags=re.UNICODE,
|
||||
)
|
||||
|
||||
|
||||
def _strip_emoji(value: object) -> str:
|
||||
text = str(value or "")
|
||||
return _EMOJI_RE.sub("", text).strip()
|
||||
|
||||
|
||||
def _now_local() -> datetime:
|
||||
settings = load_settings()
|
||||
try:
|
||||
@@ -76,7 +93,9 @@ def _payload(row: dict) -> dict:
|
||||
def _payload_json(payload: dict) -> str:
|
||||
if not payload:
|
||||
return ""
|
||||
return json.dumps(payload, ensure_ascii=False, sort_keys=True)
|
||||
|
||||
text = json.dumps(payload, ensure_ascii=False, sort_keys=True)
|
||||
return _strip_emoji(text)
|
||||
|
||||
|
||||
def _export_row(row: dict) -> list[str]:
|
||||
@@ -84,15 +103,15 @@ def _export_row(row: dict) -> list[str]:
|
||||
|
||||
return [
|
||||
_format_datetime(row.get("created_at")),
|
||||
str(row.get("level") or ""),
|
||||
str(row.get("event_type") or ""),
|
||||
_event_title(row.get("event_type")),
|
||||
str(row.get("message") or ""),
|
||||
str(payload.get("account_mode") or "").upper(),
|
||||
str(payload.get("screen") or ""),
|
||||
str(payload.get("action") or ""),
|
||||
str(payload.get("error_type") or ""),
|
||||
str(payload.get("raw_error") or ""),
|
||||
_strip_emoji(row.get("level")),
|
||||
_strip_emoji(row.get("event_type")),
|
||||
_strip_emoji(_event_title(row.get("event_type"))),
|
||||
_strip_emoji(row.get("message")),
|
||||
_strip_emoji(str(payload.get("account_mode") or "").upper()),
|
||||
_strip_emoji(payload.get("screen")),
|
||||
_strip_emoji(payload.get("action")),
|
||||
_strip_emoji(payload.get("error_type")),
|
||||
_strip_emoji(payload.get("raw_error")),
|
||||
_payload_json(payload),
|
||||
]
|
||||
|
||||
|
||||
@@ -204,19 +204,19 @@ class MarketAnalysisService:
|
||||
rsi_text = f", RSI={rsi_value:.2f}" if rsi_value is not None else ""
|
||||
|
||||
if state == MarketState.TREND_UP:
|
||||
return f"Рынок в восходящем тренде. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
return f"Рынок перешёл в рост. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
|
||||
if state == MarketState.TREND_DOWN:
|
||||
return f"Рынок в нисходящем тренде. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
return f"Рынок перешёл в снижение. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
|
||||
if state == MarketState.RANGE:
|
||||
return f"Рынок в боковике. Тренд не подтверждён. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
return f"На рынке нет выраженного направления. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
|
||||
if state == MarketState.HIGH_VOLATILITY:
|
||||
return f"Рынок слишком волатильный. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
return f"Рынок стал слишком волатильным. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
|
||||
if state == MarketState.LOW_VOLATILITY:
|
||||
return f"Рынок слишком спокойный. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
return f"Рынок почти не движется. ATR={atr_percent:.2f}%{rsi_text}."
|
||||
|
||||
return f"Состояние рынка не определено. Trend={trend}, volatility={volatility}."
|
||||
|
||||
|
||||
Reference in New Issue
Block a user