mcli-framework 7.6.0__py3-none-any.whl → 7.6.1__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.

Files changed (49) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/main.py +10 -2
  3. mcli/app/model_cmd.py +1 -1
  4. mcli/lib/custom_commands.py +4 -10
  5. mcli/ml/api/app.py +1 -5
  6. mcli/ml/dashboard/app.py +2 -2
  7. mcli/ml/dashboard/app_integrated.py +168 -116
  8. mcli/ml/dashboard/app_supabase.py +7 -3
  9. mcli/ml/dashboard/app_training.py +3 -6
  10. mcli/ml/dashboard/components/charts.py +74 -115
  11. mcli/ml/dashboard/components/metrics.py +24 -44
  12. mcli/ml/dashboard/components/tables.py +32 -40
  13. mcli/ml/dashboard/overview.py +102 -78
  14. mcli/ml/dashboard/pages/cicd.py +103 -56
  15. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  16. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  17. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  18. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  19. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  20. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  21. mcli/ml/dashboard/pages/trading.py +238 -169
  22. mcli/ml/dashboard/pages/workflows.py +129 -84
  23. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  24. mcli/ml/dashboard/utils.py +24 -21
  25. mcli/ml/dashboard/warning_suppression.py +6 -4
  26. mcli/ml/database/session.py +16 -5
  27. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  28. mcli/ml/predictions/monte_carlo.py +6 -18
  29. mcli/ml/trading/alpaca_client.py +95 -96
  30. mcli/ml/trading/migrations.py +76 -40
  31. mcli/ml/trading/models.py +78 -60
  32. mcli/ml/trading/paper_trading.py +92 -74
  33. mcli/ml/trading/risk_management.py +106 -85
  34. mcli/ml/trading/trading_service.py +155 -110
  35. mcli/ml/training/train_model.py +1 -3
  36. mcli/self/self_cmd.py +71 -57
  37. mcli/workflow/daemon/daemon.py +2 -0
  38. mcli/workflow/model_service/openai_adapter.py +6 -2
  39. mcli/workflow/politician_trading/models.py +6 -2
  40. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  41. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  42. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  43. mcli/workflow/politician_trading/seed_database.py +70 -89
  44. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
  45. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +49 -49
  46. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
  47. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
  48. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
  49. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.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, TradingOrder, RiskLevel
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(self, positions: List, position_risks: List[Dict]) -> Dict:
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 = max(risk["risk_score"] for risk in position_risks) if position_risks else 0
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, "MSFT": 1.1, "GOOGL": 1.3, "AMZN": 1.4,
167
- "TSLA": 2.0, "NVDA": 1.8, "AMD": 1.6, "META": 1.5,
168
- "JPM": 1.3, "BAC": 1.4, "WMT": 0.5, "JNJ": 0.7,
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(f"Position size ({new_position_size_pct:.1f}%) exceeds limit ({max_position_size}%)")
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 = ((existing_position.market_value + order_value) / float(portfolio.current_value)) * 100
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(f"Total position size ({new_total_size:.1f}%) would exceed limit")
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, 'risk_level', RiskLevel.MODERATE)
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, # 5%
261
- RiskLevel.AGGRESSIVE: 0.10, # 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)