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,768 @@
1
+ """Trading dashboard page for portfolio management and trade execution"""
2
+
3
+ import logging
4
+ import warnings
5
+ from datetime import datetime, timedelta
6
+ from typing import Dict, List, Optional
7
+ from uuid import UUID
8
+
9
+ import pandas as pd
10
+ import plotly.express as px
11
+ import plotly.graph_objects as go
12
+ from plotly.subplots import make_subplots
13
+ import streamlit as st
14
+
15
+ # Suppress Streamlit warnings when used outside runtime context
16
+ warnings.filterwarnings("ignore", message=".*missing ScriptRunContext.*")
17
+ warnings.filterwarnings("ignore", message=".*No runtime found.*")
18
+ warnings.filterwarnings("ignore", message=".*Session state does not function.*")
19
+ warnings.filterwarnings("ignore", message=".*to view this Streamlit app.*")
20
+
21
+ # Try to import trading dependencies with fallbacks
22
+ try:
23
+ from sqlalchemy.orm import Session
24
+ from mcli.ml.trading.trading_service import TradingService
25
+ from mcli.ml.trading.models import (
26
+ TradingAccountCreate, PortfolioCreate, OrderCreate, PortfolioType, RiskLevel,
27
+ OrderType, OrderSide, TradingSignalResponse
28
+ )
29
+ from mcli.ml.database.session import get_session
30
+ HAS_TRADING_DEPS = True
31
+ except ImportError as e:
32
+ st.error(f"Trading dependencies not available: {e}")
33
+ HAS_TRADING_DEPS = False
34
+ # Create dummy classes for fallback
35
+ class TradingService:
36
+ def __init__(self, db):
37
+ pass
38
+ class TradingAccountCreate:
39
+ def __init__(self, **kwargs):
40
+ pass
41
+ class PortfolioCreate:
42
+ def __init__(self, **kwargs):
43
+ pass
44
+ class OrderCreate:
45
+ def __init__(self, **kwargs):
46
+ pass
47
+ class PortfolioType:
48
+ TEST = "test"
49
+ PAPER = "paper"
50
+ LIVE = "live"
51
+ class RiskLevel:
52
+ CONSERVATIVE = "conservative"
53
+ MODERATE = "moderate"
54
+ AGGRESSIVE = "aggressive"
55
+ class OrderType:
56
+ MARKET = "market"
57
+ LIMIT = "limit"
58
+ class OrderSide:
59
+ BUY = "buy"
60
+ SELL = "sell"
61
+ class TradingSignalResponse:
62
+ def __init__(self, **kwargs):
63
+ pass
64
+ def get_session():
65
+ return None
66
+
67
+ logger = logging.getLogger(__name__)
68
+
69
+
70
+ def show_trading_dashboard():
71
+ """Main trading dashboard page"""
72
+ st.title("📈 Trading Dashboard")
73
+ st.markdown("Manage your portfolios and execute trades based on politician trading insights")
74
+
75
+ # Add a simple test to ensure the page is rendering
76
+ st.info("📋 Page loaded successfully - Trading Dashboard functionality is available")
77
+
78
+ # Check if trading dependencies are available
79
+ if not HAS_TRADING_DEPS:
80
+ st.error("⚠️ Trading functionality is not available. Please ensure all trading dependencies are installed.")
81
+ st.info("This page requires the trading service and database models to be properly configured.")
82
+
83
+ with st.expander("📋 Required Dependencies"):
84
+ st.markdown("""
85
+ The trading page requires the following:
86
+
87
+ 1. **Trading Module** (`mcli.ml.trading`)
88
+ 2. **Database Connection** (PostgreSQL/SQLite)
89
+ 3. **Alpaca API** (for live trading - optional for testing)
90
+
91
+ **To fix this:**
92
+ ```bash
93
+ # Install trading dependencies
94
+ pip install alpaca-py sqlalchemy
95
+
96
+ # Configure database in .env
97
+ DATABASE_URL=postgresql://user:pass@localhost/dbname
98
+ ```
99
+ """)
100
+ return
101
+
102
+ # Initialize session state
103
+ if "trading_page" not in st.session_state:
104
+ st.session_state.trading_page = "overview"
105
+
106
+ # Sidebar navigation
107
+ with st.sidebar:
108
+ st.markdown("### 🎯 Trading Navigation")
109
+ page = st.radio(
110
+ "Select Page",
111
+ ["Overview", "Portfolios", "Trading", "Performance", "Signals", "Settings"],
112
+ key="trading_nav"
113
+ )
114
+
115
+ # Route to appropriate page
116
+ try:
117
+ if page == "Overview":
118
+ show_trading_overview()
119
+ elif page == "Portfolios":
120
+ show_portfolios_page()
121
+ elif page == "Trading":
122
+ show_trading_page()
123
+ elif page == "Performance":
124
+ show_performance_page()
125
+ elif page == "Signals":
126
+ show_signals_page()
127
+ elif page == "Settings":
128
+ show_settings_page()
129
+ except Exception as e:
130
+ st.error(f"Error loading trading page: {e}")
131
+ logger.error(f"Trading page error: {e}", exc_info=True)
132
+ st.info("Please check the logs for more details and ensure the database is properly configured.")
133
+
134
+ with st.expander("🔍 Error Details"):
135
+ st.code(str(e))
136
+
137
+
138
+ def show_trading_overview():
139
+ """Show trading overview with key metrics"""
140
+ st.header("📊 Trading Overview")
141
+
142
+ try:
143
+ if not HAS_TRADING_DEPS:
144
+ st.warning("Trading service not available. Please check your configuration.")
145
+ return
146
+
147
+ # Check database connection
148
+ try:
149
+ db_session = get_session()
150
+ if db_session is None:
151
+ st.error("Database connection not available. Please check your database configuration.")
152
+ with st.expander("📋 Database Configuration Help"):
153
+ st.markdown("""
154
+ **Database Setup:**
155
+
156
+ 1. Create a `.env` file with:
157
+ ```
158
+ DATABASE_URL=sqlite:///ml_system.db
159
+ ```
160
+
161
+ 2. Or for PostgreSQL:
162
+ ```
163
+ DATABASE_URL=postgresql://user:password@localhost/trading_db
164
+ ```
165
+
166
+ 3. Initialize the database:
167
+ ```bash
168
+ python -m mcli.ml.database.migrations
169
+ ```
170
+ """)
171
+ return
172
+ except Exception as e:
173
+ st.error(f"Failed to connect to database: {e}")
174
+ logger.error(f"Database connection error: {e}", exc_info=True)
175
+ return
176
+
177
+ with db_session as db:
178
+ trading_service = TradingService(db)
179
+
180
+ # Get user portfolios (assuming user_id from session)
181
+ user_id = st.session_state.get("user_id", "default_user")
182
+ portfolios = trading_service.get_user_portfolios(UUID(user_id) if user_id != "default_user" else None)
183
+
184
+ if not portfolios:
185
+ st.info("No portfolios found. Create your first portfolio to get started!")
186
+ return
187
+
188
+ # Overview metrics
189
+ col1, col2, col3, col4 = st.columns(4)
190
+
191
+ total_value = sum(float(p.current_value) for p in portfolios)
192
+ total_return = sum(p.total_return for p in portfolios)
193
+ total_return_pct = (total_return / sum(float(p.initial_capital) for p in portfolios)) * 100 if portfolios else 0
194
+
195
+ with col1:
196
+ st.metric("Total Portfolio Value", f"${total_value:,.2f}")
197
+ with col2:
198
+ st.metric("Total Return", f"${total_return:,.2f}")
199
+ with col3:
200
+ st.metric("Total Return %", f"{total_return_pct:.2f}%")
201
+ with col4:
202
+ active_positions = sum(len(trading_service.get_portfolio_positions(p.id)) for p in portfolios)
203
+ st.metric("Active Positions", active_positions)
204
+
205
+ # Portfolio performance chart
206
+ st.subheader("Portfolio Performance")
207
+
208
+ # Get performance data for all portfolios
209
+ performance_data = []
210
+ for portfolio in portfolios:
211
+ perf_df = trading_service.get_portfolio_performance(portfolio.id, days=30)
212
+ if not perf_df.empty:
213
+ perf_df["portfolio_name"] = portfolio.name
214
+ performance_data.append(perf_df)
215
+
216
+ if performance_data:
217
+ combined_df = pd.concat(performance_data, ignore_index=True)
218
+
219
+ fig = px.line(
220
+ combined_df,
221
+ x="date",
222
+ y="total_return_pct",
223
+ color="portfolio_name",
224
+ title="Portfolio Performance Over Time",
225
+ labels={"total_return_pct": "Total Return (%)", "date": "Date"}
226
+ )
227
+ fig.update_layout(height=400)
228
+ st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
229
+ else:
230
+ st.info("No performance data available yet. Start trading to see your performance!")
231
+
232
+ # Recent activity
233
+ st.subheader("Recent Activity")
234
+
235
+ # Get recent orders across all portfolios
236
+ recent_orders = []
237
+ for portfolio in portfolios:
238
+ orders = trading_service.get_portfolio_orders(portfolio.id)
239
+ for order in orders[:5]: # Last 5 orders per portfolio
240
+ recent_orders.append({
241
+ "portfolio": portfolio.name,
242
+ "symbol": order.symbol,
243
+ "side": order.side.value,
244
+ "quantity": order.quantity,
245
+ "status": order.status.value,
246
+ "created_at": order.created_at,
247
+ })
248
+
249
+ if recent_orders:
250
+ orders_df = pd.DataFrame(recent_orders)
251
+ orders_df = orders_df.sort_values("created_at", ascending=False).head(10)
252
+ st.dataframe(orders_df, use_container_width=True)
253
+ else:
254
+ st.info("No recent trading activity")
255
+
256
+ except Exception as e:
257
+ st.error(f"Error loading trading overview: {e}")
258
+ logger.error(f"Trading overview error: {e}")
259
+
260
+
261
+ def show_portfolios_page():
262
+ """Show portfolios management page"""
263
+ st.header("💼 Portfolio Management")
264
+
265
+ try:
266
+ with get_session() as db:
267
+ trading_service = TradingService(db)
268
+
269
+ # Create new portfolio section
270
+ with st.expander("➕ Create New Portfolio", expanded=False):
271
+ with st.form("create_portfolio"):
272
+ col1, col2 = st.columns(2)
273
+
274
+ with col1:
275
+ portfolio_name = st.text_input("Portfolio Name", placeholder="My Trading Portfolio")
276
+ description = st.text_area("Description", placeholder="Optional description")
277
+
278
+ with col2:
279
+ initial_capital = st.number_input(
280
+ "Initial Capital ($)",
281
+ min_value=1000,
282
+ value=100000,
283
+ step=10000
284
+ )
285
+ account_type = st.selectbox(
286
+ "Account Type",
287
+ [PortfolioType.TEST, PortfolioType.PAPER, PortfolioType.LIVE]
288
+ )
289
+
290
+ if st.form_submit_button("Create Portfolio", type="primary"):
291
+ if portfolio_name:
292
+ try:
293
+ # Create trading account first if needed
294
+ user_id = st.session_state.get("user_id", "default_user")
295
+ account_id = UUID(user_id) if user_id != "default_user" else None
296
+
297
+ if not account_id:
298
+ # Create default account
299
+ account_data = TradingAccountCreate(
300
+ account_name="Default Account",
301
+ account_type=account_type,
302
+ paper_trading=True
303
+ )
304
+ account = trading_service.create_trading_account(UUID("00000000-0000-0000-0000-000000000000"), account_data)
305
+ account_id = account.id
306
+
307
+ # Create portfolio
308
+ portfolio_data = PortfolioCreate(
309
+ name=portfolio_name,
310
+ description=description,
311
+ initial_capital=initial_capital
312
+ )
313
+
314
+ portfolio = trading_service.create_portfolio(account_id, portfolio_data)
315
+ st.success(f"Portfolio '{portfolio_name}' created successfully!")
316
+ st.rerun()
317
+
318
+ except Exception as e:
319
+ st.error(f"Failed to create portfolio: {e}")
320
+ else:
321
+ st.error("Please enter a portfolio name")
322
+
323
+ # Display existing portfolios
324
+ user_id = st.session_state.get("user_id", "default_user")
325
+ portfolios = trading_service.get_user_portfolios(UUID(user_id) if user_id != "default_user" else None)
326
+
327
+ if not portfolios:
328
+ st.info("No portfolios found. Create your first portfolio above!")
329
+ return
330
+
331
+ # Portfolio cards
332
+ for portfolio in portfolios:
333
+ with st.container():
334
+ col1, col2, col3, col4 = st.columns([3, 1, 1, 1])
335
+
336
+ with col1:
337
+ st.markdown(f"### {portfolio.name}")
338
+ if portfolio.description:
339
+ st.markdown(f"*{portfolio.description}*")
340
+
341
+ with col2:
342
+ st.metric("Value", f"${float(portfolio.current_value):,.0f}")
343
+
344
+ with col3:
345
+ st.metric("Return", f"{portfolio.total_return_pct:.2f}%")
346
+
347
+ with col4:
348
+ positions = trading_service.get_portfolio_positions(portfolio.id)
349
+ st.metric("Positions", len(positions))
350
+
351
+ # Portfolio actions
352
+ col1, col2, col3 = st.columns(3)
353
+
354
+ with col1:
355
+ if st.button(f"View Details", key=f"view_{portfolio.id}"):
356
+ st.session_state.selected_portfolio = portfolio.id
357
+ st.session_state.trading_page = "trading"
358
+
359
+ with col2:
360
+ if st.button(f"Sync with Alpaca", key=f"sync_{portfolio.id}"):
361
+ with st.spinner("Syncing with Alpaca..."):
362
+ success = trading_service.sync_portfolio_with_alpaca(portfolio)
363
+ if success:
364
+ st.success("Portfolio synced successfully!")
365
+ st.rerun()
366
+ else:
367
+ st.error("Failed to sync portfolio")
368
+
369
+ with col3:
370
+ if st.button(f"Performance", key=f"perf_{portfolio.id}"):
371
+ st.session_state.selected_portfolio = portfolio.id
372
+ st.session_state.trading_page = "performance"
373
+
374
+ st.divider()
375
+
376
+ except Exception as e:
377
+ st.error(f"Error loading portfolios: {e}")
378
+ logger.error(f"Portfolios page error: {e}")
379
+
380
+
381
+ def show_trading_page():
382
+ """Show trading interface page"""
383
+ st.header("🎯 Trading Interface")
384
+
385
+ try:
386
+ with get_session() as db:
387
+ trading_service = TradingService(db)
388
+
389
+ # Get selected portfolio
390
+ portfolio_id = st.session_state.get("selected_portfolio")
391
+ if not portfolio_id:
392
+ st.warning("Please select a portfolio from the Portfolios page")
393
+ return
394
+
395
+ portfolio = trading_service.get_portfolio(portfolio_id)
396
+ if not portfolio:
397
+ st.error("Portfolio not found")
398
+ return
399
+
400
+ st.markdown(f"**Trading Portfolio:** {portfolio.name}")
401
+
402
+ # Portfolio summary
403
+ col1, col2, col3, col4 = st.columns(4)
404
+
405
+ with col1:
406
+ st.metric("Portfolio Value", f"${float(portfolio.current_value):,.2f}")
407
+ with col2:
408
+ st.metric("Cash Balance", f"${float(portfolio.cash_balance):,.2f}")
409
+ with col3:
410
+ st.metric("Total Return", f"{portfolio.total_return_pct:.2f}%")
411
+ with col4:
412
+ positions = trading_service.get_portfolio_positions(portfolio_id)
413
+ st.metric("Positions", len(positions))
414
+
415
+ # Trading interface
416
+ col1, col2 = st.columns([1, 1])
417
+
418
+ with col1:
419
+ st.subheader("📈 Place Order")
420
+
421
+ with st.form("place_order"):
422
+ symbol = st.text_input("Symbol", placeholder="AAPL", help="Stock symbol to trade")
423
+ side = st.selectbox("Side", [OrderSide.BUY, OrderSide.SELL])
424
+ order_type = st.selectbox("Order Type", [OrderType.MARKET, OrderType.LIMIT])
425
+ quantity = st.number_input("Quantity", min_value=1, value=1)
426
+
427
+ limit_price = None
428
+ if order_type == OrderType.LIMIT:
429
+ limit_price = st.number_input("Limit Price", min_value=0.01, value=100.0, step=0.01)
430
+
431
+ time_in_force = st.selectbox("Time in Force", ["day", "gtc"])
432
+ extended_hours = st.checkbox("Extended Hours", value=False)
433
+
434
+ if st.form_submit_button("Place Order", type="primary"):
435
+ if symbol:
436
+ try:
437
+ order_data = OrderCreate(
438
+ symbol=symbol.upper(),
439
+ side=side,
440
+ order_type=order_type,
441
+ quantity=quantity,
442
+ limit_price=limit_price,
443
+ time_in_force=time_in_force,
444
+ extended_hours=extended_hours
445
+ )
446
+
447
+ order = trading_service.place_order(portfolio_id, order_data)
448
+ st.success(f"Order placed successfully! Order ID: {order.id}")
449
+ st.rerun()
450
+
451
+ except Exception as e:
452
+ st.error(f"Failed to place order: {e}")
453
+ else:
454
+ st.error("Please enter a symbol")
455
+
456
+ with col2:
457
+ st.subheader("📊 Current Positions")
458
+
459
+ positions = trading_service.get_portfolio_positions(portfolio_id)
460
+ if positions:
461
+ positions_data = []
462
+ for pos in positions:
463
+ positions_data.append({
464
+ "Symbol": pos.symbol,
465
+ "Quantity": pos.quantity,
466
+ "Side": pos.side.value,
467
+ "Avg Price": f"${pos.average_price:.2f}",
468
+ "Current Price": f"${pos.current_price:.2f}",
469
+ "Market Value": f"${pos.market_value:,.2f}",
470
+ "P&L": f"${pos.unrealized_pnl:,.2f}",
471
+ "P&L %": f"{pos.unrealized_pnl_pct:.2f}%",
472
+ "Weight": f"{pos.weight:.1%}",
473
+ })
474
+
475
+ positions_df = pd.DataFrame(positions_data)
476
+ st.dataframe(positions_df, use_container_width=True)
477
+ else:
478
+ st.info("No positions found")
479
+
480
+ # Recent orders
481
+ st.subheader("📋 Recent Orders")
482
+
483
+ orders = trading_service.get_portfolio_orders(portfolio_id)
484
+ if orders:
485
+ orders_data = []
486
+ for order in orders[:10]: # Last 10 orders
487
+ orders_data.append({
488
+ "Symbol": order.symbol,
489
+ "Side": order.side.value,
490
+ "Type": order.order_type.value,
491
+ "Quantity": order.quantity,
492
+ "Status": order.status.value,
493
+ "Created": order.created_at.strftime("%Y-%m-%d %H:%M"),
494
+ "Filled": order.filled_at.strftime("%Y-%m-%d %H:%M") if order.filled_at else "-",
495
+ })
496
+
497
+ orders_df = pd.DataFrame(orders_data)
498
+ st.dataframe(orders_df, use_container_width=True)
499
+ else:
500
+ st.info("No orders found")
501
+
502
+ except Exception as e:
503
+ st.error(f"Error loading trading page: {e}")
504
+ logger.error(f"Trading page error: {e}")
505
+
506
+
507
+ def show_performance_page():
508
+ """Show portfolio performance analytics"""
509
+ st.header("📊 Performance Analytics")
510
+
511
+ try:
512
+ with get_session() as db:
513
+ trading_service = TradingService(db)
514
+
515
+ # Get selected portfolio
516
+ portfolio_id = st.session_state.get("selected_portfolio")
517
+ if not portfolio_id:
518
+ st.warning("Please select a portfolio from the Portfolios page")
519
+ return
520
+
521
+ portfolio = trading_service.get_portfolio(portfolio_id)
522
+ if not portfolio:
523
+ st.error("Portfolio not found")
524
+ return
525
+
526
+ st.markdown(f"**Performance Analysis for:** {portfolio.name}")
527
+
528
+ # Performance metrics
529
+ metrics = trading_service.calculate_portfolio_metrics(portfolio_id)
530
+
531
+ col1, col2, col3, col4 = st.columns(4)
532
+
533
+ with col1:
534
+ st.metric("Total Return", f"${metrics.get('total_return', 0):,.2f}")
535
+ with col2:
536
+ st.metric("Total Return %", f"{metrics.get('total_return_pct', 0):.2f}%")
537
+ with col3:
538
+ st.metric("Volatility", f"{metrics.get('volatility', 0):.2f}%")
539
+ with col4:
540
+ st.metric("Sharpe Ratio", f"{metrics.get('sharpe_ratio', 0):.2f}")
541
+
542
+ # Performance chart
543
+ st.subheader("Performance Over Time")
544
+
545
+ performance_df = trading_service.get_portfolio_performance(portfolio_id, days=90)
546
+
547
+ if not performance_df.empty:
548
+ # Create performance chart
549
+ fig = make_subplots(
550
+ rows=2, cols=1,
551
+ subplot_titles=("Portfolio Value", "Daily Returns"),
552
+ vertical_spacing=0.1
553
+ )
554
+
555
+ # Portfolio value
556
+ fig.add_trace(
557
+ go.Scatter(
558
+ x=performance_df["date"],
559
+ y=performance_df["portfolio_value"],
560
+ mode="lines",
561
+ name="Portfolio Value",
562
+ line=dict(color="blue")
563
+ ),
564
+ row=1, col=1
565
+ )
566
+
567
+ # Daily returns
568
+ fig.add_trace(
569
+ go.Bar(
570
+ x=performance_df["date"],
571
+ y=performance_df["daily_return_pct"],
572
+ name="Daily Return %",
573
+ marker_color=["green" if x >= 0 else "red" for x in performance_df["daily_return_pct"]]
574
+ ),
575
+ row=2, col=1
576
+ )
577
+
578
+ fig.update_layout(height=600, showlegend=True)
579
+ fig.update_xaxes(title_text="Date", row=2, col=1)
580
+ fig.update_yaxes(title_text="Portfolio Value ($)", row=1, col=1)
581
+ fig.update_yaxes(title_text="Daily Return (%)", row=2, col=1)
582
+
583
+ st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
584
+ else:
585
+ st.info("No performance data available yet")
586
+
587
+ # Risk metrics
588
+ st.subheader("Risk Analysis")
589
+
590
+ col1, col2 = st.columns(2)
591
+
592
+ with col1:
593
+ st.metric("Max Drawdown", f"{metrics.get('max_drawdown', 0):.2f}%")
594
+ st.metric("Current Value", f"${metrics.get('current_value', 0):,.2f}")
595
+
596
+ with col2:
597
+ st.metric("Cash Balance", f"${metrics.get('cash_balance', 0):,.2f}")
598
+ st.metric("Number of Positions", metrics.get('num_positions', 0))
599
+
600
+ except Exception as e:
601
+ st.error(f"Error loading performance page: {e}")
602
+ logger.error(f"Performance page error: {e}")
603
+
604
+
605
+ def show_signals_page():
606
+ """Show trading signals page"""
607
+ st.header("🎯 Trading Signals")
608
+
609
+ try:
610
+ with get_session() as db:
611
+ trading_service = TradingService(db)
612
+
613
+ # Get selected portfolio
614
+ portfolio_id = st.session_state.get("selected_portfolio")
615
+ if not portfolio_id:
616
+ st.warning("Please select a portfolio from the Portfolios page")
617
+ return
618
+
619
+ portfolio = trading_service.get_portfolio(portfolio_id)
620
+ if not portfolio:
621
+ st.error("Portfolio not found")
622
+ return
623
+
624
+ st.markdown(f"**Trading Signals for:** {portfolio.name}")
625
+
626
+ # Get active signals
627
+ signals = trading_service.get_active_signals(portfolio_id)
628
+
629
+ if not signals:
630
+ st.info("No active trading signals found")
631
+ return
632
+
633
+ # Display signals
634
+ for signal in signals:
635
+ with st.container():
636
+ col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
637
+
638
+ with col1:
639
+ st.markdown(f"### {signal.symbol} - {signal.signal_type.upper()}")
640
+ st.markdown(f"**Confidence:** {signal.confidence:.2f} | **Strength:** {signal.strength:.2f}")
641
+ if signal.model_id:
642
+ st.markdown(f"*Generated by: {signal.model_id}*")
643
+
644
+ with col2:
645
+ if signal.target_price:
646
+ st.metric("Target Price", f"${signal.target_price:.2f}")
647
+
648
+ with col3:
649
+ if signal.stop_loss:
650
+ st.metric("Stop Loss", f"${signal.stop_loss:.2f}")
651
+
652
+ with col4:
653
+ if signal.position_size:
654
+ st.metric("Position Size", f"{signal.position_size:.1%}")
655
+
656
+ # Signal actions
657
+ col1, col2, col3 = st.columns(3)
658
+
659
+ with col1:
660
+ if st.button(f"Execute Trade", key=f"execute_{signal.id}"):
661
+ st.info("Trade execution would be implemented here")
662
+
663
+ with col2:
664
+ if st.button(f"View Details", key=f"details_{signal.id}"):
665
+ st.info("Signal details would be shown here")
666
+
667
+ with col3:
668
+ if st.button(f"Dismiss", key=f"dismiss_{signal.id}"):
669
+ st.info("Signal would be dismissed here")
670
+
671
+ st.divider()
672
+
673
+ except Exception as e:
674
+ st.error(f"Error loading signals page: {e}")
675
+ logger.error(f"Signals page error: {e}")
676
+
677
+
678
+ def show_settings_page():
679
+ """Show trading settings page"""
680
+ import os
681
+
682
+ st.header("⚙️ Trading Settings")
683
+
684
+ st.subheader("Alpaca API Configuration")
685
+
686
+ # Check current configuration from environment
687
+ api_key_configured = bool(os.getenv("ALPACA_API_KEY"))
688
+ secret_key_configured = bool(os.getenv("ALPACA_SECRET_KEY"))
689
+ base_url = os.getenv("ALPACA_BASE_URL", "https://paper-api.alpaca.markets")
690
+ is_paper = "paper" in base_url.lower()
691
+
692
+ # Show current configuration status
693
+ st.info("📝 **Configuration Status**")
694
+ col1, col2, col3 = st.columns(3)
695
+
696
+ with col1:
697
+ if api_key_configured:
698
+ st.success("✅ API Key Configured")
699
+ # Show masked version
700
+ api_key_value = os.getenv("ALPACA_API_KEY", "")
701
+ if len(api_key_value) > 8:
702
+ masked_key = api_key_value[:4] + "..." + api_key_value[-4:]
703
+ st.code(masked_key)
704
+ else:
705
+ st.error("❌ API Key Not Set")
706
+
707
+ with col2:
708
+ if secret_key_configured:
709
+ st.success("✅ Secret Key Configured")
710
+ else:
711
+ st.error("❌ Secret Key Not Set")
712
+
713
+ with col3:
714
+ st.metric("Environment", "Paper Trading" if is_paper else "Live Trading")
715
+
716
+ st.markdown("---")
717
+
718
+ # Configuration instructions
719
+ with st.expander("🔧 How to Configure Alpaca API Keys"):
720
+ st.markdown("""
721
+ ### Setting up Alpaca API Credentials
722
+
723
+ 1. **Get your API keys from Alpaca:**
724
+ - Visit [Alpaca Dashboard](https://app.alpaca.markets/paper/dashboard/overview)
725
+ - Go to "Your API Keys" section
726
+ - Generate new API keys if needed
727
+
728
+ 2. **Add keys to your `.env` file:**
729
+ ```bash
730
+ ALPACA_API_KEY=your_api_key_here
731
+ ALPACA_SECRET_KEY=your_secret_key_here
732
+ ALPACA_BASE_URL=https://paper-api.alpaca.markets # For paper trading
733
+ ```
734
+
735
+ 3. **Restart the Streamlit app** to load the new configuration
736
+
737
+ **Current Configuration File:** `/Users/lefv/repos/mcli/.env`
738
+ """)
739
+
740
+ st.subheader("Risk Management")
741
+
742
+ col1, col2 = st.columns(2)
743
+
744
+ with col1:
745
+ st.metric("Current Max Position Size", "10%")
746
+ st.metric("Current Max Portfolio Risk", "20%")
747
+
748
+ with col2:
749
+ st.metric("Active Risk Level", "Moderate")
750
+ st.metric("Paper Trading", "Enabled" if is_paper else "Disabled")
751
+
752
+ st.subheader("Portfolio Alerts")
753
+
754
+ alert_types = [
755
+ "Daily performance summary",
756
+ "Large position changes",
757
+ "Risk threshold breaches",
758
+ "Signal generation",
759
+ "Order executions"
760
+ ]
761
+
762
+ for alert_type in alert_types:
763
+ st.checkbox(alert_type, value=True)
764
+
765
+
766
+ # Module-level execution only when run directly (not when imported)
767
+ if __name__ == "__main__":
768
+ show_trading_dashboard()