diff --git a/app/src/telegram/handlers/trade/main.py b/app/src/telegram/handlers/trade/main.py
index 8970163..164b5cd 100644
--- a/app/src/telegram/handlers/trade/main.py
+++ b/app/src/telegram/handlers/trade/main.py
@@ -15,30 +15,90 @@ from src.telegram.handlers.trade.new_order import (
router = Router(name="trade_main")
+def _mode_line() -> str:
+ from src.core.system_status import get_runtime_mode_label
+ return f"Режим: {get_runtime_mode_label()}\n\n"
+
+
+def _trade_screen(title: str) -> str:
+ return (
+ f"📊 Торговля — {title}\n"
+ f"{_mode_line()}"
+ "Выбери раздел"
+ )
+
+
+# =========================
+# KEYBOARDS
+# =========================
+
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.button(text="📝 Ордер", callback_data="trade:new_order")
+ builder.button(text="📂 Ордера", callback_data="trade:orders")
+ builder.button(text="📜 История", callback_data="trade:history")
+ builder.button(text="⚙️ Настройки", callback_data="trade:settings")
builder.adjust(2, 2)
return builder.as_markup()
-def _trade_back_keyboard() -> InlineKeyboardMarkup:
+def _trade_home_button() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- builder.button(text="⬅️ К торговле", callback_data="trade:home")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
return builder.as_markup()
+def _orders_menu_keyboard() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="📂 Черновики", callback_data="trade:orders:drafts")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+ builder.adjust(2)
+ return builder.as_markup()
+
+
+def _history_menu_keyboard() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="✅ Исполненные", callback_data="trade:history:filled")
+ builder.button(text="🚫 Отменённые", callback_data="trade:history:canceled")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+ builder.adjust(2, 1)
+ return builder.as_markup()
+
+
+def _settings_menu_keyboard() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⚙️ Параметры", callback_data="trade:settings:params")
+ builder.button(text="🔁 Режим", callback_data="trade:settings:mode")
+ builder.button(text="ℹ️ Справка", callback_data="trade:settings:help")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+ builder.adjust(2, 2)
+ return builder.as_markup()
+
+
+# =========================
+# TEXTS
+# =========================
+
def _trade_home_text() -> str:
- return (
- "⚡ Торговля\n\n"
- "‼️ Режим черновика"
- )
+ return _trade_screen("Основной экран")
+
+def _trade_orders_text() -> str:
+ return _trade_screen("Ордера")
-@router.message(F.text == "⚡ Торговля")
+def _trade_history_text() -> str:
+ return _trade_screen("История")
+
+
+def _trade_settings_text() -> str:
+ return _trade_screen("Настройки")
+
+
+# =========================
+# ENTRY
+# =========================
+
+@router.message(F.text.in_({"📊 Торговля", "⚡ Торговля", "Торговля"}))
async def open_trade(message: Message) -> None:
await message.answer(
_trade_home_text(),
@@ -47,8 +107,13 @@ async def open_trade(message: Message) -> None:
@router.callback_query(F.data == "trade:home")
-async def open_trade_home_callback(callback: CallbackQuery) -> None:
+async def open_trade_home_callback(
+ callback: CallbackQuery,
+ state: FSMContext,
+) -> None:
+ await state.clear()
await callback.answer()
+
if callback.message is not None:
await callback.message.edit_text(
_trade_home_text(),
@@ -56,6 +121,10 @@ async def open_trade_home_callback(callback: CallbackQuery) -> None:
)
+# =========================
+# NEW ORDER
+# =========================
+
@router.callback_query(F.data == "trade:new_order")
async def open_new_order_from_trade(
callback: CallbackQuery,
@@ -66,39 +135,110 @@ async def open_new_order_from_trade(
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:
+# =========================
+# ORDERS
+# =========================
+
+@router.callback_query(F.data == "trade:orders")
+async def open_orders_from_trade(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ _trade_orders_text(),
+ reply_markup=_orders_menu_keyboard(),
+ )
+
+
+@router.callback_query(F.data == "trade:orders:drafts")
+async def open_drafts_from_orders(callback: CallbackQuery) -> None:
await callback.answer()
if callback.message is not None:
await show_recent_drafts(callback.message, edit_mode=True, page=1)
+# =========================
+# HISTORY
+# =========================
+
+@router.callback_query(F.data == "trade:history")
+async def open_trade_history(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ _trade_history_text(),
+ reply_markup=_history_menu_keyboard(),
+ )
+
+
+@router.callback_query(F.data == "trade:history:filled")
+async def open_filled_history(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ "📊 Торговля — История\n\n"
+ "Шаг 1/1: Исполненные\n"
+ "Раздел в разработке.",
+ reply_markup=_trade_home_button(),
+ )
+
+
+@router.callback_query(F.data == "trade:history:canceled")
+async def open_canceled_history(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ "📊 Торговля — История\n\n"
+ "Шаг 1/1: Отменённые\n"
+ "Раздел в разработке.",
+ reply_markup=_trade_home_button(),
+ )
+
+
+# =========================
+# SETTINGS
+# =========================
+
@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(
- "⚡ Торговля — Настройки ордера\n\n"
- "Раздел в разработке.\n\n"
- "Планируется добавить:\n"
- "• параметры ордера по умолчанию\n"
- "• пресеты количества\n"
- "• режим цены: Bid / Ask / Last",
- reply_markup=_trade_back_keyboard(),
+ _trade_settings_text(),
+ reply_markup=_settings_menu_keyboard(),
)
-@router.callback_query(F.data == "trade:help")
-async def open_trade_help(callback: CallbackQuery) -> None:
+@router.callback_query(F.data == "trade:settings:params")
+async def open_trade_settings_params(callback: CallbackQuery) -> None:
await callback.answer()
if callback.message is not None:
await callback.message.edit_text(
- "⚡ Торговля — Справка\n\n"
- "Режим черновика — ордер не отправляется на биржу.\n\n"
- "Сейчас можно:\n"
- "• собрать черновик ордера\n"
- "• проверить параметры\n"
- "• сохранить черновик в базу\n\n"
- "Реальная отправка ордера появится позже.",
- reply_markup=_trade_back_keyboard(),
+ "📊 Торговля — Настройки\n\n"
+ "Шаг 1/1: Параметры ордера\n"
+ "Раздел в разработке.",
+ reply_markup=_trade_home_button(),
+ )
+
+
+@router.callback_query(F.data == "trade:settings:mode")
+async def open_trade_settings_mode(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ "📊 Торговля — Настройки\n\n"
+ "Шаг 1/1: Режим работы\n"
+ "Текущий режим: demo",
+ reply_markup=_trade_home_button(),
+ )
+
+
+@router.callback_query(F.data == "trade:settings:help")
+async def open_trade_settings_help(callback: CallbackQuery) -> None:
+ await callback.answer()
+ if callback.message is not None:
+ await callback.message.edit_text(
+ "📊 Торговля — Справка\n\n"
+ "Шаг 1/1: Информация\n"
+ "Раздел в разработке.",
+ reply_markup=_trade_home_button(),
)
\ No newline at end of file
diff --git a/app/src/telegram/handlers/trade/new_order.py b/app/src/telegram/handlers/trade/new_order.py
index 911a061..398a33d 100644
--- a/app/src/telegram/handlers/trade/new_order.py
+++ b/app/src/telegram/handlers/trade/new_order.py
@@ -2,6 +2,9 @@
from __future__ import annotations
+from datetime import datetime
+from zoneinfo import ZoneInfo
+
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
@@ -21,9 +24,8 @@ 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, 2)
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+ builder.adjust(2, 1)
return builder.as_markup()
@@ -32,29 +34,45 @@ def _type_keyboard() -> InlineKeyboardMarkup:
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.button(text="🏠 К торговле", callback_data="trade:home")
builder.adjust(2, 2)
return builder.as_markup()
+def _mode_line() -> str:
+ from src.core.system_status import get_runtime_mode_label
+ return f"Режим: {get_runtime_mode_label()}\n\n"
+
+
def _quantity_keyboard(presets: list[str]) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- labels = ["25%", "50%", "75%", "100%"]
+
+ all_labels = ["1%", "5%", "10%", "25%", "50%", "100%"]
+ labels = all_labels[: len(presets)]
for label, value in zip(labels, presets):
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, 2)
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+
+ if len(presets) == 0:
+ builder.adjust(1, 2)
+ elif len(presets) <= 4:
+ builder.adjust(2, 2, 1, 2)
+ elif len(presets) == 5:
+ builder.adjust(3, 2, 1, 2)
+ else:
+ builder.adjust(3, 3, 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.button(text="⬅️ Назад", callback_data="order_manual_back:quantity")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
builder.adjust(2)
return builder.as_markup()
@@ -66,26 +84,41 @@ def _price_keyboard(bid: float, ask: float, last: float) -> InlineKeyboardMarkup
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.button(text="🏠 К торговле", callback_data="trade:home")
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.button(text="⬅️ Назад", callback_data="order_manual_back:price")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
builder.adjust(2)
return builder.as_markup()
+def _confirm_keyboard() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="✅ Подтвердить", callback_data="order_confirm")
+ builder.button(text="⬅️ Назад", callback_data="order_back:confirm")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
+ builder.adjust(1, 2)
+ return builder.as_markup()
+
+
def _trade_back_home_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
- builder.button(text="⬅️ К торговле", callback_data="trade:home")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
return builder.as_markup()
-def _drafts_keyboard(page: int, total_pages: int) -> InlineKeyboardMarkup:
+def _drafts_back_keyboard(page: int) -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ К черновикам", callback_data=f"drafts:{page}")
+ return builder.as_markup()
+
+
+def _drafts_pagination_keyboard(page: int, total_pages: int) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
if page > 1:
@@ -103,12 +136,21 @@ def _drafts_keyboard(page: int, total_pages: int) -> InlineKeyboardMarkup:
if page < total_pages:
first_row_count += 1
- builder.button(text="⬅️ К торговле", callback_data="trade:home")
+ builder.button(text="🏠 К торговле", callback_data="trade:home")
builder.adjust(first_row_count, 1)
return builder.as_markup()
+def _draft_detail_keyboard(draft_id: str, page: int) -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="✏️ Редактировать", callback_data=f"draft_edit:{draft_id}:{page}")
+ builder.button(text="🗑 Удалить", callback_data=f"draft_delete:{draft_id}")
+ builder.button(text="⬅️ К черновикам", callback_data=f"drafts:{page}")
+ builder.adjust(2, 1)
+ return builder.as_markup()
+
+
def _render_draft_summary(
symbol: str,
side: str,
@@ -117,20 +159,23 @@ def _render_draft_summary(
price: str | None,
) -> str:
lines = [
- "⚡ Торговля — Черновик ордера",
+ "📊 Торговля — Черновик ордера",
+ _mode_line().rstrip(),
"",
- "📝 Черновик создан",
- "",
- f"• инструмент: {symbol}",
- f"• сторона: {side}",
- f"• тип: {order_type}",
- f"• количество: {quantity}",
+ f"Инструмент: {symbol}",
+ f"Сторона: {side}",
+ f"Тип: {order_type}",
+ f"Количество: {quantity}",
]
+
if price:
- lines.append(f"• цена: {price}")
+ lines.append(f"Цена: {price}")
+
lines.extend(
[
- "• статус: draft",
+ "Статус: draft",
+ "",
+ "✅ Черновик создан",
"",
"Ордер не отправлялся на биржу",
]
@@ -138,27 +183,154 @@ def _render_draft_summary(
return "\n".join(lines)
+def _render_confirm(
+ symbol: str,
+ side: str,
+ order_type: str,
+ quantity: str,
+ price: str | None,
+ notional: float | None,
+ is_edit_mode: bool = False,
+) -> str:
+ lines = [
+ _screen_title(is_edit_mode),
+ _mode_line().rstrip(),
+ "",
+ f"Инструмент: {symbol}",
+ f"Сторона: {side}",
+ f"Тип: {order_type}",
+ f"Количество: {quantity}",
+ ]
+
+ if price:
+ lines.append(f"Цена: {price}")
+
+ if notional is not None:
+ lines.append(f"Сумма: {notional:.2f}")
+
+ lines.extend(
+ [
+ "",
+ "Шаг 4/4. Подтверди черновик",
+ ]
+ )
+
+ return "\n".join(lines)
+
+
def _render_validation_error(errors: list[str]) -> str:
lines = [
- "⚡ Торговля — Ошибка валидации",
+ "📊 Торговля — Ошибка валидации",
+ _mode_line().rstrip(),
+ "Шаг 4/4. Проверь параметры черновика",
"",
"❌ Черновик не сохранён",
"",
- "Причины",
- "",
]
for item in errors:
lines.append(f"• {item}")
return "\n".join(lines)
+def _render_inline_error(
+ title: str,
+ step_text: str,
+ errors: list[str],
+ help_text: str | None = None,
+) -> str:
+ lines = [
+ title,
+ _mode_line().rstrip(),
+ step_text,
+ "",
+ "⚠️ Найдены ошибки",
+ "",
+ ]
+
+ for item in errors:
+ lines.append(f"• {item}")
+
+ if help_text:
+ lines.extend(["", help_text])
+
+ return "\n".join(lines)
+
+
+def _render_quantity_input_help(
+ *,
+ min_qty: str | None,
+ step_size: str | None,
+ min_notional: str | None,
+ example: str,
+) -> str:
+ lines = [
+ "📏 Правила ввода количества",
+ "",
+ ]
+
+ if min_qty:
+ lines.append(f"• минимум: {min_qty}")
+ if step_size:
+ lines.append(f"• шаг: {step_size}")
+ if min_notional:
+ lines.append(f"• мин. сумма: {min_notional}")
+
+ lines.extend(["", f"Пример: {example}"])
+ return "\n".join(lines)
+
+
+def _render_price_input_help(
+ *,
+ tick_size: str | None,
+ example: str,
+) -> str:
+ lines = [
+ "📏 Правила ввода цены",
+ "",
+ ]
+
+ if tick_size:
+ lines.append(f"• шаг цены: {tick_size}")
+
+ lines.extend(["", f"Пример: {example}"])
+ return "\n".join(lines)
+
+
+def _render_draft_detail(draft: dict[str, str]) -> str:
+ quantity = _format_draft_quantity(draft["quantity"])
+ created_at = _format_draft_time(draft["created_at"])
+
+ lines = [
+ "📊 Торговля — Черновик",
+ _mode_line().rstrip(),
+ f"Инструмент: {draft['symbol']}",
+ f"Сторона: {draft['side']}",
+ f"Тип: {draft['order_type']}",
+ f"Количество: {quantity}",
+ ]
+
+ if draft.get("price"):
+ lines.append(f"Цена: {draft['price']}")
+
+ lines.extend(
+ [
+ f"Статус: {draft['status']}",
+ f"Время: {created_at}",
+ ]
+ )
+
+ 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
+ try:
+ dt = datetime.fromisoformat(str(value))
+ if dt.tzinfo is None:
+ dt = dt.replace(tzinfo=ZoneInfo("UTC"))
+ local_dt = dt.astimezone(ZoneInfo("Europe/Minsk"))
+ return local_dt.strftime("%Y-%m-%d %H:%M:%S")
+ except Exception:
+ return str(value)
def _format_draft_quantity(value: str) -> str:
@@ -166,6 +338,12 @@ def _format_draft_quantity(value: str) -> str:
return text or "0"
+def _screen_title(is_edit_mode: bool) -> str:
+ if is_edit_mode:
+ return "📊 Торговля — Редактирование черновика"
+ return "📊 Торговля — Новый ордер"
+
+
async def show_recent_drafts(
message: Message,
edit_mode: bool = False,
@@ -182,18 +360,28 @@ async def show_recent_drafts(
end = start + DRAFTS_PAGE_SIZE
drafts = all_drafts[start:end]
+ # --- если нет черновиков ---
if not drafts:
text = (
- "⚡ Торговля — Черновики\n\n"
+ "📊 Торговля — Черновики\n"
+ f"{_mode_line()}"
+ "Список пуст\n\n"
"Черновиков пока нет."
)
if edit_mode:
await message.edit_text(text, reply_markup=_trade_back_home_keyboard())
else:
- await message.answer(text)
+ await message.answer(text, reply_markup=_trade_back_home_keyboard())
return
- lines = ["⚡ Торговля — Черновики", "", "Последние записи", ""]
+ # --- список черновиков ---
+ lines = [
+ "📊 Торговля — Черновики",
+ _mode_line().rstrip(),
+ "",
+ ]
+
+ details_builder = InlineKeyboardBuilder()
for item in drafts:
quantity = _format_draft_quantity(item["quantity"])
@@ -201,16 +389,28 @@ async def show_recent_drafts(
lines.extend(
[
- f"{item['symbol']} · {item['side']} · {item['order_type']}",
- f"• количество: {quantity}",
- f"• статус: {item['status']}",
- f"• время: {created_at}",
+ f"{item['symbol']}",
+ f"{item['side']} · {item['order_type']}",
+ f"Количество: {quantity}",
+ f"Статус: {item['status']}",
+ f"Время: {created_at}",
"",
]
)
+ details_builder.button(
+ text=f"📄 {item['symbol']} {item['side']}",
+ callback_data=f"draft_open:{item['id']}:{page}",
+ )
+
+ details_builder.adjust(1)
+
+ # пагинация + кнопка домой
+ pagination_markup = _drafts_pagination_keyboard(page, total_pages)
+ details_builder.attach(InlineKeyboardBuilder.from_markup(pagination_markup))
+
text = "\n".join(lines).rstrip()
- keyboard = _drafts_keyboard(page, total_pages)
+ keyboard = details_builder.as_markup()
if edit_mode:
await message.edit_text(text, reply_markup=keyboard)
@@ -236,27 +436,102 @@ async def paginate_drafts(callback: CallbackQuery) -> None:
await show_recent_drafts(callback.message, edit_mode=True, page=page)
+@router.callback_query(F.data.startswith("draft_open:"))
+async def open_draft(callback: CallbackQuery) -> None:
+ _, draft_id, page_raw = callback.data.split(":", 2)
+ page = int(page_raw)
+
+ service = OrderDraftsService()
+ draft = service.get_draft_by_id(draft_id)
+
+ if not draft:
+ await callback.answer("Черновик не найден", show_alert=True)
+ return
+
+ await callback.message.edit_text(
+ _render_draft_detail(draft),
+ reply_markup=_draft_detail_keyboard(draft_id, page),
+ )
+ await callback.answer()
+
+
+@router.callback_query(F.data.startswith("draft_edit:"))
+async def edit_draft(callback: CallbackQuery, state: FSMContext) -> None:
+ _, draft_id, page_raw = callback.data.split(":", 2)
+ page = int(page_raw)
+
+ service = OrderDraftsService()
+ draft = service.get_draft_by_id(draft_id)
+
+ if not draft:
+ await callback.answer("Черновик не найден", show_alert=True)
+ return
+
+ side = str(draft["side"]).upper()
+ order_type = str(draft["order_type"]).upper()
+ quantity = str(draft["quantity"])
+ price = str(draft.get("price") or "")
+
+ await state.clear()
+ await state.update_data(
+ draft_edit_id=draft_id,
+ draft_edit_page=page,
+ side=side,
+ order_type=order_type,
+ quantity=quantity,
+ )
+
+ title = _screen_title(is_edit_mode=True)
+
+ if order_type == "LIMIT":
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_price)
+ await callback.message.edit_text(
+ f"{title}\n"
+ f"{_mode_line()}"
+ f"Bid: {context.bid_price:.2f}\n"
+ f"Ask: {context.ask_price:.2f}\n"
+ f"Last: {context.last_price:.2f}\n\n"
+ "Шаг 4/4. Выбери цену",
+ reply_markup=_price_keyboard(
+ bid=context.bid_price,
+ ask=context.ask_price,
+ last=context.last_price,
+ ),
+ )
+ await callback.answer()
+ return
+
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_quantity)
+ await callback.message.edit_text(
+ f"{title}\n"
+ f"{_mode_line()}"
+ f"Инструмент: {context.symbol}\n"
+ f"Доступно: {context.available_balance:.8f} {context.balance_currency}\n"
+ f"Ориентир цены: {context.reference_price:.2f}\n\n"
+ "Шаг 3/4. Выбери количество",
+ reply_markup=_quantity_keyboard(context.quantity_presets),
+ )
+ await callback.answer()
+
+
+@router.callback_query(F.data.startswith("draft_delete:"))
+async def delete_draft_stub(callback: CallbackQuery) -> None:
+ await callback.answer("Удаление скоро появится")
+
+
@router.message(Command("cancel_order"))
async def cancel_order_builder(message: Message, state: FSMContext) -> None:
await state.clear()
await message.answer(
- "⚡ Торговля\n\n"
- "Создание черновика отменено."
- )
-
-
-@router.callback_query(F.data == "order_cancel")
-async def cancel_order_builder_callback(
- callback: CallbackQuery,
- state: FSMContext,
-) -> None:
- await state.clear()
- await callback.message.edit_text(
- "⚡ Торговля\n\n"
- "Создание черновика отменено.",
+ "📊 Торговля — Новый ордер\n"
+ f"{_mode_line()}"
+ "⛔ Создание черновика отменено",
reply_markup=_trade_back_home_keyboard(),
)
- await callback.answer()
@router.message(Command("new_order"))
@@ -269,9 +544,9 @@ async def start_new_order_draft(
await state.set_state(NewOrderDraftStates.waiting_side)
text = (
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 1/4\n"
- "Выберите сторону:"
+ "📊 Торговля — Новый ордер\n"
+ f"{_mode_line()}"
+ "Шаг 1/4. Выбери сторону"
)
if edit_mode:
@@ -283,10 +558,15 @@ async def start_new_order_draft(
@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)
+
+ text = (
+ "📊 Торговля — Новый ордер\n"
+ f"{_mode_line()}"
+ "Шаг 1/4. Выбери сторону"
+ )
+
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 1/4\n"
- "Выберите сторону:",
+ text,
reply_markup=_side_keyboard(),
)
await callback.answer()
@@ -295,10 +575,13 @@ async def go_back_to_side(callback: CallbackQuery, state: FSMContext) -> None:
@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)
+ text = (
+ "📊 Торговля — Новый ордер\n"
+ f"{_mode_line()}"
+ "Шаг 2/4. Выбери тип ордера"
+ )
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 2/4\n"
- "Выберите тип ордера:",
+ text,
reply_markup=_type_keyboard(),
)
await callback.answer()
@@ -310,22 +593,137 @@ async def go_back_to_quantity(callback: CallbackQuery, state: FSMContext) -> Non
data = await state.get_data()
side = data.get("side", "BUY")
order_type = data.get("order_type", "MARKET")
+ is_edit_mode = bool(data.get("draft_edit_id"))
context = service.get_entry_context(side=side, order_type=order_type)
+
await state.set_state(NewOrderDraftStates.waiting_quantity)
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 3/4\n"
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
f"Инструмент: {context.symbol}\n"
f"Доступно: {context.available_balance:.8f} {context.balance_currency}\n"
f"Ориентир цены: {context.reference_price:.2f}\n\n"
- "Выберите количество или введите его вручную:",
+ "Шаг 3/4. Выбери количество",
reply_markup=_quantity_keyboard(context.quantity_presets),
)
await callback.answer()
+@router.callback_query(F.data == "order_back:confirm")
+async def go_back_from_confirm(callback: CallbackQuery, state: FSMContext) -> None:
+ service = OrderDraftsService()
+ data = await state.get_data()
+
+ confirm_draft = data.get("confirm_draft")
+ if not confirm_draft:
+ await state.clear()
+ await callback.message.edit_text(
+ "📊 Торговля\n\n"
+ "Не удалось восстановить шаг подтверждения.",
+ reply_markup=_trade_back_home_keyboard(),
+ )
+ await callback.answer()
+ return
+
+ side = confirm_draft["side"]
+ order_type = confirm_draft["order_type"]
+ is_edit_mode = bool(data.get("draft_edit_id"))
+
+ if order_type == "LIMIT":
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_price)
+ await callback.message.edit_text(
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
+ f"Bid: {context.bid_price:.2f}\n"
+ f"Ask: {context.ask_price:.2f}\n"
+ f"Last: {context.last_price:.2f}\n\n"
+ "Шаг 4/4. Выбери цену",
+ reply_markup=_price_keyboard(
+ bid=context.bid_price,
+ ask=context.ask_price,
+ last=context.last_price,
+ ),
+ )
+ await callback.answer()
+ return
+
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_quantity)
+ await callback.message.edit_text(
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
+ f"Инструмент: {context.symbol}\n"
+ f"Доступно: {context.available_balance:.8f} {context.balance_currency}\n"
+ f"Ориентир цены: {context.reference_price:.2f}\n\n"
+ "Шаг 3/4. Выбери количество",
+ reply_markup=_quantity_keyboard(context.quantity_presets),
+ )
+ 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")
+ is_edit_mode = bool(data.get("draft_edit_id"))
+
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_quantity)
+ await callback.message.edit_text(
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
+ f"Инструмент: {context.symbol}\n"
+ f"Доступно: {context.available_balance:.8f} {context.balance_currency}\n"
+ f"Ориентир цены: {context.reference_price:.2f}\n\n"
+ "Шаг 3/4. Выбери количество",
+ reply_markup=_quantity_keyboard(context.quantity_presets),
+ )
+ 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")
+ is_edit_mode = bool(data.get("draft_edit_id"))
+
+ context = service.get_entry_context(side=side, order_type=order_type)
+
+ await state.set_state(NewOrderDraftStates.waiting_price)
+ await callback.message.edit_text(
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
+ f"Bid: {context.bid_price:.2f}\n"
+ f"Ask: {context.ask_price:.2f}\n"
+ f"Last: {context.last_price:.2f}\n\n"
+ "Шаг 4/4. Выбери цену",
+ reply_markup=_price_keyboard(
+ bid=context.bid_price,
+ ask=context.ask_price,
+ last=context.last_price,
+ ),
+ )
+ await callback.answer()
+
+
@router.callback_query(
NewOrderDraftStates.waiting_side,
F.data.startswith("order_side:"),
@@ -335,14 +733,15 @@ async def process_order_side_callback(
state: FSMContext,
) -> None:
side = callback.data.split(":", 1)[1]
-
await state.update_data(side=side)
await state.set_state(NewOrderDraftStates.waiting_type)
-
+ text = (
+ "📊 Торговля — Новый ордер\n"
+ f"{_mode_line()}"
+ "Шаг 2/4. Выбери тип ордера"
+ )
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 2/4\n"
- "Выберите тип ордера:",
+ text,
reply_markup=_type_keyboard(),
)
await callback.answer()
@@ -369,6 +768,7 @@ async def process_order_type_callback(
data = await state.get_data()
side = data.get("side", "BUY")
+ is_edit_mode = bool(data.get("draft_edit_id"))
await state.update_data(order_type=order_type)
await state.set_state(NewOrderDraftStates.waiting_quantity)
@@ -376,12 +776,12 @@ async def process_order_type_callback(
context = service.get_entry_context(side=side, order_type=order_type)
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 3/4\n"
+ f"{_screen_title(is_edit_mode)}\n"
+ f"{_mode_line()}"
f"Инструмент: {context.symbol}\n"
f"Доступно: {context.available_balance:.8f} {context.balance_currency}\n"
f"Ориентир цены: {context.reference_price:.2f}\n\n"
- "Выберите количество или введите его вручную:",
+ "Шаг 3/4. Выбери количество",
reply_markup=_quantity_keyboard(context.quantity_presets),
)
await callback.answer()
@@ -404,38 +804,55 @@ async def process_quantity_callback(
state: FSMContext,
) -> None:
value = callback.data.split(":", 1)[1]
+ data = await state.get_data()
+ is_edit_mode = bool(data.get("draft_edit_id"))
+ title = _screen_title(is_edit_mode)
+
+ service = OrderDraftsService()
if value == "manual":
+ rules = service.get_entry_rules()
+ context = service.get_entry_context(
+ side=data.get("side", "BUY"),
+ order_type=data.get("order_type", "MARKET"),
+ )
+ quantity_example = context.quantity_presets[0] if context.quantity_presets else "0.001"
+
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 3/4\n"
- "Введите количество вручную, например: 0.001",
+ f"{title}\n"
+ f"{_mode_line()}"
+ "Шаг 3/4. Введи количество\n\n"
+ f"{_render_quantity_input_help(
+ min_qty=rules['min_qty'],
+ step_size=rules['step_size'],
+ min_notional=rules['min_notional'],
+ example=quantity_example,
+ )}",
reply_markup=_quantity_manual_keyboard(),
)
await callback.answer()
return
- service = OrderDraftsService()
quantity = service.normalize_quantity(value)
if quantity is None:
await callback.answer("Некорректное значение количества.", show_alert=True)
return
- data = await state.get_data()
order_type = data.get("order_type", "MARKET")
await state.update_data(quantity=quantity)
if order_type == "LIMIT":
context = service.get_entry_context(side=data["side"], order_type=order_type)
+
await state.set_state(NewOrderDraftStates.waiting_price)
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 4/4\n"
+ f"{title}\n"
+ f"{_mode_line()}"
f"Bid: {context.bid_price:.2f}\n"
f"Ask: {context.ask_price:.2f}\n"
f"Last: {context.last_price:.2f}\n\n"
- "Выберите цену или введите её вручную:",
+ "Шаг 4/4. Выбери цену",
reply_markup=_price_keyboard(
bid=context.bid_price,
ask=context.ask_price,
@@ -450,28 +867,31 @@ async def process_quantity_callback(
order_type=order_type,
quantity=quantity,
)
- 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()
+ notional = service.calculate_notional(quantity, None)
+
+ await state.update_data(
+ confirm_draft={
+ "symbol": draft.symbol,
+ "side": draft.side,
+ "order_type": draft.order_type,
+ "quantity": draft.quantity,
+ "price": draft.price,
+ }
+ )
+ await state.set_state(NewOrderDraftStates.waiting_confirm)
+
await callback.message.edit_text(
- _render_draft_summary(
+ _render_confirm(
symbol=draft.symbol,
side=draft.side,
order_type=draft.order_type,
quantity=draft.quantity,
price=draft.price,
+ notional=notional,
+ is_edit_mode=is_edit_mode,
),
- reply_markup=_trade_back_home_keyboard(),
+ reply_markup=_confirm_keyboard(),
)
await callback.answer()
@@ -479,26 +899,65 @@ async def process_quantity_callback(
@router.message(NewOrderDraftStates.waiting_quantity)
async def process_order_quantity(message: Message, state: FSMContext) -> None:
service = OrderDraftsService()
- quantity = service.normalize_quantity(message.text or "")
- if quantity is None:
- await message.answer("Введите корректное количество, например: 0.001")
- return
+ raw_quantity = message.text or ""
+ quantity = service.normalize_quantity(raw_quantity)
data = await state.get_data()
+ side = data.get("side", "BUY")
order_type = data.get("order_type", "MARKET")
+ is_edit_mode = bool(data.get("draft_edit_id"))
+ title = _screen_title(is_edit_mode)
+
+ context = service.get_entry_context(side=side, order_type=order_type)
+ rules = service.get_entry_rules()
+ quantity_example = context.quantity_presets[0] if context.quantity_presets else "0.001"
+ help_text = _render_quantity_input_help(
+ min_qty=rules["min_qty"],
+ step_size=rules["step_size"],
+ min_notional=rules["min_notional"],
+ example=quantity_example,
+ )
+
+ if quantity is None:
+ await message.answer(
+ _render_inline_error(
+ title=title,
+ step_text="Шаг 3/4. Проверь введённое значение",
+ errors=[...],
+ help_text=help_text,
+ ),
+ reply_markup=_quantity_manual_keyboard(),
+ )
+ return
+
+ quantity_errors = service.validate_entry_quantity(
+ side=side,
+ order_type=order_type,
+ quantity=quantity,
+ price=None,
+ )
+ if quantity_errors:
+ await message.answer(
+ _render_inline_error(
+ title=title,
+ errors=quantity_errors,
+ help_text=help_text,
+ ),
+ reply_markup=_quantity_manual_keyboard(),
+ )
+ return
await state.update_data(quantity=quantity)
if order_type == "LIMIT":
- context = service.get_entry_context(side=data["side"], order_type=order_type)
await state.set_state(NewOrderDraftStates.waiting_price)
await message.answer(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 4/4\n"
+ f"{title}\n"
+ f"{_mode_line()}"
f"Bid: {context.bid_price:.2f}\n"
f"Ask: {context.ask_price:.2f}\n"
f"Last: {context.last_price:.2f}\n\n"
- "Выберите цену или введите её вручную:",
+ "Шаг 4/4. Выбери цену",
reply_markup=_price_keyboard(
bid=context.bid_price,
ask=context.ask_price,
@@ -508,27 +967,35 @@ async def process_order_quantity(message: Message, state: FSMContext) -> None:
return
draft = service.build_draft(
- side=data["side"],
+ side=side,
order_type=order_type,
quantity=quantity,
)
- 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()
+ notional = service.calculate_notional(quantity, None)
+
+ await state.update_data(
+ confirm_draft={
+ "symbol": draft.symbol,
+ "side": draft.side,
+ "order_type": draft.order_type,
+ "quantity": draft.quantity,
+ "price": draft.price,
+ }
+ )
+ await state.set_state(NewOrderDraftStates.waiting_confirm)
+
await message.answer(
- _render_draft_summary(
+ _render_confirm(
symbol=draft.symbol,
side=draft.side,
order_type=draft.order_type,
quantity=draft.quantity,
price=draft.price,
- )
+ notional=notional,
+ is_edit_mode=is_edit_mode,
+ ),
+ reply_markup=_confirm_keyboard(),
)
@@ -541,43 +1008,203 @@ async def process_price_callback(
state: FSMContext,
) -> None:
value = callback.data.split(":", 1)[1]
+ data = await state.get_data()
+ is_edit_mode = bool(data.get("draft_edit_id"))
+ title = _screen_title(is_edit_mode)
+
+ service = OrderDraftsService()
if value == "manual":
+ rules = service.get_entry_rules()
+ context = service.get_entry_context(
+ side=data.get("side", "BUY"),
+ order_type=data.get("order_type", "LIMIT"),
+ )
+ price_example = f"{context.last_price:.2f}"
+
await callback.message.edit_text(
- "⚡ Торговля — Новый ордер\n\n"
- "Шаг 4/4\n"
- "Введите цену вручную, например: 73000.123",
+ f"{title}\n"
+ f"{_mode_line()}"
+ "Шаг 4/4. Введи цену\n\n"
+ f"{_render_price_input_help(
+ tick_size=rules['tick_size'],
+ example=price_example,
+ )}",
reply_markup=_price_manual_keyboard(),
)
await callback.answer()
return
- service = OrderDraftsService()
price = service.normalize_price(value)
+
if price is None:
await callback.answer("Некорректная цена.", show_alert=True)
return
- data = await state.get_data()
draft = service.build_draft(
side=data["side"],
order_type=data["order_type"],
quantity=data["quantity"],
price=price,
)
+
+ notional = service.calculate_notional(data["quantity"], price)
+
+ await state.update_data(
+ confirm_draft={
+ "symbol": draft.symbol,
+ "side": draft.side,
+ "order_type": draft.order_type,
+ "quantity": draft.quantity,
+ "price": draft.price,
+ }
+ )
+ await state.set_state(NewOrderDraftStates.waiting_confirm)
+
+ await callback.message.edit_text(
+ _render_confirm(
+ symbol=draft.symbol,
+ side=draft.side,
+ order_type=draft.order_type,
+ quantity=draft.quantity,
+ price=draft.price,
+ notional=notional,
+ is_edit_mode=is_edit_mode,
+ ),
+ reply_markup=_confirm_keyboard(),
+ )
+ await callback.answer()
+
+
+@router.message(NewOrderDraftStates.waiting_price)
+async def process_order_price(message: Message, state: FSMContext) -> None:
+ service = OrderDraftsService()
+ raw_price = message.text or ""
+ price = service.normalize_price(raw_price)
+
+ data = await state.get_data()
+ is_edit_mode = bool(data.get("draft_edit_id"))
+ title = _screen_title(is_edit_mode)
+
+ rules = service.get_entry_rules()
+ context = service.get_entry_context(
+ side=data.get("side", "BUY"),
+ order_type=data.get("order_type", "LIMIT"),
+ )
+ price_example = f"{context.last_price:.2f}"
+ help_text = _render_price_input_help(
+ tick_size=rules["tick_size"],
+ example=price_example,
+ )
+
+ if price is None:
+ await message.answer(
+ _render_inline_error(
+ title=title,
+ step_text="Шаг 4/4. Проверь введённое значение",
+ errors=[...],
+ help_text=help_text,
+ ),
+ reply_markup=_price_manual_keyboard(),
+ )
+ return
+
+ draft = service.build_draft(
+ side=data["side"],
+ order_type=data["order_type"],
+ quantity=data["quantity"],
+ price=price,
+ )
+
+ validation = service.validate_draft(draft)
+ if not validation.is_valid:
+ await message.answer(
+ _render_inline_error(
+ title=title,
+ errors=validation.errors,
+ help_text=help_text,
+ ),
+ reply_markup=_price_manual_keyboard(),
+ )
+ return
+
+ notional = service.calculate_notional(data["quantity"], price)
+
+ await state.update_data(
+ confirm_draft={
+ "symbol": draft.symbol,
+ "side": draft.side,
+ "order_type": draft.order_type,
+ "quantity": draft.quantity,
+ "price": draft.price,
+ }
+ )
+ await state.set_state(NewOrderDraftStates.waiting_confirm)
+
+ await message.answer(
+ _render_confirm(
+ symbol=draft.symbol,
+ side=draft.side,
+ order_type=draft.order_type,
+ quantity=draft.quantity,
+ price=draft.price,
+ notional=notional,
+ is_edit_mode=is_edit_mode,
+ ),
+ reply_markup=_confirm_keyboard(),
+ )
+
+
+@router.message(Command("drafts"))
+async def drafts_command(message: Message) -> None:
+ await show_recent_drafts(message, edit_mode=False, page=1)
+
+
+@router.callback_query(NewOrderDraftStates.waiting_confirm, F.data == "order_confirm")
+async def confirm_order(callback: CallbackQuery, state: FSMContext) -> None:
+ service = OrderDraftsService()
+ data = await state.get_data()
+
+ raw = data.get("confirm_draft")
+ if not raw:
+ await state.clear()
+ await callback.answer("Ошибка состояния", show_alert=True)
+ return
+
+ draft = service.build_draft(
+ side=raw["side"],
+ order_type=raw["order_type"],
+ quantity=raw["quantity"],
+ price=raw.get("price"),
+ )
+
try:
service.save_draft(draft)
except ValueError as exc:
+ edit_page = data.get("draft_edit_page")
await state.clear()
errors = [item.strip() for item in str(exc).split(";") if item.strip()]
+ reply_markup = (
+ _drafts_back_keyboard(int(edit_page))
+ if edit_page
+ else _trade_back_home_keyboard()
+ )
await callback.message.edit_text(
_render_validation_error(errors),
- reply_markup=_trade_back_home_keyboard(),
+ reply_markup=reply_markup,
)
await callback.answer()
return
+ edit_page = data.get("draft_edit_page")
await state.clear()
+
+ reply_markup = (
+ _drafts_back_keyboard(int(edit_page))
+ if edit_page
+ else _trade_back_home_keyboard()
+ )
+
await callback.message.edit_text(
_render_draft_summary(
symbol=draft.symbol,
@@ -586,46 +1213,6 @@ async def process_price_callback(
quantity=draft.quantity,
price=draft.price,
),
- reply_markup=_trade_back_home_keyboard(),
+ reply_markup=reply_markup,
)
- await callback.answer()
-
-
-@router.message(NewOrderDraftStates.waiting_price)
-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.123")
- return
-
- data = await state.get_data()
- draft = service.build_draft(
- side=data["side"],
- order_type=data["order_type"],
- quantity=data["quantity"],
- price=price,
- )
- 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,
- side=draft.side,
- order_type=draft.order_type,
- quantity=draft.quantity,
- price=draft.price,
- )
- )
-
-
-@router.message(Command("drafts"))
-async def drafts_command(message: Message) -> None:
- await show_recent_drafts(message, edit_mode=False, page=1)
\ No newline at end of file
+ await callback.answer()
\ No newline at end of file
diff --git a/app/src/telegram/keyboards/reply.py b/app/src/telegram/keyboards/reply.py
index ab0e4a6..fece25a 100644
--- a/app/src/telegram/keyboards/reply.py
+++ b/app/src/telegram/keyboards/reply.py
@@ -10,7 +10,7 @@ def build_main_menu_keyboard() -> ReplyKeyboardMarkup:
KeyboardButton(text="💼 Портфель"),
],
[
- KeyboardButton(text="⚡ Торговля"),
+ KeyboardButton(text="📊 Торговля"),
KeyboardButton(text="🤖 Авто"),
KeyboardButton(text="📒 Журнал"),
],
diff --git a/app/src/telegram/menus.py b/app/src/telegram/menus.py
index d9afce9..b099b85 100644
--- a/app/src/telegram/menus.py
+++ b/app/src/telegram/menus.py
@@ -27,6 +27,6 @@ SYSTEM_TEXT = (
MARKET_TEXT = "📈 Рынок\n\nРаздел пока в разработке."
PORTFOLIO_TEXT = "💼 Портфель\n\nРаздел пока в разработке."
-TRADE_TEXT = "⚡ Торговля\n\nВыберите действие:\nDRAFT режим"
+TRADE_TEXT = "📊 Торговля\n\nВыберите действие:\nDRAFT режим"
AUTO_TEXT = "🤖 Авто\n\nРаздел пока в разработке."
JOURNAL_TEXT = "📒 Журнал\n\nРаздел пока в разработке."
\ No newline at end of file