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
|
@@ -8,14 +8,14 @@ from uuid import UUID
|
|
|
8
8
|
import pandas as pd
|
|
9
9
|
import plotly.express as px
|
|
10
10
|
import plotly.graph_objects as go
|
|
11
|
-
from plotly.subplots import make_subplots
|
|
12
11
|
import streamlit as st
|
|
12
|
+
from plotly.subplots import make_subplots
|
|
13
13
|
from sqlalchemy.orm import Session
|
|
14
14
|
|
|
15
|
+
from mcli.ml.database.session import get_session
|
|
16
|
+
from mcli.ml.trading.models import OrderCreate, OrderSide, OrderType
|
|
15
17
|
from mcli.ml.trading.paper_trading import create_paper_trading_engine
|
|
16
18
|
from mcli.ml.trading.trading_service import TradingService
|
|
17
|
-
from mcli.ml.trading.models import OrderCreate, OrderType, OrderSide
|
|
18
|
-
from mcli.ml.database.session import get_session
|
|
19
19
|
|
|
20
20
|
logger = logging.getLogger(__name__)
|
|
21
21
|
|
|
@@ -24,14 +24,14 @@ def show_test_portfolio():
|
|
|
24
24
|
"""Test portfolio page for paper trading"""
|
|
25
25
|
st.title("🧪 Test Portfolio - Paper Trading")
|
|
26
26
|
st.markdown("Test your trading strategies with paper money before going live")
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
# Add a simple test to ensure the page is rendering
|
|
29
29
|
st.info("📋 Page loaded successfully - Test Portfolio functionality is available")
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Initialize session state
|
|
32
32
|
if "test_portfolio_id" not in st.session_state:
|
|
33
33
|
st.session_state.test_portfolio_id = None
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
try:
|
|
36
36
|
# Try to get database session
|
|
37
37
|
try:
|
|
@@ -39,7 +39,8 @@ def show_test_portfolio():
|
|
|
39
39
|
db = session_context.__enter__()
|
|
40
40
|
except Exception as db_error:
|
|
41
41
|
st.error(f"⚠️ Database connection unavailable: {str(db_error)[:200]}")
|
|
42
|
-
st.info(
|
|
42
|
+
st.info(
|
|
43
|
+
"""
|
|
43
44
|
**Note:** The Test Portfolio feature requires a PostgreSQL database connection.
|
|
44
45
|
|
|
45
46
|
**To enable this feature on Streamlit Cloud:**
|
|
@@ -64,22 +65,21 @@ def show_test_portfolio():
|
|
|
64
65
|
- **Predictions** - ML-based stock predictions
|
|
65
66
|
|
|
66
67
|
These features use the Supabase REST API which is already configured.
|
|
67
|
-
"""
|
|
68
|
+
"""
|
|
69
|
+
)
|
|
68
70
|
return
|
|
69
71
|
|
|
70
72
|
try:
|
|
71
73
|
trading_service = TradingService(db)
|
|
72
74
|
paper_engine = create_paper_trading_engine(db)
|
|
73
|
-
|
|
75
|
+
|
|
74
76
|
# Create test portfolio if none exists
|
|
75
77
|
if not st.session_state.test_portfolio_id:
|
|
76
78
|
if st.button("Create Test Portfolio", type="primary"):
|
|
77
79
|
with st.spinner("Creating test portfolio..."):
|
|
78
80
|
user_id = UUID("00000000-0000-0000-0000-000000000000") # Default user
|
|
79
81
|
portfolio = paper_engine.create_test_portfolio(
|
|
80
|
-
user_id=user_id,
|
|
81
|
-
name="My Test Portfolio",
|
|
82
|
-
initial_capital=100000.0
|
|
82
|
+
user_id=user_id, name="My Test Portfolio", initial_capital=100000.0
|
|
83
83
|
)
|
|
84
84
|
st.session_state.test_portfolio_id = portfolio.id
|
|
85
85
|
st.success("Test portfolio created successfully!")
|
|
@@ -92,10 +92,10 @@ def show_test_portfolio():
|
|
|
92
92
|
st.session_state.test_portfolio_id = None
|
|
93
93
|
st.rerun()
|
|
94
94
|
return
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
# Portfolio header
|
|
97
97
|
col1, col2, col3, col4 = st.columns(4)
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
with col1:
|
|
100
100
|
st.metric("Portfolio Value", f"${float(portfolio.current_value):,.2f}")
|
|
101
101
|
with col2:
|
|
@@ -105,25 +105,29 @@ def show_test_portfolio():
|
|
|
105
105
|
with col4:
|
|
106
106
|
positions = trading_service.get_portfolio_positions(portfolio.id)
|
|
107
107
|
st.metric("Positions", len(positions))
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
# Trading interface
|
|
110
110
|
st.subheader("📈 Paper Trading Interface")
|
|
111
|
-
|
|
111
|
+
|
|
112
112
|
col1, col2 = st.columns([1, 1])
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
with col1:
|
|
115
115
|
st.markdown("#### Place Test Order")
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
with st.form("test_order"):
|
|
118
|
-
symbol = st.text_input(
|
|
118
|
+
symbol = st.text_input(
|
|
119
|
+
"Symbol", placeholder="AAPL", help="Stock symbol to trade"
|
|
120
|
+
)
|
|
119
121
|
side = st.selectbox("Side", [OrderSide.BUY, OrderSide.SELL])
|
|
120
122
|
order_type = st.selectbox("Order Type", [OrderType.MARKET, OrderType.LIMIT])
|
|
121
123
|
quantity = st.number_input("Quantity", min_value=1, value=1)
|
|
122
|
-
|
|
124
|
+
|
|
123
125
|
limit_price = None
|
|
124
126
|
if order_type == OrderType.LIMIT:
|
|
125
|
-
limit_price = st.number_input(
|
|
126
|
-
|
|
127
|
+
limit_price = st.number_input(
|
|
128
|
+
"Limit Price", min_value=0.01, value=100.0, step=0.01
|
|
129
|
+
)
|
|
130
|
+
|
|
127
131
|
if st.form_submit_button("Place Test Order", type="primary"):
|
|
128
132
|
if symbol:
|
|
129
133
|
try:
|
|
@@ -134,69 +138,73 @@ def show_test_portfolio():
|
|
|
134
138
|
quantity=quantity,
|
|
135
139
|
limit_price=limit_price,
|
|
136
140
|
time_in_force="day",
|
|
137
|
-
extended_hours=False
|
|
141
|
+
extended_hours=False,
|
|
138
142
|
)
|
|
139
|
-
|
|
143
|
+
|
|
140
144
|
# Create order
|
|
141
145
|
order = trading_service.place_order(portfolio.id, order_data)
|
|
142
|
-
|
|
146
|
+
|
|
143
147
|
# Execute paper trade
|
|
144
148
|
success = paper_engine.execute_paper_order(order)
|
|
145
|
-
|
|
149
|
+
|
|
146
150
|
if success:
|
|
147
|
-
st.success(
|
|
151
|
+
st.success(
|
|
152
|
+
f"Test order executed: {symbol} {side.value} {quantity} shares"
|
|
153
|
+
)
|
|
148
154
|
st.rerun()
|
|
149
155
|
else:
|
|
150
156
|
st.error("Failed to execute test order")
|
|
151
|
-
|
|
157
|
+
|
|
152
158
|
except Exception as e:
|
|
153
159
|
st.error(f"Error placing order: {e}")
|
|
154
160
|
else:
|
|
155
161
|
st.error("Please enter a symbol")
|
|
156
|
-
|
|
162
|
+
|
|
157
163
|
with col2:
|
|
158
164
|
st.markdown("#### Current Positions")
|
|
159
|
-
|
|
165
|
+
|
|
160
166
|
positions = trading_service.get_portfolio_positions(portfolio.id)
|
|
161
167
|
if positions:
|
|
162
168
|
positions_data = []
|
|
163
169
|
for pos in positions:
|
|
164
|
-
positions_data.append(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
positions_data.append(
|
|
171
|
+
{
|
|
172
|
+
"Symbol": pos.symbol,
|
|
173
|
+
"Quantity": pos.quantity,
|
|
174
|
+
"Side": pos.side.value,
|
|
175
|
+
"Avg Price": f"${pos.average_price:.2f}",
|
|
176
|
+
"Current Price": f"${pos.current_price:.2f}",
|
|
177
|
+
"Market Value": f"${pos.market_value:,.2f}",
|
|
178
|
+
"P&L": f"${pos.unrealized_pnl:,.2f}",
|
|
179
|
+
"P&L %": f"{pos.unrealized_pnl_pct:.2f}%",
|
|
180
|
+
"Weight": f"{pos.weight:.1%}",
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
176
184
|
positions_df = pd.DataFrame(positions_data)
|
|
177
185
|
st.dataframe(positions_df, use_container_width=True)
|
|
178
186
|
else:
|
|
179
187
|
st.info("No positions found")
|
|
180
|
-
|
|
188
|
+
|
|
181
189
|
# Market simulation
|
|
182
190
|
st.subheader("🎲 Market Simulation")
|
|
183
|
-
|
|
191
|
+
|
|
184
192
|
col1, col2, col3 = st.columns(3)
|
|
185
|
-
|
|
193
|
+
|
|
186
194
|
with col1:
|
|
187
195
|
if st.button("Simulate 1 Day", help="Simulate market movement for 1 day"):
|
|
188
196
|
with st.spinner("Simulating market movement..."):
|
|
189
197
|
paper_engine.simulate_market_movement(portfolio.id, days=1)
|
|
190
198
|
st.success("Market simulation completed!")
|
|
191
199
|
st.rerun()
|
|
192
|
-
|
|
200
|
+
|
|
193
201
|
with col2:
|
|
194
202
|
if st.button("Simulate 1 Week", help="Simulate market movement for 1 week"):
|
|
195
203
|
with st.spinner("Simulating market movement..."):
|
|
196
204
|
paper_engine.simulate_market_movement(portfolio.id, days=7)
|
|
197
205
|
st.success("Market simulation completed!")
|
|
198
206
|
st.rerun()
|
|
199
|
-
|
|
207
|
+
|
|
200
208
|
with col3:
|
|
201
209
|
if st.button("Reset Portfolio", help="Reset portfolio to initial state"):
|
|
202
210
|
if st.session_state.get("confirm_reset", False):
|
|
@@ -205,14 +213,15 @@ def show_test_portfolio():
|
|
|
205
213
|
portfolio.cash_balance = portfolio.initial_capital
|
|
206
214
|
portfolio.total_return = 0.0
|
|
207
215
|
portfolio.total_return_pct = 0.0
|
|
208
|
-
|
|
216
|
+
|
|
209
217
|
# Clear positions
|
|
210
|
-
trading_service.db.query(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
trading_service.db.query(
|
|
219
|
+
trading_service.db.query(Position)
|
|
220
|
+
.filter(Position.portfolio_id == portfolio.id)
|
|
221
|
+
.first()
|
|
222
|
+
.__class__
|
|
223
|
+
).filter(Position.portfolio_id == portfolio.id).delete()
|
|
224
|
+
|
|
216
225
|
trading_service.db.commit()
|
|
217
226
|
st.success("Portfolio reset successfully!")
|
|
218
227
|
st.session_state.confirm_reset = False
|
|
@@ -220,19 +229,20 @@ def show_test_portfolio():
|
|
|
220
229
|
else:
|
|
221
230
|
st.session_state.confirm_reset = True
|
|
222
231
|
st.warning("Click again to confirm reset")
|
|
223
|
-
|
|
232
|
+
|
|
224
233
|
# Performance chart
|
|
225
234
|
st.subheader("📊 Performance Chart")
|
|
226
|
-
|
|
235
|
+
|
|
227
236
|
performance_df = trading_service.get_portfolio_performance(portfolio.id, days=30)
|
|
228
|
-
|
|
237
|
+
|
|
229
238
|
if not performance_df.empty:
|
|
230
239
|
fig = make_subplots(
|
|
231
|
-
rows=2,
|
|
240
|
+
rows=2,
|
|
241
|
+
cols=1,
|
|
232
242
|
subplot_titles=("Portfolio Value", "Daily Returns"),
|
|
233
|
-
vertical_spacing=0.1
|
|
243
|
+
vertical_spacing=0.1,
|
|
234
244
|
)
|
|
235
|
-
|
|
245
|
+
|
|
236
246
|
# Portfolio value
|
|
237
247
|
fig.add_trace(
|
|
238
248
|
go.Scatter(
|
|
@@ -240,81 +250,95 @@ def show_test_portfolio():
|
|
|
240
250
|
y=performance_df["portfolio_value"],
|
|
241
251
|
mode="lines",
|
|
242
252
|
name="Portfolio Value",
|
|
243
|
-
line=dict(color="blue")
|
|
253
|
+
line=dict(color="blue"),
|
|
244
254
|
),
|
|
245
|
-
row=1,
|
|
255
|
+
row=1,
|
|
256
|
+
col=1,
|
|
246
257
|
)
|
|
247
|
-
|
|
258
|
+
|
|
248
259
|
# Daily returns
|
|
249
260
|
fig.add_trace(
|
|
250
261
|
go.Bar(
|
|
251
262
|
x=performance_df["date"],
|
|
252
263
|
y=performance_df["daily_return_pct"],
|
|
253
264
|
name="Daily Return %",
|
|
254
|
-
marker_color=[
|
|
265
|
+
marker_color=[
|
|
266
|
+
"green" if x >= 0 else "red"
|
|
267
|
+
for x in performance_df["daily_return_pct"]
|
|
268
|
+
],
|
|
255
269
|
),
|
|
256
|
-
row=2,
|
|
270
|
+
row=2,
|
|
271
|
+
col=1,
|
|
257
272
|
)
|
|
258
|
-
|
|
273
|
+
|
|
259
274
|
fig.update_layout(height=600, showlegend=True)
|
|
260
275
|
fig.update_xaxes(title_text="Date", row=2, col=1)
|
|
261
276
|
fig.update_yaxes(title_text="Portfolio Value ($)", row=1, col=1)
|
|
262
277
|
fig.update_yaxes(title_text="Daily Return (%)", row=2, col=1)
|
|
263
|
-
|
|
278
|
+
|
|
264
279
|
st.plotly_chart(fig, config={"displayModeBar": True}, use_container_width=True)
|
|
265
280
|
else:
|
|
266
|
-
st.info(
|
|
267
|
-
|
|
281
|
+
st.info(
|
|
282
|
+
"No performance data available yet. Start trading to see your performance!"
|
|
283
|
+
)
|
|
284
|
+
|
|
268
285
|
# Recent orders
|
|
269
286
|
st.subheader("📋 Recent Test Orders")
|
|
270
|
-
|
|
287
|
+
|
|
271
288
|
orders = trading_service.get_portfolio_orders(portfolio.id)
|
|
272
289
|
if orders:
|
|
273
290
|
orders_data = []
|
|
274
291
|
for order in orders[:10]: # Last 10 orders
|
|
275
|
-
orders_data.append(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
orders_data.append(
|
|
293
|
+
{
|
|
294
|
+
"Symbol": order.symbol,
|
|
295
|
+
"Side": order.side.value,
|
|
296
|
+
"Type": order.order_type.value,
|
|
297
|
+
"Quantity": order.quantity,
|
|
298
|
+
"Status": order.status.value,
|
|
299
|
+
"Price": (
|
|
300
|
+
f"${order.average_fill_price:.2f}"
|
|
301
|
+
if order.average_fill_price
|
|
302
|
+
else "-"
|
|
303
|
+
),
|
|
304
|
+
"Created": order.created_at.strftime("%Y-%m-%d %H:%M"),
|
|
305
|
+
"Filled": (
|
|
306
|
+
order.filled_at.strftime("%Y-%m-%d %H:%M")
|
|
307
|
+
if order.filled_at
|
|
308
|
+
else "-"
|
|
309
|
+
),
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
|
|
286
313
|
orders_df = pd.DataFrame(orders_data)
|
|
287
314
|
st.dataframe(orders_df, use_container_width=True)
|
|
288
315
|
else:
|
|
289
316
|
st.info("No test orders found")
|
|
290
|
-
|
|
317
|
+
|
|
291
318
|
# Backtesting section
|
|
292
319
|
st.subheader("🔬 Backtesting")
|
|
293
|
-
|
|
320
|
+
|
|
294
321
|
with st.expander("Run Historical Backtest", expanded=False):
|
|
295
322
|
col1, col2 = st.columns(2)
|
|
296
|
-
|
|
323
|
+
|
|
297
324
|
with col1:
|
|
298
325
|
start_date = st.date_input(
|
|
299
326
|
"Start Date",
|
|
300
327
|
value=datetime.now() - timedelta(days=365),
|
|
301
|
-
max_value=datetime.now() - timedelta(days=1)
|
|
328
|
+
max_value=datetime.now() - timedelta(days=1),
|
|
302
329
|
)
|
|
303
|
-
|
|
330
|
+
|
|
304
331
|
with col2:
|
|
305
332
|
end_date = st.date_input(
|
|
306
333
|
"End Date",
|
|
307
334
|
value=datetime.now() - timedelta(days=1),
|
|
308
|
-
max_value=datetime.now()
|
|
335
|
+
max_value=datetime.now(),
|
|
309
336
|
)
|
|
310
|
-
|
|
337
|
+
|
|
311
338
|
initial_capital = st.number_input(
|
|
312
|
-
"Initial Capital",
|
|
313
|
-
min_value=1000,
|
|
314
|
-
value=100000,
|
|
315
|
-
step=10000
|
|
339
|
+
"Initial Capital", min_value=1000, value=100000, step=10000
|
|
316
340
|
)
|
|
317
|
-
|
|
341
|
+
|
|
318
342
|
if st.button("Run Backtest", type="primary"):
|
|
319
343
|
with st.spinner("Running backtest..."):
|
|
320
344
|
try:
|
|
@@ -322,38 +346,44 @@ def show_test_portfolio():
|
|
|
322
346
|
portfolio_id=portfolio.id,
|
|
323
347
|
start_date=datetime.combine(start_date, datetime.min.time()),
|
|
324
348
|
end_date=datetime.combine(end_date, datetime.min.time()),
|
|
325
|
-
initial_capital=initial_capital
|
|
349
|
+
initial_capital=initial_capital,
|
|
326
350
|
)
|
|
327
|
-
|
|
351
|
+
|
|
328
352
|
st.success("Backtest completed!")
|
|
329
|
-
|
|
353
|
+
|
|
330
354
|
# Display results
|
|
331
355
|
col1, col2, col3, col4 = st.columns(4)
|
|
332
|
-
|
|
356
|
+
|
|
333
357
|
with col1:
|
|
334
|
-
st.metric(
|
|
358
|
+
st.metric(
|
|
359
|
+
"Initial Capital", f"${results['initial_capital']:,.2f}"
|
|
360
|
+
)
|
|
335
361
|
with col2:
|
|
336
362
|
st.metric("Final Value", f"${results['final_value']:,.2f}")
|
|
337
363
|
with col3:
|
|
338
364
|
st.metric("Total Return", f"${results['total_return']:,.2f}")
|
|
339
365
|
with col4:
|
|
340
|
-
st.metric(
|
|
341
|
-
|
|
366
|
+
st.metric(
|
|
367
|
+
"Total Return %", f"{results['total_return_pct']:.2f}%"
|
|
368
|
+
)
|
|
369
|
+
|
|
342
370
|
except Exception as e:
|
|
343
371
|
st.error(f"Backtest failed: {e}")
|
|
344
|
-
|
|
372
|
+
|
|
345
373
|
# Portfolio actions
|
|
346
374
|
st.subheader("⚙️ Portfolio Actions")
|
|
347
|
-
|
|
375
|
+
|
|
348
376
|
col1, col2, col3 = st.columns(3)
|
|
349
|
-
|
|
377
|
+
|
|
350
378
|
with col1:
|
|
351
|
-
if st.button(
|
|
379
|
+
if st.button(
|
|
380
|
+
"Sync with Market", help="Update all positions with current market prices"
|
|
381
|
+
):
|
|
352
382
|
with st.spinner("Syncing with market..."):
|
|
353
383
|
paper_engine.simulate_market_movement(portfolio.id, days=0)
|
|
354
384
|
st.success("Portfolio synced with current market prices!")
|
|
355
385
|
st.rerun()
|
|
356
|
-
|
|
386
|
+
|
|
357
387
|
with col2:
|
|
358
388
|
if st.button("Export Data", help="Export portfolio data to CSV"):
|
|
359
389
|
# Export portfolio data
|
|
@@ -361,29 +391,31 @@ def show_test_portfolio():
|
|
|
361
391
|
if positions:
|
|
362
392
|
positions_data = []
|
|
363
393
|
for pos in positions:
|
|
364
|
-
positions_data.append(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
394
|
+
positions_data.append(
|
|
395
|
+
{
|
|
396
|
+
"Symbol": pos.symbol,
|
|
397
|
+
"Quantity": pos.quantity,
|
|
398
|
+
"Side": pos.side.value,
|
|
399
|
+
"Average Price": float(pos.average_price),
|
|
400
|
+
"Current Price": float(pos.current_price),
|
|
401
|
+
"Market Value": float(pos.market_value),
|
|
402
|
+
"Unrealized P&L": float(pos.unrealized_pnl),
|
|
403
|
+
"Unrealized P&L %": pos.unrealized_pnl_pct,
|
|
404
|
+
"Weight": pos.weight,
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
|
|
376
408
|
positions_df = pd.DataFrame(positions_data)
|
|
377
409
|
csv = positions_df.to_csv(index=False)
|
|
378
410
|
st.download_button(
|
|
379
411
|
label="Download Positions CSV",
|
|
380
412
|
data=csv,
|
|
381
413
|
file_name=f"test_portfolio_positions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
|
|
382
|
-
mime="text/csv"
|
|
414
|
+
mime="text/csv",
|
|
383
415
|
)
|
|
384
416
|
else:
|
|
385
417
|
st.info("No positions to export")
|
|
386
|
-
|
|
418
|
+
|
|
387
419
|
with col3:
|
|
388
420
|
if st.button("Delete Portfolio", help="Delete this test portfolio"):
|
|
389
421
|
if st.session_state.get("confirm_delete", False):
|
|
@@ -400,7 +432,7 @@ def show_test_portfolio():
|
|
|
400
432
|
|
|
401
433
|
finally:
|
|
402
434
|
# Clean up database session
|
|
403
|
-
if
|
|
435
|
+
if "db" in locals() and "session_context" in locals():
|
|
404
436
|
try:
|
|
405
437
|
session_context.__exit__(None, None, None)
|
|
406
438
|
except Exception:
|
|
@@ -420,4 +452,4 @@ except ImportError:
|
|
|
420
452
|
|
|
421
453
|
# Module-level execution only when run directly (not when imported)
|
|
422
454
|
if __name__ == "__main__":
|
|
423
|
-
show_test_portfolio()
|
|
455
|
+
show_test_portfolio()
|