From d63913785577c2ddaa3851d8dc11d9264617bfee Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 28 Apr 2026 13:20:59 +0300 Subject: [PATCH] Stage 07.3 - auto trading analysis cycle skeleton --- app/src/telegram/handlers/auto.py | 33 ++++++++- app/src/trading/auto/service.py | 23 +++++- app/src/trading/auto/state.py | 17 ++++- ...o-analysis-cycle-before-background-loop.md | 19 +++++ docs/roadmap/master-roadmap.md | 7 +- docs/roadmap/stage-07-roadmap.md | 17 +++-- ..._3-auto-trading-analysis-cycle-skeleton.md | 72 +++++++++++++++++++ 7 files changed, 178 insertions(+), 10 deletions(-) create mode 100644 docs/decisions/0017-auto-analysis-cycle-before-background-loop.md create mode 100644 docs/stages/stage-07_3-auto-trading-analysis-cycle-skeleton.md diff --git a/app/src/telegram/handlers/auto.py b/app/src/telegram/handlers/auto.py index a50995d..fc29c0e 100644 --- a/app/src/telegram/handlers/auto.py +++ b/app/src/telegram/handlers/auto.py @@ -15,6 +15,7 @@ from src.trading.auto.service import AutoTradeService router = Router(name="auto") +# красивое отображение стратегии def _strategy_label(strategy: str | None) -> str: mapping = { "TREND": "📈 Trend Following", @@ -22,8 +23,9 @@ def _strategy_label(strategy: str | None) -> str: "SCALP": "⚡ Scalping", } return mapping.get(strategy or "", "—") - + +# красивое отображение статуса def _status_label(status: str) -> str: mapping = { "OFF": "⚪ Выключена", @@ -33,6 +35,17 @@ def _status_label(status: str) -> str: return mapping.get(status, status) +# красивое отображение сигнала +def _signal_label(signal: str | None) -> str: + mapping = { + "BUY": "🟢 BUY", + "SELL": "🔴 SELL", + "HOLD": "🟡 HOLD", + } + return mapping.get(signal or "", "—") + + +# клавиатура автоторговли def _auto_keyboard() -> InlineKeyboardMarkup: builder = InlineKeyboardBuilder() @@ -48,8 +61,14 @@ def _auto_keyboard() -> InlineKeyboardMarkup: return builder.as_markup() +# собрать текст экрана def _build_auto_text() -> str: - state = AutoTradeService().get_state() + service = AutoTradeService() + + # выполнить один цикл анализа + service.run_cycle() + + state = service.get_state() strategy = _strategy_label(state.strategy) risk = f"{state.risk_percent:.1f}%" if state.risk_percent is not None else "—" @@ -61,10 +80,13 @@ def _build_auto_text() -> str: f"Стратегия: {strategy}\n" f"Инструмент: {state.symbol}\n" f"Риск: {risk}\n" - f"PnL: {state.pnl_usd:.2f} USD" + f"PnL: {state.pnl_usd:.2f} USD\n" + f"Последний анализ: {state.last_check_at or '—'}\n" + f"Сигнал: {_signal_label(state.last_signal)}" ) +# отрисовать экран async def _render_auto_screen( target_message: Message, *, @@ -83,12 +105,14 @@ async def _render_auto_screen( await target_message.answer(text, reply_markup=_auto_keyboard()) +# открыть экран из меню @router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"})) async def open_auto(message: Message, state: FSMContext) -> None: await state.clear() await _render_auto_screen(message, edit_mode=False) +# открыть экран callback @router.callback_query(F.data == "auto:home") async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None: await state.clear() @@ -101,6 +125,7 @@ async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> await callback.answer() +# включить активную торговлю @router.callback_query(F.data == "auto:start") async def auto_start(callback: CallbackQuery) -> None: service = AutoTradeService() @@ -112,6 +137,7 @@ async def auto_start(callback: CallbackQuery) -> None: await callback.answer(message) +# включить наблюдение @router.callback_query(F.data == "auto:observe") async def auto_observe(callback: CallbackQuery) -> None: service = AutoTradeService() @@ -123,6 +149,7 @@ async def auto_observe(callback: CallbackQuery) -> None: await callback.answer(message) +# выключить автоторговлю @router.callback_query(F.data == "auto:stop") async def auto_stop(callback: CallbackQuery) -> None: service = AutoTradeService() diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py index e441e16..e020e9d 100644 --- a/app/src/trading/auto/service.py +++ b/app/src/trading/auto/service.py @@ -2,6 +2,9 @@ from __future__ import annotations +import random +from datetime import datetime + from src.core.config import load_settings from src.trading.auto.state import AutoTradeState @@ -9,12 +12,13 @@ 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() @@ -28,6 +32,7 @@ class AutoTradeService: state.status = "RUNNING" return state, "Автоторговля запущена." + # включить режим наблюдения def observe(self) -> tuple[AutoTradeState, str]: state = self.get_state() previous_status = state.status @@ -42,6 +47,7 @@ class AutoTradeService: return state, "Автоторговля переведена в режим наблюдения." + # полностью выключить автоторговлю def stop(self) -> tuple[AutoTradeState, str]: state = self.get_state() @@ -51,17 +57,32 @@ class AutoTradeService: state.status = "OFF" return state, "Автоторговля выключена." + # установить инструмент def set_symbol(self, symbol: str) -> AutoTradeState: state = self.get_state() state.symbol = symbol return state + # установить стратегию def set_strategy(self, strategy: str) -> AutoTradeState: state = self.get_state() state.strategy = strategy return state + # установить риск def set_risk_percent(self, risk_percent: float) -> AutoTradeState: state = self.get_state() state.risk_percent = risk_percent + return state + + # выполнить один цикл анализа рынка + def run_cycle(self) -> AutoTradeState: + state = self.get_state() + + if state.status == "OFF": + return state + + state.last_check_at = datetime.now().strftime("%H:%M:%S") + state.last_signal = random.choice(["BUY", "SELL", "HOLD"]) + return state \ No newline at end of file diff --git a/app/src/trading/auto/state.py b/app/src/trading/auto/state.py index c857c83..42ffe97 100644 --- a/app/src/trading/auto/state.py +++ b/app/src/trading/auto/state.py @@ -7,8 +7,23 @@ from dataclasses import dataclass @dataclass(slots=True) class AutoTradeState: + # текущее состояние: OFF / OBSERVING / RUNNING status: str = "OFF" + + # выбранная стратегия: TREND / GRID / SCALP strategy: str | None = None + + # торговый инструмент symbol: str = "" + + # риск на одну сделку в % risk_percent: float | None = None - pnl_usd: float = 0.0 \ No newline at end of file + + # текущий PnL + pnl_usd: float = 0.0 + + # время последней проверки + last_check_at: str | None = None + + # последний сигнал стратегии + last_signal: str | None = None \ No newline at end of file diff --git a/docs/decisions/0017-auto-analysis-cycle-before-background-loop.md b/docs/decisions/0017-auto-analysis-cycle-before-background-loop.md new file mode 100644 index 0000000..f3f5312 --- /dev/null +++ b/docs/decisions/0017-auto-analysis-cycle-before-background-loop.md @@ -0,0 +1,19 @@ +# 0017 — Auto Analysis Cycle before Background Loop + +## Решение + +Сначала реализовать run_cycle() и UI integration. + +Фоновый loop вынести в следующий подэтап. + +## Причины + +- проще тестировать; + +- безопаснее; + +- меньше race conditions. + +## Последствия + +Анализ пока запускается только при открытии / обновлении экрана. \ No newline at end of file diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index dfb6131..cba790a 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -98,7 +98,12 @@ ✔ risk presets ### 07.3 -⏳ background loop +✔ analysis cycle skeleton +✔ mock signals +✔ UI integration + +### 07.3.1 +⏳ asyncio background loop ### 07.4 ⏳ strategy plugin architecture diff --git a/docs/roadmap/stage-07-roadmap.md b/docs/roadmap/stage-07-roadmap.md index 9389a8c..1984da9 100644 --- a/docs/roadmap/stage-07-roadmap.md +++ b/docs/roadmap/stage-07-roadmap.md @@ -23,11 +23,20 @@ --- -## 07.3 — Background loop +## 07.3 — Analysis Cycle Skeleton -⏳ scheduler -⏳ market polling -⏳ signal loop +✔ run_cycle() +✔ last_check_at +✔ last_signal +✔ mock signals +✔ UI integration + +--- + +## 07.3.1 — Background loop + +⏳ asyncio loop +⏳ live cycle --- diff --git a/docs/stages/stage-07_3-auto-trading-analysis-cycle-skeleton.md b/docs/stages/stage-07_3-auto-trading-analysis-cycle-skeleton.md new file mode 100644 index 0000000..2ec12e2 --- /dev/null +++ b/docs/stages/stage-07_3-auto-trading-analysis-cycle-skeleton.md @@ -0,0 +1,72 @@ +# Stage 07.3 — Auto Trading Analysis Cycle Skeleton + +## Что сделано + +Реализован skeleton цикла анализа рынка для автоторговли. + +--- + +## 1. Analysis cycle + +В `AutoTradeService` добавлен метод: + +- run_cycle() + +Метод: + +- обновляет время анализа +- генерирует mock сигнал +- сохраняет состояние + +--- + +## 2. AutoTradeState update + +Добавлены поля: + +- last_check_at +- last_signal + +--- + +## 3. UI integration + +Экран 🤖 Автоторговля теперь показывает: + +- Последний анализ +- Сигнал стратегии + +Пример: + +Последний анализ: 12:34:56 +Сигнал: 🟢 BUY + +--- + +## 4. Mock signal generator + +Для MVP используется: + +- BUY +- SELL +- HOLD + +через random.choice() + +--- + +## 5. Foundation for background loop + +Подготовлена база для следующего этапа: + +07.3.1 — asyncio background loop + +--- + +## Commit + +``` +git add . +git commit -m "Stage 07.3 - auto trading analysis cycle skeleton" +git push +``` \ No newline at end of file