Stage 07.4.3.1 — trend strategy stabilization
This commit is contained in:
@@ -46,6 +46,101 @@ def _signal_label(signal: str | None) -> str:
|
|||||||
return mapping.get(signal or "", "—")
|
return mapping.get(signal or "", "—")
|
||||||
|
|
||||||
|
|
||||||
|
# красивое отображение решения
|
||||||
|
def _decision_label(status: str) -> str:
|
||||||
|
mapping = {
|
||||||
|
"WAITING": "🟡 Ожидание",
|
||||||
|
"CONFIRMING": "🟠 Подтверждение",
|
||||||
|
"READY": "🟢 Готово к входу",
|
||||||
|
"BLOCKED": "🔴 Заблокировано",
|
||||||
|
}
|
||||||
|
return mapping.get(status, status)
|
||||||
|
|
||||||
|
|
||||||
|
# компактное значение или заглушка
|
||||||
|
def _value_or_dash(value: object) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
# формат цены
|
||||||
|
def _price_or_dash(value: float | None) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return f"{value:.2f}"
|
||||||
|
|
||||||
|
|
||||||
|
# формат USD
|
||||||
|
def _usd_or_dash(value: float | None) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return f"{value:.2f} USD"
|
||||||
|
|
||||||
|
|
||||||
|
# формат размера позиции
|
||||||
|
def _size_or_dash(value: float | None) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return f"{value:.8f}".rstrip("0").rstrip(".")
|
||||||
|
|
||||||
|
|
||||||
|
# формат плеча
|
||||||
|
def _leverage_or_dash(value: float | None) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return f"{value:.1f}x"
|
||||||
|
|
||||||
|
|
||||||
|
# формат торгового инструмента для UI
|
||||||
|
def _format_symbol(symbol: str | None) -> str:
|
||||||
|
if not symbol:
|
||||||
|
return "—"
|
||||||
|
|
||||||
|
base_symbol = symbol.split("_", 1)[0]
|
||||||
|
parts = base_symbol.split("/", 1)
|
||||||
|
|
||||||
|
if len(parts) == 2:
|
||||||
|
return f"{parts[0]} / {parts[1]}"
|
||||||
|
|
||||||
|
return base_symbol
|
||||||
|
|
||||||
|
|
||||||
|
# стратегия для компактного UI
|
||||||
|
def _compact_strategy(strategy: str | None) -> str:
|
||||||
|
if not strategy:
|
||||||
|
return "—"
|
||||||
|
return strategy.upper()
|
||||||
|
|
||||||
|
|
||||||
|
# плечо для компактного UI
|
||||||
|
def _compact_leverage(value: float | None) -> str:
|
||||||
|
if value is None:
|
||||||
|
return "—"
|
||||||
|
return f"x{value:g}"
|
||||||
|
|
||||||
|
|
||||||
|
# проверка, настроена ли автоторговля минимально
|
||||||
|
def _is_auto_configured(state) -> bool:
|
||||||
|
return bool(
|
||||||
|
state.symbol
|
||||||
|
and state.strategy
|
||||||
|
and state.risk_percent is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# строка инструмента / стратегии / плеча
|
||||||
|
def _context_line(state) -> str:
|
||||||
|
symbol = _format_symbol(state.symbol)
|
||||||
|
strategy = _compact_strategy(state.strategy)
|
||||||
|
leverage = _compact_leverage(state.leverage)
|
||||||
|
|
||||||
|
if leverage == "—":
|
||||||
|
return f"{symbol} · {strategy}"
|
||||||
|
|
||||||
|
return f"{symbol} · {strategy} · {leverage}"
|
||||||
|
|
||||||
|
|
||||||
# клавиатура автоторговли
|
# клавиатура автоторговли
|
||||||
def _auto_keyboard() -> InlineKeyboardMarkup:
|
def _auto_keyboard() -> InlineKeyboardMarkup:
|
||||||
builder = InlineKeyboardBuilder()
|
builder = InlineKeyboardBuilder()
|
||||||
@@ -64,21 +159,46 @@ def _build_auto_text() -> str:
|
|||||||
service = AutoTradeService()
|
service = AutoTradeService()
|
||||||
state = service.get_state()
|
state = service.get_state()
|
||||||
|
|
||||||
strategy = _strategy_label(state.strategy)
|
account_mode = "DEMO" if "DEMO" in mode_line().upper() else "LIVE"
|
||||||
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 "—"
|
||||||
|
configured = _is_auto_configured(state)
|
||||||
|
|
||||||
|
header = (
|
||||||
|
"<b>🤖 Автоторговля</b>\n"
|
||||||
|
f"🔸 {account_mode} аккаунт\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
if state.status == "OFF":
|
||||||
|
if not configured:
|
||||||
|
return (
|
||||||
|
f"{header}"
|
||||||
|
"⚪ Выключена\n\n"
|
||||||
|
"⚠️ Не настроена\n"
|
||||||
|
"Настрой параметры"
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
f"{header}"
|
||||||
|
"⚪ Выключена\n\n"
|
||||||
|
f"{_context_line(state)}\n"
|
||||||
|
f"Risk: {risk}"
|
||||||
|
)
|
||||||
|
|
||||||
|
status_line = (
|
||||||
|
"🟢 Активна"
|
||||||
|
if state.status == "RUNNING"
|
||||||
|
else "👀 Наблюдение"
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
"<b>🤖 Автоторговля</b>\n"
|
f"{header}"
|
||||||
f"{mode_line()}"
|
f"{status_line}\n\n"
|
||||||
f"Статус: {_status_label(state.status)}\n"
|
f"{_context_line(state)}\n\n"
|
||||||
f"Стратегия: {strategy}\n"
|
f"{_signal_label(state.last_signal)} ×{state.last_signal_repeat_count} "
|
||||||
f"Инструмент: {state.symbol}\n"
|
f"· {state.decision_status}\n\n"
|
||||||
f"Риск: {risk}\n"
|
f"Pos: {_value_or_dash(state.position_side)} | "
|
||||||
f"PnL: {state.pnl_usd:.2f} USD\n"
|
f"PnL: {_usd_or_dash(state.unrealized_pnl_usd)}\n"
|
||||||
f"Последний анализ: {state.last_check_at or '—'}\n"
|
f"Risk: {risk}"
|
||||||
f"Сигнал: {_signal_label(state.last_signal)} · {state.last_signal_repeat_count} подряд\n"
|
|
||||||
f"Уверенность: {state.last_signal_confidence:.2f}\n"
|
|
||||||
f"Причина: {state.last_signal_reason or '—'}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -168,10 +168,16 @@ async def open_system_management(callback: CallbackQuery) -> None:
|
|||||||
@router.callback_query(F.data == "settings:auto")
|
@router.callback_query(F.data == "settings:auto")
|
||||||
async def open_auto_settings(callback: CallbackQuery) -> None:
|
async def open_auto_settings(callback: CallbackQuery) -> None:
|
||||||
AutoTradeRunner.set_current_screen("settings_auto")
|
AutoTradeRunner.set_current_screen("settings_auto")
|
||||||
|
|
||||||
if callback.message is None:
|
if callback.message is None:
|
||||||
await callback.answer("Сообщение не найдено", show_alert=True)
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
AutoTradeRunner.unregister_screen(
|
||||||
|
chat_id=callback.message.chat.id,
|
||||||
|
message_id=callback.message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
state = AutoTradeService().get_state()
|
state = AutoTradeService().get_state()
|
||||||
|
|
||||||
strategy_map = {
|
strategy_map = {
|
||||||
@@ -181,13 +187,15 @@ async def open_auto_settings(callback: CallbackQuery) -> None:
|
|||||||
}
|
}
|
||||||
strategy = strategy_map.get(state.strategy or "", "—")
|
strategy = strategy_map.get(state.strategy or "", "—")
|
||||||
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 "—"
|
||||||
|
leverage = f"x{state.leverage:g}" if state.leverage is not None else "—"
|
||||||
|
|
||||||
text = (
|
text = (
|
||||||
"<b>🤖 Автоторговля</b>\n\n"
|
"<b>🤖 Автоторговля</b>\n\n"
|
||||||
"<b>СИСТЕМА</b> · Настройки\n\n"
|
"<b>СИСТЕМА</b> · Настройки\n\n"
|
||||||
f"Стратегия: {strategy}\n"
|
f"Стратегия: {strategy}\n"
|
||||||
f"Инструмент: {state.symbol}\n"
|
f"Инструмент: {state.symbol}\n"
|
||||||
f"Риск: {risk}\n\n"
|
f"Риск: {risk}\n"
|
||||||
|
f"Плечо: {leverage}\n\n"
|
||||||
"Выберите настройку:"
|
"Выберите настройку:"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -195,9 +203,10 @@ async def open_auto_settings(callback: CallbackQuery) -> None:
|
|||||||
builder.button(text="🧠 Стратегия", callback_data="settings:auto_strategy")
|
builder.button(text="🧠 Стратегия", callback_data="settings:auto_strategy")
|
||||||
builder.button(text="📈 Инструмент", callback_data="settings:auto_symbol")
|
builder.button(text="📈 Инструмент", callback_data="settings:auto_symbol")
|
||||||
builder.button(text="🛡️ Риск", callback_data="settings:auto_risk")
|
builder.button(text="🛡️ Риск", callback_data="settings:auto_risk")
|
||||||
|
builder.button(text="⚙️ Плечо", callback_data="settings:auto_leverage")
|
||||||
builder.button(text="⬅️ Назад", callback_data="system:management")
|
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||||
builder.button(text="🤖 Автоторговля", callback_data="auto:home")
|
builder.button(text="🤖 Автоторговля", callback_data="auto:home")
|
||||||
builder.adjust(2, 1, 2)
|
builder.adjust(2, 2, 2)
|
||||||
|
|
||||||
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
@@ -313,6 +322,46 @@ async def set_auto_risk(callback: CallbackQuery) -> None:
|
|||||||
await callback.answer("Риск обновлён")
|
await callback.answer("Риск обновлён")
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data == "settings:auto_leverage")
|
||||||
|
async def open_auto_leverage_settings(callback: CallbackQuery) -> None:
|
||||||
|
AutoTradeRunner.set_current_screen("settings_auto")
|
||||||
|
|
||||||
|
if callback.message is None:
|
||||||
|
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = (
|
||||||
|
"<b>⚙️ Плечо</b>\n\n"
|
||||||
|
"<b>СИСТЕМА</b> · Настройки · Автоторговля\n\n"
|
||||||
|
"Выберите плечо:"
|
||||||
|
)
|
||||||
|
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
builder.button(text="x1", callback_data="settings:auto_leverage:1")
|
||||||
|
builder.button(text="x2", callback_data="settings:auto_leverage:2")
|
||||||
|
builder.button(text="x3", callback_data="settings:auto_leverage:3")
|
||||||
|
builder.button(text="x5", callback_data="settings:auto_leverage:5")
|
||||||
|
builder.button(text="x10", callback_data="settings:auto_leverage:10")
|
||||||
|
builder.button(text="x20", callback_data="settings:auto_leverage:20")
|
||||||
|
builder.button(text="⬅️ Назад", callback_data="settings:auto")
|
||||||
|
builder.adjust(3, 3, 1)
|
||||||
|
|
||||||
|
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(F.data.startswith("settings:auto_leverage:"))
|
||||||
|
async def set_auto_leverage(callback: CallbackQuery) -> None:
|
||||||
|
leverage = float(callback.data.split(":", 2)[2])
|
||||||
|
AutoTradeService().set_leverage(leverage)
|
||||||
|
|
||||||
|
if callback.message is not None:
|
||||||
|
await open_auto_settings(callback)
|
||||||
|
|
||||||
|
AutoTradeRunner.set_current_screen("settings_auto")
|
||||||
|
await callback.answer("Плечо обновлено")
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == "settings:trade")
|
@router.callback_query(F.data == "settings:trade")
|
||||||
async def open_trade_settings(callback: CallbackQuery) -> None:
|
async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||||
if callback.message is None:
|
if callback.message is None:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
from aiogram import Bot
|
from aiogram import Bot
|
||||||
|
from aiogram.exceptions import TelegramBadRequest, TelegramRetryAfter
|
||||||
|
|
||||||
from src.trading.auto.service import AutoTradeService
|
from src.trading.auto.service import AutoTradeService
|
||||||
|
|
||||||
@@ -18,9 +20,11 @@ class AutoTradeRunner:
|
|||||||
_render_text: Callable[[], str] | None = None
|
_render_text: Callable[[], str] | None = None
|
||||||
_render_markup: Callable[[], object] | None = None
|
_render_markup: Callable[[], object] | None = None
|
||||||
_current_screen: str | None = None
|
_current_screen: str | None = None
|
||||||
_interval_seconds = 5
|
_interval_seconds = 15
|
||||||
|
|
||||||
|
_last_text: str | None = None
|
||||||
|
_retry_after_until: float = 0.0
|
||||||
|
|
||||||
# зарегистрировать live-экран для автообновления
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_screen(
|
def register_screen(
|
||||||
cls,
|
cls,
|
||||||
@@ -36,8 +40,8 @@ class AutoTradeRunner:
|
|||||||
cls._message_id = message_id
|
cls._message_id = message_id
|
||||||
cls._render_text = render_text
|
cls._render_text = render_text
|
||||||
cls._render_markup = render_markup
|
cls._render_markup = render_markup
|
||||||
|
cls._last_text = None
|
||||||
|
|
||||||
# удалить ранее зарегистрированный live-экран
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_registered_screen(
|
async def delete_registered_screen(
|
||||||
cls,
|
cls,
|
||||||
@@ -62,13 +66,27 @@ class AutoTradeRunner:
|
|||||||
cls._message_id = None
|
cls._message_id = None
|
||||||
cls._render_text = None
|
cls._render_text = None
|
||||||
cls._render_markup = None
|
cls._render_markup = None
|
||||||
|
cls._last_text = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister_screen(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
chat_id: int,
|
||||||
|
message_id: int,
|
||||||
|
) -> None:
|
||||||
|
if cls._chat_id != chat_id or cls._message_id != message_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
cls._message_id = None
|
||||||
|
cls._render_text = None
|
||||||
|
cls._render_markup = None
|
||||||
|
cls._last_text = None
|
||||||
|
|
||||||
# переключить активный экран
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_current_screen(cls, screen: str) -> None:
|
def set_current_screen(cls, screen: str) -> None:
|
||||||
cls._current_screen = screen
|
cls._current_screen = screen
|
||||||
|
|
||||||
# запустить background runner
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def start(cls) -> None:
|
def start(cls) -> None:
|
||||||
if cls._task is not None and not cls._task.done():
|
if cls._task is not None and not cls._task.done():
|
||||||
@@ -76,7 +94,6 @@ class AutoTradeRunner:
|
|||||||
|
|
||||||
cls._task = asyncio.create_task(cls._worker())
|
cls._task = asyncio.create_task(cls._worker())
|
||||||
|
|
||||||
# остановить background runner
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def stop(cls) -> None:
|
def stop(cls) -> None:
|
||||||
if cls._task is None:
|
if cls._task is None:
|
||||||
@@ -85,7 +102,6 @@ class AutoTradeRunner:
|
|||||||
cls._task.cancel()
|
cls._task.cancel()
|
||||||
cls._task = None
|
cls._task = None
|
||||||
|
|
||||||
# background loop автоторговли
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _worker(cls) -> None:
|
async def _worker(cls) -> None:
|
||||||
service = AutoTradeService()
|
service = AutoTradeService()
|
||||||
@@ -102,9 +118,11 @@ class AutoTradeRunner:
|
|||||||
await cls._refresh_screen()
|
await cls._refresh_screen()
|
||||||
await asyncio.sleep(cls._interval_seconds)
|
await asyncio.sleep(cls._interval_seconds)
|
||||||
|
|
||||||
# обновить live-экран Telegram
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _refresh_screen(cls) -> None:
|
async def _refresh_screen(cls) -> None:
|
||||||
|
if time.monotonic() < cls._retry_after_until:
|
||||||
|
return
|
||||||
|
|
||||||
if not all(
|
if not all(
|
||||||
[
|
[
|
||||||
cls._bot,
|
cls._bot,
|
||||||
@@ -116,12 +134,36 @@ class AutoTradeRunner:
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
text = cls._render_text()
|
||||||
|
|
||||||
|
if text == cls._last_text:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await cls._bot.edit_message_text(
|
await cls._bot.edit_message_text(
|
||||||
chat_id=cls._chat_id,
|
chat_id=cls._chat_id,
|
||||||
message_id=cls._message_id,
|
message_id=cls._message_id,
|
||||||
text=cls._render_text(),
|
text=text,
|
||||||
reply_markup=cls._render_markup(),
|
reply_markup=cls._render_markup(),
|
||||||
)
|
)
|
||||||
|
cls._last_text = text
|
||||||
|
|
||||||
|
except TelegramRetryAfter as exc:
|
||||||
|
cls._retry_after_until = time.monotonic() + exc.retry_after + 5
|
||||||
|
|
||||||
|
except TelegramBadRequest as exc:
|
||||||
|
error_text = str(exc).lower()
|
||||||
|
|
||||||
|
if "message is not modified" in error_text:
|
||||||
|
cls._last_text = text
|
||||||
|
return
|
||||||
|
|
||||||
|
if "message to edit not found" in error_text:
|
||||||
|
cls._message_id = None
|
||||||
|
cls._render_text = None
|
||||||
|
cls._render_markup = None
|
||||||
|
cls._last_text = None
|
||||||
|
return
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -16,8 +16,18 @@ class AutoTradeService:
|
|||||||
_state = AutoTradeState()
|
_state = AutoTradeState()
|
||||||
_loop_task: asyncio.Task | None = None
|
_loop_task: asyncio.Task | None = None
|
||||||
_loop_interval_seconds = 5
|
_loop_interval_seconds = 5
|
||||||
|
|
||||||
|
# минимальное количество повторов BUY / SELL для подтверждения сигнала
|
||||||
|
_confirm_repeats = 3
|
||||||
|
|
||||||
|
# минимальная уверенность для готовности к будущему execution
|
||||||
|
_ready_confidence = 0.7
|
||||||
|
|
||||||
_last_signal_key: str | None = None
|
_last_signal_key: str | None = None
|
||||||
_last_signal_value: str | None = None
|
_last_signal_value: str | None = None
|
||||||
|
_last_signal_reason: str = ""
|
||||||
|
_last_signal_confidence: float = 0.0
|
||||||
|
_last_signal_payload: dict | None = None
|
||||||
_same_signal_count = 0
|
_same_signal_count = 0
|
||||||
|
|
||||||
# получить текущее состояние автоторговли
|
# получить текущее состояние автоторговли
|
||||||
@@ -106,15 +116,14 @@ class AutoTradeService:
|
|||||||
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
|
||||||
|
self._reset_signal_tracking()
|
||||||
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.strip().upper()
|
state.strategy = strategy.strip().upper()
|
||||||
self._last_signal_key = None
|
self._reset_signal_tracking()
|
||||||
self._last_signal_value = None
|
|
||||||
self._same_signal_count = 0
|
|
||||||
return state
|
return state
|
||||||
|
|
||||||
# установить риск
|
# установить риск
|
||||||
@@ -122,6 +131,30 @@ class AutoTradeService:
|
|||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
state.risk_percent = risk_percent
|
state.risk_percent = risk_percent
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
# установить плечо
|
||||||
|
def set_leverage(self, leverage: float) -> AutoTradeState:
|
||||||
|
state = self.get_state()
|
||||||
|
state.leverage = leverage
|
||||||
|
return state
|
||||||
|
|
||||||
|
# сбросить внутренний трекинг сигналов
|
||||||
|
def _reset_signal_tracking(self) -> None:
|
||||||
|
self._last_signal_key = None
|
||||||
|
self._last_signal_value = None
|
||||||
|
self._last_signal_reason = ""
|
||||||
|
self._last_signal_confidence = 0.0
|
||||||
|
self._last_signal_payload = None
|
||||||
|
self._same_signal_count = 0
|
||||||
|
|
||||||
|
state = self.get_state()
|
||||||
|
state.last_signal_repeat_count = 0
|
||||||
|
state.last_signal_confidence = 0.0
|
||||||
|
state.last_signal_reason = None
|
||||||
|
state.decision_status = "WAITING"
|
||||||
|
state.decision_reason = None
|
||||||
|
state.is_signal_confirmed = False
|
||||||
|
state.is_signal_ready = False
|
||||||
|
|
||||||
# собрать контекст для стратегии
|
# собрать контекст для стратегии
|
||||||
def _build_strategy_context(self) -> StrategyContext:
|
def _build_strategy_context(self) -> StrategyContext:
|
||||||
@@ -138,6 +171,46 @@ class AutoTradeService:
|
|||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
return StrategyRegistry.get(state.strategy)
|
return StrategyRegistry.get(state.strategy)
|
||||||
|
|
||||||
|
# обновить статус решения по текущему сигналу
|
||||||
|
def _update_decision_state(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str,
|
||||||
|
confidence: float,
|
||||||
|
) -> None:
|
||||||
|
state.is_signal_confirmed = False
|
||||||
|
state.is_signal_ready = False
|
||||||
|
|
||||||
|
if signal == "HOLD":
|
||||||
|
state.decision_status = "WAITING"
|
||||||
|
state.decision_reason = "Нет торгового направления."
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._same_signal_count < self._confirm_repeats:
|
||||||
|
state.decision_status = "CONFIRMING"
|
||||||
|
state.decision_reason = (
|
||||||
|
f"Сигнал {signal} подтверждается: "
|
||||||
|
f"{self._same_signal_count}/{self._confirm_repeats} повторов."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
state.is_signal_confirmed = True
|
||||||
|
|
||||||
|
if confidence < self._ready_confidence:
|
||||||
|
state.decision_status = "BLOCKED"
|
||||||
|
state.decision_reason = (
|
||||||
|
f"Сигнал {signal} подтверждён, но уверенность низкая: "
|
||||||
|
f"{confidence:.2f} < {self._ready_confidence:.2f}."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
state.is_signal_ready = True
|
||||||
|
state.decision_status = "READY"
|
||||||
|
state.decision_reason = (
|
||||||
|
f"Сигнал {signal} подтверждён и готов к будущему execution."
|
||||||
|
)
|
||||||
|
|
||||||
# записать новый сигнал и итог предыдущей серии при смене сигнала
|
# записать новый сигнал и итог предыдущей серии при смене сигнала
|
||||||
def _log_signal_if_changed(
|
def _log_signal_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -156,6 +229,12 @@ class AutoTradeService:
|
|||||||
|
|
||||||
if is_same_signal:
|
if is_same_signal:
|
||||||
self._same_signal_count += 1
|
self._same_signal_count += 1
|
||||||
|
self._update_signal_state_fields(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
reason=reason,
|
||||||
|
confidence=confidence,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if previous_signal is not None:
|
if previous_signal is not None:
|
||||||
@@ -166,6 +245,9 @@ class AutoTradeService:
|
|||||||
previous_signal=previous_signal,
|
previous_signal=previous_signal,
|
||||||
previous_count=previous_count,
|
previous_count=previous_count,
|
||||||
next_signal=signal,
|
next_signal=signal,
|
||||||
|
reason=self._last_signal_reason,
|
||||||
|
confidence=self._last_signal_confidence,
|
||||||
|
payload=self._last_signal_payload,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._log_signal_event(
|
self._log_signal_event(
|
||||||
@@ -173,7 +255,7 @@ class AutoTradeService:
|
|||||||
state=state,
|
state=state,
|
||||||
signal=previous_signal,
|
signal=previous_signal,
|
||||||
reason=f"{previous_signal} завершился без серии.",
|
reason=f"{previous_signal} завершился без серии.",
|
||||||
confidence=0.0,
|
confidence=self._last_signal_confidence,
|
||||||
payload={
|
payload={
|
||||||
"previous_signal": previous_signal,
|
"previous_signal": previous_signal,
|
||||||
"next_signal": signal,
|
"next_signal": signal,
|
||||||
@@ -182,13 +264,39 @@ class AutoTradeService:
|
|||||||
|
|
||||||
self._last_signal_key = signal_key
|
self._last_signal_key = signal_key
|
||||||
self._last_signal_value = signal
|
self._last_signal_value = signal
|
||||||
|
self._last_signal_reason = reason
|
||||||
|
self._last_signal_confidence = confidence
|
||||||
|
self._last_signal_payload = payload
|
||||||
self._same_signal_count = 1
|
self._same_signal_count = 1
|
||||||
|
|
||||||
# Новый сигнал не пишем сразу.
|
self._update_signal_state_fields(
|
||||||
# Он попадёт в журнал при следующей смене сигнала:
|
state=state,
|
||||||
# либо как одиночный сигнал, либо как серия.
|
signal=signal,
|
||||||
|
reason=reason,
|
||||||
# записать сам сигнал в журнал
|
confidence=confidence,
|
||||||
|
)
|
||||||
|
|
||||||
|
# обновить поля state для экрана автоторговли
|
||||||
|
def _update_signal_state_fields(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str,
|
||||||
|
reason: str,
|
||||||
|
confidence: float,
|
||||||
|
) -> None:
|
||||||
|
state.last_signal = signal
|
||||||
|
state.last_signal_repeat_count = self._same_signal_count
|
||||||
|
state.last_signal_confidence = confidence
|
||||||
|
state.last_signal_reason = reason
|
||||||
|
|
||||||
|
self._update_decision_state(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
confidence=confidence,
|
||||||
|
)
|
||||||
|
|
||||||
|
# записать одиночный сигнал в журнал
|
||||||
def _log_signal_event(
|
def _log_signal_event(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -220,7 +328,7 @@ class AutoTradeService:
|
|||||||
"confidence": confidence,
|
"confidence": confidence,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
"repeat_count": 1,
|
"repeat_count": 1,
|
||||||
"is_strong_signal": confidence > 0.7,
|
"is_strong_signal": confidence > self._ready_confidence,
|
||||||
"is_aggregated": False,
|
"is_aggregated": False,
|
||||||
"payload": payload or {},
|
"payload": payload or {},
|
||||||
},
|
},
|
||||||
@@ -237,6 +345,9 @@ class AutoTradeService:
|
|||||||
previous_signal: str,
|
previous_signal: str,
|
||||||
previous_count: int,
|
previous_count: int,
|
||||||
next_signal: str,
|
next_signal: str,
|
||||||
|
reason: str,
|
||||||
|
confidence: float,
|
||||||
|
payload: dict | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
emoji_map = {
|
emoji_map = {
|
||||||
"BUY": "🟢",
|
"BUY": "🟢",
|
||||||
@@ -261,7 +372,11 @@ class AutoTradeService:
|
|||||||
"signal": previous_signal,
|
"signal": previous_signal,
|
||||||
"next_signal": next_signal,
|
"next_signal": next_signal,
|
||||||
"repeat_count": previous_count,
|
"repeat_count": previous_count,
|
||||||
|
"confidence": confidence,
|
||||||
|
"reason": reason,
|
||||||
|
"is_strong_signal": confidence > self._ready_confidence,
|
||||||
"is_aggregated": True,
|
"is_aggregated": True,
|
||||||
|
"payload": payload or {},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -279,7 +394,6 @@ class AutoTradeService:
|
|||||||
result = strategy.analyze(context)
|
result = strategy.analyze(context)
|
||||||
|
|
||||||
state.last_check_at = datetime.now().strftime("%H:%M:%S")
|
state.last_check_at = datetime.now().strftime("%H:%M:%S")
|
||||||
state.last_signal = result.signal.value
|
|
||||||
|
|
||||||
self._log_signal_if_changed(
|
self._log_signal_if_changed(
|
||||||
strategy_name=strategy.name,
|
strategy_name=strategy.name,
|
||||||
@@ -290,8 +404,4 @@ class AutoTradeService:
|
|||||||
payload=result.payload,
|
payload=result.payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
state.last_signal_repeat_count = self._same_signal_count
|
|
||||||
state.last_signal_confidence = result.confidence
|
|
||||||
state.last_signal_reason = result.reason
|
|
||||||
|
|
||||||
return state
|
return state
|
||||||
@@ -25,7 +25,7 @@ class AutoTradeState:
|
|||||||
# время последней проверки
|
# время последней проверки
|
||||||
last_check_at: str | None = None
|
last_check_at: str | None = None
|
||||||
|
|
||||||
# последний сигнал стратегии
|
# последний сырой сигнал стратегии
|
||||||
last_signal: str | None = None
|
last_signal: str | None = None
|
||||||
|
|
||||||
# количество одинаковых сигналов подряд
|
# количество одинаковых сигналов подряд
|
||||||
@@ -35,4 +35,34 @@ class AutoTradeState:
|
|||||||
last_signal_confidence: float = 0.0
|
last_signal_confidence: float = 0.0
|
||||||
|
|
||||||
# причина последнего сигнала
|
# причина последнего сигнала
|
||||||
last_signal_reason: str | None = None
|
last_signal_reason: str | None = None
|
||||||
|
|
||||||
|
# статус торгового решения: WAITING / CONFIRMING / READY / BLOCKED
|
||||||
|
decision_status: str = "WAITING"
|
||||||
|
|
||||||
|
# человекочитаемое объяснение решения
|
||||||
|
decision_reason: str | None = None
|
||||||
|
|
||||||
|
# сигнал подтверждён по количеству повторов
|
||||||
|
is_signal_confirmed: bool = False
|
||||||
|
|
||||||
|
# сигнал готов к будущему execution
|
||||||
|
is_signal_ready: bool = False
|
||||||
|
|
||||||
|
# текущая позиция: NONE / LONG / SHORT
|
||||||
|
position_side: str = "NONE"
|
||||||
|
|
||||||
|
# цена входа
|
||||||
|
entry_price: float | None = None
|
||||||
|
|
||||||
|
# размер позиции
|
||||||
|
position_size: float | None = None
|
||||||
|
|
||||||
|
# нереализованный PnL
|
||||||
|
unrealized_pnl_usd: float | None = None
|
||||||
|
|
||||||
|
# максимальная просадка
|
||||||
|
max_drawdown_usd: float | None = None
|
||||||
|
|
||||||
|
# плечо
|
||||||
|
leverage: float | None = 2.0
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
✔ стратегия
|
✔ стратегия
|
||||||
✔ риск
|
✔ риск
|
||||||
✔ символ
|
✔ символ
|
||||||
|
✔ leverage (default x2)
|
||||||
|
|
||||||
## 07.3 — Analysis Cycle
|
## 07.3 — Analysis Cycle
|
||||||
✔ run_cycle()
|
✔ run_cycle()
|
||||||
@@ -122,8 +123,23 @@
|
|||||||
### 07.4.2
|
### 07.4.2
|
||||||
✔ Strategy Registry
|
✔ Strategy Registry
|
||||||
|
|
||||||
### 07.4.3
|
### 07.4.3 — Trend Strategy
|
||||||
⏳ Trend Strategy
|
✔ signal generation
|
||||||
|
✔ repeat confirmation logic
|
||||||
|
✔ confidence scoring
|
||||||
|
✔ UI integration
|
||||||
|
|
||||||
|
### 07.4.3.1 — UI Optimization
|
||||||
|
✔ compact auto screen
|
||||||
|
✔ state-based rendering (OFF / RUNNING / OBSERVING)
|
||||||
|
✔ minimal trading layout
|
||||||
|
✔ 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
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid Strategy
|
⏳ Grid Strategy
|
||||||
@@ -166,5 +182,5 @@
|
|||||||
|
|
||||||
## Текущий статус проекта
|
## Текущий статус проекта
|
||||||
|
|
||||||
👉 Завершён: 07.4.1
|
👉 Завершён: 07.4.3.1
|
||||||
👉 Следующий шаг: 07.4.2 Strategy Registry
|
👉 Следующий шаг: 07.4.3.2 — Engine Decoupling + Price Polling
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
✔ стратегия
|
✔ стратегия
|
||||||
✔ риск
|
✔ риск
|
||||||
✔ символ
|
✔ символ
|
||||||
✔ presets UI
|
✔ leverage (default x2)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,8 +86,38 @@
|
|||||||
### 07.4.2
|
### 07.4.2
|
||||||
✔ registry стратегий
|
✔ registry стратегий
|
||||||
|
|
||||||
### 07.4.3
|
---
|
||||||
⏳ Trend strategy
|
|
||||||
|
### 07.4.3 — Trend Strategy
|
||||||
|
✔ генерация сигналов
|
||||||
|
✔ repeat tracking
|
||||||
|
✔ confidence logic
|
||||||
|
✔ decision state (WAITING / CONFIRMING / READY / BLOCKED)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 07.4.3.1 — UI Optimization
|
||||||
|
✔ компактный экран автоторговли
|
||||||
|
✔ разделение OFF / ACTIVE / OBSERVING
|
||||||
|
✔ убраны дубли (WAITING / HOLD и т.д.)
|
||||||
|
✔ оптимизация под mobile
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 07.4.3.2 — Engine Decoupling (NEXT)
|
||||||
|
⏳ разделение:
|
||||||
|
- analysis loop (частый)
|
||||||
|
- UI loop (редкий)
|
||||||
|
|
||||||
|
⏳ price polling:
|
||||||
|
- быстрый (1s)
|
||||||
|
- независимый от UI
|
||||||
|
|
||||||
|
⏳ Telegram:
|
||||||
|
- обновление только при изменении состояния
|
||||||
|
- защита от flood control
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### 07.4.4
|
### 07.4.4
|
||||||
⏳ Grid strategy
|
⏳ Grid strategy
|
||||||
@@ -99,5 +129,5 @@
|
|||||||
|
|
||||||
## Текущий статус
|
## Текущий статус
|
||||||
|
|
||||||
👉 Завершён: 07.4.1
|
👉 Завершён: 07.4.3.1
|
||||||
👉 Следующий шаг: 07.4.2
|
👉 Следующий шаг: 07.4.3.2 — Decoupling + Price Polling
|
||||||
289
docs/stages/stage-07_4_3_1-trend-strategy-stabilization.md
Normal file
289
docs/stages/stage-07_4_3_1-trend-strategy-stabilization.md
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
# 07.4.3.1 — Trend Strategy Stabilization
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Стабилизировать сигналы автоторговли перед будущим execution-слоем.
|
||||||
|
|
||||||
|
На этапе 07.4.3 бот уже начал генерировать реальные BUY / SELL / HOLD сигналы. На этапе 07.4.3.1 добавлена логика, которая отделяет сырой сигнал стратегии от торгового решения.
|
||||||
|
|
||||||
|
Главная цель:
|
||||||
|
|
||||||
|
- не реагировать на одиночные шумовые сигналы;
|
||||||
|
- подтверждать BUY / SELL через серию повторов;
|
||||||
|
- учитывать confidence;
|
||||||
|
- показывать пользователю не только сигнал, но и статус решения;
|
||||||
|
- подготовить систему к безопасному execution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Что реализовано
|
||||||
|
|
||||||
|
### 1. Decision state
|
||||||
|
|
||||||
|
В состояние автоторговли добавлены поля торгового решения:
|
||||||
|
|
||||||
|
- decision_status;
|
||||||
|
- decision_reason;
|
||||||
|
- is_signal_confirmed;
|
||||||
|
- is_signal_ready.
|
||||||
|
|
||||||
|
Статусы решения:
|
||||||
|
|
||||||
|
- WAITING — нет торгового направления;
|
||||||
|
- CONFIRMING — сигнал есть, но ещё подтверждается;
|
||||||
|
- READY — сигнал подтверждён и готов к будущему execution;
|
||||||
|
- BLOCKED — сигнал подтверждён, но заблокирован условиями фильтра.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Подтверждение сигнала через повторы
|
||||||
|
|
||||||
|
BUY / SELL больше не считаются готовыми сразу после первого появления.
|
||||||
|
|
||||||
|
Текущая логика:
|
||||||
|
|
||||||
|
- BUY / SELL 1 раз → CONFIRMING;
|
||||||
|
- BUY / SELL 2 раза → CONFIRMING;
|
||||||
|
- BUY / SELL 3 раза → потенциально READY;
|
||||||
|
- HOLD → WAITING.
|
||||||
|
|
||||||
|
Текущий порог подтверждения:
|
||||||
|
|
||||||
|
- 3 одинаковых сигнала подряд.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Фильтр confidence
|
||||||
|
|
||||||
|
Даже подтверждённый сигнал не становится READY, если confidence ниже порога.
|
||||||
|
|
||||||
|
Текущий порог:
|
||||||
|
|
||||||
|
- confidence >= 0.70.
|
||||||
|
|
||||||
|
Если сигнал повторился нужное количество раз, но confidence ниже 0.70, решение получает статус BLOCKED.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Разделение raw signal и decision
|
||||||
|
|
||||||
|
Сигнал стратегии и торговое решение теперь разделены.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
- raw signal: BUY;
|
||||||
|
- repeat count: 2;
|
||||||
|
- confidence: 0.85;
|
||||||
|
- decision: CONFIRMING.
|
||||||
|
|
||||||
|
Это важно, потому что стратегия может видеть направление, но execution ещё не должен открывать сделку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Улучшение экрана автоторговли
|
||||||
|
|
||||||
|
Экран автоторговли был подготовлен под реальную торговлю и стал компактнее.
|
||||||
|
|
||||||
|
Согласованный формат для выключенной автоторговли без полной настройки:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🤖 Автоторговля
|
||||||
|
🔸 DEMO аккаунт
|
||||||
|
|
||||||
|
⚪ Выключена
|
||||||
|
|
||||||
|
⚠️ Не настроена
|
||||||
|
Настрой параметры
|
||||||
|
```
|
||||||
|
|
||||||
|
Согласованный формат для выключенной автоторговли с настроенными параметрами:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🤖 Автоторговля
|
||||||
|
🔸 DEMO аккаунт
|
||||||
|
|
||||||
|
⚪ Выключена
|
||||||
|
|
||||||
|
BTC / USD · TREND · x2
|
||||||
|
Risk: 0.5%
|
||||||
|
```
|
||||||
|
|
||||||
|
Согласованный формат для включенной автоторговли:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🤖 Автоторговля
|
||||||
|
🔸 DEMO аккаунт
|
||||||
|
|
||||||
|
🟢 Активна
|
||||||
|
|
||||||
|
BTC / USD · TREND · x2
|
||||||
|
|
||||||
|
🟡 HOLD ×12 · WAITING
|
||||||
|
|
||||||
|
Pos: — | PnL: —
|
||||||
|
Risk: 0.5%
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Форматирование инструмента для UI
|
||||||
|
|
||||||
|
Биржа возвращает инструмент в формате:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTC/USD_LEVERAGE
|
||||||
|
```
|
||||||
|
|
||||||
|
В UI отображается человекочитаемый формат:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTC / USD
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Плечо в настройках автоторговли
|
||||||
|
|
||||||
|
Добавлено поле leverage в состояние автоторговли.
|
||||||
|
|
||||||
|
Значение по умолчанию:
|
||||||
|
|
||||||
|
- x2.
|
||||||
|
|
||||||
|
Плечо вынесено в настройки автоторговли.
|
||||||
|
|
||||||
|
Доступные варианты:
|
||||||
|
|
||||||
|
- x1;
|
||||||
|
- x2;
|
||||||
|
- x3;
|
||||||
|
- x5;
|
||||||
|
- x10;
|
||||||
|
- x20.
|
||||||
|
|
||||||
|
В UI плечо отображается компактно:
|
||||||
|
|
||||||
|
```text
|
||||||
|
x2
|
||||||
|
x5
|
||||||
|
x20
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Подготовка execution-полей
|
||||||
|
|
||||||
|
В AutoTradeState добавлены поля под будущий execution:
|
||||||
|
|
||||||
|
- position_side;
|
||||||
|
- entry_price;
|
||||||
|
- position_size;
|
||||||
|
- unrealized_pnl_usd;
|
||||||
|
- max_drawdown_usd;
|
||||||
|
- leverage.
|
||||||
|
|
||||||
|
Пока execution не подключён, эти поля отображаются как заглушки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Защита от лишних UI-обновлений
|
||||||
|
|
||||||
|
Выявлена проблема Telegram flood control при частых edit_message_text.
|
||||||
|
|
||||||
|
Причина:
|
||||||
|
|
||||||
|
- анализ и обновление Telegram UI пока работают слишком близко друг к другу;
|
||||||
|
- live-обновления могут слишком часто редактировать одно и то же сообщение.
|
||||||
|
|
||||||
|
Зафиксировано решение на следующий этап:
|
||||||
|
|
||||||
|
- развязать частоту анализа и частоту Telegram UI;
|
||||||
|
- обновлять UI редко или при важном изменении;
|
||||||
|
- вынести быстрый price polling в отдельный слой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Изменённые файлы
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/trading/auto/state.py
|
||||||
|
app/src/trading/auto/service.py
|
||||||
|
app/src/telegram/handlers/auto.py
|
||||||
|
app/src/telegram/handlers/system.py
|
||||||
|
app/src/trading/auto/runner.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Поведение после этапа
|
||||||
|
|
||||||
|
### OFF, не настроено
|
||||||
|
|
||||||
|
Пользователь видит, что автоторговля выключена и параметры ещё не заданы.
|
||||||
|
|
||||||
|
### OFF, настроено
|
||||||
|
|
||||||
|
Пользователь видит текущий торговый контекст:
|
||||||
|
|
||||||
|
- инструмент;
|
||||||
|
- стратегию;
|
||||||
|
- плечо;
|
||||||
|
- риск.
|
||||||
|
|
||||||
|
### OBSERVING
|
||||||
|
|
||||||
|
Бот анализирует рынок, считает повторы сигналов, confidence и decision_status, но не открывает сделки.
|
||||||
|
|
||||||
|
### RUNNING
|
||||||
|
|
||||||
|
Бот анализирует рынок и показывает готовность сигнала к будущему execution. Реальное исполнение сделок пока не подключено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ограничения этапа
|
||||||
|
|
||||||
|
Пока не реализовано:
|
||||||
|
|
||||||
|
- отдельный быстрый price polling;
|
||||||
|
- отдельная частота анализа;
|
||||||
|
- отдельная частота Telegram UI;
|
||||||
|
- execution;
|
||||||
|
- реальные позиции;
|
||||||
|
- реальные заявки;
|
||||||
|
- риск-менеджмент исполнения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Следующий этап
|
||||||
|
|
||||||
|
### 07.4.3.2 — Analysis/UI Decoupling + Fast Price Polling
|
||||||
|
|
||||||
|
План следующего этапа:
|
||||||
|
|
||||||
|
- разделить частоту анализа и частоту Telegram UI;
|
||||||
|
- получать цену чаще, например раз в 1 секунду;
|
||||||
|
- запускать стратегию отдельно от UI;
|
||||||
|
- обновлять Telegram не чаще заданного интервала;
|
||||||
|
- делать force update только при важных изменениях:
|
||||||
|
- смена signal;
|
||||||
|
- смена decision_status;
|
||||||
|
- CONFIRMING → READY;
|
||||||
|
- READY → BLOCKED;
|
||||||
|
- RUNNING → OFF;
|
||||||
|
- ошибка / восстановление.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Итог
|
||||||
|
|
||||||
|
На этапе 07.4.3.1 автоторговля получила слой стабилизации сигналов.
|
||||||
|
|
||||||
|
Система теперь различает:
|
||||||
|
|
||||||
|
- сырой сигнал стратегии;
|
||||||
|
- подтверждённый сигнал;
|
||||||
|
- готовность к будущему исполнению;
|
||||||
|
- заблокированный сигнал;
|
||||||
|
- отсутствие торгового направления.
|
||||||
|
|
||||||
|
Это ключевой шаг перед подключением execution.
|
||||||
Reference in New Issue
Block a user