Download config.py from 3VVM/Lk: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/spaces/3VVM/Lk/resolve/main/config.py
- Command line
-
hf download hf://spaces/3VVM/Lk/config.py
-
curl -L -o config.py https://huggingface.co/spaces/3VVM/Lk/resolve/main/config.py
10.3 kB
| """ | |
| Central configuration for the Moirai-MoE / AutoARIMA / Chronos-2 | |
| Forex/Crypto forecasting system. All thresholds, feature schema, and | |
| resource limits live here so they are auditable and change in one | |
| place (spec sections 19-20, 30, 42, 85, 111). | |
| """ | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| # --- Predictive models --------------------------------------------------- | |
| # Moirai-MoE was the sole model through Aug 2026. AutoARIMA and | |
| # Chronos-2 were added alongside it as fully independent, selectable | |
| # models (app.py's model picker) -- never blended, never a fallback for | |
| # one another (see forecast_types.py's docstring for what "independent" | |
| # means structurally). MODEL_CHECKPOINT is kept as a standalone constant | |
| # (rather than folded into the registry below) because moirai_model.py | |
| # and existing tests already reference it directly. | |
| MODEL_CHECKPOINT = "Salesforce/moirai-moe-1.0-R-small" | |
| # key -> (display name, module.ClassName as a string for lazy import, | |
| # real pretrained checkpoint or None for a classical/no-checkpoint model) | |
| AVAILABLE_MODELS = { | |
| "moirai": {"display_name": "Moirai-MoE-1.0-R-Small", "checkpoint": MODEL_CHECKPOINT}, | |
| "autoarima": {"display_name": "AutoARIMA", "checkpoint": None}, | |
| "chronos2": {"display_name": "Chronos-2", "checkpoint": "amazon/chronos-2"}, | |
| } | |
| DEFAULT_MODEL_KEY = "moirai" | |
| # --- Timeframes --------------------------------------------------------- | |
| # native = directly retrievable from yfinance | |
| # derived = built via causal resampling from a finer native timeframe (section 10-11) | |
| NATIVE_INTRADAY = ["1m", "2m", "5m", "15m", "30m", "1h"] | |
| NATIVE_OTHER = ["1d", "1wk", "1mo"] | |
| DERIVED_MAP = { | |
| # target_tf: (source_tf, bars_per_bin) | |
| "10m": ("5m", 2), | |
| "2h": ("1h", 2), | |
| "4h": ("1h", 4), | |
| } | |
| ALL_TIMEFRAMES = NATIVE_INTRADAY + list(DERIVED_MAP.keys()) + NATIVE_OTHER | |
| TIMEFRAME_MINUTES = { | |
| "1m": 1, "2m": 2, "5m": 5, "10m": 10, "15m": 15, "30m": 30, | |
| "1h": 60, "2h": 120, "4h": 240, "1d": 1440, "1wk": 10080, "1mo": 43200, | |
| } | |
| # Approximate Yahoo intraday lookback ceilings in days (source-enforced, | |
| # see spec section 8; yfinance will simply return less data than | |
| # requested rather than erroring, so we surface this explicitly) | |
| YAHOO_INTRADAY_MAX_DAYS = { | |
| "1m": 7, "2m": 60, "5m": 60, "15m": 60, "30m": 60, "1h": 730, | |
| } | |
| # Pandas frequency alias for each timeframe. This is passed EXPLICITLY to | |
| # gluonts' PandasDataset(freq=...) in moirai_model.py rather than relying | |
| # on pd.infer_freq() on the raw timestamps -- real Yahoo data has gaps | |
| # (weekends, exchange closures, occasional missing bars) that break | |
| # automatic frequency inference deterministically, on every call, which | |
| # is exactly the "Model errors: 11200/11200" failure mode this fixes. | |
| TIMEFRAME_TO_PANDAS_FREQ = { | |
| "1m": "1min", "2m": "2min", "5m": "5min", "10m": "10min", | |
| "15m": "15min", "30m": "30min", | |
| "1h": "1h", "2h": "2h", "4h": "4h", | |
| "1d": "1D", "1wk": "1W", "1mo": "1MS", | |
| } | |
| # --- History window choices (UI-facing) -------------------------------- | |
| # Fine-grained day counts plus month counts, so the person can pick from | |
| # a dropdown instead of typing a period string. data_layer.resolve_history_window() | |
| # turns a label from this list into an actual start date (or "max"). | |
| HISTORY_WINDOW_CHOICES = ( | |
| [f"{d} day" if d == 1 else f"{d} days" for d in range(1, 31)] | |
| + [f"{m} months" for m in range(2, 13)] | |
| + ["1 year", "2 years", "5 years", "max"] | |
| ) | |
| # --- Symbol choices (UI-facing) ----------------------------------------- | |
| # A curated, non-exhaustive list of common Yahoo Finance Forex/Crypto | |
| # tickers for convenient selection (spec section 12 wants search/ | |
| # autocomplete "when feasible" -- yfinance itself exposes no symbol | |
| # directory, so this is that best-effort list). The UI Dropdown also | |
| # allows a custom value, so any Yahoo-supported symbol still works even | |
| # if it isn't in this list -- this list is a convenience, not a limit. | |
| FOREX_SYMBOLS = [ | |
| "EURUSD=X", "GBPUSD=X", "USDJPY=X", "USDCHF=X", "AUDUSD=X", | |
| "USDCAD=X", "NZDUSD=X", "EURGBP=X", "EURJPY=X", "GBPJPY=X", | |
| "EURCHF=X", "AUDJPY=X", "EURAUD=X", "CHFJPY=X", "GBPCHF=X", | |
| "USDCNY=X", "USDINR=X", "USDMXN=X", "USDZAR=X", "USDTRY=X", | |
| "USDSEK=X", "USDNOK=X", "USDSGD=X", "USDHKD=X", | |
| ] | |
| CRYPTO_SYMBOLS = [ | |
| "BTC-USD", "ETH-USD", "BNB-USD", "XRP-USD", "SOL-USD", | |
| "ADA-USD", "DOGE-USD", "AVAX-USD", "DOT-USD", "LTC-USD", | |
| "LINK-USD", "MATIC-USD", "TRX-USD", "SHIB-USD", "BCH-USD", | |
| "XLM-USD", "ATOM-USD", "UNI-USD", "ETC-USD", "XMR-USD", | |
| ] | |
| ALL_SYMBOLS = FOREX_SYMBOLS + CRYPTO_SYMBOLS | |
| class IndicatorConfig: | |
| rsi_period: int = 14 | |
| macd_fast: int = 12 | |
| macd_slow: int = 26 | |
| macd_signal: int = 9 | |
| bb_period: int = 20 | |
| bb_std: float = 2.0 | |
| sma_periods: tuple = (20, 50) | |
| ema_periods: tuple = (12, 26) | |
| stoch_k: int = 14 | |
| stoch_d: int = 3 | |
| atr_period: int = 14 | |
| adx_period: int = 14 | |
| roc_period: int = 12 | |
| # Deterministic feature schema — order matters and is persisted with every | |
| # forecast/model artifact (spec section 85). Never silently reordered. | |
| PAST_DYNAMIC_FEATURES = [ | |
| "open", "high", "low", "volume", | |
| "rsi", | |
| "macd", "macd_signal", "macd_hist", | |
| "bb_mid", "bb_upper", "bb_lower", | |
| "sma_20", "sma_50", | |
| "ema_12", "ema_26", | |
| "stoch_k", "stoch_d", | |
| "atr", "adx", "roc", "obv", | |
| ] | |
| TARGET_FEATURE = "close" | |
| class DecisionConfig: | |
| cost_bps: float = 5.0 # default round-trip cost estimate, basis points | |
| buy_threshold: float = 0.0015 # expected edge required to BUY, post-cost | |
| sell_threshold: float = -0.0015 | |
| min_probability: float = 0.55 # P(return favorable) required to act | |
| max_interval_width_pct: float = 0.05 # HOLD if forecast too uncertain (P90-P10) | |
| class BacktestCostProfile: | |
| spread_bps: float | |
| commission_bps: float | |
| slippage_bps: float | |
| execution_delay_bars: int = 1 | |
| class RiskManagement: | |
| """Optional intra-trade exit rules, checked against intrabar high/low | |
| between entry and the horizon exit (see backtest_engine.py). Added | |
| Aug 2026 after real backtest data showed the previously-only exit | |
| rule -- always hold the full fixed horizon, no matter what happens | |
| in between -- was exactly why losing trades ran larger than winning | |
| ones: a trade that turns adverse right after entry still rides out | |
| the entire horizon before closing, with no way to cut it short. | |
| None (the default for both fields) means "no early exit at all", | |
| which reproduces every prior backtest's numbers exactly -- this is | |
| opt-in, not a change to existing behavior.""" | |
| stop_loss_frac: Optional[float] = None # e.g. 0.003 = exit if price moves 0.3% against the position | |
| take_profit_frac: Optional[float] = None # e.g. 0.006 = exit if price moves 0.6% in favor of the position | |
| class WalkForwardConfig: | |
| """Controls for walk_forward.py's train/validation/test split and | |
| the (deliberately small, bounded) parameter search -- spec: "do not | |
| search thousands of combinations", "avoid parameter values that | |
| produce only a handful of trades". Every field here is a knob a | |
| person can see and change, not a hidden magic number (section 25).""" | |
| train_frac: float = 0.5 | |
| val_frac: float = 0.25 | |
| # remaining ~0.25 is test, computed as 1 - train_frac - val_frac | |
| min_probability_grid: tuple = (0.55, 0.60, 0.65, 0.70) | |
| buy_threshold_grid: tuple = (0.0010, 0.0015, 0.0020, 0.0030) | |
| min_trades_for_acceptance: int = 30 # item 13: reject configs "profitable" on a handful of trades | |
| min_trades_for_calibration: int = 50 # separate, usually-larger floor for calibration itself (needs more pairs than a single accept/reject check) | |
| max_single_period_profit_share: float = 0.70 # item 18: reject if one sub-period explains >70% of total profit | |
| # Forex and crypto have different microstructure -> different cost profiles | |
| # (spec section 30). These are reasonable defaults, not measured values — | |
| # override them per-symbol if you have real spread/fee data. | |
| COST_PROFILES = { | |
| "forex": BacktestCostProfile(spread_bps=1.5, commission_bps=0.0, slippage_bps=0.5), | |
| "crypto": BacktestCostProfile(spread_bps=2.0, commission_bps=10.0, slippage_bps=2.0), | |
| } | |
| def round_trip_cost_frac(profile: BacktestCostProfile) -> float: | |
| """Round-trip cost (spread+commission+slippage) as a return fraction. | |
| Lives here (not in backtest_engine.py) because it's a pure function of | |
| a COST_PROFILES entry, and both the backtest engine and the Live tab's | |
| decide() call need the same, real, asset-class-specific number rather | |
| than each guessing independently (spec section 30).""" | |
| return (profile.spread_bps + profile.commission_bps + profile.slippage_bps) / 10_000 | |
| def infer_asset_class(symbol: str) -> str: | |
| """Best-effort classification of a Yahoo ticker into a COST_PROFILES | |
| key. The Live tab has no explicit asset-class selector (unlike the | |
| Backtest tab's `b_class` radio), so without this it silently fell back | |
| to a flat, asset-unaware cost estimate for every symbol -- wrong for | |
| both forex (too high) and crypto (too low). Checks the curated lists | |
| first, then falls back to Yahoo's own ticker conventions ('=X' suffix | |
| for forex pairs, a '-USD'/'-USDT'/etc. suffix for crypto pairs) for any | |
| other symbol typed via allow_custom_value, since those conventions are | |
| Yahoo's, not a guess of ours. Defaults to 'forex' (the cheaper, more | |
| conservative profile) only if nothing matches.""" | |
| if symbol in FOREX_SYMBOLS: | |
| return "forex" | |
| if symbol in CRYPTO_SYMBOLS: | |
| return "crypto" | |
| s = symbol.strip().upper() | |
| if s.endswith("=X"): | |
| return "forex" | |
| quote = s.split("-")[-1] if "-" in s else "" | |
| if quote in ("USD", "USDT", "USDC", "BTC", "ETH"): | |
| return "crypto" | |
| return "forex" | |
| # Context length ceiling — validate against the ACTUAL loaded checkpoint's | |
| # max_seq_len at runtime (moirai_model.py does this); this is a | |
| # conservative fallback default, not an assumption of unlimited context | |
| # (spec section 86). | |
| MODEL_MAX_CONTEXT_DEFAULT = 512 | |
| RESOURCE_LIMITS = dict(cpu_cores=2, ram_gb=16, disk_gb=50, max_forecast_samples=100, max_workers=2) | |