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.
- mcli/app/commands_cmd.py +51 -39
- mcli/app/completion_helpers.py +4 -13
- mcli/app/main.py +21 -25
- mcli/app/model_cmd.py +119 -9
- mcli/lib/custom_commands.py +16 -11
- mcli/ml/api/app.py +1 -5
- mcli/ml/dashboard/app.py +2 -2
- mcli/ml/dashboard/app_integrated.py +168 -116
- mcli/ml/dashboard/app_supabase.py +7 -3
- mcli/ml/dashboard/app_training.py +3 -6
- mcli/ml/dashboard/components/charts.py +74 -115
- mcli/ml/dashboard/components/metrics.py +24 -44
- mcli/ml/dashboard/components/tables.py +32 -40
- mcli/ml/dashboard/overview.py +102 -78
- mcli/ml/dashboard/pages/cicd.py +103 -56
- mcli/ml/dashboard/pages/debug_dependencies.py +35 -28
- mcli/ml/dashboard/pages/gravity_viz.py +374 -313
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +50 -48
- mcli/ml/dashboard/pages/predictions_enhanced.py +396 -248
- mcli/ml/dashboard/pages/scrapers_and_logs.py +299 -273
- mcli/ml/dashboard/pages/test_portfolio.py +153 -121
- mcli/ml/dashboard/pages/trading.py +238 -169
- mcli/ml/dashboard/pages/workflows.py +129 -84
- mcli/ml/dashboard/streamlit_extras_utils.py +70 -79
- mcli/ml/dashboard/utils.py +24 -21
- mcli/ml/dashboard/warning_suppression.py +6 -4
- mcli/ml/database/session.py +16 -5
- mcli/ml/mlops/pipeline_orchestrator.py +1 -3
- mcli/ml/predictions/monte_carlo.py +6 -18
- mcli/ml/trading/alpaca_client.py +95 -96
- mcli/ml/trading/migrations.py +76 -40
- mcli/ml/trading/models.py +78 -60
- mcli/ml/trading/paper_trading.py +92 -74
- mcli/ml/trading/risk_management.py +106 -85
- mcli/ml/trading/trading_service.py +155 -110
- mcli/ml/training/train_model.py +1 -3
- mcli/{app → self}/completion_cmd.py +6 -6
- mcli/self/self_cmd.py +100 -57
- mcli/test/test_cmd.py +30 -0
- mcli/workflow/daemon/daemon.py +2 -0
- mcli/workflow/model_service/openai_adapter.py +347 -0
- mcli/workflow/politician_trading/models.py +6 -2
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +39 -88
- mcli/workflow/politician_trading/scrapers_free_sources.py +32 -39
- mcli/workflow/politician_trading/scrapers_third_party.py +21 -39
- mcli/workflow/politician_trading/seed_database.py +70 -89
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/METADATA +1 -1
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/RECORD +56 -54
- /mcli/{app → self}/logs_cmd.py +0 -0
- /mcli/{app → self}/redis_cmd.py +0 -0
- /mcli/{app → self}/visual_cmd.py +0 -0
- /mcli/{app → test}/cron_test_cmd.py +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.5.1.dist-info → mcli_framework-7.6.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
24
|
+
|
|
25
|
+
from mcli.ml.database.session import get_session
|
|
25
26
|
from mcli.ml.trading.models import (
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
OrderCreate,
|
|
28
|
+
OrderSide,
|
|
29
|
+
OrderType,
|
|
30
|
+
PortfolioCreate,
|
|
31
|
+
PortfolioType,
|
|
32
|
+
RiskLevel,
|
|
33
|
+
TradingAccountCreate,
|
|
34
|
+
TradingSignalResponse,
|
|
28
35
|
)
|
|
29
|
-
from mcli.ml.
|
|
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(
|
|
81
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 = (
|
|
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(
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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(
|
|
276
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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,
|
|
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,
|
|
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=[
|
|
635
|
+
marker_color=[
|
|
636
|
+
"green" if x >= 0 else "red" for x in performance_df["daily_return_pct"]
|
|
637
|
+
],
|
|
574
638
|
),
|
|
575
|
-
row=2,
|
|
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(
|
|
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(
|
|
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()
|