feat: unify market/portfolio/system UI, improve exchange errors and asset valuation
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,14 @@ from aiogram import F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery
|
||||
|
||||
from src.integrations.exchange.exceptions import ExchangeError, format_exchange_error_for_user
|
||||
from src.telegram.handlers.trade.new_order_core import router
|
||||
from src.telegram.handlers.trade.new_order_ui import (
|
||||
mode_line,
|
||||
_draft_detail_keyboard,
|
||||
_price_keyboard,
|
||||
_quantity_keyboard,
|
||||
_render_draft_detail,
|
||||
_render_exchange_error,
|
||||
_render_order_path,
|
||||
_render_price_step_screen,
|
||||
_render_quantity_step_screen,
|
||||
@@ -20,6 +21,7 @@ from src.telegram.handlers.trade.new_order_ui import (
|
||||
_side_keyboard,
|
||||
_trade_back_home_keyboard,
|
||||
_type_keyboard,
|
||||
mode_line,
|
||||
)
|
||||
from src.trading.orders.service import OrderDraftsService
|
||||
from src.trading.orders.states import NewOrderDraftStates
|
||||
@@ -50,25 +52,65 @@ async def _return_to_draft_detail(
|
||||
await callback.answer()
|
||||
|
||||
|
||||
async def _show_navigation_exchange_error(
|
||||
callback: CallbackQuery,
|
||||
*,
|
||||
title: str,
|
||||
exc: Exception,
|
||||
draft_page: int | None = None,
|
||||
) -> None:
|
||||
reply_markup = (
|
||||
_draft_detail_keyboard("", draft_page) # won't use if branch below replaces
|
||||
if False
|
||||
else None
|
||||
)
|
||||
|
||||
if draft_page:
|
||||
keyboard = _draft_detail_keyboard("noop", draft_page)
|
||||
# заменим клавиатуру сразу на корректную
|
||||
# edit/detail тут не нужны, нужен простой возврат к черновикам
|
||||
from src.telegram.handlers.trade.new_order_ui import _drafts_back_keyboard
|
||||
|
||||
reply_markup = _drafts_back_keyboard(int(draft_page))
|
||||
else:
|
||||
reply_markup = _trade_back_home_keyboard()
|
||||
|
||||
await callback.message.edit_text(
|
||||
_render_exchange_error(
|
||||
title=title,
|
||||
message=format_exchange_error_for_user(exc),
|
||||
),
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:side")
|
||||
async def go_back_to_side(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
service = OrderDraftsService()
|
||||
context = service.get_entry_context(side="BUY", order_type="MARKET")
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_side)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
"Шаг 1/4. Выбери сторону"
|
||||
)
|
||||
await callback.message.edit_text(text, reply_markup=_side_keyboard())
|
||||
await callback.answer()
|
||||
try:
|
||||
context = service.get_entry_context(side="BUY", order_type="MARKET")
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_side)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
"Шаг 1/4. Выбери сторону"
|
||||
)
|
||||
await callback.message.edit_text(text, reply_markup=_side_keyboard())
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:type")
|
||||
async def go_back_to_type(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
"""Возвращает пользователя на шаг выбора типа ордера или в карточку черновика при редактировании."""
|
||||
data = await state.get_data()
|
||||
|
||||
draft_id = data.get("draft_edit_id")
|
||||
@@ -84,27 +126,31 @@ async def go_back_to_type(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
service = OrderDraftsService()
|
||||
side = data.get("side", "BUY")
|
||||
context = service.get_entry_context(side=side, order_type="MARKET")
|
||||
path = _render_order_path(side=side)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_type)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
f"{path}\n\n"
|
||||
"Шаг 2/4. Выбери тип ордера"
|
||||
)
|
||||
await callback.message.edit_text(text, reply_markup=_type_keyboard())
|
||||
await callback.answer()
|
||||
try:
|
||||
context = service.get_entry_context(side=side, order_type="MARKET")
|
||||
path = _render_order_path(side=side)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_type)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
f"{path}\n\n"
|
||||
"Шаг 2/4. Выбери тип ордера"
|
||||
)
|
||||
await callback.message.edit_text(text, reply_markup=_type_keyboard())
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:quantity")
|
||||
async def go_back_to_quantity(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
"""
|
||||
Возвращает пользователя на шаг выбора количества.
|
||||
Используется как возврат со шага цены.
|
||||
"""
|
||||
service = OrderDraftsService()
|
||||
data = await state.get_data()
|
||||
|
||||
@@ -115,39 +161,47 @@ async def go_back_to_quantity(callback: CallbackQuery, state: FSMContext) -> Non
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
try:
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
if not quantity:
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
if not quantity:
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
exc=exc,
|
||||
draft_page=drafts_page,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:confirm")
|
||||
@@ -173,7 +227,144 @@ async def go_back_from_confirm(callback: CallbackQuery, state: FSMContext) -> No
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
if order_type == "LIMIT":
|
||||
try:
|
||||
if order_type == "LIMIT":
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_price)
|
||||
await callback.message.edit_text(
|
||||
_render_price_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
bid=context.bid_price,
|
||||
ask=context.ask_price,
|
||||
last=context.last_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_price_keyboard(
|
||||
bid=context.bid_price,
|
||||
ask=context.ask_price,
|
||||
last=context.last_price,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title=_screen_title(is_edit_mode),
|
||||
exc=exc,
|
||||
draft_page=drafts_page,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_manual_back:quantity")
|
||||
async def go_back_from_manual_quantity(
|
||||
callback: CallbackQuery,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
service = OrderDraftsService()
|
||||
data = await state.get_data()
|
||||
|
||||
side = data.get("side", "BUY")
|
||||
order_type = data.get("order_type", "MARKET")
|
||||
quantity = data.get("quantity")
|
||||
is_edit_mode = bool(data.get("draft_edit_id"))
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
try:
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
if not quantity:
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title=_screen_title(is_edit_mode),
|
||||
exc=exc,
|
||||
draft_page=drafts_page,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_manual_back:price")
|
||||
async def go_back_from_manual_price(
|
||||
callback: CallbackQuery,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
service = OrderDraftsService()
|
||||
data = await state.get_data()
|
||||
|
||||
side = data.get("side", "BUY")
|
||||
order_type = data.get("order_type", "LIMIT")
|
||||
quantity = data.get("quantity")
|
||||
is_edit_mode = bool(data.get("draft_edit_id"))
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
try:
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
@@ -201,123 +392,10 @@ async def go_back_from_confirm(callback: CallbackQuery, state: FSMContext) -> No
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_manual_back:quantity")
|
||||
async def go_back_from_manual_quantity(
|
||||
callback: CallbackQuery,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
service = OrderDraftsService()
|
||||
data = await state.get_data()
|
||||
|
||||
side = data.get("side", "BUY")
|
||||
order_type = data.get("order_type", "MARKET")
|
||||
quantity = data.get("quantity")
|
||||
is_edit_mode = bool(data.get("draft_edit_id"))
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
if not quantity:
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
await callback.message.edit_text(
|
||||
_render_quantity_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
available_balance=context.available_balance,
|
||||
balance_currency=context.balance_currency,
|
||||
reference_price=context.reference_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_quantity_keyboard(
|
||||
context.quantity_presets,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_manual_back:price")
|
||||
async def go_back_from_manual_price(
|
||||
callback: CallbackQuery,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
service = OrderDraftsService()
|
||||
data = await state.get_data()
|
||||
|
||||
side = data.get("side", "BUY")
|
||||
order_type = data.get("order_type", "LIMIT")
|
||||
quantity = data.get("quantity")
|
||||
is_edit_mode = bool(data.get("draft_edit_id"))
|
||||
draft_page = data.get("draft_edit_page")
|
||||
drafts_page = int(draft_page) if draft_page else None
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
path = _render_order_path(
|
||||
side=side,
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
base_currency=context.base_currency,
|
||||
)
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_price)
|
||||
await callback.message.edit_text(
|
||||
_render_price_step_screen(
|
||||
title=_screen_title(is_edit_mode),
|
||||
symbol=context.symbol,
|
||||
bid=context.bid_price,
|
||||
ask=context.ask_price,
|
||||
last=context.last_price,
|
||||
quote_currency=context.quote_currency,
|
||||
order_path=path,
|
||||
),
|
||||
reply_markup=_price_keyboard(
|
||||
bid=context.bid_price,
|
||||
ask=context.ask_price,
|
||||
last=context.last_price,
|
||||
drafts_page=drafts_page,
|
||||
),
|
||||
)
|
||||
await callback.answer()
|
||||
exc=exc,
|
||||
draft_page=drafts_page,
|
||||
)
|
||||
@@ -12,6 +12,11 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from src.telegram.handlers.trade.new_order_core import DRAFTS_PAGE_SIZE
|
||||
from src.telegram.ui.common import mode_line
|
||||
from src.trading.orders.service import OrderDraftsService
|
||||
from src.integrations.exchange.exceptions import (
|
||||
ExchangeConnectionError,
|
||||
ExchangeError,
|
||||
ExchangeResponseError,
|
||||
)
|
||||
|
||||
|
||||
def _clean_number(value: str | float | None, precision: int | None = None) -> str:
|
||||
@@ -32,26 +37,29 @@ def _clean_number(value: str | float | None, precision: int | None = None) -> st
|
||||
|
||||
|
||||
def _resolve_symbol_assets(symbol: str) -> tuple[str | None, str | None]:
|
||||
service = OrderDraftsService()
|
||||
validation = service.exchange.validate_symbol(symbol)
|
||||
try:
|
||||
service = OrderDraftsService()
|
||||
validation = service.exchange.validate_symbol(symbol)
|
||||
symbol_info = validation.symbol_info
|
||||
|
||||
symbol_info = validation.symbol_info
|
||||
if symbol_info is None:
|
||||
if symbol_info is None:
|
||||
return None, None
|
||||
|
||||
base_currency = (
|
||||
str(symbol_info.base_asset).upper()
|
||||
if getattr(symbol_info, "base_asset", None)
|
||||
else None
|
||||
)
|
||||
quote_currency = (
|
||||
str(symbol_info.quote_asset).upper()
|
||||
if getattr(symbol_info, "quote_asset", None)
|
||||
else None
|
||||
)
|
||||
|
||||
return base_currency, quote_currency
|
||||
except Exception:
|
||||
return None, None
|
||||
|
||||
base_currency = (
|
||||
str(symbol_info.base_asset).upper()
|
||||
if getattr(symbol_info, "base_asset", None)
|
||||
else None
|
||||
)
|
||||
quote_currency = (
|
||||
str(symbol_info.quote_asset).upper()
|
||||
if getattr(symbol_info, "quote_asset", None)
|
||||
else None
|
||||
)
|
||||
|
||||
return base_currency, quote_currency
|
||||
|
||||
|
||||
def _to_decimal(value: str | float | int | None) -> Decimal | None:
|
||||
if value is None:
|
||||
@@ -79,6 +87,52 @@ def _side_badge(side: str) -> str:
|
||||
return "🟢 <b>BUY</b>" if side.upper() == "BUY" else "🔴 <b>SELL</b>"
|
||||
|
||||
|
||||
def _describe_exchange_error(exc: Exception) -> str:
|
||||
text = str(exc).strip()
|
||||
|
||||
if isinstance(exc, ExchangeResponseError) and (
|
||||
"-1021" in text or "doesn't match server time" in text
|
||||
):
|
||||
return (
|
||||
"Не удалось получить данные биржи: время на устройстве "
|
||||
"не синхронизировано со временем биржи. "
|
||||
"Проверь системное время и повтори попытку."
|
||||
)
|
||||
|
||||
if isinstance(exc, ExchangeConnectionError):
|
||||
return (
|
||||
"Не удалось получить данные биржи: таймаут или ошибка сети. "
|
||||
"Попробуй ещё раз через несколько секунд."
|
||||
)
|
||||
|
||||
if isinstance(exc, ExchangeResponseError):
|
||||
return (
|
||||
"Не удалось получить данные биржи: биржа вернула некорректный ответ. "
|
||||
"Попробуй ещё раз через несколько секунд."
|
||||
)
|
||||
|
||||
if isinstance(exc, ExchangeError):
|
||||
return text or "Не удалось получить данные биржи."
|
||||
|
||||
return text or "Не удалось получить данные биржи."
|
||||
|
||||
|
||||
def _render_exchange_error(
|
||||
*,
|
||||
title: str,
|
||||
exc: Exception,
|
||||
) -> str:
|
||||
lines = [
|
||||
title,
|
||||
mode_line().rstrip(),
|
||||
"",
|
||||
"<b>⚠️ Данные биржи временно недоступны</b>",
|
||||
"",
|
||||
_describe_exchange_error(exc),
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# Оценивает минимально допустимое количество по правилу minNotional.
|
||||
def _estimate_min_quantity_by_notional(
|
||||
*,
|
||||
@@ -265,6 +319,27 @@ def _draft_detail_keyboard(draft_id: str, page: int) -> InlineKeyboardMarkup:
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _exchange_error_keyboard(
|
||||
*,
|
||||
back_callback_data: str | None = None,
|
||||
drafts_page: int | None = None,
|
||||
) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
# Кнопка "Назад" (если есть куда возвращаться)
|
||||
if back_callback_data:
|
||||
builder.button(text="⬅️ Назад", callback_data=back_callback_data)
|
||||
|
||||
# Кнопка "К черновикам" (если мы в edit flow)
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(2 if back_callback_data else 1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _format_value_with_currency(
|
||||
value: str | float | None,
|
||||
currency: str | None,
|
||||
@@ -938,7 +1013,11 @@ def _render_order_card(
|
||||
|
||||
quantity_text = _format_value_with_asset(quantity, base_currency)
|
||||
price_text = _format_value_with_currency(price, quote_currency) if price else None
|
||||
notional_text = _format_value_with_currency(notional, quote_currency) if notional is not None else None
|
||||
notional_text = (
|
||||
_format_value_with_currency(notional, quote_currency)
|
||||
if notional is not None
|
||||
else None
|
||||
)
|
||||
|
||||
lines = [
|
||||
f"<b>{symbol}</b>",
|
||||
|
||||
Reference in New Issue
Block a user