07.4.4.1.9.2 Signal Confirmation Runtime
This commit is contained in:
@@ -1,280 +0,0 @@
|
||||
# app/src/telegram/handlers/auto/ui.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.telegram.ui.common import mode_line
|
||||
from src.telegram.ui.currency_ui import format_usd_amount
|
||||
from src.trading.auto.service import AutoTradeService
|
||||
|
||||
|
||||
PAPER_BALANCE_USD = 1000.0
|
||||
|
||||
def strategy_label(strategy: str | None) -> str:
|
||||
mapping = {
|
||||
"TREND": "📈 Trend Following",
|
||||
"GRID": "🧩 Grid Trading",
|
||||
"SCALP": "⚡ Scalping",
|
||||
}
|
||||
return mapping.get(strategy or "", "—")
|
||||
|
||||
|
||||
def status_label(status: str) -> str:
|
||||
mapping = {
|
||||
"OFF": "⚪ Выключена",
|
||||
"OBSERVING": "👀 Наблюдение",
|
||||
"RUNNING": "🟢 Активна",
|
||||
}
|
||||
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 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}"
|
||||
|
||||
|
||||
def market_price_or_dash(symbol: str | None) -> str:
|
||||
if not symbol:
|
||||
return "—"
|
||||
|
||||
try:
|
||||
ticker = ExchangeService().get_price(symbol)
|
||||
return f"$ {format_usd_amount(ticker.price)}"
|
||||
except Exception:
|
||||
return "—"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def compact_strategy(strategy: str | None) -> str:
|
||||
if not strategy:
|
||||
return "—"
|
||||
return strategy.upper()
|
||||
|
||||
|
||||
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:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.button(text="▶️ Start", callback_data="auto:start")
|
||||
builder.button(text="👀 Watch", callback_data="auto:observe")
|
||||
builder.button(text="🛑 Stop", callback_data="auto:stop")
|
||||
builder.button(text="🛠️ Настройки", callback_data="settings:auto")
|
||||
builder.button(text="⚠️ Risk", callback_data="auto:risk")
|
||||
|
||||
builder.adjust(3, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def risk_settings_line(state) -> str:
|
||||
sl = f"{state.stop_loss_percent:g}%" if state.stop_loss_percent is not None else "off"
|
||||
tp = f"{state.take_profit_percent:g}%" if state.take_profit_percent is not None else "off"
|
||||
max_loss = f"{state.max_loss_usd:g} USD" if state.max_loss_usd is not None else "off"
|
||||
|
||||
return f"Controls: SL {sl} · TP {tp} · ML {max_loss}"
|
||||
|
||||
|
||||
def target_risk_usd_line(state) -> str:
|
||||
if state.risk_percent is None:
|
||||
return "Target Risk: —"
|
||||
|
||||
risk_usd = PAPER_BALANCE_USD * (state.risk_percent / 100)
|
||||
return f"Target Risk: {risk_usd:.2f} USD"
|
||||
|
||||
|
||||
def estimated_size_line(state) -> str:
|
||||
if (
|
||||
state.risk_percent is None
|
||||
or state.risk_percent <= 0
|
||||
or state.stop_loss_percent is None
|
||||
or state.stop_loss_percent <= 0
|
||||
):
|
||||
return "Est. Size: —"
|
||||
|
||||
try:
|
||||
ticker = ExchangeService().get_price(state.symbol)
|
||||
price = ticker.price
|
||||
except Exception:
|
||||
return "Est. Size: —"
|
||||
|
||||
if price <= 0:
|
||||
return "Est. Size: —"
|
||||
|
||||
target_risk_usd = PAPER_BALANCE_USD * (state.risk_percent / 100)
|
||||
stop_loss_distance_usd = price * (state.stop_loss_percent / 100)
|
||||
|
||||
if stop_loss_distance_usd <= 0:
|
||||
return "Est. Size: —"
|
||||
|
||||
size = target_risk_usd / stop_loss_distance_usd
|
||||
return f"Est. Size: {size:.8f}".rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
def position_size_line(state) -> str:
|
||||
if state.position_size is None or state.position_size == 0:
|
||||
return "Position Size: —"
|
||||
|
||||
return f"Position Size: {state.position_size:.8f}".rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
def actual_risk_usd_line(state) -> str:
|
||||
if (
|
||||
state.position_side == "NONE"
|
||||
or state.entry_price is None
|
||||
or state.position_size is None
|
||||
or state.stop_loss_percent is None
|
||||
or state.stop_loss_percent <= 0
|
||||
):
|
||||
return "Actual Risk: —"
|
||||
|
||||
stop_loss_distance_usd = state.entry_price * (state.stop_loss_percent / 100)
|
||||
actual_risk_usd = stop_loss_distance_usd * state.position_size
|
||||
|
||||
return f"Actual Risk: {actual_risk_usd:.2f} USD"
|
||||
|
||||
|
||||
def build_auto_text() -> str:
|
||||
service = AutoTradeService()
|
||||
state = service.get_state()
|
||||
|
||||
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 "—"
|
||||
configured = is_auto_configured(state)
|
||||
price = market_price_or_dash(state.symbol)
|
||||
|
||||
status_line = {
|
||||
"OFF": "⚪ Off",
|
||||
"OBSERVING": "👀 Watch",
|
||||
"RUNNING": "🟢 On",
|
||||
}.get(state.status, state.status)
|
||||
|
||||
header = (
|
||||
f"<b>🤖 Автоторговля · {status_line}</b>\n"
|
||||
f"🔸 {account_mode} аккаунт\n\n"
|
||||
)
|
||||
|
||||
if state.status == "OFF":
|
||||
if not configured:
|
||||
return (
|
||||
f"{header}"
|
||||
"⚠️ Не настроена\n"
|
||||
"Настрой параметры"
|
||||
)
|
||||
|
||||
return (
|
||||
f"{header}"
|
||||
f"{context_line(state)}\n"
|
||||
f"Price: {price}\n"
|
||||
f"Position Risk: {risk}\n"
|
||||
f"{target_risk_usd_line(state)}\n"
|
||||
f"{estimated_size_line(state)}\n"
|
||||
f"{risk_settings_line(state)}"
|
||||
)
|
||||
|
||||
position_line = (
|
||||
f"Pos: {value_or_dash(state.position_side)} | "
|
||||
f"PnL: {usd_or_dash(state.unrealized_pnl_usd)}"
|
||||
)
|
||||
|
||||
if state.position_side != "NONE" and state.entry_price is not None:
|
||||
position_line = (
|
||||
f"Pos: {value_or_dash(state.position_side)} | "
|
||||
f"Entry: $ {price_or_dash(state.entry_price)} | "
|
||||
f"PnL: {usd_or_dash(state.unrealized_pnl_usd)}"
|
||||
)
|
||||
|
||||
return (
|
||||
f"{header}"
|
||||
f"{context_line(state)}\n"
|
||||
f"Price: {price}\n\n"
|
||||
f"{signal_label(state.last_signal)} ×{state.last_signal_repeat_count} "
|
||||
f"· {state.decision_status}\n\n"
|
||||
f"{position_line}\n"
|
||||
f"Position Risk: {risk}\n"
|
||||
f"{target_risk_usd_line(state)}\n"
|
||||
f"{position_size_line(state)}\n"
|
||||
f"{actual_risk_usd_line(state)}\n"
|
||||
f"{risk_settings_line(state)}"
|
||||
)
|
||||
@@ -10,7 +10,6 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.telegram.ui.common import mode_line
|
||||
from src.telegram.ui.currency_ui import format_usd_amount
|
||||
from src.trading.auto.service import AutoTradeService
|
||||
|
||||
|
||||
@@ -76,9 +75,9 @@ def _build_not_configured_text(state) -> str:
|
||||
"🤖 Автоторговля ⚪ Не настроена",
|
||||
_account_mode_line(),
|
||||
"",
|
||||
f"{symbol_icon} <b>Актив</b> · {_asset_symbol(state.symbol)}\n"
|
||||
f"{strategy_icon} <b>Стратегия</b> · {_required_value(_strategy_short(state.strategy))}\n"
|
||||
f"{risk_icon} <b>Риск на сделку</b> · {_required_value(_risk_percent_text(state))}\n"
|
||||
f"{symbol_icon} Актив · {_asset_symbol(state.symbol)}",
|
||||
f"{strategy_icon} Стратегия · {_required_value(_strategy_short(state.strategy))}",
|
||||
f"{risk_icon} Риск · {_required_value(_risk_percent_text(state))}",
|
||||
]
|
||||
|
||||
strategy = (state.strategy or "").upper()
|
||||
@@ -91,8 +90,7 @@ def _build_not_configured_text(state) -> str:
|
||||
)
|
||||
|
||||
sl_icon = "" if sl_value != "⏤" else "⚠️"
|
||||
|
||||
parts.append(f"{sl_icon} <b>SL</b> · {sl_value}")
|
||||
parts.append(f"{sl_icon} SL · {sl_value}")
|
||||
|
||||
parts.extend([
|
||||
"",
|
||||
@@ -109,7 +107,6 @@ def _build_stopped_without_position_text(state) -> str:
|
||||
estimated_size = _estimated_size(state, price)
|
||||
|
||||
rr_line = _risk_reward_line(state)
|
||||
|
||||
risk_line = _risk_summary_line(
|
||||
state,
|
||||
estimated_size,
|
||||
@@ -120,14 +117,14 @@ def _build_stopped_without_position_text(state) -> str:
|
||||
f"🤖 Автоторговля {_status_text(state.status)}",
|
||||
_account_mode_line(),
|
||||
"",
|
||||
f"<b>Доступно</b> · $ {_format_money_compact(available)}\n",
|
||||
"🧾 <b>Подготовка ордера</b>",
|
||||
f"Доступно 💰 {_format_money_compact(available)}",
|
||||
"",
|
||||
"Подготовка ордера 🧾",
|
||||
_order_header_line(state),
|
||||
f"<b>Цена</b> · {_format_usd_or_dash(price)}",
|
||||
f"Цена · {_format_plain_or_dash(price)}",
|
||||
_estimated_size_text(state, price),
|
||||
_max_reserved_line(state, price),
|
||||
f"<b>Риск</b> · {_risk_percent_text(state)} ($ {_format_money_compact(_target_risk_usd(state))})",
|
||||
f"Риск · {_format_money_compact(_target_risk_usd(state))}",
|
||||
]
|
||||
|
||||
if rr_line or risk_line:
|
||||
@@ -157,8 +154,8 @@ def _build_waiting_text(state) -> str:
|
||||
|
||||
signal_lines = [
|
||||
_signal_line(state),
|
||||
_market_state_line(state),
|
||||
_market_diagnostics_line(state),
|
||||
_signal_confirmation_line(state),
|
||||
_market_semantic_line(state),
|
||||
_entry_block_line(state),
|
||||
_execution_quality_line(state),
|
||||
*_signal_confidence_lines(state),
|
||||
@@ -171,17 +168,21 @@ def _build_waiting_text(state) -> str:
|
||||
f"🤖 Автоторговля {_status_text(state.status)}",
|
||||
_account_mode_line(),
|
||||
"",
|
||||
f"<b>Доступно</b> · $ {_format_money_compact(available)}",
|
||||
f"Доступно 💰 {_format_money_compact(available)}",
|
||||
]
|
||||
|
||||
if signal_lines:
|
||||
parts.extend(["", *signal_lines])
|
||||
|
||||
parts.extend([
|
||||
"",
|
||||
*signal_lines,
|
||||
"",
|
||||
"🧾 Подготовка ордера",
|
||||
"Подготовка ордера 🧾",
|
||||
_order_header_line(state),
|
||||
f"<b>{_price_label_for_signal(state)}</b> · {_format_usd_or_dash(price)}",
|
||||
f"{_price_label_for_signal(state)} · {_format_plain_or_dash(price)}",
|
||||
_estimated_size_text(state, price),
|
||||
_max_reserved_line(state, price),
|
||||
f"<b>Риск</b> · {_risk_percent_text(state)} ($ {_format_money_compact(_target_risk_usd(state))})",
|
||||
]
|
||||
f"Риск · {_format_money_compact(_target_risk_usd(state))}",
|
||||
])
|
||||
|
||||
if rr_line or risk_line:
|
||||
parts.append("")
|
||||
@@ -210,29 +211,40 @@ def _build_active_position_text(state) -> str:
|
||||
|
||||
side_icon = "🟢" if state.position_side == "LONG" else "🔴"
|
||||
|
||||
market_lines = [
|
||||
_market_semantic_line(state),
|
||||
_execution_quality_line(state),
|
||||
*_execution_block_lines(state),
|
||||
]
|
||||
market_lines = [line for line in market_lines if line]
|
||||
|
||||
parts = [
|
||||
f"🤖 Автоторговля {_status_text(state.status)}",
|
||||
_account_mode_line(),
|
||||
"",
|
||||
f"<b>Доступно</b> · $ {_format_money_compact(available)}",
|
||||
f"<b>Зарезервировано</b> · $ {_format_money_compact(reserved)}",
|
||||
f"<b>P&L</b> {_format_signed_usd_with_direction(pnl)}",
|
||||
_market_state_line(state),
|
||||
_execution_quality_line(state),
|
||||
*_execution_block_lines(state),
|
||||
f"Доступно 💰 {_format_money_compact(available)}",
|
||||
f"Маржа · {_format_money_compact(reserved)}",
|
||||
f"P&L {_format_signed_plain_with_direction(pnl)}",
|
||||
]
|
||||
|
||||
if market_lines:
|
||||
parts.extend(["", *market_lines])
|
||||
|
||||
parts.extend([
|
||||
"",
|
||||
(
|
||||
f"{side_icon} <b>{_asset_symbol(state.symbol)}</b> · "
|
||||
f"{side_icon} {_asset_symbol(state.symbol)} · "
|
||||
f"{_strategy_short(state.strategy)} · "
|
||||
f"<b>{state.position_side}</b> {_leverage_text(state.leverage)}"
|
||||
f"{state.position_side} {_leverage_text(state.leverage)}"
|
||||
),
|
||||
"",
|
||||
f"<b>Количество</b> · {_format_crypto_size(size)} ⇢ $ {_format_money_compact(notional)}",
|
||||
f"<b>Цена входа</b> · $ {_format_money(state.entry_price)}",
|
||||
f"<b>Текущая цена</b> · {_format_usd_or_dash(current_price)}",
|
||||
f"Размер · {_format_crypto_size(size)}",
|
||||
f"Позиция · {_format_money_compact(notional)}",
|
||||
f"Вход · {_format_plain_or_dash(state.entry_price)}",
|
||||
f"Цена · {_format_plain_or_dash(price_for_calc)}",
|
||||
"",
|
||||
"⚠️ Комиссии не учтены",
|
||||
]
|
||||
])
|
||||
|
||||
if rr_line or risk_line:
|
||||
parts.append("")
|
||||
@@ -246,63 +258,72 @@ def _build_active_position_text(state) -> str:
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _market_state_line(state) -> str:
|
||||
def _market_semantic_line(state) -> str:
|
||||
market_state = getattr(state, "market_state", None)
|
||||
|
||||
labels = {
|
||||
"TREND_UP": "📈 Тренд · Вверх",
|
||||
"TREND_DOWN": "📉 Тренд · Вниз",
|
||||
"RANGE": "🟰 Рынок · Флэт",
|
||||
"HIGH_VOLATILITY": "⚠️ Рынок · Высокая волатильность",
|
||||
"LOW_VOLATILITY": "🟰 Рынок · Низкая активность",
|
||||
"UNKNOWN": "⏳ Рынок · Идёт анализ",
|
||||
None: "⏳ Рынок · Идёт анализ",
|
||||
}
|
||||
|
||||
return labels.get(market_state, "⏳ Рынок · Идёт анализ")
|
||||
|
||||
|
||||
def _market_diagnostics_line(state) -> str:
|
||||
trend = getattr(state, "market_trend", None)
|
||||
strength = getattr(state, "market_trend_strength", None)
|
||||
quality = getattr(state, "market_trend_quality", None)
|
||||
phase = getattr(state, "market_phase", None)
|
||||
|
||||
if not strength and not quality and not phase:
|
||||
return ""
|
||||
if market_state in {None, "UNKNOWN"}:
|
||||
return "⏳ Рынок · анализ"
|
||||
|
||||
strength_labels = {
|
||||
"WEAK": "слабый",
|
||||
"NORMAL": "нормальный",
|
||||
"STRONG": "сильный",
|
||||
}
|
||||
if market_state == "HIGH_VOLATILITY":
|
||||
return "⚠️ Рынок · перегрев"
|
||||
|
||||
quality_labels = {
|
||||
"CLEAN": "чистый",
|
||||
"NOISY": "шумный",
|
||||
}
|
||||
if market_state == "LOW_VOLATILITY" or phase == "SQUEEZE":
|
||||
return "🟦 Рынок · сжатие"
|
||||
|
||||
phase_labels = {
|
||||
"IMPULSE": "импульс",
|
||||
"PULLBACK": "откат",
|
||||
"RANGE": "флэт",
|
||||
"SQUEEZE": "сжатие",
|
||||
}
|
||||
if market_state == "RANGE" or phase == "RANGE":
|
||||
return "🟰 Рынок · флэт"
|
||||
|
||||
parts = []
|
||||
if phase == "PULLBACK":
|
||||
if trend == "UP":
|
||||
return "↘️ Рынок · коррекция"
|
||||
|
||||
if strength in strength_labels:
|
||||
parts.append(strength_labels[strength])
|
||||
if trend == "DOWN":
|
||||
return "↗️ Рынок · откат вверх"
|
||||
|
||||
if quality in quality_labels:
|
||||
parts.append(quality_labels[quality])
|
||||
return "↔️ Рынок · откат"
|
||||
|
||||
if phase in phase_labels:
|
||||
parts.append(phase_labels[phase])
|
||||
if quality == "NOISY":
|
||||
if trend == "UP":
|
||||
return "⚠️ Рынок · шумный рост"
|
||||
|
||||
if not parts:
|
||||
return ""
|
||||
if trend == "DOWN":
|
||||
return "⚠️ Рынок · шумное снижение"
|
||||
|
||||
return f"Анализ · {' · '.join(parts)}"
|
||||
return "⚠️ Рынок · шум"
|
||||
|
||||
if strength == "WEAK":
|
||||
if trend == "UP":
|
||||
return "🟡 Рынок · слабый рост"
|
||||
|
||||
if trend == "DOWN":
|
||||
return "🟡 Рынок · слабое снижение"
|
||||
|
||||
return "🟡 Рынок · слабое движение"
|
||||
|
||||
if phase == "IMPULSE":
|
||||
if trend == "UP" and strength == "STRONG":
|
||||
return "⚡ Рынок · сильный рост"
|
||||
|
||||
if trend == "DOWN" and strength == "STRONG":
|
||||
return "⚡ Рынок · сильное снижение"
|
||||
|
||||
if trend == "UP":
|
||||
return "📈 Рынок · рост"
|
||||
|
||||
if trend == "DOWN":
|
||||
return "📉 Рынок · снижение"
|
||||
|
||||
if trend == "UP":
|
||||
return "📈 Рынок · рост"
|
||||
|
||||
if trend == "DOWN":
|
||||
return "📉 Рынок · снижение"
|
||||
|
||||
return "⏳ Рынок · анализ"
|
||||
|
||||
|
||||
def _compact_entry_block_message(message: str) -> str:
|
||||
@@ -328,15 +349,8 @@ def _entry_block_line(state) -> str:
|
||||
return ""
|
||||
|
||||
compact_message = _compact_entry_block_message(str(message))
|
||||
signal = (state.last_signal or "HOLD").upper()
|
||||
|
||||
if signal == "HOLD":
|
||||
return f"Условие · {compact_message}"
|
||||
|
||||
if signal in {"BUY", "SELL"}:
|
||||
return f"Вход · {compact_message}"
|
||||
|
||||
return ""
|
||||
return f"🧩 Фильтр · {compact_message}"
|
||||
|
||||
|
||||
def _execution_quality_line(state) -> str:
|
||||
@@ -352,10 +366,10 @@ def _execution_quality_line(state) -> str:
|
||||
return ""
|
||||
|
||||
if reason == "WIDE_SPREAD" and spread_percent is not None:
|
||||
return f"⚠️ Рынок · spread {_format_percent(spread_percent)}"
|
||||
return f"⚠️ Вход · spread {_format_percent(spread_percent)}"
|
||||
|
||||
if reason == "AGING_SNAPSHOT" and age_seconds is not None:
|
||||
return f"⚠️ Рынок · данные стареют ({age_seconds:.1f}с)"
|
||||
return f"⚠️ Вход · данные стареют {age_seconds:.1f}с"
|
||||
|
||||
if reason == "STALE_SNAPSHOT":
|
||||
return "⛔ Вход · рынок неактуален"
|
||||
@@ -364,7 +378,7 @@ def _execution_quality_line(state) -> str:
|
||||
return f"⛔ Вход · высокий spread {_format_percent(spread_percent)}"
|
||||
|
||||
if reason == "SNAPSHOT_UNAVAILABLE":
|
||||
return "⚠️ Рынок · нет depth snapshot"
|
||||
return "⚠️ Вход · нет стакана"
|
||||
|
||||
if reason == "SNAPSHOT_ERROR":
|
||||
return "⛔ Вход · нет данных рынка"
|
||||
@@ -374,7 +388,7 @@ def _execution_quality_line(state) -> str:
|
||||
if not message:
|
||||
return ""
|
||||
|
||||
return f"⚠️ Рынок · {message}"
|
||||
return f"⚠️ Вход · {message}"
|
||||
|
||||
|
||||
def _execution_block_lines(state) -> list[str]:
|
||||
@@ -392,9 +406,7 @@ def _execution_block_lines(state) -> list[str]:
|
||||
adjustment = getattr(state, "execution_size_adjustment_reason", None)
|
||||
|
||||
if adjustment == "MARGIN_LIMIT":
|
||||
lines.append(
|
||||
"Позиция ограничена настройкой Max Reserved."
|
||||
)
|
||||
lines.append("Позиция ограничена настройкой Max Reserved.")
|
||||
|
||||
return lines
|
||||
|
||||
@@ -428,18 +440,16 @@ def _max_reserved_line(state, price: float | None = None) -> str:
|
||||
size = _estimated_size(state, price)
|
||||
|
||||
if size is None or price is None or price <= 0:
|
||||
return "<b>Собственные средства</b> · —"
|
||||
return "Маржа · —"
|
||||
|
||||
leverage = state.leverage or 1.0
|
||||
if leverage <= 0:
|
||||
return "<b>Собственные средства</b> · —"
|
||||
return "Маржа · —"
|
||||
|
||||
position_size_usd = size * price
|
||||
own_funds_usd = position_size_usd / leverage
|
||||
|
||||
return (
|
||||
f"<b>Собственные средства</b> · $ {_format_money_compact(own_funds_usd)}"
|
||||
)
|
||||
return f"Маржа · {_format_money_compact(own_funds_usd)}"
|
||||
|
||||
|
||||
def _market_snapshot(symbol: str | None) -> dict[str, object] | None:
|
||||
@@ -532,13 +542,13 @@ def _estimated_size(state, price: float | None) -> float | None:
|
||||
def _estimated_size_text(state, price: float | None) -> str:
|
||||
size = _estimated_size(state, price)
|
||||
if size is None or price is None:
|
||||
return "<b>Количество</b> · —"
|
||||
return "Размер · —\nПозиция · —"
|
||||
|
||||
notional = size * price
|
||||
|
||||
return (
|
||||
f"<b>Количество</b> · {_format_crypto_size(size)}\n"
|
||||
f"<b>Размер позиции</b> · $ {_format_money_compact(notional)}"
|
||||
f"Размер · {_format_crypto_size(size)}\n"
|
||||
f"Позиция · {_format_money_compact(notional)}"
|
||||
)
|
||||
|
||||
|
||||
@@ -569,9 +579,9 @@ def _risk_summary_line(
|
||||
)
|
||||
|
||||
items = [
|
||||
f"<b>SL</b> {sl}" if sl else "<b>SL</b> off",
|
||||
f"<b>TP</b> {tp}" if tp else "<b>TP</b> off",
|
||||
f"<b>ML</b> {ml}" if ml else "<b>ML</b> off",
|
||||
f"SL {sl}" if sl else "SL off",
|
||||
f"TP {tp}" if tp else "TP off",
|
||||
f"ML {ml}" if ml else "ML off",
|
||||
]
|
||||
|
||||
return " | ".join(items)
|
||||
@@ -585,19 +595,19 @@ def _risk_loss_text(
|
||||
entry_price: float | None,
|
||||
) -> str:
|
||||
if fixed_loss is not None:
|
||||
return f"-$ {_format_money_compact(abs(fixed_loss))}"
|
||||
return f"-{_format_money_compact(abs(fixed_loss))}"
|
||||
|
||||
if percent is None:
|
||||
return ""
|
||||
|
||||
if size is None or size <= 0 or entry_price is None or entry_price <= 0:
|
||||
loss = _target_loss_by_percent_stub(percent)
|
||||
return f"-$ {_format_money_compact(loss)}" if loss is not None else ""
|
||||
return f"-{_format_money_compact(loss)}" if loss is not None else ""
|
||||
|
||||
move = entry_price * (percent / 100)
|
||||
loss = move * size
|
||||
|
||||
return f"-$ {_format_money_compact(loss)}"
|
||||
return f"-{_format_money_compact(loss)}"
|
||||
|
||||
|
||||
def _risk_profit_text(
|
||||
@@ -615,7 +625,7 @@ def _risk_profit_text(
|
||||
move = entry_price * (percent / 100)
|
||||
profit = move * size
|
||||
|
||||
return f"+$ {_format_money_compact(profit)}"
|
||||
return f"+{_format_money_compact(profit)}"
|
||||
|
||||
|
||||
def _target_loss_by_percent_stub(percent: float | None) -> float | None:
|
||||
@@ -635,7 +645,7 @@ def _risk_reward_line(state) -> str:
|
||||
return ""
|
||||
|
||||
ratio = state.take_profit_percent / state.stop_loss_percent
|
||||
return f"<b>R:R</b> = 1 : {_format_ratio_value(ratio)}"
|
||||
return f"R:R = 1 : {_format_ratio_value(ratio)}"
|
||||
|
||||
|
||||
def _order_header_line(state) -> str:
|
||||
@@ -645,14 +655,14 @@ def _order_header_line(state) -> str:
|
||||
return (
|
||||
f"🟢 <b>{_asset_symbol(state.symbol)}</b> · "
|
||||
f"{_strategy_short(state.strategy)} · "
|
||||
f"<b>LONG</b> {_leverage_text(state.leverage)}"
|
||||
f"LONG {_leverage_text(state.leverage)}"
|
||||
)
|
||||
|
||||
if signal == "SELL":
|
||||
return (
|
||||
f"🔴 <b>{_asset_symbol(state.symbol)}</b> · "
|
||||
f"{_strategy_short(state.strategy)} · "
|
||||
f"<b>SHORT</b> {_leverage_text(state.leverage)}"
|
||||
f"SHORT {_leverage_text(state.leverage)}"
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -666,7 +676,7 @@ def _price_label_for_signal(state) -> str:
|
||||
signal = (state.last_signal or "").upper()
|
||||
|
||||
if signal in {"BUY", "SELL"}:
|
||||
return "Цена входа"
|
||||
return "Вход"
|
||||
|
||||
return "Цена"
|
||||
|
||||
@@ -674,15 +684,52 @@ def _price_label_for_signal(state) -> str:
|
||||
def _signal_line(state) -> str:
|
||||
signal = (state.last_signal or "HOLD").upper()
|
||||
|
||||
signal_text = f"Сигнал {_signal_icon(signal)} {signal}"
|
||||
|
||||
if signal in {"BUY", "SELL"} and (
|
||||
state.decision_status == "READY"
|
||||
or getattr(state, "is_signal_ready", False)
|
||||
):
|
||||
return f"Сигнал {_signal_icon(signal)} {signal} · READY"
|
||||
return f"{signal_text} · READY"
|
||||
|
||||
duration = _signal_duration_text(state)
|
||||
|
||||
return f"Сигнал {_signal_icon(signal)} {signal} · {duration}"
|
||||
return f"{signal_text} · {duration}"
|
||||
|
||||
|
||||
def _signal_confirmation_line(state) -> str:
|
||||
signal = (state.last_signal or "HOLD").upper()
|
||||
|
||||
if signal not in {"BUY", "SELL"}:
|
||||
return ""
|
||||
|
||||
status = getattr(state, "decision_status", None)
|
||||
|
||||
seconds = int(getattr(state, "signal_confirmation_seconds", 0) or 0)
|
||||
required_seconds = int(
|
||||
getattr(state, "signal_confirmation_required_seconds", 10) or 10
|
||||
)
|
||||
repeats = int(getattr(state, "last_signal_repeat_count", 0) or 0)
|
||||
|
||||
missing_repeats = int(
|
||||
getattr(state, "signal_confirmation_missing_repeats", 0) or 0
|
||||
)
|
||||
required_repeats = repeats + missing_repeats
|
||||
|
||||
if status == "READY" or getattr(state, "is_signal_ready", False):
|
||||
return "✅ Подтверждение · готово"
|
||||
|
||||
if status == "BLOCKED":
|
||||
return "⛔ Подтверждение · заблокировано"
|
||||
|
||||
if status == "CONFIRMING":
|
||||
return (
|
||||
f"⏳ Подтверждение · "
|
||||
f"{repeats}/{required_repeats} · "
|
||||
f"{seconds}/{required_seconds}с"
|
||||
)
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _signal_duration_text(state) -> str:
|
||||
@@ -768,6 +815,7 @@ def _strategy_short(strategy: str | None) -> str:
|
||||
def _leverage_text(value: float | None) -> str:
|
||||
if value is None:
|
||||
return "x—"
|
||||
|
||||
return f"x{value:g}"
|
||||
|
||||
|
||||
@@ -802,6 +850,7 @@ def _signal_icon(signal: str | None) -> str:
|
||||
"SELL": "🔴",
|
||||
"HOLD": "🟡",
|
||||
}
|
||||
|
||||
return mapping.get(signal or "", "")
|
||||
|
||||
|
||||
@@ -817,6 +866,7 @@ def _round_size(value: float | int | None) -> float | None:
|
||||
|
||||
def _format_crypto_size(value: float | int | None) -> str:
|
||||
rounded = _round_size(value)
|
||||
|
||||
if rounded is None:
|
||||
return "—"
|
||||
|
||||
@@ -835,13 +885,6 @@ def _format_percent(value: float | int | None) -> str:
|
||||
return f"{number:.2f}".rstrip("0").rstrip(".") + "%"
|
||||
|
||||
|
||||
def _format_money(value: float | int | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
|
||||
return format_usd_amount(float(value))
|
||||
|
||||
|
||||
def _format_money_compact(value: float | int | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
@@ -854,11 +897,15 @@ def _format_money_compact(value: float | int | None) -> str:
|
||||
return f"{number:,.2f}".replace(",", " ").rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
def _format_usd_or_dash(value: float | None) -> str:
|
||||
def _format_plain_or_dash(value: float | int | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
|
||||
return f"$ {_format_money(value)}"
|
||||
return _format_money_compact(value)
|
||||
|
||||
|
||||
def _format_usd_or_dash(value: float | None) -> str:
|
||||
return _format_plain_or_dash(value)
|
||||
|
||||
|
||||
def _format_signed_usd(value: float | int | None) -> str:
|
||||
@@ -868,24 +915,28 @@ def _format_signed_usd(value: float | int | None) -> str:
|
||||
amount = float(value)
|
||||
|
||||
if amount > 0:
|
||||
return f"+$ {_format_money_compact(amount)}"
|
||||
return f"+{_format_money_compact(amount)}"
|
||||
|
||||
if amount < 0:
|
||||
return f"−$ {_format_money_compact(abs(amount))}"
|
||||
return f"−{_format_money_compact(abs(amount))}"
|
||||
|
||||
return "$ 0"
|
||||
return "0"
|
||||
|
||||
|
||||
def _format_signed_usd_with_direction(value: float | int | None) -> str:
|
||||
return _format_signed_plain_with_direction(value)
|
||||
|
||||
|
||||
def _format_signed_plain_with_direction(value: float | int | None) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
|
||||
amount = float(value)
|
||||
|
||||
if amount > 0:
|
||||
return f"🟢 +$ {_format_money_compact(amount)}"
|
||||
return f"🟢 +{_format_money_compact(amount)}"
|
||||
|
||||
if amount < 0:
|
||||
return f"🔴 −$ {_format_money_compact(abs(amount))}"
|
||||
return f"🔴 −{_format_money_compact(abs(amount))}"
|
||||
|
||||
return "$ 0"
|
||||
return "0"
|
||||
@@ -24,6 +24,9 @@ class AutoTradeService:
|
||||
# минимальное количество повторов BUY / SELL для подтверждения сигнала
|
||||
_confirm_repeats = 2
|
||||
|
||||
# минимальное время удержания BUY / SELL сигнала для подтверждения
|
||||
_confirm_min_duration_seconds = 10
|
||||
|
||||
# минимальная уверенность для готовности к будущему execution
|
||||
_ready_confidence = 0.3
|
||||
|
||||
@@ -114,6 +117,11 @@ class AutoTradeService:
|
||||
state.last_signal_repeat_count = repeat_count
|
||||
state.last_signal_confidence = confidence
|
||||
state.last_signal_reason = reason
|
||||
state.signal_confirmation_seconds = self._confirm_min_duration_seconds
|
||||
state.signal_confirmation_required_seconds = self._confirm_min_duration_seconds
|
||||
state.signal_confirmation_missing_repeats = 0
|
||||
state.signal_confirmation_progress = 1.0
|
||||
state.signal_confirmation_reason = "debug confirmation"
|
||||
|
||||
if normalized_signal == "HOLD":
|
||||
state.decision_status = "WAITING"
|
||||
@@ -359,6 +367,11 @@ class AutoTradeService:
|
||||
state.decision_reason = None
|
||||
state.is_signal_confirmed = False
|
||||
state.is_signal_ready = False
|
||||
state.signal_confirmation_seconds = 0
|
||||
state.signal_confirmation_required_seconds = self._confirm_min_duration_seconds
|
||||
state.signal_confirmation_missing_repeats = self._confirm_repeats
|
||||
state.signal_confirmation_progress = 0.0
|
||||
state.signal_confirmation_reason = None
|
||||
state.execution_block_reason = None
|
||||
state.signal_started_at = None
|
||||
state.signal_updated_at = None
|
||||
@@ -436,20 +449,60 @@ class AutoTradeService:
|
||||
state.is_signal_confirmed = False
|
||||
state.is_signal_ready = False
|
||||
|
||||
state.signal_confirmation_required_seconds = self._confirm_min_duration_seconds
|
||||
|
||||
if signal == "HOLD":
|
||||
state.signal_confirmation_seconds = 0
|
||||
state.signal_confirmation_missing_repeats = self._confirm_repeats
|
||||
state.signal_confirmation_progress = 0.0
|
||||
state.signal_confirmation_reason = None
|
||||
state.decision_status = "WAITING"
|
||||
state.decision_reason = "Нет торгового направления."
|
||||
return
|
||||
|
||||
if self._same_signal_count < self._confirm_repeats:
|
||||
now = time.monotonic()
|
||||
|
||||
if state.signal_started_at is None:
|
||||
signal_age_seconds = 0
|
||||
else:
|
||||
signal_age_seconds = max(0, int(now - float(state.signal_started_at)))
|
||||
|
||||
missing_repeats = max(0, self._confirm_repeats - self._same_signal_count)
|
||||
missing_seconds = max(
|
||||
0,
|
||||
self._confirm_min_duration_seconds - signal_age_seconds,
|
||||
)
|
||||
|
||||
repeat_progress = min(
|
||||
1.0,
|
||||
self._same_signal_count / max(1, self._confirm_repeats),
|
||||
)
|
||||
time_progress = min(
|
||||
1.0,
|
||||
signal_age_seconds / max(1, self._confirm_min_duration_seconds),
|
||||
)
|
||||
|
||||
confirmation_progress = min(repeat_progress, time_progress)
|
||||
|
||||
state.signal_confirmation_seconds = signal_age_seconds
|
||||
state.signal_confirmation_missing_repeats = missing_repeats
|
||||
state.signal_confirmation_progress = round(confirmation_progress, 3)
|
||||
|
||||
if missing_repeats > 0 or missing_seconds > 0:
|
||||
state.decision_status = "CONFIRMING"
|
||||
state.signal_confirmation_reason = (
|
||||
f"{self._same_signal_count}/{self._confirm_repeats} повторов, "
|
||||
f"{signal_age_seconds}/{self._confirm_min_duration_seconds}с"
|
||||
)
|
||||
state.decision_reason = (
|
||||
f"Сигнал {signal} подтверждается: "
|
||||
f"{self._same_signal_count}/{self._confirm_repeats} повторов."
|
||||
f"{self._same_signal_count}/{self._confirm_repeats} повторов, "
|
||||
f"{signal_age_seconds}/{self._confirm_min_duration_seconds}с."
|
||||
)
|
||||
return
|
||||
|
||||
state.is_signal_confirmed = True
|
||||
state.signal_confirmation_reason = "сигнал подтверждён"
|
||||
|
||||
if confidence < self._ready_confidence:
|
||||
state.decision_status = "BLOCKED"
|
||||
@@ -460,9 +513,10 @@ class AutoTradeService:
|
||||
return
|
||||
|
||||
state.is_signal_ready = True
|
||||
state.signal_confirmation_progress = 1.0
|
||||
state.decision_status = "READY"
|
||||
state.decision_reason = (
|
||||
f"Сигнал {signal} подтверждён и готов к будущему execution."
|
||||
f"Сигнал {signal} подтверждён по повторам и времени удержания."
|
||||
)
|
||||
|
||||
# записать новый сигнал и итог предыдущей серии при смене сигнала
|
||||
@@ -728,6 +782,9 @@ class AutoTradeService:
|
||||
"decision_status": state.decision_status,
|
||||
"is_strong_signal": confidence > self._ready_confidence,
|
||||
"is_aggregated": False,
|
||||
"confirmation_seconds": state.signal_confirmation_seconds,
|
||||
"confirmation_required_seconds": state.signal_confirmation_required_seconds,
|
||||
"confirmation_progress": state.signal_confirmation_progress,
|
||||
},
|
||||
)
|
||||
except Exception:
|
||||
|
||||
@@ -164,4 +164,19 @@ class AutoTradeState:
|
||||
execution_quality_message: str | None = None
|
||||
|
||||
# признак деградации runtime market data
|
||||
market_runtime_degraded: bool = False
|
||||
market_runtime_degraded: bool = False
|
||||
|
||||
# сколько секунд текущий BUY / SELL сигнал удерживается
|
||||
signal_confirmation_seconds: int = 0
|
||||
|
||||
# сколько секунд нужно удерживать BUY / SELL сигнал для подтверждения
|
||||
signal_confirmation_required_seconds: int = 10
|
||||
|
||||
# сколько повторов ещё не хватает до подтверждения
|
||||
signal_confirmation_missing_repeats: int = 0
|
||||
|
||||
# прогресс подтверждения сигнала от 0.0 до 1.0
|
||||
signal_confirmation_progress: float = 0.0
|
||||
|
||||
# человекочитаемая причина текущего confirmation status
|
||||
signal_confirmation_reason: str | None = None
|
||||
@@ -683,6 +683,108 @@
|
||||
- подготовлена база для semantic entry filters
|
||||
- подготовлена база для более точного TREND execution
|
||||
|
||||
#### 07.4.4.1.9.1 ✅ Market Semantic Runtime Layer
|
||||
- добавлен semantic runtime layer поверх adaptive market diagnostics
|
||||
- добавлена единая semantic-интерпретация market_state / trend / strength / quality / phase
|
||||
- добавлена функция `_market_semantic_line()` в Telegram UI
|
||||
- `_market_semantic_line()` заменяет связку `_market_state_line()` и `_market_diagnostics_line()`
|
||||
- UI больше не показывает раздельно технические признаки тренда и фазы
|
||||
- UI теперь показывает одно итоговое смысловое состояние рынка
|
||||
- добавлена semantic-нормализация TREND_UP состояний
|
||||
- добавлена semantic-нормализация TREND_DOWN состояний
|
||||
- добавлена semantic-нормализация RANGE состояния
|
||||
- добавлена semantic-нормализация LOW_VOLATILITY / SQUEEZE состояния
|
||||
- добавлена semantic-нормализация HIGH_VOLATILITY состояния
|
||||
- добавлена semantic-нормализация UNKNOWN состояния
|
||||
- добавлено состояние `⚡ Рынок · сильный рост`
|
||||
- добавлено состояние `⚡ Рынок · сильное снижение`
|
||||
- добавлено состояние `📈 Рынок · рост`
|
||||
- добавлено состояние `📉 Рынок · снижение`
|
||||
- добавлено состояние `🟡 Рынок · слабый рост`
|
||||
- добавлено состояние `🟡 Рынок · слабое снижение`
|
||||
- добавлено состояние `⚠️ Рынок · шумный рост`
|
||||
- добавлено состояние `⚠️ Рынок · шумное снижение`
|
||||
- добавлено состояние `↘️ Рынок · коррекция`
|
||||
- добавлено состояние `↗️ Рынок · откат вверх`
|
||||
- добавлено состояние `🟰 Рынок · флэт`
|
||||
- добавлено состояние `🟦 Рынок · сжатие`
|
||||
- добавлено состояние `⚠️ Рынок · перегрев`
|
||||
- добавлено состояние `⏳ Рынок · анализ`
|
||||
- добавлен приоритет semantic-состояний рынка
|
||||
- HIGH_VOLATILITY получает приоритет над обычным трендом
|
||||
- LOW_VOLATILITY / SQUEEZE отображается как semantic-сжатие
|
||||
- RANGE отображается как semantic-флэт
|
||||
- PULLBACK отображается как коррекция или откат вверх
|
||||
- NOISY trend отображается как шумный рост или шумное снижение
|
||||
- WEAK trend отображается как слабый рост или слабое снижение
|
||||
- STRONG + CLEAN + IMPULSE отображается как сильный рост или сильное снижение
|
||||
- NORMAL + CLEAN + IMPULSE отображается как рост или снижение
|
||||
- fallback по направлению тренда сохранён
|
||||
- execution quality не смешивается с market semantic layer
|
||||
- строки `Вход · spread`, `Вход · нет стакана`, `Вход · нет данных рынка` остались отдельными
|
||||
- строка `Фильтр` осталась отдельной для причин HOLD
|
||||
- экран ожидания AutoTrade переведён на semantic market line
|
||||
- экран активной позиции AutoTrade переведён на semantic market line
|
||||
- убрано дублирование `Тренд` + `Фаза` в UI
|
||||
- экран автоторговли стал короче
|
||||
- экран автоторговли стал понятнее для runtime-наблюдения
|
||||
- HOLD diagnostics стали более смысловыми
|
||||
- market state теперь отображается как торговый смысл, а не как raw enum
|
||||
- подготовлена база для semantic entry filters
|
||||
- подготовлена база для market regime scoring
|
||||
- подготовлена база для adaptive threshold tuning
|
||||
- подготовлена база для confidence scoring по состоянию рынка
|
||||
- подготовлена база для объяснимого AutoTrade decision screen
|
||||
|
||||
#### 07.4.4.1.9.2 ✅ Signal Confirmation Runtime
|
||||
- добавлен Signal Confirmation Runtime layer
|
||||
- confirmation runtime вынесен в отдельный stateful lifecycle
|
||||
- BUY / SELL больше не переходят мгновенно в READY
|
||||
- добавлена runtime-фаза CONFIRMING
|
||||
- confirmation теперь требует repeat consistency
|
||||
- confirmation теперь требует time persistence
|
||||
- добавлен minimum signal lifetime filter
|
||||
- добавлен `_confirm_min_duration_seconds`
|
||||
- BUY / SELL теперь обязаны удерживаться во времени
|
||||
- runtime теперь анализирует signal persistence
|
||||
- runtime теперь анализирует directional continuity
|
||||
- runtime теперь анализирует signal lifetime
|
||||
- добавлен confirmation progress runtime
|
||||
- добавлен `signal_confirmation_seconds`
|
||||
- добавлен `signal_confirmation_required_seconds`
|
||||
- добавлен `signal_confirmation_missing_repeats`
|
||||
- добавлен `signal_confirmation_progress`
|
||||
- добавлен `signal_confirmation_reason`
|
||||
- confirmation progress теперь рассчитывается по repeat progress
|
||||
- confirmation progress теперь рассчитывается по time progress
|
||||
- BUY / SELL проходят lifecycle HOLD → CONFIRMING → READY
|
||||
- HOLD теперь сбрасывает confirmation progress
|
||||
- HOLD теперь очищает confirmation runtime state
|
||||
- READY теперь требует repeats и удержания сигнала
|
||||
- добавлена защита от micro-breakout noise
|
||||
- добавлена защита от single candle reversal
|
||||
- добавлена защита от volatility spikes
|
||||
- TREND runtime стал устойчивее к ложным импульсам
|
||||
- улучшено распознавание устойчивого directional movement
|
||||
- улучшена фильтрация краткосрочного рыночного шума
|
||||
- Telegram UI получил runtime confirmation line
|
||||
- UI показывает `⏳ Подтверждение`
|
||||
- UI показывает `✅ Подтверждение · готово`
|
||||
- confirmation line отображается только для BUY / SELL
|
||||
- HOLD больше не показывает confirmation line
|
||||
- confirmation runtime интегрирован в decision lifecycle
|
||||
- decision_status теперь полноценно использует CONFIRMING state
|
||||
- signal confirmation runtime синхронизирован с READY state
|
||||
- debug_force_signal обновляет confirmation runtime state
|
||||
- signal_ready journal events расширены confirmation analytics
|
||||
- signal runtime стал более stateful
|
||||
- AutoTrade приблизился к professional signal confirmation flow
|
||||
- подготовлена база для adaptive confirmation thresholds
|
||||
- подготовлена база для probabilistic signal scoring
|
||||
- подготовлена база для multi-timeframe confirmation
|
||||
- подготовлена база для direction persistence engine
|
||||
- подготовлена база для execution scoring system
|
||||
- подготовлена база для probabilistic signal engine
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -659,6 +659,109 @@
|
||||
- подготовлена база для semantic entry filters
|
||||
- подготовлена база для более точного TREND execution
|
||||
|
||||
#### 07.4.4.1.9.1 ✅ Market Semantic Runtime Layer
|
||||
- добавлен semantic runtime layer поверх adaptive market diagnostics
|
||||
- добавлена единая semantic-интерпретация market_state / trend / strength / quality / phase
|
||||
- добавлена функция `_market_semantic_line()` в Telegram UI
|
||||
- `_market_semantic_line()` заменяет связку `_market_state_line()` и `_market_diagnostics_line()`
|
||||
- UI больше не показывает раздельно технические признаки тренда и фазы
|
||||
- UI теперь показывает одно итоговое смысловое состояние рынка
|
||||
- добавлена semantic-нормализация TREND_UP состояний
|
||||
- добавлена semantic-нормализация TREND_DOWN состояний
|
||||
- добавлена semantic-нормализация RANGE состояния
|
||||
- добавлена semantic-нормализация LOW_VOLATILITY / SQUEEZE состояния
|
||||
- добавлена semantic-нормализация HIGH_VOLATILITY состояния
|
||||
- добавлена semantic-нормализация UNKNOWN состояния
|
||||
- добавлено состояние `⚡ Рынок · сильный рост`
|
||||
- добавлено состояние `⚡ Рынок · сильное снижение`
|
||||
- добавлено состояние `📈 Рынок · рост`
|
||||
- добавлено состояние `📉 Рынок · снижение`
|
||||
- добавлено состояние `🟡 Рынок · слабый рост`
|
||||
- добавлено состояние `🟡 Рынок · слабое снижение`
|
||||
- добавлено состояние `⚠️ Рынок · шумный рост`
|
||||
- добавлено состояние `⚠️ Рынок · шумное снижение`
|
||||
- добавлено состояние `↘️ Рынок · коррекция`
|
||||
- добавлено состояние `↗️ Рынок · откат вверх`
|
||||
- добавлено состояние `🟰 Рынок · флэт`
|
||||
- добавлено состояние `🟦 Рынок · сжатие`
|
||||
- добавлено состояние `⚠️ Рынок · перегрев`
|
||||
- добавлено состояние `⏳ Рынок · анализ`
|
||||
- добавлен приоритет semantic-состояний рынка
|
||||
- HIGH_VOLATILITY получает приоритет над обычным трендом
|
||||
- LOW_VOLATILITY / SQUEEZE отображается как semantic-сжатие
|
||||
- RANGE отображается как semantic-флэт
|
||||
- PULLBACK отображается как коррекция или откат вверх
|
||||
- NOISY trend отображается как шумный рост или шумное снижение
|
||||
- WEAK trend отображается как слабый рост или слабое снижение
|
||||
- STRONG + CLEAN + IMPULSE отображается как сильный рост или сильное снижение
|
||||
- NORMAL + CLEAN + IMPULSE отображается как рост или снижение
|
||||
- fallback по направлению тренда сохранён
|
||||
- execution quality не смешивается с market semantic layer
|
||||
- строки `Вход · spread`, `Вход · нет стакана`, `Вход · нет данных рынка` остались отдельными
|
||||
- строка `Фильтр` осталась отдельной для причин HOLD
|
||||
- экран ожидания AutoTrade переведён на semantic market line
|
||||
- экран активной позиции AutoTrade переведён на semantic market line
|
||||
- убрано дублирование `Тренд` + `Фаза` в UI
|
||||
- экран автоторговли стал короче
|
||||
- экран автоторговли стал понятнее для runtime-наблюдения
|
||||
- HOLD diagnostics стали более смысловыми
|
||||
- market state теперь отображается как торговый смысл, а не как raw enum
|
||||
- подготовлена база для semantic entry filters
|
||||
- подготовлена база для market regime scoring
|
||||
- подготовлена база для adaptive threshold tuning
|
||||
- подготовлена база для confidence scoring по состоянию рынка
|
||||
- подготовлена база для объяснимого AutoTrade decision screen
|
||||
|
||||
#### 07.4.4.1.9.2 ✅ Signal Confirmation Runtime
|
||||
- добавлен Signal Confirmation Runtime layer
|
||||
- confirmation runtime вынесен в отдельный stateful lifecycle
|
||||
- BUY / SELL больше не переходят мгновенно в READY
|
||||
- добавлена runtime-фаза CONFIRMING
|
||||
- confirmation теперь требует repeat consistency
|
||||
- confirmation теперь требует time persistence
|
||||
- добавлен minimum signal lifetime filter
|
||||
- добавлен `_confirm_min_duration_seconds`
|
||||
- BUY / SELL теперь обязаны удерживаться во времени
|
||||
- runtime теперь анализирует signal persistence
|
||||
- runtime теперь анализирует directional continuity
|
||||
- runtime теперь анализирует signal lifetime
|
||||
- добавлен confirmation progress runtime
|
||||
- добавлен `signal_confirmation_seconds`
|
||||
- добавлен `signal_confirmation_required_seconds`
|
||||
- добавлен `signal_confirmation_missing_repeats`
|
||||
- добавлен `signal_confirmation_progress`
|
||||
- добавлен `signal_confirmation_reason`
|
||||
- confirmation progress теперь рассчитывается по repeat progress
|
||||
- confirmation progress теперь рассчитывается по time progress
|
||||
- BUY / SELL проходят lifecycle HOLD → CONFIRMING → READY
|
||||
- HOLD теперь сбрасывает confirmation progress
|
||||
- HOLD теперь очищает confirmation runtime state
|
||||
- READY теперь требует repeats и удержания сигнала
|
||||
- добавлена защита от micro-breakout noise
|
||||
- добавлена защита от single candle reversal
|
||||
- добавлена защита от volatility spikes
|
||||
- TREND runtime стал устойчивее к ложным импульсам
|
||||
- улучшено распознавание устойчивого directional movement
|
||||
- улучшена фильтрация краткосрочного рыночного шума
|
||||
- Telegram UI получил runtime confirmation line
|
||||
- UI показывает `⏳ Подтверждение`
|
||||
- UI показывает `✅ Подтверждение · готово`
|
||||
- confirmation line отображается только для BUY / SELL
|
||||
- HOLD больше не показывает confirmation line
|
||||
- confirmation runtime интегрирован в decision lifecycle
|
||||
- decision_status теперь полноценно использует CONFIRMING state
|
||||
- signal confirmation runtime синхронизирован с READY state
|
||||
- debug_force_signal обновляет confirmation runtime state
|
||||
- signal_ready journal events расширены confirmation analytics
|
||||
- signal runtime стал более stateful
|
||||
- AutoTrade приблизился к professional signal confirmation flow
|
||||
- подготовлена база для adaptive confirmation thresholds
|
||||
- подготовлена база для probabilistic signal scoring
|
||||
- подготовлена база для multi-timeframe confirmation
|
||||
- подготовлена база для direction persistence engine
|
||||
- подготовлена база для execution scoring system
|
||||
- подготовлена база для probabilistic signal engine
|
||||
|
||||
---
|
||||
|
||||
### 07.4.5
|
||||
|
||||
452
docs/stages/stage-07_4_4_1_9_1-market_semantic_runtime_layer.md
Normal file
452
docs/stages/stage-07_4_4_1_9_1-market_semantic_runtime_layer.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# 07.4.4.1.9.1 Market Semantic Runtime Layer
|
||||
|
||||
## Что сделано
|
||||
|
||||
Добавлен Market Semantic Runtime Layer — слой, который переводит технические состояния market analytics в понятные конечные semantic-состояния для UI и дальнейших runtime-фильтров.
|
||||
|
||||
До этого этапа система уже умела рассчитывать расширенную диагностику рынка:
|
||||
|
||||
- market_state
|
||||
- market_trend
|
||||
- market_volatility
|
||||
- market_trend_strength
|
||||
- market_trend_quality
|
||||
- market_phase
|
||||
- market_trend_gap_percent
|
||||
- market_trend_consistency
|
||||
|
||||
Но UI отображал эти признаки раздельно:
|
||||
|
||||
- Тренд · Вверх
|
||||
- Фаза · коррекция
|
||||
- Качество · шум
|
||||
- Сила · слабая
|
||||
|
||||
Это было информативно, но могло путать, потому что одно и то же состояние рынка раскладывалось на несколько строк.
|
||||
|
||||
На этом этапе добавлен semantic layer, который собирает runtime-признаки в одну итоговую смысловую строку.
|
||||
|
||||
Теперь вместо набора технических признаков экран показывает одно конечное состояние рынка:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
↘️ Рынок · коррекция
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
⚠️ Рынок · шумный рост
|
||||
```
|
||||
|
||||
## Главная цель этапа
|
||||
|
||||
Цель этапа — сделать market analytics не просто технической диагностикой, а понятным semantic runtime-состоянием.
|
||||
|
||||
Система теперь отвечает не только на вопрос:
|
||||
|
||||
```text
|
||||
Какие технические признаки у рынка?
|
||||
```
|
||||
|
||||
но и на вопрос:
|
||||
|
||||
```text
|
||||
Что это состояние рынка значит для автоторговли прямо сейчас?
|
||||
```
|
||||
|
||||
## Что изменилось в аналитике
|
||||
|
||||
### 1. Добавлена semantic-нормализация market state
|
||||
|
||||
Ранее MarketAnalysisService и TrendStrategy передавали в UI набор независимых признаков:
|
||||
|
||||
```text
|
||||
TREND_UP
|
||||
STRONG
|
||||
CLEAN
|
||||
IMPULSE
|
||||
```
|
||||
|
||||
Теперь UI получает возможность интерпретировать эту комбинацию как единое смысловое состояние:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
```
|
||||
|
||||
То есть слой не заменяет аналитику, а делает её результат удобным для принятия решения.
|
||||
|
||||
### 2. Введена единая строка состояния рынка
|
||||
|
||||
В Telegram UI добавлена функция:
|
||||
|
||||
```python
|
||||
_market_semantic_line(state)
|
||||
```
|
||||
|
||||
Она заменяет старую пару строк:
|
||||
|
||||
```python
|
||||
_market_state_line(state)
|
||||
_market_diagnostics_line(state)
|
||||
```
|
||||
|
||||
Теперь вместо двух раздельных строк формируется одна semantic-строка.
|
||||
|
||||
Было:
|
||||
|
||||
```text
|
||||
📈 Тренд · Вверх
|
||||
⚡ Импульс · сильный
|
||||
```
|
||||
|
||||
Стало:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
```
|
||||
|
||||
Было:
|
||||
|
||||
```text
|
||||
📈 Тренд · Вверх
|
||||
↘️ Фаза · коррекция
|
||||
```
|
||||
|
||||
Стало:
|
||||
|
||||
```text
|
||||
↘️ Рынок · коррекция
|
||||
```
|
||||
|
||||
Было:
|
||||
|
||||
```text
|
||||
📉 Тренд · Вниз
|
||||
⚠️ Качество · шум
|
||||
```
|
||||
|
||||
Стало:
|
||||
|
||||
```text
|
||||
⚠️ Рынок · шумное снижение
|
||||
```
|
||||
|
||||
### 3. Убрано дублирование смыслов в UI
|
||||
|
||||
До этапа состояние рынка могло одновременно отображаться как:
|
||||
|
||||
```text
|
||||
📈 Тренд · Вверх
|
||||
↘️ Фаза · коррекция
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
🟰 Рынок · Флэт
|
||||
```
|
||||
|
||||
и это смешивало уровни анализа:
|
||||
|
||||
- направление
|
||||
- фазу
|
||||
- качество
|
||||
- силу
|
||||
- применимость для входа
|
||||
|
||||
После внедрения semantic layer UI показывает итоговую интерпретацию, а не набор технических слоёв.
|
||||
|
||||
### 4. Добавлена приоритетность market semantics
|
||||
|
||||
При формировании semantic-состояния используется приоритет:
|
||||
|
||||
1. UNKNOWN
|
||||
2. HIGH_VOLATILITY
|
||||
3. LOW_VOLATILITY / SQUEEZE
|
||||
4. RANGE
|
||||
5. PULLBACK
|
||||
6. NOISY
|
||||
7. WEAK
|
||||
8. STRONG IMPULSE
|
||||
9. NORMAL IMPULSE
|
||||
10. fallback по направлению тренда
|
||||
|
||||
Это важно, потому что одно состояние может содержать несколько признаков одновременно.
|
||||
|
||||
Например:
|
||||
|
||||
```text
|
||||
TREND_UP + NOISY + PULLBACK
|
||||
```
|
||||
|
||||
В этом случае важнее показать:
|
||||
|
||||
```text
|
||||
↘️ Рынок · коррекция
|
||||
```
|
||||
|
||||
а не просто:
|
||||
|
||||
```text
|
||||
⚠️ Рынок · шумный рост
|
||||
```
|
||||
|
||||
Потому что pullback сильнее влияет на вход TREND-стратегии.
|
||||
|
||||
### 5. Улучшена читаемость TREND-аналитики
|
||||
|
||||
Для стратегии TREND semantic layer особенно важен, потому что стратегия работает только тогда, когда рынок не просто направленный, а достаточно качественный для входа.
|
||||
|
||||
Теперь пользователь видит не внутреннюю механику:
|
||||
|
||||
```text
|
||||
trend=UP
|
||||
strength=WEAK
|
||||
quality=CLEAN
|
||||
phase=RANGE
|
||||
```
|
||||
|
||||
а итог:
|
||||
|
||||
```text
|
||||
🟡 Рынок · слабый рост
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
⚠️ Рынок · шумный рост
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
↘️ Рынок · коррекция
|
||||
```
|
||||
|
||||
Это упрощает понимание причин HOLD.
|
||||
|
||||
## Поддерживаемые semantic-состояния рынка
|
||||
|
||||
### Рост
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
📈 Рынок · рост
|
||||
🟡 Рынок · слабый рост
|
||||
⚠️ Рынок · шумный рост
|
||||
↘️ Рынок · коррекция
|
||||
```
|
||||
|
||||
### Снижение
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильное снижение
|
||||
📉 Рынок · снижение
|
||||
🟡 Рынок · слабое снижение
|
||||
⚠️ Рынок · шумное снижение
|
||||
↗️ Рынок · откат вверх
|
||||
```
|
||||
|
||||
### Нейтральные состояния
|
||||
|
||||
```text
|
||||
🟰 Рынок · флэт
|
||||
🟦 Рынок · сжатие
|
||||
⚠️ Рынок · перегрев
|
||||
⏳ Рынок · анализ
|
||||
```
|
||||
|
||||
## Как semantic-состояния связаны с TREND-стратегией
|
||||
|
||||
### Потенциально рабочие состояния
|
||||
|
||||
Эти состояния могут быть пригодны для входа, если live-импульс подтвердит направление:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
📈 Рынок · рост
|
||||
⚡ Рынок · сильное снижение
|
||||
📉 Рынок · снижение
|
||||
```
|
||||
|
||||
### Блокирующие или HOLD-состояния
|
||||
|
||||
Эти состояния не являются подходящими для TREND-входа:
|
||||
|
||||
```text
|
||||
🟡 Рынок · слабый рост
|
||||
🟡 Рынок · слабое снижение
|
||||
⚠️ Рынок · шумный рост
|
||||
⚠️ Рынок · шумное снижение
|
||||
↘️ Рынок · коррекция
|
||||
↗️ Рынок · откат вверх
|
||||
🟰 Рынок · флэт
|
||||
🟦 Рынок · сжатие
|
||||
⚠️ Рынок · перегрев
|
||||
⏳ Рынок · анализ
|
||||
```
|
||||
|
||||
Важно: semantic line сама по себе не открывает и не блокирует сделку.
|
||||
|
||||
Решение принимает TrendStrategy и ExecutionEngine.
|
||||
|
||||
Но semantic line теперь корректно показывает пользователю смысл этого решения.
|
||||
|
||||
## Что изменилось в Telegram UI
|
||||
|
||||
### Было
|
||||
|
||||
UI показывал технические строки:
|
||||
|
||||
```text
|
||||
📈 Тренд · Вверх
|
||||
↘️ Фаза · коррекция
|
||||
🧩 Фильтр · откат
|
||||
⚠️ Вход · spread 0.12%
|
||||
```
|
||||
|
||||
### Стало
|
||||
|
||||
UI показывает одну смысловую строку рынка:
|
||||
|
||||
```text
|
||||
↘️ Рынок · коррекция
|
||||
🧩 Фильтр · откат
|
||||
⚠️ Вход · spread 0.12%
|
||||
```
|
||||
|
||||
Или в рабочем сценарии:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
```
|
||||
|
||||
Если дополнительных блокировок нет, лишние строки не отображаются.
|
||||
|
||||
## Разделение ответственности строк UI
|
||||
|
||||
После этапа строки разделены так:
|
||||
|
||||
### Сигнал
|
||||
|
||||
Показывает текущий торговый сигнал стратегии:
|
||||
|
||||
```text
|
||||
Сигнал 🟡 HOLD · 27с
|
||||
Сигнал 🟢 BUY · READY
|
||||
Сигнал 🔴 SELL · READY
|
||||
```
|
||||
|
||||
### Рынок
|
||||
|
||||
Показывает semantic-состояние market analytics:
|
||||
|
||||
```text
|
||||
⚡ Рынок · сильный рост
|
||||
↘️ Рынок · коррекция
|
||||
🟰 Рынок · флэт
|
||||
```
|
||||
|
||||
### Фильтр
|
||||
|
||||
Показывает причину, почему стратегия не даёт вход:
|
||||
|
||||
```text
|
||||
🧩 Фильтр · слабый тренд
|
||||
🧩 Фильтр · шумный тренд
|
||||
🧩 Фильтр · откат
|
||||
```
|
||||
|
||||
### Вход
|
||||
|
||||
Показывает проблемы именно execution/runtime качества:
|
||||
|
||||
```text
|
||||
⚠️ Вход · spread 0.12%
|
||||
⛔ Вход · высокий spread 0.18%
|
||||
⚠️ Вход · нет стакана
|
||||
⛔ Вход · нет данных рынка
|
||||
```
|
||||
|
||||
## Что было исправлено
|
||||
|
||||
- убрано дублирование строк market state и diagnostics
|
||||
- устранена неоднозначность между “Тренд”, “Рынок”, “Фаза”, “Качество”
|
||||
- semantic-состояние рынка теперь отображается одной строкой
|
||||
- UI стал стабильнее и короче
|
||||
- экран автоторговли стал легче читать
|
||||
- состояния рынка стали ближе к смыслу торгового решения
|
||||
- HOLD-причины стали понятнее
|
||||
- состояние “рынок готов” больше не перегружает UI лишними строками
|
||||
- execution quality остался отдельным от market semantics
|
||||
|
||||
## Что осталось отдельным от semantic layer
|
||||
|
||||
Market Semantic Runtime Layer не заменяет execution quality.
|
||||
|
||||
То есть строки вида:
|
||||
|
||||
```text
|
||||
⚠️ Вход · spread 0.12%
|
||||
⛔ Вход · высокий spread 0.18%
|
||||
```
|
||||
|
||||
остаются отдельными, потому что они относятся не к анализу направления рынка, а к качеству входа и возможности исполнения.
|
||||
|
||||
Это правильное разделение:
|
||||
|
||||
- `Рынок` — что происходит с market structure
|
||||
- `Фильтр` — почему стратегия не входит
|
||||
- `Вход` — можно ли технически исполнить вход
|
||||
|
||||
## Проверка
|
||||
|
||||
После внедрения нужно проверить:
|
||||
|
||||
```bash
|
||||
python -m compileall src
|
||||
```
|
||||
|
||||
Runtime-проверка:
|
||||
|
||||
- экран автоторговли открывается
|
||||
- Auto screen refresh не падает
|
||||
- строка `Рынок` отображается одной semantic-строкой
|
||||
- нет одновременного дубля `Тренд · Вверх` + `Фаза · ...`
|
||||
- HOLD timer продолжает обновляться
|
||||
- при слабом тренде отображается `🟡 Рынок · слабый рост/снижение`
|
||||
- при шумном тренде отображается `⚠️ Рынок · шумный рост/снижение`
|
||||
- при откате отображается `↘️ Рынок · коррекция` или `↗️ Рынок · откат вверх`
|
||||
- при флэте отображается `🟰 Рынок · флэт`
|
||||
- при squeeze отображается `🟦 Рынок · сжатие`
|
||||
- при высокой волатильности отображается `⚠️ Рынок · перегрев`
|
||||
- execution quality продолжает отображаться отдельной строкой `Вход`
|
||||
- spread warning не смешивается со строкой `Рынок`
|
||||
- active position screen тоже использует semantic market line
|
||||
|
||||
## Результат
|
||||
|
||||
Этап завершил переход от raw market diagnostics к semantic runtime-представлению.
|
||||
|
||||
Теперь система имеет три уровня:
|
||||
|
||||
1. MarketAnalysisService рассчитывает технические признаки.
|
||||
2. TrendStrategy использует признаки для HOLD / BUY / SELL.
|
||||
3. Telegram UI показывает человеку итоговое semantic-состояние рынка.
|
||||
|
||||
Это подготовило базу для следующих этапов:
|
||||
|
||||
- semantic entry filters
|
||||
- adaptive threshold tuning
|
||||
- market regime scoring
|
||||
- confidence scoring по semantic-состояниям
|
||||
- более точного TREND execution
|
||||
- future strategy arbitration
|
||||
- объяснимого AutoTrade decision screen
|
||||
212
docs/stages/stage-07_4_4_1_9_2-signal_confirmation_runtime.md
Normal file
212
docs/stages/stage-07_4_4_1_9_2-signal_confirmation_runtime.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# 07.4.4.1.9.2 Signal Confirmation Runtime
|
||||
|
||||
## Что сделано
|
||||
|
||||
Добавлен Signal Confirmation Runtime — отдельный runtime-слой подтверждения торгового сигнала, который находится между raw BUY/SELL сигналом стратегии и итоговым READY-состоянием AutoTrade.
|
||||
|
||||
До этого этапа система подтверждала сигнал только количеством одинаковых повторов:
|
||||
|
||||
```text
|
||||
BUY → BUY
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
SELL → SELL
|
||||
```
|
||||
|
||||
После достижения `_confirm_repeats` сигнал сразу переходил в:
|
||||
|
||||
```text
|
||||
READY
|
||||
```
|
||||
|
||||
Теперь подтверждение сигнала стало полноценным runtime-процессом, который требует:
|
||||
|
||||
1. повторяемости сигнала
|
||||
2. удержания сигнала во времени
|
||||
|
||||
---
|
||||
|
||||
# Главная цель этапа
|
||||
|
||||
Цель этапа — научить AutoTrade отличать:
|
||||
|
||||
```text
|
||||
кратковременный рыночный шум
|
||||
```
|
||||
|
||||
от:
|
||||
|
||||
```text
|
||||
устойчивого directional impulse
|
||||
```
|
||||
|
||||
Теперь система оценивает не только:
|
||||
|
||||
```text
|
||||
Что сигнал появился
|
||||
```
|
||||
|
||||
но и:
|
||||
|
||||
```text
|
||||
Насколько стабильно рынок удерживает это направление
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Что изменилось в аналитике
|
||||
|
||||
## 1. Подтверждение стало time-aware
|
||||
|
||||
Ранее confirmation runtime был только repeat-based:
|
||||
|
||||
```text
|
||||
BUY → BUY → READY
|
||||
```
|
||||
|
||||
Теперь confirmation стал:
|
||||
|
||||
```text
|
||||
BUY
|
||||
→ BUY удерживается
|
||||
→ BUY повторяется
|
||||
→ BUY живёт достаточно долго
|
||||
→ READY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Добавлен runtime-анализ устойчивости сигнала
|
||||
|
||||
Signal Confirmation Runtime теперь анализирует:
|
||||
|
||||
- длительность жизни BUY/SELL
|
||||
- стабильность направления
|
||||
- количество повторов
|
||||
- consistency сигнала во времени
|
||||
|
||||
---
|
||||
|
||||
## 3. Добавлен signal lifetime tracking
|
||||
|
||||
В `AutoTradeState` добавлены:
|
||||
|
||||
```python
|
||||
signal_confirmation_seconds
|
||||
signal_confirmation_required_seconds
|
||||
signal_confirmation_missing_repeats
|
||||
signal_confirmation_progress
|
||||
signal_confirmation_reason
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. BUY/SELL теперь проходят confirmation phase
|
||||
|
||||
Ранее жизненный цикл был:
|
||||
|
||||
```text
|
||||
HOLD
|
||||
→ BUY
|
||||
→ READY
|
||||
```
|
||||
|
||||
Теперь:
|
||||
|
||||
```text
|
||||
HOLD
|
||||
→ BUY
|
||||
→ CONFIRMING
|
||||
→ READY
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
HOLD
|
||||
→ SELL
|
||||
→ CONFIRMING
|
||||
→ READY
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Добавлен signal persistence filter
|
||||
|
||||
Теперь сигнал обязан удерживаться минимум:
|
||||
|
||||
```python
|
||||
_confirm_min_duration_seconds = 10
|
||||
```
|
||||
|
||||
Даже если repeats уже подтверждены — READY не наступит, пока сигнал не проживёт достаточное время.
|
||||
|
||||
---
|
||||
|
||||
# Что изменилось в Telegram UI
|
||||
|
||||
## Добавлена runtime-строка подтверждения сигнала
|
||||
|
||||
Для BUY/SELL теперь отображается:
|
||||
|
||||
```text
|
||||
⏳ Подтверждение · 1/2 · 4/10с
|
||||
```
|
||||
|
||||
или:
|
||||
|
||||
```text
|
||||
✅ Подтверждение · готово
|
||||
```
|
||||
|
||||
## HOLD больше не показывает confirmation line
|
||||
|
||||
HOLD отображается как раньше:
|
||||
|
||||
```text
|
||||
Сигнал 🟡 HOLD · 54с
|
||||
```
|
||||
|
||||
без confirmation runtime строки.
|
||||
|
||||
---
|
||||
|
||||
# Проверка
|
||||
|
||||
После внедрения:
|
||||
|
||||
```bash
|
||||
python -m compileall src
|
||||
```
|
||||
|
||||
Runtime-проверка:
|
||||
|
||||
- HOLD не показывает confirmation line
|
||||
- BUY показывает `⏳ Подтверждение`
|
||||
- SELL показывает `⏳ Подтверждение`
|
||||
- READY показывает `✅ Подтверждение · готово`
|
||||
- READY не появляется мгновенно
|
||||
- READY требует repeats
|
||||
- READY требует удержания сигнала
|
||||
- HOLD сбрасывает confirmation progress
|
||||
|
||||
---
|
||||
|
||||
# Результат
|
||||
|
||||
Этап завершил переход от:
|
||||
|
||||
```text
|
||||
repeat-based signal confirmation
|
||||
```
|
||||
|
||||
к:
|
||||
|
||||
```text
|
||||
stateful runtime confirmation engine
|
||||
```
|
||||
|
||||
Теперь AutoTrade анализирует не только факт сигнала, но и устойчивость направления во времени.
|
||||
Reference in New Issue
Block a user