From f662ff19015aa0de092776d8747b879396532d86 Mon Sep 17 00:00:00 2001 From: Sergey Date: Thu, 16 Apr 2026 21:23:35 +0300 Subject: [PATCH] Stage 05.1 - order draft flow --- app/src/telegram/handlers/trade/__init__.py | 1 + app/src/telegram/handlers/trade/main.py | 18 ++++++ app/src/telegram/handlers/trade/new_order.py | 53 ++++++++++++++++++ app/src/telegram/routers.py | 6 +- app/src/trading/orders/__init__.py | 1 + app/src/trading/orders/models.py | 12 ++++ app/src/trading/orders/service.py | 55 +++++++++++++++++++ .../0012-order-drafts-before-live-orders.md | 14 +++++ docs/stages/stage-05-1-order-draft-flow.md | 27 +++++++++ 9 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 app/src/telegram/handlers/trade/__init__.py create mode 100644 app/src/telegram/handlers/trade/main.py create mode 100644 app/src/telegram/handlers/trade/new_order.py create mode 100644 app/src/trading/orders/__init__.py create mode 100644 app/src/trading/orders/models.py create mode 100644 app/src/trading/orders/service.py create mode 100644 docs/decisions/0012-order-drafts-before-live-orders.md create mode 100644 docs/stages/stage-05-1-order-draft-flow.md diff --git a/app/src/telegram/handlers/trade/__init__.py b/app/src/telegram/handlers/trade/__init__.py new file mode 100644 index 0000000..d8df7b8 --- /dev/null +++ b/app/src/telegram/handlers/trade/__init__.py @@ -0,0 +1 @@ +"""Package marker.""" diff --git a/app/src/telegram/handlers/trade/main.py b/app/src/telegram/handlers/trade/main.py new file mode 100644 index 0000000..3841738 --- /dev/null +++ b/app/src/telegram/handlers/trade/main.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from aiogram import F, Router +from aiogram.types import Message + +router = Router(name="trade_main") + + +@router.message(F.text == "⚡ Торговля") +async def open_trade(message: Message) -> None: + text = ( + "⚡ Торговля\n\n" + "Доступные действия:\n" + "• /new_order — создать черновик ордера\n" + "• /drafts — показать последние черновики\n\n" + "На этом этапе реальные ордера ещё не отправляются." + ) + await message.answer(text) diff --git a/app/src/telegram/handlers/trade/new_order.py b/app/src/telegram/handlers/trade/new_order.py new file mode 100644 index 0000000..741c3e2 --- /dev/null +++ b/app/src/telegram/handlers/trade/new_order.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from aiogram import Router +from aiogram.filters import Command +from aiogram.types import Message + +from src.trading.orders.service import OrderDraftsService + +router = Router(name="trade_new_order") + + +@router.message(Command("new_order")) +async def create_new_order_draft(message: Message) -> None: + service = OrderDraftsService() + draft = service.create_default_draft() + + text = ( + "📝 Черновик ордера создан\n\n" + f"• инструмент: {draft.symbol}\n" + f"• сторона: {draft.side}\n" + f"• тип: {draft.order_type}\n" + f"• количество: {draft.quantity}\n" + f"• статус: {draft.status}\n\n" + "Это тестовый draft flow. Реальный ордер не отправлялся." + ) + await message.answer(text) + + +@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( + "📝 Черновики ордеров\n\n" + "Черновиков пока нет." + ) + return + + lines = ["📝 Черновики ордеров", "", "Последние записи", ""] + + 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()) diff --git a/app/src/telegram/routers.py b/app/src/telegram/routers.py index 5e08e44..9425b98 100644 --- a/app/src/telegram/routers.py +++ b/app/src/telegram/routers.py @@ -7,7 +7,8 @@ from src.telegram.handlers.market import router as market_router from src.telegram.handlers.portfolio import router as portfolio_router from src.telegram.handlers.start import router as start_router from src.telegram.handlers.system import router as system_router -from src.telegram.handlers.trade import router as trade_router +from src.telegram.handlers.trade.main import router as trade_main_router +from src.telegram.handlers.trade.new_order import router as trade_new_order_router def setup_routers(dispatcher: Dispatcher) -> None: @@ -15,7 +16,8 @@ def setup_routers(dispatcher: Dispatcher) -> None: dispatcher.include_router(home_router) dispatcher.include_router(market_router) dispatcher.include_router(portfolio_router) - dispatcher.include_router(trade_router) + dispatcher.include_router(trade_main_router) + dispatcher.include_router(trade_new_order_router) dispatcher.include_router(auto_router) dispatcher.include_router(journal_router) dispatcher.include_router(system_router) diff --git a/app/src/trading/orders/__init__.py b/app/src/trading/orders/__init__.py new file mode 100644 index 0000000..d8df7b8 --- /dev/null +++ b/app/src/trading/orders/__init__.py @@ -0,0 +1 @@ +"""Package marker.""" diff --git a/app/src/trading/orders/models.py b/app/src/trading/orders/models.py new file mode 100644 index 0000000..b46b622 --- /dev/null +++ b/app/src/trading/orders/models.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(slots=True) +class OrderDraft: + symbol: str + side: str + order_type: str + quantity: str + status: str = "draft" diff --git a/app/src/trading/orders/service.py b/app/src/trading/orders/service.py new file mode 100644 index 0000000..5e7611a --- /dev/null +++ b/app/src/trading/orders/service.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from src.core.config import load_settings +from src.storage.repositories.order_drafts import OrderDraftRepository +from src.trading.journal.service import JournalService +from src.trading.orders.models import OrderDraft + + +class OrderDraftsService: + def __init__(self) -> None: + self.settings = load_settings() + self.repository = OrderDraftRepository() + self.journal = JournalService() + + def create_default_draft(self) -> OrderDraft: + draft = OrderDraft( + symbol=self.settings.default_symbol, + side="BUY", + order_type="MARKET", + quantity="0.001", + status="draft", + ) + self._save_draft(draft) + return draft + + def _save_draft(self, draft: OrderDraft) -> None: + self.repository.add_draft( + symbol=draft.symbol, + side=draft.side, + order_type=draft.order_type, + quantity=draft.quantity, + status=draft.status, + payload={ + "source": "trade_screen", + "mode": "draft_only", + }, + ) + + try: + self.journal.log_info( + "order_draft_saved", + "Черновик ордера сохранён.", + { + "symbol": draft.symbol, + "side": draft.side, + "order_type": draft.order_type, + "quantity": draft.quantity, + "status": draft.status, + }, + ) + except Exception: + pass + + def list_recent_drafts(self, limit: int = 5) -> list[dict[str, str]]: + return self.repository.list_recent_drafts(limit=limit) diff --git a/docs/decisions/0012-order-drafts-before-live-orders.md b/docs/decisions/0012-order-drafts-before-live-orders.md new file mode 100644 index 0000000..fb671d6 --- /dev/null +++ b/docs/decisions/0012-order-drafts-before-live-orders.md @@ -0,0 +1,14 @@ +# 0012 — Order Drafts before Live Orders + +## Решение +Сначала внедрить безопасный draft flow, а уже потом реальные live orders. + +## Причины +- это снижает торговый риск +- позволяет проверить архитектуру order flow +- даёт возможность тестировать UI и storage без отправки ордеров + +## Последствия +- появляется первый безопасный трейдинговый сценарий +- order_drafts начинают реально использоваться +- live order execution откладывается на следующий этап diff --git a/docs/stages/stage-05-1-order-draft-flow.md b/docs/stages/stage-05-1-order-draft-flow.md new file mode 100644 index 0000000..82a43d3 --- /dev/null +++ b/docs/stages/stage-05-1-order-draft-flow.md @@ -0,0 +1,27 @@ +# Stage 05.1 — Order Draft Flow + +## Цель +Сделать первый безопасный trading flow без отправки реальных ордеров. + +## Что реализовано +- `OrderDraftsService` +- `OrderDraft` model +- сохранение draft в `order_drafts` +- команды: + - `/new_order` + - `/drafts` +- экран `⚡ Торговля` как входная точка + +## Что это даёт +- появляется первый order flow +- storage начинает использоваться не только для snapshots +- можно безопасно тестировать торговый сценарий без риска отправки ордера + +## Ограничения +- параметры draft пока фиксированные +- нет диалога ввода стороны / количества +- нет отправки live order + +## Следующий шаг +- Stage 05.2 — interactive draft builder +- Stage 05.3 — order validation