Stage 07.1 - auto trading skeleton UI, state machine and mock controls

This commit is contained in:
2026-04-28 11:17:22 +03:00
parent cea74da4c4
commit b48d9c7f35
7 changed files with 336 additions and 9 deletions

View File

@@ -6,29 +6,72 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.exceptions import TelegramBadRequest
from src.telegram.menus import AUTO_TEXT
from src.telegram.ui.common import mode_line
from src.trading.auto.service import AutoTradeService
router = Router(name="auto")
def _status_label(status: str) -> str:
mapping = {
"OFF": "⚪ Выключена",
"OBSERVING": "👀 Наблюдение",
"RUNNING": "🟢 Активна",
}
return mapping.get(status, status)
def _auto_keyboard() -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
# 1 ряд
builder.button(text="▶️ Start", callback_data="auto:start")
builder.button(text="👀 Watch", callback_data="auto:observe")
builder.button(text="🛑 Stop", callback_data="auto:stop")
# 2 ряд
builder.button(text="🛠️ Настройки", callback_data="settings:auto")
builder.adjust(1)
builder.adjust(3, 1)
return builder.as_markup()
def _build_auto_text() -> str:
state = AutoTradeService().get_state()
strategy = state.strategy or ""
risk = f"{state.risk_percent:.1f}%" if state.risk_percent is not None else ""
return (
"<b>🤖 Автоторговля</b>\n"
f"{mode_line()}"
f"Статус: {_status_label(state.status)}\n"
f"Стратегия: {strategy}\n"
f"Инструмент: {state.symbol}\n"
f"Риск: {risk}\n"
f"PnL: {state.pnl_usd:.2f} USD"
)
async def _render_auto_screen(
target_message: Message,
*,
edit_mode: bool,
) -> None:
text = _build_auto_text()
if edit_mode:
await target_message.edit_text(AUTO_TEXT, reply_markup=_auto_keyboard())
try:
await target_message.edit_text(text, reply_markup=_auto_keyboard())
except TelegramBadRequest as exc:
if "message is not modified" in str(exc).lower():
return
raise
else:
await target_message.answer(AUTO_TEXT, reply_markup=_auto_keyboard())
await target_message.answer(text, reply_markup=_auto_keyboard())
@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
@@ -46,4 +89,37 @@ async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) ->
return
await _render_auto_screen(callback.message, edit_mode=True)
await callback.answer()
await callback.answer()
@router.callback_query(F.data == "auto:start")
async def auto_start(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.start()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)
await callback.answer(message)
@router.callback_query(F.data == "auto:observe")
async def auto_observe(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.observe()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)
await callback.answer(message)
@router.callback_query(F.data == "auto:stop")
async def auto_stop(callback: CallbackQuery) -> None:
service = AutoTradeService()
_, message = service.stop()
if callback.message is not None:
await _render_auto_screen(callback.message, edit_mode=True)
await callback.answer(message)

View File

@@ -0,0 +1,52 @@
# app/src/trading/auto/service.py
from __future__ import annotations
from src.core.config import load_settings
from src.trading.auto.state import AutoTradeState
class AutoTradeService:
_state = AutoTradeState()
def get_state(self) -> AutoTradeState:
if not self._state.symbol:
self._state.symbol = load_settings().default_symbol
return self._state
def start(self) -> tuple[AutoTradeState, str]:
state = self.get_state()
if state.status == "RUNNING":
return state, "Автоторговля уже активна."
if state.status == "OBSERVING":
state.status = "RUNNING"
return state, "Автоторговля активирована."
state.status = "RUNNING"
return state, "Автоторговля запущена."
def observe(self) -> tuple[AutoTradeState, str]:
state = self.get_state()
previous_status = state.status
if previous_status == "OBSERVING":
return state, "Режим наблюдения уже включён."
state.status = "OBSERVING"
if previous_status == "OFF":
return state, "Включён режим наблюдения."
return state, "Автоторговля переведена в режим наблюдения."
def stop(self) -> tuple[AutoTradeState, str]:
state = self.get_state()
if state.status == "OFF":
return state, "Автоторговля уже выключена."
state.status = "OFF"
return state, "Автоторговля выключена."

View File

@@ -0,0 +1,14 @@
# app/src/trading/auto/state.py
from __future__ import annotations
from dataclasses import dataclass
@dataclass(slots=True)
class AutoTradeState:
status: str = "OFF"
strategy: str | None = None
symbol: str = ""
risk_percent: float | None = None
pnl_usd: float = 0.0

View File

@@ -0,0 +1,35 @@
# 0015 — Auto Trading State Machine
## Решение
Для автоторговли вводится state-machine из трёх состояний:
- OFF
- OBSERVING
- RUNNING
## Причины
OFF:
полное отключение loop.
OBSERVING:
анализ рынка без открытия новых сделок.
RUNNING:
анализ + торговля.
## Последствия
Позволяет:
- быстро строить background loop;
- безопасно включать наблюдение;
- расширять стратегический движок.

View File

@@ -85,10 +85,21 @@
---
## Stage 07 — Observability
⏳ логирование
⏳ алерты
⏳ метрики
## Stage 07 — Auto Trading
### 07.1
✔ auto trading skeleton UI
✔ state machine
✔ mock controls
### 07.2
⏳ real settings
### 07.3
⏳ background loop
### 07.4
⏳ strategy plugin architecture
---

View File

@@ -0,0 +1,37 @@
# Stage 07 — Auto Trading Roadmap
## Цель
Добавить автоторговлю.
---
## 07.1 — Skeleton UI
✔ экран автоторговли
✔ state machine
✔ mock controls
---
## 07.2 — Real settings
⏳ стратегия
⏳ риск
⏳ символ
---
## 07.3 — Background loop
⏳ scheduler
⏳ market polling
⏳ signal loop
---
## 07.4 — Strategy plugins
⏳ plugin architecture
⏳ strategy registry
⏳ signal execution

View File

@@ -0,0 +1,102 @@
# Stage 07.1 — Auto Trading Skeleton UI
## Что сделано
Реализован базовый skeleton автоторговли.
---
## 1. Экран 🤖 Автоторговля
Добавлен новый экран:
Показывает:
- режим аккаунта
- статус автоторговли
- стратегию
- инструмент
- риск
- PnL
---
## 2. State machine
Добавлены состояния:
- OFF → выключена
- OBSERVING → наблюдение
- RUNNING → активна
Логика:
### OFF
бот полностью выключен
### OBSERVING
бот следит за рынком, но не торгует
### RUNNING
бот следит за рынком и торгует
---
## 3. Mock controls
Добавлены кнопки управления:
- ▶️ Start
- 👀 Watch
- 🛑 Stop
Поведение:
### Start
OFF / OBSERVING → RUNNING
### Watch
OFF / RUNNING → OBSERVING
### Stop
OBSERVING / RUNNING → OFF
---
## 4. Service layer
Добавлены файлы:
```
src/trading/auto/state.py
src/trading/auto/service.py
```
### AutoTradeState
Хранит:
* status
* strategy
* symbol
* risk_percent
* pnl_usd
### AutoTradeService
Методы:
* get_state()
* start()
* observe()
* stop()
---
## 5. Навигация
Добавлен переход:
Автоторговля → Настройки
Настройки → Автоторговля