mcli-framework 7.2.0__py3-none-any.whl → 7.4.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 (97) hide show
  1. mcli/__init__.py +160 -0
  2. mcli/__main__.py +14 -0
  3. mcli/app/__init__.py +23 -0
  4. mcli/app/commands_cmd.py +741 -0
  5. mcli/app/model/__init__.py +0 -0
  6. mcli/app/video/__init__.py +5 -0
  7. mcli/chat/__init__.py +34 -0
  8. mcli/lib/__init__.py +0 -0
  9. mcli/lib/api/__init__.py +0 -0
  10. mcli/lib/auth/__init__.py +1 -0
  11. mcli/lib/config/__init__.py +1 -0
  12. mcli/lib/erd/__init__.py +25 -0
  13. mcli/lib/files/__init__.py +0 -0
  14. mcli/lib/fs/__init__.py +1 -0
  15. mcli/lib/logger/__init__.py +3 -0
  16. mcli/lib/performance/__init__.py +17 -0
  17. mcli/lib/pickles/__init__.py +1 -0
  18. mcli/lib/shell/__init__.py +0 -0
  19. mcli/lib/toml/__init__.py +1 -0
  20. mcli/lib/watcher/__init__.py +0 -0
  21. mcli/ml/__init__.py +16 -0
  22. mcli/ml/api/__init__.py +30 -0
  23. mcli/ml/api/routers/__init__.py +27 -0
  24. mcli/ml/api/schemas.py +2 -2
  25. mcli/ml/auth/__init__.py +45 -0
  26. mcli/ml/auth/models.py +2 -2
  27. mcli/ml/backtesting/__init__.py +39 -0
  28. mcli/ml/cli/__init__.py +5 -0
  29. mcli/ml/cli/main.py +1 -1
  30. mcli/ml/config/__init__.py +33 -0
  31. mcli/ml/configs/__init__.py +16 -0
  32. mcli/ml/dashboard/__init__.py +12 -0
  33. mcli/ml/dashboard/app_integrated.py +296 -30
  34. mcli/ml/dashboard/app_training.py +1 -1
  35. mcli/ml/dashboard/components/__init__.py +7 -0
  36. mcli/ml/dashboard/pages/__init__.py +6 -0
  37. mcli/ml/dashboard/pages/cicd.py +1 -1
  38. mcli/ml/dashboard/pages/debug_dependencies.py +364 -0
  39. mcli/ml/dashboard/pages/gravity_viz.py +565 -0
  40. mcli/ml/dashboard/pages/monte_carlo_predictions.py +555 -0
  41. mcli/ml/dashboard/pages/overview.py +378 -0
  42. mcli/ml/dashboard/pages/predictions_enhanced.py +20 -6
  43. mcli/ml/dashboard/pages/scrapers_and_logs.py +22 -6
  44. mcli/ml/dashboard/pages/test_portfolio.py +423 -0
  45. mcli/ml/dashboard/pages/trading.py +768 -0
  46. mcli/ml/dashboard/streamlit_extras_utils.py +297 -0
  47. mcli/ml/dashboard/utils.py +161 -0
  48. mcli/ml/dashboard/warning_suppression.py +34 -0
  49. mcli/ml/data_ingestion/__init__.py +39 -0
  50. mcli/ml/database/__init__.py +47 -0
  51. mcli/ml/database/session.py +169 -16
  52. mcli/ml/experimentation/__init__.py +29 -0
  53. mcli/ml/features/__init__.py +39 -0
  54. mcli/ml/mlops/__init__.py +33 -0
  55. mcli/ml/models/__init__.py +94 -0
  56. mcli/ml/monitoring/__init__.py +25 -0
  57. mcli/ml/optimization/__init__.py +27 -0
  58. mcli/ml/predictions/__init__.py +5 -0
  59. mcli/ml/predictions/monte_carlo.py +428 -0
  60. mcli/ml/preprocessing/__init__.py +28 -0
  61. mcli/ml/scripts/__init__.py +1 -0
  62. mcli/ml/trading/__init__.py +66 -0
  63. mcli/ml/trading/alpaca_client.py +417 -0
  64. mcli/ml/trading/migrations.py +164 -0
  65. mcli/ml/trading/models.py +418 -0
  66. mcli/ml/trading/paper_trading.py +326 -0
  67. mcli/ml/trading/risk_management.py +370 -0
  68. mcli/ml/trading/trading_service.py +480 -0
  69. mcli/ml/training/__init__.py +10 -0
  70. mcli/mygroup/__init__.py +3 -0
  71. mcli/public/__init__.py +1 -0
  72. mcli/public/commands/__init__.py +2 -0
  73. mcli/self/__init__.py +3 -0
  74. mcli/self/self_cmd.py +514 -15
  75. mcli/workflow/__init__.py +0 -0
  76. mcli/workflow/daemon/__init__.py +15 -0
  77. mcli/workflow/daemon/daemon.py +21 -3
  78. mcli/workflow/dashboard/__init__.py +5 -0
  79. mcli/workflow/docker/__init__.py +0 -0
  80. mcli/workflow/file/__init__.py +0 -0
  81. mcli/workflow/gcloud/__init__.py +1 -0
  82. mcli/workflow/git_commit/__init__.py +0 -0
  83. mcli/workflow/interview/__init__.py +0 -0
  84. mcli/workflow/politician_trading/__init__.py +4 -0
  85. mcli/workflow/registry/__init__.py +0 -0
  86. mcli/workflow/repo/__init__.py +0 -0
  87. mcli/workflow/scheduler/__init__.py +25 -0
  88. mcli/workflow/search/__init__.py +0 -0
  89. mcli/workflow/sync/__init__.py +5 -0
  90. mcli/workflow/videos/__init__.py +1 -0
  91. mcli/workflow/wakatime/__init__.py +80 -0
  92. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/METADATA +4 -1
  93. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/RECORD +97 -18
  94. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/WHEEL +0 -0
  95. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/entry_points.txt +0 -0
  96. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/licenses/LICENSE +0 -0
  97. {mcli_framework-7.2.0.dist-info → mcli_framework-7.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,423 @@
1
+ """Test portfolio page for paper trading and backtesting"""
2
+
3
+ import logging
4
+ from datetime import datetime, timedelta
5
+ from typing import Dict, List, Optional
6
+ from uuid import UUID
7
+
8
+ import pandas as pd
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from plotly.subplots import make_subplots
12
+ import streamlit as st
13
+ from sqlalchemy.orm import Session
14
+
15
+ from mcli.ml.trading.paper_trading import create_paper_trading_engine
16
+ 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
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def show_test_portfolio():
24
+ """Test portfolio page for paper trading"""
25
+ st.title("🧪 Test Portfolio - Paper Trading")
26
+ st.markdown("Test your trading strategies with paper money before going live")
27
+
28
+ # Add a simple test to ensure the page is rendering
29
+ st.info("📋 Page loaded successfully - Test Portfolio functionality is available")
30
+
31
+ # Initialize session state
32
+ if "test_portfolio_id" not in st.session_state:
33
+ st.session_state.test_portfolio_id = None
34
+
35
+ try:
36
+ # Try to get database session
37
+ try:
38
+ session_context = get_session()
39
+ db = session_context.__enter__()
40
+ except Exception as db_error:
41
+ st.error(f"⚠️ Database connection unavailable: {str(db_error)[:200]}")
42
+ st.info("""
43
+ **Note:** The Test Portfolio feature requires a PostgreSQL database connection.
44
+
45
+ **To enable this feature on Streamlit Cloud:**
46
+
47
+ 1. Go to your [Streamlit Cloud Dashboard](https://share.streamlit.io/)
48
+ 2. Click on your app → Settings → Secrets
49
+ 3. Add a `DATABASE_URL` secret with your Supabase PostgreSQL connection string:
50
+
51
+ ```
52
+ DATABASE_URL = "postgresql://postgres:YOUR_PASSWORD@db.uljsqvwkomdrlnofmlad.supabase.co:5432/postgres"
53
+ ```
54
+
55
+ **Where to find your Supabase password:**
56
+ - Go to your Supabase project settings
57
+ - Navigate to Database → Connection String
58
+ - Copy the connection pooler URL or direct connection URL
59
+ - Replace `[YOUR-PASSWORD]` with your actual database password
60
+
61
+ **For now, you can use these features instead:**
62
+ - **Monte Carlo Predictions** - Stock price simulations
63
+ - **Scrapers & Logs** - View politician trading data
64
+ - **Predictions** - ML-based stock predictions
65
+
66
+ These features use the Supabase REST API which is already configured.
67
+ """)
68
+ return
69
+
70
+ try:
71
+ trading_service = TradingService(db)
72
+ paper_engine = create_paper_trading_engine(db)
73
+
74
+ # Create test portfolio if none exists
75
+ if not st.session_state.test_portfolio_id:
76
+ if st.button("Create Test Portfolio", type="primary"):
77
+ with st.spinner("Creating test portfolio..."):
78
+ user_id = UUID("00000000-0000-0000-0000-000000000000") # Default user
79
+ portfolio = paper_engine.create_test_portfolio(
80
+ user_id=user_id,
81
+ name="My Test Portfolio",
82
+ initial_capital=100000.0
83
+ )
84
+ st.session_state.test_portfolio_id = portfolio.id
85
+ st.success("Test portfolio created successfully!")
86
+ st.rerun()
87
+ else:
88
+ # Get portfolio
89
+ portfolio = trading_service.get_portfolio(st.session_state.test_portfolio_id)
90
+ if not portfolio:
91
+ st.error("Test portfolio not found")
92
+ st.session_state.test_portfolio_id = None
93
+ st.rerun()
94
+ return
95
+
96
+ # Portfolio header
97
+ col1, col2, col3, col4 = st.columns(4)
98
+
99
+ with col1:
100
+ st.metric("Portfolio Value", f"${float(portfolio.current_value):,.2f}")
101
+ with col2:
102
+ st.metric("Cash Balance", f"${float(portfolio.cash_balance):,.2f}")
103
+ with col3:
104
+ st.metric("Total Return", f"{portfolio.total_return_pct:.2f}%")
105
+ with col4:
106
+ positions = trading_service.get_portfolio_positions(portfolio.id)
107
+ st.metric("Positions", len(positions))
108
+
109
+ # Trading interface
110
+ st.subheader("📈 Paper Trading Interface")
111
+
112
+ col1, col2 = st.columns([1, 1])
113
+
114
+ with col1:
115
+ st.markdown("#### Place Test Order")
116
+
117
+ with st.form("test_order"):
118
+ symbol = st.text_input("Symbol", placeholder="AAPL", help="Stock symbol to trade")
119
+ side = st.selectbox("Side", [OrderSide.BUY, OrderSide.SELL])
120
+ order_type = st.selectbox("Order Type", [OrderType.MARKET, OrderType.LIMIT])
121
+ quantity = st.number_input("Quantity", min_value=1, value=1)
122
+
123
+ limit_price = None
124
+ 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
+ if st.form_submit_button("Place Test Order", type="primary"):
128
+ if symbol:
129
+ try:
130
+ order_data = OrderCreate(
131
+ symbol=symbol.upper(),
132
+ side=side,
133
+ order_type=order_type,
134
+ quantity=quantity,
135
+ limit_price=limit_price,
136
+ time_in_force="day",
137
+ extended_hours=False
138
+ )
139
+
140
+ # Create order
141
+ order = trading_service.place_order(portfolio.id, order_data)
142
+
143
+ # Execute paper trade
144
+ success = paper_engine.execute_paper_order(order)
145
+
146
+ if success:
147
+ st.success(f"Test order executed: {symbol} {side.value} {quantity} shares")
148
+ st.rerun()
149
+ else:
150
+ st.error("Failed to execute test order")
151
+
152
+ except Exception as e:
153
+ st.error(f"Error placing order: {e}")
154
+ else:
155
+ st.error("Please enter a symbol")
156
+
157
+ with col2:
158
+ st.markdown("#### Current Positions")
159
+
160
+ positions = trading_service.get_portfolio_positions(portfolio.id)
161
+ if positions:
162
+ positions_data = []
163
+ 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
+
176
+ positions_df = pd.DataFrame(positions_data)
177
+ st.dataframe(positions_df, use_container_width=True)
178
+ else:
179
+ st.info("No positions found")
180
+
181
+ # Market simulation
182
+ st.subheader("🎲 Market Simulation")
183
+
184
+ col1, col2, col3 = st.columns(3)
185
+
186
+ with col1:
187
+ if st.button("Simulate 1 Day", help="Simulate market movement for 1 day"):
188
+ with st.spinner("Simulating market movement..."):
189
+ paper_engine.simulate_market_movement(portfolio.id, days=1)
190
+ st.success("Market simulation completed!")
191
+ st.rerun()
192
+
193
+ with col2:
194
+ if st.button("Simulate 1 Week", help="Simulate market movement for 1 week"):
195
+ with st.spinner("Simulating market movement..."):
196
+ paper_engine.simulate_market_movement(portfolio.id, days=7)
197
+ st.success("Market simulation completed!")
198
+ st.rerun()
199
+
200
+ with col3:
201
+ if st.button("Reset Portfolio", help="Reset portfolio to initial state"):
202
+ if st.session_state.get("confirm_reset", False):
203
+ # Reset portfolio
204
+ portfolio.current_value = portfolio.initial_capital
205
+ portfolio.cash_balance = portfolio.initial_capital
206
+ portfolio.total_return = 0.0
207
+ portfolio.total_return_pct = 0.0
208
+
209
+ # 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
+
216
+ trading_service.db.commit()
217
+ st.success("Portfolio reset successfully!")
218
+ st.session_state.confirm_reset = False
219
+ st.rerun()
220
+ else:
221
+ st.session_state.confirm_reset = True
222
+ st.warning("Click again to confirm reset")
223
+
224
+ # Performance chart
225
+ st.subheader("📊 Performance Chart")
226
+
227
+ performance_df = trading_service.get_portfolio_performance(portfolio.id, days=30)
228
+
229
+ if not performance_df.empty:
230
+ fig = make_subplots(
231
+ rows=2, cols=1,
232
+ subplot_titles=("Portfolio Value", "Daily Returns"),
233
+ vertical_spacing=0.1
234
+ )
235
+
236
+ # Portfolio value
237
+ fig.add_trace(
238
+ go.Scatter(
239
+ x=performance_df["date"],
240
+ y=performance_df["portfolio_value"],
241
+ mode="lines",
242
+ name="Portfolio Value",
243
+ line=dict(color="blue")
244
+ ),
245
+ row=1, col=1
246
+ )
247
+
248
+ # Daily returns
249
+ fig.add_trace(
250
+ go.Bar(
251
+ x=performance_df["date"],
252
+ y=performance_df["daily_return_pct"],
253
+ name="Daily Return %",
254
+ marker_color=["green" if x >= 0 else "red" for x in performance_df["daily_return_pct"]]
255
+ ),
256
+ row=2, col=1
257
+ )
258
+
259
+ fig.update_layout(height=600, showlegend=True)
260
+ fig.update_xaxes(title_text="Date", row=2, col=1)
261
+ fig.update_yaxes(title_text="Portfolio Value ($)", row=1, col=1)
262
+ fig.update_yaxes(title_text="Daily Return (%)", row=2, col=1)
263
+
264
+ st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
265
+ else:
266
+ st.info("No performance data available yet. Start trading to see your performance!")
267
+
268
+ # Recent orders
269
+ st.subheader("📋 Recent Test Orders")
270
+
271
+ orders = trading_service.get_portfolio_orders(portfolio.id)
272
+ if orders:
273
+ orders_data = []
274
+ 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
+
286
+ orders_df = pd.DataFrame(orders_data)
287
+ st.dataframe(orders_df, use_container_width=True)
288
+ else:
289
+ st.info("No test orders found")
290
+
291
+ # Backtesting section
292
+ st.subheader("🔬 Backtesting")
293
+
294
+ with st.expander("Run Historical Backtest", expanded=False):
295
+ col1, col2 = st.columns(2)
296
+
297
+ with col1:
298
+ start_date = st.date_input(
299
+ "Start Date",
300
+ value=datetime.now() - timedelta(days=365),
301
+ max_value=datetime.now() - timedelta(days=1)
302
+ )
303
+
304
+ with col2:
305
+ end_date = st.date_input(
306
+ "End Date",
307
+ value=datetime.now() - timedelta(days=1),
308
+ max_value=datetime.now()
309
+ )
310
+
311
+ initial_capital = st.number_input(
312
+ "Initial Capital",
313
+ min_value=1000,
314
+ value=100000,
315
+ step=10000
316
+ )
317
+
318
+ if st.button("Run Backtest", type="primary"):
319
+ with st.spinner("Running backtest..."):
320
+ try:
321
+ results = paper_engine.run_backtest(
322
+ portfolio_id=portfolio.id,
323
+ start_date=datetime.combine(start_date, datetime.min.time()),
324
+ end_date=datetime.combine(end_date, datetime.min.time()),
325
+ initial_capital=initial_capital
326
+ )
327
+
328
+ st.success("Backtest completed!")
329
+
330
+ # Display results
331
+ col1, col2, col3, col4 = st.columns(4)
332
+
333
+ with col1:
334
+ st.metric("Initial Capital", f"${results['initial_capital']:,.2f}")
335
+ with col2:
336
+ st.metric("Final Value", f"${results['final_value']:,.2f}")
337
+ with col3:
338
+ st.metric("Total Return", f"${results['total_return']:,.2f}")
339
+ with col4:
340
+ st.metric("Total Return %", f"{results['total_return_pct']:.2f}%")
341
+
342
+ except Exception as e:
343
+ st.error(f"Backtest failed: {e}")
344
+
345
+ # Portfolio actions
346
+ st.subheader("⚙️ Portfolio Actions")
347
+
348
+ col1, col2, col3 = st.columns(3)
349
+
350
+ with col1:
351
+ if st.button("Sync with Market", help="Update all positions with current market prices"):
352
+ with st.spinner("Syncing with market..."):
353
+ paper_engine.simulate_market_movement(portfolio.id, days=0)
354
+ st.success("Portfolio synced with current market prices!")
355
+ st.rerun()
356
+
357
+ with col2:
358
+ if st.button("Export Data", help="Export portfolio data to CSV"):
359
+ # Export portfolio data
360
+ positions = trading_service.get_portfolio_positions(portfolio.id)
361
+ if positions:
362
+ positions_data = []
363
+ 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
+
376
+ positions_df = pd.DataFrame(positions_data)
377
+ csv = positions_df.to_csv(index=False)
378
+ st.download_button(
379
+ label="Download Positions CSV",
380
+ data=csv,
381
+ file_name=f"test_portfolio_positions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
382
+ mime="text/csv"
383
+ )
384
+ else:
385
+ st.info("No positions to export")
386
+
387
+ with col3:
388
+ if st.button("Delete Portfolio", help="Delete this test portfolio"):
389
+ if st.session_state.get("confirm_delete", False):
390
+ # Delete portfolio
391
+ trading_service.db.delete(portfolio)
392
+ trading_service.db.commit()
393
+ st.session_state.test_portfolio_id = None
394
+ st.session_state.confirm_delete = False
395
+ st.success("Test portfolio deleted!")
396
+ st.rerun()
397
+ else:
398
+ st.session_state.confirm_delete = True
399
+ st.warning("Click again to confirm deletion")
400
+
401
+ finally:
402
+ # Clean up database session
403
+ if 'db' in locals() and 'session_context' in locals():
404
+ try:
405
+ session_context.__exit__(None, None, None)
406
+ except Exception:
407
+ pass
408
+
409
+ except Exception as e:
410
+ st.error(f"Error loading test portfolio: {e}")
411
+ logger.error(f"Test portfolio error: {e}")
412
+
413
+
414
+ # Import Position for the reset functionality
415
+ try:
416
+ from mcli.ml.trading.models import Position
417
+ except ImportError:
418
+ Position = None
419
+
420
+
421
+ # Module-level execution only when run directly (not when imported)
422
+ if __name__ == "__main__":
423
+ show_test_portfolio()