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