mcli-framework 7.12.2__py3-none-any.whl → 7.12.4__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 +30 -26
- mcli/app/completion_helpers.py +5 -5
- mcli/app/init_cmd.py +10 -10
- mcli/app/lock_cmd.py +29 -24
- mcli/app/main.py +2 -8
- 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 +10 -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.2.dist-info → mcli_framework-7.12.4.dist-info}/METADATA +1 -1
- mcli_framework-7.12.4.dist-info/RECORD +279 -0
- mcli_framework-7.12.2.dist-info/RECORD +0 -279
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.2.dist-info → mcli_framework-7.12.4.dist-info}/top_level.txt +0 -0
mcli/ml/auth/permissions.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Permission management system"""
|
|
1
|
+
"""Permission management system."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import Any, Dict,
|
|
6
|
+
from typing import Any, Dict, Set
|
|
7
7
|
|
|
8
8
|
from fastapi import HTTPException, status
|
|
9
9
|
from sqlalchemy.orm import Session
|
|
@@ -12,7 +12,7 @@ from mcli.ml.database.models import User, UserRole
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Permission(Enum):
|
|
15
|
-
"""System permissions"""
|
|
15
|
+
"""System permissions."""
|
|
16
16
|
|
|
17
17
|
# Model permissions
|
|
18
18
|
MODEL_VIEW = "model:view"
|
|
@@ -97,13 +97,13 @@ ROLE_PERMISSIONS: Dict[UserRole, Set[Permission]] = {
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def has_permission(user: User, permission: Permission) -> bool:
|
|
100
|
-
"""Check if user has specific permission"""
|
|
100
|
+
"""Check if user has specific permission."""
|
|
101
101
|
user_permissions = ROLE_PERMISSIONS.get(user.role, set())
|
|
102
102
|
return permission in user_permissions
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def check_permission(user: User, permission: Permission) -> None:
|
|
106
|
-
"""Check permission and raise exception if not allowed"""
|
|
106
|
+
"""Check permission and raise exception if not allowed."""
|
|
107
107
|
if not has_permission(user, permission):
|
|
108
108
|
raise HTTPException(
|
|
109
109
|
status_code=status.HTTP_403_FORBIDDEN, detail=f"Permission denied: {permission.value}"
|
|
@@ -111,7 +111,7 @@ def check_permission(user: User, permission: Permission) -> None:
|
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
def require_permission(permission: Permission):
|
|
114
|
-
"""Decorator to require specific permission"""
|
|
114
|
+
"""Decorator to require specific permission."""
|
|
115
115
|
|
|
116
116
|
def decorator(func):
|
|
117
117
|
@wraps(func)
|
|
@@ -133,7 +133,7 @@ def require_permission(permission: Permission):
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
def require_any_permission(*permissions: Permission):
|
|
136
|
-
"""Decorator to require any of the specified permissions"""
|
|
136
|
+
"""Decorator to require any of the specified permissions."""
|
|
137
137
|
|
|
138
138
|
def decorator(func):
|
|
139
139
|
@wraps(func)
|
|
@@ -159,7 +159,7 @@ def require_any_permission(*permissions: Permission):
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def require_all_permissions(*permissions: Permission):
|
|
162
|
-
"""Decorator to require all of the specified permissions"""
|
|
162
|
+
"""Decorator to require all of the specified permissions."""
|
|
163
163
|
|
|
164
164
|
def decorator(func):
|
|
165
165
|
@wraps(func)
|
|
@@ -182,7 +182,7 @@ def require_all_permissions(*permissions: Permission):
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
class PermissionChecker:
|
|
185
|
-
"""FastAPI dependency for permission checking"""
|
|
185
|
+
"""FastAPI dependency for permission checking."""
|
|
186
186
|
|
|
187
187
|
def __init__(self, permission: Permission):
|
|
188
188
|
self.permission = permission
|
|
@@ -194,11 +194,11 @@ class PermissionChecker:
|
|
|
194
194
|
|
|
195
195
|
# Resource-based permissions
|
|
196
196
|
class ResourcePermission:
|
|
197
|
-
"""Check permissions for specific resources"""
|
|
197
|
+
"""Check permissions for specific resources."""
|
|
198
198
|
|
|
199
199
|
@staticmethod
|
|
200
200
|
def can_edit_portfolio(user: User, portfolio) -> bool:
|
|
201
|
-
"""Check if user can edit a specific portfolio"""
|
|
201
|
+
"""Check if user can edit a specific portfolio."""
|
|
202
202
|
# Admin can edit any portfolio
|
|
203
203
|
if user.role == UserRole.ADMIN:
|
|
204
204
|
return True
|
|
@@ -211,7 +211,7 @@ class ResourcePermission:
|
|
|
211
211
|
|
|
212
212
|
@staticmethod
|
|
213
213
|
def can_view_portfolio(user: User, portfolio) -> bool:
|
|
214
|
-
"""Check if user can view a specific portfolio"""
|
|
214
|
+
"""Check if user can view a specific portfolio."""
|
|
215
215
|
# Admin can view any portfolio
|
|
216
216
|
if user.role == UserRole.ADMIN:
|
|
217
217
|
return True
|
|
@@ -228,20 +228,20 @@ class ResourcePermission:
|
|
|
228
228
|
|
|
229
229
|
@staticmethod
|
|
230
230
|
def can_delete_model(user: User, model) -> bool:
|
|
231
|
-
"""Check if user can delete a specific model"""
|
|
231
|
+
"""Check if user can delete a specific model."""
|
|
232
232
|
# Only admin can delete models
|
|
233
233
|
return user.role == UserRole.ADMIN
|
|
234
234
|
|
|
235
235
|
@staticmethod
|
|
236
236
|
def can_deploy_model(user: User, model) -> bool:
|
|
237
|
-
"""Check if user can deploy a specific model"""
|
|
237
|
+
"""Check if user can deploy a specific model."""
|
|
238
238
|
# Admin and Analyst can deploy models
|
|
239
239
|
return user.role in [UserRole.ADMIN, UserRole.ANALYST]
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
# Audit logging
|
|
243
243
|
class AuditLogger:
|
|
244
|
-
"""Log permission-related actions"""
|
|
244
|
+
"""Log permission-related actions."""
|
|
245
245
|
|
|
246
246
|
@staticmethod
|
|
247
247
|
async def log_access(
|
|
@@ -252,7 +252,7 @@ class AuditLogger:
|
|
|
252
252
|
details: Dict[str, Any] = None,
|
|
253
253
|
db: Session = None,
|
|
254
254
|
):
|
|
255
|
-
"""Log access attempt"""
|
|
255
|
+
"""Log access attempt."""
|
|
256
256
|
log_entry = {
|
|
257
257
|
"user_id": str(user.id),
|
|
258
258
|
"username": user.username,
|
|
@@ -269,7 +269,7 @@ class AuditLogger:
|
|
|
269
269
|
|
|
270
270
|
# Permission groups for easier management
|
|
271
271
|
class PermissionGroup:
|
|
272
|
-
"""Predefined permission groups"""
|
|
272
|
+
"""Predefined permission groups."""
|
|
273
273
|
|
|
274
274
|
BASIC_USER = {
|
|
275
275
|
Permission.MODEL_VIEW,
|
mcli/ml/backtesting/__init__.py
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
"""Backtesting engine for trading strategies"""
|
|
1
|
+
"""Backtesting engine for trading strategies."""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import logging
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
from dataclasses import dataclass, field
|
|
8
|
-
from datetime import datetime, timedelta
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import datetime
|
|
9
6
|
from enum import Enum
|
|
10
|
-
from
|
|
11
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
12
8
|
|
|
13
9
|
import numpy as np
|
|
14
10
|
import pandas as pd
|
|
@@ -19,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
19
15
|
|
|
20
16
|
|
|
21
17
|
class OrderType(Enum):
|
|
22
|
-
"""Order types"""
|
|
18
|
+
"""Order types."""
|
|
23
19
|
|
|
24
20
|
MARKET = "market"
|
|
25
21
|
LIMIT = "limit"
|
|
@@ -28,7 +24,7 @@ class OrderType(Enum):
|
|
|
28
24
|
|
|
29
25
|
|
|
30
26
|
class OrderSide(Enum):
|
|
31
|
-
"""Order side"""
|
|
27
|
+
"""Order side."""
|
|
32
28
|
|
|
33
29
|
BUY = "buy"
|
|
34
30
|
SELL = "sell"
|
|
@@ -36,7 +32,7 @@ class OrderSide(Enum):
|
|
|
36
32
|
|
|
37
33
|
@dataclass
|
|
38
34
|
class BacktestConfig:
|
|
39
|
-
"""Backtesting configuration"""
|
|
35
|
+
"""Backtesting configuration."""
|
|
40
36
|
|
|
41
37
|
initial_capital: float = 100000.0
|
|
42
38
|
start_date: Optional[datetime] = None
|
|
@@ -55,7 +51,7 @@ class BacktestConfig:
|
|
|
55
51
|
|
|
56
52
|
@dataclass
|
|
57
53
|
class BacktestResult:
|
|
58
|
-
"""Backtesting results"""
|
|
54
|
+
"""Backtesting results."""
|
|
59
55
|
|
|
60
56
|
portfolio_value: pd.Series
|
|
61
57
|
returns: pd.Series
|
|
@@ -67,7 +63,7 @@ class BacktestResult:
|
|
|
67
63
|
|
|
68
64
|
|
|
69
65
|
class TradingStrategy:
|
|
70
|
-
"""Base trading strategy class"""
|
|
66
|
+
"""Base trading strategy class."""
|
|
71
67
|
|
|
72
68
|
def __init__(self, model: Optional[StockRecommendationModel] = None):
|
|
73
69
|
self.model = model
|
|
@@ -77,7 +73,7 @@ class TradingStrategy:
|
|
|
77
73
|
def generate_signals(
|
|
78
74
|
self, data: pd.DataFrame, current_date: datetime, portfolio_value: float
|
|
79
75
|
) -> List[Dict[str, Any]]:
|
|
80
|
-
"""Generate trading signals"""
|
|
76
|
+
"""Generate trading signals."""
|
|
81
77
|
signals = []
|
|
82
78
|
|
|
83
79
|
if self.model:
|
|
@@ -104,7 +100,7 @@ class TradingStrategy:
|
|
|
104
100
|
def _get_model_recommendations(
|
|
105
101
|
self, data: pd.DataFrame, current_date: datetime
|
|
106
102
|
) -> List[PortfolioRecommendation]:
|
|
107
|
-
"""Get recommendations from ML model"""
|
|
103
|
+
"""Get recommendations from ML model."""
|
|
108
104
|
# Filter data up to current date
|
|
109
105
|
historical_data = data[data["date"] <= current_date]
|
|
110
106
|
|
|
@@ -125,7 +121,7 @@ class TradingStrategy:
|
|
|
125
121
|
def _momentum_strategy(
|
|
126
122
|
self, data: pd.DataFrame, current_date: datetime
|
|
127
123
|
) -> List[Dict[str, Any]]:
|
|
128
|
-
"""Simple momentum strategy"""
|
|
124
|
+
"""Simple momentum strategy."""
|
|
129
125
|
signals = []
|
|
130
126
|
|
|
131
127
|
# Get recent data
|
|
@@ -163,7 +159,7 @@ class TradingStrategy:
|
|
|
163
159
|
|
|
164
160
|
|
|
165
161
|
class PositionManager:
|
|
166
|
-
"""Manage portfolio positions"""
|
|
162
|
+
"""Manage portfolio positions."""
|
|
167
163
|
|
|
168
164
|
def __init__(self, config: BacktestConfig):
|
|
169
165
|
self.config = config
|
|
@@ -174,7 +170,7 @@ class PositionManager:
|
|
|
174
170
|
def open_position(
|
|
175
171
|
self, ticker: str, quantity: int, price: float, date: datetime, signal: Dict[str, Any]
|
|
176
172
|
):
|
|
177
|
-
"""Open a new position"""
|
|
173
|
+
"""Open a new position."""
|
|
178
174
|
cost = quantity * price * (1 + self.config.commission + self.config.slippage)
|
|
179
175
|
|
|
180
176
|
if cost > self.cash:
|
|
@@ -197,7 +193,7 @@ class PositionManager:
|
|
|
197
193
|
return True
|
|
198
194
|
|
|
199
195
|
def close_position(self, ticker: str, price: float, date: datetime) -> float:
|
|
200
|
-
"""Close a position"""
|
|
196
|
+
"""Close a position."""
|
|
201
197
|
if ticker not in self.positions:
|
|
202
198
|
return 0
|
|
203
199
|
|
|
@@ -218,7 +214,7 @@ class PositionManager:
|
|
|
218
214
|
return realized_pnl
|
|
219
215
|
|
|
220
216
|
def update_positions(self, price_data: Dict[str, float]):
|
|
221
|
-
"""Update position prices and calculate unrealized PnL"""
|
|
217
|
+
"""Update position prices and calculate unrealized PnL."""
|
|
222
218
|
for ticker, position in self.positions.items():
|
|
223
219
|
if ticker in price_data:
|
|
224
220
|
current_price = price_data[ticker]
|
|
@@ -242,7 +238,7 @@ class PositionManager:
|
|
|
242
238
|
return None, None
|
|
243
239
|
|
|
244
240
|
def get_portfolio_value(self, price_data: Dict[str, float]) -> float:
|
|
245
|
-
"""Calculate total portfolio value"""
|
|
241
|
+
"""Calculate total portfolio value."""
|
|
246
242
|
positions_value = sum(
|
|
247
243
|
pos["quantity"] * price_data.get(ticker, pos["current_price"])
|
|
248
244
|
for ticker, pos in self.positions.items()
|
|
@@ -250,7 +246,7 @@ class PositionManager:
|
|
|
250
246
|
return self.cash + positions_value
|
|
251
247
|
|
|
252
248
|
def get_position_weights(self, price_data: Dict[str, float]) -> Dict[str, float]:
|
|
253
|
-
"""Get position weights"""
|
|
249
|
+
"""Get position weights."""
|
|
254
250
|
portfolio_value = self.get_portfolio_value(price_data)
|
|
255
251
|
weights = {}
|
|
256
252
|
|
|
@@ -263,7 +259,7 @@ class PositionManager:
|
|
|
263
259
|
|
|
264
260
|
|
|
265
261
|
class BacktestEngine:
|
|
266
|
-
"""Main backtesting engine"""
|
|
262
|
+
"""Main backtesting engine."""
|
|
267
263
|
|
|
268
264
|
def __init__(self, config: BacktestConfig):
|
|
269
265
|
self.config = config
|
|
@@ -273,13 +269,13 @@ class BacktestEngine:
|
|
|
273
269
|
self.strategy = None
|
|
274
270
|
|
|
275
271
|
def set_strategy(self, strategy: TradingStrategy):
|
|
276
|
-
"""Set trading strategy"""
|
|
272
|
+
"""Set trading strategy."""
|
|
277
273
|
self.strategy = strategy
|
|
278
274
|
|
|
279
275
|
def run(
|
|
280
276
|
self, price_data: pd.DataFrame, trading_data: Optional[pd.DataFrame] = None
|
|
281
277
|
) -> BacktestResult:
|
|
282
|
-
"""Run backtest"""
|
|
278
|
+
"""Run backtest."""
|
|
283
279
|
logger.info("Starting backtest...")
|
|
284
280
|
|
|
285
281
|
# Prepare data
|
|
@@ -367,12 +363,12 @@ class BacktestEngine:
|
|
|
367
363
|
def _get_current_prices(
|
|
368
364
|
self, price_data: pd.DataFrame, current_date: datetime
|
|
369
365
|
) -> Dict[str, float]:
|
|
370
|
-
"""Get current prices for all tickers"""
|
|
366
|
+
"""Get current prices for all tickers."""
|
|
371
367
|
current_data = price_data[price_data["date"] == current_date]
|
|
372
368
|
return dict(zip(current_data["symbol"], current_data["close"]))
|
|
373
369
|
|
|
374
370
|
def _should_rebalance(self, day_index: int, current_date: datetime) -> bool:
|
|
375
|
-
"""Check if should rebalance portfolio"""
|
|
371
|
+
"""Check if should rebalance portfolio."""
|
|
376
372
|
if self.config.rebalance_frequency == "daily":
|
|
377
373
|
return True
|
|
378
374
|
elif self.config.rebalance_frequency == "weekly":
|
|
@@ -387,7 +383,7 @@ class BacktestEngine:
|
|
|
387
383
|
current_prices: Dict[str, float],
|
|
388
384
|
current_date: datetime,
|
|
389
385
|
):
|
|
390
|
-
"""Execute trading signals"""
|
|
386
|
+
"""Execute trading signals."""
|
|
391
387
|
for signal in signals:
|
|
392
388
|
ticker = signal["ticker"]
|
|
393
389
|
|
|
@@ -402,9 +398,9 @@ class BacktestEngine:
|
|
|
402
398
|
position_value = portfolio_value * signal.get("position_size", 0.05)
|
|
403
399
|
quantity = int(position_value / price)
|
|
404
400
|
|
|
405
|
-
if quantity > 0:
|
|
401
|
+
if quantity > 0: # noqa: SIM102
|
|
406
402
|
# Check if already have position
|
|
407
|
-
if ticker not in self.position_manager.positions:
|
|
403
|
+
if ticker not in self.position_manager.positions: # noqa: SIM102
|
|
408
404
|
# Check max positions
|
|
409
405
|
if len(self.position_manager.positions) < self.config.max_positions:
|
|
410
406
|
success = self.position_manager.open_position(
|
|
@@ -423,7 +419,7 @@ class BacktestEngine:
|
|
|
423
419
|
}
|
|
424
420
|
)
|
|
425
421
|
|
|
426
|
-
elif signal["action"] == "sell":
|
|
422
|
+
elif signal["action"] == "sell": # noqa: SIM102
|
|
427
423
|
if ticker in self.position_manager.positions:
|
|
428
424
|
pnl = self.position_manager.close_position(ticker, price, current_date)
|
|
429
425
|
|
|
@@ -441,7 +437,7 @@ class BacktestEngine:
|
|
|
441
437
|
)
|
|
442
438
|
|
|
443
439
|
def _execute_exit(self, ticker: str, price: float, current_date: datetime, exit_type: str):
|
|
444
|
-
"""Execute position exit"""
|
|
440
|
+
"""Execute position exit."""
|
|
445
441
|
if ticker in self.position_manager.positions:
|
|
446
442
|
position = self.position_manager.positions[ticker]
|
|
447
443
|
pnl = self.position_manager.close_position(ticker, price, current_date)
|
|
@@ -460,7 +456,7 @@ class BacktestEngine:
|
|
|
460
456
|
def _get_position_snapshot(
|
|
461
457
|
self, date: datetime, current_prices: Dict[str, float]
|
|
462
458
|
) -> Dict[str, Any]:
|
|
463
|
-
"""Get current position snapshot"""
|
|
459
|
+
"""Get current position snapshot."""
|
|
464
460
|
snapshot = {
|
|
465
461
|
"date": date,
|
|
466
462
|
"num_positions": len(self.position_manager.positions),
|
|
@@ -476,7 +472,7 @@ class BacktestEngine:
|
|
|
476
472
|
return snapshot
|
|
477
473
|
|
|
478
474
|
def _calculate_metrics(self, portfolio_df: pd.DataFrame) -> Dict[str, float]:
|
|
479
|
-
"""Calculate performance metrics"""
|
|
475
|
+
"""Calculate performance metrics."""
|
|
480
476
|
returns = portfolio_df["returns"]
|
|
481
477
|
|
|
482
478
|
# Basic metrics
|
|
@@ -515,7 +511,7 @@ class BacktestEngine:
|
|
|
515
511
|
def _get_benchmark_returns(
|
|
516
512
|
self, price_data: pd.DataFrame, dates: np.ndarray
|
|
517
513
|
) -> Optional[pd.Series]:
|
|
518
|
-
"""Get benchmark returns"""
|
|
514
|
+
"""Get benchmark returns."""
|
|
519
515
|
if self.config.benchmark not in price_data["symbol"].unique():
|
|
520
516
|
return None
|
|
521
517
|
|
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
"""Performance metrics and analysis for backtesting"""
|
|
1
|
+
"""Performance metrics and analysis for backtesting."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from
|
|
6
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
5
|
+
from typing import Dict, Optional, Tuple
|
|
7
6
|
|
|
8
7
|
import matplotlib.pyplot as plt
|
|
9
8
|
import numpy as np
|
|
10
9
|
import pandas as pd
|
|
11
|
-
import seaborn as sns
|
|
12
10
|
|
|
13
11
|
logger = logging.getLogger(__name__)
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
@dataclass
|
|
17
15
|
class PortfolioMetrics:
|
|
18
|
-
"""Portfolio performance metrics"""
|
|
16
|
+
"""Portfolio performance metrics."""
|
|
19
17
|
|
|
20
18
|
total_return: float
|
|
21
19
|
annualized_return: float
|
|
@@ -39,7 +37,7 @@ class PortfolioMetrics:
|
|
|
39
37
|
|
|
40
38
|
@dataclass
|
|
41
39
|
class RiskMetrics:
|
|
42
|
-
"""Risk metrics"""
|
|
40
|
+
"""Risk metrics."""
|
|
43
41
|
|
|
44
42
|
value_at_risk_95: float
|
|
45
43
|
conditional_var_95: float
|
|
@@ -56,7 +54,7 @@ class RiskMetrics:
|
|
|
56
54
|
|
|
57
55
|
|
|
58
56
|
class PerformanceAnalyzer:
|
|
59
|
-
"""Analyze backtest performance"""
|
|
57
|
+
"""Analyze backtest performance."""
|
|
60
58
|
|
|
61
59
|
def __init__(self, risk_free_rate: float = 0.02):
|
|
62
60
|
self.risk_free_rate = risk_free_rate
|
|
@@ -67,7 +65,7 @@ class PerformanceAnalyzer:
|
|
|
67
65
|
benchmark_returns: Optional[pd.Series] = None,
|
|
68
66
|
trades: Optional[pd.DataFrame] = None,
|
|
69
67
|
) -> Tuple[PortfolioMetrics, RiskMetrics]:
|
|
70
|
-
"""Calculate comprehensive performance metrics"""
|
|
68
|
+
"""Calculate comprehensive performance metrics."""
|
|
71
69
|
|
|
72
70
|
# Portfolio metrics
|
|
73
71
|
portfolio_metrics = self._calculate_portfolio_metrics(returns, trades)
|
|
@@ -80,7 +78,7 @@ class PerformanceAnalyzer:
|
|
|
80
78
|
def _calculate_portfolio_metrics(
|
|
81
79
|
self, returns: pd.Series, trades: Optional[pd.DataFrame] = None
|
|
82
80
|
) -> PortfolioMetrics:
|
|
83
|
-
"""Calculate portfolio performance metrics"""
|
|
81
|
+
"""Calculate portfolio performance metrics."""
|
|
84
82
|
|
|
85
83
|
# Basic returns
|
|
86
84
|
total_return = (1 + returns).prod() - 1
|
|
@@ -148,7 +146,7 @@ class PerformanceAnalyzer:
|
|
|
148
146
|
def _calculate_risk_metrics(
|
|
149
147
|
self, returns: pd.Series, benchmark_returns: Optional[pd.Series] = None
|
|
150
148
|
) -> RiskMetrics:
|
|
151
|
-
"""Calculate risk metrics"""
|
|
149
|
+
"""Calculate risk metrics."""
|
|
152
150
|
|
|
153
151
|
# Value at Risk (VaR)
|
|
154
152
|
var_95 = np.percentile(returns, 5)
|
|
@@ -231,7 +229,7 @@ class PerformanceAnalyzer:
|
|
|
231
229
|
)
|
|
232
230
|
|
|
233
231
|
def _calculate_max_drawdown_duration(self, drawdown: pd.Series) -> int:
|
|
234
|
-
"""Calculate maximum drawdown duration in days"""
|
|
232
|
+
"""Calculate maximum drawdown duration in days."""
|
|
235
233
|
is_drawdown = drawdown < 0
|
|
236
234
|
drawdown_periods = []
|
|
237
235
|
current_duration = 0
|
|
@@ -250,7 +248,7 @@ class PerformanceAnalyzer:
|
|
|
250
248
|
return max(drawdown_periods) if drawdown_periods else 0
|
|
251
249
|
|
|
252
250
|
def _analyze_trades(self, trades: pd.DataFrame) -> Dict[str, float]:
|
|
253
|
-
"""Analyze trade statistics"""
|
|
251
|
+
"""Analyze trade statistics."""
|
|
254
252
|
# Filter for trades with PnL
|
|
255
253
|
pnl_trades = trades[trades["pnl"].notna()].copy()
|
|
256
254
|
|
|
@@ -308,7 +306,7 @@ class PerformanceAnalyzer:
|
|
|
308
306
|
}
|
|
309
307
|
|
|
310
308
|
def _max_consecutive(self, arr: np.ndarray, value: bool) -> int:
|
|
311
|
-
"""Calculate maximum consecutive occurrences of value"""
|
|
309
|
+
"""Calculate maximum consecutive occurrences of value."""
|
|
312
310
|
max_count = 0
|
|
313
311
|
current_count = 0
|
|
314
312
|
|
|
@@ -323,7 +321,7 @@ class PerformanceAnalyzer:
|
|
|
323
321
|
|
|
324
322
|
|
|
325
323
|
def plot_performance(backtest_result, save_path: Optional[str] = None):
|
|
326
|
-
"""Plot backtest performance charts"""
|
|
324
|
+
"""Plot backtest performance charts."""
|
|
327
325
|
fig, axes = plt.subplots(3, 2, figsize=(15, 12))
|
|
328
326
|
|
|
329
327
|
# Portfolio value
|
mcli/ml/backtesting/run.py
CHANGED
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
5
|
|
|
6
|
-
from mcli.lib.ui.styling import error, info
|
|
6
|
+
from mcli.lib.ui.styling import error, info
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@click.group(name="mcli-backtest", help="Backtesting CLI for MCLI trading strategies")
|
|
10
10
|
def cli():
|
|
11
11
|
"""Main CLI group for backtesting."""
|
|
12
|
-
pass
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
@cli.command(name="run", help="Run a backtest on historical data")
|