mcli-framework 7.0.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.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,834 @@
1
+ """Advanced portfolio optimization for stock recommendations"""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from typing import Dict, List, Tuple, Optional, Union, Any
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime, timedelta
8
+ from pathlib import Path
9
+ import logging
10
+ from enum import Enum
11
+ from abc import ABC, abstractmethod
12
+
13
+ # Optimization libraries
14
+ import cvxpy as cp
15
+ from scipy.optimize import minimize
16
+ from scipy.stats import norm
17
+ import matplotlib.pyplot as plt
18
+ import seaborn as sns
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class OptimizationObjective(Enum):
24
+ """Portfolio optimization objectives"""
25
+ MEAN_VARIANCE = "mean_variance"
26
+ RISK_PARITY = "risk_parity"
27
+ MINIMUM_VARIANCE = "minimum_variance"
28
+ MAXIMUM_SHARPE = "maximum_sharpe"
29
+ BLACK_LITTERMAN = "black_litterman"
30
+ FACTOR_MODEL = "factor_model"
31
+ CVaR = "conditional_value_at_risk"
32
+ KELLY_CRITERION = "kelly_criterion"
33
+
34
+
35
+ @dataclass
36
+ class OptimizationConstraints:
37
+ """Portfolio optimization constraints"""
38
+ # Weight constraints
39
+ min_weight: float = 0.0
40
+ max_weight: float = 1.0
41
+ sum_weights: float = 1.0
42
+
43
+ # Sector/factor constraints
44
+ max_sector_weight: Optional[Dict[str, float]] = None
45
+ max_factor_exposure: Optional[Dict[str, float]] = None
46
+
47
+ # Risk constraints
48
+ max_volatility: Optional[float] = None
49
+ max_var: Optional[float] = None
50
+ max_cvar: Optional[float] = None
51
+
52
+ # Transaction costs
53
+ transaction_costs: float = 0.001
54
+ max_turnover: Optional[float] = None
55
+
56
+ # Long/short constraints
57
+ allow_short: bool = False
58
+ gross_leverage: Optional[float] = None
59
+
60
+ # Cardinality constraints
61
+ min_assets: Optional[int] = None
62
+ max_assets: Optional[int] = None
63
+
64
+
65
+ @dataclass
66
+ class PortfolioAllocation:
67
+ """Portfolio allocation result"""
68
+ weights: Dict[str, float]
69
+ expected_return: float
70
+ expected_volatility: float
71
+ sharpe_ratio: float
72
+
73
+ # Risk metrics
74
+ var_95: Optional[float] = None
75
+ cvar_95: Optional[float] = None
76
+ max_drawdown: Optional[float] = None
77
+
78
+ # Portfolio characteristics
79
+ concentration: float = 0.0
80
+ turnover: float = 0.0
81
+ transaction_cost: float = 0.0
82
+
83
+ # Factor exposures
84
+ factor_exposures: Dict[str, float] = field(default_factory=dict)
85
+ sector_allocations: Dict[str, float] = field(default_factory=dict)
86
+
87
+ # Metadata
88
+ optimization_method: str = ""
89
+ timestamp: datetime = field(default_factory=datetime.now)
90
+
91
+
92
+ class BaseOptimizer(ABC):
93
+ """Base class for portfolio optimizers"""
94
+
95
+ def __init__(self, constraints: OptimizationConstraints):
96
+ self.constraints = constraints
97
+
98
+ @abstractmethod
99
+ def optimize(self, expected_returns: pd.Series,
100
+ covariance_matrix: pd.DataFrame,
101
+ **kwargs) -> PortfolioAllocation:
102
+ """Optimize portfolio allocation"""
103
+ pass
104
+
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]:
109
+ """Calculate portfolio return, volatility, and Sharpe ratio"""
110
+ portfolio_return = np.dot(weights, expected_returns)
111
+ portfolio_variance = np.dot(weights.T, np.dot(covariance_matrix, weights))
112
+ portfolio_volatility = np.sqrt(portfolio_variance)
113
+
114
+ sharpe_ratio = (portfolio_return - risk_free_rate) / portfolio_volatility if portfolio_volatility > 0 else 0
115
+
116
+ return portfolio_return, portfolio_volatility, sharpe_ratio
117
+
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]:
122
+ """Calculate Value at Risk and Conditional Value at Risk"""
123
+ portfolio_return = np.dot(weights, expected_returns)
124
+ portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(covariance_matrix, weights)))
125
+
126
+ # Assuming normal distribution
127
+ z_score = norm.ppf(1 - confidence_level)
128
+ var = portfolio_return + z_score * portfolio_volatility
129
+
130
+ # CVaR calculation
131
+ cvar = portfolio_return - portfolio_volatility * norm.pdf(z_score) / (1 - confidence_level)
132
+
133
+ return var, cvar
134
+
135
+
136
+ class MeanVarianceOptimizer(BaseOptimizer):
137
+ """Modern Portfolio Theory mean-variance optimizer"""
138
+
139
+ def optimize(self, expected_returns: pd.Series,
140
+ covariance_matrix: pd.DataFrame,
141
+ risk_aversion: float = 1.0,
142
+ **kwargs) -> PortfolioAllocation:
143
+ """Optimize using mean-variance framework"""
144
+ n_assets = len(expected_returns)
145
+
146
+ # Decision variable: portfolio weights
147
+ w = cp.Variable(n_assets)
148
+
149
+ # Objective: maximize utility (return - risk_aversion * variance)
150
+ portfolio_return = expected_returns.values.T @ w
151
+ portfolio_variance = cp.quad_form(w, covariance_matrix.values)
152
+ objective = cp.Maximize(portfolio_return - 0.5 * risk_aversion * portfolio_variance)
153
+
154
+ # Constraints
155
+ constraints = [
156
+ cp.sum(w) == self.constraints.sum_weights, # Weights sum to 1
157
+ w >= self.constraints.min_weight, # Min weight
158
+ w <= self.constraints.max_weight # Max weight
159
+ ]
160
+
161
+ # Additional constraints
162
+ if not self.constraints.allow_short:
163
+ constraints.append(w >= 0)
164
+
165
+ if self.constraints.max_volatility:
166
+ constraints.append(cp.sqrt(portfolio_variance) <= self.constraints.max_volatility)
167
+
168
+ # Solve optimization problem
169
+ problem = cp.Problem(objective, constraints)
170
+ problem.solve()
171
+
172
+ if problem.status not in ["infeasible", "unbounded"]:
173
+ optimal_weights = w.value
174
+ weights_dict = dict(zip(expected_returns.index, optimal_weights))
175
+
176
+ # Calculate metrics
177
+ port_return, port_vol, sharpe = self._calculate_portfolio_metrics(
178
+ optimal_weights, expected_returns, covariance_matrix
179
+ )
180
+
181
+ var_95, cvar_95 = self._calculate_var_cvar(
182
+ optimal_weights, expected_returns, covariance_matrix
183
+ )
184
+
185
+ return PortfolioAllocation(
186
+ weights=weights_dict,
187
+ expected_return=port_return,
188
+ expected_volatility=port_vol,
189
+ sharpe_ratio=sharpe,
190
+ var_95=var_95,
191
+ cvar_95=cvar_95,
192
+ concentration=np.sum(np.square(optimal_weights)), # Herfindahl index
193
+ optimization_method="mean_variance"
194
+ )
195
+ else:
196
+ raise ValueError(f"Optimization failed with status: {problem.status}")
197
+
198
+
199
+ class RiskParityOptimizer(BaseOptimizer):
200
+ """Risk parity portfolio optimizer"""
201
+
202
+ def optimize(self, expected_returns: pd.Series,
203
+ covariance_matrix: pd.DataFrame,
204
+ **kwargs) -> PortfolioAllocation:
205
+ """Optimize using risk parity approach"""
206
+
207
+ def risk_parity_objective(weights, cov_matrix):
208
+ """Risk parity objective function"""
209
+ portfolio_vol = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights)))
210
+ marginal_contrib = np.dot(cov_matrix, weights) / portfolio_vol
211
+ contrib = weights * marginal_contrib
212
+ target_contrib = np.ones(len(weights)) / len(weights)
213
+
214
+ return np.sum((contrib / np.sum(contrib) - target_contrib) ** 2)
215
+
216
+ # Initial guess: equal weights
217
+ n_assets = len(expected_returns)
218
+ initial_weights = np.ones(n_assets) / n_assets
219
+
220
+ # Constraints
221
+ constraints = [
222
+ {'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0} # Weights sum to 1
223
+ ]
224
+
225
+ # Bounds
226
+ bounds = [(self.constraints.min_weight, self.constraints.max_weight)
227
+ for _ in range(n_assets)]
228
+
229
+ if not self.constraints.allow_short:
230
+ bounds = [(0, self.constraints.max_weight) for _ in range(n_assets)]
231
+
232
+ # Optimize
233
+ result = minimize(
234
+ risk_parity_objective,
235
+ initial_weights,
236
+ args=(covariance_matrix.values,),
237
+ method='SLSQP',
238
+ bounds=bounds,
239
+ constraints=constraints
240
+ )
241
+
242
+ if result.success:
243
+ optimal_weights = result.x
244
+ weights_dict = dict(zip(expected_returns.index, optimal_weights))
245
+
246
+ # Calculate metrics
247
+ port_return, port_vol, sharpe = self._calculate_portfolio_metrics(
248
+ optimal_weights, expected_returns, covariance_matrix
249
+ )
250
+
251
+ var_95, cvar_95 = self._calculate_var_cvar(
252
+ optimal_weights, expected_returns, covariance_matrix
253
+ )
254
+
255
+ return PortfolioAllocation(
256
+ weights=weights_dict,
257
+ expected_return=port_return,
258
+ expected_volatility=port_vol,
259
+ sharpe_ratio=sharpe,
260
+ var_95=var_95,
261
+ cvar_95=cvar_95,
262
+ concentration=np.sum(np.square(optimal_weights)),
263
+ optimization_method="risk_parity"
264
+ )
265
+ else:
266
+ raise ValueError(f"Risk parity optimization failed: {result.message}")
267
+
268
+
269
+ class BlackLittermanOptimizer(BaseOptimizer):
270
+ """Black-Litterman portfolio optimizer"""
271
+
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:
280
+ """Optimize using Black-Litterman model"""
281
+
282
+ # Market capitalization weights (if not provided, use equal weights)
283
+ if market_caps is None:
284
+ market_weights = np.ones(len(expected_returns)) / len(expected_returns)
285
+ else:
286
+ market_weights = (market_caps / market_caps.sum()).values
287
+
288
+ # Implied equilibrium returns
289
+ implied_returns = risk_aversion * np.dot(covariance_matrix.values, market_weights)
290
+
291
+ # If no views provided, use implied returns
292
+ if views is None or len(views) == 0:
293
+ bl_returns = pd.Series(implied_returns, index=expected_returns.index)
294
+ bl_cov = covariance_matrix
295
+ else:
296
+ # Black-Litterman adjustment with views
297
+ P = np.zeros((len(views), len(expected_returns))) # Picking matrix
298
+ Q = np.zeros(len(views)) # View returns
299
+ Omega = np.zeros((len(views), len(views))) # View uncertainty matrix
300
+
301
+ for i, (asset, view_return) in enumerate(views.items()):
302
+ asset_idx = expected_returns.index.get_loc(asset)
303
+ P[i, asset_idx] = 1.0
304
+ Q[i] = view_return
305
+
306
+ # View uncertainty (if not provided, use default)
307
+ if view_uncertainties and asset in view_uncertainties:
308
+ Omega[i, i] = view_uncertainties[asset]
309
+ else:
310
+ Omega[i, i] = tau * covariance_matrix.iloc[asset_idx, asset_idx]
311
+
312
+ # Black-Litterman formulas
313
+ tau_cov = tau * covariance_matrix.values
314
+
315
+ # New expected returns
316
+ M1 = np.linalg.inv(tau_cov)
317
+ M2 = np.dot(P.T, np.dot(np.linalg.inv(Omega), P))
318
+ M3 = np.dot(np.linalg.inv(tau_cov), implied_returns)
319
+ M4 = np.dot(P.T, np.dot(np.linalg.inv(Omega), Q))
320
+
321
+ bl_returns = np.dot(np.linalg.inv(M1 + M2), M3 + M4)
322
+ bl_returns = pd.Series(bl_returns, index=expected_returns.index)
323
+
324
+ # New covariance matrix
325
+ bl_cov = np.linalg.inv(M1 + M2)
326
+ bl_cov = pd.DataFrame(bl_cov, index=covariance_matrix.index, columns=covariance_matrix.columns)
327
+
328
+ # Now optimize using mean-variance with BL inputs
329
+ mv_optimizer = MeanVarianceOptimizer(self.constraints)
330
+ allocation = mv_optimizer.optimize(bl_returns, bl_cov, risk_aversion)
331
+ allocation.optimization_method = "black_litterman"
332
+
333
+ return allocation
334
+
335
+
336
+ class CVaROptimizer(BaseOptimizer):
337
+ """Conditional Value at Risk optimizer"""
338
+
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:
344
+ """Optimize portfolio to minimize CVaR"""
345
+
346
+ if scenarios is None:
347
+ # Generate scenarios from normal distribution
348
+ n_scenarios = 10000
349
+ mean_returns = expected_returns.values
350
+ cov_matrix = covariance_matrix.values
351
+
352
+ scenarios = np.random.multivariate_normal(mean_returns, cov_matrix, n_scenarios)
353
+ scenarios = pd.DataFrame(scenarios, columns=expected_returns.index)
354
+
355
+ n_assets = len(expected_returns)
356
+ n_scenarios = len(scenarios)
357
+
358
+ # Decision variables
359
+ w = cp.Variable(n_assets) # Portfolio weights
360
+ alpha = cp.Variable() # VaR
361
+ u = cp.Variable(n_scenarios, nonneg=True) # Auxiliary variables for CVaR
362
+
363
+ # Portfolio returns for each scenario
364
+ portfolio_returns = scenarios.values @ w
365
+
366
+ # CVaR objective
367
+ cvar = alpha - cp.sum(u) / (n_scenarios * (1 - confidence_level))
368
+ objective = cp.Maximize(cvar)
369
+
370
+ # Constraints
371
+ constraints = [
372
+ cp.sum(w) == 1, # Weights sum to 1
373
+ w >= self.constraints.min_weight,
374
+ w <= self.constraints.max_weight,
375
+ u >= 0,
376
+ u >= alpha - portfolio_returns # CVaR constraints
377
+ ]
378
+
379
+ if not self.constraints.allow_short:
380
+ constraints.append(w >= 0)
381
+
382
+ # Solve
383
+ problem = cp.Problem(objective, constraints)
384
+ problem.solve()
385
+
386
+ if problem.status not in ["infeasible", "unbounded"]:
387
+ optimal_weights = w.value
388
+ weights_dict = dict(zip(expected_returns.index, optimal_weights))
389
+
390
+ # Calculate metrics
391
+ port_return, port_vol, sharpe = self._calculate_portfolio_metrics(
392
+ optimal_weights, expected_returns, covariance_matrix
393
+ )
394
+
395
+ var_95, cvar_95 = self._calculate_var_cvar(
396
+ optimal_weights, expected_returns, covariance_matrix
397
+ )
398
+
399
+ return PortfolioAllocation(
400
+ weights=weights_dict,
401
+ expected_return=port_return,
402
+ expected_volatility=port_vol,
403
+ sharpe_ratio=sharpe,
404
+ var_95=var_95,
405
+ cvar_95=cvar_95,
406
+ concentration=np.sum(np.square(optimal_weights)),
407
+ optimization_method="cvar"
408
+ )
409
+ else:
410
+ raise ValueError(f"CVaR optimization failed with status: {problem.status}")
411
+
412
+
413
+ class KellyCriterionOptimizer(BaseOptimizer):
414
+ """Kelly Criterion optimizer for growth-optimal portfolios"""
415
+
416
+ def optimize(self, expected_returns: pd.Series,
417
+ covariance_matrix: pd.DataFrame,
418
+ **kwargs) -> PortfolioAllocation:
419
+ """Optimize using Kelly Criterion"""
420
+
421
+ # Kelly optimal weights: w* = Σ^(-1) * μ
422
+ # where μ is expected excess returns and Σ is covariance matrix
423
+
424
+ try:
425
+ inv_cov = np.linalg.inv(covariance_matrix.values)
426
+ kelly_weights = np.dot(inv_cov, expected_returns.values)
427
+
428
+ # Apply constraints
429
+ if not self.constraints.allow_short:
430
+ kelly_weights = np.maximum(kelly_weights, 0)
431
+
432
+ kelly_weights = np.clip(kelly_weights,
433
+ self.constraints.min_weight,
434
+ self.constraints.max_weight)
435
+
436
+ # Normalize to sum to 1
437
+ if np.sum(kelly_weights) > 0:
438
+ kelly_weights = kelly_weights / np.sum(kelly_weights)
439
+ else:
440
+ # Fall back to equal weights
441
+ kelly_weights = np.ones(len(expected_returns)) / len(expected_returns)
442
+
443
+ weights_dict = dict(zip(expected_returns.index, kelly_weights))
444
+
445
+ # Calculate metrics
446
+ port_return, port_vol, sharpe = self._calculate_portfolio_metrics(
447
+ kelly_weights, expected_returns, covariance_matrix
448
+ )
449
+
450
+ var_95, cvar_95 = self._calculate_var_cvar(
451
+ kelly_weights, expected_returns, covariance_matrix
452
+ )
453
+
454
+ return PortfolioAllocation(
455
+ weights=weights_dict,
456
+ expected_return=port_return,
457
+ expected_volatility=port_vol,
458
+ sharpe_ratio=sharpe,
459
+ var_95=var_95,
460
+ cvar_95=cvar_95,
461
+ concentration=np.sum(np.square(kelly_weights)),
462
+ optimization_method="kelly_criterion"
463
+ )
464
+
465
+ except np.linalg.LinAlgError:
466
+ # Covariance matrix is singular, use regularization
467
+ regularization = 1e-8 * np.eye(len(expected_returns))
468
+ regularized_cov = covariance_matrix.values + regularization
469
+
470
+ inv_cov = np.linalg.inv(regularized_cov)
471
+ kelly_weights = np.dot(inv_cov, expected_returns.values)
472
+ kelly_weights = kelly_weights / np.sum(np.abs(kelly_weights))
473
+
474
+ weights_dict = dict(zip(expected_returns.index, kelly_weights))
475
+
476
+ port_return, port_vol, sharpe = self._calculate_portfolio_metrics(
477
+ kelly_weights, expected_returns, covariance_matrix
478
+ )
479
+
480
+ return PortfolioAllocation(
481
+ weights=weights_dict,
482
+ expected_return=port_return,
483
+ expected_volatility=port_vol,
484
+ sharpe_ratio=sharpe,
485
+ optimization_method="kelly_criterion_regularized"
486
+ )
487
+
488
+
489
+ class AdvancedPortfolioOptimizer:
490
+ """Advanced portfolio optimization system"""
491
+
492
+ def __init__(self, constraints: Optional[OptimizationConstraints] = None):
493
+ self.constraints = constraints or OptimizationConstraints()
494
+
495
+ # Initialize optimizers
496
+ self.optimizers = {
497
+ OptimizationObjective.MEAN_VARIANCE: MeanVarianceOptimizer(self.constraints),
498
+ OptimizationObjective.RISK_PARITY: RiskParityOptimizer(self.constraints),
499
+ OptimizationObjective.BLACK_LITTERMAN: BlackLittermanOptimizer(self.constraints),
500
+ OptimizationObjective.CVaR: CVaROptimizer(self.constraints),
501
+ OptimizationObjective.KELLY_CRITERION: KellyCriterionOptimizer(self.constraints)
502
+ }
503
+
504
+ self.optimization_history = []
505
+
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:
511
+ """Optimize portfolio using specified objective"""
512
+
513
+ if objective not in self.optimizers:
514
+ raise ValueError(f"Unsupported optimization objective: {objective}")
515
+
516
+ optimizer = self.optimizers[objective]
517
+ allocation = optimizer.optimize(expected_returns, covariance_matrix, **optimizer_kwargs)
518
+
519
+ # Add additional metrics
520
+ allocation = self._enhance_allocation_metrics(allocation, expected_returns, covariance_matrix)
521
+
522
+ # Store in history
523
+ self.optimization_history.append(allocation)
524
+
525
+ return allocation
526
+
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:
532
+ """Combine multiple optimization objectives"""
533
+
534
+ if weights is None:
535
+ weights = [1.0 / len(objectives)] * len(objectives)
536
+
537
+ if len(weights) != len(objectives):
538
+ raise ValueError("Number of weights must match number of objectives")
539
+
540
+ # Get individual allocations
541
+ allocations = []
542
+ for obj in objectives:
543
+ allocation = self.optimize_portfolio(expected_returns, covariance_matrix, obj)
544
+ allocations.append(allocation)
545
+
546
+ # Combine allocations using weights
547
+ combined_weights = {}
548
+ for asset in expected_returns.index:
549
+ combined_weights[asset] = sum(
550
+ w * alloc.weights.get(asset, 0) for w, alloc in zip(weights, allocations)
551
+ )
552
+
553
+ # Normalize
554
+ total_weight = sum(combined_weights.values())
555
+ if total_weight > 0:
556
+ combined_weights = {k: v / total_weight for k, v in combined_weights.items()}
557
+
558
+ # Calculate metrics for combined portfolio
559
+ weights_array = np.array([combined_weights[asset] for asset in expected_returns.index])
560
+ port_return, port_vol, sharpe = self.optimizers[objectives[0]]._calculate_portfolio_metrics(
561
+ weights_array, expected_returns, covariance_matrix
562
+ )
563
+
564
+ var_95, cvar_95 = self.optimizers[objectives[0]]._calculate_var_cvar(
565
+ weights_array, expected_returns, covariance_matrix
566
+ )
567
+
568
+ return PortfolioAllocation(
569
+ weights=combined_weights,
570
+ expected_return=port_return,
571
+ expected_volatility=port_vol,
572
+ sharpe_ratio=sharpe,
573
+ var_95=var_95,
574
+ cvar_95=cvar_95,
575
+ concentration=np.sum(np.square(weights_array)),
576
+ optimization_method=f"multi_objective_{'+'.join([obj.value for obj in objectives])}"
577
+ )
578
+
579
+ def efficient_frontier(self,
580
+ expected_returns: pd.Series,
581
+ covariance_matrix: pd.DataFrame,
582
+ n_points: int = 20) -> pd.DataFrame:
583
+ """Generate efficient frontier"""
584
+
585
+ min_vol_allocation = self.optimize_portfolio(
586
+ expected_returns, covariance_matrix,
587
+ OptimizationObjective.MEAN_VARIANCE,
588
+ risk_aversion=1000 # High risk aversion for min vol
589
+ )
590
+
591
+ max_return = expected_returns.max()
592
+ min_return = min_vol_allocation.expected_return
593
+
594
+ target_returns = np.linspace(min_return, max_return, n_points)
595
+
596
+ frontier_data = []
597
+
598
+ for target_return in target_returns:
599
+ try:
600
+ # Optimize for minimum variance given target return
601
+ n_assets = len(expected_returns)
602
+ w = cp.Variable(n_assets)
603
+
604
+ portfolio_return = expected_returns.values.T @ w
605
+ portfolio_variance = cp.quad_form(w, covariance_matrix.values)
606
+
607
+ objective = cp.Minimize(portfolio_variance)
608
+ constraints = [
609
+ cp.sum(w) == 1,
610
+ portfolio_return >= target_return,
611
+ w >= self.constraints.min_weight,
612
+ w <= self.constraints.max_weight
613
+ ]
614
+
615
+ if not self.constraints.allow_short:
616
+ constraints.append(w >= 0)
617
+
618
+ problem = cp.Problem(objective, constraints)
619
+ problem.solve()
620
+
621
+ if problem.status not in ["infeasible", "unbounded"]:
622
+ optimal_weights = w.value
623
+ 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
+ })
632
+
633
+ except Exception as e:
634
+ logger.warning(f"Failed to compute efficient frontier point for return {target_return}: {e}")
635
+ continue
636
+
637
+ return pd.DataFrame(frontier_data)
638
+
639
+ def rebalance_portfolio(self,
640
+ current_weights: Dict[str, float],
641
+ target_allocation: PortfolioAllocation,
642
+ rebalance_threshold: float = 0.05) -> Dict[str, Any]:
643
+ """Calculate rebalancing trades"""
644
+
645
+ trades = {}
646
+ total_deviation = 0
647
+
648
+ for asset in target_allocation.weights:
649
+ current_weight = current_weights.get(asset, 0)
650
+ target_weight = target_allocation.weights[asset]
651
+ deviation = abs(target_weight - current_weight)
652
+
653
+ total_deviation += deviation
654
+
655
+ if deviation > rebalance_threshold:
656
+ trades[asset] = target_weight - current_weight
657
+
658
+ transaction_cost = sum(abs(trade) * self.constraints.transaction_costs
659
+ for trade in trades.values())
660
+
661
+ 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
667
+ }
668
+
669
+ def _enhance_allocation_metrics(self,
670
+ allocation: PortfolioAllocation,
671
+ expected_returns: pd.Series,
672
+ covariance_matrix: pd.DataFrame) -> PortfolioAllocation:
673
+ """Add additional metrics to allocation"""
674
+
675
+ weights_array = np.array([allocation.weights.get(asset, 0) for asset in expected_returns.index])
676
+
677
+ # Calculate max drawdown (simplified)
678
+ returns_series = expected_returns.values
679
+ cumulative_returns = np.cumprod(1 + returns_series)
680
+ running_max = np.maximum.accumulate(cumulative_returns)
681
+ drawdowns = (cumulative_returns - running_max) / running_max
682
+ allocation.max_drawdown = np.min(drawdowns) if len(drawdowns) > 0 else 0
683
+
684
+ return allocation
685
+
686
+ def plot_allocation(self, allocation: PortfolioAllocation,
687
+ save_path: Optional[Path] = None) -> None:
688
+ """Plot portfolio allocation"""
689
+
690
+ # Filter out zero weights
691
+ non_zero_weights = {k: v for k, v in allocation.weights.items() if abs(v) > 0.001}
692
+
693
+ if not non_zero_weights:
694
+ logger.warning("No significant weights to plot")
695
+ return
696
+
697
+ plt.figure(figsize=(12, 8))
698
+
699
+ # Pie chart of allocations
700
+ plt.subplot(2, 2, 1)
701
+ assets = list(non_zero_weights.keys())
702
+ weights = list(non_zero_weights.values())
703
+
704
+ plt.pie(weights, labels=assets, autopct='%1.1f%%', startangle=90)
705
+ plt.title('Portfolio Allocation')
706
+
707
+ # Bar chart of weights
708
+ plt.subplot(2, 2, 2)
709
+ plt.bar(range(len(assets)), weights)
710
+ plt.xticks(range(len(assets)), assets, rotation=45)
711
+ plt.ylabel('Weight')
712
+ plt.title('Asset Weights')
713
+
714
+ # Risk metrics
715
+ plt.subplot(2, 2, 3)
716
+ metrics = ['Expected Return', 'Volatility', 'Sharpe Ratio', 'VaR 95%', 'CVaR 95%']
717
+ values = [
718
+ allocation.expected_return * 100,
719
+ allocation.expected_volatility * 100,
720
+ allocation.sharpe_ratio,
721
+ allocation.var_95 * 100 if allocation.var_95 else 0,
722
+ allocation.cvar_95 * 100 if allocation.cvar_95 else 0
723
+ ]
724
+
725
+ plt.bar(metrics, values)
726
+ plt.xticks(rotation=45)
727
+ plt.ylabel('Value (%)')
728
+ plt.title('Portfolio Metrics')
729
+
730
+ # Summary text
731
+ plt.subplot(2, 2, 4)
732
+ plt.axis('off')
733
+ summary_text = f"""
734
+ Optimization Method: {allocation.optimization_method}
735
+ Expected Return: {allocation.expected_return:.3f}
736
+ Volatility: {allocation.expected_volatility:.3f}
737
+ Sharpe Ratio: {allocation.sharpe_ratio:.3f}
738
+ Concentration: {allocation.concentration:.3f}
739
+ Number of Assets: {len(non_zero_weights)}
740
+ """
741
+ plt.text(0.1, 0.5, summary_text, fontsize=10, verticalalignment='center')
742
+
743
+ plt.tight_layout()
744
+
745
+ if save_path:
746
+ plt.savefig(save_path, dpi=300, bbox_inches='tight')
747
+
748
+ plt.show()
749
+
750
+
751
+ # Example usage
752
+ if __name__ == "__main__":
753
+ # Generate sample data
754
+ np.random.seed(42)
755
+
756
+ assets = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA']
757
+ n_assets = len(assets)
758
+
759
+ # Expected returns (annual)
760
+ expected_returns = pd.Series(
761
+ np.random.uniform(0.05, 0.15, n_assets),
762
+ index=assets
763
+ )
764
+
765
+ # Generate covariance matrix
766
+ correlation_matrix = np.random.uniform(0.1, 0.7, (n_assets, n_assets))
767
+ correlation_matrix = (correlation_matrix + correlation_matrix.T) / 2
768
+ np.fill_diagonal(correlation_matrix, 1.0)
769
+
770
+ volatilities = np.random.uniform(0.15, 0.35, n_assets)
771
+ covariance_matrix = np.outer(volatilities, volatilities) * correlation_matrix
772
+ covariance_matrix = pd.DataFrame(covariance_matrix, index=assets, columns=assets)
773
+
774
+ # Initialize optimizer
775
+ constraints = OptimizationConstraints(
776
+ max_weight=0.4,
777
+ transaction_costs=0.001,
778
+ allow_short=False
779
+ )
780
+
781
+ optimizer = AdvancedPortfolioOptimizer(constraints)
782
+
783
+ # Test different optimization methods
784
+ objectives = [
785
+ OptimizationObjective.MEAN_VARIANCE,
786
+ OptimizationObjective.RISK_PARITY,
787
+ OptimizationObjective.KELLY_CRITERION
788
+ ]
789
+
790
+ results = {}
791
+
792
+ for obj in objectives:
793
+ try:
794
+ allocation = optimizer.optimize_portfolio(expected_returns, covariance_matrix, obj)
795
+ results[obj.value] = allocation
796
+
797
+ print(f"\n{obj.value.upper()} Optimization:")
798
+ print(f"Expected Return: {allocation.expected_return:.3f}")
799
+ print(f"Volatility: {allocation.expected_volatility:.3f}")
800
+ print(f"Sharpe Ratio: {allocation.sharpe_ratio:.3f}")
801
+ print("Top 3 Holdings:")
802
+ sorted_weights = sorted(allocation.weights.items(), key=lambda x: abs(x[1]), reverse=True)
803
+ for asset, weight in sorted_weights[:3]:
804
+ print(f" {asset}: {weight:.3f}")
805
+
806
+ except Exception as e:
807
+ logger.error(f"Failed to optimize with {obj.value}: {e}")
808
+
809
+ # Test multi-objective optimization
810
+ try:
811
+ multi_obj_allocation = optimizer.multi_objective_optimization(
812
+ expected_returns, covariance_matrix,
813
+ objectives=[OptimizationObjective.MEAN_VARIANCE, OptimizationObjective.RISK_PARITY],
814
+ weights=[0.7, 0.3]
815
+ )
816
+
817
+ print(f"\nMULTI-OBJECTIVE Optimization:")
818
+ print(f"Expected Return: {multi_obj_allocation.expected_return:.3f}")
819
+ print(f"Volatility: {multi_obj_allocation.expected_volatility:.3f}")
820
+ print(f"Sharpe Ratio: {multi_obj_allocation.sharpe_ratio:.3f}")
821
+
822
+ except Exception as e:
823
+ logger.error(f"Multi-objective optimization failed: {e}")
824
+
825
+ # Generate efficient frontier
826
+ try:
827
+ frontier_df = optimizer.efficient_frontier(expected_returns, covariance_matrix, n_points=10)
828
+ print(f"\nEfficient Frontier generated with {len(frontier_df)} points")
829
+ print(f"Max Sharpe Ratio: {frontier_df['sharpe'].max():.3f}")
830
+
831
+ except Exception as e:
832
+ logger.error(f"Efficient frontier generation failed: {e}")
833
+
834
+ logger.info("Advanced portfolio optimization demo completed")