mcli-framework 7.1.1__py3-none-any.whl → 7.1.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.

Files changed (94) hide show
  1. mcli/app/completion_cmd.py +59 -49
  2. mcli/app/completion_helpers.py +60 -138
  3. mcli/app/logs_cmd.py +6 -2
  4. mcli/app/main.py +17 -14
  5. mcli/app/model_cmd.py +19 -4
  6. mcli/chat/chat.py +3 -2
  7. mcli/lib/search/cached_vectorizer.py +1 -0
  8. mcli/lib/services/data_pipeline.py +12 -5
  9. mcli/lib/services/lsh_client.py +68 -57
  10. mcli/ml/api/app.py +28 -36
  11. mcli/ml/api/middleware.py +8 -16
  12. mcli/ml/api/routers/admin_router.py +3 -1
  13. mcli/ml/api/routers/auth_router.py +32 -56
  14. mcli/ml/api/routers/backtest_router.py +3 -1
  15. mcli/ml/api/routers/data_router.py +3 -1
  16. mcli/ml/api/routers/model_router.py +35 -74
  17. mcli/ml/api/routers/monitoring_router.py +3 -1
  18. mcli/ml/api/routers/portfolio_router.py +3 -1
  19. mcli/ml/api/routers/prediction_router.py +60 -65
  20. mcli/ml/api/routers/trade_router.py +6 -2
  21. mcli/ml/api/routers/websocket_router.py +12 -9
  22. mcli/ml/api/schemas.py +10 -2
  23. mcli/ml/auth/auth_manager.py +49 -114
  24. mcli/ml/auth/models.py +30 -15
  25. mcli/ml/auth/permissions.py +12 -19
  26. mcli/ml/backtesting/backtest_engine.py +134 -108
  27. mcli/ml/backtesting/performance_metrics.py +142 -108
  28. mcli/ml/cache.py +12 -18
  29. mcli/ml/cli/main.py +37 -23
  30. mcli/ml/config/settings.py +29 -12
  31. mcli/ml/dashboard/app.py +122 -130
  32. mcli/ml/dashboard/app_integrated.py +216 -150
  33. mcli/ml/dashboard/app_supabase.py +176 -108
  34. mcli/ml/dashboard/app_training.py +212 -206
  35. mcli/ml/dashboard/cli.py +14 -5
  36. mcli/ml/data_ingestion/api_connectors.py +51 -81
  37. mcli/ml/data_ingestion/data_pipeline.py +127 -125
  38. mcli/ml/data_ingestion/stream_processor.py +72 -80
  39. mcli/ml/database/migrations/env.py +3 -2
  40. mcli/ml/database/models.py +112 -79
  41. mcli/ml/database/session.py +6 -5
  42. mcli/ml/experimentation/ab_testing.py +149 -99
  43. mcli/ml/features/ensemble_features.py +9 -8
  44. mcli/ml/features/political_features.py +6 -5
  45. mcli/ml/features/recommendation_engine.py +15 -14
  46. mcli/ml/features/stock_features.py +7 -6
  47. mcli/ml/features/test_feature_engineering.py +8 -7
  48. mcli/ml/logging.py +10 -15
  49. mcli/ml/mlops/data_versioning.py +57 -64
  50. mcli/ml/mlops/experiment_tracker.py +49 -41
  51. mcli/ml/mlops/model_serving.py +59 -62
  52. mcli/ml/mlops/pipeline_orchestrator.py +203 -149
  53. mcli/ml/models/base_models.py +8 -7
  54. mcli/ml/models/ensemble_models.py +6 -5
  55. mcli/ml/models/recommendation_models.py +7 -6
  56. mcli/ml/models/test_models.py +18 -14
  57. mcli/ml/monitoring/drift_detection.py +95 -74
  58. mcli/ml/monitoring/metrics.py +10 -22
  59. mcli/ml/optimization/portfolio_optimizer.py +172 -132
  60. mcli/ml/predictions/prediction_engine.py +62 -50
  61. mcli/ml/preprocessing/data_cleaners.py +6 -5
  62. mcli/ml/preprocessing/feature_extractors.py +7 -6
  63. mcli/ml/preprocessing/ml_pipeline.py +3 -2
  64. mcli/ml/preprocessing/politician_trading_preprocessor.py +11 -10
  65. mcli/ml/preprocessing/test_preprocessing.py +4 -4
  66. mcli/ml/scripts/populate_sample_data.py +36 -16
  67. mcli/ml/tasks.py +82 -83
  68. mcli/ml/tests/test_integration.py +86 -76
  69. mcli/ml/tests/test_training_dashboard.py +169 -142
  70. mcli/mygroup/test_cmd.py +2 -1
  71. mcli/self/self_cmd.py +31 -16
  72. mcli/self/test_cmd.py +2 -1
  73. mcli/workflow/dashboard/dashboard_cmd.py +13 -6
  74. mcli/workflow/lsh_integration.py +46 -58
  75. mcli/workflow/politician_trading/commands.py +576 -427
  76. mcli/workflow/politician_trading/config.py +7 -7
  77. mcli/workflow/politician_trading/connectivity.py +35 -33
  78. mcli/workflow/politician_trading/data_sources.py +72 -71
  79. mcli/workflow/politician_trading/database.py +18 -16
  80. mcli/workflow/politician_trading/demo.py +4 -3
  81. mcli/workflow/politician_trading/models.py +5 -5
  82. mcli/workflow/politician_trading/monitoring.py +13 -13
  83. mcli/workflow/politician_trading/scrapers.py +332 -224
  84. mcli/workflow/politician_trading/scrapers_california.py +116 -94
  85. mcli/workflow/politician_trading/scrapers_eu.py +70 -71
  86. mcli/workflow/politician_trading/scrapers_uk.py +118 -90
  87. mcli/workflow/politician_trading/scrapers_us_states.py +125 -92
  88. mcli/workflow/politician_trading/workflow.py +98 -71
  89. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +1 -1
  90. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -94
  91. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
  92. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
  93. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
  94. {mcli_framework-7.1.1.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,28 @@
1
1
  """Advanced portfolio optimization for stock recommendations"""
2
2
 
3
- import numpy as np
4
- import pandas as pd
5
- from typing import Dict, List, Tuple, Optional, Union, Any
3
+ import logging
4
+ from abc import ABC, abstractmethod
6
5
  from dataclasses import dataclass, field
7
6
  from datetime import datetime, timedelta
8
- from pathlib import Path
9
- import logging
10
7
  from enum import Enum
11
- from abc import ABC, abstractmethod
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Tuple, Union
12
10
 
13
11
  # Optimization libraries
14
12
  import cvxpy as cp
15
- from scipy.optimize import minimize
16
- from scipy.stats import norm
17
13
  import matplotlib.pyplot as plt
14
+ import numpy as np
15
+ import pandas as pd
18
16
  import seaborn as sns
17
+ from scipy.optimize import minimize
18
+ from scipy.stats import norm
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
22
22
 
23
23
  class OptimizationObjective(Enum):
24
24
  """Portfolio optimization objectives"""
25
+
25
26
  MEAN_VARIANCE = "mean_variance"
26
27
  RISK_PARITY = "risk_parity"
27
28
  MINIMUM_VARIANCE = "minimum_variance"
@@ -35,6 +36,7 @@ class OptimizationObjective(Enum):
35
36
  @dataclass
36
37
  class OptimizationConstraints:
37
38
  """Portfolio optimization constraints"""
39
+
38
40
  # Weight constraints
39
41
  min_weight: float = 0.0
40
42
  max_weight: float = 1.0
@@ -65,6 +67,7 @@ class OptimizationConstraints:
65
67
  @dataclass
66
68
  class PortfolioAllocation:
67
69
  """Portfolio allocation result"""
70
+
68
71
  weights: Dict[str, float]
69
72
  expected_return: float
70
73
  expected_volatility: float
@@ -96,29 +99,39 @@ class BaseOptimizer(ABC):
96
99
  self.constraints = constraints
97
100
 
98
101
  @abstractmethod
99
- def optimize(self, expected_returns: pd.Series,
100
- covariance_matrix: pd.DataFrame,
101
- **kwargs) -> PortfolioAllocation:
102
+ def optimize(
103
+ self, expected_returns: pd.Series, covariance_matrix: pd.DataFrame, **kwargs
104
+ ) -> PortfolioAllocation:
102
105
  """Optimize portfolio allocation"""
103
106
  pass
104
107
 
105
- def _calculate_portfolio_metrics(self, weights: np.ndarray,
106
- expected_returns: pd.Series,
107
- covariance_matrix: pd.DataFrame,
108
- risk_free_rate: float = 0.02) -> Tuple[float, float, float]:
108
+ def _calculate_portfolio_metrics(
109
+ self,
110
+ weights: np.ndarray,
111
+ expected_returns: pd.Series,
112
+ covariance_matrix: pd.DataFrame,
113
+ risk_free_rate: float = 0.02,
114
+ ) -> Tuple[float, float, float]:
109
115
  """Calculate portfolio return, volatility, and Sharpe ratio"""
110
116
  portfolio_return = np.dot(weights, expected_returns)
111
117
  portfolio_variance = np.dot(weights.T, np.dot(covariance_matrix, weights))
112
118
  portfolio_volatility = np.sqrt(portfolio_variance)
113
119
 
114
- sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_volatility if portfolio_volatility > 0 else 0
120
+ sharpe_ratio = (
121
+ (portfolio_return - risk_free_rate) / portfolio_volatility
122
+ if portfolio_volatility > 0
123
+ else 0
124
+ )
115
125
 
116
126
  return portfolio_return, portfolio_volatility, sharpe_ratio
117
127
 
118
- def _calculate_var_cvar(self, weights: np.ndarray,
119
- expected_returns: pd.Series,
120
- covariance_matrix: pd.DataFrame,
121
- confidence_level: float = 0.95) -> Tuple[float, float]:
128
+ def _calculate_var_cvar(
129
+ self,
130
+ weights: np.ndarray,
131
+ expected_returns: pd.Series,
132
+ covariance_matrix: pd.DataFrame,
133
+ confidence_level: float = 0.95,
134
+ ) -> Tuple[float, float]:
122
135
  """Calculate Value at Risk and Conditional Value at Risk"""
123
136
  portfolio_return = np.dot(weights, expected_returns)
124
137
  portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(covariance_matrix, weights)))
@@ -136,10 +149,13 @@ class BaseOptimizer(ABC):
136
149
  class MeanVarianceOptimizer(BaseOptimizer):
137
150
  """Modern Portfolio Theory mean-variance optimizer"""
138
151
 
139
- def optimize(self, expected_returns: pd.Series,
140
- covariance_matrix: pd.DataFrame,
141
- risk_aversion: float = 1.0,
142
- **kwargs) -> PortfolioAllocation:
152
+ def optimize(
153
+ self,
154
+ expected_returns: pd.Series,
155
+ covariance_matrix: pd.DataFrame,
156
+ risk_aversion: float = 1.0,
157
+ **kwargs,
158
+ ) -> PortfolioAllocation:
143
159
  """Optimize using mean-variance framework"""
144
160
  n_assets = len(expected_returns)
145
161
 
@@ -155,7 +171,7 @@ class MeanVarianceOptimizer(BaseOptimizer):
155
171
  constraints = [
156
172
  cp.sum(w) == self.constraints.sum_weights, # Weights sum to 1
157
173
  w >= self.constraints.min_weight, # Min weight
158
- w <= self.constraints.max_weight # Max weight
174
+ w <= self.constraints.max_weight, # Max weight
159
175
  ]
160
176
 
161
177
  # Additional constraints
@@ -190,7 +206,7 @@ class MeanVarianceOptimizer(BaseOptimizer):
190
206
  var_95=var_95,
191
207
  cvar_95=cvar_95,
192
208
  concentration=np.sum(np.square(optimal_weights)), # Herfindahl index
193
- optimization_method="mean_variance"
209
+ optimization_method="mean_variance",
194
210
  )
195
211
  else:
196
212
  raise ValueError(f"Optimization failed with status: {problem.status}")
@@ -199,9 +215,9 @@ class MeanVarianceOptimizer(BaseOptimizer):
199
215
  class RiskParityOptimizer(BaseOptimizer):
200
216
  """Risk parity portfolio optimizer"""
201
217
 
202
- def optimize(self, expected_returns: pd.Series,
203
- covariance_matrix: pd.DataFrame,
204
- **kwargs) -> PortfolioAllocation:
218
+ def optimize(
219
+ self, expected_returns: pd.Series, covariance_matrix: pd.DataFrame, **kwargs
220
+ ) -> PortfolioAllocation:
205
221
  """Optimize using risk parity approach"""
206
222
 
207
223
  def risk_parity_objective(weights, cov_matrix):
@@ -218,13 +234,12 @@ class RiskParityOptimizer(BaseOptimizer):
218
234
  initial_weights = np.ones(n_assets) / n_assets
219
235
 
220
236
  # Constraints
221
- constraints = [
222
- {'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0} # Weights sum to 1
223
- ]
237
+ constraints = [{"type": "eq", "fun": lambda x: np.sum(x) - 1.0}] # Weights sum to 1
224
238
 
225
239
  # Bounds
226
- bounds = [(self.constraints.min_weight, self.constraints.max_weight)
227
- for _ in range(n_assets)]
240
+ bounds = [
241
+ (self.constraints.min_weight, self.constraints.max_weight) for _ in range(n_assets)
242
+ ]
228
243
 
229
244
  if not self.constraints.allow_short:
230
245
  bounds = [(0, self.constraints.max_weight) for _ in range(n_assets)]
@@ -234,9 +249,9 @@ class RiskParityOptimizer(BaseOptimizer):
234
249
  risk_parity_objective,
235
250
  initial_weights,
236
251
  args=(covariance_matrix.values,),
237
- method='SLSQP',
252
+ method="SLSQP",
238
253
  bounds=bounds,
239
- constraints=constraints
254
+ constraints=constraints,
240
255
  )
241
256
 
242
257
  if result.success:
@@ -260,7 +275,7 @@ class RiskParityOptimizer(BaseOptimizer):
260
275
  var_95=var_95,
261
276
  cvar_95=cvar_95,
262
277
  concentration=np.sum(np.square(optimal_weights)),
263
- optimization_method="risk_parity"
278
+ optimization_method="risk_parity",
264
279
  )
265
280
  else:
266
281
  raise ValueError(f"Risk parity optimization failed: {result.message}")
@@ -269,14 +284,17 @@ class RiskParityOptimizer(BaseOptimizer):
269
284
  class BlackLittermanOptimizer(BaseOptimizer):
270
285
  """Black-Litterman portfolio optimizer"""
271
286
 
272
- def optimize(self, expected_returns: pd.Series,
273
- covariance_matrix: pd.DataFrame,
274
- market_caps: Optional[pd.Series] = None,
275
- views: Optional[Dict[str, float]] = None,
276
- view_uncertainties: Optional[Dict[str, float]] = None,
277
- tau: float = 0.1,
278
- risk_aversion: float = 3.0,
279
- **kwargs) -> PortfolioAllocation:
287
+ def optimize(
288
+ self,
289
+ expected_returns: pd.Series,
290
+ covariance_matrix: pd.DataFrame,
291
+ market_caps: Optional[pd.Series] = None,
292
+ views: Optional[Dict[str, float]] = None,
293
+ view_uncertainties: Optional[Dict[str, float]] = None,
294
+ tau: float = 0.1,
295
+ risk_aversion: float = 3.0,
296
+ **kwargs,
297
+ ) -> PortfolioAllocation:
280
298
  """Optimize using Black-Litterman model"""
281
299
 
282
300
  # Market capitalization weights (if not provided, use equal weights)
@@ -323,7 +341,9 @@ class BlackLittermanOptimizer(BaseOptimizer):
323
341
 
324
342
  # New covariance matrix
325
343
  bl_cov = np.linalg.inv(M1 + M2)
326
- bl_cov = pd.DataFrame(bl_cov, index=covariance_matrix.index, columns=covariance_matrix.columns)
344
+ bl_cov = pd.DataFrame(
345
+ bl_cov, index=covariance_matrix.index, columns=covariance_matrix.columns
346
+ )
327
347
 
328
348
  # Now optimize using mean-variance with BL inputs
329
349
  mv_optimizer = MeanVarianceOptimizer(self.constraints)
@@ -336,11 +356,14 @@ class BlackLittermanOptimizer(BaseOptimizer):
336
356
  class CVaROptimizer(BaseOptimizer):
337
357
  """Conditional Value at Risk optimizer"""
338
358
 
339
- def optimize(self, expected_returns: pd.Series,
340
- covariance_matrix: pd.DataFrame,
341
- scenarios: Optional[pd.DataFrame] = None,
342
- confidence_level: float = 0.95,
343
- **kwargs) -> PortfolioAllocation:
359
+ def optimize(
360
+ self,
361
+ expected_returns: pd.Series,
362
+ covariance_matrix: pd.DataFrame,
363
+ scenarios: Optional[pd.DataFrame] = None,
364
+ confidence_level: float = 0.95,
365
+ **kwargs,
366
+ ) -> PortfolioAllocation:
344
367
  """Optimize portfolio to minimize CVaR"""
345
368
 
346
369
  if scenarios is None:
@@ -373,7 +396,7 @@ class CVaROptimizer(BaseOptimizer):
373
396
  w >= self.constraints.min_weight,
374
397
  w <= self.constraints.max_weight,
375
398
  u >= 0,
376
- u >= alpha - portfolio_returns # CVaR constraints
399
+ u >= alpha - portfolio_returns, # CVaR constraints
377
400
  ]
378
401
 
379
402
  if not self.constraints.allow_short:
@@ -404,7 +427,7 @@ class CVaROptimizer(BaseOptimizer):
404
427
  var_95=var_95,
405
428
  cvar_95=cvar_95,
406
429
  concentration=np.sum(np.square(optimal_weights)),
407
- optimization_method="cvar"
430
+ optimization_method="cvar",
408
431
  )
409
432
  else:
410
433
  raise ValueError(f"CVaR optimization failed with status: {problem.status}")
@@ -413,9 +436,9 @@ class CVaROptimizer(BaseOptimizer):
413
436
  class KellyCriterionOptimizer(BaseOptimizer):
414
437
  """Kelly Criterion optimizer for growth-optimal portfolios"""
415
438
 
416
- def optimize(self, expected_returns: pd.Series,
417
- covariance_matrix: pd.DataFrame,
418
- **kwargs) -> PortfolioAllocation:
439
+ def optimize(
440
+ self, expected_returns: pd.Series, covariance_matrix: pd.DataFrame, **kwargs
441
+ ) -> PortfolioAllocation:
419
442
  """Optimize using Kelly Criterion"""
420
443
 
421
444
  # Kelly optimal weights: w* = Σ^(-1) * μ
@@ -429,9 +452,9 @@ class KellyCriterionOptimizer(BaseOptimizer):
429
452
  if not self.constraints.allow_short:
430
453
  kelly_weights = np.maximum(kelly_weights, 0)
431
454
 
432
- kelly_weights = np.clip(kelly_weights,
433
- self.constraints.min_weight,
434
- self.constraints.max_weight)
455
+ kelly_weights = np.clip(
456
+ kelly_weights, self.constraints.min_weight, self.constraints.max_weight
457
+ )
435
458
 
436
459
  # Normalize to sum to 1
437
460
  if np.sum(kelly_weights) > 0:
@@ -459,7 +482,7 @@ class KellyCriterionOptimizer(BaseOptimizer):
459
482
  var_95=var_95,
460
483
  cvar_95=cvar_95,
461
484
  concentration=np.sum(np.square(kelly_weights)),
462
- optimization_method="kelly_criterion"
485
+ optimization_method="kelly_criterion",
463
486
  )
464
487
 
465
488
  except np.linalg.LinAlgError:
@@ -482,7 +505,7 @@ class KellyCriterionOptimizer(BaseOptimizer):
482
505
  expected_return=port_return,
483
506
  expected_volatility=port_vol,
484
507
  sharpe_ratio=sharpe,
485
- optimization_method="kelly_criterion_regularized"
508
+ optimization_method="kelly_criterion_regularized",
486
509
  )
487
510
 
488
511
 
@@ -498,16 +521,18 @@ class AdvancedPortfolioOptimizer:
498
521
  OptimizationObjective.RISK_PARITY: RiskParityOptimizer(self.constraints),
499
522
  OptimizationObjective.BLACK_LITTERMAN: BlackLittermanOptimizer(self.constraints),
500
523
  OptimizationObjective.CVaR: CVaROptimizer(self.constraints),
501
- OptimizationObjective.KELLY_CRITERION: KellyCriterionOptimizer(self.constraints)
524
+ OptimizationObjective.KELLY_CRITERION: KellyCriterionOptimizer(self.constraints),
502
525
  }
503
526
 
504
527
  self.optimization_history = []
505
528
 
506
- def optimize_portfolio(self,
507
- expected_returns: pd.Series,
508
- covariance_matrix: pd.DataFrame,
509
- objective: OptimizationObjective = OptimizationObjective.MEAN_VARIANCE,
510
- **optimizer_kwargs) -> PortfolioAllocation:
529
+ def optimize_portfolio(
530
+ self,
531
+ expected_returns: pd.Series,
532
+ covariance_matrix: pd.DataFrame,
533
+ objective: OptimizationObjective = OptimizationObjective.MEAN_VARIANCE,
534
+ **optimizer_kwargs,
535
+ ) -> PortfolioAllocation:
511
536
  """Optimize portfolio using specified objective"""
512
537
 
513
538
  if objective not in self.optimizers:
@@ -517,18 +542,22 @@ class AdvancedPortfolioOptimizer:
517
542
  allocation = optimizer.optimize(expected_returns, covariance_matrix, **optimizer_kwargs)
518
543
 
519
544
  # Add additional metrics
520
- allocation = self._enhance_allocation_metrics(allocation, expected_returns, covariance_matrix)
545
+ allocation = self._enhance_allocation_metrics(
546
+ allocation, expected_returns, covariance_matrix
547
+ )
521
548
 
522
549
  # Store in history
523
550
  self.optimization_history.append(allocation)
524
551
 
525
552
  return allocation
526
553
 
527
- def multi_objective_optimization(self,
528
- expected_returns: pd.Series,
529
- covariance_matrix: pd.DataFrame,
530
- objectives: List[OptimizationObjective],
531
- weights: Optional[List[float]] = None) -> PortfolioAllocation:
554
+ def multi_objective_optimization(
555
+ self,
556
+ expected_returns: pd.Series,
557
+ covariance_matrix: pd.DataFrame,
558
+ objectives: List[OptimizationObjective],
559
+ weights: Optional[List[float]] = None,
560
+ ) -> PortfolioAllocation:
532
561
  """Combine multiple optimization objectives"""
533
562
 
534
563
  if weights is None:
@@ -573,19 +602,19 @@ class AdvancedPortfolioOptimizer:
573
602
  var_95=var_95,
574
603
  cvar_95=cvar_95,
575
604
  concentration=np.sum(np.square(weights_array)),
576
- optimization_method=f"multi_objective_{'+'.join([obj.value for obj in objectives])}"
605
+ optimization_method=f"multi_objective_{'+'.join([obj.value for obj in objectives])}",
577
606
  )
578
607
 
579
- def efficient_frontier(self,
580
- expected_returns: pd.Series,
581
- covariance_matrix: pd.DataFrame,
582
- n_points: int = 20) -> pd.DataFrame:
608
+ def efficient_frontier(
609
+ self, expected_returns: pd.Series, covariance_matrix: pd.DataFrame, n_points: int = 20
610
+ ) -> pd.DataFrame:
583
611
  """Generate efficient frontier"""
584
612
 
585
613
  min_vol_allocation = self.optimize_portfolio(
586
- expected_returns, covariance_matrix,
614
+ expected_returns,
615
+ covariance_matrix,
587
616
  OptimizationObjective.MEAN_VARIANCE,
588
- risk_aversion=1000 # High risk aversion for min vol
617
+ risk_aversion=1000, # High risk aversion for min vol
589
618
  )
590
619
 
591
620
  max_return = expected_returns.max()
@@ -609,7 +638,7 @@ class AdvancedPortfolioOptimizer:
609
638
  cp.sum(w) == 1,
610
639
  portfolio_return >= target_return,
611
640
  w >= self.constraints.min_weight,
612
- w <= self.constraints.max_weight
641
+ w <= self.constraints.max_weight,
613
642
  ]
614
643
 
615
644
  if not self.constraints.allow_short:
@@ -621,25 +650,32 @@ class AdvancedPortfolioOptimizer:
621
650
  if problem.status not in ["infeasible", "unbounded"]:
622
651
  optimal_weights = w.value
623
652
  port_return = np.dot(optimal_weights, expected_returns)
624
- port_vol = np.sqrt(np.dot(optimal_weights.T,
625
- np.dot(covariance_matrix.values, optimal_weights)))
626
-
627
- frontier_data.append({
628
- 'return': port_return,
629
- 'volatility': port_vol,
630
- 'sharpe': (port_return - 0.02) / port_vol if port_vol > 0 else 0
631
- })
653
+ port_vol = np.sqrt(
654
+ np.dot(optimal_weights.T, np.dot(covariance_matrix.values, optimal_weights))
655
+ )
656
+
657
+ frontier_data.append(
658
+ {
659
+ "return": port_return,
660
+ "volatility": port_vol,
661
+ "sharpe": (port_return - 0.02) / port_vol if port_vol > 0 else 0,
662
+ }
663
+ )
632
664
 
633
665
  except Exception as e:
634
- logger.warning(f"Failed to compute efficient frontier point for return {target_return}: {e}")
666
+ logger.warning(
667
+ f"Failed to compute efficient frontier point for return {target_return}: {e}"
668
+ )
635
669
  continue
636
670
 
637
671
  return pd.DataFrame(frontier_data)
638
672
 
639
- def rebalance_portfolio(self,
640
- current_weights: Dict[str, float],
641
- target_allocation: PortfolioAllocation,
642
- rebalance_threshold: float = 0.05) -> Dict[str, Any]:
673
+ def rebalance_portfolio(
674
+ self,
675
+ current_weights: Dict[str, float],
676
+ target_allocation: PortfolioAllocation,
677
+ rebalance_threshold: float = 0.05,
678
+ ) -> Dict[str, Any]:
643
679
  """Calculate rebalancing trades"""
644
680
 
645
681
  trades = {}
@@ -655,24 +691,29 @@ class AdvancedPortfolioOptimizer:
655
691
  if deviation > rebalance_threshold:
656
692
  trades[asset] = target_weight - current_weight
657
693
 
658
- transaction_cost = sum(abs(trade) * self.constraints.transaction_costs
659
- for trade in trades.values())
694
+ transaction_cost = sum(
695
+ abs(trade) * self.constraints.transaction_costs for trade in trades.values()
696
+ )
660
697
 
661
698
  return {
662
- 'trades': trades,
663
- 'total_deviation': total_deviation,
664
- 'transaction_cost': transaction_cost,
665
- 'rebalance_needed': total_deviation > rebalance_threshold,
666
- 'net_trades': sum(trades.values()) # Should be close to 0
699
+ "trades": trades,
700
+ "total_deviation": total_deviation,
701
+ "transaction_cost": transaction_cost,
702
+ "rebalance_needed": total_deviation > rebalance_threshold,
703
+ "net_trades": sum(trades.values()), # Should be close to 0
667
704
  }
668
705
 
669
- def _enhance_allocation_metrics(self,
670
- allocation: PortfolioAllocation,
671
- expected_returns: pd.Series,
672
- covariance_matrix: pd.DataFrame) -> PortfolioAllocation:
706
+ def _enhance_allocation_metrics(
707
+ self,
708
+ allocation: PortfolioAllocation,
709
+ expected_returns: pd.Series,
710
+ covariance_matrix: pd.DataFrame,
711
+ ) -> PortfolioAllocation:
673
712
  """Add additional metrics to allocation"""
674
713
 
675
- weights_array = np.array([allocation.weights.get(asset, 0) for asset in expected_returns.index])
714
+ weights_array = np.array(
715
+ [allocation.weights.get(asset, 0) for asset in expected_returns.index]
716
+ )
676
717
 
677
718
  # Calculate max drawdown (simplified)
678
719
  returns_series = expected_returns.values
@@ -683,8 +724,9 @@ class AdvancedPortfolioOptimizer:
683
724
 
684
725
  return allocation
685
726
 
686
- def plot_allocation(self, allocation: PortfolioAllocation,
687
- save_path: Optional[Path] = None) -> None:
727
+ def plot_allocation(
728
+ self, allocation: PortfolioAllocation, save_path: Optional[Path] = None
729
+ ) -> None:
688
730
  """Plot portfolio allocation"""
689
731
 
690
732
  # Filter out zero weights
@@ -701,35 +743,35 @@ class AdvancedPortfolioOptimizer:
701
743
  assets = list(non_zero_weights.keys())
702
744
  weights = list(non_zero_weights.values())
703
745
 
704
- plt.pie(weights, labels=assets, autopct='%1.1f%%', startangle=90)
705
- plt.title('Portfolio Allocation')
746
+ plt.pie(weights, labels=assets, autopct="%1.1f%%", startangle=90)
747
+ plt.title("Portfolio Allocation")
706
748
 
707
749
  # Bar chart of weights
708
750
  plt.subplot(2, 2, 2)
709
751
  plt.bar(range(len(assets)), weights)
710
752
  plt.xticks(range(len(assets)), assets, rotation=45)
711
- plt.ylabel('Weight')
712
- plt.title('Asset Weights')
753
+ plt.ylabel("Weight")
754
+ plt.title("Asset Weights")
713
755
 
714
756
  # Risk metrics
715
757
  plt.subplot(2, 2, 3)
716
- metrics = ['Expected Return', 'Volatility', 'Sharpe Ratio', 'VaR 95%', 'CVaR 95%']
758
+ metrics = ["Expected Return", "Volatility", "Sharpe Ratio", "VaR 95%", "CVaR 95%"]
717
759
  values = [
718
760
  allocation.expected_return * 100,
719
761
  allocation.expected_volatility * 100,
720
762
  allocation.sharpe_ratio,
721
763
  allocation.var_95 * 100 if allocation.var_95 else 0,
722
- allocation.cvar_95 * 100 if allocation.cvar_95 else 0
764
+ allocation.cvar_95 * 100 if allocation.cvar_95 else 0,
723
765
  ]
724
766
 
725
767
  plt.bar(metrics, values)
726
768
  plt.xticks(rotation=45)
727
- plt.ylabel('Value (%)')
728
- plt.title('Portfolio Metrics')
769
+ plt.ylabel("Value (%)")
770
+ plt.title("Portfolio Metrics")
729
771
 
730
772
  # Summary text
731
773
  plt.subplot(2, 2, 4)
732
- plt.axis('off')
774
+ plt.axis("off")
733
775
  summary_text = f"""
734
776
  Optimization Method: {allocation.optimization_method}
735
777
  Expected Return: {allocation.expected_return:.3f}
@@ -738,12 +780,12 @@ class AdvancedPortfolioOptimizer:
738
780
  Concentration: {allocation.concentration:.3f}
739
781
  Number of Assets: {len(non_zero_weights)}
740
782
  """
741
- plt.text(0.1, 0.5, summary_text, fontsize=10, verticalalignment='center')
783
+ plt.text(0.1, 0.5, summary_text, fontsize=10, verticalalignment="center")
742
784
 
743
785
  plt.tight_layout()
744
786
 
745
787
  if save_path:
746
- plt.savefig(save_path, dpi=300, bbox_inches='tight')
788
+ plt.savefig(save_path, dpi=300, bbox_inches="tight")
747
789
 
748
790
  plt.show()
749
791
 
@@ -753,14 +795,11 @@ if __name__ == "__main__":
753
795
  # Generate sample data
754
796
  np.random.seed(42)
755
797
 
756
- assets = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
798
+ assets = ["AAPL", "MSFT", "GOOGL", "AMZN", "TSLA"]
757
799
  n_assets = len(assets)
758
800
 
759
801
  # Expected returns (annual)
760
- expected_returns = pd.Series(
761
- np.random.uniform(0.05, 0.15, n_assets),
762
- index=assets
763
- )
802
+ expected_returns = pd.Series(np.random.uniform(0.05, 0.15, n_assets), index=assets)
764
803
 
765
804
  # Generate covariance matrix
766
805
  correlation_matrix = np.random.uniform(0.1, 0.7, (n_assets, n_assets))
@@ -773,9 +812,7 @@ if __name__ == "__main__":
773
812
 
774
813
  # Initialize optimizer
775
814
  constraints = OptimizationConstraints(
776
- max_weight=0.4,
777
- transaction_costs=0.001,
778
- allow_short=False
815
+ max_weight=0.4, transaction_costs=0.001, allow_short=False
779
816
  )
780
817
 
781
818
  optimizer = AdvancedPortfolioOptimizer(constraints)
@@ -784,7 +821,7 @@ if __name__ == "__main__":
784
821
  objectives = [
785
822
  OptimizationObjective.MEAN_VARIANCE,
786
823
  OptimizationObjective.RISK_PARITY,
787
- OptimizationObjective.KELLY_CRITERION
824
+ OptimizationObjective.KELLY_CRITERION,
788
825
  ]
789
826
 
790
827
  results = {}
@@ -799,7 +836,9 @@ if __name__ == "__main__":
799
836
  print(f"Volatility: {allocation.expected_volatility:.3f}")
800
837
  print(f"Sharpe Ratio: {allocation.sharpe_ratio:.3f}")
801
838
  print("Top 3 Holdings:")
802
- sorted_weights = sorted(allocation.weights.items(), key=lambda x: abs(x[1]), reverse=True)
839
+ sorted_weights = sorted(
840
+ allocation.weights.items(), key=lambda x: abs(x[1]), reverse=True
841
+ )
803
842
  for asset, weight in sorted_weights[:3]:
804
843
  print(f" {asset}: {weight:.3f}")
805
844
 
@@ -809,9 +848,10 @@ if __name__ == "__main__":
809
848
  # Test multi-objective optimization
810
849
  try:
811
850
  multi_obj_allocation = optimizer.multi_objective_optimization(
812
- expected_returns, covariance_matrix,
851
+ expected_returns,
852
+ covariance_matrix,
813
853
  objectives=[OptimizationObjective.MEAN_VARIANCE, OptimizationObjective.RISK_PARITY],
814
- weights=[0.7, 0.3]
854
+ weights=[0.7, 0.3],
815
855
  )
816
856
 
817
857
  print(f"\nMULTI-OBJECTIVE Optimization:")
@@ -831,4 +871,4 @@ if __name__ == "__main__":
831
871
  except Exception as e:
832
872
  logger.error(f"Efficient frontier generation failed: {e}")
833
873
 
834
- logger.info("Advanced portfolio optimization demo completed")
874
+ logger.info("Advanced portfolio optimization demo completed")