feat(live): add live screens for market and portfolio
This commit is contained in:
@@ -9,19 +9,21 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.integrations.exchange.exceptions import ExchangeError
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.telegram.ui.common import mode_line
|
||||
from src.telegram.live.runner import LiveScreen, LiveScreenRunner
|
||||
from src.telegram.ui.common import mode_line, now_line
|
||||
from src.telegram.ui.exchange_error import (
|
||||
classify_exchange_error,
|
||||
show_callback_exchange_error,
|
||||
show_message_exchange_error,
|
||||
)
|
||||
from src.trading.journal.service import JournalService
|
||||
from src.trading.auto.runner import AutoTradeRunner
|
||||
from src.trading.journal.service import JournalService
|
||||
|
||||
|
||||
router = Router(name="market")
|
||||
|
||||
|
||||
# клавиатура экрана рынка
|
||||
def _market_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
@@ -29,6 +31,7 @@ def _market_keyboard() -> InlineKeyboardMarkup:
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
# собрать текст рынка по готовым данным
|
||||
def _build_market_text(
|
||||
*,
|
||||
ticker_price: float,
|
||||
@@ -39,6 +42,8 @@ def _build_market_text(
|
||||
quote_asset: str,
|
||||
tick_size: str,
|
||||
) -> str:
|
||||
from src.telegram.ui.common import now_line
|
||||
|
||||
status_map = {
|
||||
"TRADING": "доступен для торговли",
|
||||
"HALT": "торги остановлены",
|
||||
@@ -61,10 +66,68 @@ def _build_market_text(
|
||||
f"Тип инструмента: {market_type_ru}\n"
|
||||
f"Базовый актив: {base_asset}\n"
|
||||
f"Валюта котировки: {quote_asset}\n"
|
||||
f"Шаг цены: {tick_size} {quote_asset}"
|
||||
f"Шаг цены: {tick_size} {quote_asset}\n\n"
|
||||
f"{now_line()}"
|
||||
)
|
||||
|
||||
|
||||
# собрать актуальный live-текст рынка
|
||||
def _build_market_live_text() -> str:
|
||||
service = ExchangeService()
|
||||
requested_symbol = service.settings.default_symbol
|
||||
|
||||
validation = service.validate_symbol(requested_symbol)
|
||||
|
||||
if not validation.is_valid:
|
||||
return (
|
||||
"<b>📈 Рынок</b>\n"
|
||||
f"{mode_line()}"
|
||||
"⚠️ Ошибка инструмента\n\n"
|
||||
"Инструмент недоступен."
|
||||
)
|
||||
|
||||
ticker = service.get_price(validation.normalized_symbol)
|
||||
|
||||
symbol_info = validation.symbol_info
|
||||
symbol_status = symbol_info.status if symbol_info else "n/a"
|
||||
market_type = symbol_info.market_type if symbol_info else "n/a"
|
||||
tick_size = (
|
||||
f"{symbol_info.tick_size}"
|
||||
if symbol_info and symbol_info.tick_size is not None
|
||||
else "n/a"
|
||||
)
|
||||
base_asset = symbol_info.base_asset if symbol_info and symbol_info.base_asset else "n/a"
|
||||
quote_asset = symbol_info.quote_asset if symbol_info and symbol_info.quote_asset else "n/a"
|
||||
name = symbol_info.name if symbol_info and symbol_info.name else ticker.symbol
|
||||
|
||||
return _build_market_text(
|
||||
ticker_price=ticker.price,
|
||||
name=name,
|
||||
symbol_status=symbol_status,
|
||||
market_type=market_type,
|
||||
base_asset=base_asset,
|
||||
quote_asset=quote_asset,
|
||||
tick_size=tick_size,
|
||||
)
|
||||
|
||||
|
||||
# зарегистрировать сообщение как live-экран рынка
|
||||
def _register_market_live_screen(message: Message) -> None:
|
||||
LiveScreenRunner.register_screen(
|
||||
LiveScreen(
|
||||
screen="market",
|
||||
bot=message.bot,
|
||||
chat_id=message.chat.id,
|
||||
message_id=message.message_id,
|
||||
render_text=_build_market_live_text,
|
||||
render_markup=_market_keyboard,
|
||||
interval_seconds=5,
|
||||
)
|
||||
)
|
||||
LiveScreenRunner.start("market")
|
||||
|
||||
|
||||
# отрисовать экран рынка
|
||||
async def _render_market_screen(
|
||||
target_message: Message,
|
||||
*,
|
||||
@@ -74,7 +137,8 @@ async def _render_market_screen(
|
||||
action: str,
|
||||
) -> None:
|
||||
AutoTradeRunner.set_current_screen("market")
|
||||
|
||||
LiveScreenRunner.set_current_screen("market")
|
||||
|
||||
service = ExchangeService()
|
||||
journal = JournalService()
|
||||
requested_symbol = service.settings.default_symbol
|
||||
@@ -114,8 +178,11 @@ async def _render_market_screen(
|
||||
|
||||
if edit_mode:
|
||||
await target_message.edit_text(text, reply_markup=_market_keyboard())
|
||||
_register_market_live_screen(target_message)
|
||||
else:
|
||||
await target_message.answer(text, reply_markup=_market_keyboard())
|
||||
sent_message = await target_message.answer(text, reply_markup=_market_keyboard())
|
||||
_register_market_live_screen(sent_message)
|
||||
|
||||
return
|
||||
|
||||
ticker = service.get_price(validation.normalized_symbol)
|
||||
@@ -157,14 +224,23 @@ async def _render_market_screen(
|
||||
|
||||
if edit_mode:
|
||||
await target_message.edit_text(text, reply_markup=_market_keyboard())
|
||||
_register_market_live_screen(target_message)
|
||||
else:
|
||||
await target_message.answer(text, reply_markup=_market_keyboard())
|
||||
sent_message = await target_message.answer(text, reply_markup=_market_keyboard())
|
||||
_register_market_live_screen(sent_message)
|
||||
|
||||
|
||||
# открыть рынок из главного меню
|
||||
@router.message(F.text == "📈 Рынок")
|
||||
async def open_market(message: Message, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
await LiveScreenRunner.delete_screen(
|
||||
screen="market",
|
||||
bot=message.bot,
|
||||
chat_id=message.chat.id,
|
||||
)
|
||||
|
||||
user_id = message.from_user.id if message.from_user else None
|
||||
chat_id = message.chat.id if message.chat else None
|
||||
|
||||
@@ -198,6 +274,7 @@ async def open_market(message: Message, state: FSMContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
# обновить рынок вручную
|
||||
@router.callback_query(F.data == "market:retry")
|
||||
async def retry_market(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
@@ -217,7 +294,7 @@ async def retry_market(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
edit_mode=True,
|
||||
action="retry",
|
||||
)
|
||||
await callback.answer()
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
JournalService().log_ui_error(
|
||||
event_type="market_retry_error",
|
||||
|
||||
Reference in New Issue
Block a user