Stage 07.3 - auto trading analysis cycle skeleton
This commit is contained in:
@@ -15,6 +15,7 @@ from src.trading.auto.service import AutoTradeService
|
|||||||
router = Router(name="auto")
|
router = Router(name="auto")
|
||||||
|
|
||||||
|
|
||||||
|
# красивое отображение стратегии
|
||||||
def _strategy_label(strategy: str | None) -> str:
|
def _strategy_label(strategy: str | None) -> str:
|
||||||
mapping = {
|
mapping = {
|
||||||
"TREND": "📈 Trend Following",
|
"TREND": "📈 Trend Following",
|
||||||
@@ -24,6 +25,7 @@ def _strategy_label(strategy: str | None) -> str:
|
|||||||
return mapping.get(strategy or "", "—")
|
return mapping.get(strategy or "", "—")
|
||||||
|
|
||||||
|
|
||||||
|
# красивое отображение статуса
|
||||||
def _status_label(status: str) -> str:
|
def _status_label(status: str) -> str:
|
||||||
mapping = {
|
mapping = {
|
||||||
"OFF": "⚪ Выключена",
|
"OFF": "⚪ Выключена",
|
||||||
@@ -33,6 +35,17 @@ def _status_label(status: str) -> str:
|
|||||||
return mapping.get(status, status)
|
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:
|
def _auto_keyboard() -> InlineKeyboardMarkup:
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
@@ -48,8 +61,14 @@ def _auto_keyboard() -> InlineKeyboardMarkup:
|
|||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
|
# собрать текст экрана
|
||||||
def _build_auto_text() -> str:
|
def _build_auto_text() -> str:
|
||||||
state = AutoTradeService().get_state()
|
service = AutoTradeService()
|
||||||
|
|
||||||
|
# выполнить один цикл анализа
|
||||||
|
service.run_cycle()
|
||||||
|
|
||||||
|
state = service.get_state()
|
||||||
|
|
||||||
strategy = _strategy_label(state.strategy)
|
strategy = _strategy_label(state.strategy)
|
||||||
risk = f"{state.risk_percent:.1f}%" if state.risk_percent is not None else "—"
|
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"Стратегия: {strategy}\n"
|
||||||
f"Инструмент: {state.symbol}\n"
|
f"Инструмент: {state.symbol}\n"
|
||||||
f"Риск: {risk}\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(
|
async def _render_auto_screen(
|
||||||
target_message: Message,
|
target_message: Message,
|
||||||
*,
|
*,
|
||||||
@@ -83,12 +105,14 @@ async def _render_auto_screen(
|
|||||||
await target_message.answer(text, reply_markup=_auto_keyboard())
|
await target_message.answer(text, reply_markup=_auto_keyboard())
|
||||||
|
|
||||||
|
|
||||||
|
# открыть экран из меню
|
||||||
@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
|
@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
|
||||||
async def open_auto(message: Message, state: FSMContext) -> None:
|
async def open_auto(message: Message, state: FSMContext) -> None:
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await _render_auto_screen(message, edit_mode=False)
|
await _render_auto_screen(message, edit_mode=False)
|
||||||
|
|
||||||
|
|
||||||
|
# открыть экран callback
|
||||||
@router.callback_query(F.data == "auto:home")
|
@router.callback_query(F.data == "auto:home")
|
||||||
async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None:
|
async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None:
|
||||||
await state.clear()
|
await state.clear()
|
||||||
@@ -101,6 +125,7 @@ async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) ->
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
# включить активную торговлю
|
||||||
@router.callback_query(F.data == "auto:start")
|
@router.callback_query(F.data == "auto:start")
|
||||||
async def auto_start(callback: CallbackQuery) -> None:
|
async def auto_start(callback: CallbackQuery) -> None:
|
||||||
service = AutoTradeService()
|
service = AutoTradeService()
|
||||||
@@ -112,6 +137,7 @@ async def auto_start(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer(message)
|
await callback.answer(message)
|
||||||
|
|
||||||
|
|
||||||
|
# включить наблюдение
|
||||||
@router.callback_query(F.data == "auto:observe")
|
@router.callback_query(F.data == "auto:observe")
|
||||||
async def auto_observe(callback: CallbackQuery) -> None:
|
async def auto_observe(callback: CallbackQuery) -> None:
|
||||||
service = AutoTradeService()
|
service = AutoTradeService()
|
||||||
@@ -123,6 +149,7 @@ async def auto_observe(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer(message)
|
await callback.answer(message)
|
||||||
|
|
||||||
|
|
||||||
|
# выключить автоторговлю
|
||||||
@router.callback_query(F.data == "auto:stop")
|
@router.callback_query(F.data == "auto:stop")
|
||||||
async def auto_stop(callback: CallbackQuery) -> None:
|
async def auto_stop(callback: CallbackQuery) -> None:
|
||||||
service = AutoTradeService()
|
service = AutoTradeService()
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from src.core.config import load_settings
|
from src.core.config import load_settings
|
||||||
from src.trading.auto.state import AutoTradeState
|
from src.trading.auto.state import AutoTradeState
|
||||||
|
|
||||||
@@ -9,12 +12,13 @@ from src.trading.auto.state import AutoTradeState
|
|||||||
class AutoTradeService:
|
class AutoTradeService:
|
||||||
_state = AutoTradeState()
|
_state = AutoTradeState()
|
||||||
|
|
||||||
|
# получить текущее состояние автоторговли
|
||||||
def get_state(self) -> AutoTradeState:
|
def get_state(self) -> AutoTradeState:
|
||||||
if not self._state.symbol:
|
if not self._state.symbol:
|
||||||
self._state.symbol = load_settings().default_symbol
|
self._state.symbol = load_settings().default_symbol
|
||||||
|
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
|
# запустить активную торговлю
|
||||||
def start(self) -> tuple[AutoTradeState, str]:
|
def start(self) -> tuple[AutoTradeState, str]:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|
||||||
@@ -28,6 +32,7 @@ class AutoTradeService:
|
|||||||
state.status = "RUNNING"
|
state.status = "RUNNING"
|
||||||
return state, "Автоторговля запущена."
|
return state, "Автоторговля запущена."
|
||||||
|
|
||||||
|
# включить режим наблюдения
|
||||||
def observe(self) -> tuple[AutoTradeState, str]:
|
def observe(self) -> tuple[AutoTradeState, str]:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
previous_status = state.status
|
previous_status = state.status
|
||||||
@@ -42,6 +47,7 @@ class AutoTradeService:
|
|||||||
|
|
||||||
return state, "Автоторговля переведена в режим наблюдения."
|
return state, "Автоторговля переведена в режим наблюдения."
|
||||||
|
|
||||||
|
# полностью выключить автоторговлю
|
||||||
def stop(self) -> tuple[AutoTradeState, str]:
|
def stop(self) -> tuple[AutoTradeState, str]:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|
||||||
@@ -51,17 +57,32 @@ class AutoTradeService:
|
|||||||
state.status = "OFF"
|
state.status = "OFF"
|
||||||
return state, "Автоторговля выключена."
|
return state, "Автоторговля выключена."
|
||||||
|
|
||||||
|
# установить инструмент
|
||||||
def set_symbol(self, symbol: str) -> AutoTradeState:
|
def set_symbol(self, symbol: str) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.symbol = symbol
|
state.symbol = symbol
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
# установить стратегию
|
||||||
def set_strategy(self, strategy: str) -> AutoTradeState:
|
def set_strategy(self, strategy: str) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.strategy = strategy
|
state.strategy = strategy
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
# установить риск
|
||||||
def set_risk_percent(self, risk_percent: float) -> AutoTradeState:
|
def set_risk_percent(self, risk_percent: float) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.risk_percent = risk_percent
|
state.risk_percent = risk_percent
|
||||||
return state
|
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
|
||||||
@@ -7,8 +7,23 @@ from dataclasses import dataclass
|
|||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class AutoTradeState:
|
class AutoTradeState:
|
||||||
|
# текущее состояние: OFF / OBSERVING / RUNNING
|
||||||
status: str = "OFF"
|
status: str = "OFF"
|
||||||
|
|
||||||
|
# выбранная стратегия: TREND / GRID / SCALP
|
||||||
strategy: str | None = None
|
strategy: str | None = None
|
||||||
|
|
||||||
|
# торговый инструмент
|
||||||
symbol: str = ""
|
symbol: str = ""
|
||||||
|
|
||||||
|
# риск на одну сделку в %
|
||||||
risk_percent: float | None = None
|
risk_percent: float | None = None
|
||||||
|
|
||||||
|
# текущий PnL
|
||||||
pnl_usd: float = 0.0
|
pnl_usd: float = 0.0
|
||||||
|
|
||||||
|
# время последней проверки
|
||||||
|
last_check_at: str | None = None
|
||||||
|
|
||||||
|
# последний сигнал стратегии
|
||||||
|
last_signal: str | None = None
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 0017 — Auto Analysis Cycle before Background Loop
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Сначала реализовать run_cycle() и UI integration.
|
||||||
|
|
||||||
|
Фоновый loop вынести в следующий подэтап.
|
||||||
|
|
||||||
|
## Причины
|
||||||
|
|
||||||
|
- проще тестировать;
|
||||||
|
|
||||||
|
- безопаснее;
|
||||||
|
|
||||||
|
- меньше race conditions.
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
Анализ пока запускается только при открытии / обновлении экрана.
|
||||||
@@ -98,7 +98,12 @@
|
|||||||
✔ risk presets
|
✔ risk presets
|
||||||
|
|
||||||
### 07.3
|
### 07.3
|
||||||
⏳ background loop
|
✔ analysis cycle skeleton
|
||||||
|
✔ mock signals
|
||||||
|
✔ UI integration
|
||||||
|
|
||||||
|
### 07.3.1
|
||||||
|
⏳ asyncio background loop
|
||||||
|
|
||||||
### 07.4
|
### 07.4
|
||||||
⏳ strategy plugin architecture
|
⏳ strategy plugin architecture
|
||||||
|
|||||||
@@ -23,11 +23,20 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 07.3 — Background loop
|
## 07.3 — Analysis Cycle Skeleton
|
||||||
|
|
||||||
⏳ scheduler
|
✔ run_cycle()
|
||||||
⏳ market polling
|
✔ last_check_at
|
||||||
⏳ signal loop
|
✔ last_signal
|
||||||
|
✔ mock signals
|
||||||
|
✔ UI integration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 07.3.1 — Background loop
|
||||||
|
|
||||||
|
⏳ asyncio loop
|
||||||
|
⏳ live cycle
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user