Stage 05.2+ - advanced draft builder (FSM, UI, pagination)
This commit is contained in:
@@ -1,18 +1,104 @@
|
||||
# app/src/telegram/handlers/trade/main.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import Message
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.telegram.handlers.trade.new_order import (
|
||||
show_recent_drafts,
|
||||
start_new_order_draft,
|
||||
)
|
||||
|
||||
router = Router(name="trade_main")
|
||||
|
||||
|
||||
def _trade_home_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="📝 Новый ордер", callback_data="trade:new_order")
|
||||
builder.button(text="📂 Черновики", callback_data="trade:drafts")
|
||||
builder.button(text="⚙️ Настройки ордера", callback_data="trade:settings")
|
||||
builder.button(text="ℹ️ Справка", callback_data="trade:help")
|
||||
builder.adjust(2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _trade_back_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⬅️ К торговле", callback_data="trade:home")
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _trade_home_text() -> str:
|
||||
return (
|
||||
"<b>⚡ Торговля</b>\n\n"
|
||||
"<b>‼️ Режим черновика</b>"
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "⚡ Торговля")
|
||||
async def open_trade(message: Message) -> None:
|
||||
text = (
|
||||
"<b>⚡ Торговля</b>\n\n"
|
||||
"Доступные действия:\n"
|
||||
"• /new_order — создать черновик ордера\n"
|
||||
"• /drafts — показать последние черновики\n\n"
|
||||
"На этом этапе реальные ордера ещё не отправляются."
|
||||
await message.answer(
|
||||
_trade_home_text(),
|
||||
reply_markup=_trade_home_keyboard(),
|
||||
)
|
||||
await message.answer(text)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "trade:home")
|
||||
async def open_trade_home_callback(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
_trade_home_text(),
|
||||
reply_markup=_trade_home_keyboard(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "trade:new_order")
|
||||
async def open_new_order_from_trade(
|
||||
callback: CallbackQuery,
|
||||
state: FSMContext,
|
||||
) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await start_new_order_draft(callback.message, state, edit_mode=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "trade:drafts")
|
||||
async def open_drafts_from_trade(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await show_recent_drafts(callback.message, edit_mode=True, page=1)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "trade:settings")
|
||||
async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля — Настройки ордера</b>\n\n"
|
||||
"Раздел в разработке.\n\n"
|
||||
"Планируется добавить:\n"
|
||||
"• параметры ордера по умолчанию\n"
|
||||
"• пресеты количества\n"
|
||||
"• режим цены: Bid / Ask / Last",
|
||||
reply_markup=_trade_back_keyboard(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "trade:help")
|
||||
async def open_trade_help(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля — Справка</b>\n\n"
|
||||
"<b>Режим черновика</b> — ордер не отправляется на биржу.\n\n"
|
||||
"Сейчас можно:\n"
|
||||
"• собрать черновик ордера\n"
|
||||
"• проверить параметры\n"
|
||||
"• сохранить черновик в базу\n\n"
|
||||
"Реальная отправка ордера появится позже.",
|
||||
reply_markup=_trade_back_keyboard(),
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
# app/src/telegram/handlers/trade/new_order.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogram import F, Router
|
||||
@@ -12,13 +14,16 @@ from src.trading.orders.states import NewOrderDraftStates
|
||||
|
||||
router = Router(name="trade_new_order")
|
||||
|
||||
DRAFTS_PAGE_SIZE = 3
|
||||
|
||||
|
||||
def _side_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🟢 BUY", callback_data="order_side:BUY")
|
||||
builder.button(text="🔴 SELL", callback_data="order_side:SELL")
|
||||
builder.button(text="⬅️ Назад", callback_data="trade:home")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2, 1)
|
||||
builder.adjust(2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -26,14 +31,9 @@ def _type_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⚡ MARKET", callback_data="order_type:MARKET")
|
||||
builder.button(text="🎯 LIMIT", callback_data="order_type:LIMIT")
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:side")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _cancel_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -45,8 +45,17 @@ def _quantity_keyboard(presets: list[str]) -> InlineKeyboardMarkup:
|
||||
builder.button(text=label, callback_data=f"order_qty:{value}")
|
||||
|
||||
builder.button(text="✍️ Ввести вручную", callback_data="order_qty:manual")
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:type")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2, 2, 1)
|
||||
builder.adjust(2, 2, 1, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _quantity_manual_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:type")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -56,8 +65,47 @@ def _price_keyboard(bid: float, ask: float, last: float) -> InlineKeyboardMarkup
|
||||
builder.button(text=f"Ask {ask:.2f}", callback_data=f"order_price:{ask}")
|
||||
builder.button(text=f"Last {last:.2f}", callback_data=f"order_price:{last}")
|
||||
builder.button(text="✍️ Ввести вручную", callback_data="order_price:manual")
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:quantity")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2, 1, 1, 1)
|
||||
builder.adjust(2, 2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _price_manual_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:quantity")
|
||||
builder.button(text="✖️ Отмена", callback_data="order_cancel")
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _trade_back_home_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⬅️ К торговле", callback_data="trade:home")
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _drafts_keyboard(page: int, total_pages: int) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if page > 1:
|
||||
builder.button(text="⏮️", callback_data="drafts:1")
|
||||
builder.button(text="⬅️", callback_data=f"drafts:{page - 1}")
|
||||
|
||||
builder.button(text=f"{page}/{total_pages}", callback_data="drafts:noop")
|
||||
|
||||
if page < total_pages:
|
||||
builder.button(text="➡️", callback_data=f"drafts:{page + 1}")
|
||||
|
||||
first_row_count = 1
|
||||
if page > 1:
|
||||
first_row_count += 2
|
||||
if page < total_pages:
|
||||
first_row_count += 1
|
||||
|
||||
builder.button(text="⬅️ К торговле", callback_data="trade:home")
|
||||
builder.adjust(first_row_count, 1)
|
||||
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -69,7 +117,9 @@ def _render_draft_summary(
|
||||
price: str | None,
|
||||
) -> str:
|
||||
lines = [
|
||||
"<b>📝 Черновик ордера создан</b>",
|
||||
"<b>⚡ Торговля — Черновик ордера</b>",
|
||||
"",
|
||||
"<b>📝 Черновик создан</b>",
|
||||
"",
|
||||
f"• инструмент: {symbol}",
|
||||
f"• сторона: {side}",
|
||||
@@ -82,12 +132,110 @@ def _render_draft_summary(
|
||||
[
|
||||
"• статус: draft",
|
||||
"",
|
||||
"Это тестовый draft flow. Реальный ордер не отправлялся.",
|
||||
"<i>Ордер не отправлялся на биржу</i>",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _render_validation_error(errors: list[str]) -> str:
|
||||
lines = [
|
||||
"<b>⚡ Торговля — Ошибка валидации</b>",
|
||||
"",
|
||||
"<b>❌ Черновик не сохранён</b>",
|
||||
"",
|
||||
"<b>Причины</b>",
|
||||
"",
|
||||
]
|
||||
for item in errors:
|
||||
lines.append(f"• {item}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_draft_time(value: str) -> str:
|
||||
text = str(value).replace("T", " ")
|
||||
if "+" in text:
|
||||
text = text.split("+", 1)[0]
|
||||
if "." in text:
|
||||
text = text.split(".", 1)[0]
|
||||
return text
|
||||
|
||||
|
||||
def _format_draft_quantity(value: str) -> str:
|
||||
text = str(value).rstrip("0").rstrip(".")
|
||||
return text or "0"
|
||||
|
||||
|
||||
async def show_recent_drafts(
|
||||
message: Message,
|
||||
edit_mode: bool = False,
|
||||
page: int = 1,
|
||||
) -> None:
|
||||
service = OrderDraftsService()
|
||||
all_drafts = service.list_recent_drafts(limit=100)
|
||||
|
||||
total = len(all_drafts)
|
||||
total_pages = max(1, (total + DRAFTS_PAGE_SIZE - 1) // DRAFTS_PAGE_SIZE)
|
||||
page = max(1, min(page, total_pages))
|
||||
|
||||
start = (page - 1) * DRAFTS_PAGE_SIZE
|
||||
end = start + DRAFTS_PAGE_SIZE
|
||||
drafts = all_drafts[start:end]
|
||||
|
||||
if not drafts:
|
||||
text = (
|
||||
"<b>⚡ Торговля — Черновики</b>\n\n"
|
||||
"Черновиков пока нет."
|
||||
)
|
||||
if edit_mode:
|
||||
await message.edit_text(text, reply_markup=_trade_back_home_keyboard())
|
||||
else:
|
||||
await message.answer(text)
|
||||
return
|
||||
|
||||
lines = ["<b>⚡ Торговля — Черновики</b>", "", "<b>Последние записи</b>", ""]
|
||||
|
||||
for item in drafts:
|
||||
quantity = _format_draft_quantity(item["quantity"])
|
||||
created_at = _format_draft_time(item["created_at"])
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
f"<b>{item['symbol']}</b> · {item['side']} · {item['order_type']}",
|
||||
f"• количество: {quantity}",
|
||||
f"• статус: {item['status']}",
|
||||
f"• время: {created_at}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
text = "\n".join(lines).rstrip()
|
||||
keyboard = _drafts_keyboard(page, total_pages)
|
||||
|
||||
if edit_mode:
|
||||
await message.edit_text(text, reply_markup=keyboard)
|
||||
else:
|
||||
await message.answer(text, reply_markup=keyboard)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "drafts:noop")
|
||||
async def drafts_noop(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("drafts:"))
|
||||
async def paginate_drafts(callback: CallbackQuery) -> None:
|
||||
value = callback.data.split(":", 1)[1]
|
||||
if value == "noop":
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
page = int(value)
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await show_recent_drafts(callback.message, edit_mode=True, page=page)
|
||||
|
||||
|
||||
@router.message(Command("cancel_order"))
|
||||
async def cancel_order_builder(message: Message, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
@@ -105,21 +253,77 @@ async def cancel_order_builder_callback(
|
||||
await state.clear()
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля</b>\n\n"
|
||||
"Создание черновика отменено."
|
||||
"Создание черновика отменено.",
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.message(Command("new_order"))
|
||||
async def start_new_order_draft(message: Message, state: FSMContext) -> None:
|
||||
async def start_new_order_draft(
|
||||
message: Message,
|
||||
state: FSMContext,
|
||||
edit_mode: bool = False,
|
||||
) -> None:
|
||||
await state.clear()
|
||||
await state.set_state(NewOrderDraftStates.waiting_side)
|
||||
await message.answer(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
|
||||
text = (
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 1/4\n"
|
||||
"Выберите сторону:"
|
||||
)
|
||||
|
||||
if edit_mode:
|
||||
await message.edit_text(text, reply_markup=_side_keyboard())
|
||||
else:
|
||||
await message.answer(text, reply_markup=_side_keyboard())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:side")
|
||||
async def go_back_to_side(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.set_state(NewOrderDraftStates.waiting_side)
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 1/4\n"
|
||||
"Выберите сторону:",
|
||||
reply_markup=_side_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "order_back:type")
|
||||
async def go_back_to_type(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.set_state(NewOrderDraftStates.waiting_type)
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 2/4\n"
|
||||
"Выберите тип ордера:",
|
||||
reply_markup=_type_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@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()
|
||||
side = data.get("side", "BUY")
|
||||
order_type = data.get("order_type", "MARKET")
|
||||
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
await state.set_state(NewOrderDraftStates.waiting_quantity)
|
||||
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 3/4\n"
|
||||
f"Инструмент: <b>{context.symbol}</b>\n"
|
||||
f"Доступно: <b>{context.available_balance:.8f} {context.balance_currency}</b>\n"
|
||||
f"Ориентир цены: <b>{context.reference_price:.2f}</b>\n\n"
|
||||
"Выберите количество или введите его вручную:",
|
||||
reply_markup=_quantity_keyboard(context.quantity_presets),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(
|
||||
@@ -136,7 +340,7 @@ async def process_order_side_callback(
|
||||
await state.set_state(NewOrderDraftStates.waiting_type)
|
||||
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 2/4\n"
|
||||
"Выберите тип ордера:",
|
||||
reply_markup=_type_keyboard(),
|
||||
@@ -172,7 +376,7 @@ async def process_order_type_callback(
|
||||
context = service.get_entry_context(side=side, order_type=order_type)
|
||||
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 3/4\n"
|
||||
f"Инструмент: <b>{context.symbol}</b>\n"
|
||||
f"Доступно: <b>{context.available_balance:.8f} {context.balance_currency}</b>\n"
|
||||
@@ -203,10 +407,10 @@ async def process_quantity_callback(
|
||||
|
||||
if value == "manual":
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 3/4\n"
|
||||
"Введите количество вручную, например: <b>0.001</b>",
|
||||
reply_markup=_cancel_keyboard(),
|
||||
reply_markup=_quantity_manual_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
@@ -226,7 +430,7 @@ async def process_quantity_callback(
|
||||
context = service.get_entry_context(side=data["side"], order_type=order_type)
|
||||
await state.set_state(NewOrderDraftStates.waiting_price)
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 4/4\n"
|
||||
f"Bid: <b>{context.bid_price:.2f}</b>\n"
|
||||
f"Ask: <b>{context.ask_price:.2f}</b>\n"
|
||||
@@ -246,9 +450,19 @@ async def process_quantity_callback(
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
)
|
||||
service.save_draft(draft)
|
||||
await state.clear()
|
||||
try:
|
||||
service.save_draft(draft)
|
||||
except ValueError as exc:
|
||||
await state.clear()
|
||||
errors = [item.strip() for item in str(exc).split(";") if item.strip()]
|
||||
await callback.message.edit_text(
|
||||
_render_validation_error(errors),
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
await state.clear()
|
||||
await callback.message.edit_text(
|
||||
_render_draft_summary(
|
||||
symbol=draft.symbol,
|
||||
@@ -256,7 +470,8 @@ async def process_quantity_callback(
|
||||
order_type=draft.order_type,
|
||||
quantity=draft.quantity,
|
||||
price=draft.price,
|
||||
)
|
||||
),
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -278,7 +493,7 @@ async def process_order_quantity(message: Message, state: FSMContext) -> None:
|
||||
context = service.get_entry_context(side=data["side"], order_type=order_type)
|
||||
await state.set_state(NewOrderDraftStates.waiting_price)
|
||||
await message.answer(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 4/4\n"
|
||||
f"Bid: <b>{context.bid_price:.2f}</b>\n"
|
||||
f"Ask: <b>{context.ask_price:.2f}</b>\n"
|
||||
@@ -297,9 +512,15 @@ async def process_order_quantity(message: Message, state: FSMContext) -> None:
|
||||
order_type=order_type,
|
||||
quantity=quantity,
|
||||
)
|
||||
service.save_draft(draft)
|
||||
await state.clear()
|
||||
try:
|
||||
service.save_draft(draft)
|
||||
except ValueError as exc:
|
||||
await state.clear()
|
||||
errors = [item.strip() for item in str(exc).split(";") if item.strip()]
|
||||
await message.answer(_render_validation_error(errors))
|
||||
return
|
||||
|
||||
await state.clear()
|
||||
await message.answer(
|
||||
_render_draft_summary(
|
||||
symbol=draft.symbol,
|
||||
@@ -323,10 +544,10 @@ async def process_price_callback(
|
||||
|
||||
if value == "manual":
|
||||
await callback.message.edit_text(
|
||||
"<b>⚡ Новый черновик ордера</b>\n\n"
|
||||
"<b>⚡ Торговля — Новый ордер</b>\n\n"
|
||||
"Шаг 4/4\n"
|
||||
"Введите цену вручную, например: <b>73000</b>",
|
||||
reply_markup=_cancel_keyboard(),
|
||||
"Введите цену вручную, например: <b>73000.123</b>",
|
||||
reply_markup=_price_manual_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
@@ -344,9 +565,19 @@ async def process_price_callback(
|
||||
quantity=data["quantity"],
|
||||
price=price,
|
||||
)
|
||||
service.save_draft(draft)
|
||||
await state.clear()
|
||||
try:
|
||||
service.save_draft(draft)
|
||||
except ValueError as exc:
|
||||
await state.clear()
|
||||
errors = [item.strip() for item in str(exc).split(";") if item.strip()]
|
||||
await callback.message.edit_text(
|
||||
_render_validation_error(errors),
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
await state.clear()
|
||||
await callback.message.edit_text(
|
||||
_render_draft_summary(
|
||||
symbol=draft.symbol,
|
||||
@@ -354,7 +585,8 @@ async def process_price_callback(
|
||||
order_type=draft.order_type,
|
||||
quantity=draft.quantity,
|
||||
price=draft.price,
|
||||
)
|
||||
),
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@@ -364,7 +596,7 @@ async def process_order_price(message: Message, state: FSMContext) -> None:
|
||||
service = OrderDraftsService()
|
||||
price = service.normalize_price(message.text or "")
|
||||
if price is None:
|
||||
await message.answer("Введите корректную цену, например: 73000")
|
||||
await message.answer("Введите корректную цену, например: 73000.123")
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
@@ -374,9 +606,15 @@ async def process_order_price(message: Message, state: FSMContext) -> None:
|
||||
quantity=data["quantity"],
|
||||
price=price,
|
||||
)
|
||||
service.save_draft(draft)
|
||||
await state.clear()
|
||||
try:
|
||||
service.save_draft(draft)
|
||||
except ValueError as exc:
|
||||
await state.clear()
|
||||
errors = [item.strip() for item in str(exc).split(";") if item.strip()]
|
||||
await message.answer(_render_validation_error(errors))
|
||||
return
|
||||
|
||||
await state.clear()
|
||||
await message.answer(
|
||||
_render_draft_summary(
|
||||
symbol=draft.symbol,
|
||||
@@ -389,27 +627,5 @@ async def process_order_price(message: Message, state: FSMContext) -> None:
|
||||
|
||||
|
||||
@router.message(Command("drafts"))
|
||||
async def show_recent_drafts(message: Message) -> None:
|
||||
service = OrderDraftsService()
|
||||
drafts = service.list_recent_drafts(limit=5)
|
||||
|
||||
if not drafts:
|
||||
await message.answer(
|
||||
"<b>📝 Черновики ордеров</b>\n\n"
|
||||
"Черновиков пока нет."
|
||||
)
|
||||
return
|
||||
|
||||
lines = ["<b>📝 Черновики ордеров</b>", "", "<b>Последние записи</b>", ""]
|
||||
|
||||
for item in drafts:
|
||||
lines.extend(
|
||||
[
|
||||
f"• {item['symbol']} | {item['side']} | {item['order_type']}",
|
||||
f" qty: {item['quantity']} | status: {item['status']}",
|
||||
f" time: {item['created_at']}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
await message.answer("\n".join(lines).rstrip())
|
||||
async def drafts_command(message: Message) -> None:
|
||||
await show_recent_drafts(message, edit_mode=False, page=1)
|
||||
Reference in New Issue
Block a user