73 строки
2.6 KiB
Python
73 строки
2.6 KiB
Python
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.ai_bot import AiTradingBot
|
|
from app.core import Settings
|
|
from app.models import AiDecision
|
|
|
|
|
|
@dataclass
|
|
class ExecutionResult:
|
|
executed: bool
|
|
mode: str
|
|
message: str
|
|
payload: dict[str, Any]
|
|
|
|
|
|
class LiveExecutionGuard:
|
|
def __init__(self, settings: Settings, ai_bot: AiTradingBot) -> None:
|
|
self.settings = settings
|
|
self.ai_bot = ai_bot
|
|
|
|
def assert_live_ready(self, db: Session) -> dict[str, Any]:
|
|
state = self.ai_bot.get_or_create_state(db)
|
|
problems: list[str] = []
|
|
|
|
if state.trade_mode != 'live':
|
|
problems.append('bot state trade_mode is not live')
|
|
if not state.live_acknowledged:
|
|
problems.append('live trading risk acknowledgement is missing')
|
|
if state.emergency_stop:
|
|
problems.append('emergency stop is active')
|
|
if not self.settings.enable_live_trading or self.settings.trade_mode != 'live':
|
|
problems.append('environment live flags are not enabled')
|
|
if not self.settings.coinex_credentials_present:
|
|
problems.append('CoinEx credentials are missing')
|
|
|
|
return {
|
|
'ready': not problems,
|
|
'problems': problems,
|
|
'state': self.ai_bot.state_to_dict(state),
|
|
'env_live_enabled': self.settings.live_enabled,
|
|
'coinex_credentials_present': self.settings.coinex_credentials_present,
|
|
}
|
|
|
|
def execute_guarded_live_order(self, db: Session, decision: AiDecision, quote_amount: float) -> ExecutionResult:
|
|
readiness = self.assert_live_ready(db)
|
|
risk = self.ai_bot.risk_check(db, decision, quote_amount)
|
|
|
|
if not readiness['ready']:
|
|
return ExecutionResult(
|
|
executed=False,
|
|
mode='live_guard_blocked',
|
|
message='Live execution is not ready',
|
|
payload={'readiness': readiness, 'risk': risk},
|
|
)
|
|
|
|
if not risk['allowed']:
|
|
return ExecutionResult(
|
|
executed=False,
|
|
mode='live_risk_blocked',
|
|
message='Risk engine blocked live execution',
|
|
payload={'readiness': readiness, 'risk': risk},
|
|
)
|
|
|
|
return ExecutionResult(
|
|
executed=False,
|
|
mode='live_adapter_pending',
|
|
message='Live guard passed. Low-level CoinEx order adapter is intentionally separated for final verification.',
|
|
payload={'readiness': readiness, 'risk': risk, 'decision_id': decision.id},
|
|
)
|