mcli-framework 7.12.0__py3-none-any.whl → 7.12.3__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/__init__.py +0 -2
- mcli/app/commands_cmd.py +19 -23
- mcli/app/completion_helpers.py +5 -5
- mcli/app/init_cmd.py +10 -10
- mcli/app/lock_cmd.py +82 -27
- mcli/app/main.py +4 -50
- mcli/app/model/model.py +5 -10
- mcli/app/store_cmd.py +8 -8
- mcli/app/video/__init__.py +0 -2
- mcli/app/video/video.py +1 -14
- mcli/chat/chat.py +90 -108
- mcli/chat/command_rag.py +0 -4
- mcli/chat/enhanced_chat.py +32 -41
- mcli/chat/system_controller.py +37 -37
- mcli/chat/system_integration.py +4 -5
- mcli/cli.py +2 -3
- mcli/lib/api/api.py +4 -9
- mcli/lib/api/daemon_client.py +19 -20
- mcli/lib/api/daemon_client_local.py +1 -3
- mcli/lib/api/daemon_decorator.py +6 -6
- mcli/lib/api/mcli_decorators.py +4 -8
- mcli/lib/auth/__init__.py +0 -1
- mcli/lib/auth/auth.py +4 -5
- mcli/lib/auth/mcli_manager.py +7 -12
- mcli/lib/auth/token_util.py +5 -5
- mcli/lib/config/__init__.py +29 -1
- mcli/lib/config/config.py +0 -1
- mcli/lib/custom_commands.py +1 -1
- mcli/lib/discovery/command_discovery.py +15 -15
- mcli/lib/erd/erd.py +7 -7
- mcli/lib/files/files.py +1 -1
- mcli/lib/fs/__init__.py +31 -1
- mcli/lib/fs/fs.py +12 -13
- mcli/lib/lib.py +0 -1
- mcli/lib/logger/logger.py +7 -10
- mcli/lib/performance/optimizer.py +25 -27
- mcli/lib/performance/rust_bridge.py +22 -27
- mcli/lib/performance/uvloop_config.py +0 -1
- mcli/lib/pickles/__init__.py +0 -1
- mcli/lib/pickles/pickles.py +0 -2
- mcli/lib/secrets/commands.py +0 -2
- mcli/lib/secrets/manager.py +0 -1
- mcli/lib/secrets/repl.py +2 -3
- mcli/lib/secrets/store.py +1 -2
- mcli/lib/services/data_pipeline.py +34 -34
- mcli/lib/services/lsh_client.py +38 -40
- mcli/lib/shell/shell.py +2 -2
- mcli/lib/toml/__init__.py +0 -1
- mcli/lib/ui/styling.py +0 -1
- mcli/lib/ui/visual_effects.py +33 -41
- mcli/lib/watcher/watcher.py +0 -1
- mcli/ml/__init__.py +1 -1
- mcli/ml/api/__init__.py +1 -1
- mcli/ml/api/app.py +8 -9
- mcli/ml/api/middleware.py +10 -10
- mcli/ml/api/routers/__init__.py +1 -1
- mcli/ml/api/routers/admin_router.py +3 -3
- mcli/ml/api/routers/auth_router.py +17 -18
- mcli/ml/api/routers/backtest_router.py +2 -2
- mcli/ml/api/routers/data_router.py +2 -2
- mcli/ml/api/routers/model_router.py +14 -15
- mcli/ml/api/routers/monitoring_router.py +2 -2
- mcli/ml/api/routers/portfolio_router.py +2 -2
- mcli/ml/api/routers/prediction_router.py +10 -9
- mcli/ml/api/routers/trade_router.py +2 -2
- mcli/ml/api/routers/websocket_router.py +6 -7
- mcli/ml/api/schemas.py +2 -2
- mcli/ml/auth/__init__.py +1 -1
- mcli/ml/auth/auth_manager.py +22 -23
- mcli/ml/auth/models.py +17 -17
- mcli/ml/auth/permissions.py +17 -17
- mcli/ml/backtesting/__init__.py +1 -1
- mcli/ml/backtesting/backtest_engine.py +31 -35
- mcli/ml/backtesting/performance_metrics.py +12 -14
- mcli/ml/backtesting/run.py +1 -2
- mcli/ml/cache.py +35 -36
- mcli/ml/cli/__init__.py +1 -1
- mcli/ml/cli/main.py +21 -24
- mcli/ml/config/__init__.py +1 -1
- mcli/ml/config/settings.py +28 -29
- mcli/ml/configs/__init__.py +1 -1
- mcli/ml/configs/dvc_config.py +14 -15
- mcli/ml/configs/mlflow_config.py +12 -13
- mcli/ml/configs/mlops_manager.py +19 -21
- mcli/ml/dashboard/__init__.py +4 -4
- mcli/ml/dashboard/app.py +20 -30
- mcli/ml/dashboard/app_supabase.py +16 -19
- mcli/ml/dashboard/app_training.py +11 -14
- mcli/ml/dashboard/cli.py +2 -2
- mcli/ml/dashboard/common.py +2 -3
- mcli/ml/dashboard/components/__init__.py +1 -1
- mcli/ml/dashboard/components/charts.py +13 -11
- mcli/ml/dashboard/components/metrics.py +7 -7
- mcli/ml/dashboard/components/tables.py +12 -9
- mcli/ml/dashboard/overview.py +2 -2
- mcli/ml/dashboard/pages/__init__.py +1 -1
- mcli/ml/dashboard/pages/cicd.py +15 -18
- mcli/ml/dashboard/pages/debug_dependencies.py +7 -7
- mcli/ml/dashboard/pages/monte_carlo_predictions.py +11 -18
- mcli/ml/dashboard/pages/predictions_enhanced.py +24 -32
- mcli/ml/dashboard/pages/scrapers_and_logs.py +22 -24
- mcli/ml/dashboard/pages/test_portfolio.py +3 -6
- mcli/ml/dashboard/pages/trading.py +16 -18
- mcli/ml/dashboard/pages/workflows.py +20 -30
- mcli/ml/dashboard/utils.py +9 -9
- mcli/ml/dashboard/warning_suppression.py +3 -3
- mcli/ml/data_ingestion/__init__.py +1 -1
- mcli/ml/data_ingestion/api_connectors.py +41 -46
- mcli/ml/data_ingestion/data_pipeline.py +36 -46
- mcli/ml/data_ingestion/stream_processor.py +43 -46
- mcli/ml/database/__init__.py +1 -1
- mcli/ml/database/migrations/env.py +2 -2
- mcli/ml/database/models.py +22 -24
- mcli/ml/database/session.py +14 -14
- mcli/ml/experimentation/__init__.py +1 -1
- mcli/ml/experimentation/ab_testing.py +45 -46
- mcli/ml/features/__init__.py +1 -1
- mcli/ml/features/ensemble_features.py +22 -27
- mcli/ml/features/recommendation_engine.py +30 -30
- mcli/ml/features/stock_features.py +29 -32
- mcli/ml/features/test_feature_engineering.py +10 -11
- mcli/ml/logging.py +4 -4
- mcli/ml/mlops/__init__.py +1 -1
- mcli/ml/mlops/data_versioning.py +29 -30
- mcli/ml/mlops/experiment_tracker.py +24 -24
- mcli/ml/mlops/model_serving.py +31 -34
- mcli/ml/mlops/pipeline_orchestrator.py +27 -35
- mcli/ml/models/__init__.py +5 -6
- mcli/ml/models/base_models.py +23 -23
- mcli/ml/models/ensemble_models.py +31 -31
- mcli/ml/models/recommendation_models.py +18 -19
- mcli/ml/models/test_models.py +14 -16
- mcli/ml/monitoring/__init__.py +1 -1
- mcli/ml/monitoring/drift_detection.py +32 -36
- mcli/ml/monitoring/metrics.py +2 -2
- mcli/ml/optimization/__init__.py +1 -1
- mcli/ml/optimization/optimize.py +1 -2
- mcli/ml/optimization/portfolio_optimizer.py +30 -32
- mcli/ml/predictions/__init__.py +1 -1
- mcli/ml/preprocessing/__init__.py +1 -1
- mcli/ml/preprocessing/data_cleaners.py +22 -23
- mcli/ml/preprocessing/feature_extractors.py +23 -26
- mcli/ml/preprocessing/ml_pipeline.py +23 -23
- mcli/ml/preprocessing/test_preprocessing.py +7 -8
- mcli/ml/scripts/populate_sample_data.py +0 -4
- mcli/ml/serving/serve.py +1 -2
- mcli/ml/tasks.py +17 -17
- mcli/ml/tests/test_integration.py +29 -30
- mcli/ml/tests/test_training_dashboard.py +21 -21
- mcli/ml/trading/__init__.py +1 -1
- mcli/ml/trading/migrations.py +5 -5
- mcli/ml/trading/models.py +21 -23
- mcli/ml/trading/paper_trading.py +16 -13
- mcli/ml/trading/risk_management.py +17 -18
- mcli/ml/trading/trading_service.py +25 -28
- mcli/ml/training/__init__.py +1 -1
- mcli/ml/training/train.py +0 -1
- mcli/public/oi/oi.py +1 -2
- mcli/self/completion_cmd.py +6 -10
- mcli/self/logs_cmd.py +19 -24
- mcli/self/migrate_cmd.py +22 -20
- mcli/self/redis_cmd.py +10 -11
- mcli/self/self_cmd.py +62 -18
- mcli/self/store_cmd.py +10 -12
- mcli/self/visual_cmd.py +9 -14
- mcli/self/zsh_cmd.py +2 -4
- mcli/workflow/daemon/async_command_database.py +23 -24
- mcli/workflow/daemon/async_process_manager.py +27 -29
- mcli/workflow/daemon/client.py +27 -33
- mcli/workflow/daemon/daemon.py +32 -36
- mcli/workflow/daemon/enhanced_daemon.py +24 -33
- mcli/workflow/daemon/process_cli.py +11 -12
- mcli/workflow/daemon/process_manager.py +23 -26
- mcli/workflow/daemon/test_daemon.py +4 -5
- mcli/workflow/dashboard/dashboard_cmd.py +0 -1
- mcli/workflow/doc_convert.py +15 -17
- mcli/workflow/gcloud/__init__.py +0 -1
- mcli/workflow/gcloud/gcloud.py +11 -8
- mcli/workflow/git_commit/ai_service.py +14 -15
- mcli/workflow/lsh_integration.py +9 -11
- mcli/workflow/model_service/client.py +26 -31
- mcli/workflow/model_service/download_and_run_efficient_models.py +10 -14
- mcli/workflow/model_service/lightweight_embedder.py +25 -35
- mcli/workflow/model_service/lightweight_model_server.py +26 -32
- mcli/workflow/model_service/lightweight_test.py +7 -10
- mcli/workflow/model_service/model_service.py +80 -91
- mcli/workflow/model_service/ollama_efficient_runner.py +14 -18
- mcli/workflow/model_service/openai_adapter.py +23 -23
- mcli/workflow/model_service/pdf_processor.py +21 -26
- mcli/workflow/model_service/test_efficient_runner.py +12 -16
- mcli/workflow/model_service/test_example.py +11 -13
- mcli/workflow/model_service/test_integration.py +3 -5
- mcli/workflow/model_service/test_new_features.py +7 -8
- mcli/workflow/notebook/converter.py +1 -1
- mcli/workflow/notebook/notebook_cmd.py +5 -6
- mcli/workflow/notebook/schema.py +0 -1
- mcli/workflow/notebook/validator.py +7 -3
- mcli/workflow/openai/openai.py +1 -2
- mcli/workflow/registry/registry.py +4 -1
- mcli/workflow/repo/repo.py +6 -7
- mcli/workflow/scheduler/cron_parser.py +16 -19
- mcli/workflow/scheduler/job.py +10 -10
- mcli/workflow/scheduler/monitor.py +15 -15
- mcli/workflow/scheduler/persistence.py +17 -18
- mcli/workflow/scheduler/scheduler.py +37 -38
- mcli/workflow/secrets/__init__.py +1 -1
- mcli/workflow/sync/test_cmd.py +0 -1
- mcli/workflow/wakatime/__init__.py +5 -9
- mcli/workflow/wakatime/wakatime.py +1 -2
- {mcli_framework-7.12.0.dist-info → mcli_framework-7.12.3.dist-info}/METADATA +1 -1
- mcli_framework-7.12.3.dist-info/RECORD +279 -0
- mcli_framework-7.12.0.dist-info/RECORD +0 -279
- {mcli_framework-7.12.0.dist-info → mcli_framework-7.12.3.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.0.dist-info → mcli_framework-7.12.3.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.0.dist-info → mcli_framework-7.12.3.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.0.dist-info → mcli_framework-7.12.3.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Unit tests for training dashboard functionality"""
|
|
1
|
+
"""Unit tests for training dashboard functionality."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
|
-
from unittest.mock import MagicMock, Mock
|
|
4
|
+
from unittest.mock import MagicMock, Mock
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
@@ -12,17 +12,17 @@ from mcli.ml.database.models import Experiment, Model, ModelStatus
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class TestTrainingDashboard:
|
|
15
|
-
"""Test suite for training dashboard functions"""
|
|
15
|
+
"""Test suite for training dashboard functions."""
|
|
16
16
|
|
|
17
17
|
@pytest.fixture
|
|
18
18
|
def mock_db_session(self):
|
|
19
|
-
"""Create mock database session"""
|
|
19
|
+
"""Create mock database session."""
|
|
20
20
|
session = MagicMock(spec=Session)
|
|
21
21
|
return session
|
|
22
22
|
|
|
23
23
|
@pytest.fixture
|
|
24
24
|
def sample_models(self):
|
|
25
|
-
"""Create sample model data"""
|
|
25
|
+
"""Create sample model data."""
|
|
26
26
|
models = []
|
|
27
27
|
|
|
28
28
|
# Bitcoin-style model comparison data
|
|
@@ -120,7 +120,7 @@ class TestTrainingDashboard:
|
|
|
120
120
|
|
|
121
121
|
@pytest.fixture
|
|
122
122
|
def sample_experiments(self):
|
|
123
|
-
"""Create sample experiment data"""
|
|
123
|
+
"""Create sample experiment data."""
|
|
124
124
|
experiments = []
|
|
125
125
|
|
|
126
126
|
for i in range(10):
|
|
@@ -147,7 +147,7 @@ class TestTrainingDashboard:
|
|
|
147
147
|
return experiments
|
|
148
148
|
|
|
149
149
|
def test_model_comparison_metrics(self, sample_models):
|
|
150
|
-
"""Test model comparison metrics calculation"""
|
|
150
|
+
"""Test model comparison metrics calculation."""
|
|
151
151
|
# Convert to DataFrame as the dashboard would
|
|
152
152
|
df = pd.DataFrame(
|
|
153
153
|
[
|
|
@@ -176,7 +176,7 @@ class TestTrainingDashboard:
|
|
|
176
176
|
assert sorted_by_mae.iloc[0]["test_mae"] < 125
|
|
177
177
|
|
|
178
178
|
def test_model_performance_aggregation(self, sample_models):
|
|
179
|
-
"""Test aggregation of model performance"""
|
|
179
|
+
"""Test aggregation of model performance."""
|
|
180
180
|
metrics = {
|
|
181
181
|
"total_models": len(sample_models),
|
|
182
182
|
"deployed_models": sum(1 for m in sample_models if m.status == ModelStatus.DEPLOYED),
|
|
@@ -190,7 +190,7 @@ class TestTrainingDashboard:
|
|
|
190
190
|
assert 0.75 < metrics["avg_r2"] < 0.85
|
|
191
191
|
|
|
192
192
|
def test_feature_importance_calculation(self, sample_models):
|
|
193
|
-
"""Test feature importance extraction and ranking"""
|
|
193
|
+
"""Test feature importance extraction and ranking."""
|
|
194
194
|
model = sample_models[0]
|
|
195
195
|
|
|
196
196
|
# Simulate feature importance
|
|
@@ -208,7 +208,7 @@ class TestTrainingDashboard:
|
|
|
208
208
|
assert all(top_5["importance"] > 0)
|
|
209
209
|
|
|
210
210
|
def test_residuals_analysis(self):
|
|
211
|
-
"""Test residual analysis calculations"""
|
|
211
|
+
"""Test residual analysis calculations."""
|
|
212
212
|
# Generate sample predictions and actuals
|
|
213
213
|
np.random.seed(42)
|
|
214
214
|
n = 500
|
|
@@ -234,7 +234,7 @@ class TestTrainingDashboard:
|
|
|
234
234
|
assert 0 <= p_value <= 1
|
|
235
235
|
|
|
236
236
|
def test_cross_validation_metrics(self):
|
|
237
|
-
"""Test cross-validation metrics calculation"""
|
|
237
|
+
"""Test cross-validation metrics calculation."""
|
|
238
238
|
# Simulate CV scores
|
|
239
239
|
cv_scores = [0.80, 0.82, 0.78, 0.85, 0.79]
|
|
240
240
|
|
|
@@ -248,7 +248,7 @@ class TestTrainingDashboard:
|
|
|
248
248
|
assert cv_std / cv_mean < 0.1 # Coefficient of variation < 10%
|
|
249
249
|
|
|
250
250
|
def test_training_duration_analysis(self, sample_experiments):
|
|
251
|
-
"""Test training duration analysis"""
|
|
251
|
+
"""Test training duration analysis."""
|
|
252
252
|
completed = [exp for exp in sample_experiments if exp.status == "completed"]
|
|
253
253
|
|
|
254
254
|
durations = [exp.duration_seconds for exp in completed]
|
|
@@ -260,7 +260,7 @@ class TestTrainingDashboard:
|
|
|
260
260
|
assert min_duration <= avg_duration <= max_duration
|
|
261
261
|
|
|
262
262
|
def test_model_comparison_ranking(self, sample_models):
|
|
263
|
-
"""Test ranking models by multiple metrics"""
|
|
263
|
+
"""Test ranking models by multiple metrics."""
|
|
264
264
|
df = pd.DataFrame(
|
|
265
265
|
[
|
|
266
266
|
{
|
|
@@ -289,7 +289,7 @@ class TestTrainingDashboard:
|
|
|
289
289
|
assert best_overall["test_rmse"] < 160
|
|
290
290
|
|
|
291
291
|
def test_feature_categorization(self):
|
|
292
|
-
"""Test feature categorization (lag, MA, volatility, etc.)"""
|
|
292
|
+
"""Test feature categorization (lag, MA, volatility, etc.)."""
|
|
293
293
|
features = [
|
|
294
294
|
"lag_1",
|
|
295
295
|
"lag_7",
|
|
@@ -329,7 +329,7 @@ class TestTrainingDashboard:
|
|
|
329
329
|
assert len(categories["Technical"]) == 3
|
|
330
330
|
|
|
331
331
|
def test_mape_calculation(self):
|
|
332
|
-
"""Test Mean Absolute Percentage Error calculation"""
|
|
332
|
+
"""Test Mean Absolute Percentage Error calculation."""
|
|
333
333
|
actual = np.array([100, 200, 150, 300, 250])
|
|
334
334
|
predicted = np.array([105, 195, 160, 295, 245])
|
|
335
335
|
|
|
@@ -339,7 +339,7 @@ class TestTrainingDashboard:
|
|
|
339
339
|
assert mape < 10 # Should be reasonably low for good predictions
|
|
340
340
|
|
|
341
341
|
def test_error_metrics_comparison(self):
|
|
342
|
-
"""Test that RMSE >= MAE for any predictions"""
|
|
342
|
+
"""Test that RMSE >= MAE for any predictions."""
|
|
343
343
|
# This is a mathematical property: RMSE is always >= MAE
|
|
344
344
|
|
|
345
345
|
errors = np.array([5, 3, 8, 2, 10])
|
|
@@ -350,7 +350,7 @@ class TestTrainingDashboard:
|
|
|
350
350
|
assert rmse >= mae
|
|
351
351
|
|
|
352
352
|
def test_r2_score_properties(self):
|
|
353
|
-
"""Test R² score properties"""
|
|
353
|
+
"""Test R² score properties."""
|
|
354
354
|
# Perfect predictions
|
|
355
355
|
y_true = np.array([1, 2, 3, 4, 5])
|
|
356
356
|
y_pred = np.array([1, 2, 3, 4, 5])
|
|
@@ -369,7 +369,7 @@ class TestTrainingDashboard:
|
|
|
369
369
|
assert r2_random < 1.0
|
|
370
370
|
|
|
371
371
|
def test_experiment_status_distribution(self, sample_experiments):
|
|
372
|
-
"""Test experiment status distribution"""
|
|
372
|
+
"""Test experiment status distribution."""
|
|
373
373
|
status_counts = {}
|
|
374
374
|
for exp in sample_experiments:
|
|
375
375
|
status_counts[exp.status] = status_counts.get(exp.status, 0) + 1
|
|
@@ -381,10 +381,10 @@ class TestTrainingDashboard:
|
|
|
381
381
|
|
|
382
382
|
|
|
383
383
|
class TestModelVersioning:
|
|
384
|
-
"""Test model versioning functionality"""
|
|
384
|
+
"""Test model versioning functionality."""
|
|
385
385
|
|
|
386
386
|
def test_version_comparison(self):
|
|
387
|
-
"""Test semantic version comparison"""
|
|
387
|
+
"""Test semantic version comparison."""
|
|
388
388
|
versions = ["1.0.0", "1.1.0", "1.0.1", "2.0.0", "1.2.0"]
|
|
389
389
|
|
|
390
390
|
# Parse and sort versions
|
|
@@ -395,7 +395,7 @@ class TestModelVersioning:
|
|
|
395
395
|
assert sorted_versions[-1] == (2, 0, 0)
|
|
396
396
|
|
|
397
397
|
def test_model_deployment_tracking(self):
|
|
398
|
-
"""Test tracking which models are deployed"""
|
|
398
|
+
"""Test tracking which models are deployed."""
|
|
399
399
|
models = [
|
|
400
400
|
{"name": "model-a", "version": "1.0.0", "deployed": True},
|
|
401
401
|
{"name": "model-a", "version": "1.1.0", "deployed": False},
|
mcli/ml/trading/__init__.py
CHANGED
mcli/ml/trading/migrations.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Database migrations for trading functionality"""
|
|
1
|
+
"""Database migrations for trading functionality."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def create_trading_tables():
|
|
15
|
-
"""Create trading-related tables in the database"""
|
|
15
|
+
"""Create trading-related tables in the database."""
|
|
16
16
|
try:
|
|
17
17
|
# Get database URL from settings
|
|
18
18
|
settings = get_settings()
|
|
@@ -30,7 +30,7 @@ def create_trading_tables():
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def drop_trading_tables():
|
|
33
|
-
"""Drop trading-related tables from the database"""
|
|
33
|
+
"""Drop trading-related tables from the database."""
|
|
34
34
|
try:
|
|
35
35
|
# Get database URL from settings
|
|
36
36
|
settings = get_settings()
|
|
@@ -48,7 +48,7 @@ def drop_trading_tables():
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def migrate_trading_data():
|
|
51
|
-
"""Migrate existing data to new trading schema"""
|
|
51
|
+
"""Migrate existing data to new trading schema."""
|
|
52
52
|
try:
|
|
53
53
|
# Get database URL from settings
|
|
54
54
|
settings = get_settings()
|
|
@@ -69,7 +69,7 @@ def migrate_trading_data():
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def verify_trading_schema():
|
|
72
|
-
"""Verify that trading schema is properly set up"""
|
|
72
|
+
"""Verify that trading schema is properly set up."""
|
|
73
73
|
try:
|
|
74
74
|
# Get database URL from settings
|
|
75
75
|
settings = get_settings()
|
mcli/ml/trading/models.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
"""Trading and portfolio models for the ML system"""
|
|
1
|
+
"""Trading and portfolio models for the ML system."""
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
3
|
from datetime import datetime
|
|
5
|
-
from decimal import Decimal
|
|
6
4
|
from enum import Enum
|
|
7
|
-
from typing import
|
|
5
|
+
from typing import List, Optional
|
|
8
6
|
from uuid import UUID, uuid4
|
|
9
7
|
|
|
10
8
|
from pydantic import BaseModel, Field
|
|
@@ -19,7 +17,7 @@ Base = declarative_base()
|
|
|
19
17
|
|
|
20
18
|
|
|
21
19
|
class OrderStatus(Enum):
|
|
22
|
-
"""Order status enumeration"""
|
|
20
|
+
"""Order status enumeration."""
|
|
23
21
|
|
|
24
22
|
PENDING = "pending"
|
|
25
23
|
SUBMITTED = "submitted"
|
|
@@ -31,7 +29,7 @@ class OrderStatus(Enum):
|
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
class OrderType(Enum):
|
|
34
|
-
"""Order type enumeration"""
|
|
32
|
+
"""Order type enumeration."""
|
|
35
33
|
|
|
36
34
|
MARKET = "market"
|
|
37
35
|
LIMIT = "limit"
|
|
@@ -41,21 +39,21 @@ class OrderType(Enum):
|
|
|
41
39
|
|
|
42
40
|
|
|
43
41
|
class OrderSide(Enum):
|
|
44
|
-
"""Order side enumeration"""
|
|
42
|
+
"""Order side enumeration."""
|
|
45
43
|
|
|
46
44
|
BUY = "buy"
|
|
47
45
|
SELL = "sell"
|
|
48
46
|
|
|
49
47
|
|
|
50
48
|
class PositionSide(Enum):
|
|
51
|
-
"""Position side enumeration"""
|
|
49
|
+
"""Position side enumeration."""
|
|
52
50
|
|
|
53
51
|
LONG = "long"
|
|
54
52
|
SHORT = "short"
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
class PortfolioType(Enum):
|
|
58
|
-
"""Portfolio type enumeration"""
|
|
56
|
+
"""Portfolio type enumeration."""
|
|
59
57
|
|
|
60
58
|
TEST = "test"
|
|
61
59
|
LIVE = "live"
|
|
@@ -63,7 +61,7 @@ class PortfolioType(Enum):
|
|
|
63
61
|
|
|
64
62
|
|
|
65
63
|
class RiskLevel(Enum):
|
|
66
|
-
"""Risk level enumeration"""
|
|
64
|
+
"""Risk level enumeration."""
|
|
67
65
|
|
|
68
66
|
CONSERVATIVE = "conservative"
|
|
69
67
|
MODERATE = "moderate"
|
|
@@ -72,7 +70,7 @@ class RiskLevel(Enum):
|
|
|
72
70
|
|
|
73
71
|
# Database Models
|
|
74
72
|
class TradingAccount(Base):
|
|
75
|
-
"""Trading account information"""
|
|
73
|
+
"""Trading account information."""
|
|
76
74
|
|
|
77
75
|
__tablename__ = "trading_accounts"
|
|
78
76
|
|
|
@@ -103,7 +101,7 @@ class TradingAccount(Base):
|
|
|
103
101
|
|
|
104
102
|
|
|
105
103
|
class Portfolio(Base):
|
|
106
|
-
"""Portfolio information"""
|
|
104
|
+
"""Portfolio information."""
|
|
107
105
|
|
|
108
106
|
__tablename__ = "portfolios"
|
|
109
107
|
|
|
@@ -148,7 +146,7 @@ class Portfolio(Base):
|
|
|
148
146
|
|
|
149
147
|
|
|
150
148
|
class Position(Base):
|
|
151
|
-
"""Individual position in a portfolio"""
|
|
149
|
+
"""Individual position in a portfolio."""
|
|
152
150
|
|
|
153
151
|
__tablename__ = "positions"
|
|
154
152
|
|
|
@@ -186,7 +184,7 @@ class Position(Base):
|
|
|
186
184
|
|
|
187
185
|
|
|
188
186
|
class TradingOrder(Base):
|
|
189
|
-
"""Trading order information"""
|
|
187
|
+
"""Trading order information."""
|
|
190
188
|
|
|
191
189
|
__tablename__ = "trading_orders"
|
|
192
190
|
|
|
@@ -238,7 +236,7 @@ class TradingOrder(Base):
|
|
|
238
236
|
|
|
239
237
|
|
|
240
238
|
class PortfolioPerformanceSnapshot(Base):
|
|
241
|
-
"""Daily portfolio performance snapshots"""
|
|
239
|
+
"""Daily portfolio performance snapshots."""
|
|
242
240
|
|
|
243
241
|
__tablename__ = "portfolio_performance_snapshots"
|
|
244
242
|
|
|
@@ -277,7 +275,7 @@ class PortfolioPerformanceSnapshot(Base):
|
|
|
277
275
|
|
|
278
276
|
|
|
279
277
|
class TradingSignal(Base):
|
|
280
|
-
"""Trading signals generated by ML models"""
|
|
278
|
+
"""Trading signals generated by ML models."""
|
|
281
279
|
|
|
282
280
|
__tablename__ = "trading_signals"
|
|
283
281
|
|
|
@@ -318,7 +316,7 @@ class TradingSignal(Base):
|
|
|
318
316
|
|
|
319
317
|
# Pydantic Models for API
|
|
320
318
|
class TradingAccountCreate(BaseModel):
|
|
321
|
-
"""Create trading account request"""
|
|
319
|
+
"""Create trading account request."""
|
|
322
320
|
|
|
323
321
|
account_name: str = Field(..., min_length=1, max_length=100)
|
|
324
322
|
account_type: PortfolioType = Field(default=PortfolioType.TEST)
|
|
@@ -331,7 +329,7 @@ class TradingAccountCreate(BaseModel):
|
|
|
331
329
|
|
|
332
330
|
|
|
333
331
|
class PortfolioCreate(BaseModel):
|
|
334
|
-
"""Create portfolio request"""
|
|
332
|
+
"""Create portfolio request."""
|
|
335
333
|
|
|
336
334
|
name: str = Field(..., min_length=1, max_length=100)
|
|
337
335
|
description: Optional[str] = None
|
|
@@ -339,7 +337,7 @@ class PortfolioCreate(BaseModel):
|
|
|
339
337
|
|
|
340
338
|
|
|
341
339
|
class OrderCreate(BaseModel):
|
|
342
|
-
"""Create order request"""
|
|
340
|
+
"""Create order request."""
|
|
343
341
|
|
|
344
342
|
symbol: str = Field(..., min_length=1, max_length=10)
|
|
345
343
|
side: OrderSide
|
|
@@ -352,7 +350,7 @@ class OrderCreate(BaseModel):
|
|
|
352
350
|
|
|
353
351
|
|
|
354
352
|
class PositionResponse(BaseModel):
|
|
355
|
-
"""Position response model"""
|
|
353
|
+
"""Position response model."""
|
|
356
354
|
|
|
357
355
|
id: UUID
|
|
358
356
|
symbol: str
|
|
@@ -372,7 +370,7 @@ class PositionResponse(BaseModel):
|
|
|
372
370
|
|
|
373
371
|
|
|
374
372
|
class OrderResponse(BaseModel):
|
|
375
|
-
"""Order response model"""
|
|
373
|
+
"""Order response model."""
|
|
376
374
|
|
|
377
375
|
id: UUID
|
|
378
376
|
symbol: str
|
|
@@ -395,7 +393,7 @@ class OrderResponse(BaseModel):
|
|
|
395
393
|
|
|
396
394
|
|
|
397
395
|
class PortfolioResponse(BaseModel):
|
|
398
|
-
"""Portfolio response model"""
|
|
396
|
+
"""Portfolio response model."""
|
|
399
397
|
|
|
400
398
|
id: UUID
|
|
401
399
|
name: str
|
|
@@ -418,7 +416,7 @@ class PortfolioResponse(BaseModel):
|
|
|
418
416
|
|
|
419
417
|
|
|
420
418
|
class TradingSignalResponse(BaseModel):
|
|
421
|
-
"""Trading signal response model"""
|
|
419
|
+
"""Trading signal response model."""
|
|
422
420
|
|
|
423
421
|
id: UUID
|
|
424
422
|
symbol: str
|
mcli/ml/trading/paper_trading.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Paper trading implementation for testing portfolios without real money"""
|
|
1
|
+
"""Paper trading implementation for testing portfolios without real money."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
5
|
from decimal import Decimal
|
|
6
|
-
from typing import Dict,
|
|
6
|
+
from typing import Dict, Optional
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
9
|
import pandas as pd
|
|
@@ -15,6 +15,7 @@ from mcli.ml.trading.models import (
|
|
|
15
15
|
OrderStatus,
|
|
16
16
|
OrderType,
|
|
17
17
|
Portfolio,
|
|
18
|
+
PortfolioCreate,
|
|
18
19
|
Position,
|
|
19
20
|
PositionSide,
|
|
20
21
|
TradingOrder,
|
|
@@ -25,14 +26,14 @@ logger = logging.getLogger(__name__)
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class PaperTradingEngine:
|
|
28
|
-
"""Paper trading engine for testing strategies without real money"""
|
|
29
|
+
"""Paper trading engine for testing strategies without real money."""
|
|
29
30
|
|
|
30
31
|
def __init__(self, trading_service: TradingService):
|
|
31
32
|
self.trading_service = trading_service
|
|
32
33
|
self.db = trading_service.db
|
|
33
34
|
|
|
34
35
|
def execute_paper_order(self, order: TradingOrder) -> bool:
|
|
35
|
-
"""Execute a paper trade order"""
|
|
36
|
+
"""Execute a paper trade order."""
|
|
36
37
|
try:
|
|
37
38
|
# Get current market price
|
|
38
39
|
current_price = self._get_current_price(order.symbol)
|
|
@@ -44,7 +45,9 @@ class PaperTradingEngine:
|
|
|
44
45
|
if order.order_type == OrderType.MARKET:
|
|
45
46
|
execution_price = current_price
|
|
46
47
|
elif order.order_type == OrderType.LIMIT:
|
|
47
|
-
if
|
|
48
|
+
if (
|
|
49
|
+
order.side == OrderSide.BUY and order.limit_price >= current_price
|
|
50
|
+
): # noqa: SIM114
|
|
48
51
|
execution_price = current_price
|
|
49
52
|
elif order.side == OrderSide.SELL and order.limit_price <= current_price:
|
|
50
53
|
execution_price = current_price
|
|
@@ -80,7 +83,7 @@ class PaperTradingEngine:
|
|
|
80
83
|
return False
|
|
81
84
|
|
|
82
85
|
def _get_current_price(self, symbol: str) -> Optional[float]:
|
|
83
|
-
"""Get current market price for a symbol"""
|
|
86
|
+
"""Get current market price for a symbol."""
|
|
84
87
|
try:
|
|
85
88
|
ticker = yf.Ticker(symbol)
|
|
86
89
|
data = ticker.history(period="1d", interval="1m")
|
|
@@ -92,7 +95,7 @@ class PaperTradingEngine:
|
|
|
92
95
|
return None
|
|
93
96
|
|
|
94
97
|
def _update_portfolio_positions(self, order: TradingOrder, execution_price: float):
|
|
95
|
-
"""Update portfolio positions after order execution"""
|
|
98
|
+
"""Update portfolio positions after order execution."""
|
|
96
99
|
try:
|
|
97
100
|
portfolio = self.trading_service.get_portfolio(order.portfolio_id)
|
|
98
101
|
if not portfolio:
|
|
@@ -190,7 +193,7 @@ class PaperTradingEngine:
|
|
|
190
193
|
raise
|
|
191
194
|
|
|
192
195
|
def _update_portfolio_value(self, portfolio: Portfolio):
|
|
193
|
-
"""Update portfolio value and metrics"""
|
|
196
|
+
"""Update portfolio value and metrics."""
|
|
194
197
|
try:
|
|
195
198
|
# Get all positions
|
|
196
199
|
positions = self.db.query(Position).filter(Position.portfolio_id == portfolio.id).all()
|
|
@@ -218,7 +221,7 @@ class PaperTradingEngine:
|
|
|
218
221
|
raise
|
|
219
222
|
|
|
220
223
|
def simulate_market_movement(self, portfolio_id: UUID, days: int = 1):
|
|
221
|
-
"""Simulate market movement for paper trading"""
|
|
224
|
+
"""Simulate market movement for paper trading."""
|
|
222
225
|
try:
|
|
223
226
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
224
227
|
if not portfolio:
|
|
@@ -261,7 +264,7 @@ class PaperTradingEngine:
|
|
|
261
264
|
def create_test_portfolio(
|
|
262
265
|
self, user_id: UUID, name: str = "Test Portfolio", initial_capital: float = 100000.0
|
|
263
266
|
) -> Portfolio:
|
|
264
|
-
"""Create a test portfolio for paper trading"""
|
|
267
|
+
"""Create a test portfolio for paper trading."""
|
|
265
268
|
try:
|
|
266
269
|
# Create trading account
|
|
267
270
|
from mcli.ml.trading.models import TradingAccountCreate
|
|
@@ -293,7 +296,7 @@ class PaperTradingEngine:
|
|
|
293
296
|
end_date: datetime,
|
|
294
297
|
initial_capital: float = 100000.0,
|
|
295
298
|
) -> Dict:
|
|
296
|
-
"""Run a backtest on historical data"""
|
|
299
|
+
"""Run a backtest on historical data."""
|
|
297
300
|
try:
|
|
298
301
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
299
302
|
if not portfolio:
|
|
@@ -309,7 +312,7 @@ class PaperTradingEngine:
|
|
|
309
312
|
self.db.query(Position).filter(Position.portfolio_id == portfolio_id).delete()
|
|
310
313
|
|
|
311
314
|
# Get historical data for the period
|
|
312
|
-
|
|
315
|
+
_date_range = pd.date_range(start=start_date, end=end_date, freq="D") # noqa: F841
|
|
313
316
|
|
|
314
317
|
# This is a simplified backtest - in practice you'd want to:
|
|
315
318
|
# 1. Get historical signals
|
|
@@ -339,6 +342,6 @@ class PaperTradingEngine:
|
|
|
339
342
|
|
|
340
343
|
|
|
341
344
|
def create_paper_trading_engine(db_session: Session) -> PaperTradingEngine:
|
|
342
|
-
"""Create a paper trading engine"""
|
|
345
|
+
"""Create a paper trading engine."""
|
|
343
346
|
trading_service = TradingService(db_session)
|
|
344
347
|
return PaperTradingEngine(trading_service)
|
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
"""Risk management module for trading portfolios"""
|
|
1
|
+
"""Risk management module for trading portfolios."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
from typing import Dict, List,
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Dict, List, Tuple
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
|
-
import pandas as pd
|
|
10
9
|
from sqlalchemy.orm import Session
|
|
11
10
|
|
|
12
|
-
from mcli.ml.trading.models import
|
|
11
|
+
from mcli.ml.trading.models import Position, RiskLevel
|
|
13
12
|
from mcli.ml.trading.trading_service import TradingService
|
|
14
13
|
|
|
15
14
|
logger = logging.getLogger(__name__)
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class RiskManager:
|
|
19
|
-
"""Risk management system for trading portfolios"""
|
|
18
|
+
"""Risk management system for trading portfolios."""
|
|
20
19
|
|
|
21
20
|
def __init__(self, trading_service: TradingService):
|
|
22
21
|
self.trading_service = trading_service
|
|
23
22
|
self.db = trading_service.db
|
|
24
23
|
|
|
25
24
|
def calculate_portfolio_risk(self, portfolio_id: UUID) -> Dict:
|
|
26
|
-
"""Calculate comprehensive risk metrics for a portfolio"""
|
|
25
|
+
"""Calculate comprehensive risk metrics for a portfolio."""
|
|
27
26
|
try:
|
|
28
27
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
29
28
|
if not portfolio:
|
|
@@ -60,7 +59,7 @@ class RiskManager:
|
|
|
60
59
|
return {}
|
|
61
60
|
|
|
62
61
|
def _calculate_position_risk(self, position: Position, total_market_value: float) -> Dict:
|
|
63
|
-
"""Calculate risk metrics for an individual position"""
|
|
62
|
+
"""Calculate risk metrics for an individual position."""
|
|
64
63
|
try:
|
|
65
64
|
# Position size as percentage of portfolio
|
|
66
65
|
position_size_pct = (position.market_value / total_market_value) * 100
|
|
@@ -88,7 +87,7 @@ class RiskManager:
|
|
|
88
87
|
return {}
|
|
89
88
|
|
|
90
89
|
def _estimate_volatility(self, symbol: str, days: int = 30) -> float:
|
|
91
|
-
"""Estimate volatility for a symbol (simplified)"""
|
|
90
|
+
"""Estimate volatility for a symbol (simplified)."""
|
|
92
91
|
try:
|
|
93
92
|
# In practice, you would calculate historical volatility
|
|
94
93
|
# For now, use a simplified approach based on market cap and sector
|
|
@@ -117,7 +116,7 @@ class RiskManager:
|
|
|
117
116
|
def _calculate_portfolio_risk_metrics(
|
|
118
117
|
self, positions: List, position_risks: List[Dict]
|
|
119
118
|
) -> Dict:
|
|
120
|
-
"""Calculate portfolio-level risk metrics"""
|
|
119
|
+
"""Calculate portfolio-level risk metrics."""
|
|
121
120
|
try:
|
|
122
121
|
if not positions or not position_risks:
|
|
123
122
|
return {}
|
|
@@ -162,7 +161,7 @@ class RiskManager:
|
|
|
162
161
|
return {}
|
|
163
162
|
|
|
164
163
|
def _estimate_beta(self, symbol: str) -> float:
|
|
165
|
-
"""Estimate beta for a symbol (simplified)"""
|
|
164
|
+
"""Estimate beta for a symbol (simplified)."""
|
|
166
165
|
try:
|
|
167
166
|
# In practice, calculate beta against market index
|
|
168
167
|
# For now, use simplified estimates
|
|
@@ -186,7 +185,7 @@ class RiskManager:
|
|
|
186
185
|
return 1.0
|
|
187
186
|
|
|
188
187
|
def check_risk_limits(self, portfolio_id: UUID, new_order: Dict) -> Tuple[bool, List[str]]:
|
|
189
|
-
"""Check if a new order would violate risk limits"""
|
|
188
|
+
"""Check if a new order would violate risk limits."""
|
|
190
189
|
try:
|
|
191
190
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
192
191
|
if not portfolio:
|
|
@@ -215,7 +214,7 @@ class RiskManager:
|
|
|
215
214
|
|
|
216
215
|
# Check if adding to existing position
|
|
217
216
|
existing_position = next((pos for pos in positions if pos.symbol == symbol), None)
|
|
218
|
-
if existing_position:
|
|
217
|
+
if existing_position: # noqa: SIM102
|
|
219
218
|
if side == "buy":
|
|
220
219
|
new_total_size = (
|
|
221
220
|
(existing_position.market_value + order_value)
|
|
@@ -248,7 +247,7 @@ class RiskManager:
|
|
|
248
247
|
return False, [f"Risk check failed: {e}"]
|
|
249
248
|
|
|
250
249
|
def _estimate_order_value(self, symbol: str, quantity: int) -> float:
|
|
251
|
-
"""Estimate the value of an order"""
|
|
250
|
+
"""Estimate the value of an order."""
|
|
252
251
|
try:
|
|
253
252
|
import yfinance as yf
|
|
254
253
|
|
|
@@ -269,7 +268,7 @@ class RiskManager:
|
|
|
269
268
|
signal_strength: float,
|
|
270
269
|
risk_level: RiskLevel = RiskLevel.MODERATE,
|
|
271
270
|
) -> float:
|
|
272
|
-
"""Calculate recommended position size based on signal strength and risk level"""
|
|
271
|
+
"""Calculate recommended position size based on signal strength and risk level."""
|
|
273
272
|
try:
|
|
274
273
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
275
274
|
if not portfolio:
|
|
@@ -303,7 +302,7 @@ class RiskManager:
|
|
|
303
302
|
return 0.0
|
|
304
303
|
|
|
305
304
|
def generate_risk_report(self, portfolio_id: UUID) -> Dict:
|
|
306
|
-
"""Generate comprehensive risk report for a portfolio"""
|
|
305
|
+
"""Generate comprehensive risk report for a portfolio."""
|
|
307
306
|
try:
|
|
308
307
|
portfolio = self.trading_service.get_portfolio(portfolio_id)
|
|
309
308
|
if not portfolio:
|
|
@@ -352,7 +351,7 @@ class RiskManager:
|
|
|
352
351
|
return {}
|
|
353
352
|
|
|
354
353
|
def _generate_risk_recommendations(self, risk_metrics: Dict, max_drawdown: float) -> List[str]:
|
|
355
|
-
"""Generate risk management recommendations"""
|
|
354
|
+
"""Generate risk management recommendations."""
|
|
356
355
|
recommendations = []
|
|
357
356
|
|
|
358
357
|
# Concentration risk
|
|
@@ -386,6 +385,6 @@ class RiskManager:
|
|
|
386
385
|
|
|
387
386
|
|
|
388
387
|
def create_risk_manager(db_session: Session) -> RiskManager:
|
|
389
|
-
"""Create a risk manager instance"""
|
|
388
|
+
"""Create a risk manager instance."""
|
|
390
389
|
trading_service = TradingService(db_session)
|
|
391
390
|
return RiskManager(trading_service)
|