mcli-framework 7.1.0__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 +46 -13
  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 +69 -58
  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 +283 -152
  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 +235 -0
  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 +38 -18
  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.0.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +2 -2
  90. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -93
  91. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
  92. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
  93. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
  94. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
@@ -1,26 +1,28 @@
1
1
  """Backtesting engine for trading strategies"""
2
2
 
3
- import pandas as pd
4
- import numpy as np
5
- from typing import Dict, Any, Optional, List, Callable, Union, Tuple
3
+ import json
4
+ import logging
5
+ import os
6
+ import sys
6
7
  from dataclasses import dataclass, field
7
8
  from datetime import datetime, timedelta
8
9
  from enum import Enum
9
- import logging
10
10
  from pathlib import Path
11
- import json
11
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
12
+
13
+ import numpy as np
14
+ import pandas as pd
12
15
 
13
- import sys
14
- import os
15
16
  sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
16
17
 
17
- from ml.models.recommendation_models import StockRecommendationModel, PortfolioRecommendation
18
+ from ml.models.recommendation_models import PortfolioRecommendation, StockRecommendationModel
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
21
22
 
22
23
  class OrderType(Enum):
23
24
  """Order types"""
25
+
24
26
  MARKET = "market"
25
27
  LIMIT = "limit"
26
28
  STOP = "stop"
@@ -29,6 +31,7 @@ class OrderType(Enum):
29
31
 
30
32
  class OrderSide(Enum):
31
33
  """Order side"""
34
+
32
35
  BUY = "buy"
33
36
  SELL = "sell"
34
37
 
@@ -36,6 +39,7 @@ class OrderSide(Enum):
36
39
  @dataclass
37
40
  class BacktestConfig:
38
41
  """Backtesting configuration"""
42
+
39
43
  initial_capital: float = 100000.0
40
44
  start_date: Optional[datetime] = None
41
45
  end_date: Optional[datetime] = None
@@ -54,6 +58,7 @@ class BacktestConfig:
54
58
  @dataclass
55
59
  class BacktestResult:
56
60
  """Backtesting results"""
61
+
57
62
  portfolio_value: pd.Series
58
63
  returns: pd.Series
59
64
  positions: pd.DataFrame
@@ -71,9 +76,9 @@ class TradingStrategy:
71
76
  self.current_positions = {}
72
77
  self.pending_orders = []
73
78
 
74
- def generate_signals(self, data: pd.DataFrame,
75
- current_date: datetime,
76
- portfolio_value: float) -> List[Dict[str, Any]]:
79
+ def generate_signals(
80
+ self, data: pd.DataFrame, current_date: datetime, portfolio_value: float
81
+ ) -> List[Dict[str, Any]]:
77
82
  """Generate trading signals"""
78
83
  signals = []
79
84
 
@@ -89,7 +94,7 @@ class TradingStrategy:
89
94
  "position_size": rec.position_size,
90
95
  "entry_price": rec.entry_price,
91
96
  "target_price": rec.target_price,
92
- "stop_loss": rec.stop_loss
97
+ "stop_loss": rec.stop_loss,
93
98
  }
94
99
  signals.append(signal)
95
100
  else:
@@ -98,17 +103,18 @@ class TradingStrategy:
98
103
 
99
104
  return signals
100
105
 
101
- def _get_model_recommendations(self, data: pd.DataFrame,
102
- current_date: datetime) -> List[PortfolioRecommendation]:
106
+ def _get_model_recommendations(
107
+ self, data: pd.DataFrame, current_date: datetime
108
+ ) -> List[PortfolioRecommendation]:
103
109
  """Get recommendations from ML model"""
104
110
  # Filter data up to current date
105
- historical_data = data[data['date'] <= current_date]
111
+ historical_data = data[data["date"] <= current_date]
106
112
 
107
113
  # Extract features (simplified)
108
114
  features = historical_data.select_dtypes(include=[np.number]).values[-1:, :]
109
115
 
110
116
  # Get unique tickers
111
- tickers = historical_data['symbol'].unique()[:5] # Limit to 5 for speed
117
+ tickers = historical_data["symbol"].unique()[:5] # Limit to 5 for speed
112
118
 
113
119
  # Generate recommendations
114
120
  try:
@@ -118,42 +124,42 @@ class TradingStrategy:
118
124
  logger.warning(f"Model prediction failed: {e}")
119
125
  return []
120
126
 
121
- def _momentum_strategy(self, data: pd.DataFrame,
122
- current_date: datetime) -> List[Dict[str, Any]]:
127
+ def _momentum_strategy(
128
+ self, data: pd.DataFrame, current_date: datetime
129
+ ) -> List[Dict[str, Any]]:
123
130
  """Simple momentum strategy"""
124
131
  signals = []
125
132
 
126
133
  # Get recent data
127
- recent_data = data[data['date'] <= current_date].tail(20)
134
+ recent_data = data[data["date"] <= current_date].tail(20)
128
135
 
129
136
  if len(recent_data) < 20:
130
137
  return signals
131
138
 
132
139
  # Calculate momentum for each ticker
133
- for ticker in recent_data['symbol'].unique():
134
- ticker_data = recent_data[recent_data['symbol'] == ticker]
140
+ for ticker in recent_data["symbol"].unique():
141
+ ticker_data = recent_data[recent_data["symbol"] == ticker]
135
142
 
136
143
  if len(ticker_data) < 2:
137
144
  continue
138
145
 
139
146
  # Simple momentum: compare current price to 20-day average
140
- current_price = ticker_data['close'].iloc[-1]
141
- avg_price = ticker_data['close'].mean()
147
+ current_price = ticker_data["close"].iloc[-1]
148
+ avg_price = ticker_data["close"].mean()
142
149
 
143
150
  if current_price > avg_price * 1.05: # 5% above average
144
- signals.append({
145
- "ticker": ticker,
146
- "action": "buy",
147
- "confidence": 0.6,
148
- "position_size": 0.05 # 5% position
149
- })
151
+ signals.append(
152
+ {
153
+ "ticker": ticker,
154
+ "action": "buy",
155
+ "confidence": 0.6,
156
+ "position_size": 0.05, # 5% position
157
+ }
158
+ )
150
159
  elif current_price < avg_price * 0.95: # 5% below average
151
- signals.append({
152
- "ticker": ticker,
153
- "action": "sell",
154
- "confidence": 0.6,
155
- "position_size": 0.05
156
- })
160
+ signals.append(
161
+ {"ticker": ticker, "action": "sell", "confidence": 0.6, "position_size": 0.05}
162
+ )
157
163
 
158
164
  return signals
159
165
 
@@ -167,8 +173,9 @@ class PositionManager:
167
173
  self.cash = config.initial_capital
168
174
  self.portfolio_value = config.initial_capital
169
175
 
170
- def open_position(self, ticker: str, quantity: int, price: float,
171
- date: datetime, signal: Dict[str, Any]):
176
+ def open_position(
177
+ self, ticker: str, quantity: int, price: float, date: datetime, signal: Dict[str, Any]
178
+ ):
172
179
  """Open a new position"""
173
180
  cost = quantity * price * (1 + self.config.commission + self.config.slippage)
174
181
 
@@ -185,7 +192,7 @@ class PositionManager:
185
192
  "stop_loss": signal.get("stop_loss"),
186
193
  "take_profit": signal.get("target_price"),
187
194
  "unrealized_pnl": 0,
188
- "realized_pnl": 0
195
+ "realized_pnl": 0,
189
196
  }
190
197
 
191
198
  logger.debug(f"Opened position: {ticker} - {quantity} shares @ ${price:.2f}")
@@ -271,21 +278,22 @@ class BacktestEngine:
271
278
  """Set trading strategy"""
272
279
  self.strategy = strategy
273
280
 
274
- def run(self, price_data: pd.DataFrame,
275
- trading_data: Optional[pd.DataFrame] = None) -> BacktestResult:
281
+ def run(
282
+ self, price_data: pd.DataFrame, trading_data: Optional[pd.DataFrame] = None
283
+ ) -> BacktestResult:
276
284
  """Run backtest"""
277
285
  logger.info("Starting backtest...")
278
286
 
279
287
  # Prepare data
280
- price_data = price_data.sort_values('date')
288
+ price_data = price_data.sort_values("date")
281
289
 
282
290
  if self.config.start_date:
283
- price_data = price_data[price_data['date'] >= self.config.start_date]
291
+ price_data = price_data[price_data["date"] >= self.config.start_date]
284
292
  if self.config.end_date:
285
- price_data = price_data[price_data['date'] <= self.config.end_date]
293
+ price_data = price_data[price_data["date"] <= self.config.end_date]
286
294
 
287
295
  # Get unique dates
288
- dates = price_data['date'].unique()
296
+ dates = price_data["date"].unique()
289
297
 
290
298
  # Initialize results
291
299
  portfolio_values = []
@@ -301,15 +309,16 @@ class BacktestEngine:
301
309
 
302
310
  # Handle stop loss or take profit triggers
303
311
  if trigger_ticker:
304
- self._execute_exit(trigger_ticker, current_prices[trigger_ticker],
305
- current_date, trigger_type)
312
+ self._execute_exit(
313
+ trigger_ticker, current_prices[trigger_ticker], current_date, trigger_type
314
+ )
306
315
 
307
316
  # Generate signals (e.g., weekly rebalancing)
308
317
  if self._should_rebalance(i, current_date):
309
318
  signals = self.strategy.generate_signals(
310
- price_data[price_data['date'] <= current_date],
319
+ price_data[price_data["date"] <= current_date],
311
320
  current_date,
312
- self.position_manager.get_portfolio_value(current_prices)
321
+ self.position_manager.get_portfolio_value(current_prices),
313
322
  )
314
323
 
315
324
  # Execute signals
@@ -317,12 +326,14 @@ class BacktestEngine:
317
326
 
318
327
  # Record portfolio value
319
328
  portfolio_value = self.position_manager.get_portfolio_value(current_prices)
320
- portfolio_values.append({
321
- "date": current_date,
322
- "value": portfolio_value,
323
- "cash": self.position_manager.cash,
324
- "positions_value": portfolio_value - self.position_manager.cash
325
- })
329
+ portfolio_values.append(
330
+ {
331
+ "date": current_date,
332
+ "value": portfolio_value,
333
+ "cash": self.position_manager.cash,
334
+ "positions_value": portfolio_value - self.position_manager.cash,
335
+ }
336
+ )
326
337
 
327
338
  # Record positions
328
339
  position_snapshot = self._get_position_snapshot(current_date, current_prices)
@@ -330,10 +341,10 @@ class BacktestEngine:
330
341
 
331
342
  # Create results
332
343
  portfolio_df = pd.DataFrame(portfolio_values)
333
- portfolio_df.set_index('date', inplace=True)
344
+ portfolio_df.set_index("date", inplace=True)
334
345
 
335
346
  # Calculate returns
336
- portfolio_df['returns'] = portfolio_df['value'].pct_change().fillna(0)
347
+ portfolio_df["returns"] = portfolio_df["value"].pct_change().fillna(0)
337
348
 
338
349
  # Calculate metrics
339
350
  metrics = self._calculate_metrics(portfolio_df)
@@ -342,24 +353,25 @@ class BacktestEngine:
342
353
  benchmark_returns = self._get_benchmark_returns(price_data, dates)
343
354
 
344
355
  result = BacktestResult(
345
- portfolio_value=portfolio_df['value'],
346
- returns=portfolio_df['returns'],
356
+ portfolio_value=portfolio_df["value"],
357
+ returns=portfolio_df["returns"],
347
358
  positions=pd.DataFrame(daily_positions),
348
359
  trades=pd.DataFrame(self.trades),
349
360
  metrics=metrics,
350
361
  benchmark_returns=benchmark_returns,
351
- strategy_name=self.strategy.__class__.__name__
362
+ strategy_name=self.strategy.__class__.__name__,
352
363
  )
353
364
 
354
365
  logger.info(f"Backtest complete. Total return: {metrics['total_return']:.2%}")
355
366
 
356
367
  return result
357
368
 
358
- def _get_current_prices(self, price_data: pd.DataFrame,
359
- current_date: datetime) -> Dict[str, float]:
369
+ def _get_current_prices(
370
+ self, price_data: pd.DataFrame, current_date: datetime
371
+ ) -> Dict[str, float]:
360
372
  """Get current prices for all tickers"""
361
- current_data = price_data[price_data['date'] == current_date]
362
- return dict(zip(current_data['symbol'], current_data['close']))
373
+ current_data = price_data[price_data["date"] == current_date]
374
+ return dict(zip(current_data["symbol"], current_data["close"]))
363
375
 
364
376
  def _should_rebalance(self, day_index: int, current_date: datetime) -> bool:
365
377
  """Check if should rebalance portfolio"""
@@ -371,9 +383,12 @@ class BacktestEngine:
371
383
  return day_index % 21 == 0
372
384
  return False
373
385
 
374
- def _execute_signals(self, signals: List[Dict[str, Any]],
375
- current_prices: Dict[str, float],
376
- current_date: datetime):
386
+ def _execute_signals(
387
+ self,
388
+ signals: List[Dict[str, Any]],
389
+ current_prices: Dict[str, float],
390
+ current_date: datetime,
391
+ ):
377
392
  """Execute trading signals"""
378
393
  for signal in signals:
379
394
  ticker = signal["ticker"]
@@ -399,52 +414,60 @@ class BacktestEngine:
399
414
  )
400
415
 
401
416
  if success:
402
- self.trades.append({
403
- "date": current_date,
404
- "ticker": ticker,
405
- "action": "buy",
406
- "quantity": quantity,
407
- "price": price,
408
- "value": quantity * price
409
- })
417
+ self.trades.append(
418
+ {
419
+ "date": current_date,
420
+ "ticker": ticker,
421
+ "action": "buy",
422
+ "quantity": quantity,
423
+ "price": price,
424
+ "value": quantity * price,
425
+ }
426
+ )
410
427
 
411
428
  elif signal["action"] == "sell":
412
429
  if ticker in self.position_manager.positions:
413
430
  pnl = self.position_manager.close_position(ticker, price, current_date)
414
431
 
415
- self.trades.append({
416
- "date": current_date,
417
- "ticker": ticker,
418
- "action": "sell",
419
- "quantity": self.position_manager.positions.get(ticker, {}).get("quantity", 0),
420
- "price": price,
421
- "pnl": pnl
422
- })
423
-
424
- def _execute_exit(self, ticker: str, price: float,
425
- current_date: datetime, exit_type: str):
432
+ self.trades.append(
433
+ {
434
+ "date": current_date,
435
+ "ticker": ticker,
436
+ "action": "sell",
437
+ "quantity": self.position_manager.positions.get(ticker, {}).get(
438
+ "quantity", 0
439
+ ),
440
+ "price": price,
441
+ "pnl": pnl,
442
+ }
443
+ )
444
+
445
+ def _execute_exit(self, ticker: str, price: float, current_date: datetime, exit_type: str):
426
446
  """Execute position exit"""
427
447
  if ticker in self.position_manager.positions:
428
448
  position = self.position_manager.positions[ticker]
429
449
  pnl = self.position_manager.close_position(ticker, price, current_date)
430
450
 
431
- self.trades.append({
432
- "date": current_date,
433
- "ticker": ticker,
434
- "action": f"sell_{exit_type}",
435
- "quantity": position["quantity"],
436
- "price": price,
437
- "pnl": pnl
438
- })
439
-
440
- def _get_position_snapshot(self, date: datetime,
441
- current_prices: Dict[str, float]) -> Dict[str, Any]:
451
+ self.trades.append(
452
+ {
453
+ "date": current_date,
454
+ "ticker": ticker,
455
+ "action": f"sell_{exit_type}",
456
+ "quantity": position["quantity"],
457
+ "price": price,
458
+ "pnl": pnl,
459
+ }
460
+ )
461
+
462
+ def _get_position_snapshot(
463
+ self, date: datetime, current_prices: Dict[str, float]
464
+ ) -> Dict[str, Any]:
442
465
  """Get current position snapshot"""
443
466
  snapshot = {
444
467
  "date": date,
445
468
  "num_positions": len(self.position_manager.positions),
446
469
  "cash": self.position_manager.cash,
447
- "portfolio_value": self.position_manager.get_portfolio_value(current_prices)
470
+ "portfolio_value": self.position_manager.get_portfolio_value(current_prices),
448
471
  }
449
472
 
450
473
  # Add position weights
@@ -456,15 +479,17 @@ class BacktestEngine:
456
479
 
457
480
  def _calculate_metrics(self, portfolio_df: pd.DataFrame) -> Dict[str, float]:
458
481
  """Calculate performance metrics"""
459
- returns = portfolio_df['returns']
482
+ returns = portfolio_df["returns"]
460
483
 
461
484
  # Basic metrics
462
- total_return = (portfolio_df['value'].iloc[-1] / portfolio_df['value'].iloc[0]) - 1
485
+ total_return = (portfolio_df["value"].iloc[-1] / portfolio_df["value"].iloc[0]) - 1
463
486
  annualized_return = (1 + total_return) ** (252 / len(returns)) - 1
464
487
 
465
488
  # Risk metrics
466
489
  volatility = returns.std() * np.sqrt(252)
467
- sharpe_ratio = (annualized_return - self.config.risk_free_rate) / volatility if volatility > 0 else 0
490
+ sharpe_ratio = (
491
+ (annualized_return - self.config.risk_free_rate) / volatility if volatility > 0 else 0
492
+ )
468
493
 
469
494
  # Drawdown
470
495
  cumulative = (1 + returns).cumprod()
@@ -485,18 +510,19 @@ class BacktestEngine:
485
510
  "max_drawdown": max_drawdown,
486
511
  "win_rate": win_rate,
487
512
  "total_trades": len(self.trades),
488
- "final_value": portfolio_df['value'].iloc[-1],
489
- "initial_value": self.config.initial_capital
513
+ "final_value": portfolio_df["value"].iloc[-1],
514
+ "initial_value": self.config.initial_capital,
490
515
  }
491
516
 
492
- def _get_benchmark_returns(self, price_data: pd.DataFrame,
493
- dates: np.ndarray) -> Optional[pd.Series]:
517
+ def _get_benchmark_returns(
518
+ self, price_data: pd.DataFrame, dates: np.ndarray
519
+ ) -> Optional[pd.Series]:
494
520
  """Get benchmark returns"""
495
- if self.config.benchmark not in price_data['symbol'].unique():
521
+ if self.config.benchmark not in price_data["symbol"].unique():
496
522
  return None
497
523
 
498
- benchmark_data = price_data[price_data['symbol'] == self.config.benchmark]
499
- benchmark_data = benchmark_data.set_index('date')['close']
524
+ benchmark_data = price_data[price_data["symbol"] == self.config.benchmark]
525
+ benchmark_data = benchmark_data.set_index("date")["close"]
500
526
  benchmark_returns = benchmark_data.pct_change().fillna(0)
501
527
 
502
- return benchmark_returns[benchmark_returns.index.isin(dates)]
528
+ return benchmark_returns[benchmark_returns.index.isin(dates)]