Files
momentum-system/core/contracts.py
T
momentum devandClaude Opus 4.8 007cd95c79 Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB
rebuild zip, the Windows .venv, and .env are excluded via .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:39:29 -04:00

260 lines
7.1 KiB
Python

"""Module contracts and interfaces for the momentum trading system.
Every module must implement the ModuleContract protocol, defining:
- input_schema: What the module expects
- output_schema: What the module produces
- run(): Execute the module
- validate_input(): Ensure data meets contracts
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime, date
from typing import Any, Protocol, TypeVar
T_Input = TypeVar("T_Input")
T_Output = TypeVar("T_Output")
@dataclass
class ModuleMetrics:
"""Standard metrics every module should track."""
module_name: str
timestamp: datetime
duration_ms: float
input_count: int = 0
output_count: int = 0
error_count: int = 0
warnings: list[str] = field(default_factory=list)
custom_metrics: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict:
return {
"module_name": self.module_name,
"timestamp": self.timestamp.isoformat(),
"duration_ms": self.duration_ms,
"input_count": self.input_count,
"output_count": self.output_count,
"error_count": self.error_count,
"warnings": self.warnings,
"custom_metrics": self.custom_metrics,
}
@dataclass
class ValidationFailure:
"""Single validation failure with context."""
field: str
value: Any
reason: str
severity: str = "error" # "error" or "warning"
@dataclass
class ModuleResult:
"""Base class for all module results."""
success: bool
timestamp: datetime
metrics: ModuleMetrics
validation_failures: list[ValidationFailure] = field(default_factory=list)
def has_errors(self) -> bool:
return bool(self.validation_failures or not self.success)
def error_summary(self) -> str:
"""Human-readable error summary."""
if not self.validation_failures:
return "No errors"
errors = [f for f in self.validation_failures if f.severity == "error"]
if not errors:
return f"{len(self.validation_failures)} warnings only"
return f"{len(errors)} error(s), {len([f for f in self.validation_failures if f.severity == 'warning'])} warning(s)"
class ModuleContract(Protocol):
"""Contract every module must fulfill."""
def run(self, input: Any) -> ModuleResult:
"""Execute the module.
Args:
input: Module-specific input conforming to input_schema
Returns:
Module-specific result inheriting from ModuleResult
"""
...
def validate_input(self, input: Any) -> tuple[bool, list[ValidationFailure]]:
"""Validate input before processing.
Args:
input: Data to validate
Returns:
(is_valid, failures)
"""
...
def get_metrics(self) -> ModuleMetrics:
"""Get last execution metrics."""
...
# ============================================================================
# INGESTION MODULE CONTRACTS
# ============================================================================
@dataclass
class BarIngestRequest:
"""Input contract for ingestion module."""
symbols: list[str]
session_date: date
lookback_minutes: int = 120 # how many minutes of history to fetch
source: str = "alpaca" # "alpaca", "test", "file"
@dataclass
class BarIngestResult(ModuleResult):
"""Output contract for ingestion module."""
minute_rows_inserted: int = 0
symbols_updated: list[str] = field(default_factory=list)
source_health: dict[str, str] = field(default_factory=dict) # {source: "healthy"|"error"}
# ============================================================================
# RESEARCH MODULE CONTRACTS
# ============================================================================
@dataclass
class ResearchRequest:
"""Input contract for research module."""
symbols: list[str]
session_date: date
lookback_days: int = 30 # for daily history
@dataclass
class ResearchResult(ModuleResult):
"""Output contract for research module."""
symbols_researched: list[str] = field(default_factory=list)
symbols_with_data: list[str] = field(default_factory=list)
symbols_missing_data: list[str] = field(default_factory=list)
total_bars_available: int = 0
# ============================================================================
# EVALUATION MODULE CONTRACTS
# ============================================================================
@dataclass
class CriterionDetail:
"""Result of evaluating one criterion."""
name: str
passed: bool
value: float # actual metric value
threshold: float | None = None # minimum to pass
reason: str = ""
confidence: float = 1.0 # 0-1, how confident in this result
@dataclass
class EvaluationRequest:
"""Input contract for evaluation module."""
symbol: str
bars_df: Any # pd.DataFrame with {timestamp, open, high, low, close, volume}
previous_close: float
avg_daily_volume: float
session_date: date
@dataclass
class EvaluationResult(ModuleResult):
"""Output contract for evaluation module."""
symbol: str = ""
status: str = "late" # "ready", "blocked", "late", "error"
score: float = 0.0 # 0-100
criteria: list[CriterionDetail] = field(default_factory=list)
entry_price: float | None = None
stop_loss: float | None = None
target_price: float | None = None
quality_score: float = 0.0 # 0-1
blocking_reason: str | None = None
metrics_dict: dict[str, float] = field(default_factory=dict) # gap%, rvol, etc.
# ============================================================================
# EXECUTION MODULE CONTRACTS
# ============================================================================
@dataclass
class ExecutionRequest:
"""Input contract for execution module."""
symbol: str
entry_price: float
stop_price: float
target_price: float
score: float
criteria_count: int
account_equity: float
current_positions: int
today_realized_pnl: float
@dataclass
class ExecutionResult(ModuleResult):
"""Output contract for execution module."""
symbol: str = ""
status: str = "" # "approved", "rejected", "pending_approval", "error"
reason: str = "" # why it was approved/rejected
order_id: str | None = None
shares: int | None = None
risk_dollars: float | None = None
risk_checks_applied: list[str] = field(default_factory=list)
# ============================================================================
# BROKER MODULE CONTRACTS
# ============================================================================
@dataclass
class BrokerRequest:
"""Input contract for broker module."""
symbol: str
order_id: str
entry_price: float
stop_price: float
target_price: float
shares: int
order_type: str = "limit" # "limit" or "market"
@dataclass
class BrokerResult(ModuleResult):
"""Output contract for broker module."""
symbol: str = ""
status: str = "" # "submitted", "rejected", "error"
broker_order_id: str | None = None
submitted_at: datetime | None = None