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