Download contracts.py from raghava4u/Trading-Bot-M20: direct link, hf CLI and curl.
- Browser
- Download file 5.15 kB
-
https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/contracts.py
- Command line
-
hf download hf://raghava4u/Trading-Bot-M20/contracts.py
-
curl -L -o contracts.py https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/contracts.py
5.15 kB
| """ | |
| contracts.py β Cross-module typed interfaces. | |
| Every inter-module call MUST use these types. No raw dicts between modules. | |
| """ | |
| from __future__ import annotations | |
| import datetime | |
| from typing import Literal | |
| import pandas as pd | |
| from pydantic import BaseModel, Field, field_validator | |
| # ββ Exceptions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class InsufficientDataError(Exception): | |
| """Raised when fewer than n_bars are available for a symbol/timeframe.""" | |
| class ConfigError(Exception): | |
| """Raised when configuration validation fails.""" | |
| class BrokerError(Exception): | |
| """Raised on unrecoverable broker API errors.""" | |
| class SafeModeError(Exception): | |
| """Raised when an operation is attempted while SAFE MODE is active.""" | |
| # ββ Signal Contracts βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class TechnicalScore(BaseModel): | |
| """Output of signals/technical.py for a single symbol at a point in time.""" | |
| model_config = {"arbitrary_types_allowed": True} | |
| symbol: str | |
| timestamp: datetime.datetime | |
| n_signals_computed: int = 0 | |
| score: float = Field(ge=-1.0, le=1.0) | |
| sub_signals: dict[str, float] = Field(default_factory=dict) | |
| skip_reason: str | None = None | |
| def force_zero_if_insufficient(cls, v: float, info) -> float: | |
| n = info.data.get("n_signals_computed", 0) | |
| if n < 3: | |
| return 0.0 | |
| return v | |
| class SentimentScore(BaseModel): | |
| """Output of signals/sentiment.py for a single symbol.""" | |
| symbol: str | |
| score: float = Field(ge=-1.0, le=1.0) | |
| cached_at: datetime.datetime | |
| source_count: int = 0 | |
| stale: bool = False | |
| def compute_stale(cls, v: bool, info) -> bool: | |
| cached = info.data.get("cached_at") | |
| if cached is None: | |
| return True | |
| now = datetime.datetime.now(datetime.timezone.utc) | |
| if cached.tzinfo is None: | |
| cached = cached.replace(tzinfo=datetime.timezone.utc) | |
| delta = (now - cached).total_seconds() | |
| return delta > 35 * 60 # stale if > 35 minutes | |
| class FinalScore(BaseModel): | |
| """Combined signal output from signals/combiner.py.""" | |
| symbol: str | |
| timestamp: datetime.datetime | |
| technical: float | |
| sentiment: float | |
| final: float | |
| decision: Literal["BUY", "SELL", "HOLD"] | |
| skip_reason: str | None = None | |
| class PriceTarget(BaseModel): | |
| """Predicted price targets for dashboard display.""" | |
| symbol: str | |
| current_price: float | |
| predicted_direction: Literal["UP", "DOWN", "NEUTRAL"] | |
| entry_price: float | None = None | |
| stop_loss_price: float | None = None | |
| take_profit_price: float | None = None | |
| expected_profit_usd: float = 0.0 | |
| confidence: float = Field(ge=0.0, le=1.0, default=0.0) | |
| # ββ Order Contracts ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class OrderRequest(BaseModel): | |
| """Internal contract for placing orders.""" | |
| symbol: str | |
| side: Literal["buy", "sell"] | |
| qty: float | |
| limit_price: float | |
| stop_price: float | None = None | |
| tp_price: float | None = None | |
| trail_percent: float | None = None | |
| is_fractional: bool = False | |
| dry_run: bool = False | |
| class LinkedOrderGroup(BaseModel): | |
| """Tracks entry + stop + TP legs for fractional orders.""" | |
| symbol: str | |
| entry_order_id: str | |
| stop_order_id: str | None = None | |
| tp_order_id: str | None = None | |
| entry_filled: bool = False | |
| stop_submitted: bool = False | |
| tp_submitted: bool = False | |
| is_fractional: bool = False | |
| entry_price: float | None = None | |
| orphaned: bool = False | |
| # ββ Data Contract ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # get_bars(symbol, timeframe, n_bars, adjusted=True) β pd.DataFrame | |
| # Columns: timestamp (UTC index), open, high, low, close, volume, vwap, adjusted | |
| # Raises InsufficientDataError if fewer than n_bars available. | |
| BAR_COLUMNS = ["open", "high", "low", "close", "volume", "vwap", "adjusted"] | |
| def validate_bar_dataframe(df: pd.DataFrame, symbol: str, n_bars: int) -> pd.DataFrame: | |
| """Validate that a bars DataFrame matches the contract.""" | |
| if len(df) < n_bars: | |
| raise InsufficientDataError( | |
| f"{symbol}: requested {n_bars} bars, got {len(df)}" | |
| ) | |
| missing = [c for c in BAR_COLUMNS if c not in df.columns] | |
| if missing: | |
| raise ValueError(f"{symbol}: missing columns {missing}") | |
| return df | |