mcli-framework 7.6.0__py3-none-any.whl → 7.6.2__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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/commands_cmd.py +51 -39
- mcli/app/main.py +10 -2
- mcli/app/model_cmd.py +1 -1
- mcli/lib/custom_commands.py +4 -10
- mcli/ml/api/app.py +1 -5
- mcli/ml/dashboard/app.py +2 -2
- mcli/ml/dashboard/app_integrated.py +168 -116
- mcli/ml/dashboard/app_supabase.py +7 -3
- mcli/ml/dashboard/app_training.py +3 -6
- mcli/ml/dashboard/components/charts.py +74 -115
- mcli/ml/dashboard/components/metrics.py +24 -44
- mcli/ml/dashboard/components/tables.py +32 -40
- mcli/ml/dashboard/overview.py +102 -78
- mcli/ml/dashboard/pages/cicd.py +103 -56
- mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
- mcli/ml/dashboard/pages/gravity_viz.py +374 -313
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
- mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
- mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
- mcli/ml/dashboard/pages/test_portfolio.py +153 -121
- mcli/ml/dashboard/pages/trading.py +238 -169
- mcli/ml/dashboard/pages/workflows.py +129 -84
- mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
- mcli/ml/dashboard/utils.py +24 -21
- mcli/ml/dashboard/warning_suppression.py +6 -4
- mcli/ml/database/session.py +16 -5
- mcli/ml/mlops/pipeline_orchestrator.py +1 -3
- mcli/ml/predictions/monte_carlo.py +6 -18
- mcli/ml/trading/alpaca_client.py +95 -96
- mcli/ml/trading/migrations.py +76 -40
- mcli/ml/trading/models.py +78 -60
- mcli/ml/trading/paper_trading.py +92 -74
- mcli/ml/trading/risk_management.py +106 -85
- mcli/ml/trading/trading_service.py +155 -110
- mcli/ml/training/train_model.py +1 -3
- mcli/self/self_cmd.py +71 -57
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +6 -2
- mcli/workflow/politician_trading/models.py +6 -2
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
- mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
- mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
- mcli/workflow/politician_trading/seed_database.py +70 -89
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/METADATA +1 -1
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/RECORD +49 -49
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/WHEEL +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.2.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,7 @@ import numpy as np
|
|
|
9
9
|
import pandas as pd
|
|
10
10
|
from sqlalchemy.orm import Session
|
|
11
11
|
|
|
12
|
-
from mcli.ml.trading.models import Portfolio, Position,
|
|
12
|
+
from mcli.ml.trading.models import Portfolio, Position, RiskLevel, TradingOrder
|
|
13
13
|
from mcli.ml.trading.trading_service import TradingService
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
@@ -17,20 +17,20 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
|
|
18
18
|
class RiskManager:
|
|
19
19
|
"""Risk management system for trading portfolios"""
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
def __init__(self, trading_service: TradingService):
|
|
22
22
|
self.trading_service = trading_service
|
|
23
23
|
self.db = trading_service.db
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
def calculate_portfolio_risk(self, portfolio_id: UUID) -> Dict:
|
|
26
26
|
"""Calculate comprehensive risk metrics for a portfolio"""
|
|
27
27
|
try:
|
|
28
28
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
29
29
|
if not portfolio:
|
|
30
30
|
return {}
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
positions = self.trading_service.get_portfolio_positions(portfolio_id)
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
if not positions:
|
|
35
35
|
return {
|
|
36
36
|
"total_risk": 0.0,
|
|
@@ -41,39 +41,39 @@ class RiskManager:
|
|
|
41
41
|
"portfolio_beta": 1.0,
|
|
42
42
|
"risk_score": 0.0,
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
# Calculate individual position risks
|
|
46
46
|
position_risks = []
|
|
47
47
|
total_market_value = sum(pos.market_value for pos in positions)
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
for position in positions:
|
|
50
50
|
position_risk = self._calculate_position_risk(position, total_market_value)
|
|
51
51
|
position_risks.append(position_risk)
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
# Calculate portfolio-level risk metrics
|
|
54
54
|
portfolio_risk = self._calculate_portfolio_risk_metrics(positions, position_risks)
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
return portfolio_risk
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
except Exception as e:
|
|
59
59
|
logger.error(f"Failed to calculate portfolio risk: {e}")
|
|
60
60
|
return {}
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
def _calculate_position_risk(self, position: Position, total_market_value: float) -> Dict:
|
|
63
63
|
"""Calculate risk metrics for an individual position"""
|
|
64
64
|
try:
|
|
65
65
|
# Position size as percentage of portfolio
|
|
66
66
|
position_size_pct = (position.market_value / total_market_value) * 100
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
# Volatility estimate (simplified - in practice, use historical data)
|
|
69
69
|
volatility = self._estimate_volatility(position.symbol)
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# Value at Risk (simplified calculation)
|
|
72
72
|
var_95 = position.market_value * volatility * 1.645 # 95% VaR
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
# Position risk score (combination of size and volatility)
|
|
75
75
|
risk_score = position_size_pct * volatility * 100
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
return {
|
|
78
78
|
"symbol": position.symbol,
|
|
79
79
|
"position_size_pct": position_size_pct,
|
|
@@ -82,26 +82,26 @@ class RiskManager:
|
|
|
82
82
|
"risk_score": risk_score,
|
|
83
83
|
"market_value": position.market_value,
|
|
84
84
|
}
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
except Exception as e:
|
|
87
87
|
logger.error(f"Failed to calculate position risk for {position.symbol}: {e}")
|
|
88
88
|
return {}
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
def _estimate_volatility(self, symbol: str, days: int = 30) -> float:
|
|
91
91
|
"""Estimate volatility for a symbol (simplified)"""
|
|
92
92
|
try:
|
|
93
93
|
# In practice, you would calculate historical volatility
|
|
94
94
|
# For now, use a simplified approach based on market cap and sector
|
|
95
95
|
import yfinance as yf
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
ticker = yf.Ticker(symbol)
|
|
98
98
|
data = ticker.history(period=f"{days}d")
|
|
99
|
-
|
|
99
|
+
|
|
100
100
|
if not data.empty and len(data) > 1:
|
|
101
101
|
returns = data["Close"].pct_change().dropna()
|
|
102
102
|
volatility = returns.std() * np.sqrt(252) # Annualized
|
|
103
103
|
return min(volatility, 1.0) # Cap at 100%
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
# Default volatility based on symbol characteristics
|
|
106
106
|
if symbol in ["AAPL", "MSFT", "GOOGL", "AMZN"]:
|
|
107
107
|
return 0.25 # Large cap tech
|
|
@@ -109,39 +109,43 @@ class RiskManager:
|
|
|
109
109
|
return 0.45 # High volatility tech
|
|
110
110
|
else:
|
|
111
111
|
return 0.30 # Default
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
except Exception as e:
|
|
114
114
|
logger.error(f"Failed to estimate volatility for {symbol}: {e}")
|
|
115
115
|
return 0.30 # Default volatility
|
|
116
|
-
|
|
117
|
-
def _calculate_portfolio_risk_metrics(
|
|
116
|
+
|
|
117
|
+
def _calculate_portfolio_risk_metrics(
|
|
118
|
+
self, positions: List, position_risks: List[Dict]
|
|
119
|
+
) -> Dict:
|
|
118
120
|
"""Calculate portfolio-level risk metrics"""
|
|
119
121
|
try:
|
|
120
122
|
if not positions or not position_risks:
|
|
121
123
|
return {}
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
# Total portfolio risk (sum of individual position risks)
|
|
124
126
|
total_risk = sum(risk["risk_score"] for risk in position_risks)
|
|
125
|
-
|
|
127
|
+
|
|
126
128
|
# Concentration risk (Herfindahl-Hirschman Index)
|
|
127
129
|
weights = [pos.weight for pos in positions]
|
|
128
130
|
concentration_risk = sum(w**2 for w in weights) * 100
|
|
129
|
-
|
|
131
|
+
|
|
130
132
|
# Portfolio Value at Risk (simplified)
|
|
131
133
|
portfolio_var = sum(risk["var_95"] for risk in position_risks)
|
|
132
|
-
|
|
134
|
+
|
|
133
135
|
# Conditional Value at Risk (simplified)
|
|
134
136
|
portfolio_cvar = portfolio_var * 1.3 # Rough approximation
|
|
135
|
-
|
|
137
|
+
|
|
136
138
|
# Maximum position risk
|
|
137
|
-
max_position_risk =
|
|
138
|
-
|
|
139
|
+
max_position_risk = (
|
|
140
|
+
max(risk["risk_score"] for risk in position_risks) if position_risks else 0
|
|
141
|
+
)
|
|
142
|
+
|
|
139
143
|
# Portfolio beta (simplified - assume equal weight)
|
|
140
144
|
portfolio_beta = np.mean([self._estimate_beta(pos.symbol) for pos in positions])
|
|
141
|
-
|
|
145
|
+
|
|
142
146
|
# Overall risk score (0-100)
|
|
143
147
|
risk_score = min(total_risk + concentration_risk, 100)
|
|
144
|
-
|
|
148
|
+
|
|
145
149
|
return {
|
|
146
150
|
"total_risk": total_risk,
|
|
147
151
|
"concentration_risk": concentration_risk,
|
|
@@ -152,85 +156,102 @@ class RiskManager:
|
|
|
152
156
|
"risk_score": risk_score,
|
|
153
157
|
"num_positions": len(positions),
|
|
154
158
|
}
|
|
155
|
-
|
|
159
|
+
|
|
156
160
|
except Exception as e:
|
|
157
161
|
logger.error(f"Failed to calculate portfolio risk metrics: {e}")
|
|
158
162
|
return {}
|
|
159
|
-
|
|
163
|
+
|
|
160
164
|
def _estimate_beta(self, symbol: str) -> float:
|
|
161
165
|
"""Estimate beta for a symbol (simplified)"""
|
|
162
166
|
try:
|
|
163
167
|
# In practice, calculate beta against market index
|
|
164
168
|
# For now, use simplified estimates
|
|
165
169
|
beta_estimates = {
|
|
166
|
-
"AAPL": 1.2,
|
|
167
|
-
"
|
|
168
|
-
"
|
|
170
|
+
"AAPL": 1.2,
|
|
171
|
+
"MSFT": 1.1,
|
|
172
|
+
"GOOGL": 1.3,
|
|
173
|
+
"AMZN": 1.4,
|
|
174
|
+
"TSLA": 2.0,
|
|
175
|
+
"NVDA": 1.8,
|
|
176
|
+
"AMD": 1.6,
|
|
177
|
+
"META": 1.5,
|
|
178
|
+
"JPM": 1.3,
|
|
179
|
+
"BAC": 1.4,
|
|
180
|
+
"WMT": 0.5,
|
|
181
|
+
"JNJ": 0.7,
|
|
169
182
|
}
|
|
170
183
|
return beta_estimates.get(symbol, 1.0)
|
|
171
184
|
except Exception as e:
|
|
172
185
|
logger.error(f"Failed to estimate beta for {symbol}: {e}")
|
|
173
186
|
return 1.0
|
|
174
|
-
|
|
187
|
+
|
|
175
188
|
def check_risk_limits(self, portfolio_id: UUID, new_order: Dict) -> Tuple[bool, List[str]]:
|
|
176
189
|
"""Check if a new order would violate risk limits"""
|
|
177
190
|
try:
|
|
178
191
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
179
192
|
if not portfolio:
|
|
180
193
|
return False, ["Portfolio not found"]
|
|
181
|
-
|
|
194
|
+
|
|
182
195
|
warnings = []
|
|
183
|
-
|
|
196
|
+
|
|
184
197
|
# Get current positions
|
|
185
198
|
positions = self.trading_service.get_portfolio_positions(portfolio_id)
|
|
186
|
-
|
|
199
|
+
|
|
187
200
|
# Calculate new position size
|
|
188
201
|
symbol = new_order["symbol"]
|
|
189
202
|
quantity = new_order["quantity"]
|
|
190
203
|
side = new_order["side"]
|
|
191
|
-
|
|
204
|
+
|
|
192
205
|
# Estimate order value (simplified)
|
|
193
206
|
order_value = self._estimate_order_value(symbol, quantity)
|
|
194
207
|
new_position_size_pct = (order_value / float(portfolio.current_value)) * 100
|
|
195
|
-
|
|
208
|
+
|
|
196
209
|
# Check position size limits
|
|
197
210
|
max_position_size = 10.0 # 10% default
|
|
198
211
|
if new_position_size_pct > max_position_size:
|
|
199
|
-
warnings.append(
|
|
200
|
-
|
|
212
|
+
warnings.append(
|
|
213
|
+
f"Position size ({new_position_size_pct:.1f}%) exceeds limit ({max_position_size}%)"
|
|
214
|
+
)
|
|
215
|
+
|
|
201
216
|
# Check if adding to existing position
|
|
202
217
|
existing_position = next((pos for pos in positions if pos.symbol == symbol), None)
|
|
203
218
|
if existing_position:
|
|
204
219
|
if side == "buy":
|
|
205
|
-
new_total_size = (
|
|
220
|
+
new_total_size = (
|
|
221
|
+
(existing_position.market_value + order_value)
|
|
222
|
+
/ float(portfolio.current_value)
|
|
223
|
+
) * 100
|
|
206
224
|
if new_total_size > max_position_size:
|
|
207
|
-
warnings.append(
|
|
208
|
-
|
|
225
|
+
warnings.append(
|
|
226
|
+
f"Total position size ({new_total_size:.1f}%) would exceed limit"
|
|
227
|
+
)
|
|
228
|
+
|
|
209
229
|
# Check portfolio concentration
|
|
210
230
|
if len(positions) >= 10: # Max 10 positions
|
|
211
231
|
warnings.append("Portfolio already has maximum number of positions")
|
|
212
|
-
|
|
232
|
+
|
|
213
233
|
# Check buying power
|
|
214
234
|
if side == "buy" and order_value > float(portfolio.cash_balance):
|
|
215
235
|
warnings.append("Insufficient buying power for this order")
|
|
216
|
-
|
|
236
|
+
|
|
217
237
|
# Check risk level compliance
|
|
218
|
-
risk_level = getattr(portfolio,
|
|
238
|
+
risk_level = getattr(portfolio, "risk_level", RiskLevel.MODERATE)
|
|
219
239
|
if risk_level == RiskLevel.CONSERVATIVE and new_position_size_pct > 5.0:
|
|
220
240
|
warnings.append("Position size too large for conservative risk level")
|
|
221
241
|
elif risk_level == RiskLevel.AGGRESSIVE and new_position_size_pct > 20.0:
|
|
222
242
|
warnings.append("Position size too large for aggressive risk level")
|
|
223
|
-
|
|
243
|
+
|
|
224
244
|
return len(warnings) == 0, warnings
|
|
225
|
-
|
|
245
|
+
|
|
226
246
|
except Exception as e:
|
|
227
247
|
logger.error(f"Failed to check risk limits: {e}")
|
|
228
248
|
return False, [f"Risk check failed: {e}"]
|
|
229
|
-
|
|
249
|
+
|
|
230
250
|
def _estimate_order_value(self, symbol: str, quantity: int) -> float:
|
|
231
251
|
"""Estimate the value of an order"""
|
|
232
252
|
try:
|
|
233
253
|
import yfinance as yf
|
|
254
|
+
|
|
234
255
|
ticker = yf.Ticker(symbol)
|
|
235
256
|
data = ticker.history(period="1d")
|
|
236
257
|
if not data.empty:
|
|
@@ -240,60 +261,60 @@ class RiskManager:
|
|
|
240
261
|
except Exception as e:
|
|
241
262
|
logger.error(f"Failed to estimate order value for {symbol}: {e}")
|
|
242
263
|
return 0.0
|
|
243
|
-
|
|
264
|
+
|
|
244
265
|
def calculate_position_size(
|
|
245
|
-
self,
|
|
246
|
-
portfolio_id: UUID,
|
|
247
|
-
symbol: str,
|
|
266
|
+
self,
|
|
267
|
+
portfolio_id: UUID,
|
|
268
|
+
symbol: str,
|
|
248
269
|
signal_strength: float,
|
|
249
|
-
risk_level: RiskLevel = RiskLevel.MODERATE
|
|
270
|
+
risk_level: RiskLevel = RiskLevel.MODERATE,
|
|
250
271
|
) -> float:
|
|
251
272
|
"""Calculate recommended position size based on signal strength and risk level"""
|
|
252
273
|
try:
|
|
253
274
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
254
275
|
if not portfolio:
|
|
255
276
|
return 0.0
|
|
256
|
-
|
|
277
|
+
|
|
257
278
|
# Base position size based on risk level
|
|
258
279
|
base_sizes = {
|
|
259
280
|
RiskLevel.CONSERVATIVE: 0.02, # 2%
|
|
260
|
-
RiskLevel.MODERATE: 0.05,
|
|
261
|
-
RiskLevel.AGGRESSIVE: 0.10,
|
|
281
|
+
RiskLevel.MODERATE: 0.05, # 5%
|
|
282
|
+
RiskLevel.AGGRESSIVE: 0.10, # 10%
|
|
262
283
|
}
|
|
263
284
|
base_size = base_sizes.get(risk_level, 0.05)
|
|
264
|
-
|
|
285
|
+
|
|
265
286
|
# Adjust based on signal strength
|
|
266
287
|
signal_multiplier = min(signal_strength * 2, 2.0) # Cap at 2x
|
|
267
|
-
|
|
288
|
+
|
|
268
289
|
# Calculate recommended position size
|
|
269
290
|
recommended_size = base_size * signal_multiplier
|
|
270
|
-
|
|
291
|
+
|
|
271
292
|
# Cap at maximum position size
|
|
272
293
|
max_position_size = 0.20 # 20% max
|
|
273
294
|
recommended_size = min(recommended_size, max_position_size)
|
|
274
|
-
|
|
295
|
+
|
|
275
296
|
# Convert to dollar amount
|
|
276
297
|
position_value = float(portfolio.current_value) * recommended_size
|
|
277
|
-
|
|
298
|
+
|
|
278
299
|
return position_value
|
|
279
|
-
|
|
300
|
+
|
|
280
301
|
except Exception as e:
|
|
281
302
|
logger.error(f"Failed to calculate position size: {e}")
|
|
282
303
|
return 0.0
|
|
283
|
-
|
|
304
|
+
|
|
284
305
|
def generate_risk_report(self, portfolio_id: UUID) -> Dict:
|
|
285
306
|
"""Generate comprehensive risk report for a portfolio"""
|
|
286
307
|
try:
|
|
287
308
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
288
309
|
if not portfolio:
|
|
289
310
|
return {}
|
|
290
|
-
|
|
311
|
+
|
|
291
312
|
# Get risk metrics
|
|
292
313
|
risk_metrics = self.calculate_portfolio_risk(portfolio_id)
|
|
293
|
-
|
|
314
|
+
|
|
294
315
|
# Get performance data
|
|
295
316
|
performance_df = self.trading_service.get_portfolio_performance(portfolio_id, days=30)
|
|
296
|
-
|
|
317
|
+
|
|
297
318
|
# Calculate additional metrics
|
|
298
319
|
max_drawdown = 0.0
|
|
299
320
|
if not performance_df.empty:
|
|
@@ -301,17 +322,17 @@ class RiskManager:
|
|
|
301
322
|
running_max = cumulative_returns.expanding().max()
|
|
302
323
|
drawdown = (cumulative_returns - running_max) / running_max
|
|
303
324
|
max_drawdown = abs(drawdown.min()) * 100
|
|
304
|
-
|
|
325
|
+
|
|
305
326
|
# Risk assessment
|
|
306
327
|
risk_level = "LOW"
|
|
307
328
|
if risk_metrics.get("risk_score", 0) > 70:
|
|
308
329
|
risk_level = "HIGH"
|
|
309
330
|
elif risk_metrics.get("risk_score", 0) > 40:
|
|
310
331
|
risk_level = "MEDIUM"
|
|
311
|
-
|
|
332
|
+
|
|
312
333
|
# Generate recommendations
|
|
313
334
|
recommendations = self._generate_risk_recommendations(risk_metrics, max_drawdown)
|
|
314
|
-
|
|
335
|
+
|
|
315
336
|
return {
|
|
316
337
|
"portfolio_id": str(portfolio_id),
|
|
317
338
|
"risk_level": risk_level,
|
|
@@ -325,46 +346,46 @@ class RiskManager:
|
|
|
325
346
|
"recommendations": recommendations,
|
|
326
347
|
"generated_at": datetime.utcnow().isoformat(),
|
|
327
348
|
}
|
|
328
|
-
|
|
349
|
+
|
|
329
350
|
except Exception as e:
|
|
330
351
|
logger.error(f"Failed to generate risk report: {e}")
|
|
331
352
|
return {}
|
|
332
|
-
|
|
353
|
+
|
|
333
354
|
def _generate_risk_recommendations(self, risk_metrics: Dict, max_drawdown: float) -> List[str]:
|
|
334
355
|
"""Generate risk management recommendations"""
|
|
335
356
|
recommendations = []
|
|
336
|
-
|
|
357
|
+
|
|
337
358
|
# Concentration risk
|
|
338
359
|
if risk_metrics.get("concentration_risk", 0) > 50:
|
|
339
360
|
recommendations.append("Consider diversifying portfolio - concentration risk is high")
|
|
340
|
-
|
|
361
|
+
|
|
341
362
|
# Position count
|
|
342
363
|
if risk_metrics.get("num_positions", 0) < 5:
|
|
343
364
|
recommendations.append("Consider adding more positions for better diversification")
|
|
344
365
|
elif risk_metrics.get("num_positions", 0) > 15:
|
|
345
366
|
recommendations.append("Consider reducing number of positions for better focus")
|
|
346
|
-
|
|
367
|
+
|
|
347
368
|
# Risk score
|
|
348
369
|
if risk_metrics.get("risk_score", 0) > 70:
|
|
349
370
|
recommendations.append("Portfolio risk is high - consider reducing position sizes")
|
|
350
|
-
|
|
371
|
+
|
|
351
372
|
# Drawdown
|
|
352
373
|
if max_drawdown > 20:
|
|
353
374
|
recommendations.append("Maximum drawdown is high - consider risk management strategies")
|
|
354
|
-
|
|
375
|
+
|
|
355
376
|
# Beta
|
|
356
377
|
if risk_metrics.get("portfolio_beta", 1.0) > 1.5:
|
|
357
378
|
recommendations.append("Portfolio beta is high - consider adding defensive positions")
|
|
358
379
|
elif risk_metrics.get("portfolio_beta", 1.0) < 0.5:
|
|
359
380
|
recommendations.append("Portfolio beta is low - consider adding growth positions")
|
|
360
|
-
|
|
381
|
+
|
|
361
382
|
if not recommendations:
|
|
362
383
|
recommendations.append("Portfolio risk profile looks good - no immediate concerns")
|
|
363
|
-
|
|
384
|
+
|
|
364
385
|
return recommendations
|
|
365
386
|
|
|
366
387
|
|
|
367
388
|
def create_risk_manager(db_session: Session) -> RiskManager:
|
|
368
389
|
"""Create a risk manager instance"""
|
|
369
390
|
trading_service = TradingService(db_session)
|
|
370
|
-
return RiskManager(trading_service)
|
|
391
|
+
return RiskManager(trading_service)
|