07.4.3.16 — Production Execution Pricing Layer
This commit is contained in:
@@ -296,7 +296,7 @@ def _market_snapshot(symbol: str | None) -> dict[str, object] | None:
|
||||
return None
|
||||
|
||||
try:
|
||||
return ExchangeService().get_market_snapshot(symbol)
|
||||
return ExchangeService().get_market_snapshot(symbol, runtime_key="auto")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
@@ -18,6 +20,11 @@ from src.trading.debug.service import DebugTradeService
|
||||
router = Router(name="debug_auto")
|
||||
|
||||
|
||||
def _ensure_signal_started_at(state) -> None:
|
||||
if state.signal_started_at is None:
|
||||
state.signal_started_at = time.monotonic()
|
||||
|
||||
|
||||
async def render_debug_auto_screen(
|
||||
target_message: Message,
|
||||
*,
|
||||
@@ -77,6 +84,7 @@ async def debug_auto_start(callback: CallbackQuery) -> None:
|
||||
service = DebugTradeService()
|
||||
state = service.get_state()
|
||||
state.status = "RUNNING"
|
||||
_ensure_signal_started_at(state)
|
||||
|
||||
DebugTradeRunner.set_current_screen("debug_auto")
|
||||
DebugTradeRunner.start()
|
||||
@@ -89,6 +97,7 @@ async def debug_auto_start(callback: CallbackQuery) -> None:
|
||||
|
||||
@router.callback_query(F.data == "debug_auto:stop")
|
||||
async def debug_auto_stop(callback: CallbackQuery) -> None:
|
||||
DebugTradeRunner.set_current_screen("debug_auto")
|
||||
DebugTradeRunner.stop()
|
||||
DebugTradeService().stop()
|
||||
|
||||
@@ -145,6 +154,8 @@ async def debug_auto_close(callback: CallbackQuery) -> None:
|
||||
service = DebugTradeService()
|
||||
_, result = service.close(reason="DEBUG_SCREEN_CLOSE")
|
||||
|
||||
DebugTradeRunner.set_current_screen("debug_auto")
|
||||
|
||||
if callback.message is not None:
|
||||
await render_debug_auto_screen(callback.message, edit_mode=True)
|
||||
|
||||
@@ -153,7 +164,9 @@ async def debug_auto_close(callback: CallbackQuery) -> None:
|
||||
|
||||
@router.callback_query(F.data == "debug_auto:reset")
|
||||
async def debug_auto_reset(callback: CallbackQuery) -> None:
|
||||
DebugTradeService().reset()
|
||||
DebugTradeRunner.set_current_screen("debug_auto")
|
||||
state = DebugTradeService().reset()
|
||||
_ensure_signal_started_at(state)
|
||||
|
||||
if callback.message is not None:
|
||||
await render_debug_auto_screen(callback.message, edit_mode=True)
|
||||
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
from aiogram.types import InlineKeyboardMarkup
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.trading.debug.service import DebugTradeService
|
||||
|
||||
|
||||
@@ -38,6 +39,8 @@ def build_debug_auto_text() -> str:
|
||||
f"<b>Баланс</b> · $ {_format_money_compact(state.allocated_balance_usd)}",
|
||||
f"<b>Realized PnL</b> · {_format_signed_usd(state.realized_pnl_usd)}",
|
||||
"",
|
||||
*_market_snapshot_lines(state.symbol),
|
||||
"",
|
||||
_signal_line(state),
|
||||
]
|
||||
|
||||
@@ -106,6 +109,90 @@ def build_debug_auto_text() -> str:
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _format_updated_at(value: object) -> str:
|
||||
if not value:
|
||||
return "—"
|
||||
|
||||
text = str(value)
|
||||
|
||||
if " " in text:
|
||||
return text.rsplit(" ", 1)[-1]
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def _market_snapshot_lines(symbol: str | None) -> list[str]:
|
||||
if not symbol:
|
||||
return [
|
||||
"<b>Market</b>",
|
||||
"Last · —",
|
||||
"Bid · —",
|
||||
"Ask · —",
|
||||
"Source · —",
|
||||
"Age · —",
|
||||
"",
|
||||
"<b>Execution</b>",
|
||||
"Source · —",
|
||||
"Age · —",
|
||||
]
|
||||
|
||||
market = None
|
||||
execution = None
|
||||
error = None
|
||||
|
||||
try:
|
||||
market = ExchangeService().get_market_snapshot(
|
||||
symbol,
|
||||
runtime_key="debug_auto",
|
||||
)
|
||||
except Exception as exc:
|
||||
error = str(exc)
|
||||
|
||||
try:
|
||||
execution = ExchangeService().get_execution_snapshot(
|
||||
symbol,
|
||||
runtime_key="debug_auto",
|
||||
)
|
||||
except Exception as exc:
|
||||
if error is None:
|
||||
error = str(exc)
|
||||
|
||||
if market is None and execution is None:
|
||||
return [
|
||||
"<b>Market</b>",
|
||||
"Last · —",
|
||||
"Bid · —",
|
||||
"Ask · —",
|
||||
"Source · error",
|
||||
f"Error · {error or 'unknown'}",
|
||||
]
|
||||
|
||||
last_price = market.get("last_price") if market else getattr(execution, "last_price", None)
|
||||
bid_price = market.get("bid_price") if market else getattr(execution, "bid_price", None)
|
||||
ask_price = market.get("ask_price") if market else getattr(execution, "ask_price", None)
|
||||
market_source = market.get("source") if market else "—"
|
||||
market_age = market.get("age_seconds") if market else None
|
||||
|
||||
execution_source = getattr(execution, "source", "—") if execution else "—"
|
||||
execution_age = getattr(execution, "age_seconds", None) if execution else None
|
||||
execution_fresh = getattr(execution, "is_fresh", None) if execution else None
|
||||
|
||||
return [
|
||||
"<b>Market</b>",
|
||||
f"Last · {_format_usd_or_dash(last_price)}",
|
||||
f"Bid · {_format_usd_or_dash(bid_price)}",
|
||||
f"Ask · {_format_usd_or_dash(ask_price)}",
|
||||
f"Source · {market_source or '—'}",
|
||||
f"Quote age · {_format_age(market_age)}",
|
||||
f"Exchange time · {_format_updated_at(market.get('updated_at') if market else None)}",
|
||||
"",
|
||||
"<b>Execution</b>",
|
||||
f"Source · {execution_source or '—'}",
|
||||
f"Quote age · {_format_age(execution_age)}",
|
||||
f"Fresh · {_format_bool(execution_fresh)}",
|
||||
]
|
||||
|
||||
|
||||
def _signal_line(state) -> str:
|
||||
signal = state.last_signal or "HOLD"
|
||||
|
||||
@@ -225,4 +312,44 @@ def _format_signed_usd(value: float | int | None) -> str:
|
||||
if amount < 0:
|
||||
return f"🔴 −$ {_format_money_compact(abs(amount))}"
|
||||
|
||||
return "$ 0"
|
||||
return "$ 0"
|
||||
|
||||
|
||||
def _format_age(value: object) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
|
||||
try:
|
||||
age = max(0.0, float(value))
|
||||
except (TypeError, ValueError):
|
||||
return "—"
|
||||
|
||||
if age < 1:
|
||||
return f"{age:.2f}с"
|
||||
|
||||
if age < 10:
|
||||
return f"{age:.1f}с"
|
||||
|
||||
total_seconds = int(age)
|
||||
|
||||
hours = total_seconds // 3600
|
||||
minutes = (total_seconds % 3600) // 60
|
||||
seconds = total_seconds % 60
|
||||
|
||||
if hours > 0:
|
||||
return f"{hours}ч {minutes:02d}м"
|
||||
|
||||
if minutes > 0:
|
||||
return f"{minutes}м {seconds:02d}с"
|
||||
|
||||
return f"{seconds}с"
|
||||
|
||||
|
||||
def _format_bool(value: object) -> str:
|
||||
if value is True:
|
||||
return "yes"
|
||||
|
||||
if value is False:
|
||||
return "no"
|
||||
|
||||
return "—"
|
||||
Reference in New Issue
Block a user