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