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.
- mcli/__init__.py +160 -0
- mcli/__main__.py +14 -0
- mcli/app/__init__.py +23 -0
- mcli/app/main.py +10 -0
- mcli/app/model/__init__.py +0 -0
- mcli/app/video/__init__.py +5 -0
- mcli/chat/__init__.py +34 -0
- mcli/lib/__init__.py +0 -0
- mcli/lib/api/__init__.py +0 -0
- mcli/lib/auth/__init__.py +1 -0
- mcli/lib/config/__init__.py +1 -0
- mcli/lib/custom_commands.py +424 -0
- mcli/lib/erd/__init__.py +25 -0
- mcli/lib/files/__init__.py +0 -0
- mcli/lib/fs/__init__.py +1 -0
- mcli/lib/logger/__init__.py +3 -0
- mcli/lib/paths.py +12 -0
- mcli/lib/performance/__init__.py +17 -0
- mcli/lib/pickles/__init__.py +1 -0
- mcli/lib/shell/__init__.py +0 -0
- mcli/lib/toml/__init__.py +1 -0
- mcli/lib/watcher/__init__.py +0 -0
- mcli/ml/__init__.py +16 -0
- mcli/ml/api/__init__.py +30 -0
- mcli/ml/api/routers/__init__.py +27 -0
- mcli/ml/api/schemas.py +2 -2
- mcli/ml/auth/__init__.py +45 -0
- mcli/ml/auth/models.py +2 -2
- mcli/ml/backtesting/__init__.py +39 -0
- mcli/ml/cli/__init__.py +5 -0
- mcli/ml/cli/main.py +1 -1
- mcli/ml/config/__init__.py +33 -0
- mcli/ml/configs/__init__.py +16 -0
- mcli/ml/dashboard/__init__.py +12 -0
- mcli/ml/dashboard/app.py +13 -13
- mcli/ml/dashboard/app_integrated.py +1309 -148
- mcli/ml/dashboard/app_supabase.py +46 -21
- mcli/ml/dashboard/app_training.py +14 -14
- mcli/ml/dashboard/components/__init__.py +7 -0
- mcli/ml/dashboard/components/charts.py +258 -0
- mcli/ml/dashboard/components/metrics.py +125 -0
- mcli/ml/dashboard/components/tables.py +228 -0
- mcli/ml/dashboard/pages/__init__.py +6 -0
- mcli/ml/dashboard/pages/cicd.py +382 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +834 -0
- mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
- mcli/ml/dashboard/pages/test_portfolio.py +373 -0
- mcli/ml/dashboard/pages/trading.py +714 -0
- mcli/ml/dashboard/pages/workflows.py +533 -0
- mcli/ml/dashboard/utils.py +154 -0
- mcli/ml/data_ingestion/__init__.py +39 -0
- mcli/ml/database/__init__.py +47 -0
- mcli/ml/experimentation/__init__.py +29 -0
- mcli/ml/features/__init__.py +39 -0
- mcli/ml/mlops/__init__.py +33 -0
- mcli/ml/models/__init__.py +94 -0
- mcli/ml/monitoring/__init__.py +25 -0
- mcli/ml/optimization/__init__.py +27 -0
- mcli/ml/predictions/__init__.py +5 -0
- mcli/ml/preprocessing/__init__.py +28 -0
- mcli/ml/scripts/__init__.py +1 -0
- mcli/ml/trading/__init__.py +60 -0
- mcli/ml/trading/alpaca_client.py +353 -0
- mcli/ml/trading/migrations.py +164 -0
- mcli/ml/trading/models.py +418 -0
- mcli/ml/trading/paper_trading.py +326 -0
- mcli/ml/trading/risk_management.py +370 -0
- mcli/ml/trading/trading_service.py +480 -0
- mcli/ml/training/__init__.py +10 -0
- mcli/ml/training/train_model.py +569 -0
- mcli/mygroup/__init__.py +3 -0
- mcli/public/__init__.py +1 -0
- mcli/public/commands/__init__.py +2 -0
- mcli/self/__init__.py +3 -0
- mcli/self/self_cmd.py +579 -91
- mcli/workflow/__init__.py +0 -0
- mcli/workflow/daemon/__init__.py +15 -0
- mcli/workflow/daemon/daemon.py +21 -3
- mcli/workflow/dashboard/__init__.py +5 -0
- mcli/workflow/docker/__init__.py +0 -0
- mcli/workflow/file/__init__.py +0 -0
- mcli/workflow/gcloud/__init__.py +1 -0
- mcli/workflow/git_commit/__init__.py +0 -0
- mcli/workflow/interview/__init__.py +0 -0
- mcli/workflow/politician_trading/__init__.py +4 -0
- mcli/workflow/politician_trading/data_sources.py +259 -1
- mcli/workflow/politician_trading/models.py +159 -1
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
- mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
- mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
- mcli/workflow/politician_trading/seed_database.py +539 -0
- mcli/workflow/registry/__init__.py +0 -0
- mcli/workflow/repo/__init__.py +0 -0
- mcli/workflow/scheduler/__init__.py +25 -0
- mcli/workflow/search/__init__.py +0 -0
- mcli/workflow/sync/__init__.py +5 -0
- mcli/workflow/videos/__init__.py +1 -0
- mcli/workflow/wakatime/__init__.py +80 -0
- mcli/workflow/workflow.py +8 -27
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/METADATA +3 -1
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/RECORD +105 -29
- mcli/workflow/daemon/api_daemon.py +0 -800
- mcli/workflow/daemon/commands.py +0 -1196
- mcli/workflow/dashboard/dashboard_cmd.py +0 -120
- mcli/workflow/file/file.py +0 -100
- mcli/workflow/git_commit/commands.py +0 -430
- mcli/workflow/politician_trading/commands.py +0 -1939
- mcli/workflow/scheduler/commands.py +0 -493
- mcli/workflow/sync/sync_cmd.py +0 -437
- mcli/workflow/videos/videos.py +0 -242
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.3.dist-info → mcli_framework-7.3.1.dist-info}/licenses/LICENSE +0 -0
- {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)
|