07.4.4.1.13 — AutoTrade Runtime Journal, Execution Refactor & Trade Analytics
This commit is contained in:
@@ -16,18 +16,19 @@ EVENT_TITLES = {
|
||||
|
||||
# Настройки
|
||||
"auto_settings_updated": "Автоторговля",
|
||||
"auto_status_changed": "Автоторговля",
|
||||
"risk_settings_updated": "Защита",
|
||||
|
||||
# Аналитика рынка
|
||||
"market_state_changed": "Рынок",
|
||||
"market_volatility_changed": "Рынок",
|
||||
# Аналитика автоторговли
|
||||
"market_state_changed": "Автоторговля",
|
||||
"market_volatility_changed": "Автоторговля",
|
||||
|
||||
# Мониторинг рынка
|
||||
"market_monitor_started": "Рынок",
|
||||
"market_monitor_stopped": "Рынок",
|
||||
"market_stream_connected": "Рынок",
|
||||
"market_stream_disconnected": "Рынок",
|
||||
"market_symbol_changed": "Рынок",
|
||||
# Рыночные данные runtime
|
||||
"market_monitor_started": "Автоторговля",
|
||||
"market_monitor_stopped": "Автоторговля",
|
||||
"market_stream_connected": "Автоторговля",
|
||||
"market_stream_disconnected": "Автоторговля",
|
||||
"market_symbol_changed": "Автоторговля",
|
||||
|
||||
# Мониторинг позиций
|
||||
"entry_blocked": "Вход в позицию",
|
||||
@@ -61,10 +62,6 @@ EVENT_TITLES = {
|
||||
"system_retry": "Система",
|
||||
"system_about_opened": "Система",
|
||||
|
||||
"market_open_requested": "Рынок",
|
||||
"market_open_success": "Рынок",
|
||||
"market_open_error": "Рынок",
|
||||
|
||||
"portfolio_open_requested": "Портфель",
|
||||
"portfolio_open_success": "Портфель",
|
||||
"portfolio_open_error": "Портфель",
|
||||
@@ -72,10 +69,21 @@ EVENT_TITLES = {
|
||||
|
||||
"exchange_request_error": "Биржа",
|
||||
|
||||
"exchange_auth_error": "Аккаунт",
|
||||
"exchange_auth_restored": "Аккаунт",
|
||||
"exchange_time_sync_error": "Время биржи",
|
||||
"exchange_time_sync_restored": "Время биржи",
|
||||
|
||||
"balance_summary_loaded": "Баланс",
|
||||
"balance_summary_error": "Баланс",
|
||||
|
||||
"runtime_expired": "Runtime",
|
||||
|
||||
"market_status_unavailable": "Автоторговля",
|
||||
"market_status_restored": "Автоторговля",
|
||||
"market_closed": "Автоторговля",
|
||||
"market_rest_fallback_available": "Автоторговля",
|
||||
"market_rest_fallback_unavailable": "Автоторговля",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ from zoneinfo import ZoneInfo
|
||||
|
||||
from src.core.config import load_settings
|
||||
from src.core.constants import APP_NAME, APP_VERSION
|
||||
from src.integrations.exchange.runtime_ui import build_runtime_exchange_alerts
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.integrations.exchange.status import build_exchange_error_status
|
||||
from src.storage.session import check_database_health
|
||||
from src.trading.journal.service import JournalService
|
||||
|
||||
@@ -32,6 +34,96 @@ class SystemSnapshot:
|
||||
components: list[ComponentStatus]
|
||||
|
||||
|
||||
def _build_exchange_alert_components(
|
||||
*,
|
||||
default_symbol: str,
|
||||
) -> list[ComponentStatus]:
|
||||
exchange_service = ExchangeService()
|
||||
|
||||
try:
|
||||
runtime_status = exchange_service.get_symbol_runtime_status(default_symbol)
|
||||
except Exception as exc:
|
||||
runtime_status = build_exchange_error_status(exc)
|
||||
|
||||
if not runtime_status.is_available:
|
||||
return [
|
||||
ComponentStatus(
|
||||
name="Биржа",
|
||||
state=runtime_status.ui_line,
|
||||
details=runtime_status.message,
|
||||
)
|
||||
]
|
||||
|
||||
alerts = build_runtime_exchange_alerts(symbol=default_symbol)
|
||||
|
||||
exchange_unavailable_alert = next(
|
||||
(
|
||||
alert
|
||||
for alert in alerts
|
||||
if str(alert.get("code") or "") == "EXCHANGE_UNAVAILABLE"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if exchange_unavailable_alert is not None:
|
||||
return [
|
||||
ComponentStatus(
|
||||
name="Биржа",
|
||||
state=str(
|
||||
exchange_unavailable_alert.get("ui_line")
|
||||
or exchange_unavailable_alert.get("title")
|
||||
or "⛔️ Биржа недоступна"
|
||||
),
|
||||
details=str(exchange_unavailable_alert.get("details") or ""),
|
||||
)
|
||||
]
|
||||
|
||||
components: list[ComponentStatus] = [
|
||||
ComponentStatus(
|
||||
name="Биржа",
|
||||
state="🟢",
|
||||
details=runtime_status.message,
|
||||
)
|
||||
]
|
||||
|
||||
has_account_alert = False
|
||||
|
||||
for alert in alerts:
|
||||
code = str(alert.get("code") or "")
|
||||
state = str(
|
||||
alert.get("ui_line")
|
||||
or alert.get("title")
|
||||
or "⛔️ Ошибка биржи"
|
||||
)
|
||||
|
||||
if code == "AUTH_ERROR":
|
||||
has_account_alert = True
|
||||
name = "Аккаунт"
|
||||
elif code == "TIME_ERROR":
|
||||
name = "Время биржи"
|
||||
else:
|
||||
name = "Биржа"
|
||||
|
||||
components.append(
|
||||
ComponentStatus(
|
||||
name=name,
|
||||
state=state,
|
||||
details=str(alert.get("details") or ""),
|
||||
)
|
||||
)
|
||||
|
||||
if not has_account_alert:
|
||||
components.append(
|
||||
ComponentStatus(
|
||||
name="Аккаунт",
|
||||
state="🟢",
|
||||
)
|
||||
)
|
||||
|
||||
return components
|
||||
|
||||
|
||||
# извлечь короткую версию PostgreSQL из строки health-check
|
||||
def _extract_postgres_version(raw: str) -> str:
|
||||
if not raw:
|
||||
return "PostgreSQL"
|
||||
@@ -43,99 +135,72 @@ def _extract_postgres_version(raw: str) -> str:
|
||||
return "PostgreSQL"
|
||||
|
||||
|
||||
def _build_exchange_status(
|
||||
exchange_service: ExchangeService,
|
||||
default_symbol: str,
|
||||
) -> ComponentStatus:
|
||||
try:
|
||||
symbol_validation = exchange_service.validate_symbol(default_symbol)
|
||||
except Exception as exc:
|
||||
return ComponentStatus(
|
||||
name="Биржа",
|
||||
state="🔴",
|
||||
details=_humanize_error_message(str(exc)),
|
||||
)
|
||||
|
||||
exchange_health = exchange_service.get_health()
|
||||
|
||||
if exchange_health.ok and symbol_validation.is_valid:
|
||||
return ComponentStatus(name="Биржа", state="🟢")
|
||||
|
||||
if not exchange_health.ok:
|
||||
return ComponentStatus(
|
||||
name="Биржа",
|
||||
state="🔴",
|
||||
details=_humanize_error_message(exchange_health.message or ""),
|
||||
)
|
||||
|
||||
return ComponentStatus(
|
||||
name="Биржа",
|
||||
state="🔴",
|
||||
details=symbol_validation.message or "Инструмент не прошёл проверку.",
|
||||
)
|
||||
|
||||
|
||||
def _build_account_status(exchange_service: ExchangeService) -> ComponentStatus:
|
||||
private_auth_health = exchange_service.get_private_auth_health()
|
||||
if private_auth_health.ok:
|
||||
return ComponentStatus(name="Аккаунт", state="🟢")
|
||||
|
||||
return ComponentStatus(
|
||||
name="Аккаунт",
|
||||
state="🔴",
|
||||
details=_humanize_error_message(private_auth_health.message or ""),
|
||||
)
|
||||
|
||||
|
||||
# проверить подключение к БД и вернуть компонент + подпись версии
|
||||
def _build_database_status() -> tuple[ComponentStatus, str]:
|
||||
db_ok, db_message = check_database_health()
|
||||
db_label = _extract_postgres_version(db_message)
|
||||
|
||||
if db_ok:
|
||||
return ComponentStatus(name="База данных", state="🟢"), db_label
|
||||
return (
|
||||
ComponentStatus(
|
||||
name="База данных",
|
||||
state="🟢",
|
||||
),
|
||||
db_label,
|
||||
)
|
||||
|
||||
return (
|
||||
ComponentStatus(
|
||||
name="База данных",
|
||||
state="🔴",
|
||||
details=db_message or "Ошибка подключения к БД.",
|
||||
state="🔴 База данных недоступна",
|
||||
),
|
||||
db_label,
|
||||
)
|
||||
|
||||
|
||||
# проверить доступность журнала событий
|
||||
def _build_journal_status() -> ComponentStatus:
|
||||
ok, message = JournalService().get_journal_health()
|
||||
ok, _ = JournalService().get_journal_health()
|
||||
|
||||
if ok:
|
||||
return ComponentStatus(name="Журнал", state="🟢")
|
||||
return ComponentStatus(
|
||||
name="Журнал",
|
||||
state="🟢",
|
||||
)
|
||||
|
||||
return ComponentStatus(name="Журнал", state="🔴", details=message)
|
||||
return ComponentStatus(
|
||||
name="Журнал",
|
||||
state="🔴 Журнал недоступен",
|
||||
)
|
||||
|
||||
|
||||
# определить runtime-режим по base_url биржи
|
||||
def get_runtime_mode_key() -> str:
|
||||
settings = load_settings()
|
||||
return "demo" if "demo" in settings.exchange_base_url.lower() else "live"
|
||||
|
||||
|
||||
# вернуть человекочитаемую подпись runtime-режима
|
||||
def get_runtime_mode_label() -> str:
|
||||
return "DEMO аккаунт" if get_runtime_mode_key() == "demo" else "LIVE аккаунт"
|
||||
|
||||
|
||||
# собрать полный snapshot системного экрана
|
||||
def get_system_snapshot() -> SystemSnapshot:
|
||||
settings = load_settings()
|
||||
exchange_service = ExchangeService()
|
||||
|
||||
database_status, db_label = _build_database_status()
|
||||
exchange_status = _build_exchange_status(exchange_service, settings.default_symbol)
|
||||
account_status = _build_account_status(exchange_service)
|
||||
journal_status = _build_journal_status()
|
||||
|
||||
exchange_components = _build_exchange_alert_components(
|
||||
default_symbol=settings.default_symbol,
|
||||
)
|
||||
|
||||
components = [
|
||||
ComponentStatus(name="Приложение", state="🟢"),
|
||||
database_status,
|
||||
ComponentStatus(name="Telegram", state="🟢"),
|
||||
exchange_status,
|
||||
account_status,
|
||||
*exchange_components,
|
||||
journal_status,
|
||||
]
|
||||
|
||||
@@ -150,19 +215,20 @@ def get_system_snapshot() -> SystemSnapshot:
|
||||
)
|
||||
|
||||
|
||||
# определить, есть ли системные предупреждения
|
||||
def has_system_alerts(snapshot: SystemSnapshot) -> bool:
|
||||
return any(component.state != "🟢" for component in snapshot.components)
|
||||
|
||||
|
||||
# отрендерить одну строку компонента системы
|
||||
def _render_component(component: ComponentStatus) -> str:
|
||||
line = f"{component.state} {component.name}"
|
||||
if component.state == "🟢":
|
||||
return f"{component.state} {component.name}"
|
||||
|
||||
if component.state == "🟢" or not component.details:
|
||||
return line
|
||||
|
||||
return f"{line}\n— {component.details}"
|
||||
return component.state
|
||||
|
||||
|
||||
# получить текущее локальное время для подписи обновления
|
||||
def _now_hhmmss() -> str:
|
||||
settings = load_settings()
|
||||
tz_name = settings.tz or "UTC"
|
||||
@@ -175,50 +241,22 @@ def _now_hhmmss() -> str:
|
||||
return local_dt.strftime("%H:%M:%S")
|
||||
|
||||
|
||||
# собрать текст экрана "Система"
|
||||
def build_system_text(*, include_updated_at: bool = False) -> str:
|
||||
snapshot = get_system_snapshot()
|
||||
|
||||
components_block = "\n".join(
|
||||
_render_component(component) for component in snapshot.components
|
||||
_render_component(component)
|
||||
for component in snapshot.components
|
||||
)
|
||||
|
||||
text = (
|
||||
"<b>🖥️ Система</b>\n"
|
||||
f"🔸 <b>{snapshot.mode_label}</b>\n\n"
|
||||
# f"⏱️ {snapshot.timezone_name}\n\n"
|
||||
f"{components_block}"
|
||||
)
|
||||
|
||||
if include_updated_at:
|
||||
text += f"\n\n<i>Обновлено: {_now_hhmmss()}</i>"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def _humanize_error_message(text: str) -> str:
|
||||
t = text.lower()
|
||||
|
||||
# сеть
|
||||
if "nodename nor servname" in t or "name or service not known" in t:
|
||||
return "Нет связи с биржей"
|
||||
|
||||
if "timeout" in t or "timed out" in t:
|
||||
return "Биржа не отвечает (таймаут)"
|
||||
|
||||
if "network error" in t or "connection error" in t:
|
||||
return "Ошибка сети при обращении к бирже"
|
||||
|
||||
# API / доступ
|
||||
if "private api error" in t:
|
||||
return "Ошибка доступа к аккаунту"
|
||||
|
||||
if "invalid api key" in t or "api key" in t:
|
||||
return "Неверный API ключ"
|
||||
|
||||
if "forbidden" in t or "unauthorized" in t:
|
||||
return "Нет доступа к аккаунту"
|
||||
|
||||
# время
|
||||
if "-1021" in t or "doesn't match server time" in t:
|
||||
return "Ошибка времени (рассинхронизация)"
|
||||
|
||||
return "Не удалось получить данные с биржи"
|
||||
return text
|
||||
Reference in New Issue
Block a user