mcli-framework 7.5.1__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 (56) hide show
  1. mcli/app/commands_cmd.py +51 -39
  2. mcli/app/completion_helpers.py +4 -13
  3. mcli/app/main.py +21 -25
  4. mcli/app/model_cmd.py +119 -9
  5. mcli/lib/custom_commands.py +16 -11
  6. mcli/ml/api/app.py +1 -5
  7. mcli/ml/dashboard/app.py +2 -2
  8. mcli/ml/dashboard/app_integrated.py +168 -116
  9. mcli/ml/dashboard/app_supabase.py +7 -3
  10. mcli/ml/dashboard/app_training.py +3 -6
  11. mcli/ml/dashboard/components/charts.py +74 -115
  12. mcli/ml/dashboard/components/metrics.py +24 -44
  13. mcli/ml/dashboard/components/tables.py +32 -40
  14. mcli/ml/dashboard/overview.py +102 -78
  15. mcli/ml/dashboard/pages/cicd.py +103 -56
  16. mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
  17. mcli/ml/dashboard/pages/gravity_viz.py +374 -313
  18. mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
  19. mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
  20. mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
  21. mcli/ml/dashboard/pages/test_portfolio.py +153 -121
  22. mcli/ml/dashboard/pages/trading.py +238 -169
  23. mcli/ml/dashboard/pages/workflows.py +129 -84
  24. mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
  25. mcli/ml/dashboard/utils.py +24 -21
  26. mcli/ml/dashboard/warning_suppression.py +6 -4
  27. mcli/ml/database/session.py +16 -5
  28. mcli/ml/mlops/pipeline_orchestrator.py +1 -3
  29. mcli/ml/predictions/monte_carlo.py +6 -18
  30. mcli/ml/trading/alpaca_client.py +95 -96
  31. mcli/ml/trading/migrations.py +76 -40
  32. mcli/ml/trading/models.py +78 -60
  33. mcli/ml/trading/paper_trading.py +92 -74
  34. mcli/ml/trading/risk_management.py +106 -85
  35. mcli/ml/trading/trading_service.py +155 -110
  36. mcli/ml/training/train_model.py +1 -3
  37. mcli/{app → self}/completion_cmd.py +6 -6
  38. mcli/self/self_cmd.py +100 -57
  39. mcli/test/test_cmd.py +30 -0
  40. mcli/workflow/daemon/daemon.py +2 -0
  41. mcli/workflow/model_service/openai_adapter.py +347 -0
  42. mcli/workflow/politician_trading/models.py +6 -2
  43. mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
  44. mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
  45. mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
  46. mcli/workflow/politician_trading/seed_database.py +70 -89
  47. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
  48. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +56 -54
  49. /mcli/{app → self}/logs_cmd.py +0 -0
  50. /mcli/{app → self}/redis_cmd.py +0 -0
  51. /mcli/{app → self}/visual_cmd.py +0 -0
  52. /mcli/{app → test}/cron_test_cmd.py +0 -0
  53. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
  54. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
  55. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
  56. {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/top_level.txt +0 -0
@@ -8,14 +8,14 @@ from uuid import UUID
8
8
  import pandas as pd
9
9
  import plotly.express as px
10
10
  import plotly.graph_objects as go
11
- from plotly.subplots import make_subplots
12
11
  import streamlit as st
12
+ from plotly.subplots import make_subplots
13
13
  from sqlalchemy.orm import Session
14
14
 
15
+ from mcli.ml.database.session import get_session
16
+ from mcli.ml.trading.models import OrderCreate, OrderSide, OrderType
15
17
  from mcli.ml.trading.paper_trading import create_paper_trading_engine
16
18
  from mcli.ml.trading.trading_service import TradingService
17
- from mcli.ml.trading.models import OrderCreate, OrderType, OrderSide
18
- from mcli.ml.database.session import get_session
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
@@ -24,14 +24,14 @@ def show_test_portfolio():
24
24
  """Test portfolio page for paper trading"""
25
25
  st.title("🧪 Test Portfolio - Paper Trading")
26
26
  st.markdown("Test your trading strategies with paper money before going live")
27
-
27
+
28
28
  # Add a simple test to ensure the page is rendering
29
29
  st.info("📋 Page loaded successfully - Test Portfolio functionality is available")
30
-
30
+
31
31
  # Initialize session state
32
32
  if "test_portfolio_id" not in st.session_state:
33
33
  st.session_state.test_portfolio_id = None
34
-
34
+
35
35
  try:
36
36
  # Try to get database session
37
37
  try:
@@ -39,7 +39,8 @@ def show_test_portfolio():
39
39
  db = session_context.__enter__()
40
40
  except Exception as db_error:
41
41
  st.error(f"⚠️ Database connection unavailable: {str(db_error)[:200]}")
42
- st.info("""
42
+ st.info(
43
+ """
43
44
  **Note:** The Test Portfolio feature requires a PostgreSQL database connection.
44
45
 
45
46
  **To enable this feature on Streamlit Cloud:**
@@ -64,22 +65,21 @@ def show_test_portfolio():
64
65
  - **Predictions** - ML-based stock predictions
65
66
 
66
67
  These features use the Supabase REST API which is already configured.
67
- """)
68
+ """
69
+ )
68
70
  return
69
71
 
70
72
  try:
71
73
  trading_service = TradingService(db)
72
74
  paper_engine = create_paper_trading_engine(db)
73
-
75
+
74
76
  # Create test portfolio if none exists
75
77
  if not st.session_state.test_portfolio_id:
76
78
  if st.button("Create Test Portfolio", type="primary"):
77
79
  with st.spinner("Creating test portfolio..."):
78
80
  user_id = UUID("00000000-0000-0000-0000-000000000000") # Default user
79
81
  portfolio = paper_engine.create_test_portfolio(
80
- user_id=user_id,
81
- name="My Test Portfolio",
82
- initial_capital=100000.0
82
+ user_id=user_id, name="My Test Portfolio", initial_capital=100000.0
83
83
  )
84
84
  st.session_state.test_portfolio_id = portfolio.id
85
85
  st.success("Test portfolio created successfully!")
@@ -92,10 +92,10 @@ def show_test_portfolio():
92
92
  st.session_state.test_portfolio_id = None
93
93
  st.rerun()
94
94
  return
95
-
95
+
96
96
  # Portfolio header
97
97
  col1, col2, col3, col4 = st.columns(4)
98
-
98
+
99
99
  with col1:
100
100
  st.metric("Portfolio Value", f"${float(portfolio.current_value):,.2f}")
101
101
  with col2:
@@ -105,25 +105,29 @@ def show_test_portfolio():
105
105
  with col4:
106
106
  positions = trading_service.get_portfolio_positions(portfolio.id)
107
107
  st.metric("Positions", len(positions))
108
-
108
+
109
109
  # Trading interface
110
110
  st.subheader("📈 Paper Trading Interface")
111
-
111
+
112
112
  col1, col2 = st.columns([1, 1])
113
-
113
+
114
114
  with col1:
115
115
  st.markdown("#### Place Test Order")
116
-
116
+
117
117
  with st.form("test_order"):
118
- symbol = st.text_input("Symbol", placeholder="AAPL", help="Stock symbol to trade")
118
+ symbol = st.text_input(
119
+ "Symbol", placeholder="AAPL", help="Stock symbol to trade"
120
+ )
119
121
  side = st.selectbox("Side", [OrderSide.BUY, OrderSide.SELL])
120
122
  order_type = st.selectbox("Order Type", [OrderType.MARKET, OrderType.LIMIT])
121
123
  quantity = st.number_input("Quantity", min_value=1, value=1)
122
-
124
+
123
125
  limit_price = None
124
126
  if order_type == OrderType.LIMIT:
125
- limit_price = st.number_input("Limit Price", min_value=0.01, value=100.0, step=0.01)
126
-
127
+ limit_price = st.number_input(
128
+ "Limit Price", min_value=0.01, value=100.0, step=0.01
129
+ )
130
+
127
131
  if st.form_submit_button("Place Test Order", type="primary"):
128
132
  if symbol:
129
133
  try:
@@ -134,69 +138,73 @@ def show_test_portfolio():
134
138
  quantity=quantity,
135
139
  limit_price=limit_price,
136
140
  time_in_force="day",
137
- extended_hours=False
141
+ extended_hours=False,
138
142
  )
139
-
143
+
140
144
  # Create order
141
145
  order = trading_service.place_order(portfolio.id, order_data)
142
-
146
+
143
147
  # Execute paper trade
144
148
  success = paper_engine.execute_paper_order(order)
145
-
149
+
146
150
  if success:
147
- st.success(f"Test order executed: {symbol} {side.value} {quantity} shares")
151
+ st.success(
152
+ f"Test order executed: {symbol} {side.value} {quantity} shares"
153
+ )
148
154
  st.rerun()
149
155
  else:
150
156
  st.error("Failed to execute test order")
151
-
157
+
152
158
  except Exception as e:
153
159
  st.error(f"Error placing order: {e}")
154
160
  else:
155
161
  st.error("Please enter a symbol")
156
-
162
+
157
163
  with col2:
158
164
  st.markdown("#### Current Positions")
159
-
165
+
160
166
  positions = trading_service.get_portfolio_positions(portfolio.id)
161
167
  if positions:
162
168
  positions_data = []
163
169
  for pos in positions:
164
- positions_data.append({
165
- "Symbol": pos.symbol,
166
- "Quantity": pos.quantity,
167
- "Side": pos.side.value,
168
- "Avg Price": f"${pos.average_price:.2f}",
169
- "Current Price": f"${pos.current_price:.2f}",
170
- "Market Value": f"${pos.market_value:,.2f}",
171
- "P&L": f"${pos.unrealized_pnl:,.2f}",
172
- "P&L %": f"{pos.unrealized_pnl_pct:.2f}%",
173
- "Weight": f"{pos.weight:.1%}",
174
- })
175
-
170
+ positions_data.append(
171
+ {
172
+ "Symbol": pos.symbol,
173
+ "Quantity": pos.quantity,
174
+ "Side": pos.side.value,
175
+ "Avg Price": f"${pos.average_price:.2f}",
176
+ "Current Price": f"${pos.current_price:.2f}",
177
+ "Market Value": f"${pos.market_value:,.2f}",
178
+ "P&L": f"${pos.unrealized_pnl:,.2f}",
179
+ "P&L %": f"{pos.unrealized_pnl_pct:.2f}%",
180
+ "Weight": f"{pos.weight:.1%}",
181
+ }
182
+ )
183
+
176
184
  positions_df = pd.DataFrame(positions_data)
177
185
  st.dataframe(positions_df, use_container_width=True)
178
186
  else:
179
187
  st.info("No positions found")
180
-
188
+
181
189
  # Market simulation
182
190
  st.subheader("🎲 Market Simulation")
183
-
191
+
184
192
  col1, col2, col3 = st.columns(3)
185
-
193
+
186
194
  with col1:
187
195
  if st.button("Simulate 1 Day", help="Simulate market movement for 1 day"):
188
196
  with st.spinner("Simulating market movement..."):
189
197
  paper_engine.simulate_market_movement(portfolio.id, days=1)
190
198
  st.success("Market simulation completed!")
191
199
  st.rerun()
192
-
200
+
193
201
  with col2:
194
202
  if st.button("Simulate 1 Week", help="Simulate market movement for 1 week"):
195
203
  with st.spinner("Simulating market movement..."):
196
204
  paper_engine.simulate_market_movement(portfolio.id, days=7)
197
205
  st.success("Market simulation completed!")
198
206
  st.rerun()
199
-
207
+
200
208
  with col3:
201
209
  if st.button("Reset Portfolio", help="Reset portfolio to initial state"):
202
210
  if st.session_state.get("confirm_reset", False):
@@ -205,14 +213,15 @@ def show_test_portfolio():
205
213
  portfolio.cash_balance = portfolio.initial_capital
206
214
  portfolio.total_return = 0.0
207
215
  portfolio.total_return_pct = 0.0
208
-
216
+
209
217
  # Clear positions
210
- trading_service.db.query(trading_service.db.query(Position).filter(
211
- Position.portfolio_id == portfolio.id
212
- ).first().__class__).filter(
213
- Position.portfolio_id == portfolio.id
214
- ).delete()
215
-
218
+ trading_service.db.query(
219
+ trading_service.db.query(Position)
220
+ .filter(Position.portfolio_id == portfolio.id)
221
+ .first()
222
+ .__class__
223
+ ).filter(Position.portfolio_id == portfolio.id).delete()
224
+
216
225
  trading_service.db.commit()
217
226
  st.success("Portfolio reset successfully!")
218
227
  st.session_state.confirm_reset = False
@@ -220,19 +229,20 @@ def show_test_portfolio():
220
229
  else:
221
230
  st.session_state.confirm_reset = True
222
231
  st.warning("Click again to confirm reset")
223
-
232
+
224
233
  # Performance chart
225
234
  st.subheader("📊 Performance Chart")
226
-
235
+
227
236
  performance_df = trading_service.get_portfolio_performance(portfolio.id, days=30)
228
-
237
+
229
238
  if not performance_df.empty:
230
239
  fig = make_subplots(
231
- rows=2, cols=1,
240
+ rows=2,
241
+ cols=1,
232
242
  subplot_titles=("Portfolio Value", "Daily Returns"),
233
- vertical_spacing=0.1
243
+ vertical_spacing=0.1,
234
244
  )
235
-
245
+
236
246
  # Portfolio value
237
247
  fig.add_trace(
238
248
  go.Scatter(
@@ -240,81 +250,95 @@ def show_test_portfolio():
240
250
  y=performance_df["portfolio_value"],
241
251
  mode="lines",
242
252
  name="Portfolio Value",
243
- line=dict(color="blue")
253
+ line=dict(color="blue"),
244
254
  ),
245
- row=1, col=1
255
+ row=1,
256
+ col=1,
246
257
  )
247
-
258
+
248
259
  # Daily returns
249
260
  fig.add_trace(
250
261
  go.Bar(
251
262
  x=performance_df["date"],
252
263
  y=performance_df["daily_return_pct"],
253
264
  name="Daily Return %",
254
- marker_color=["green" if x >= 0 else "red" for x in performance_df["daily_return_pct"]]
265
+ marker_color=[
266
+ "green" if x >= 0 else "red"
267
+ for x in performance_df["daily_return_pct"]
268
+ ],
255
269
  ),
256
- row=2, col=1
270
+ row=2,
271
+ col=1,
257
272
  )
258
-
273
+
259
274
  fig.update_layout(height=600, showlegend=True)
260
275
  fig.update_xaxes(title_text="Date", row=2, col=1)
261
276
  fig.update_yaxes(title_text="Portfolio Value ($)", row=1, col=1)
262
277
  fig.update_yaxes(title_text="Daily Return (%)", row=2, col=1)
263
-
278
+
264
279
  st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
265
280
  else:
266
- st.info("No performance data available yet. Start trading to see your performance!")
267
-
281
+ st.info(
282
+ "No performance data available yet. Start trading to see your performance!"
283
+ )
284
+
268
285
  # Recent orders
269
286
  st.subheader("📋 Recent Test Orders")
270
-
287
+
271
288
  orders = trading_service.get_portfolio_orders(portfolio.id)
272
289
  if orders:
273
290
  orders_data = []
274
291
  for order in orders[:10]: # Last 10 orders
275
- orders_data.append({
276
- "Symbol": order.symbol,
277
- "Side": order.side.value,
278
- "Type": order.order_type.value,
279
- "Quantity": order.quantity,
280
- "Status": order.status.value,
281
- "Price": f"${order.average_fill_price:.2f}" if order.average_fill_price else "-",
282
- "Created": order.created_at.strftime("%Y-%m-%d %H:%M"),
283
- "Filled": order.filled_at.strftime("%Y-%m-%d %H:%M") if order.filled_at else "-",
284
- })
285
-
292
+ orders_data.append(
293
+ {
294
+ "Symbol": order.symbol,
295
+ "Side": order.side.value,
296
+ "Type": order.order_type.value,
297
+ "Quantity": order.quantity,
298
+ "Status": order.status.value,
299
+ "Price": (
300
+ f"${order.average_fill_price:.2f}"
301
+ if order.average_fill_price
302
+ else "-"
303
+ ),
304
+ "Created": order.created_at.strftime("%Y-%m-%d %H:%M"),
305
+ "Filled": (
306
+ order.filled_at.strftime("%Y-%m-%d %H:%M")
307
+ if order.filled_at
308
+ else "-"
309
+ ),
310
+ }
311
+ )
312
+
286
313
  orders_df = pd.DataFrame(orders_data)
287
314
  st.dataframe(orders_df, use_container_width=True)
288
315
  else:
289
316
  st.info("No test orders found")
290
-
317
+
291
318
  # Backtesting section
292
319
  st.subheader("🔬 Backtesting")
293
-
320
+
294
321
  with st.expander("Run Historical Backtest", expanded=False):
295
322
  col1, col2 = st.columns(2)
296
-
323
+
297
324
  with col1:
298
325
  start_date = st.date_input(
299
326
  "Start Date",
300
327
  value=datetime.now() - timedelta(days=365),
301
- max_value=datetime.now() - timedelta(days=1)
328
+ max_value=datetime.now() - timedelta(days=1),
302
329
  )
303
-
330
+
304
331
  with col2:
305
332
  end_date = st.date_input(
306
333
  "End Date",
307
334
  value=datetime.now() - timedelta(days=1),
308
- max_value=datetime.now()
335
+ max_value=datetime.now(),
309
336
  )
310
-
337
+
311
338
  initial_capital = st.number_input(
312
- "Initial Capital",
313
- min_value=1000,
314
- value=100000,
315
- step=10000
339
+ "Initial Capital", min_value=1000, value=100000, step=10000
316
340
  )
317
-
341
+
318
342
  if st.button("Run Backtest", type="primary"):
319
343
  with st.spinner("Running backtest..."):
320
344
  try:
@@ -322,38 +346,44 @@ def show_test_portfolio():
322
346
  portfolio_id=portfolio.id,
323
347
  start_date=datetime.combine(start_date, datetime.min.time()),
324
348
  end_date=datetime.combine(end_date, datetime.min.time()),
325
- initial_capital=initial_capital
349
+ initial_capital=initial_capital,
326
350
  )
327
-
351
+
328
352
  st.success("Backtest completed!")
329
-
353
+
330
354
  # Display results
331
355
  col1, col2, col3, col4 = st.columns(4)
332
-
356
+
333
357
  with col1:
334
- st.metric("Initial Capital", f"${results['initial_capital']:,.2f}")
358
+ st.metric(
359
+ "Initial Capital", f"${results['initial_capital']:,.2f}"
360
+ )
335
361
  with col2:
336
362
  st.metric("Final Value", f"${results['final_value']:,.2f}")
337
363
  with col3:
338
364
  st.metric("Total Return", f"${results['total_return']:,.2f}")
339
365
  with col4:
340
- st.metric("Total Return %", f"{results['total_return_pct']:.2f}%")
341
-
366
+ st.metric(
367
+ "Total Return %", f"{results['total_return_pct']:.2f}%"
368
+ )
369
+
342
370
  except Exception as e:
343
371
  st.error(f"Backtest failed: {e}")
344
-
372
+
345
373
  # Portfolio actions
346
374
  st.subheader("⚙️ Portfolio Actions")
347
-
375
+
348
376
  col1, col2, col3 = st.columns(3)
349
-
377
+
350
378
  with col1:
351
- if st.button("Sync with Market", help="Update all positions with current market prices"):
379
+ if st.button(
380
+ "Sync with Market", help="Update all positions with current market prices"
381
+ ):
352
382
  with st.spinner("Syncing with market..."):
353
383
  paper_engine.simulate_market_movement(portfolio.id, days=0)
354
384
  st.success("Portfolio synced with current market prices!")
355
385
  st.rerun()
356
-
386
+
357
387
  with col2:
358
388
  if st.button("Export Data", help="Export portfolio data to CSV"):
359
389
  # Export portfolio data
@@ -361,29 +391,31 @@ def show_test_portfolio():
361
391
  if positions:
362
392
  positions_data = []
363
393
  for pos in positions:
364
- positions_data.append({
365
- "Symbol": pos.symbol,
366
- "Quantity": pos.quantity,
367
- "Side": pos.side.value,
368
- "Average Price": float(pos.average_price),
369
- "Current Price": float(pos.current_price),
370
- "Market Value": float(pos.market_value),
371
- "Unrealized P&L": float(pos.unrealized_pnl),
372
- "Unrealized P&L %": pos.unrealized_pnl_pct,
373
- "Weight": pos.weight,
374
- })
375
-
394
+ positions_data.append(
395
+ {
396
+ "Symbol": pos.symbol,
397
+ "Quantity": pos.quantity,
398
+ "Side": pos.side.value,
399
+ "Average Price": float(pos.average_price),
400
+ "Current Price": float(pos.current_price),
401
+ "Market Value": float(pos.market_value),
402
+ "Unrealized P&L": float(pos.unrealized_pnl),
403
+ "Unrealized P&L %": pos.unrealized_pnl_pct,
404
+ "Weight": pos.weight,
405
+ }
406
+ )
407
+
376
408
  positions_df = pd.DataFrame(positions_data)
377
409
  csv = positions_df.to_csv(index=False)
378
410
  st.download_button(
379
411
  label="Download Positions CSV",
380
412
  data=csv,
381
413
  file_name=f"test_portfolio_positions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
382
- mime="text/csv"
414
+ mime="text/csv",
383
415
  )
384
416
  else:
385
417
  st.info("No positions to export")
386
-
418
+
387
419
  with col3:
388
420
  if st.button("Delete Portfolio", help="Delete this test portfolio"):
389
421
  if st.session_state.get("confirm_delete", False):
@@ -400,7 +432,7 @@ def show_test_portfolio():
400
432
 
401
433
  finally:
402
434
  # Clean up database session
403
- if 'db' in locals() and 'session_context' in locals():
435
+ if "db" in locals() and "session_context" in locals():
404
436
  try:
405
437
  session_context.__exit__(None, None, None)
406
438
  except Exception:
@@ -420,4 +452,4 @@ except ImportError:
420
452
 
421
453
  # Module-level execution only when run directly (not when imported)
422
454
  if __name__ == "__main__":
423
- show_test_portfolio()
455
+ show_test_portfolio()