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
mcli/ml/trading/paper_trading.py
CHANGED
|
@@ -11,7 +11,13 @@ import yfinance as yf
|
|
|
11
11
|
from sqlalchemy.orm import Session
|
|
12
12
|
|
|
13
13
|
from mcli.ml.trading.models import (
|
|
14
|
-
|
|
14
|
+
OrderSide,
|
|
15
|
+
OrderStatus,
|
|
16
|
+
OrderType,
|
|
17
|
+
Portfolio,
|
|
18
|
+
Position,
|
|
19
|
+
PositionSide,
|
|
20
|
+
TradingOrder,
|
|
15
21
|
)
|
|
16
22
|
from mcli.ml.trading.trading_service import TradingService
|
|
17
23
|
|
|
@@ -20,11 +26,11 @@ logger = logging.getLogger(__name__)
|
|
|
20
26
|
|
|
21
27
|
class PaperTradingEngine:
|
|
22
28
|
"""Paper trading engine for testing strategies without real money"""
|
|
23
|
-
|
|
29
|
+
|
|
24
30
|
def __init__(self, trading_service: TradingService):
|
|
25
31
|
self.trading_service = trading_service
|
|
26
32
|
self.db = trading_service.db
|
|
27
|
-
|
|
33
|
+
|
|
28
34
|
def execute_paper_order(self, order: TradingOrder) -> bool:
|
|
29
35
|
"""Execute a paper trade order"""
|
|
30
36
|
try:
|
|
@@ -33,7 +39,7 @@ class PaperTradingEngine:
|
|
|
33
39
|
if current_price is None:
|
|
34
40
|
logger.error(f"Could not get current price for {order.symbol}")
|
|
35
41
|
return False
|
|
36
|
-
|
|
42
|
+
|
|
37
43
|
# Calculate execution details
|
|
38
44
|
if order.order_type == OrderType.MARKET:
|
|
39
45
|
execution_price = current_price
|
|
@@ -51,26 +57,28 @@ class PaperTradingEngine:
|
|
|
51
57
|
else:
|
|
52
58
|
logger.error(f"Unsupported order type for paper trading: {order.order_type}")
|
|
53
59
|
return False
|
|
54
|
-
|
|
60
|
+
|
|
55
61
|
# Execute the order
|
|
56
62
|
order.status = OrderStatus.FILLED
|
|
57
63
|
order.filled_at = datetime.utcnow()
|
|
58
64
|
order.filled_quantity = order.quantity
|
|
59
65
|
order.remaining_quantity = 0
|
|
60
66
|
order.average_fill_price = Decimal(str(execution_price))
|
|
61
|
-
|
|
67
|
+
|
|
62
68
|
# Update portfolio and positions
|
|
63
69
|
self._update_portfolio_positions(order, execution_price)
|
|
64
|
-
|
|
70
|
+
|
|
65
71
|
self.db.commit()
|
|
66
|
-
logger.info(
|
|
72
|
+
logger.info(
|
|
73
|
+
f"Paper trade executed: {order.symbol} {order.side.value} {order.quantity} @ ${execution_price}"
|
|
74
|
+
)
|
|
67
75
|
return True
|
|
68
|
-
|
|
76
|
+
|
|
69
77
|
except Exception as e:
|
|
70
78
|
logger.error(f"Failed to execute paper order: {e}")
|
|
71
79
|
self.db.rollback()
|
|
72
80
|
return False
|
|
73
|
-
|
|
81
|
+
|
|
74
82
|
def _get_current_price(self, symbol: str) -> Optional[float]:
|
|
75
83
|
"""Get current market price for a symbol"""
|
|
76
84
|
try:
|
|
@@ -82,33 +90,36 @@ class PaperTradingEngine:
|
|
|
82
90
|
except Exception as e:
|
|
83
91
|
logger.error(f"Failed to get price for {symbol}: {e}")
|
|
84
92
|
return None
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
def _update_portfolio_positions(self, order: TradingOrder, execution_price: float):
|
|
87
95
|
"""Update portfolio positions after order execution"""
|
|
88
96
|
try:
|
|
89
97
|
portfolio = self.trading_service.get_portfolio(order.portfolio_id)
|
|
90
98
|
if not portfolio:
|
|
91
99
|
return
|
|
92
|
-
|
|
100
|
+
|
|
93
101
|
# Calculate trade value
|
|
94
102
|
trade_value = execution_price * order.quantity
|
|
95
|
-
|
|
103
|
+
|
|
96
104
|
if order.side == OrderSide.BUY:
|
|
97
105
|
# Buying shares
|
|
98
106
|
portfolio.cash_balance -= Decimal(str(trade_value))
|
|
99
|
-
|
|
107
|
+
|
|
100
108
|
# Update or create position
|
|
101
|
-
position =
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
position = (
|
|
110
|
+
self.db.query(Position)
|
|
111
|
+
.filter(
|
|
112
|
+
Position.portfolio_id == order.portfolio_id, Position.symbol == order.symbol
|
|
113
|
+
)
|
|
114
|
+
.first()
|
|
115
|
+
)
|
|
116
|
+
|
|
106
117
|
if position:
|
|
107
118
|
# Update existing position
|
|
108
119
|
total_quantity = position.quantity + order.quantity
|
|
109
120
|
total_cost = (position.cost_basis * position.quantity) + trade_value
|
|
110
121
|
new_avg_price = total_cost / total_quantity
|
|
111
|
-
|
|
122
|
+
|
|
112
123
|
position.quantity = total_quantity
|
|
113
124
|
position.average_price = new_avg_price
|
|
114
125
|
position.cost_basis = total_cost
|
|
@@ -134,27 +145,30 @@ class PaperTradingEngine:
|
|
|
134
145
|
weight=0.0,
|
|
135
146
|
)
|
|
136
147
|
self.db.add(position)
|
|
137
|
-
|
|
148
|
+
|
|
138
149
|
else: # SELL
|
|
139
150
|
# Selling shares
|
|
140
151
|
portfolio.cash_balance += Decimal(str(trade_value))
|
|
141
|
-
|
|
152
|
+
|
|
142
153
|
# Update position
|
|
143
|
-
position =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
position = (
|
|
155
|
+
self.db.query(Position)
|
|
156
|
+
.filter(
|
|
157
|
+
Position.portfolio_id == order.portfolio_id, Position.symbol == order.symbol
|
|
158
|
+
)
|
|
159
|
+
.first()
|
|
160
|
+
)
|
|
161
|
+
|
|
148
162
|
if position and position.quantity >= order.quantity:
|
|
149
163
|
# Calculate realized P&L
|
|
150
164
|
cost_basis = position.cost_basis * (order.quantity / position.quantity)
|
|
151
165
|
realized_pnl = trade_value - float(cost_basis)
|
|
152
|
-
|
|
166
|
+
|
|
153
167
|
# Update position
|
|
154
168
|
position.quantity -= order.quantity
|
|
155
169
|
position.cost_basis -= cost_basis
|
|
156
170
|
position.realized_pnl += Decimal(str(realized_pnl))
|
|
157
|
-
|
|
171
|
+
|
|
158
172
|
if position.quantity == 0:
|
|
159
173
|
# Remove position if fully sold
|
|
160
174
|
self.db.delete(position)
|
|
@@ -164,140 +178,144 @@ class PaperTradingEngine:
|
|
|
164
178
|
position.market_value = position.quantity * execution_price
|
|
165
179
|
position.unrealized_pnl = position.market_value - position.cost_basis
|
|
166
180
|
if position.cost_basis > 0:
|
|
167
|
-
position.unrealized_pnl_pct = float(
|
|
168
|
-
|
|
181
|
+
position.unrealized_pnl_pct = float(
|
|
182
|
+
position.unrealized_pnl / position.cost_basis * 100
|
|
183
|
+
)
|
|
184
|
+
|
|
169
185
|
# Update portfolio value
|
|
170
186
|
self._update_portfolio_value(portfolio)
|
|
171
|
-
|
|
187
|
+
|
|
172
188
|
except Exception as e:
|
|
173
189
|
logger.error(f"Failed to update portfolio positions: {e}")
|
|
174
190
|
raise
|
|
175
|
-
|
|
191
|
+
|
|
176
192
|
def _update_portfolio_value(self, portfolio: Portfolio):
|
|
177
193
|
"""Update portfolio value and metrics"""
|
|
178
194
|
try:
|
|
179
195
|
# Get all positions
|
|
180
196
|
positions = self.db.query(Position).filter(Position.portfolio_id == portfolio.id).all()
|
|
181
|
-
|
|
197
|
+
|
|
182
198
|
# Calculate total market value
|
|
183
199
|
total_market_value = sum(float(pos.market_value) for pos in positions)
|
|
184
|
-
portfolio.current_value = Decimal(
|
|
185
|
-
|
|
200
|
+
portfolio.current_value = Decimal(
|
|
201
|
+
str(total_market_value + float(portfolio.cash_balance))
|
|
202
|
+
)
|
|
203
|
+
|
|
186
204
|
# Calculate returns
|
|
187
205
|
if portfolio.initial_capital > 0:
|
|
188
206
|
total_return = portfolio.current_value - portfolio.initial_capital
|
|
189
207
|
portfolio.total_return = float(total_return)
|
|
190
208
|
portfolio.total_return_pct = float(total_return / portfolio.initial_capital * 100)
|
|
191
|
-
|
|
209
|
+
|
|
192
210
|
# Update position weights
|
|
193
211
|
for position in positions:
|
|
194
212
|
if portfolio.current_value > 0:
|
|
195
213
|
position.weight = float(position.market_value / portfolio.current_value)
|
|
196
214
|
position.position_size_pct = position.weight * 100
|
|
197
|
-
|
|
215
|
+
|
|
198
216
|
except Exception as e:
|
|
199
217
|
logger.error(f"Failed to update portfolio value: {e}")
|
|
200
218
|
raise
|
|
201
|
-
|
|
219
|
+
|
|
202
220
|
def simulate_market_movement(self, portfolio_id: UUID, days: int = 1):
|
|
203
221
|
"""Simulate market movement for paper trading"""
|
|
204
222
|
try:
|
|
205
223
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
206
224
|
if not portfolio:
|
|
207
225
|
return
|
|
208
|
-
|
|
226
|
+
|
|
209
227
|
positions = self.db.query(Position).filter(Position.portfolio_id == portfolio_id).all()
|
|
210
|
-
|
|
228
|
+
|
|
211
229
|
for position in positions:
|
|
212
230
|
# Get historical data for the symbol
|
|
213
231
|
ticker = yf.Ticker(position.symbol)
|
|
214
232
|
end_date = datetime.now()
|
|
215
|
-
start_date = end_date - timedelta(
|
|
216
|
-
|
|
233
|
+
start_date = end_date - timedelta(
|
|
234
|
+
days=days + 5
|
|
235
|
+
) # Get extra days for weekend handling
|
|
236
|
+
|
|
217
237
|
data = ticker.history(start=start_date, end=end_date)
|
|
218
238
|
if not data.empty:
|
|
219
239
|
# Get the most recent price
|
|
220
240
|
new_price = float(data["Close"].iloc[-1])
|
|
221
|
-
|
|
241
|
+
|
|
222
242
|
# Update position
|
|
223
243
|
position.current_price = Decimal(str(new_price))
|
|
224
244
|
position.market_value = position.quantity * new_price
|
|
225
245
|
position.unrealized_pnl = position.market_value - position.cost_basis
|
|
226
246
|
if position.cost_basis > 0:
|
|
227
|
-
position.unrealized_pnl_pct = float(
|
|
228
|
-
|
|
247
|
+
position.unrealized_pnl_pct = float(
|
|
248
|
+
position.unrealized_pnl / position.cost_basis * 100
|
|
249
|
+
)
|
|
250
|
+
|
|
229
251
|
# Update portfolio value
|
|
230
252
|
self._update_portfolio_value(portfolio)
|
|
231
253
|
self.db.commit()
|
|
232
|
-
|
|
254
|
+
|
|
233
255
|
logger.info(f"Simulated market movement for portfolio {portfolio_id}")
|
|
234
|
-
|
|
256
|
+
|
|
235
257
|
except Exception as e:
|
|
236
258
|
logger.error(f"Failed to simulate market movement: {e}")
|
|
237
259
|
self.db.rollback()
|
|
238
|
-
|
|
260
|
+
|
|
239
261
|
def create_test_portfolio(
|
|
240
|
-
self,
|
|
241
|
-
user_id: UUID,
|
|
242
|
-
name: str = "Test Portfolio",
|
|
243
|
-
initial_capital: float = 100000.0
|
|
262
|
+
self, user_id: UUID, name: str = "Test Portfolio", initial_capital: float = 100000.0
|
|
244
263
|
) -> Portfolio:
|
|
245
264
|
"""Create a test portfolio for paper trading"""
|
|
246
265
|
try:
|
|
247
266
|
# Create trading account
|
|
248
267
|
from mcli.ml.trading.models import TradingAccountCreate
|
|
268
|
+
|
|
249
269
|
account_data = TradingAccountCreate(
|
|
250
|
-
account_name="Test Account",
|
|
251
|
-
account_type="test",
|
|
252
|
-
paper_trading=True
|
|
270
|
+
account_name="Test Account", account_type="test", paper_trading=True
|
|
253
271
|
)
|
|
254
272
|
account = self.trading_service.create_trading_account(user_id, account_data)
|
|
255
|
-
|
|
273
|
+
|
|
256
274
|
# Create portfolio
|
|
257
275
|
portfolio_data = PortfolioCreate(
|
|
258
276
|
name=name,
|
|
259
277
|
description="Test portfolio for paper trading",
|
|
260
|
-
initial_capital=initial_capital
|
|
278
|
+
initial_capital=initial_capital,
|
|
261
279
|
)
|
|
262
280
|
portfolio = self.trading_service.create_portfolio(account.id, portfolio_data)
|
|
263
|
-
|
|
281
|
+
|
|
264
282
|
logger.info(f"Created test portfolio {portfolio.id} for user {user_id}")
|
|
265
283
|
return portfolio
|
|
266
|
-
|
|
284
|
+
|
|
267
285
|
except Exception as e:
|
|
268
286
|
logger.error(f"Failed to create test portfolio: {e}")
|
|
269
287
|
raise
|
|
270
|
-
|
|
288
|
+
|
|
271
289
|
def run_backtest(
|
|
272
|
-
self,
|
|
273
|
-
portfolio_id: UUID,
|
|
274
|
-
start_date: datetime,
|
|
290
|
+
self,
|
|
291
|
+
portfolio_id: UUID,
|
|
292
|
+
start_date: datetime,
|
|
275
293
|
end_date: datetime,
|
|
276
|
-
initial_capital: float = 100000.0
|
|
294
|
+
initial_capital: float = 100000.0,
|
|
277
295
|
) -> Dict:
|
|
278
296
|
"""Run a backtest on historical data"""
|
|
279
297
|
try:
|
|
280
298
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
281
299
|
if not portfolio:
|
|
282
300
|
raise ValueError("Portfolio not found")
|
|
283
|
-
|
|
301
|
+
|
|
284
302
|
# Reset portfolio to initial state
|
|
285
303
|
portfolio.current_value = Decimal(str(initial_capital))
|
|
286
304
|
portfolio.cash_balance = Decimal(str(initial_capital))
|
|
287
305
|
portfolio.total_return = 0.0
|
|
288
306
|
portfolio.total_return_pct = 0.0
|
|
289
|
-
|
|
307
|
+
|
|
290
308
|
# Clear existing positions
|
|
291
309
|
self.db.query(Position).filter(Position.portfolio_id == portfolio_id).delete()
|
|
292
|
-
|
|
310
|
+
|
|
293
311
|
# Get historical data for the period
|
|
294
|
-
date_range = pd.date_range(start=start_date, end=end_date, freq=
|
|
295
|
-
|
|
312
|
+
date_range = pd.date_range(start=start_date, end=end_date, freq="D")
|
|
313
|
+
|
|
296
314
|
# This is a simplified backtest - in practice you'd want to:
|
|
297
315
|
# 1. Get historical signals
|
|
298
316
|
# 2. Execute trades based on signals
|
|
299
317
|
# 3. Track performance over time
|
|
300
|
-
|
|
318
|
+
|
|
301
319
|
backtest_results = {
|
|
302
320
|
"start_date": start_date,
|
|
303
321
|
"end_date": end_date,
|
|
@@ -309,11 +327,11 @@ class PaperTradingEngine:
|
|
|
309
327
|
"max_drawdown": 0.0,
|
|
310
328
|
"sharpe_ratio": 0.0,
|
|
311
329
|
}
|
|
312
|
-
|
|
330
|
+
|
|
313
331
|
self.db.commit()
|
|
314
332
|
logger.info(f"Backtest completed for portfolio {portfolio_id}")
|
|
315
333
|
return backtest_results
|
|
316
|
-
|
|
334
|
+
|
|
317
335
|
except Exception as e:
|
|
318
336
|
logger.error(f"Failed to run backtest: {e}")
|
|
319
337
|
self.db.rollback()
|
|
@@ -323,4 +341,4 @@ class PaperTradingEngine:
|
|
|
323
341
|
def create_paper_trading_engine(db_session: Session) -> PaperTradingEngine:
|
|
324
342
|
"""Create a paper trading engine"""
|
|
325
343
|
trading_service = TradingService(db_session)
|
|
326
|
-
return PaperTradingEngine(trading_service)
|
|
344
|
+
return PaperTradingEngine(trading_service)
|