Stage 05.1 - order draft flow
This commit is contained in:
1
app/src/telegram/handlers/trade/__init__.py
Normal file
1
app/src/telegram/handlers/trade/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Package marker."""
|
||||||
18
app/src/telegram/handlers/trade/main.py
Normal file
18
app/src/telegram/handlers/trade/main.py
Normal file
@@ -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 = (
|
||||||
|
"<b>⚡ Торговля</b>\n\n"
|
||||||
|
"Доступные действия:\n"
|
||||||
|
"• /new_order — создать черновик ордера\n"
|
||||||
|
"• /drafts — показать последние черновики\n\n"
|
||||||
|
"На этом этапе реальные ордера ещё не отправляются."
|
||||||
|
)
|
||||||
|
await message.answer(text)
|
||||||
53
app/src/telegram/handlers/trade/new_order.py
Normal file
53
app/src/telegram/handlers/trade/new_order.py
Normal file
@@ -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 = (
|
||||||
|
"<b>📝 Черновик ордера создан</b>\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(
|
||||||
|
"<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())
|
||||||
@@ -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.portfolio import router as portfolio_router
|
||||||
from src.telegram.handlers.start import router as start_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.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:
|
def setup_routers(dispatcher: Dispatcher) -> None:
|
||||||
@@ -15,7 +16,8 @@ def setup_routers(dispatcher: Dispatcher) -> None:
|
|||||||
dispatcher.include_router(home_router)
|
dispatcher.include_router(home_router)
|
||||||
dispatcher.include_router(market_router)
|
dispatcher.include_router(market_router)
|
||||||
dispatcher.include_router(portfolio_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(auto_router)
|
||||||
dispatcher.include_router(journal_router)
|
dispatcher.include_router(journal_router)
|
||||||
dispatcher.include_router(system_router)
|
dispatcher.include_router(system_router)
|
||||||
|
|||||||
1
app/src/trading/orders/__init__.py
Normal file
1
app/src/trading/orders/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Package marker."""
|
||||||
12
app/src/trading/orders/models.py
Normal file
12
app/src/trading/orders/models.py
Normal file
@@ -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"
|
||||||
55
app/src/trading/orders/service.py
Normal file
55
app/src/trading/orders/service.py
Normal file
@@ -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)
|
||||||
14
docs/decisions/0012-order-drafts-before-live-orders.md
Normal file
14
docs/decisions/0012-order-drafts-before-live-orders.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 0012 — Order Drafts before Live Orders
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
Сначала внедрить безопасный draft flow, а уже потом реальные live orders.
|
||||||
|
|
||||||
|
## Причины
|
||||||
|
- это снижает торговый риск
|
||||||
|
- позволяет проверить архитектуру order flow
|
||||||
|
- даёт возможность тестировать UI и storage без отправки ордеров
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
- появляется первый безопасный трейдинговый сценарий
|
||||||
|
- order_drafts начинают реально использоваться
|
||||||
|
- live order execution откладывается на следующий этап
|
||||||
27
docs/stages/stage-05-1-order-draft-flow.md
Normal file
27
docs/stages/stage-05-1-order-draft-flow.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user