mcli-framework 7.2.0__py3-none-any.whl → 7.3.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 (85) hide show
  1. mcli/__init__.py +160 -0
  2. mcli/__main__.py +14 -0
  3. mcli/app/__init__.py +23 -0
  4. mcli/app/model/__init__.py +0 -0
  5. mcli/app/video/__init__.py +5 -0
  6. mcli/chat/__init__.py +34 -0
  7. mcli/lib/__init__.py +0 -0
  8. mcli/lib/api/__init__.py +0 -0
  9. mcli/lib/auth/__init__.py +1 -0
  10. mcli/lib/config/__init__.py +1 -0
  11. mcli/lib/erd/__init__.py +25 -0
  12. mcli/lib/files/__init__.py +0 -0
  13. mcli/lib/fs/__init__.py +1 -0
  14. mcli/lib/logger/__init__.py +3 -0
  15. mcli/lib/performance/__init__.py +17 -0
  16. mcli/lib/pickles/__init__.py +1 -0
  17. mcli/lib/shell/__init__.py +0 -0
  18. mcli/lib/toml/__init__.py +1 -0
  19. mcli/lib/watcher/__init__.py +0 -0
  20. mcli/ml/__init__.py +16 -0
  21. mcli/ml/api/__init__.py +30 -0
  22. mcli/ml/api/routers/__init__.py +27 -0
  23. mcli/ml/api/schemas.py +2 -2
  24. mcli/ml/auth/__init__.py +45 -0
  25. mcli/ml/auth/models.py +2 -2
  26. mcli/ml/backtesting/__init__.py +39 -0
  27. mcli/ml/cli/__init__.py +5 -0
  28. mcli/ml/cli/main.py +1 -1
  29. mcli/ml/config/__init__.py +33 -0
  30. mcli/ml/configs/__init__.py +16 -0
  31. mcli/ml/dashboard/__init__.py +12 -0
  32. mcli/ml/dashboard/app_integrated.py +23 -6
  33. mcli/ml/dashboard/components/__init__.py +7 -0
  34. mcli/ml/dashboard/pages/__init__.py +6 -0
  35. mcli/ml/dashboard/pages/predictions_enhanced.py +20 -6
  36. mcli/ml/dashboard/pages/test_portfolio.py +373 -0
  37. mcli/ml/dashboard/pages/trading.py +714 -0
  38. mcli/ml/dashboard/utils.py +154 -0
  39. mcli/ml/data_ingestion/__init__.py +39 -0
  40. mcli/ml/database/__init__.py +47 -0
  41. mcli/ml/experimentation/__init__.py +29 -0
  42. mcli/ml/features/__init__.py +39 -0
  43. mcli/ml/mlops/__init__.py +33 -0
  44. mcli/ml/models/__init__.py +94 -0
  45. mcli/ml/monitoring/__init__.py +25 -0
  46. mcli/ml/optimization/__init__.py +27 -0
  47. mcli/ml/predictions/__init__.py +5 -0
  48. mcli/ml/preprocessing/__init__.py +28 -0
  49. mcli/ml/scripts/__init__.py +1 -0
  50. mcli/ml/trading/__init__.py +60 -0
  51. mcli/ml/trading/alpaca_client.py +353 -0
  52. mcli/ml/trading/migrations.py +164 -0
  53. mcli/ml/trading/models.py +418 -0
  54. mcli/ml/trading/paper_trading.py +326 -0
  55. mcli/ml/trading/risk_management.py +370 -0
  56. mcli/ml/trading/trading_service.py +480 -0
  57. mcli/ml/training/__init__.py +10 -0
  58. mcli/mygroup/__init__.py +3 -0
  59. mcli/public/__init__.py +1 -0
  60. mcli/public/commands/__init__.py +2 -0
  61. mcli/self/__init__.py +3 -0
  62. mcli/self/self_cmd.py +260 -0
  63. mcli/workflow/__init__.py +0 -0
  64. mcli/workflow/daemon/__init__.py +15 -0
  65. mcli/workflow/daemon/daemon.py +21 -3
  66. mcli/workflow/dashboard/__init__.py +5 -0
  67. mcli/workflow/docker/__init__.py +0 -0
  68. mcli/workflow/file/__init__.py +0 -0
  69. mcli/workflow/gcloud/__init__.py +1 -0
  70. mcli/workflow/git_commit/__init__.py +0 -0
  71. mcli/workflow/interview/__init__.py +0 -0
  72. mcli/workflow/politician_trading/__init__.py +4 -0
  73. mcli/workflow/registry/__init__.py +0 -0
  74. mcli/workflow/repo/__init__.py +0 -0
  75. mcli/workflow/scheduler/__init__.py +25 -0
  76. mcli/workflow/search/__init__.py +0 -0
  77. mcli/workflow/sync/__init__.py +5 -0
  78. mcli/workflow/videos/__init__.py +1 -0
  79. mcli/workflow/wakatime/__init__.py +80 -0
  80. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/METADATA +3 -1
  81. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/RECORD +85 -13
  82. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/WHEEL +0 -0
  83. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/entry_points.txt +0 -0
  84. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/licenses/LICENSE +0 -0
  85. {mcli_framework-7.2.0.dist-info → mcli_framework-7.3.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,480 @@
1
+ """Trading service for managing portfolios and executing trades"""
2
+
3
+ import logging
4
+ from datetime import datetime, timedelta
5
+ from decimal import Decimal
6
+ from typing import Dict, List, Optional, Tuple, Union
7
+ from uuid import UUID
8
+
9
+ import pandas as pd
10
+ from sqlalchemy.orm import Session
11
+ from sqlalchemy import and_, desc, func
12
+
13
+ from mcli.ml.trading.alpaca_client import AlpacaTradingClient, create_trading_client
14
+ from mcli.ml.trading.models import (
15
+ TradingAccount, Portfolio, Position, TradingOrder, PortfolioPerformanceSnapshot,
16
+ TradingSignal, OrderStatus, OrderType, OrderSide, PositionSide, PortfolioType,
17
+ TradingAccountCreate, PortfolioCreate, OrderCreate, PortfolioResponse,
18
+ PositionResponse, OrderResponse, TradingSignalResponse
19
+ )
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class TradingService:
25
+ """Service for managing trading operations"""
26
+
27
+ def __init__(self, db_session: Session):
28
+ self.db = db_session
29
+
30
+ def create_trading_account(self, user_id: UUID, account_data: TradingAccountCreate) -> TradingAccount:
31
+ """Create a new trading account"""
32
+ try:
33
+ account = TradingAccount(
34
+ user_id=user_id,
35
+ account_name=account_data.account_name,
36
+ account_type=account_data.account_type,
37
+ alpaca_api_key=account_data.alpaca_api_key,
38
+ alpaca_secret_key=account_data.alpaca_secret_key,
39
+ paper_trading=account_data.paper_trading,
40
+ risk_level=account_data.risk_level,
41
+ max_position_size=account_data.max_position_size,
42
+ max_portfolio_risk=account_data.max_portfolio_risk,
43
+ )
44
+
45
+ self.db.add(account)
46
+ self.db.commit()
47
+ self.db.refresh(account)
48
+
49
+ logger.info(f"Created trading account {account.id} for user {user_id}")
50
+ return account
51
+
52
+ except Exception as e:
53
+ self.db.rollback()
54
+ logger.error(f"Failed to create trading account: {e}")
55
+ raise
56
+
57
+ def get_trading_account(self, account_id: UUID) -> Optional[TradingAccount]:
58
+ """Get trading account by ID"""
59
+ return self.db.query(TradingAccount).filter(
60
+ TradingAccount.id == account_id,
61
+ TradingAccount.is_active == True
62
+ ).first()
63
+
64
+ def create_portfolio(self, account_id: UUID, portfolio_data: PortfolioCreate) -> Portfolio:
65
+ """Create a new portfolio"""
66
+ try:
67
+ portfolio = Portfolio(
68
+ trading_account_id=account_id,
69
+ name=portfolio_data.name,
70
+ description=portfolio_data.description,
71
+ initial_capital=Decimal(str(portfolio_data.initial_capital)),
72
+ current_value=Decimal(str(portfolio_data.initial_capital)),
73
+ cash_balance=Decimal(str(portfolio_data.initial_capital)),
74
+ )
75
+
76
+ self.db.add(portfolio)
77
+ self.db.commit()
78
+ self.db.refresh(portfolio)
79
+
80
+ logger.info(f"Created portfolio {portfolio.id} for account {account_id}")
81
+ return portfolio
82
+
83
+ except Exception as e:
84
+ self.db.rollback()
85
+ logger.error(f"Failed to create portfolio: {e}")
86
+ raise
87
+
88
+ def get_portfolio(self, portfolio_id: UUID) -> Optional[Portfolio]:
89
+ """Get portfolio by ID"""
90
+ return self.db.query(Portfolio).filter(
91
+ Portfolio.id == portfolio_id,
92
+ Portfolio.is_active == True
93
+ ).first()
94
+
95
+ def get_user_portfolios(self, user_id: UUID) -> List[Portfolio]:
96
+ """Get all portfolios for a user"""
97
+ return self.db.query(Portfolio).join(TradingAccount).filter(
98
+ TradingAccount.user_id == user_id,
99
+ Portfolio.is_active == True
100
+ ).all()
101
+
102
+ def create_alpaca_client(self, account: TradingAccount) -> AlpacaTradingClient:
103
+ """Create Alpaca client for trading account"""
104
+ if not account.alpaca_api_key or not account.alpaca_secret_key:
105
+ raise ValueError("Alpaca credentials not configured for this account")
106
+
107
+ return create_trading_client(
108
+ api_key=account.alpaca_api_key,
109
+ secret_key=account.alpaca_secret_key,
110
+ paper_trading=account.paper_trading
111
+ )
112
+
113
+ def sync_portfolio_with_alpaca(self, portfolio: Portfolio) -> bool:
114
+ """Sync portfolio data with Alpaca"""
115
+ try:
116
+ account = self.get_trading_account(portfolio.trading_account_id)
117
+ if not account:
118
+ return False
119
+
120
+ alpaca_client = self.create_alpaca_client(account)
121
+ alpaca_portfolio = alpaca_client.get_portfolio()
122
+
123
+ # Update portfolio values
124
+ portfolio.current_value = Decimal(str(alpaca_portfolio.portfolio_value))
125
+ portfolio.cash_balance = Decimal(str(alpaca_portfolio.cash))
126
+ portfolio.unrealized_pl = Decimal(str(alpaca_portfolio.unrealized_pl))
127
+ portfolio.realized_pl = Decimal(str(alpaca_portfolio.realized_pl))
128
+
129
+ # Calculate returns
130
+ if portfolio.initial_capital > 0:
131
+ total_return = portfolio.current_value - portfolio.initial_capital
132
+ portfolio.total_return = float(total_return)
133
+ portfolio.total_return_pct = float(total_return / portfolio.initial_capital * 100)
134
+
135
+ # Update positions
136
+ self._sync_positions(portfolio, alpaca_portfolio.positions)
137
+
138
+ # Create performance snapshot
139
+ self._create_performance_snapshot(portfolio)
140
+
141
+ self.db.commit()
142
+ logger.info(f"Synced portfolio {portfolio.id} with Alpaca")
143
+ return True
144
+
145
+ except Exception as e:
146
+ self.db.rollback()
147
+ logger.error(f"Failed to sync portfolio with Alpaca: {e}")
148
+ return False
149
+
150
+ def _sync_positions(self, portfolio: Portfolio, alpaca_positions: List):
151
+ """Sync positions with Alpaca data"""
152
+ # Clear existing positions
153
+ self.db.query(Position).filter(Position.portfolio_id == portfolio.id).delete()
154
+
155
+ # Add new positions
156
+ for alpaca_pos in alpaca_positions:
157
+ position = Position(
158
+ portfolio_id=portfolio.id,
159
+ symbol=alpaca_pos.symbol,
160
+ quantity=alpaca_pos.quantity,
161
+ side=PositionSide.LONG if alpaca_pos.quantity > 0 else PositionSide.SHORT,
162
+ average_price=Decimal(str(alpaca_pos.cost_basis / alpaca_pos.quantity)),
163
+ current_price=Decimal(str(alpaca_pos.current_price)),
164
+ market_value=Decimal(str(alpaca_pos.market_value)),
165
+ cost_basis=Decimal(str(alpaca_pos.cost_basis)),
166
+ unrealized_pnl=Decimal(str(alpaca_pos.unrealized_pl)),
167
+ unrealized_pnl_pct=float(alpaca_pos.unrealized_plpc),
168
+ position_size_pct=float(alpaca_pos.market_value / portfolio.current_value * 100),
169
+ weight=float(alpaca_pos.market_value / portfolio.current_value),
170
+ )
171
+ self.db.add(position)
172
+
173
+ def _create_performance_snapshot(self, portfolio: Portfolio):
174
+ """Create daily performance snapshot"""
175
+ snapshot = PortfolioPerformanceSnapshot(
176
+ portfolio_id=portfolio.id,
177
+ snapshot_date=datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0),
178
+ portfolio_value=portfolio.current_value,
179
+ cash_balance=portfolio.cash_balance,
180
+ daily_return=portfolio.daily_return,
181
+ daily_return_pct=portfolio.daily_return_pct,
182
+ total_return=Decimal(str(portfolio.total_return)),
183
+ total_return_pct=portfolio.total_return_pct,
184
+ volatility=portfolio.volatility,
185
+ sharpe_ratio=portfolio.sharpe_ratio,
186
+ max_drawdown=portfolio.max_drawdown,
187
+ positions_data=self._get_positions_data(portfolio.id),
188
+ )
189
+ self.db.add(snapshot)
190
+
191
+ def _get_positions_data(self, portfolio_id: UUID) -> Dict:
192
+ """Get positions data for snapshot"""
193
+ positions = self.db.query(Position).filter(Position.portfolio_id == portfolio_id).all()
194
+ return {
195
+ pos.symbol: {
196
+ "quantity": pos.quantity,
197
+ "side": pos.side.value,
198
+ "average_price": float(pos.average_price),
199
+ "current_price": float(pos.current_price),
200
+ "market_value": float(pos.market_value),
201
+ "unrealized_pnl": float(pos.unrealized_pnl),
202
+ "weight": pos.weight,
203
+ }
204
+ for pos in positions
205
+ }
206
+
207
+ def place_order(self, portfolio_id: UUID, order_data: OrderCreate, check_risk: bool = True) -> TradingOrder:
208
+ """Place a trading order"""
209
+ try:
210
+ portfolio = self.get_portfolio(portfolio_id)
211
+ if not portfolio:
212
+ raise ValueError("Portfolio not found")
213
+
214
+ account = self.get_trading_account(portfolio.trading_account_id)
215
+ if not account:
216
+ raise ValueError("Trading account not found")
217
+
218
+ # Check risk limits if enabled
219
+ if check_risk:
220
+ from mcli.ml.trading.risk_management import RiskManager
221
+ risk_manager = RiskManager(self)
222
+
223
+ order_dict = {
224
+ "symbol": order_data.symbol,
225
+ "quantity": order_data.quantity,
226
+ "side": order_data.side.value,
227
+ }
228
+
229
+ risk_ok, warnings = risk_manager.check_risk_limits(portfolio_id, order_dict)
230
+ if not risk_ok:
231
+ raise ValueError(f"Order violates risk limits: {'; '.join(warnings)}")
232
+
233
+ # Create order record
234
+ order = TradingOrder(
235
+ trading_account_id=account.id,
236
+ portfolio_id=portfolio.id,
237
+ symbol=order_data.symbol,
238
+ side=order_data.side,
239
+ order_type=order_data.order_type,
240
+ quantity=order_data.quantity,
241
+ limit_price=Decimal(str(order_data.limit_price)) if order_data.limit_price else None,
242
+ stop_price=Decimal(str(order_data.stop_price)) if order_data.stop_price else None,
243
+ remaining_quantity=order_data.quantity,
244
+ time_in_force=order_data.time_in_force,
245
+ extended_hours=order_data.extended_hours,
246
+ )
247
+
248
+ self.db.add(order)
249
+ self.db.flush() # Get the ID
250
+
251
+ # Place order with Alpaca if account has credentials
252
+ if account.alpaca_api_key and account.alpaca_secret_key:
253
+ alpaca_client = self.create_alpaca_client(account)
254
+
255
+ if order_data.order_type == OrderType.MARKET:
256
+ alpaca_order = alpaca_client.place_market_order(
257
+ symbol=order_data.symbol,
258
+ quantity=order_data.quantity,
259
+ side=order_data.side.value,
260
+ time_in_force=order_data.time_in_force
261
+ )
262
+ elif order_data.order_type == OrderType.LIMIT:
263
+ alpaca_order = alpaca_client.place_limit_order(
264
+ symbol=order_data.symbol,
265
+ quantity=order_data.quantity,
266
+ side=order_data.side.value,
267
+ limit_price=order_data.limit_price,
268
+ time_in_force=order_data.time_in_force
269
+ )
270
+ else:
271
+ raise ValueError(f"Unsupported order type: {order_data.order_type}")
272
+
273
+ order.alpaca_order_id = alpaca_order.id
274
+ order.status = OrderStatus.SUBMITTED
275
+ order.submitted_at = datetime.utcnow()
276
+
277
+ self.db.commit()
278
+ self.db.refresh(order)
279
+
280
+ logger.info(f"Placed order {order.id} for portfolio {portfolio_id}")
281
+ return order
282
+
283
+ except Exception as e:
284
+ self.db.rollback()
285
+ logger.error(f"Failed to place order: {e}")
286
+ raise
287
+
288
+ def get_portfolio_positions(self, portfolio_id: UUID) -> List[PositionResponse]:
289
+ """Get all positions for a portfolio"""
290
+ positions = self.db.query(Position).filter(Position.portfolio_id == portfolio_id).all()
291
+ return [
292
+ PositionResponse(
293
+ id=pos.id,
294
+ symbol=pos.symbol,
295
+ quantity=pos.quantity,
296
+ side=pos.side,
297
+ average_price=float(pos.average_price),
298
+ current_price=float(pos.current_price),
299
+ market_value=float(pos.market_value),
300
+ cost_basis=float(pos.cost_basis),
301
+ unrealized_pnl=float(pos.unrealized_pnl),
302
+ unrealized_pnl_pct=pos.unrealized_pnl_pct,
303
+ realized_pnl=float(pos.realized_pnl),
304
+ position_size_pct=pos.position_size_pct,
305
+ weight=pos.weight,
306
+ created_at=pos.created_at,
307
+ updated_at=pos.updated_at,
308
+ )
309
+ for pos in positions
310
+ ]
311
+
312
+ def get_portfolio_orders(self, portfolio_id: UUID, status: Optional[OrderStatus] = None) -> List[OrderResponse]:
313
+ """Get orders for a portfolio"""
314
+ query = self.db.query(TradingOrder).filter(TradingOrder.portfolio_id == portfolio_id)
315
+ if status:
316
+ query = query.filter(TradingOrder.status == status)
317
+
318
+ orders = query.order_by(desc(TradingOrder.created_at)).all()
319
+ return [
320
+ OrderResponse(
321
+ id=order.id,
322
+ symbol=order.symbol,
323
+ side=order.side,
324
+ order_type=order.order_type,
325
+ quantity=order.quantity,
326
+ limit_price=float(order.limit_price) if order.limit_price else None,
327
+ stop_price=float(order.stop_price) if order.stop_price else None,
328
+ average_fill_price=float(order.average_fill_price) if order.average_fill_price else None,
329
+ status=order.status,
330
+ filled_quantity=order.filled_quantity,
331
+ remaining_quantity=order.remaining_quantity,
332
+ created_at=order.created_at,
333
+ submitted_at=order.submitted_at,
334
+ filled_at=order.filled_at,
335
+ cancelled_at=order.cancelled_at,
336
+ time_in_force=order.time_in_force,
337
+ extended_hours=order.extended_hours,
338
+ alpaca_order_id=order.alpaca_order_id,
339
+ )
340
+ for order in orders
341
+ ]
342
+
343
+ def get_portfolio_performance(self, portfolio_id: UUID, days: int = 30) -> pd.DataFrame:
344
+ """Get portfolio performance history"""
345
+ end_date = datetime.utcnow()
346
+ start_date = end_date - timedelta(days=days)
347
+
348
+ snapshots = self.db.query(PortfolioPerformanceSnapshot).filter(
349
+ PortfolioPerformanceSnapshot.portfolio_id == portfolio_id,
350
+ PortfolioPerformanceSnapshot.snapshot_date >= start_date
351
+ ).order_by(PortfolioPerformanceSnapshot.snapshot_date).all()
352
+
353
+ data = []
354
+ for snapshot in snapshots:
355
+ data.append({
356
+ "date": snapshot.snapshot_date,
357
+ "portfolio_value": float(snapshot.portfolio_value),
358
+ "cash_balance": float(snapshot.cash_balance),
359
+ "daily_return": float(snapshot.daily_return),
360
+ "daily_return_pct": snapshot.daily_return_pct,
361
+ "total_return": float(snapshot.total_return),
362
+ "total_return_pct": snapshot.total_return_pct,
363
+ "volatility": snapshot.volatility,
364
+ "sharpe_ratio": snapshot.sharpe_ratio,
365
+ "max_drawdown": snapshot.max_drawdown,
366
+ })
367
+
368
+ return pd.DataFrame(data)
369
+
370
+ def create_trading_signal(
371
+ self,
372
+ portfolio_id: UUID,
373
+ symbol: str,
374
+ signal_type: str,
375
+ confidence: float,
376
+ strength: float,
377
+ model_id: Optional[str] = None,
378
+ target_price: Optional[float] = None,
379
+ stop_loss: Optional[float] = None,
380
+ take_profit: Optional[float] = None,
381
+ position_size: Optional[float] = None,
382
+ expires_hours: int = 24
383
+ ) -> TradingSignal:
384
+ """Create a trading signal"""
385
+ try:
386
+ expires_at = datetime.utcnow() + timedelta(hours=expires_hours) if expires_hours > 0 else None
387
+
388
+ signal = TradingSignal(
389
+ portfolio_id=portfolio_id,
390
+ symbol=symbol,
391
+ signal_type=signal_type,
392
+ confidence=confidence,
393
+ strength=strength,
394
+ model_id=model_id,
395
+ target_price=Decimal(str(target_price)) if target_price else None,
396
+ stop_loss=Decimal(str(stop_loss)) if stop_loss else None,
397
+ take_profit=Decimal(str(take_profit)) if take_profit else None,
398
+ position_size=position_size,
399
+ expires_at=expires_at,
400
+ )
401
+
402
+ self.db.add(signal)
403
+ self.db.commit()
404
+ self.db.refresh(signal)
405
+
406
+ logger.info(f"Created trading signal {signal.id} for {symbol}")
407
+ return signal
408
+
409
+ except Exception as e:
410
+ self.db.rollback()
411
+ logger.error(f"Failed to create trading signal: {e}")
412
+ raise
413
+
414
+ def get_active_signals(self, portfolio_id: UUID) -> List[TradingSignalResponse]:
415
+ """Get active trading signals for a portfolio"""
416
+ signals = self.db.query(TradingSignal).filter(
417
+ TradingSignal.portfolio_id == portfolio_id,
418
+ TradingSignal.is_active == True,
419
+ TradingSignal.expires_at > datetime.utcnow()
420
+ ).order_by(desc(TradingSignal.created_at)).all()
421
+
422
+ return [
423
+ TradingSignalResponse(
424
+ id=signal.id,
425
+ symbol=signal.symbol,
426
+ signal_type=signal.signal_type,
427
+ confidence=signal.confidence,
428
+ strength=signal.strength,
429
+ model_id=signal.model_id,
430
+ model_version=signal.model_version,
431
+ target_price=float(signal.target_price) if signal.target_price else None,
432
+ stop_loss=float(signal.stop_loss) if signal.stop_loss else None,
433
+ take_profit=float(signal.take_profit) if signal.take_profit else None,
434
+ position_size=signal.position_size,
435
+ created_at=signal.created_at,
436
+ expires_at=signal.expires_at,
437
+ is_active=signal.is_active,
438
+ )
439
+ for signal in signals
440
+ ]
441
+
442
+ def calculate_portfolio_metrics(self, portfolio_id: UUID) -> Dict:
443
+ """Calculate portfolio performance metrics"""
444
+ portfolio = self.get_portfolio(portfolio_id)
445
+ if not portfolio:
446
+ return {}
447
+
448
+ # Get performance history
449
+ performance_df = self.get_portfolio_performance(portfolio_id, days=90)
450
+
451
+ if performance_df.empty:
452
+ return {
453
+ "total_return": 0.0,
454
+ "total_return_pct": 0.0,
455
+ "volatility": 0.0,
456
+ "sharpe_ratio": 0.0,
457
+ "max_drawdown": 0.0,
458
+ "current_value": float(portfolio.current_value),
459
+ "cash_balance": float(portfolio.cash_balance),
460
+ }
461
+
462
+ # Calculate metrics
463
+ returns = performance_df["daily_return_pct"].dropna()
464
+
465
+ total_return = performance_df["total_return"].iloc[-1] if not performance_df.empty else 0
466
+ total_return_pct = performance_df["total_return_pct"].iloc[-1] if not performance_df.empty else 0
467
+ volatility = returns.std() * (252 ** 0.5) if len(returns) > 1 else 0 # Annualized
468
+ sharpe_ratio = (returns.mean() * 252) / volatility if volatility > 0 else 0 # Annualized
469
+ max_drawdown = performance_df["max_drawdown"].max() if not performance_df.empty else 0
470
+
471
+ return {
472
+ "total_return": total_return,
473
+ "total_return_pct": total_return_pct,
474
+ "volatility": volatility,
475
+ "sharpe_ratio": sharpe_ratio,
476
+ "max_drawdown": max_drawdown,
477
+ "current_value": float(portfolio.current_value),
478
+ "cash_balance": float(portfolio.cash_balance),
479
+ "num_positions": len(self.get_portfolio_positions(portfolio_id)),
480
+ }
@@ -0,0 +1,10 @@
1
+ """ML model training module"""
2
+
3
+ from .train_model import (
4
+ PoliticianTradingNet,
5
+ main as train_model,
6
+ fetch_training_data,
7
+ prepare_dataset,
8
+ )
9
+
10
+ __all__ = ["PoliticianTradingNet", "train_model", "fetch_training_data", "prepare_dataset"]
@@ -0,0 +1,3 @@
1
+ """
2
+ Mygroup commands for mcli.
3
+ """
@@ -0,0 +1 @@
1
+ # logger.info("I am in mcli.public.__init__.py")
@@ -0,0 +1,2 @@
1
+ # User-generated commands directory
2
+ # Commands created through the MCLI chat interface are stored here
mcli/self/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """
2
+ Self-management module for mcli.
3
+ """