sigma-terminal 2.0.2__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sigma/__init__.py +182 -6
- sigma/__main__.py +2 -2
- sigma/analytics/__init__.py +636 -0
- sigma/app.py +801 -892
- sigma/backtest.py +372 -0
- sigma/charts.py +407 -0
- sigma/cli.py +465 -0
- sigma/comparison.py +611 -0
- sigma/config.py +366 -0
- sigma/core/__init__.py +4 -17
- sigma/core/engine.py +493 -0
- sigma/core/intent.py +595 -0
- sigma/core/models.py +516 -125
- sigma/data/__init__.py +681 -0
- sigma/data/models.py +130 -0
- sigma/llm.py +639 -0
- sigma/monitoring.py +666 -0
- sigma/portfolio.py +697 -0
- sigma/reporting.py +658 -0
- sigma/robustness.py +675 -0
- sigma/setup.py +374 -403
- sigma/strategy.py +753 -0
- sigma/tools/backtest.py +23 -5
- sigma/tools.py +617 -0
- sigma/visualization.py +766 -0
- sigma_terminal-3.3.0.dist-info/METADATA +583 -0
- sigma_terminal-3.3.0.dist-info/RECORD +30 -0
- sigma_terminal-3.3.0.dist-info/entry_points.txt +6 -0
- sigma_terminal-3.3.0.dist-info/licenses/LICENSE +25 -0
- sigma/core/agent.py +0 -205
- sigma/core/config.py +0 -119
- sigma/core/llm.py +0 -794
- sigma/tools/__init__.py +0 -5
- sigma/tools/charts.py +0 -400
- sigma/tools/financial.py +0 -1457
- sigma/ui/__init__.py +0 -1
- sigma_terminal-2.0.2.dist-info/METADATA +0 -222
- sigma_terminal-2.0.2.dist-info/RECORD +0 -19
- sigma_terminal-2.0.2.dist-info/entry_points.txt +0 -2
- sigma_terminal-2.0.2.dist-info/licenses/LICENSE +0 -42
- {sigma_terminal-2.0.2.dist-info → sigma_terminal-3.3.0.dist-info}/WHEEL +0 -0
sigma/core/models.py
CHANGED
|
@@ -1,153 +1,544 @@
|
|
|
1
|
-
"""Data models for Sigma."""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime
|
|
4
|
-
from enum import Enum
|
|
5
|
-
from typing import Any, Optional
|
|
1
|
+
"""Data models for Sigma Financial Intelligence Platform."""
|
|
6
2
|
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, date
|
|
5
|
+
from enum import Enum, auto
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
|
+
import json
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
USER = "user"
|
|
14
|
-
ASSISTANT = "assistant"
|
|
15
|
-
TOOL = "tool"
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# ENUMS
|
|
13
|
+
# ============================================================================
|
|
16
14
|
|
|
15
|
+
class AssetClass(str, Enum):
|
|
16
|
+
"""Asset class types."""
|
|
17
|
+
EQUITY = "equity"
|
|
18
|
+
ETF = "etf"
|
|
19
|
+
OPTION = "option"
|
|
20
|
+
FUTURE = "future"
|
|
21
|
+
FOREX = "forex"
|
|
22
|
+
CRYPTO = "crypto"
|
|
23
|
+
RATES = "rates"
|
|
24
|
+
COMMODITY = "commodity"
|
|
25
|
+
INDEX = "index"
|
|
26
|
+
FUND = "fund"
|
|
27
|
+
UNKNOWN = "unknown"
|
|
17
28
|
|
|
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
|
-
|
|
29
|
+
|
|
30
|
+
class TimeHorizon(str, Enum):
|
|
31
|
+
"""Investment time horizons."""
|
|
32
|
+
INTRADAY = "intraday"
|
|
33
|
+
DAILY = "daily"
|
|
34
|
+
WEEKLY = "weekly"
|
|
35
|
+
MONTHLY = "monthly"
|
|
36
|
+
QUARTERLY = "quarterly"
|
|
37
|
+
YEARLY = "yearly"
|
|
38
|
+
MULTI_YEAR = "multi_year"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RiskProfile(str, Enum):
|
|
42
|
+
"""Risk tolerance levels."""
|
|
43
|
+
CONSERVATIVE = "conservative"
|
|
44
|
+
MODERATE = "moderate"
|
|
45
|
+
AGGRESSIVE = "aggressive"
|
|
46
|
+
VERY_AGGRESSIVE = "very_aggressive"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DeliverableType(str, Enum):
|
|
50
|
+
"""Types of research deliverables."""
|
|
51
|
+
QUICK_ANSWER = "quick_answer"
|
|
52
|
+
ANALYSIS = "analysis"
|
|
53
|
+
COMPARISON = "comparison"
|
|
54
|
+
BACKTEST = "backtest"
|
|
55
|
+
PORTFOLIO = "portfolio"
|
|
56
|
+
STRATEGY = "strategy"
|
|
57
|
+
REPORT = "report"
|
|
58
|
+
ALERT = "alert"
|
|
59
|
+
CHART = "chart"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Regime(str, Enum):
|
|
63
|
+
"""Market regime types."""
|
|
64
|
+
BULL = "bull"
|
|
65
|
+
BEAR = "bear"
|
|
66
|
+
SIDEWAYS = "sideways"
|
|
67
|
+
HIGH_VOL = "high_volatility"
|
|
68
|
+
LOW_VOL = "low_volatility"
|
|
69
|
+
CRISIS = "crisis"
|
|
70
|
+
RECOVERY = "recovery"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SignalType(str, Enum):
|
|
74
|
+
"""Signal types for strategies."""
|
|
75
|
+
TREND = "trend"
|
|
76
|
+
MOMENTUM = "momentum"
|
|
77
|
+
MEAN_REVERSION = "mean_reversion"
|
|
78
|
+
CARRY = "carry"
|
|
79
|
+
VALUE = "value"
|
|
80
|
+
QUALITY = "quality"
|
|
81
|
+
LOW_VOL = "low_volatility"
|
|
82
|
+
SIZE = "size"
|
|
83
|
+
DEFENSIVE = "defensive"
|
|
84
|
+
GROWTH = "growth"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class DataSource(str, Enum):
|
|
88
|
+
"""Data source providers."""
|
|
89
|
+
YFINANCE = "yfinance"
|
|
90
|
+
ALPHA_VANTAGE = "alpha_vantage"
|
|
91
|
+
POLYGON = "polygon"
|
|
92
|
+
TIINGO = "tiingo"
|
|
93
|
+
FRED = "fred"
|
|
94
|
+
QUANDL = "quandl"
|
|
95
|
+
LEAN = "lean"
|
|
96
|
+
CUSTOM = "custom"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ============================================================================
|
|
100
|
+
# RESEARCH PLAN MODELS
|
|
101
|
+
# ============================================================================
|
|
102
|
+
|
|
103
|
+
class Constraint(BaseModel):
|
|
104
|
+
"""Investment constraint."""
|
|
66
105
|
name: str
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
106
|
+
type: str # max_weight, sector_cap, leverage_cap, turnover_cap, etc.
|
|
107
|
+
value: float
|
|
108
|
+
description: Optional[str] = None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ResearchPlan(BaseModel):
|
|
112
|
+
"""Structured research plan from user intent."""
|
|
113
|
+
goal: str = Field(..., description="Primary research goal")
|
|
114
|
+
assets: List[str] = Field(default_factory=list, description="Tickers/assets to analyze")
|
|
115
|
+
asset_classes: List[AssetClass] = Field(default_factory=list)
|
|
116
|
+
horizon: TimeHorizon = Field(default=TimeHorizon.DAILY)
|
|
117
|
+
benchmark: Optional[str] = Field(default="SPY", description="Benchmark for comparison")
|
|
118
|
+
risk_profile: RiskProfile = Field(default=RiskProfile.MODERATE)
|
|
119
|
+
constraints: List[Constraint] = Field(default_factory=list)
|
|
120
|
+
deliverable: DeliverableType = Field(default=DeliverableType.ANALYSIS)
|
|
121
|
+
|
|
122
|
+
# Account and tax settings
|
|
123
|
+
account_type: Optional[str] = Field(default=None) # taxable, ira, 401k
|
|
124
|
+
leverage_allowed: bool = Field(default=False)
|
|
125
|
+
max_leverage: float = Field(default=1.0)
|
|
126
|
+
tax_aware: bool = Field(default=False)
|
|
127
|
+
|
|
128
|
+
# Date ranges
|
|
129
|
+
start_date: Optional[date] = None
|
|
130
|
+
end_date: Optional[date] = None
|
|
131
|
+
lookback_period: Optional[str] = Field(default="2y")
|
|
132
|
+
|
|
133
|
+
# Additional context
|
|
134
|
+
context: Dict[str, Any] = Field(default_factory=dict)
|
|
135
|
+
clarifications_needed: List[str] = Field(default_factory=list)
|
|
136
|
+
|
|
137
|
+
class Config:
|
|
138
|
+
use_enum_values = True
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ============================================================================
|
|
142
|
+
# DATA MODELS
|
|
143
|
+
# ============================================================================
|
|
72
144
|
|
|
145
|
+
class DataQualityReport(BaseModel):
|
|
146
|
+
"""Data quality assessment."""
|
|
147
|
+
total_records: int
|
|
148
|
+
missing_count: int
|
|
149
|
+
missing_pct: float
|
|
150
|
+
stale_ticks: int
|
|
151
|
+
outliers_detected: int
|
|
152
|
+
timezone_issues: int
|
|
153
|
+
date_range: tuple
|
|
154
|
+
gaps: List[tuple]
|
|
155
|
+
warnings: List[str]
|
|
156
|
+
passed: bool
|
|
73
157
|
|
|
74
|
-
|
|
75
|
-
|
|
158
|
+
|
|
159
|
+
class DataLineage(BaseModel):
|
|
160
|
+
"""Track data provenance."""
|
|
161
|
+
source: DataSource
|
|
162
|
+
fetch_timestamp: datetime
|
|
163
|
+
symbols: List[str]
|
|
164
|
+
date_range: tuple
|
|
165
|
+
transformations: List[str] = Field(default_factory=list)
|
|
166
|
+
quality_report: Optional[DataQualityReport] = None
|
|
167
|
+
version: str = "1.0"
|
|
168
|
+
config: Dict[str, Any] = Field(default_factory=dict)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class CorporateAction(BaseModel):
|
|
172
|
+
"""Corporate action event."""
|
|
173
|
+
date: date
|
|
174
|
+
symbol: str
|
|
175
|
+
action_type: str # split, dividend, merger, symbol_change, spinoff
|
|
176
|
+
details: Dict[str, Any]
|
|
177
|
+
adjustment_factor: Optional[float] = None
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class PriceBar(BaseModel):
|
|
181
|
+
"""OHLCV price bar."""
|
|
182
|
+
timestamp: datetime
|
|
183
|
+
open: float
|
|
184
|
+
high: float
|
|
185
|
+
low: float
|
|
186
|
+
close: float
|
|
187
|
+
volume: float
|
|
188
|
+
adjusted_close: Optional[float] = None
|
|
189
|
+
vwap: Optional[float] = None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Fundamental(BaseModel):
|
|
193
|
+
"""Point-in-time fundamental data."""
|
|
194
|
+
as_of_date: date # When data was known (avoids lookahead)
|
|
195
|
+
report_date: date # When the report period ends
|
|
76
196
|
symbol: str
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
197
|
+
metrics: Dict[str, float] # revenue, earnings, etc.
|
|
198
|
+
source: str
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ============================================================================
|
|
202
|
+
# ANALYTICS MODELS
|
|
203
|
+
# ============================================================================
|
|
204
|
+
|
|
205
|
+
class PerformanceMetrics(BaseModel):
|
|
206
|
+
"""Comprehensive performance metrics."""
|
|
207
|
+
# Returns
|
|
208
|
+
total_return: float
|
|
209
|
+
cagr: float
|
|
210
|
+
mtd_return: Optional[float] = None
|
|
211
|
+
ytd_return: Optional[float] = None
|
|
212
|
+
|
|
213
|
+
# Risk
|
|
214
|
+
volatility: float
|
|
215
|
+
downside_deviation: float
|
|
216
|
+
max_drawdown: float
|
|
217
|
+
drawdown_duration_days: Optional[int] = None
|
|
218
|
+
var_95: Optional[float] = None
|
|
219
|
+
cvar_95: Optional[float] = None
|
|
220
|
+
|
|
221
|
+
# Risk-adjusted
|
|
222
|
+
sharpe_ratio: float
|
|
223
|
+
sortino_ratio: float
|
|
224
|
+
calmar_ratio: float
|
|
225
|
+
information_ratio: Optional[float] = None
|
|
226
|
+
treynor_ratio: Optional[float] = None
|
|
227
|
+
|
|
228
|
+
# Other
|
|
88
229
|
beta: Optional[float] = None
|
|
89
|
-
|
|
90
|
-
|
|
230
|
+
alpha: Optional[float] = None
|
|
231
|
+
r_squared: Optional[float] = None
|
|
232
|
+
tracking_error: Optional[float] = None
|
|
233
|
+
win_rate: Optional[float] = None
|
|
234
|
+
profit_factor: Optional[float] = None
|
|
91
235
|
|
|
92
236
|
|
|
93
|
-
class
|
|
94
|
-
"""
|
|
237
|
+
class FactorExposure(BaseModel):
|
|
238
|
+
"""Factor exposure analysis."""
|
|
95
239
|
symbol: str
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
240
|
+
as_of_date: date
|
|
241
|
+
market_beta: float
|
|
242
|
+
size_exposure: Optional[float] = None
|
|
243
|
+
value_exposure: Optional[float] = None
|
|
244
|
+
momentum_exposure: Optional[float] = None
|
|
245
|
+
quality_exposure: Optional[float] = None
|
|
246
|
+
low_vol_exposure: Optional[float] = None
|
|
247
|
+
growth_exposure: Optional[float] = None
|
|
248
|
+
r_squared: float
|
|
249
|
+
residual_vol: float
|
|
100
250
|
|
|
101
251
|
|
|
102
|
-
class
|
|
103
|
-
"""
|
|
252
|
+
class RegimeAnalysis(BaseModel):
|
|
253
|
+
"""Regime detection results."""
|
|
254
|
+
current_regime: Regime
|
|
255
|
+
regime_probability: float
|
|
256
|
+
regime_history: List[tuple] # (start_date, end_date, regime)
|
|
257
|
+
transition_matrix: Optional[Dict[str, Dict[str, float]]] = None
|
|
258
|
+
indicators: Dict[str, float]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class SeasonalityAnalysis(BaseModel):
|
|
262
|
+
"""Seasonality patterns."""
|
|
104
263
|
symbol: str
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
264
|
+
monthly_returns: Dict[int, float] # month -> avg return
|
|
265
|
+
day_of_week_returns: Dict[int, float] # day -> avg return
|
|
266
|
+
pre_earnings_drift: Optional[float] = None
|
|
267
|
+
post_earnings_drift: Optional[float] = None
|
|
268
|
+
strongest_month: int
|
|
269
|
+
weakest_month: int
|
|
270
|
+
statistical_significance: Dict[str, float]
|
|
111
271
|
|
|
112
272
|
|
|
113
|
-
class
|
|
114
|
-
"""
|
|
273
|
+
class EventStudy(BaseModel):
|
|
274
|
+
"""Event study results."""
|
|
275
|
+
event_type: str # earnings, fomc, cpi, etc.
|
|
115
276
|
symbol: str
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
277
|
+
events_analyzed: int
|
|
278
|
+
avg_move: float
|
|
279
|
+
std_move: float
|
|
280
|
+
positive_rate: float
|
|
281
|
+
pre_event_drift: float
|
|
282
|
+
post_event_drift: float
|
|
283
|
+
distribution: Dict[str, float] # percentiles
|
|
284
|
+
conditional_outcomes: Dict[str, float]
|
|
123
285
|
|
|
124
286
|
|
|
125
|
-
|
|
126
|
-
|
|
287
|
+
# ============================================================================
|
|
288
|
+
# STRATEGY MODELS
|
|
289
|
+
# ============================================================================
|
|
290
|
+
|
|
291
|
+
class TradingRule(BaseModel):
|
|
292
|
+
"""Explicit trading rule."""
|
|
293
|
+
name: str
|
|
294
|
+
entry_condition: str # Human-readable condition
|
|
295
|
+
exit_condition: str
|
|
296
|
+
position_size: str # Sizing rule description
|
|
297
|
+
stop_loss: Optional[str] = None
|
|
298
|
+
take_profit: Optional[str] = None
|
|
299
|
+
rebalance_frequency: str
|
|
300
|
+
assumptions: List[str] = Field(default_factory=list)
|
|
301
|
+
failure_modes: List[str] = Field(default_factory=list)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class StrategyHypothesis(BaseModel):
|
|
305
|
+
"""Trading strategy hypothesis."""
|
|
306
|
+
name: str
|
|
307
|
+
description: str
|
|
308
|
+
signal_type: SignalType
|
|
309
|
+
asset_classes: List[AssetClass]
|
|
310
|
+
timeframe: TimeHorizon
|
|
311
|
+
expected_sharpe: float
|
|
312
|
+
expected_turnover: float
|
|
313
|
+
rules: List[TradingRule]
|
|
314
|
+
data_requirements: List[str]
|
|
315
|
+
assumptions: List[str]
|
|
316
|
+
risks: List[str]
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class BacktestResult(BaseModel):
|
|
320
|
+
"""Comprehensive backtest results."""
|
|
321
|
+
strategy_name: str
|
|
127
322
|
symbol: str
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
323
|
+
period: str
|
|
324
|
+
|
|
325
|
+
# Performance
|
|
326
|
+
metrics: PerformanceMetrics
|
|
327
|
+
|
|
328
|
+
# Equity curve data
|
|
329
|
+
equity_curve: List[float]
|
|
330
|
+
drawdown_curve: List[float]
|
|
331
|
+
|
|
332
|
+
# Trade analysis
|
|
333
|
+
total_trades: int
|
|
334
|
+
winning_trades: int
|
|
335
|
+
losing_trades: int
|
|
336
|
+
avg_win: float
|
|
337
|
+
avg_loss: float
|
|
338
|
+
largest_win: float
|
|
339
|
+
largest_loss: float
|
|
340
|
+
avg_holding_period: float
|
|
341
|
+
|
|
342
|
+
# Factor analysis
|
|
343
|
+
factor_exposures: Optional[FactorExposure] = None
|
|
344
|
+
|
|
345
|
+
# Diagnostics
|
|
346
|
+
warnings: List[str] = Field(default_factory=list)
|
|
347
|
+
issues_detected: List[str] = Field(default_factory=list)
|
|
348
|
+
|
|
349
|
+
# Reproducibility
|
|
350
|
+
parameters: Dict[str, Any] = Field(default_factory=dict)
|
|
351
|
+
data_lineage: Optional[DataLineage] = None
|
|
133
352
|
|
|
134
353
|
|
|
135
|
-
class
|
|
136
|
-
"""
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
354
|
+
class ParameterSweepResult(BaseModel):
|
|
355
|
+
"""Parameter optimization results."""
|
|
356
|
+
strategy_name: str
|
|
357
|
+
best_params: Dict[str, Any]
|
|
358
|
+
best_metric: float
|
|
359
|
+
optimization_metric: str
|
|
360
|
+
all_results: List[Dict[str, Any]]
|
|
361
|
+
stability_score: float
|
|
362
|
+
overfitting_warning: bool
|
|
363
|
+
train_sharpe: float
|
|
364
|
+
test_sharpe: float
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# ============================================================================
|
|
368
|
+
# PORTFOLIO MODELS
|
|
369
|
+
# ============================================================================
|
|
370
|
+
|
|
371
|
+
class Position(BaseModel):
|
|
372
|
+
"""Portfolio position."""
|
|
373
|
+
symbol: str
|
|
374
|
+
shares: float
|
|
375
|
+
entry_price: float
|
|
376
|
+
current_price: float
|
|
377
|
+
market_value: float
|
|
378
|
+
weight: float
|
|
379
|
+
pnl: float
|
|
380
|
+
pnl_pct: float
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class PortfolioAllocation(BaseModel):
|
|
384
|
+
"""Portfolio allocation."""
|
|
385
|
+
positions: Dict[str, float] # symbol -> weight
|
|
386
|
+
method: str # mean_variance, risk_parity, min_variance, etc.
|
|
387
|
+
expected_return: float
|
|
388
|
+
expected_volatility: float
|
|
389
|
+
expected_sharpe: float
|
|
390
|
+
constraints_applied: List[str]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class RiskBudget(BaseModel):
|
|
394
|
+
"""Risk budget allocation."""
|
|
395
|
+
total_risk: float # Portfolio volatility target
|
|
396
|
+
position_risks: Dict[str, float] # symbol -> risk contribution
|
|
397
|
+
marginal_risks: Dict[str, float] # symbol -> marginal risk
|
|
398
|
+
diversification_ratio: float
|
|
399
|
+
|
|
143
400
|
|
|
401
|
+
class HedgeSuggestion(BaseModel):
|
|
402
|
+
"""Hedge recommendation."""
|
|
403
|
+
hedge_type: str # index, sector, duration, volatility
|
|
404
|
+
instrument: str
|
|
405
|
+
hedge_ratio: float
|
|
406
|
+
expected_beta_reduction: float
|
|
407
|
+
cost_estimate: float
|
|
408
|
+
rationale: str
|
|
144
409
|
|
|
145
|
-
|
|
146
|
-
|
|
410
|
+
|
|
411
|
+
# ============================================================================
|
|
412
|
+
# REPORTING MODELS
|
|
413
|
+
# ============================================================================
|
|
414
|
+
|
|
415
|
+
class ResearchMemo(BaseModel):
|
|
416
|
+
"""Research memo output."""
|
|
147
417
|
title: str
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
418
|
+
date: datetime
|
|
419
|
+
executive_summary: str
|
|
420
|
+
key_findings: List[str]
|
|
421
|
+
methodology: str
|
|
422
|
+
risks: List[str]
|
|
423
|
+
conclusion: str
|
|
424
|
+
what_would_change: List[str] # Factors that would change the conclusion
|
|
425
|
+
charts: List[str] # Paths to chart files
|
|
426
|
+
data_sources: List[DataLineage]
|
|
427
|
+
reproducibility_config: Dict[str, Any]
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
class Alert(BaseModel):
|
|
431
|
+
"""Monitoring alert."""
|
|
432
|
+
timestamp: datetime
|
|
433
|
+
alert_type: str # signal, regime_shift, volume, drawdown, drift
|
|
434
|
+
symbol: str
|
|
435
|
+
message: str
|
|
436
|
+
severity: str # info, warning, critical
|
|
437
|
+
current_value: float
|
|
438
|
+
threshold: float
|
|
439
|
+
action_suggested: Optional[str] = None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# ============================================================================
|
|
443
|
+
# COMPARISON MODELS
|
|
444
|
+
# ============================================================================
|
|
445
|
+
|
|
446
|
+
class ComparisonResult(BaseModel):
|
|
447
|
+
"""Multi-asset comparison result."""
|
|
448
|
+
assets: List[str]
|
|
449
|
+
period: str
|
|
450
|
+
|
|
451
|
+
# Performance comparison
|
|
452
|
+
performance_metrics: Dict[str, PerformanceMetrics]
|
|
453
|
+
|
|
454
|
+
# Risk comparison
|
|
455
|
+
correlation_matrix: Dict[str, Dict[str, float]]
|
|
456
|
+
beta_to_benchmark: Dict[str, float]
|
|
457
|
+
|
|
458
|
+
# Behavioral comparison
|
|
459
|
+
momentum_scores: Dict[str, float]
|
|
460
|
+
mean_reversion_scores: Dict[str, float]
|
|
461
|
+
regime_sensitivity: Dict[str, Dict[str, float]]
|
|
462
|
+
|
|
463
|
+
# Fundamental comparison (if applicable)
|
|
464
|
+
fundamental_comparison: Optional[Dict[str, Dict[str, float]]] = None
|
|
465
|
+
|
|
466
|
+
# Ranking
|
|
467
|
+
overall_ranking: List[str]
|
|
468
|
+
ranking_rationale: str
|
|
469
|
+
tradeoffs: List[str]
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# ============================================================================
|
|
473
|
+
# UTILITY FUNCTIONS
|
|
474
|
+
# ============================================================================
|
|
475
|
+
|
|
476
|
+
def detect_asset_class(symbol: str) -> AssetClass:
|
|
477
|
+
"""Auto-detect asset class from symbol."""
|
|
478
|
+
symbol = symbol.upper()
|
|
479
|
+
|
|
480
|
+
# Crypto patterns
|
|
481
|
+
if symbol.endswith(("USD", "USDT", "BTC", "ETH")) or symbol in ["BTC", "ETH", "SOL", "DOGE"]:
|
|
482
|
+
return AssetClass.CRYPTO
|
|
483
|
+
|
|
484
|
+
# Forex patterns
|
|
485
|
+
if len(symbol) == 6 and symbol[:3] in ["USD", "EUR", "GBP", "JPY", "CHF", "AUD", "CAD", "NZD"]:
|
|
486
|
+
return AssetClass.FOREX
|
|
487
|
+
|
|
488
|
+
# Futures patterns
|
|
489
|
+
if symbol.startswith(("/", "@")) or symbol.endswith(("F", "Z", "H", "M", "U")) and len(symbol) <= 5:
|
|
490
|
+
return AssetClass.FUTURE
|
|
491
|
+
|
|
492
|
+
# Options (simplified detection)
|
|
493
|
+
if len(symbol) > 10 and any(c.isdigit() for c in symbol):
|
|
494
|
+
return AssetClass.OPTION
|
|
495
|
+
|
|
496
|
+
# Index patterns
|
|
497
|
+
if symbol.startswith("^") or symbol in ["SPX", "NDX", "DJI", "VIX", "RUT"]:
|
|
498
|
+
return AssetClass.INDEX
|
|
499
|
+
|
|
500
|
+
# Common ETFs
|
|
501
|
+
etf_patterns = ["SPY", "QQQ", "IWM", "DIA", "VTI", "VOO", "XL", "IY", "VB", "VG"]
|
|
502
|
+
if any(symbol.startswith(p) for p in etf_patterns):
|
|
503
|
+
return AssetClass.ETF
|
|
504
|
+
|
|
505
|
+
# Default to equity
|
|
506
|
+
return AssetClass.EQUITY
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def parse_time_period(period: str) -> tuple:
|
|
510
|
+
"""Parse period string to start/end dates."""
|
|
511
|
+
from datetime import timedelta
|
|
512
|
+
|
|
513
|
+
today = date.today()
|
|
514
|
+
period = period.lower().strip()
|
|
515
|
+
|
|
516
|
+
period_map = {
|
|
517
|
+
"1d": timedelta(days=1),
|
|
518
|
+
"5d": timedelta(days=5),
|
|
519
|
+
"1w": timedelta(weeks=1),
|
|
520
|
+
"1mo": timedelta(days=30),
|
|
521
|
+
"3mo": timedelta(days=90),
|
|
522
|
+
"6mo": timedelta(days=180),
|
|
523
|
+
"1y": timedelta(days=365),
|
|
524
|
+
"2y": timedelta(days=730),
|
|
525
|
+
"5y": timedelta(days=1825),
|
|
526
|
+
"10y": timedelta(days=3650),
|
|
527
|
+
"ytd": None, # Special case
|
|
528
|
+
"mtd": None, # Special case
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if period == "ytd":
|
|
532
|
+
return date(today.year, 1, 1), today
|
|
533
|
+
elif period == "mtd":
|
|
534
|
+
return date(today.year, today.month, 1), today
|
|
535
|
+
elif period in period_map:
|
|
536
|
+
return today - period_map[period], today
|
|
537
|
+
else:
|
|
538
|
+
# Try parsing as date range
|
|
539
|
+
if " to " in period:
|
|
540
|
+
parts = period.split(" to ")
|
|
541
|
+
return date.fromisoformat(parts[0]), date.fromisoformat(parts[1])
|
|
542
|
+
|
|
543
|
+
# Default to 2 years
|
|
544
|
+
return today - timedelta(days=730), today
|