mcli-framework 7.6.0__py3-none-any.whl → 7.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (49) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/main.py +10 -2
  3. mcli/app/model_cmd.py +1 -1
  4. mcli/lib/custom_commands.py +4 -10
  5. mcli/ml/api/app.py +1 -5
  6. mcli/ml/dashboard/app.py +2 -2
  7. mcli/ml/dashboard/app_integrated.py +168 -116
  8. mcli/ml/dashboard/app_supabase.py +7 -3
  9. mcli/ml/dashboard/app_training.py +3 -6
  10. mcli/ml/dashboard/components/charts.py +74 -115
  11. mcli/ml/dashboard/components/metrics.py +24 -44
  12. mcli/ml/dashboard/components/tables.py +32 -40
  13. mcli/ml/dashboard/overview.py +102 -78
  14. mcli/ml/dashboard/pages/cicd.py +103 -56
  15. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  16. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  17. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  18. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  19. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  20. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  21. mcli/ml/dashboard/pages/trading.py +238 -169
  22. mcli/ml/dashboard/pages/workflows.py +129 -84
  23. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  24. mcli/ml/dashboard/utils.py +24 -21
  25. mcli/ml/dashboard/warning_suppression.py +6 -4
  26. mcli/ml/database/session.py +16 -5
  27. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  28. mcli/ml/predictions/monte_carlo.py +6 -18
  29. mcli/ml/trading/alpaca_client.py +95 -96
  30. mcli/ml/trading/migrations.py +76 -40
  31. mcli/ml/trading/models.py +78 -60
  32. mcli/ml/trading/paper_trading.py +92 -74
  33. mcli/ml/trading/risk_management.py +106 -85
  34. mcli/ml/trading/trading_service.py +155 -110
  35. mcli/ml/training/train_model.py +1 -3
  36. mcli/self/self_cmd.py +71 -57
  37. mcli/workflow/daemon/daemon.py +2 -0
  38. mcli/workflow/model_service/openai_adapter.py +6 -2
  39. mcli/workflow/politician_trading/models.py +6 -2
  40. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  41. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  42. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  43. mcli/workflow/politician_trading/seed_database.py +70 -89
  44. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
  45. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +49 -49
  46. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
  47. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
  48. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
  49. {mcli_framework-7.6.0.dist-info → mcli_framework-7.6.1.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,13 @@ import yfinance as yf
11
11
  from sqlalchemy.orm import Session
12
12
 
13
13
  from mcli.ml.trading.models import (
14
- Portfolio, Position, TradingOrder, OrderStatus, OrderType, OrderSide, PositionSide
14
+ OrderSide,
15
+ OrderStatus,
16
+ OrderType,
17
+ Portfolio,
18
+ Position,
19
+ PositionSide,
20
+ TradingOrder,
15
21
  )
16
22
  from mcli.ml.trading.trading_service import TradingService
17
23
 
@@ -20,11 +26,11 @@ logger = logging.getLogger(__name__)
20
26
 
21
27
  class PaperTradingEngine:
22
28
  """Paper trading engine for testing strategies without real money"""
23
-
29
+
24
30
  def __init__(self, trading_service: TradingService):
25
31
  self.trading_service = trading_service
26
32
  self.db = trading_service.db
27
-
33
+
28
34
  def execute_paper_order(self, order: TradingOrder) -> bool:
29
35
  """Execute a paper trade order"""
30
36
  try:
@@ -33,7 +39,7 @@ class PaperTradingEngine:
33
39
  if current_price is None:
34
40
  logger.error(f"Could not get current price for {order.symbol}")
35
41
  return False
36
-
42
+
37
43
  # Calculate execution details
38
44
  if order.order_type == OrderType.MARKET:
39
45
  execution_price = current_price
@@ -51,26 +57,28 @@ class PaperTradingEngine:
51
57
  else:
52
58
  logger.error(f"Unsupported order type for paper trading: {order.order_type}")
53
59
  return False
54
-
60
+
55
61
  # Execute the order
56
62
  order.status = OrderStatus.FILLED
57
63
  order.filled_at = datetime.utcnow()
58
64
  order.filled_quantity = order.quantity
59
65
  order.remaining_quantity = 0
60
66
  order.average_fill_price = Decimal(str(execution_price))
61
-
67
+
62
68
  # Update portfolio and positions
63
69
  self._update_portfolio_positions(order, execution_price)
64
-
70
+
65
71
  self.db.commit()
66
- logger.info(f"Paper trade executed: {order.symbol} {order.side.value} {order.quantity} @ ${execution_price}")
72
+ logger.info(
73
+ f"Paper trade executed: {order.symbol} {order.side.value} {order.quantity} @ ${execution_price}"
74
+ )
67
75
  return True
68
-
76
+
69
77
  except Exception as e:
70
78
  logger.error(f"Failed to execute paper order: {e}")
71
79
  self.db.rollback()
72
80
  return False
73
-
81
+
74
82
  def _get_current_price(self, symbol: str) -> Optional[float]:
75
83
  """Get current market price for a symbol"""
76
84
  try:
@@ -82,33 +90,36 @@ class PaperTradingEngine:
82
90
  except Exception as e:
83
91
  logger.error(f"Failed to get price for {symbol}: {e}")
84
92
  return None
85
-
93
+
86
94
  def _update_portfolio_positions(self, order: TradingOrder, execution_price: float):
87
95
  """Update portfolio positions after order execution"""
88
96
  try:
89
97
  portfolio = self.trading_service.get_portfolio(order.portfolio_id)
90
98
  if not portfolio:
91
99
  return
92
-
100
+
93
101
  # Calculate trade value
94
102
  trade_value = execution_price * order.quantity
95
-
103
+
96
104
  if order.side == OrderSide.BUY:
97
105
  # Buying shares
98
106
  portfolio.cash_balance -= Decimal(str(trade_value))
99
-
107
+
100
108
  # Update or create position
101
- position = self.db.query(Position).filter(
102
- Position.portfolio_id == order.portfolio_id,
103
- Position.symbol == order.symbol
104
- ).first()
105
-
109
+ position = (
110
+ self.db.query(Position)
111
+ .filter(
112
+ Position.portfolio_id == order.portfolio_id, Position.symbol == order.symbol
113
+ )
114
+ .first()
115
+ )
116
+
106
117
  if position:
107
118
  # Update existing position
108
119
  total_quantity = position.quantity + order.quantity
109
120
  total_cost = (position.cost_basis * position.quantity) + trade_value
110
121
  new_avg_price = total_cost / total_quantity
111
-
122
+
112
123
  position.quantity = total_quantity
113
124
  position.average_price = new_avg_price
114
125
  position.cost_basis = total_cost
@@ -134,27 +145,30 @@ class PaperTradingEngine:
134
145
  weight=0.0,
135
146
  )
136
147
  self.db.add(position)
137
-
148
+
138
149
  else: # SELL
139
150
  # Selling shares
140
151
  portfolio.cash_balance += Decimal(str(trade_value))
141
-
152
+
142
153
  # Update position
143
- position = self.db.query(Position).filter(
144
- Position.portfolio_id == order.portfolio_id,
145
- Position.symbol == order.symbol
146
- ).first()
147
-
154
+ position = (
155
+ self.db.query(Position)
156
+ .filter(
157
+ Position.portfolio_id == order.portfolio_id, Position.symbol == order.symbol
158
+ )
159
+ .first()
160
+ )
161
+
148
162
  if position and position.quantity >= order.quantity:
149
163
  # Calculate realized P&L
150
164
  cost_basis = position.cost_basis * (order.quantity / position.quantity)
151
165
  realized_pnl = trade_value - float(cost_basis)
152
-
166
+
153
167
  # Update position
154
168
  position.quantity -= order.quantity
155
169
  position.cost_basis -= cost_basis
156
170
  position.realized_pnl += Decimal(str(realized_pnl))
157
-
171
+
158
172
  if position.quantity == 0:
159
173
  # Remove position if fully sold
160
174
  self.db.delete(position)
@@ -164,140 +178,144 @@ class PaperTradingEngine:
164
178
  position.market_value = position.quantity * execution_price
165
179
  position.unrealized_pnl = position.market_value - position.cost_basis
166
180
  if position.cost_basis > 0:
167
- position.unrealized_pnl_pct = float(position.unrealized_pnl / position.cost_basis * 100)
168
-
181
+ position.unrealized_pnl_pct = float(
182
+ position.unrealized_pnl / position.cost_basis * 100
183
+ )
184
+
169
185
  # Update portfolio value
170
186
  self._update_portfolio_value(portfolio)
171
-
187
+
172
188
  except Exception as e:
173
189
  logger.error(f"Failed to update portfolio positions: {e}")
174
190
  raise
175
-
191
+
176
192
  def _update_portfolio_value(self, portfolio: Portfolio):
177
193
  """Update portfolio value and metrics"""
178
194
  try:
179
195
  # Get all positions
180
196
  positions = self.db.query(Position).filter(Position.portfolio_id == portfolio.id).all()
181
-
197
+
182
198
  # Calculate total market value
183
199
  total_market_value = sum(float(pos.market_value) for pos in positions)
184
- portfolio.current_value = Decimal(str(total_market_value + float(portfolio.cash_balance)))
185
-
200
+ portfolio.current_value = Decimal(
201
+ str(total_market_value + float(portfolio.cash_balance))
202
+ )
203
+
186
204
  # Calculate returns
187
205
  if portfolio.initial_capital > 0:
188
206
  total_return = portfolio.current_value - portfolio.initial_capital
189
207
  portfolio.total_return = float(total_return)
190
208
  portfolio.total_return_pct = float(total_return / portfolio.initial_capital * 100)
191
-
209
+
192
210
  # Update position weights
193
211
  for position in positions:
194
212
  if portfolio.current_value > 0:
195
213
  position.weight = float(position.market_value / portfolio.current_value)
196
214
  position.position_size_pct = position.weight * 100
197
-
215
+
198
216
  except Exception as e:
199
217
  logger.error(f"Failed to update portfolio value: {e}")
200
218
  raise
201
-
219
+
202
220
  def simulate_market_movement(self, portfolio_id: UUID, days: int = 1):
203
221
  """Simulate market movement for paper trading"""
204
222
  try:
205
223
  portfolio = self.trading_service.get_portfolio(portfolio_id)
206
224
  if not portfolio:
207
225
  return
208
-
226
+
209
227
  positions = self.db.query(Position).filter(Position.portfolio_id == portfolio_id).all()
210
-
228
+
211
229
  for position in positions:
212
230
  # Get historical data for the symbol
213
231
  ticker = yf.Ticker(position.symbol)
214
232
  end_date = datetime.now()
215
- start_date = end_date - timedelta(days=days + 5) # Get extra days for weekend handling
216
-
233
+ start_date = end_date - timedelta(
234
+ days=days + 5
235
+ ) # Get extra days for weekend handling
236
+
217
237
  data = ticker.history(start=start_date, end=end_date)
218
238
  if not data.empty:
219
239
  # Get the most recent price
220
240
  new_price = float(data["Close"].iloc[-1])
221
-
241
+
222
242
  # Update position
223
243
  position.current_price = Decimal(str(new_price))
224
244
  position.market_value = position.quantity * new_price
225
245
  position.unrealized_pnl = position.market_value - position.cost_basis
226
246
  if position.cost_basis > 0:
227
- position.unrealized_pnl_pct = float(position.unrealized_pnl / position.cost_basis * 100)
228
-
247
+ position.unrealized_pnl_pct = float(
248
+ position.unrealized_pnl / position.cost_basis * 100
249
+ )
250
+
229
251
  # Update portfolio value
230
252
  self._update_portfolio_value(portfolio)
231
253
  self.db.commit()
232
-
254
+
233
255
  logger.info(f"Simulated market movement for portfolio {portfolio_id}")
234
-
256
+
235
257
  except Exception as e:
236
258
  logger.error(f"Failed to simulate market movement: {e}")
237
259
  self.db.rollback()
238
-
260
+
239
261
  def create_test_portfolio(
240
- self,
241
- user_id: UUID,
242
- name: str = "Test Portfolio",
243
- initial_capital: float = 100000.0
262
+ self, user_id: UUID, name: str = "Test Portfolio", initial_capital: float = 100000.0
244
263
  ) -> Portfolio:
245
264
  """Create a test portfolio for paper trading"""
246
265
  try:
247
266
  # Create trading account
248
267
  from mcli.ml.trading.models import TradingAccountCreate
268
+
249
269
  account_data = TradingAccountCreate(
250
- account_name="Test Account",
251
- account_type="test",
252
- paper_trading=True
270
+ account_name="Test Account", account_type="test", paper_trading=True
253
271
  )
254
272
  account = self.trading_service.create_trading_account(user_id, account_data)
255
-
273
+
256
274
  # Create portfolio
257
275
  portfolio_data = PortfolioCreate(
258
276
  name=name,
259
277
  description="Test portfolio for paper trading",
260
- initial_capital=initial_capital
278
+ initial_capital=initial_capital,
261
279
  )
262
280
  portfolio = self.trading_service.create_portfolio(account.id, portfolio_data)
263
-
281
+
264
282
  logger.info(f"Created test portfolio {portfolio.id} for user {user_id}")
265
283
  return portfolio
266
-
284
+
267
285
  except Exception as e:
268
286
  logger.error(f"Failed to create test portfolio: {e}")
269
287
  raise
270
-
288
+
271
289
  def run_backtest(
272
- self,
273
- portfolio_id: UUID,
274
- start_date: datetime,
290
+ self,
291
+ portfolio_id: UUID,
292
+ start_date: datetime,
275
293
  end_date: datetime,
276
- initial_capital: float = 100000.0
294
+ initial_capital: float = 100000.0,
277
295
  ) -> Dict:
278
296
  """Run a backtest on historical data"""
279
297
  try:
280
298
  portfolio = self.trading_service.get_portfolio(portfolio_id)
281
299
  if not portfolio:
282
300
  raise ValueError("Portfolio not found")
283
-
301
+
284
302
  # Reset portfolio to initial state
285
303
  portfolio.current_value = Decimal(str(initial_capital))
286
304
  portfolio.cash_balance = Decimal(str(initial_capital))
287
305
  portfolio.total_return = 0.0
288
306
  portfolio.total_return_pct = 0.0
289
-
307
+
290
308
  # Clear existing positions
291
309
  self.db.query(Position).filter(Position.portfolio_id == portfolio_id).delete()
292
-
310
+
293
311
  # Get historical data for the period
294
- date_range = pd.date_range(start=start_date, end=end_date, freq='D')
295
-
312
+ date_range = pd.date_range(start=start_date, end=end_date, freq="D")
313
+
296
314
  # This is a simplified backtest - in practice you'd want to:
297
315
  # 1. Get historical signals
298
316
  # 2. Execute trades based on signals
299
317
  # 3. Track performance over time
300
-
318
+
301
319
  backtest_results = {
302
320
  "start_date": start_date,
303
321
  "end_date": end_date,
@@ -309,11 +327,11 @@ class PaperTradingEngine:
309
327
  "max_drawdown": 0.0,
310
328
  "sharpe_ratio": 0.0,
311
329
  }
312
-
330
+
313
331
  self.db.commit()
314
332
  logger.info(f"Backtest completed for portfolio {portfolio_id}")
315
333
  return backtest_results
316
-
334
+
317
335
  except Exception as e:
318
336
  logger.error(f"Failed to run backtest: {e}")
319
337
  self.db.rollback()
@@ -323,4 +341,4 @@ class PaperTradingEngine:
323
341
  def create_paper_trading_engine(db_session: Session) -> PaperTradingEngine:
324
342
  """Create a paper trading engine"""
325
343
  trading_service = TradingService(db_session)
326
- return PaperTradingEngine(trading_service)
344
+ return PaperTradingEngine(trading_service)