Stage 07.4.3.3 — Paper Position & Execution Engineg

This commit is contained in:
2026-05-03 07:45:07 +03:00
parent bd6b40fcb2
commit c139bb21fd
8 changed files with 421 additions and 8 deletions

View File

@@ -11,6 +11,7 @@ from src.trading.journal.service import JournalService
from src.trading.strategies.base import BaseStrategy, StrategyContext
from src.trading.strategies.registry import StrategyRegistry
from src.core.event_bus import EventBus
from src.trading.execution.engine import ExecutionEngine
class AutoTradeService:
@@ -19,10 +20,10 @@ class AutoTradeService:
_loop_interval_seconds = 5
# минимальное количество повторов BUY / SELL для подтверждения сигнала
_confirm_repeats = 3
_confirm_repeats = 2
# минимальная уверенность для готовности к будущему execution
_ready_confidence = 0.7
_ready_confidence = 0.3
_last_signal_key: str | None = None
_last_signal_value: str | None = None
@@ -459,4 +460,6 @@ class AutoTradeService:
payload=result.payload,
)
ExecutionEngine().process(state)
return state

View File

@@ -0,0 +1,144 @@
# app/src/trading/execution/engine.py
from __future__ import annotations
from datetime import datetime
from src.core.event_bus import EventBus
from src.integrations.exchange.service import ExchangeService
from src.trading.auto.state import AutoTradeState
from src.trading.execution.models import ExecutionDecision
from src.trading.journal.service import JournalService
from src.trading.position.state import PositionState
class ExecutionEngine:
_position = PositionState()
# получить текущую paper-позицию
def get_position(self) -> PositionState:
return self._position
# обработать состояние автоторговли и принять paper-execution решение
def process(self, state: AutoTradeState) -> ExecutionDecision:
self._sync_state_from_position(state)
if state.status != "RUNNING":
return ExecutionDecision(
action="NONE",
can_execute=False,
reason="Execution доступен только в режиме RUNNING.",
)
if state.decision_status != "READY" or not state.is_signal_ready:
return ExecutionDecision(
action="NONE",
can_execute=False,
reason="Сигнал ещё не готов к execution.",
)
if state.last_signal == "BUY":
return self._open_position_if_empty(
state=state,
side="LONG",
action="OPEN_LONG",
)
if state.last_signal == "SELL":
return self._open_position_if_empty(
state=state,
side="SHORT",
action="OPEN_SHORT",
)
return ExecutionDecision(
action="NONE",
can_execute=False,
reason="Нет торгового действия.",
)
# открыть paper-позицию, если позиции ещё нет
def _open_position_if_empty(
self,
*,
state: AutoTradeState,
side: str,
action: str,
) -> ExecutionDecision:
if self._position.side != "NONE":
self._sync_state_from_position(state)
return ExecutionDecision(
action="NONE",
can_execute=False,
reason="Позиция уже открыта.",
)
try:
ticker = ExchangeService().get_price(state.symbol)
entry_price = ticker.price
except Exception as exc:
return ExecutionDecision(
action="NONE",
can_execute=False,
reason=f"Не удалось получить цену для paper execution: {exc}",
)
now = datetime.now().strftime("%H:%M:%S")
self._position.side = side
self._position.symbol = state.symbol
self._position.entry_price = entry_price
self._position.size = self._calculate_position_size(state)
self._position.leverage = state.leverage
self._position.unrealized_pnl_usd = 0.0
self._position.opened_at = now
self._position.updated_at = now
self._sync_state_from_position(state)
JournalService().log_ui_info(
event_type="paper_position_opened",
message=f"Paper-позиция открыта: {side} {state.symbol}",
screen="auto",
action="paper_execution",
payload={
"symbol": state.symbol,
"side": side,
"entry_price": entry_price,
"size": self._position.size,
"leverage": state.leverage,
"signal": state.last_signal,
"confidence": state.last_signal_confidence,
},
)
EventBus.emit(
"paper_position_opened",
{
"symbol": state.symbol,
"side": side,
"entry_price": entry_price,
"size": self._position.size,
"leverage": state.leverage,
},
)
return ExecutionDecision(
action=action,
can_execute=True,
reason=f"Paper-позиция {side} открыта.",
)
# временный расчёт размера позиции для paper mode
def _calculate_position_size(self, state: AutoTradeState) -> float:
risk_percent = state.risk_percent or 0.0
leverage = state.leverage or 1.0
return round((risk_percent * leverage) / 100, 8)
# синхронизировать AutoTradeState с текущей paper-позицией
def _sync_state_from_position(self, state: AutoTradeState) -> None:
state.position_side = self._position.side
state.entry_price = self._position.entry_price
state.position_size = self._position.size
state.unrealized_pnl_usd = self._position.unrealized_pnl_usd

View File

@@ -0,0 +1,17 @@
# app/src/trading/execution/models.py
from __future__ import annotations
from dataclasses import dataclass
@dataclass(slots=True)
class ExecutionDecision:
# действие: NONE / OPEN_LONG / OPEN_SHORT
action: str
# можно ли выполнить действие
can_execute: bool
# причина решения
reason: str

View File

@@ -0,0 +1,32 @@
# app/src/trading/position/state.py
from __future__ import annotations
from dataclasses import dataclass
@dataclass(slots=True)
class PositionState:
# сторона позиции: NONE / LONG / SHORT
side: str = "NONE"
# торговый инструмент
symbol: str = ""
# цена входа
entry_price: float | None = None
# размер позиции
size: float | None = None
# плечо
leverage: float | None = None
# нереализованный PnL
unrealized_pnl_usd: float | None = None
# время открытия позиции
opened_at: str | None = None
# время последнего обновления позиции
updated_at: str | None = None

View File

@@ -13,6 +13,17 @@ class TrendStrategy:
_last_prices: dict[str, float] = {}
_threshold_percent = 0.02
# рассчитать уверенность сигнала по силе движения цены
def _calculate_confidence(self, change_percent: float) -> float:
strength = abs(change_percent) / self._threshold_percent
if strength < 1:
return 0.0
confidence = 0.35 + ((strength - 1) / 2) * 0.65
return round(min(1.0, confidence), 2)
# анализ простого тренда по изменению цены
def analyze(self, context: StrategyContext) -> SignalResult:
try:
@@ -53,7 +64,7 @@ class TrendStrategy:
return SignalResult(
signal=SignalType.BUY,
reason="Цена растёт выше порога тренда.",
confidence=min(1.0, abs(change_percent) / self._threshold_percent),
confidence=self._calculate_confidence(change_percent),
payload={
"strategy": self.name,
"symbol": symbol,
@@ -67,7 +78,7 @@ class TrendStrategy:
return SignalResult(
signal=SignalType.SELL,
reason="Цена падает ниже порога тренда.",
confidence=min(1.0, abs(change_percent) / self._threshold_percent),
confidence=self._calculate_confidence(change_percent),
payload={
"strategy": self.name,
"symbol": symbol,

View File

@@ -136,10 +136,18 @@
✔ duplicate info removal
### 07.4.3.2 — Engine Decoupling (NEXT)
split analysis / UI refresh
fast price polling (1s)
slow UI updates (event-driven / 60s)
anti-flood protection
split analysis / UI refresh
fast price polling (1s)
slow UI updates (event-driven / 60s)
anti-flood protection
### 07.4.3.3 — Paper Position & Execution Engine
- добавлен ExecutionEngine
- реализованы paper-позиции (LONG / SHORT)
- интеграция с AutoTradeService
- синхронизация с UI
- логирование paper execution
- EventBus события (paper_position_opened)
### 07.4.4
⏳ Grid Strategy

View File

@@ -119,6 +119,16 @@
---
### Stage 07.4.3.3 — Paper Position & Execution Engine ✅
- добавлен ExecutionEngine
- реализованы paper-позиции (LONG / SHORT)
- интеграция с AutoTradeService
- синхронизация с UI
- логирование paper execution
- EventBus события (paper_position_opened)
---
### 07.4.4
⏳ Grid strategy

View File

@@ -0,0 +1,188 @@
Готово. Ниже — документация этапа 07.4.3.3 в формате .md (можешь просто сохранить как файл).
Stage 07.4.3.3 — Paper Position & Execution Engine
📌 Overview
На этом этапе реализован базовый Execution Engine и paper-позиции (без реальных ордеров).
Система теперь:
* принимает решение READY
* открывает paper-позицию (LONG / SHORT)
* синхронизирует её с UI
* логирует событие
* генерирует событие через EventBus
🧠 Архитектура
Добавлены новые слои:
Strategy → Signal → Decision → ExecutionEngine → PositionState
📂 Новые модули
1. Position Layer
app/src/trading/position/state.py
Хранит текущее состояние позиции:
* side (NONE / LONG / SHORT)
* entry_price
* size
* leverage
* unrealized_pnl_usd
2. Execution Models
app/src/trading/execution/models.py
ExecutionDecision:
action: str
can_execute: bool
reason: str
3. Execution Engine
app/src/trading/execution/engine.py
Основные функции:
* принимает AutoTradeState
* проверяет:
* RUNNING
* READY
* is_signal_ready
* открывает paper-позицию
* пишет в журнал
* эмитит событие
⚙️ Логика execution
Условия открытия позиции:
status == RUNNING
decision_status == READY
is_signal_ready == True
нет открытой позиции
Mapping сигналов:
BUY → OPEN_LONG
SELL → OPEN_SHORT
📊 Расчёт позиции (временно)
size = (risk_percent * leverage) / 100
Это placeholder для будущего risk engine.
🔄 Интеграция с AutoTradeService
В run_cycle() добавлено:
ExecutionEngine().process(state)
Теперь execution вызывается автоматически после анализа.
🖥 UI изменения
Экран автоторговли теперь показывает:
Pos: LONG | Entry: $ ... | PnL: ...
или:
Pos: NONE
🧾 Журнал
При открытии позиции:
event_type = paper_position_opened
payload:
{
"symbol": "...",
"side": "LONG | SHORT",
"entry_price": ...,
"size": ...,
"leverage": ...,
"signal": "...",
"confidence": ...
}
⚡ EventBus
Новое событие:
paper_position_opened
🚧 Ограничения текущего этапа
* ❌ Нет закрытия позиции
* ❌ Нет расчёта PnL
* ❌ Нет реальных ордеров
* ❌ Нет стопов / тейков
* ❌ Нет multi-position
✅ Результат
Теперь система полностью проходит путь:
Strategy → Signal → Decision → Execution → Position → UI
🔜 Next
07.4.3.4 — Strong Signal Alerts (Telegram notifications)
:
🗺 Обновление roadmap
Добавь:
Stage 07.4.3.3 — Paper Position & Execution Engine ✅
- добавлен ExecutionEngine
- реализованы paper-позиции (LONG / SHORT)
- интеграция с AutoTradeService
- синхронизация с UI
- логирование paper execution
- EventBus события (paper_position_opened)
Если готов — дальше сразу делаем 07.4.3.4 (уведомления в Telegram) 🚀