07.4.4.1.2 — Market State Journal Events
This commit is contained in:
@@ -107,6 +107,7 @@ class MarketDataRunner:
|
|||||||
ws_symbol = cls._ws_symbol(symbol)
|
ws_symbol = cls._ws_symbol(symbol)
|
||||||
|
|
||||||
if symbol != last_symbol:
|
if symbol != last_symbol:
|
||||||
|
previous_symbol = last_symbol
|
||||||
last_symbol = symbol
|
last_symbol = symbol
|
||||||
|
|
||||||
if not cls._is_cache_symbol_used_by_other_runtime(
|
if not cls._is_cache_symbol_used_by_other_runtime(
|
||||||
@@ -115,16 +116,18 @@ class MarketDataRunner:
|
|||||||
):
|
):
|
||||||
MarketPriceCache.clear(cache_symbol)
|
MarketPriceCache.clear(cache_symbol)
|
||||||
|
|
||||||
cls._log_info(
|
if previous_symbol is not None:
|
||||||
context,
|
cls._log_info(
|
||||||
"market_symbol_changed",
|
context,
|
||||||
f"Инструмент автоторговли изменён на {cache_symbol}.",
|
"market_symbol_changed",
|
||||||
{
|
f"Инструмент автоторговли изменён на {cache_symbol}.",
|
||||||
"symbol": symbol,
|
{
|
||||||
"cache_symbol": cache_symbol,
|
"previous_symbol": previous_symbol,
|
||||||
"ws_symbol": ws_symbol,
|
"symbol": symbol,
|
||||||
},
|
"cache_symbol": cache_symbol,
|
||||||
)
|
"ws_symbol": ws_symbol,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await cls._run_websocket(context, symbol)
|
await cls._run_websocket(context, symbol)
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ def _market_state_line(state) -> str:
|
|||||||
labels = {
|
labels = {
|
||||||
"TREND_UP": "📈 Рынок · Рост",
|
"TREND_UP": "📈 Рынок · Рост",
|
||||||
"TREND_DOWN": "📉 Рынок · Падение",
|
"TREND_DOWN": "📉 Рынок · Падение",
|
||||||
"RANGE": "🟰 Рынок · Флэт",
|
"RANGE": "🟰 Рынок · Без направления",
|
||||||
"HIGH_VOLATILITY": "⚠️ Рынок · Волатильность",
|
"HIGH_VOLATILITY": "⚠️ Рынок · Волатильность",
|
||||||
"LOW_VOLATILITY": "🟰 Рынок · Спокойный",
|
"LOW_VOLATILITY": "🟰 Рынок · Спокойный",
|
||||||
"UNKNOWN": "⏳ Рынок · Анализ",
|
"UNKNOWN": "⏳ Рынок · Анализ",
|
||||||
|
|||||||
@@ -344,10 +344,44 @@ async def open_auto_strategy_settings(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
def _log_auto_setting_updated(
|
||||||
|
*,
|
||||||
|
event_type: str = "auto_settings_updated",
|
||||||
|
message: str,
|
||||||
|
action: str,
|
||||||
|
payload: dict,
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type=event_type,
|
||||||
|
message=message,
|
||||||
|
screen="settings_auto",
|
||||||
|
action=action,
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("settings:auto_strategy:"))
|
@router.callback_query(F.data.startswith("settings:auto_strategy:"))
|
||||||
async def set_auto_strategy(callback: CallbackQuery) -> None:
|
async def set_auto_strategy(callback: CallbackQuery) -> None:
|
||||||
strategy = callback.data.split(":", 2)[2]
|
strategy = callback.data.split(":", 2)[2].upper()
|
||||||
AutoTradeService().set_strategy(strategy.upper())
|
|
||||||
|
service = AutoTradeService()
|
||||||
|
state = service.get_state()
|
||||||
|
previous_strategy = state.strategy
|
||||||
|
|
||||||
|
service.set_strategy(strategy)
|
||||||
|
|
||||||
|
if previous_strategy != strategy:
|
||||||
|
_log_auto_setting_updated(
|
||||||
|
message=f"Стратегия автоторговли изменена на {strategy}.",
|
||||||
|
action="set_strategy",
|
||||||
|
payload={
|
||||||
|
"previous_strategy": previous_strategy,
|
||||||
|
"strategy": strategy,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
await open_auto_settings(callback)
|
await open_auto_settings(callback)
|
||||||
await callback.answer("Стратегия обновлена")
|
await callback.answer("Стратегия обновлена")
|
||||||
@@ -380,7 +414,22 @@ async def open_auto_symbol_settings(callback: CallbackQuery) -> None:
|
|||||||
@router.callback_query(F.data.startswith("settings:auto_symbol:"))
|
@router.callback_query(F.data.startswith("settings:auto_symbol:"))
|
||||||
async def set_auto_symbol(callback: CallbackQuery) -> None:
|
async def set_auto_symbol(callback: CallbackQuery) -> None:
|
||||||
symbol = callback.data.split(":", 2)[2]
|
symbol = callback.data.split(":", 2)[2]
|
||||||
AutoTradeService().set_symbol(symbol)
|
|
||||||
|
service = AutoTradeService()
|
||||||
|
state = service.get_state()
|
||||||
|
previous_symbol = state.symbol
|
||||||
|
|
||||||
|
service.set_symbol(symbol)
|
||||||
|
|
||||||
|
if previous_symbol != symbol:
|
||||||
|
_log_auto_setting_updated(
|
||||||
|
message=f"Актив автоторговли изменён на {symbol}.",
|
||||||
|
action="set_symbol",
|
||||||
|
payload={
|
||||||
|
"previous_symbol": previous_symbol,
|
||||||
|
"symbol": symbol,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
await open_auto_settings(callback)
|
await open_auto_settings(callback)
|
||||||
await callback.answer("Актив обновлён")
|
await callback.answer("Актив обновлён")
|
||||||
@@ -412,7 +461,22 @@ async def open_auto_risk_settings(callback: CallbackQuery) -> None:
|
|||||||
@router.callback_query(F.data.startswith("settings:auto_risk:"))
|
@router.callback_query(F.data.startswith("settings:auto_risk:"))
|
||||||
async def set_auto_risk(callback: CallbackQuery) -> None:
|
async def set_auto_risk(callback: CallbackQuery) -> None:
|
||||||
risk = float(callback.data.split(":", 2)[2])
|
risk = float(callback.data.split(":", 2)[2])
|
||||||
AutoTradeService().set_risk_percent(risk)
|
|
||||||
|
service = AutoTradeService()
|
||||||
|
state = service.get_state()
|
||||||
|
previous_risk = state.risk_percent
|
||||||
|
|
||||||
|
service.set_risk_percent(risk)
|
||||||
|
|
||||||
|
if previous_risk != risk:
|
||||||
|
_log_auto_setting_updated(
|
||||||
|
message=f"Риск на сделку изменён на {risk:g}%.",
|
||||||
|
action="set_risk_percent",
|
||||||
|
payload={
|
||||||
|
"previous_risk_percent": previous_risk,
|
||||||
|
"risk_percent": risk,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
await open_auto_settings(callback)
|
await open_auto_settings(callback)
|
||||||
await callback.answer("Риск обновлён")
|
await callback.answer("Риск обновлён")
|
||||||
@@ -447,12 +511,79 @@ async def open_auto_leverage_settings(callback: CallbackQuery) -> None:
|
|||||||
@router.callback_query(F.data.startswith("settings:auto_leverage:"))
|
@router.callback_query(F.data.startswith("settings:auto_leverage:"))
|
||||||
async def set_auto_leverage(callback: CallbackQuery) -> None:
|
async def set_auto_leverage(callback: CallbackQuery) -> None:
|
||||||
leverage = float(callback.data.split(":", 2)[2])
|
leverage = float(callback.data.split(":", 2)[2])
|
||||||
AutoTradeService().set_leverage(leverage)
|
|
||||||
|
service = AutoTradeService()
|
||||||
|
state = service.get_state()
|
||||||
|
previous_leverage = state.leverage
|
||||||
|
|
||||||
|
service.set_leverage(leverage)
|
||||||
|
|
||||||
|
if previous_leverage != leverage:
|
||||||
|
_log_auto_setting_updated(
|
||||||
|
message=f"Плечо автоторговли изменено на x{leverage:g}.",
|
||||||
|
action="set_leverage",
|
||||||
|
payload={
|
||||||
|
"previous_leverage": previous_leverage,
|
||||||
|
"leverage": leverage,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
await open_auto_settings(callback)
|
await open_auto_settings(callback)
|
||||||
await callback.answer("Плечо обновлено")
|
await callback.answer("Плечо обновлено")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:auto_max_reserved")
|
||||||
|
async def open_auto_max_reserved_settings(callback: CallbackQuery) -> None:
|
||||||
|
if not await _prepare_system_from_callback(callback, screen="settings_auto"):
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>🏦 Лимит на сделку</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки · Автоторговля\n\n"
|
||||||
|
"Максимальная доля баланса, которую можно зарезервировать под позицию:"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="25%", callback_data="settings:auto_max_reserved:25")
|
||||||
|
builder.button(text="50%", callback_data="settings:auto_max_reserved:50")
|
||||||
|
builder.button(text="75%", callback_data="settings:auto_max_reserved:75")
|
||||||
|
builder.button(text="100%", callback_data="settings:auto_max_reserved:100")
|
||||||
|
builder.button(text="off", callback_data="settings:auto_max_reserved:off")
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="settings:auto")
|
||||||
|
builder.adjust(2, 2, 1, 1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
_register_system_screen(callback.message, screen="settings_auto")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("settings:auto_max_reserved:"))
|
||||||
|
async def set_auto_max_reserved(callback: CallbackQuery) -> None:
|
||||||
|
raw_value = callback.data.split(":", 2)[2]
|
||||||
|
value = None if raw_value == "off" else float(raw_value)
|
||||||
|
|
||||||
|
service = AutoTradeService()
|
||||||
|
state = service.get_state()
|
||||||
|
previous_value = state.max_reserved_balance_percent
|
||||||
|
|
||||||
|
service.set_max_reserved_balance_percent(value)
|
||||||
|
|
||||||
|
if previous_value != value:
|
||||||
|
value_text = "off" if value is None else f"{value:g}%"
|
||||||
|
|
||||||
|
_log_auto_setting_updated(
|
||||||
|
message=f"Лимит на сделку изменён на {value_text}.",
|
||||||
|
action="set_max_reserved_balance_percent",
|
||||||
|
payload={
|
||||||
|
"previous_max_reserved_balance_percent": previous_value,
|
||||||
|
"max_reserved_balance_percent": value,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await open_auto_settings(callback)
|
||||||
|
await callback.answer("Лимит обновлён")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "settings:trade")
|
@router.callback_query(F.data == "settings:trade")
|
||||||
async def open_trade_settings(callback: CallbackQuery) -> None:
|
async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||||
if not await _prepare_system_from_callback(callback, screen="settings_trade"):
|
if not await _prepare_system_from_callback(callback, screen="settings_trade"):
|
||||||
@@ -665,40 +796,4 @@ async def open_system_about(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
_register_system_screen(callback.message, screen="system_about")
|
_register_system_screen(callback.message, screen="system_about")
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "settings:auto_max_reserved")
|
|
||||||
async def open_auto_max_reserved_settings(callback: CallbackQuery) -> None:
|
|
||||||
if not await _prepare_system_from_callback(callback, screen="settings_auto"):
|
|
||||||
return
|
|
||||||
|
|
||||||
text = (
|
|
||||||
"<b>🏦 Лимит на сделку</b>\n\n"
|
|
||||||
"<b>СИСТЕМА</b> · Настройки · Автоторговля\n\n"
|
|
||||||
"Максимальная доля баланса, которую можно зарезервировать под позицию:"
|
|
||||||
)
|
|
||||||
|
|
||||||
builder = InlineKeyboardBuilder()
|
|
||||||
builder.button(text="25%", callback_data="settings:auto_max_reserved:25")
|
|
||||||
builder.button(text="50%", callback_data="settings:auto_max_reserved:50")
|
|
||||||
builder.button(text="75%", callback_data="settings:auto_max_reserved:75")
|
|
||||||
builder.button(text="100%", callback_data="settings:auto_max_reserved:100")
|
|
||||||
builder.button(text="off", callback_data="settings:auto_max_reserved:off")
|
|
||||||
builder.button(text="⬅️ Назад", callback_data="settings:auto")
|
|
||||||
builder.adjust(2, 2, 1, 1)
|
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
|
||||||
_register_system_screen(callback.message, screen="settings_auto")
|
|
||||||
await callback.answer()
|
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("settings:auto_max_reserved:"))
|
|
||||||
async def set_auto_max_reserved(callback: CallbackQuery) -> None:
|
|
||||||
raw_value = callback.data.split(":", 2)[2]
|
|
||||||
|
|
||||||
value = None if raw_value == "off" else float(raw_value)
|
|
||||||
AutoTradeService().set_max_reserved_balance_percent(value)
|
|
||||||
|
|
||||||
await open_auto_settings(callback)
|
|
||||||
await callback.answer("Max Reserved обновлён")
|
|
||||||
@@ -32,6 +32,9 @@ class AutoTradeService:
|
|||||||
_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
|
_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
|
_same_signal_count = 0
|
||||||
|
|
||||||
# debug: принудительно выставить сигнал и decision
|
# debug: принудительно выставить сигнал и decision
|
||||||
@@ -649,12 +652,115 @@ class AutoTradeService:
|
|||||||
if not isinstance(payload, dict):
|
if not isinstance(payload, dict):
|
||||||
return
|
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_state = payload.get("market_state")
|
||||||
state.market_trend = payload.get("market_trend")
|
state.market_trend = payload.get("market_trend")
|
||||||
state.market_volatility = payload.get("market_volatility")
|
state.market_volatility = payload.get("market_volatility")
|
||||||
state.market_analysis_interval = payload.get("market_analysis_interval")
|
state.market_analysis_interval = payload.get("market_analysis_interval")
|
||||||
state.market_analysis_reason = payload.get("market_analysis_reason")
|
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:
|
def run_cycle(self) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from zoneinfo import ZoneInfo
|
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:
|
def _now_local() -> datetime:
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
try:
|
try:
|
||||||
@@ -76,7 +93,9 @@ def _payload(row: dict) -> dict:
|
|||||||
def _payload_json(payload: dict) -> str:
|
def _payload_json(payload: dict) -> str:
|
||||||
if not payload:
|
if not payload:
|
||||||
return ""
|
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]:
|
def _export_row(row: dict) -> list[str]:
|
||||||
@@ -84,15 +103,15 @@ def _export_row(row: dict) -> list[str]:
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
_format_datetime(row.get("created_at")),
|
_format_datetime(row.get("created_at")),
|
||||||
str(row.get("level") or ""),
|
_strip_emoji(row.get("level")),
|
||||||
str(row.get("event_type") or ""),
|
_strip_emoji(row.get("event_type")),
|
||||||
_event_title(row.get("event_type")),
|
_strip_emoji(_event_title(row.get("event_type"))),
|
||||||
str(row.get("message") or ""),
|
_strip_emoji(row.get("message")),
|
||||||
str(payload.get("account_mode") or "").upper(),
|
_strip_emoji(str(payload.get("account_mode") or "").upper()),
|
||||||
str(payload.get("screen") or ""),
|
_strip_emoji(payload.get("screen")),
|
||||||
str(payload.get("action") or ""),
|
_strip_emoji(payload.get("action")),
|
||||||
str(payload.get("error_type") or ""),
|
_strip_emoji(payload.get("error_type")),
|
||||||
str(payload.get("raw_error") or ""),
|
_strip_emoji(payload.get("raw_error")),
|
||||||
_payload_json(payload),
|
_payload_json(payload),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -204,19 +204,19 @@ class MarketAnalysisService:
|
|||||||
rsi_text = f", RSI={rsi_value:.2f}" if rsi_value is not None else ""
|
rsi_text = f", RSI={rsi_value:.2f}" if rsi_value is not None else ""
|
||||||
|
|
||||||
if state == MarketState.TREND_UP:
|
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:
|
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:
|
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:
|
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:
|
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}."
|
return f"Состояние рынка не определено. Trend={trend}, volatility={volatility}."
|
||||||
|
|
||||||
|
|||||||
@@ -396,6 +396,10 @@
|
|||||||
- централизован EVENT_TITLES mapping
|
- централизован EVENT_TITLES mapping
|
||||||
- журнал подготовлен к filters/search layer
|
- журнал подготовлен к filters/search layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 07.4.4
|
||||||
|
|
||||||
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
|
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
|
||||||
- добавлено короткое human-readable отображение состояния рынка
|
- добавлено короткое human-readable отображение состояния рынка
|
||||||
- технические market_state значения скрыты из основного Auto UI
|
- технические market_state значения скрыты из основного Auto UI
|
||||||
@@ -413,8 +417,24 @@
|
|||||||
- HOLD summary теперь пишется только при реальной смене сигнала
|
- HOLD summary теперь пишется только при реальной смене сигнала
|
||||||
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
|
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
|
||||||
|
|
||||||
### 07.4.4
|
#### 07.4.4.1.2 ✅ Market State Journal Events
|
||||||
⏳ Grid Strategy
|
- добавлено journal logging изменений состояния рынка
|
||||||
|
- реализован market-state transition tracking
|
||||||
|
- добавлены market_state_changed события
|
||||||
|
- добавлены market_trend_changed события
|
||||||
|
- добавлены market_volatility_changed события
|
||||||
|
- market-analysis интегрирован в auto runtime
|
||||||
|
- устранён spam logging market-analysis циклов
|
||||||
|
- реализовано logging только при реальной смене состояния
|
||||||
|
- добавлены human-readable market messages
|
||||||
|
- убраны raw enum/state значения из UI-журнала
|
||||||
|
- журнал переведён на explainable market-analysis стиль
|
||||||
|
- добавлена фиксация отсутствия выраженного направления рынка
|
||||||
|
- подготовлена база для market analytics layer
|
||||||
|
- подготовлена база для future AI market commentary
|
||||||
|
- журнал подготовлен к market filters/search layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 07.4.5
|
### 07.4.5
|
||||||
⏳ Scalping Strategy
|
⏳ Scalping Strategy
|
||||||
|
|||||||
@@ -372,6 +372,10 @@
|
|||||||
- централизован EVENT_TITLES mapping
|
- централизован EVENT_TITLES mapping
|
||||||
- журнал подготовлен к filters/search layer
|
- журнал подготовлен к filters/search layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 07.4.4
|
||||||
|
|
||||||
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
|
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
|
||||||
- добавлено короткое human-readable отображение состояния рынка
|
- добавлено короткое human-readable отображение состояния рынка
|
||||||
- технические market_state значения скрыты из основного Auto UI
|
- технические market_state значения скрыты из основного Auto UI
|
||||||
@@ -389,10 +393,24 @@
|
|||||||
- HOLD summary теперь пишется только при реальной смене сигнала
|
- HOLD summary теперь пишется только при реальной смене сигнала
|
||||||
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
|
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
|
||||||
|
|
||||||
---
|
#### 07.4.4.1.2 ✅ Market State Journal Events
|
||||||
|
- добавлено journal logging изменений состояния рынка
|
||||||
|
- реализован market-state transition tracking
|
||||||
|
- добавлены market_state_changed события
|
||||||
|
- добавлены market_trend_changed события
|
||||||
|
- добавлены market_volatility_changed события
|
||||||
|
- market-analysis интегрирован в auto runtime
|
||||||
|
- устранён spam logging market-analysis циклов
|
||||||
|
- реализовано logging только при реальной смене состояния
|
||||||
|
- добавлены human-readable market messages
|
||||||
|
- убраны raw enum/state значения из UI-журнала
|
||||||
|
- журнал переведён на explainable market-analysis стиль
|
||||||
|
- добавлена фиксация отсутствия выраженного направления рынка
|
||||||
|
- подготовлена база для market analytics layer
|
||||||
|
- подготовлена база для future AI market commentary
|
||||||
|
- журнал подготовлен к market filters/search layer
|
||||||
|
|
||||||
### 07.4.4
|
---
|
||||||
⏳ Grid strategy
|
|
||||||
|
|
||||||
### 07.4.5
|
### 07.4.5
|
||||||
⏳ Scalping strategy
|
⏳ Scalping strategy
|
||||||
|
|||||||
198
docs/stages/stage-07_4_4_1_2-market_state_journal_events.md
Normal file
198
docs/stages/stage-07_4_4_1_2-market_state_journal_events.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# 07.4.4.1.2 — Market State Journal Events
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Добавить полноценное журналирование состояния рынка и результатов market-analysis слоя, чтобы автоторговля фиксировала не только BUY / SELL / HOLD сигналы, но и изменения самого состояния рынка:
|
||||||
|
|
||||||
|
- тренд вверх
|
||||||
|
- тренд вниз
|
||||||
|
- отсутствие выраженного направления
|
||||||
|
- повышенная волатильность
|
||||||
|
- пониженная волатильность
|
||||||
|
- неизвестное состояние
|
||||||
|
|
||||||
|
Этап подготовил инфраструктуру для дальнейшей аналитики execution layer, risk layer и explainable trading logic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что было реализовано
|
||||||
|
|
||||||
|
## 1. Добавлен journal-layer для Market Analysis
|
||||||
|
|
||||||
|
В систему внедрено отдельное журналирование market-analysis событий.
|
||||||
|
|
||||||
|
Теперь журнал фиксирует:
|
||||||
|
|
||||||
|
- смену состояния рынка
|
||||||
|
- смену тренда
|
||||||
|
- изменение волатильности
|
||||||
|
- переход рынка в режим без выраженного направления
|
||||||
|
- возврат рынка в тренд
|
||||||
|
- переход рынка в высокую волатильность
|
||||||
|
- переход рынка в низкую волатильность
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. Реализовано отслеживание изменений market state
|
||||||
|
|
||||||
|
Добавлен state-tracking между циклами анализа рынка.
|
||||||
|
|
||||||
|
Теперь система сравнивает:
|
||||||
|
|
||||||
|
- прошлое состояние рынка
|
||||||
|
- текущее состояние рынка
|
||||||
|
|
||||||
|
и пишет событие только при реальном изменении состояния.
|
||||||
|
|
||||||
|
Это устранило spam logging на каждом цикле автоторговли.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. Добавлены отдельные event_type для аналитики рынка
|
||||||
|
|
||||||
|
В журнал внедрены новые event_type:
|
||||||
|
|
||||||
|
- market_state_changed
|
||||||
|
- market_trend_changed
|
||||||
|
- market_volatility_changed
|
||||||
|
|
||||||
|
Это подготовило журнал к:
|
||||||
|
|
||||||
|
- filters/search layer
|
||||||
|
- аналитике поведения рынка
|
||||||
|
- future BI/export
|
||||||
|
- explainable AI logging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. Реализованы human-readable market messages
|
||||||
|
|
||||||
|
Технические market-state значения были преобразованы в понятные сообщения.
|
||||||
|
|
||||||
|
Вместо:
|
||||||
|
|
||||||
|
- TREND_UP
|
||||||
|
- TREND_DOWN
|
||||||
|
- RANGE
|
||||||
|
|
||||||
|
пользователь теперь видит:
|
||||||
|
|
||||||
|
- «Рынок перешёл в рост»
|
||||||
|
- «Рынок перешёл в снижение»
|
||||||
|
- «На рынке нет выраженного направления»
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. Удалён технический стиль market-analysis сообщений
|
||||||
|
|
||||||
|
Из journal UI убраны:
|
||||||
|
|
||||||
|
- raw enum values
|
||||||
|
- технические обозначения state
|
||||||
|
- служебные market constants
|
||||||
|
|
||||||
|
Журнал стал ориентирован на пользователя, а не на внутренние enum системы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. Market analysis интегрирован в auto runtime
|
||||||
|
|
||||||
|
Market-analysis теперь стал полноценной частью runtime автоторговли.
|
||||||
|
|
||||||
|
События рынка начали синхронизироваться с:
|
||||||
|
|
||||||
|
- auto runtime
|
||||||
|
- signal runtime
|
||||||
|
- execution runtime
|
||||||
|
- monitoring runtime
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7. Улучшен explainability layer
|
||||||
|
|
||||||
|
Теперь journal способен объяснять:
|
||||||
|
|
||||||
|
- почему стратегия вошла в HOLD
|
||||||
|
- почему execution заблокирован
|
||||||
|
- почему рынок считается опасным
|
||||||
|
- почему направление не подтверждено
|
||||||
|
|
||||||
|
Это критически важно для:
|
||||||
|
|
||||||
|
- debugging
|
||||||
|
- future AI-assistant layer
|
||||||
|
- user trust
|
||||||
|
- explainable autotrading
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8. Подготовлена основа для future analytics
|
||||||
|
|
||||||
|
Этап подготовил систему к следующим задачам:
|
||||||
|
|
||||||
|
- market heatmaps
|
||||||
|
- market statistics
|
||||||
|
- market transition analytics
|
||||||
|
- trend persistence analysis
|
||||||
|
- volatility tracking
|
||||||
|
- AI market commentary
|
||||||
|
- advanced journal filters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Изменения в архитектуре
|
||||||
|
|
||||||
|
## Market Analysis Layer
|
||||||
|
|
||||||
|
Расширены:
|
||||||
|
|
||||||
|
- MarketAnalysisService
|
||||||
|
- MarketAnalysisResult
|
||||||
|
- market-state tracking logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto Runtime Layer
|
||||||
|
|
||||||
|
Добавлено:
|
||||||
|
|
||||||
|
- сохранение предыдущего market state
|
||||||
|
- сравнение market transitions
|
||||||
|
- event emission при изменении рынка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Journal Layer
|
||||||
|
|
||||||
|
Добавлены:
|
||||||
|
|
||||||
|
- market-analysis event_type
|
||||||
|
- human-readable market messages
|
||||||
|
- runtime-aware market events
|
||||||
|
- unified UI logging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что изменилось для пользователя
|
||||||
|
|
||||||
|
Пользователь начал видеть в журнале:
|
||||||
|
|
||||||
|
- реальные изменения рынка
|
||||||
|
- понятные описания состояния
|
||||||
|
- объяснение поведения стратегии
|
||||||
|
- причину HOLD-сигналов
|
||||||
|
|
||||||
|
Вместо технического spam logging журнал стал выполнять роль explainable trading feed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что подготовлено дальше
|
||||||
|
|
||||||
|
Этап подготовил основу для:
|
||||||
|
|
||||||
|
- 07.4.4.1.3 — Market Transition Analytics
|
||||||
|
- volatility persistence tracking
|
||||||
|
- trend strength scoring
|
||||||
|
- market regime detection
|
||||||
|
- AI commentary layer
|
||||||
|
- unified monitoring analytics
|
||||||
Reference in New Issue
Block a user