build 039: complete Quotes Feed migration foundation
This commit is contained in:
1
app/src/trading/decision/__init__.py
Normal file
1
app/src/trading/decision/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# app/src/trading/decision/__init__.py
|
||||
19
app/src/trading/decision/exceptions.py
Normal file
19
app/src/trading/decision/exceptions.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# app/src/trading/decision/exceptions.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class TradingError(Exception):
|
||||
"""Базовая ошибка Trading Layer."""
|
||||
|
||||
|
||||
class InvalidTradingDecisionError(TradingError):
|
||||
"""Trading Layer сформировал некорректное торговое решение."""
|
||||
|
||||
|
||||
class TradingValidationError(TradingError):
|
||||
"""Ошибка проверки входных данных Trading Layer."""
|
||||
|
||||
|
||||
class TradingExecutionError(TradingError):
|
||||
"""Ошибка выполнения Trading Layer."""
|
||||
67
app/src/trading/decision/models.py
Normal file
67
app/src/trading/decision/models.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# app/src/trading/decision/models.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from src.trading.market_intelligence.common.enums import EngineStatus
|
||||
from src.trading.market_intelligence.common.models import CoordinatorResult
|
||||
from src.trading.market_intelligence.common.reasons import ReasonCode
|
||||
from src.trading.market_intelligence.common.scores import (
|
||||
EngineConfidence,
|
||||
EngineScore,
|
||||
)
|
||||
from src.trading.market_intelligence.common.types import (
|
||||
ContextDict,
|
||||
DiagnosticMessages,
|
||||
DurationMs,
|
||||
PayloadDict,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TradingDiagnostics:
|
||||
reason: ReasonCode = ReasonCode.UNKNOWN
|
||||
details: ContextDict = field(default_factory=dict)
|
||||
warnings: DiagnosticMessages = field(default_factory=list)
|
||||
errors: DiagnosticMessages = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def has_warnings(self) -> bool:
|
||||
return bool(self.warnings)
|
||||
|
||||
@property
|
||||
def has_errors(self) -> bool:
|
||||
return bool(self.errors)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TradingEvaluationMeta:
|
||||
trading_version: str
|
||||
calculated_at: float | None = None
|
||||
duration_ms: DurationMs | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class TradingDecision:
|
||||
coordinator_result: CoordinatorResult
|
||||
diagnostics: TradingDiagnostics = field(default_factory=TradingDiagnostics)
|
||||
meta: TradingEvaluationMeta | None = None
|
||||
payload: PayloadDict = field(default_factory=dict)
|
||||
|
||||
status: EngineStatus = EngineStatus.UNKNOWN
|
||||
score: EngineScore = field(default_factory=EngineScore)
|
||||
confidence: EngineConfidence = field(default_factory=EngineConfidence)
|
||||
reason: ReasonCode = ReasonCode.UNKNOWN
|
||||
|
||||
@property
|
||||
def is_usable(self) -> bool:
|
||||
return self.status in {
|
||||
EngineStatus.OK,
|
||||
EngineStatus.PARTIAL,
|
||||
EngineStatus.STALE,
|
||||
}
|
||||
|
||||
@property
|
||||
def has_errors(self) -> bool:
|
||||
return self.diagnostics.has_errors
|
||||
19
app/src/trading/decision/protocol.py
Normal file
19
app/src/trading/decision/protocol.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# app/src/trading/decision/protocol.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
|
||||
from src.trading.market_intelligence.common.models import CoordinatorResult
|
||||
from src.trading.decision.models import TradingDecision
|
||||
|
||||
|
||||
class TradingProtocol(Protocol):
|
||||
"""Контракт Trading Layer."""
|
||||
|
||||
async def decide(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> TradingDecision:
|
||||
"""Принять торговое решение на основе CoordinatorResult."""
|
||||
...
|
||||
65
app/src/trading/decision/rules.py
Normal file
65
app/src/trading/decision/rules.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# app/src/trading/decision/rules.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.trading.market_intelligence.common.enums import EngineStatus
|
||||
from src.trading.market_intelligence.common.models import CoordinatorResult
|
||||
from src.trading.decision.models import (
|
||||
TradingDecision,
|
||||
TradingDiagnostics,
|
||||
TradingEvaluationMeta,
|
||||
)
|
||||
from src.trading.market_intelligence.common.reasons import ReasonCode
|
||||
from src.trading.market_intelligence.common.scores import (
|
||||
EngineConfidence,
|
||||
EngineScore,
|
||||
)
|
||||
|
||||
|
||||
class TradingRules:
|
||||
"""Правила формирования торгового решения."""
|
||||
|
||||
def decide(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> TradingDecision:
|
||||
"""Сформировать TradingDecision."""
|
||||
return TradingDecision(
|
||||
coordinator_result=coordinator_result,
|
||||
diagnostics=TradingDiagnostics(),
|
||||
meta=TradingEvaluationMeta(
|
||||
trading_version="1.0",
|
||||
),
|
||||
status=self._resolve_status(coordinator_result),
|
||||
score=self._resolve_score(coordinator_result),
|
||||
confidence=self._resolve_confidence(coordinator_result),
|
||||
reason=self._resolve_reason(coordinator_result),
|
||||
)
|
||||
|
||||
def _resolve_status(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> EngineStatus:
|
||||
"""Определить итоговый статус Trading."""
|
||||
return coordinator_result.status
|
||||
|
||||
def _resolve_score(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> EngineScore:
|
||||
"""Вычислить итоговый Score."""
|
||||
return EngineScore()
|
||||
|
||||
def _resolve_confidence(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> EngineConfidence:
|
||||
"""Вычислить итоговый Confidence."""
|
||||
return EngineConfidence()
|
||||
|
||||
def _resolve_reason(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> ReasonCode:
|
||||
"""Определить причину принятого решения."""
|
||||
return ReasonCode.UNKNOWN
|
||||
29
app/src/trading/decision/service.py
Normal file
29
app/src/trading/decision/service.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# app/src/trading/decision/service.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.trading.market_intelligence.common.models import CoordinatorResult
|
||||
from src.trading.decision.models import TradingDecision
|
||||
from src.trading.decision.protocol import TradingProtocol
|
||||
from src.trading.decision.rules import TradingRules
|
||||
from src.trading.decision.validation import (
|
||||
TradingValidation,
|
||||
)
|
||||
|
||||
|
||||
class TradingService(TradingProtocol):
|
||||
"""Единая публичная точка входа Trading Layer."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Создать Trading Service."""
|
||||
self._validation = TradingValidation()
|
||||
self._rules = TradingRules()
|
||||
|
||||
async def decide(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> TradingDecision:
|
||||
"""Сформировать торговое решение."""
|
||||
self._validation.validate(coordinator_result)
|
||||
|
||||
return self._rules.decide(coordinator_result)
|
||||
51
app/src/trading/decision/validation.py
Normal file
51
app/src/trading/decision/validation.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# app/src/trading/decision/validation.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from src.trading.market_intelligence.common.models import CoordinatorResult
|
||||
from src.trading.decision.exceptions import (
|
||||
TradingValidationError,
|
||||
)
|
||||
|
||||
|
||||
class TradingValidation:
|
||||
"""Проверка входного CoordinatorResult для Trading Layer."""
|
||||
|
||||
def validate(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> None:
|
||||
"""Проверить CoordinatorResult перед принятием торгового решения."""
|
||||
self._validate_result_exists(coordinator_result)
|
||||
self._validate_result_usable(coordinator_result)
|
||||
self._validate_result_has_no_errors(coordinator_result)
|
||||
|
||||
def _validate_result_exists(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> None:
|
||||
"""Проверить, что CoordinatorResult передан."""
|
||||
if coordinator_result is None:
|
||||
raise TradingValidationError(
|
||||
"CoordinatorResult is required for Trading."
|
||||
)
|
||||
|
||||
def _validate_result_usable(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> None:
|
||||
"""Проверить пригодность CoordinatorResult для принятия решения."""
|
||||
if not coordinator_result.is_usable:
|
||||
raise TradingValidationError(
|
||||
"CoordinatorResult is not usable."
|
||||
)
|
||||
|
||||
def _validate_result_has_no_errors(
|
||||
self,
|
||||
coordinator_result: CoordinatorResult,
|
||||
) -> None:
|
||||
"""Проверить отсутствие критических ошибок Coordinator."""
|
||||
if coordinator_result.has_errors:
|
||||
raise TradingValidationError(
|
||||
"CoordinatorResult contains errors."
|
||||
)
|
||||
Reference in New Issue
Block a user