Stage 07.3.1 - auto trading background runner and live screen

This commit is contained in:
2026-04-28 22:29:26 +03:00
parent d639137855
commit b2801d8a19
11 changed files with 368 additions and 19 deletions

View File

@@ -3,12 +3,13 @@
from __future__ import annotations
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.exceptions import TelegramBadRequest
from src.telegram.ui.common import mode_line
from src.trading.auto.runner import AutoTradeRunner
from src.trading.auto.service import AutoTradeService
@@ -49,12 +50,9 @@ def _signal_label(signal: str | None) -> str:
def _auto_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
# 1 ряд
builder.button(text="▶️ Start", callback_data="auto:start")
builder.button(text="👀 Watch", callback_data="auto:observe")
builder.button(text="🛑 Stop", callback_data="auto:stop")
# 2 ряд
builder.button(text="🛠️ Настройки", callback_data="settings:auto")
builder.adjust(3, 1)
@@ -64,10 +62,6 @@ def _auto_keyboard() -> InlineKeyboardMarkup:
# собрать текст экрана
def _build_auto_text() -> str:
service = AutoTradeService()
# выполнить один цикл анализа
service.run_cycle()
state = service.get_state()
strategy = _strategy_label(state.strategy)
@@ -86,7 +80,7 @@ def _build_auto_text() -> str:
)
# отрисовать экран
# отрисовать live-экран автоторговли
async def _render_auto_screen(
target_message: Message,
*,
@@ -98,21 +92,47 @@ async def _render_auto_screen(
try:
await target_message.edit_text(text, reply_markup=_auto_keyboard())
except TelegramBadRequest as exc:
if "message is not modified" in str(exc).lower():
return
raise
else:
await target_message.answer(text, reply_markup=_auto_keyboard())
if "message is not modified" not in str(exc).lower():
raise
AutoTradeRunner.register_screen(
bot=target_message.bot,
chat_id=target_message.chat.id,
message_id=target_message.message_id,
render_text=_build_auto_text,
render_markup=_auto_keyboard,
)
return
sent_message = await target_message.answer(text, reply_markup=_auto_keyboard())
AutoTradeRunner.register_screen(
bot=sent_message.bot,
chat_id=sent_message.chat.id,
message_id=sent_message.message_id,
render_text=_build_auto_text,
render_markup=_auto_keyboard,
)
# открыть экран из меню
# открыть экран из главного меню
@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
async def open_auto(message: Message, state: FSMContext) -> None:
await state.clear()
AutoTradeRunner.set_current_screen("auto")
current_state = AutoTradeService().get_state()
if current_state.status in {"RUNNING", "OBSERVING"}:
await AutoTradeRunner.delete_registered_screen(
bot=message.bot,
chat_id=message.chat.id,
)
await _render_auto_screen(message, edit_mode=False)
# открыть экран callback
# открыть экран через callback
@router.callback_query(F.data == "auto:home")
async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None:
await state.clear()
@@ -121,6 +141,7 @@ async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) ->
await callback.answer("Сообщение не найдено", show_alert=True)
return
AutoTradeRunner.set_current_screen("auto")
await _render_auto_screen(callback.message, edit_mode=True)
await callback.answer()
@@ -131,6 +152,9 @@ async def auto_start(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.start()
AutoTradeRunner.set_current_screen("auto")
AutoTradeRunner.start()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)
@@ -143,6 +167,9 @@ async def auto_observe(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.observe()
AutoTradeRunner.set_current_screen("auto")
AutoTradeRunner.start()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)
@@ -155,6 +182,8 @@ async def auto_stop(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.stop()
AutoTradeRunner.stop()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)

View File

@@ -16,6 +16,7 @@ from src.telegram.handlers.journal_ui import (
render_actions,
)
from src.trading.journal.service import JournalService
from src.trading.auto.runner import AutoTradeRunner
router = Router(name="journal")
@@ -45,6 +46,7 @@ async def _show_journal_page(
page: int,
edit_mode: bool,
) -> None:
AutoTradeRunner.set_current_screen("journal")
service = JournalService()
total = service.get_total_count()
@@ -64,6 +66,7 @@ async def _show_journal_page(
@router.callback_query(F.data == "journal:actions")
async def journal_actions(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("journal")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return
@@ -178,6 +181,7 @@ async def export_journal_xlsx(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "journal:clear_confirm")
async def clear_journal_confirm(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("journal")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return

View File

@@ -16,6 +16,7 @@ from src.telegram.ui.exchange_error import (
show_message_exchange_error,
)
from src.trading.journal.service import JournalService
from src.trading.auto.runner import AutoTradeRunner
router = Router(name="market")
@@ -72,6 +73,8 @@ async def _render_market_screen(
edit_mode: bool,
action: str,
) -> None:
AutoTradeRunner.set_current_screen("market")
service = ExchangeService()
journal = JournalService()
requested_symbol = service.settings.default_symbol

View File

@@ -25,6 +25,7 @@ from src.telegram.ui.exchange_error import (
)
from src.trading.accounts.service import AccountsService
from src.trading.journal.service import JournalService
from src.trading.auto.runner import AutoTradeRunner
router = Router(name="portfolio")
@@ -70,6 +71,8 @@ async def _render_portfolio_screen(
edit_mode: bool,
action: str,
) -> None:
AutoTradeRunner.set_current_screen("portfolio")
service = AccountsService()
exchange_service = ExchangeService()
journal = JournalService()

View File

@@ -12,6 +12,7 @@ from src.core.config import load_settings
from src.core.constants import APP_NAME, APP_VERSION
from src.trading.journal.service import JournalService
from src.trading.auto.service import AutoTradeService
from src.trading.auto.runner import AutoTradeRunner
router = Router(name="system")
@@ -44,6 +45,8 @@ async def _render_system_screen(
chat_id: int | None,
action: str,
) -> None:
AutoTradeRunner.set_current_screen("system")
journal = JournalService()
journal.log_ui_info(
@@ -166,6 +169,7 @@ async def open_system_management(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "settings:auto")
async def open_auto_settings(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("settings_auto")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return
@@ -203,6 +207,7 @@ async def open_auto_settings(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "settings:auto_strategy")
async def open_auto_strategy_settings(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("settings_auto")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return
@@ -232,11 +237,13 @@ async def set_auto_strategy(callback: CallbackQuery) -> None:
if callback.message is not None:
await open_auto_settings(callback)
AutoTradeRunner.set_current_screen("settings_auto")
await callback.answer("Стратегия обновлена")
@router.callback_query(F.data == "settings:auto_symbol")
async def open_auto_symbol_settings(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("settings_auto")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return
@@ -268,11 +275,13 @@ async def set_auto_symbol(callback: CallbackQuery) -> None:
if callback.message is not None:
await open_auto_settings(callback)
AutoTradeRunner.set_current_screen("settings_auto")
await callback.answer("Инструмент обновлён")
@router.callback_query(F.data == "settings:auto_risk")
async def open_auto_risk_settings(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("settings_auto")
if callback.message is None:
await callback.answer("Сообщение не найдено", show_alert=True)
return
@@ -302,6 +311,7 @@ async def set_auto_risk(callback: CallbackQuery) -> None:
if callback.message is not None:
await open_auto_settings(callback)
AutoTradeRunner.set_current_screen("settings_auto")
await callback.answer("Риск обновлён")

View File

@@ -12,6 +12,7 @@ from src.telegram.handlers.trade.new_order import (
show_recent_drafts,
start_new_order_draft,
)
from src.trading.auto.runner import AutoTradeRunner
router = Router(name="trade_main")
@@ -96,6 +97,8 @@ def _trade_settings_text() -> str:
@router.message(F.text.in_({"📊 Торговля", "⚡ Торговля", "Торговля"}))
async def open_trade(message: Message) -> None:
AutoTradeRunner.set_current_screen("trade")
await message.answer(
_trade_home_text(),
reply_markup=_trade_home_keyboard(),
@@ -107,6 +110,8 @@ async def open_trade_home_callback(
callback: CallbackQuery,
state: FSMContext,
) -> None:
AutoTradeRunner.set_current_screen("trade")
await state.clear()
await callback.answer()
@@ -137,6 +142,8 @@ async def open_new_order_from_trade(
@router.callback_query(F.data == "trade:orders")
async def open_orders_from_trade(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("trade")
await callback.answer()
if callback.message is not None:
await callback.message.edit_text(
@@ -158,6 +165,8 @@ async def open_drafts_from_orders(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "trade:history")
async def open_trade_history(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("trade")
await callback.answer()
if callback.message is not None:
await callback.message.edit_text(
@@ -196,6 +205,8 @@ async def open_canceled_history(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "trade:settings")
async def open_trade_settings(callback: CallbackQuery) -> None:
AutoTradeRunner.set_current_screen("trade")
await callback.answer()
if callback.message is not None:
await callback.message.edit_text(