07.4.4.1.11 — Advanced Trend Quality & EMA Distance Layer

This commit is contained in:
2026-05-20 21:15:00 +03:00
parent 2c75f95b46
commit 06ea376cb5
36 changed files with 6260 additions and 2092 deletions

View File

@@ -4,33 +4,49 @@ from __future__ import annotations
import asyncio
import time
from typing import Callable
from collections.abc import Callable
from typing import ClassVar, Protocol
from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter
from aiogram.types import InlineKeyboardMarkup
from src.core.telegram_errors import (
is_message_not_modified,
is_message_to_edit_not_found,
)
from src.integrations.exchange.market_data_runner import MarketDataRunner
from src.trading.debug.service import DebugTradeService
from src.notifications.targets import NotificationTargetRegistry
from src.trading.debug.service import DebugTradeService
class RenderText(Protocol):
def __call__(self) -> str: ...
class RenderMarkup(Protocol):
def __call__(self) -> InlineKeyboardMarkup | None: ...
class DebugTradeRunner:
_task: asyncio.Task | None = None
_task: ClassVar[asyncio.Task[None] | None] = None
_bot: Bot | None = None
_chat_id: int | None = None
_message_id: int | None = None
_render_text: Callable[[], str] | None = None
_render_markup: Callable[[], object] | None = None
_bot: ClassVar[Bot | None] = None
_chat_id: ClassVar[int | None] = None
_message_id: ClassVar[int | None] = None
_current_screen: str | None = None
_text_renderer: ClassVar[RenderText | None] = None
_markup_renderer: ClassVar[RenderMarkup | None] = None
_interval_seconds = 5
_market_interval_seconds = 1
_current_screen: ClassVar[str | None] = None
_last_text: str | None = None
_last_refresh_at: float = 0.0
_retry_after_until: float = 0.0
_interval_seconds: ClassVar[int] = 5
_market_interval_seconds: ClassVar[int] = 1
_last_text: ClassVar[str | None] = None
_last_refresh_at: ClassVar[float] = 0.0
_retry_after_until: ClassVar[float] = 0.0
@classmethod
def register_screen(
@@ -39,14 +55,14 @@ class DebugTradeRunner:
bot: Bot,
chat_id: int,
message_id: int,
render_text: Callable[[], str],
render_markup: Callable[[], object],
render_text: RenderText,
render_markup: RenderMarkup,
) -> None:
cls._bot = bot
cls._chat_id = chat_id
cls._message_id = message_id
cls._render_text = render_text
cls._render_markup = render_markup
cls._text_renderer = render_text
cls._markup_renderer = render_markup
cls._last_text = None
NotificationTargetRegistry.set_default_chat(
@@ -54,6 +70,30 @@ class DebugTradeRunner:
chat_id=chat_id,
)
@classmethod
def _reset_screen(cls) -> None:
cls._message_id = None
cls._text_renderer = None
cls._markup_renderer = None
cls._last_text = None
@classmethod
def _reset_runtime(cls) -> None:
cls._bot = None
cls._chat_id = None
cls._current_screen = None
cls._reset_screen()
@classmethod
def _is_screen_ready(cls) -> bool:
return (
cls._bot is not None
and cls._chat_id is not None
and cls._message_id is not None
and cls._text_renderer is not None
and cls._markup_renderer is not None
)
@classmethod
async def delete_registered_screen(
cls,
@@ -75,10 +115,7 @@ class DebugTradeRunner:
except Exception:
pass
cls._message_id = None
cls._render_text = None
cls._render_markup = None
cls._last_text = None
cls._reset_screen()
@classmethod
async def detach_screen(
@@ -105,13 +142,7 @@ class DebugTradeRunner:
except Exception:
pass
cls._bot = None
cls._chat_id = None
cls._message_id = None
cls._render_text = None
cls._render_markup = None
cls._current_screen = None
cls._last_text = None
cls._reset_runtime()
@classmethod
def set_current_screen(cls, screen: str) -> None:
@@ -121,6 +152,7 @@ class DebugTradeRunner:
def start(cls) -> None:
service = DebugTradeService()
state = service.get_state()
state.status = "RUNNING"
MarketDataRunner.start(
@@ -167,7 +199,11 @@ class DebugTradeRunner:
await asyncio.sleep(cls._interval_seconds)
@classmethod
async def refresh_screen(cls, *, force: bool = False) -> None:
async def refresh_screen(
cls,
*,
force: bool = False,
) -> None:
if cls._current_screen != "debug_auto":
return
@@ -176,32 +212,43 @@ class DebugTradeRunner:
if now < cls._retry_after_until:
return
if not force and now - cls._last_refresh_at < cls._interval_seconds:
return
if not all(
[
cls._bot,
cls._chat_id,
cls._message_id,
cls._render_text,
cls._render_markup,
]
if (
not force
and now - cls._last_refresh_at < cls._interval_seconds
):
return
text = cls._render_text()
if not cls._is_screen_ready():
return
bot = cls._bot
chat_id = cls._chat_id
message_id = cls._message_id
text_renderer = cls._text_renderer
markup_renderer = cls._markup_renderer
if (
bot is None
or chat_id is None
or message_id is None
or text_renderer is None
or markup_renderer is None
):
return
text = text_renderer()
if text == cls._last_text:
return
try:
await cls._bot.edit_message_text(
chat_id=cls._chat_id,
message_id=cls._message_id,
await bot.edit_message_text(
chat_id=chat_id,
message_id=message_id,
text=text,
reply_markup=cls._render_markup(),
reply_markup=markup_renderer(),
)
cls._last_text = text
cls._last_refresh_at = now
@@ -209,18 +256,13 @@ class DebugTradeRunner:
cls._retry_after_until = time.monotonic() + exc.retry_after + 5
except TelegramBadRequest as exc:
error_text = str(exc).lower()
if "message is not modified" in error_text:
if is_message_not_modified(exc):
cls._last_text = text
cls._last_refresh_at = now
return
if "message to edit not found" in error_text:
cls._message_id = None
cls._render_text = None
cls._render_markup = None
cls._last_text = None
if is_message_to_edit_not_found(exc):
cls._reset_screen()
return
except Exception: