mcli-framework 7.12.1__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 +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.1.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.1.dist-info/RECORD +0 -279
- {mcli_framework-7.12.1.dist-info → mcli_framework-7.12.3.dist-info}/WHEEL +0 -0
- {mcli_framework-7.12.1.dist-info → mcli_framework-7.12.3.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.12.1.dist-info → mcli_framework-7.12.3.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.12.1.dist-info → mcli_framework-7.12.3.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Real-time stream processing for financial data"""
|
|
1
|
+
"""Real-time stream processing for financial data."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
@@ -7,21 +7,20 @@ import time
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from collections import deque
|
|
9
9
|
from dataclasses import dataclass
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime
|
|
11
11
|
from typing import Any, AsyncIterator, Callable, Dict, List, Optional
|
|
12
12
|
|
|
13
13
|
import numpy as np
|
|
14
|
-
import pandas as pd
|
|
15
14
|
import websockets
|
|
16
|
-
|
|
17
|
-
from kafka
|
|
15
|
+
|
|
16
|
+
# KafkaConsumer from kafka library not needed - we have our own implementation below
|
|
18
17
|
|
|
19
18
|
logger = logging.getLogger(__name__)
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
@dataclass
|
|
23
22
|
class StreamConfig:
|
|
24
|
-
"""Stream processing configuration"""
|
|
23
|
+
"""Stream processing configuration."""
|
|
25
24
|
|
|
26
25
|
buffer_size: int = 1000
|
|
27
26
|
batch_size: int = 100
|
|
@@ -33,7 +32,7 @@ class StreamConfig:
|
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
class DataStream(ABC):
|
|
36
|
-
"""Base class for data streams"""
|
|
35
|
+
"""Base class for data streams."""
|
|
37
36
|
|
|
38
37
|
def __init__(self, config: StreamConfig):
|
|
39
38
|
self.config = config
|
|
@@ -44,20 +43,18 @@ class DataStream(ABC):
|
|
|
44
43
|
|
|
45
44
|
@abstractmethod
|
|
46
45
|
async def connect(self):
|
|
47
|
-
"""Connect to data source"""
|
|
48
|
-
pass
|
|
46
|
+
"""Connect to data source."""
|
|
49
47
|
|
|
50
48
|
@abstractmethod
|
|
51
49
|
async def consume(self) -> AsyncIterator[Dict[str, Any]]:
|
|
52
|
-
"""Consume data from stream"""
|
|
53
|
-
pass
|
|
50
|
+
"""Consume data from stream."""
|
|
54
51
|
|
|
55
52
|
def add_handler(self, handler: Callable):
|
|
56
|
-
"""Add data handler"""
|
|
53
|
+
"""Add data handler."""
|
|
57
54
|
self.handlers.append(handler)
|
|
58
55
|
|
|
59
56
|
async def process_message(self, message: Dict[str, Any]):
|
|
60
|
-
"""Process single message"""
|
|
57
|
+
"""Process single message."""
|
|
61
58
|
# Add to buffer
|
|
62
59
|
self.buffer.append(message)
|
|
63
60
|
|
|
@@ -70,7 +67,7 @@ class DataStream(ABC):
|
|
|
70
67
|
await self.flush_buffer()
|
|
71
68
|
|
|
72
69
|
async def flush_buffer(self):
|
|
73
|
-
"""Flush buffer and process batch"""
|
|
70
|
+
"""Flush buffer and process batch."""
|
|
74
71
|
if not self.buffer:
|
|
75
72
|
return
|
|
76
73
|
|
|
@@ -89,7 +86,7 @@ class DataStream(ABC):
|
|
|
89
86
|
logger.error(f"Handler error: {e}")
|
|
90
87
|
|
|
91
88
|
async def start(self):
|
|
92
|
-
"""Start consuming stream"""
|
|
89
|
+
"""Start consuming stream."""
|
|
93
90
|
self.is_running = True
|
|
94
91
|
await self.connect()
|
|
95
92
|
|
|
@@ -102,13 +99,13 @@ class DataStream(ABC):
|
|
|
102
99
|
await self.flush_buffer()
|
|
103
100
|
|
|
104
101
|
async def stop(self):
|
|
105
|
-
"""Stop consuming stream"""
|
|
102
|
+
"""Stop consuming stream."""
|
|
106
103
|
self.is_running = False
|
|
107
104
|
await self.flush_buffer()
|
|
108
105
|
|
|
109
106
|
|
|
110
107
|
class KafkaStream(DataStream):
|
|
111
|
-
"""Kafka stream consumer"""
|
|
108
|
+
"""Kafka stream consumer."""
|
|
112
109
|
|
|
113
110
|
def __init__(
|
|
114
111
|
self,
|
|
@@ -124,7 +121,7 @@ class KafkaStream(DataStream):
|
|
|
124
121
|
self.consumer = None
|
|
125
122
|
|
|
126
123
|
async def connect(self):
|
|
127
|
-
"""Connect to Kafka"""
|
|
124
|
+
"""Connect to Kafka."""
|
|
128
125
|
self.consumer = KafkaConsumer(
|
|
129
126
|
self.topic,
|
|
130
127
|
bootstrap_servers=self.bootstrap_servers,
|
|
@@ -136,20 +133,20 @@ class KafkaStream(DataStream):
|
|
|
136
133
|
logger.info(f"Connected to Kafka topic: {self.topic}")
|
|
137
134
|
|
|
138
135
|
async def consume(self) -> AsyncIterator[Dict[str, Any]]:
|
|
139
|
-
"""Consume from Kafka"""
|
|
136
|
+
"""Consume from Kafka."""
|
|
140
137
|
loop = asyncio.get_event_loop()
|
|
141
138
|
|
|
142
139
|
while self.is_running:
|
|
143
140
|
# Poll messages
|
|
144
141
|
messages = await loop.run_in_executor(None, self.consumer.poll, 1000) # timeout ms
|
|
145
142
|
|
|
146
|
-
for
|
|
143
|
+
for _topic_partition, records in messages.items():
|
|
147
144
|
for record in records:
|
|
148
145
|
yield record.value
|
|
149
146
|
|
|
150
147
|
|
|
151
148
|
class WebSocketStream(DataStream):
|
|
152
|
-
"""WebSocket stream consumer"""
|
|
149
|
+
"""WebSocket stream consumer."""
|
|
153
150
|
|
|
154
151
|
def __init__(self, config: StreamConfig, url: str):
|
|
155
152
|
super().__init__(config)
|
|
@@ -157,12 +154,12 @@ class WebSocketStream(DataStream):
|
|
|
157
154
|
self.websocket = None
|
|
158
155
|
|
|
159
156
|
async def connect(self):
|
|
160
|
-
"""Connect to WebSocket"""
|
|
157
|
+
"""Connect to WebSocket."""
|
|
161
158
|
self.websocket = await websockets.connect(self.url)
|
|
162
159
|
logger.info(f"Connected to WebSocket: {self.url}")
|
|
163
160
|
|
|
164
161
|
async def consume(self) -> AsyncIterator[Dict[str, Any]]:
|
|
165
|
-
"""Consume from WebSocket"""
|
|
162
|
+
"""Consume from WebSocket."""
|
|
166
163
|
async for message in self.websocket:
|
|
167
164
|
try:
|
|
168
165
|
data = json.loads(message)
|
|
@@ -172,7 +169,7 @@ class WebSocketStream(DataStream):
|
|
|
172
169
|
|
|
173
170
|
|
|
174
171
|
class StreamProcessor:
|
|
175
|
-
"""Process real-time data streams"""
|
|
172
|
+
"""Process real-time data streams."""
|
|
176
173
|
|
|
177
174
|
def __init__(self, config: StreamConfig):
|
|
178
175
|
self.config = config
|
|
@@ -181,7 +178,7 @@ class StreamProcessor:
|
|
|
181
178
|
self.metrics = StreamMetrics()
|
|
182
179
|
|
|
183
180
|
def add_stream(self, name: str, stream: DataStream):
|
|
184
|
-
"""Add data stream"""
|
|
181
|
+
"""Add data stream."""
|
|
185
182
|
self.streams[name] = stream
|
|
186
183
|
|
|
187
184
|
# Add metrics handler
|
|
@@ -192,7 +189,7 @@ class StreamProcessor:
|
|
|
192
189
|
stream.add_handler(processor)
|
|
193
190
|
|
|
194
191
|
def add_processor(self, processor: Callable):
|
|
195
|
-
"""Add data processor"""
|
|
192
|
+
"""Add data processor."""
|
|
196
193
|
self.processors.append(processor)
|
|
197
194
|
|
|
198
195
|
# Add to existing streams
|
|
@@ -200,7 +197,7 @@ class StreamProcessor:
|
|
|
200
197
|
stream.add_handler(processor)
|
|
201
198
|
|
|
202
199
|
async def update_metrics(self, batch: List[Dict[str, Any]]):
|
|
203
|
-
"""Update stream metrics"""
|
|
200
|
+
"""Update stream metrics."""
|
|
204
201
|
self.metrics.messages_processed += len(batch)
|
|
205
202
|
self.metrics.last_update = datetime.now()
|
|
206
203
|
|
|
@@ -214,7 +211,7 @@ class StreamProcessor:
|
|
|
214
211
|
self.metrics.throughput = self.metrics.messages_processed / elapsed
|
|
215
212
|
|
|
216
213
|
async def start(self):
|
|
217
|
-
"""Start all streams"""
|
|
214
|
+
"""Start all streams."""
|
|
218
215
|
tasks = []
|
|
219
216
|
for name, stream in self.streams.items():
|
|
220
217
|
logger.info(f"Starting stream: {name}")
|
|
@@ -224,13 +221,13 @@ class StreamProcessor:
|
|
|
224
221
|
await asyncio.gather(*tasks)
|
|
225
222
|
|
|
226
223
|
async def stop(self):
|
|
227
|
-
"""Stop all streams"""
|
|
224
|
+
"""Stop all streams."""
|
|
228
225
|
for name, stream in self.streams.items():
|
|
229
226
|
logger.info(f"Stopping stream: {name}")
|
|
230
227
|
await stream.stop()
|
|
231
228
|
|
|
232
229
|
def get_metrics(self) -> Dict[str, Any]:
|
|
233
|
-
"""Get stream metrics"""
|
|
230
|
+
"""Get stream metrics."""
|
|
234
231
|
return {
|
|
235
232
|
"messages_processed": self.metrics.messages_processed,
|
|
236
233
|
"throughput": self.metrics.throughput,
|
|
@@ -244,7 +241,7 @@ class StreamProcessor:
|
|
|
244
241
|
|
|
245
242
|
@dataclass
|
|
246
243
|
class StreamMetrics:
|
|
247
|
-
"""Stream processing metrics"""
|
|
244
|
+
"""Stream processing metrics."""
|
|
248
245
|
|
|
249
246
|
messages_processed: int = 0
|
|
250
247
|
throughput: float = 0 # messages per second
|
|
@@ -254,7 +251,7 @@ class StreamMetrics:
|
|
|
254
251
|
|
|
255
252
|
|
|
256
253
|
class DataAggregator:
|
|
257
|
-
"""Aggregate data from multiple streams"""
|
|
254
|
+
"""Aggregate data from multiple streams."""
|
|
258
255
|
|
|
259
256
|
def __init__(self, window_size: int = 60):
|
|
260
257
|
self.window_size = window_size
|
|
@@ -263,7 +260,7 @@ class DataAggregator:
|
|
|
263
260
|
self.last_aggregation = time.time()
|
|
264
261
|
|
|
265
262
|
async def process_batch(self, batch: List[Dict[str, Any]]):
|
|
266
|
-
"""Process batch of messages"""
|
|
263
|
+
"""Process batch of messages."""
|
|
267
264
|
for message in batch:
|
|
268
265
|
# Extract key fields
|
|
269
266
|
symbol = message.get("symbol") or message.get("ticker")
|
|
@@ -280,7 +277,7 @@ class DataAggregator:
|
|
|
280
277
|
await self.aggregate()
|
|
281
278
|
|
|
282
279
|
async def aggregate(self):
|
|
283
|
-
"""Aggregate buffered data"""
|
|
280
|
+
"""Aggregate buffered data."""
|
|
284
281
|
self.last_aggregation = time.time()
|
|
285
282
|
|
|
286
283
|
for symbol, data_points in self.data_buffer.items():
|
|
@@ -318,21 +315,21 @@ class DataAggregator:
|
|
|
318
315
|
logger.info(f"Aggregated data for {len(self.aggregated_data)} symbols")
|
|
319
316
|
|
|
320
317
|
def get_aggregated_data(self, symbol: Optional[str] = None) -> Dict[str, Any]:
|
|
321
|
-
"""Get aggregated data"""
|
|
318
|
+
"""Get aggregated data."""
|
|
322
319
|
if symbol:
|
|
323
320
|
return self.aggregated_data.get(symbol, {})
|
|
324
321
|
return self.aggregated_data
|
|
325
322
|
|
|
326
323
|
|
|
327
324
|
class StreamEnricher:
|
|
328
|
-
"""Enrich streaming data with additional context"""
|
|
325
|
+
"""Enrich streaming data with additional context."""
|
|
329
326
|
|
|
330
327
|
def __init__(self):
|
|
331
328
|
self.enrichment_cache = {}
|
|
332
329
|
self.cache_ttl = 300 # 5 minutes
|
|
333
330
|
|
|
334
331
|
async def enrich_batch(self, batch: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
335
|
-
"""Enrich batch of messages"""
|
|
332
|
+
"""Enrich batch of messages."""
|
|
336
333
|
enriched = []
|
|
337
334
|
|
|
338
335
|
for message in batch:
|
|
@@ -342,7 +339,7 @@ class StreamEnricher:
|
|
|
342
339
|
return enriched
|
|
343
340
|
|
|
344
341
|
async def enrich_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
345
|
-
"""Enrich single message"""
|
|
342
|
+
"""Enrich single message."""
|
|
346
343
|
enriched = message.copy()
|
|
347
344
|
|
|
348
345
|
# Add processing metadata
|
|
@@ -359,7 +356,7 @@ class StreamEnricher:
|
|
|
359
356
|
return enriched
|
|
360
357
|
|
|
361
358
|
async def enrich_political_data(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
362
|
-
"""Enrich political trading data"""
|
|
359
|
+
"""Enrich political trading data."""
|
|
363
360
|
politician = message.get("politician")
|
|
364
361
|
|
|
365
362
|
if politician:
|
|
@@ -388,7 +385,7 @@ class StreamEnricher:
|
|
|
388
385
|
return message
|
|
389
386
|
|
|
390
387
|
async def enrich_market_data(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
|
391
|
-
"""Enrich market data"""
|
|
388
|
+
"""Enrich market data."""
|
|
392
389
|
symbol = message.get("ticker") or message.get("symbol")
|
|
393
390
|
|
|
394
391
|
if symbol:
|
|
@@ -418,7 +415,7 @@ class StreamEnricher:
|
|
|
418
415
|
|
|
419
416
|
|
|
420
417
|
class KafkaConsumer:
|
|
421
|
-
"""Kafka consumer for real-time data"""
|
|
418
|
+
"""Kafka consumer for real-time data."""
|
|
422
419
|
|
|
423
420
|
def __init__(self, bootstrap_servers: str, topics: List[str]):
|
|
424
421
|
self.bootstrap_servers = bootstrap_servers
|
|
@@ -426,7 +423,7 @@ class KafkaConsumer:
|
|
|
426
423
|
self.consumer = None
|
|
427
424
|
|
|
428
425
|
async def connect(self):
|
|
429
|
-
"""Connect to Kafka"""
|
|
426
|
+
"""Connect to Kafka."""
|
|
430
427
|
self.consumer = KafkaConsumer(
|
|
431
428
|
*self.topics,
|
|
432
429
|
bootstrap_servers=self.bootstrap_servers,
|
|
@@ -435,7 +432,7 @@ class KafkaConsumer:
|
|
|
435
432
|
)
|
|
436
433
|
|
|
437
434
|
async def consume(self, handler: Callable):
|
|
438
|
-
"""Consume messages"""
|
|
435
|
+
"""Consume messages."""
|
|
439
436
|
for message in self.consumer:
|
|
440
437
|
try:
|
|
441
438
|
await handler(message.value)
|
|
@@ -444,18 +441,18 @@ class KafkaConsumer:
|
|
|
444
441
|
|
|
445
442
|
|
|
446
443
|
class WebSocketConsumer:
|
|
447
|
-
"""WebSocket consumer for real-time data"""
|
|
444
|
+
"""WebSocket consumer for real-time data."""
|
|
448
445
|
|
|
449
446
|
def __init__(self, url: str):
|
|
450
447
|
self.url = url
|
|
451
448
|
self.websocket = None
|
|
452
449
|
|
|
453
450
|
async def connect(self):
|
|
454
|
-
"""Connect to WebSocket"""
|
|
451
|
+
"""Connect to WebSocket."""
|
|
455
452
|
self.websocket = await websockets.connect(self.url)
|
|
456
453
|
|
|
457
454
|
async def consume(self, handler: Callable):
|
|
458
|
-
"""Consume messages"""
|
|
455
|
+
"""Consume messages."""
|
|
459
456
|
async for message in self.websocket:
|
|
460
457
|
try:
|
|
461
458
|
data = json.loads(message)
|
mcli/ml/database/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Alembic environment configuration"""
|
|
1
|
+
"""Alembic environment configuration."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
@@ -27,7 +27,7 @@ target_metadata = Base.metadata
|
|
|
27
27
|
|
|
28
28
|
# Override database URL from settings
|
|
29
29
|
def get_url():
|
|
30
|
-
"""Get database URL from settings or environment"""
|
|
30
|
+
"""Get database URL from settings or environment."""
|
|
31
31
|
# First try environment variable
|
|
32
32
|
url = os.getenv("DATABASE_URL")
|
|
33
33
|
if url:
|
mcli/ml/database/models.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
"""Database models for ML system"""
|
|
1
|
+
"""Database models for ML system."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from enum import Enum as PyEnum
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
6
5
|
from uuid import uuid4
|
|
7
6
|
|
|
8
7
|
from sqlalchemy import (
|
|
@@ -26,7 +25,6 @@ from sqlalchemy import (
|
|
|
26
25
|
from sqlalchemy.dialects.postgresql import ARRAY, UUID
|
|
27
26
|
from sqlalchemy.ext.hybrid import hybrid_property
|
|
28
27
|
from sqlalchemy.orm import declarative_base, relationship, validates
|
|
29
|
-
from sqlalchemy.sql import func
|
|
30
28
|
|
|
31
29
|
Base = declarative_base()
|
|
32
30
|
|
|
@@ -52,7 +50,7 @@ experiment_models = Table(
|
|
|
52
50
|
|
|
53
51
|
|
|
54
52
|
class UserRole(PyEnum):
|
|
55
|
-
"""User role enumeration"""
|
|
53
|
+
"""User role enumeration."""
|
|
56
54
|
|
|
57
55
|
ADMIN = "admin"
|
|
58
56
|
USER = "user"
|
|
@@ -61,7 +59,7 @@ class UserRole(PyEnum):
|
|
|
61
59
|
|
|
62
60
|
|
|
63
61
|
class ModelStatus(PyEnum):
|
|
64
|
-
"""Model status enumeration"""
|
|
62
|
+
"""Model status enumeration."""
|
|
65
63
|
|
|
66
64
|
TRAINING = "training"
|
|
67
65
|
TRAINED = "trained"
|
|
@@ -71,7 +69,7 @@ class ModelStatus(PyEnum):
|
|
|
71
69
|
|
|
72
70
|
|
|
73
71
|
class TradeType(PyEnum):
|
|
74
|
-
"""Trade type enumeration"""
|
|
72
|
+
"""Trade type enumeration."""
|
|
75
73
|
|
|
76
74
|
BUY = "buy"
|
|
77
75
|
SELL = "sell"
|
|
@@ -79,7 +77,7 @@ class TradeType(PyEnum):
|
|
|
79
77
|
|
|
80
78
|
|
|
81
79
|
class AlertType(PyEnum):
|
|
82
|
-
"""Alert type enumeration"""
|
|
80
|
+
"""Alert type enumeration."""
|
|
83
81
|
|
|
84
82
|
POLITICIAN_TRADE = "politician_trade"
|
|
85
83
|
PRICE_MOVEMENT = "price_movement"
|
|
@@ -89,7 +87,7 @@ class AlertType(PyEnum):
|
|
|
89
87
|
|
|
90
88
|
|
|
91
89
|
class User(Base):
|
|
92
|
-
"""User model for authentication and authorization"""
|
|
90
|
+
"""User model for authentication and authorization."""
|
|
93
91
|
|
|
94
92
|
__tablename__ = "users"
|
|
95
93
|
|
|
@@ -127,7 +125,7 @@ class User(Base):
|
|
|
127
125
|
|
|
128
126
|
@validates("email")
|
|
129
127
|
def validate_email(self, key, email):
|
|
130
|
-
"""Validate email format"""
|
|
128
|
+
"""Validate email format."""
|
|
131
129
|
import re
|
|
132
130
|
|
|
133
131
|
if not re.match(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$", email):
|
|
@@ -136,7 +134,7 @@ class User(Base):
|
|
|
136
134
|
|
|
137
135
|
|
|
138
136
|
class Politician(Base):
|
|
139
|
-
"""Politician information"""
|
|
137
|
+
"""Politician information."""
|
|
140
138
|
|
|
141
139
|
__tablename__ = "politicians"
|
|
142
140
|
|
|
@@ -169,7 +167,7 @@ class Politician(Base):
|
|
|
169
167
|
|
|
170
168
|
|
|
171
169
|
class Trade(Base):
|
|
172
|
-
"""Political trading records"""
|
|
170
|
+
"""Political trading records."""
|
|
173
171
|
|
|
174
172
|
__tablename__ = "trades"
|
|
175
173
|
|
|
@@ -206,14 +204,14 @@ class Trade(Base):
|
|
|
206
204
|
|
|
207
205
|
@hybrid_property
|
|
208
206
|
def estimated_amount(self):
|
|
209
|
-
"""Get estimated trade amount (midpoint)"""
|
|
207
|
+
"""Get estimated trade amount (midpoint)."""
|
|
210
208
|
if self.amount_min and self.amount_max:
|
|
211
209
|
return (self.amount_min + self.amount_max) / 2
|
|
212
210
|
return self.amount_min or self.amount_max
|
|
213
211
|
|
|
214
212
|
|
|
215
213
|
class StockData(Base):
|
|
216
|
-
"""Stock market data"""
|
|
214
|
+
"""Stock market data."""
|
|
217
215
|
|
|
218
216
|
__tablename__ = "stock_data"
|
|
219
217
|
|
|
@@ -264,7 +262,7 @@ class StockData(Base):
|
|
|
264
262
|
|
|
265
263
|
|
|
266
264
|
class Prediction(Base):
|
|
267
|
-
"""Model predictions"""
|
|
265
|
+
"""Model predictions."""
|
|
268
266
|
|
|
269
267
|
__tablename__ = "predictions"
|
|
270
268
|
|
|
@@ -313,7 +311,7 @@ class Prediction(Base):
|
|
|
313
311
|
|
|
314
312
|
|
|
315
313
|
class Portfolio(Base):
|
|
316
|
-
"""User portfolios"""
|
|
314
|
+
"""User portfolios."""
|
|
317
315
|
|
|
318
316
|
__tablename__ = "portfolios"
|
|
319
317
|
|
|
@@ -358,7 +356,7 @@ class Portfolio(Base):
|
|
|
358
356
|
|
|
359
357
|
|
|
360
358
|
class Alert(Base):
|
|
361
|
-
"""User alerts and notifications"""
|
|
359
|
+
"""User alerts and notifications."""
|
|
362
360
|
|
|
363
361
|
__tablename__ = "alerts"
|
|
364
362
|
|
|
@@ -396,7 +394,7 @@ class Alert(Base):
|
|
|
396
394
|
|
|
397
395
|
|
|
398
396
|
class BacktestResult(Base):
|
|
399
|
-
"""Backtesting results"""
|
|
397
|
+
"""Backtesting results."""
|
|
400
398
|
|
|
401
399
|
__tablename__ = "backtest_results"
|
|
402
400
|
|
|
@@ -448,7 +446,7 @@ class BacktestResult(Base):
|
|
|
448
446
|
|
|
449
447
|
|
|
450
448
|
class Experiment(Base):
|
|
451
|
-
"""ML experiments tracking"""
|
|
449
|
+
"""ML experiments tracking."""
|
|
452
450
|
|
|
453
451
|
__tablename__ = "experiments"
|
|
454
452
|
|
|
@@ -496,7 +494,7 @@ class Experiment(Base):
|
|
|
496
494
|
|
|
497
495
|
|
|
498
496
|
class Model(Base):
|
|
499
|
-
"""ML models registry"""
|
|
497
|
+
"""ML models registry."""
|
|
500
498
|
|
|
501
499
|
__tablename__ = "models"
|
|
502
500
|
|
|
@@ -578,7 +576,7 @@ class Model(Base):
|
|
|
578
576
|
|
|
579
577
|
|
|
580
578
|
class FeatureSet(Base):
|
|
581
|
-
"""Feature sets for models"""
|
|
579
|
+
"""Feature sets for models."""
|
|
582
580
|
|
|
583
581
|
__tablename__ = "feature_sets"
|
|
584
582
|
|
|
@@ -617,7 +615,7 @@ class FeatureSet(Base):
|
|
|
617
615
|
|
|
618
616
|
|
|
619
617
|
class DataVersion(Base):
|
|
620
|
-
"""Data versioning for DVC"""
|
|
618
|
+
"""Data versioning for DVC."""
|
|
621
619
|
|
|
622
620
|
__tablename__ = "data_versions"
|
|
623
621
|
|
|
@@ -658,7 +656,7 @@ class DataVersion(Base):
|
|
|
658
656
|
# Database events and triggers
|
|
659
657
|
@event.listens_for(User, "before_insert")
|
|
660
658
|
def generate_api_key(mapper, connection, target):
|
|
661
|
-
"""Generate API key for new users"""
|
|
659
|
+
"""Generate API key for new users."""
|
|
662
660
|
if not target.api_key:
|
|
663
661
|
import secrets
|
|
664
662
|
|
|
@@ -668,7 +666,7 @@ def generate_api_key(mapper, connection, target):
|
|
|
668
666
|
|
|
669
667
|
@event.listens_for(Portfolio, "before_update")
|
|
670
668
|
def update_portfolio_metrics(mapper, connection, target):
|
|
671
|
-
"""Update portfolio performance metrics"""
|
|
669
|
+
"""Update portfolio performance metrics."""
|
|
672
670
|
if target.current_value and target.initial_capital:
|
|
673
671
|
target.total_return = (
|
|
674
672
|
(target.current_value - target.initial_capital) / target.initial_capital
|
|
@@ -677,7 +675,7 @@ def update_portfolio_metrics(mapper, connection, target):
|
|
|
677
675
|
|
|
678
676
|
# Create indexes for better query performance
|
|
679
677
|
def create_indexes(engine):
|
|
680
|
-
"""Create additional database indexes"""
|
|
678
|
+
"""Create additional database indexes."""
|
|
681
679
|
from sqlalchemy import text
|
|
682
680
|
|
|
683
681
|
indexes = [
|
mcli/ml/database/session.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Database session management"""
|
|
1
|
+
"""Database session management."""
|
|
2
2
|
|
|
3
3
|
# Synchronous database setup
|
|
4
4
|
# Prioritize DATABASE_URL environment variable over settings
|
|
@@ -9,7 +9,7 @@ from typing import AsyncGenerator, Generator
|
|
|
9
9
|
from sqlalchemy import create_engine
|
|
10
10
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
11
11
|
from sqlalchemy.orm import Session, sessionmaker
|
|
12
|
-
from sqlalchemy.pool import
|
|
12
|
+
from sqlalchemy.pool import StaticPool
|
|
13
13
|
|
|
14
14
|
from mcli.ml.config import settings
|
|
15
15
|
|
|
@@ -28,7 +28,7 @@ if not database_url:
|
|
|
28
28
|
settings_url = settings.database.url
|
|
29
29
|
if settings_url and "sqlite" not in settings_url:
|
|
30
30
|
database_url = settings_url
|
|
31
|
-
except
|
|
31
|
+
except Exception:
|
|
32
32
|
pass # Continue with database_url=None
|
|
33
33
|
|
|
34
34
|
# If still no valid DATABASE_URL, try to use Supabase REST API via connection pooler
|
|
@@ -103,7 +103,7 @@ import logging
|
|
|
103
103
|
logger = logging.getLogger(__name__)
|
|
104
104
|
|
|
105
105
|
if "pooler.supabase.com" in database_url:
|
|
106
|
-
logger.info(
|
|
106
|
+
logger.info("🔗 Using Supabase connection pooler")
|
|
107
107
|
elif "sqlite" in database_url:
|
|
108
108
|
logger.warning("📁 Using SQLite fallback (database features limited)")
|
|
109
109
|
else:
|
|
@@ -157,7 +157,7 @@ try:
|
|
|
157
157
|
pool_pre_ping=True,
|
|
158
158
|
echo=settings.debug,
|
|
159
159
|
)
|
|
160
|
-
except
|
|
160
|
+
except Exception:
|
|
161
161
|
# Fallback for async engine
|
|
162
162
|
import os
|
|
163
163
|
|
|
@@ -284,7 +284,7 @@ async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
def init_db() -> None:
|
|
287
|
-
"""Initialize database tables"""
|
|
287
|
+
"""Initialize database tables."""
|
|
288
288
|
Base.metadata.create_all(bind=engine)
|
|
289
289
|
|
|
290
290
|
# Create additional indexes
|
|
@@ -294,25 +294,25 @@ def init_db() -> None:
|
|
|
294
294
|
|
|
295
295
|
|
|
296
296
|
async def init_async_db() -> None:
|
|
297
|
-
"""Initialize database tables asynchronously"""
|
|
297
|
+
"""Initialize database tables asynchronously."""
|
|
298
298
|
async with async_engine.begin() as conn:
|
|
299
299
|
await conn.run_sync(Base.metadata.create_all)
|
|
300
300
|
|
|
301
301
|
|
|
302
302
|
def drop_db() -> None:
|
|
303
|
-
"""Drop all database tables (use with caution!)"""
|
|
303
|
+
"""Drop all database tables (use with caution!)."""
|
|
304
304
|
Base.metadata.drop_all(bind=engine)
|
|
305
305
|
|
|
306
306
|
|
|
307
307
|
async def drop_async_db() -> None:
|
|
308
|
-
"""Drop all database tables asynchronously"""
|
|
308
|
+
"""Drop all database tables asynchronously."""
|
|
309
309
|
async with async_engine.begin() as conn:
|
|
310
310
|
await conn.run_sync(Base.metadata.drop_all)
|
|
311
311
|
|
|
312
312
|
|
|
313
313
|
# Test database setup (for unit tests)
|
|
314
314
|
def get_test_engine():
|
|
315
|
-
"""Create test database engine with in-memory SQLite"""
|
|
315
|
+
"""Create test database engine with in-memory SQLite."""
|
|
316
316
|
from sqlalchemy import create_engine
|
|
317
317
|
|
|
318
318
|
engine = create_engine(
|
|
@@ -326,7 +326,7 @@ def get_test_engine():
|
|
|
326
326
|
|
|
327
327
|
|
|
328
328
|
def get_test_session():
|
|
329
|
-
"""Create test database session"""
|
|
329
|
+
"""Create test database session."""
|
|
330
330
|
engine = get_test_engine()
|
|
331
331
|
TestSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
332
332
|
return TestSessionLocal()
|
|
@@ -334,7 +334,7 @@ def get_test_session():
|
|
|
334
334
|
|
|
335
335
|
# Database health check
|
|
336
336
|
async def check_database_health() -> bool:
|
|
337
|
-
"""Check if database is accessible and healthy"""
|
|
337
|
+
"""Check if database is accessible and healthy."""
|
|
338
338
|
try:
|
|
339
339
|
async with get_async_session() as session:
|
|
340
340
|
await session.execute("SELECT 1")
|
|
@@ -346,14 +346,14 @@ async def check_database_health() -> bool:
|
|
|
346
346
|
|
|
347
347
|
# Utility functions for bulk operations
|
|
348
348
|
async def bulk_insert(model_class, data: list) -> None:
|
|
349
|
-
"""Bulk insert data into database"""
|
|
349
|
+
"""Bulk insert data into database."""
|
|
350
350
|
async with get_async_session() as session:
|
|
351
351
|
session.add_all([model_class(**item) for item in data])
|
|
352
352
|
await session.commit()
|
|
353
353
|
|
|
354
354
|
|
|
355
355
|
async def bulk_update(model_class, data: list, key_field: str = "id") -> None:
|
|
356
|
-
"""Bulk update data in database"""
|
|
356
|
+
"""Bulk update data in database."""
|
|
357
357
|
async with get_async_session() as session:
|
|
358
358
|
for item in data:
|
|
359
359
|
key_value = item.pop(key_field)
|