Spaces:
Sleeping
Sleeping
File size: 6,232 Bytes
558db1e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | from pydantic import BaseModel, Field, model_validator
from typing import Dict, List, Any
from constants import DEFAULT_ADV
class ConfigError(Exception):
pass
class BenchmarksConfig(BaseModel):
equity: str = "SPY"
volatility: str = "^VIX"
risk_free: str = "^TNX"
def get(self, key: str, default: Any = None) -> Any:
return getattr(self, key, default)
class AppConfig(BaseModel):
risk_free_rate: float = Field(0.04, ge=0.0, le=0.20)
transaction_cost: float = Field(0.001, ge=0.0, le=0.05)
trading_days_per_year: int = Field(252, ge=100, le=365)
rolling_cov_days: int = 756
currency_symbol: str = "$"
default_adv_proxy: float = Field(DEFAULT_ADV, ge=0.0)
benchmarks: BenchmarksConfig = Field(default_factory=BenchmarksConfig)
single_asset_min: float = Field(-1.0, ge=-1.0, le=1.0)
single_asset_max: float = Field(0.40, ge=0.01, le=1.0)
sector_limit: float = Field(0.40, ge=0.01, le=1.0)
gross_leverage_cap: float = Field(2.0, ge=1.0, le=5.0)
short_borrow_cost: float = Field(0.015, ge=0.0, le=0.50)
max_turnover: float = Field(3.0, ge=0.0)
tax_rate_lt: float = Field(0.20, ge=0.0, le=1.0)
tax_rate_st: float = Field(0.35, ge=0.0, le=1.0)
lt_days: int = Field(366, ge=1)
hrp_tax_lambda: float = Field(2.5, ge=0.0)
cvar_alpha: float = Field(0.95, ge=0.50, le=0.999)
cvar_lambda: float = Field(0.5, ge=0.0, le=20.0)
baseline_risk_factor: float = Field(3.0, ge=0.1, le=25.0)
monte_carlo_sims: int = Field(1500, ge=100, le=100_000)
monte_carlo_years: float = Field(1.0, ge=0.1, le=50.0)
garch_enabled: bool = True
cvar_enabled: bool = True
tax_enabled: bool = False
dynamic_risk: bool = True
hmm_regime: bool = True
arima_enabled: bool = False
anova_enabled: bool = False
with_futures: bool = False
overlay_mode: str = "beta_hedge"
futures_universe: List[str] = Field(default_factory=lambda: ["MES", "ES"])
futures_safety_multiplier: float = Field(3.0, ge=0.0, le=10.0)
futures_target_beta: float = Field(0.0, ge=-5.0, le=5.0)
futures_margin_headroom: float = Field(0.05, ge=0.0, le=1.0)
return_frequency: str = "daily"
# End-to-End Differentiable Optimization (Model 6)
e2e_loss_type: str = "spo" # "spo", "sharpe", or "calmar"
e2e_epochs: int = Field(default=50)
e2e_batch_size: int = Field(default=32)
e2e_lr: float = 1e-3
e2e_cache_dir: str = ".e2e_cache"
universe_categories: Dict[str, List[str]] = Field(default_factory=lambda: {
"Core Equities": ["SPY", "QQQ", "DIA", "IWM"],
"Bonds & Rates": ["TLT", "IEF", "SHY", "AGG"],
"Tech & Growth": ["AAPL", "MSFT", "NVDA", "TSLA"],
"Defensive/Value": ["JNJ", "PG", "KO", "XLP"],
"Commodities": ["GLD", "SLV", "USO", "PDBC"],
"International": ["VEA", "VWO", "EFA", "EEM"],
"Crypto Proxies": ["IBIT", "FBTC", "ETHE", "MSTR"]
})
# βββββββββββββββββββββββββββββββββββββββββββββ
# EXTENDED HISTORY & BOOTSTRAPPING
# βββββββββββββββββββββββββββββββββββββββββββββ
extended_history: bool = False
bootstrap_samples: int = 100
stitch_overlap_days: int = 252
proxy_mappings: Dict[str, Dict] = Field(default_factory=lambda: {
'SPY': {'proxy': '^GSPC', 'proxy_start': '1950-01-03', 'overlap_days': 252},
'TLT': {'proxy': '^TYX', 'proxy_start': '1977-01-03', 'is_yield': True},
'GLD': {'proxy': 'GC=F', 'proxy_start': '1974-12-31'},
'QQQ': {'proxy': '^IXIC', 'proxy_start': '1971-02-05'}
})
bond_metadata: Dict[str, Any] = Field(default_factory=dict)
model_config = {"extra": "allow"}
@model_validator(mode='after')
def check_logic(self):
if self.single_asset_min > self.single_asset_max:
raise ConfigError("single_asset_min cannot be greater than single_asset_max.")
if self.sector_limit < self.single_asset_max:
raise ConfigError(f"sector_limit ({self.sector_limit}) cannot be smaller than single_asset_max ({self.single_asset_max}).")
if self.tax_rate_lt > self.tax_rate_st:
raise ConfigError("Long-term tax rate is mathematically expected to be <= short-term tax rate.")
return self
def get(self, key: str, default: Any = None) -> Any:
"""Backward compatibility for dict-like access."""
if hasattr(self, key):
return getattr(self, key)
if self.model_extra is not None and key in self.model_extra:
return self.model_extra[key]
return default
def setdefault(self, key: str, default: Any = None) -> Any:
val = self.get(key, None)
if val is None:
self[key] = default
return default
return val
def __getitem__(self, key: str) -> Any:
if hasattr(self, key):
return getattr(self, key)
if self.model_extra is not None and key in self.model_extra:
return self.model_extra[key]
raise KeyError(key)
def __setitem__(self, key: str, value: Any) -> None:
if hasattr(self, key) or key in self.model_fields:
setattr(self, key, value)
elif key.startswith('_'):
# Internal transient keys (e.g., _risk_input, _is_historical_backtest) are allowed silently
if self.model_extra is None:
self.__dict__['__pydantic_extra__'] = {}
self.model_extra[key] = value
else:
import warnings
warnings.warn(
f"AppConfig: setting unknown key '{key}'. Did you mean one of "
f"{sorted(list(self.model_fields.keys()))[:8]}...?",
stacklevel=2
)
if self.model_extra is None:
self.__dict__['__pydantic_extra__'] = {}
self.model_extra[key] = value
def update(self, other: dict) -> None:
for k, v in other.items():
setattr(self, k, v)
DEFAULT_CONFIG = AppConfig()
|