@vizzor/cli 0.13.1 → 0.14.5
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.
- package/README.md +250 -192
- package/chronovisor-engine/pyproject.toml +31 -0
- package/chronovisor-engine/src/__init__.py +0 -0
- package/chronovisor-engine/src/inference/__init__.py +0 -0
- package/chronovisor-engine/src/inference/predict.py +44 -0
- package/chronovisor-engine/src/model_catalog.py +219 -0
- package/chronovisor-engine/src/models/__init__.py +0 -0
- package/chronovisor-engine/src/models/anomaly_detector.py +104 -0
- package/chronovisor-engine/src/models/blockchain_cycle_analyzer.py +217 -0
- package/chronovisor-engine/src/models/catalyst_event_model.py +70 -0
- package/chronovisor-engine/src/models/conformal_interval.py +50 -0
- package/chronovisor-engine/src/models/divergence_detector.py +247 -0
- package/chronovisor-engine/src/models/drift_monitor.py +51 -0
- package/chronovisor-engine/src/models/intent_classifier.py +189 -0
- package/chronovisor-engine/src/models/lstm_predictor.py +143 -0
- package/chronovisor-engine/src/models/microstructure_specialist.py +65 -0
- package/chronovisor-engine/src/models/narrative_detector.py +418 -0
- package/chronovisor-engine/src/models/portfolio_optimizer.py +162 -0
- package/chronovisor-engine/src/models/project_risk_scorer.py +184 -0
- package/chronovisor-engine/src/models/pump_detector.py +344 -0
- package/chronovisor-engine/src/models/regime_detector.py +127 -0
- package/chronovisor-engine/src/models/rug_detector.py +197 -0
- package/chronovisor-engine/src/models/sentiment_analyzer.py +257 -0
- package/chronovisor-engine/src/models/signal_classifier.py +191 -0
- package/chronovisor-engine/src/models/stacking_meta.py +56 -0
- package/chronovisor-engine/src/models/strategy_bandit.py +191 -0
- package/chronovisor-engine/src/models/ta_interpreter.py +341 -0
- package/chronovisor-engine/src/models/target_quantile.py +96 -0
- package/chronovisor-engine/src/models/trend_scorer.py +107 -0
- package/chronovisor-engine/src/models/wallet_classifier.py +261 -0
- package/chronovisor-engine/src/server.py +1686 -0
- package/chronovisor-engine/src/training/__init__.py +0 -0
- package/chronovisor-engine/src/training/data_loader.py +635 -0
- package/chronovisor-engine/src/training/pipeline.py +130 -0
- package/chronovisor-engine/src/training/train_catalyst.py +169 -0
- package/chronovisor-engine/src/training/train_classifier.py +159 -0
- package/chronovisor-engine/src/training/train_conformal.py +106 -0
- package/chronovisor-engine/src/training/train_direction.py +215 -0
- package/chronovisor-engine/src/training/train_drift.py +57 -0
- package/chronovisor-engine/src/training/train_isotonic.py +58 -0
- package/chronovisor-engine/src/training/train_lstm.py +217 -0
- package/chronovisor-engine/src/training/train_microstructure.py +102 -0
- package/chronovisor-engine/src/training/train_narrative.py +168 -0
- package/chronovisor-engine/src/training/train_pump.py +109 -0
- package/chronovisor-engine/src/training/train_regime.py +116 -0
- package/chronovisor-engine/src/training/train_rug.py +58 -0
- package/chronovisor-engine/src/training/train_sentiment.py +63 -0
- package/chronovisor-engine/src/training/train_stacking_meta.py +74 -0
- package/chronovisor-engine/src/training/train_target_quantile.py +115 -0
- package/chronovisor-engine/src/training/train_trend.py +101 -0
- package/dist/index.js +19124 -11698
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Target quantile trainer for bull/base/bear price-delta forecasts."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import joblib
|
|
8
|
+
import numpy as np
|
|
9
|
+
from sklearn.ensemble import GradientBoostingRegressor
|
|
10
|
+
from sklearn.metrics import mean_absolute_error
|
|
11
|
+
|
|
12
|
+
from .data_loader import load_target_outcomes
|
|
13
|
+
from .train_direction import ALL_FEATURE_KEYS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _horizon_to_minutes(horizon: str) -> float:
|
|
17
|
+
total = 0.0
|
|
18
|
+
for amount, unit in re.findall(r"(\d+)(mo|y|w|d|h|m)", str(horizon or "").strip().lower()):
|
|
19
|
+
quantity = float(amount or 0)
|
|
20
|
+
if unit == "m":
|
|
21
|
+
total += quantity
|
|
22
|
+
elif unit == "h":
|
|
23
|
+
total += quantity * 60
|
|
24
|
+
elif unit == "d":
|
|
25
|
+
total += quantity * 1440
|
|
26
|
+
elif unit == "w":
|
|
27
|
+
total += quantity * 10080
|
|
28
|
+
elif unit == "mo":
|
|
29
|
+
total += quantity * 43200
|
|
30
|
+
elif unit == "y":
|
|
31
|
+
total += quantity * 525600
|
|
32
|
+
return total if total > 0 else 240.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TargetQuantileTrainer:
|
|
36
|
+
model_name = "target_xgb_quantile"
|
|
37
|
+
artifact_path = Path("models") / "target_xgb_quantile.joblib"
|
|
38
|
+
|
|
39
|
+
def run(self, days: int = 180) -> dict:
|
|
40
|
+
start = time.time()
|
|
41
|
+
outcomes = load_target_outcomes(days)
|
|
42
|
+
if len(outcomes) < 60:
|
|
43
|
+
return {
|
|
44
|
+
"model": self.model_name,
|
|
45
|
+
"status": "skipped",
|
|
46
|
+
"metrics": {"samples": len(outcomes)},
|
|
47
|
+
"duration_seconds": round(time.time() - start, 2),
|
|
48
|
+
"artifact_path": "",
|
|
49
|
+
"error": f"Need at least 60 samples, got {len(outcomes)}",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
rows = []
|
|
53
|
+
for outcome in outcomes:
|
|
54
|
+
features = dict(outcome["features"])
|
|
55
|
+
features["probability_hint"] = float(outcome.get("probability", 0.5))
|
|
56
|
+
features["horizon_minutes"] = _horizon_to_minutes(str(outcome.get("horizon", "4h")))
|
|
57
|
+
rows.append(features)
|
|
58
|
+
|
|
59
|
+
feature_keys = ALL_FEATURE_KEYS + ["probability_hint", "horizon_minutes"]
|
|
60
|
+
X = np.array([[row.get(key, 0.0) for key in feature_keys] for row in rows], dtype=np.float32)
|
|
61
|
+
y = np.array([float(outcome["changePct"]) for outcome in outcomes], dtype=np.float32)
|
|
62
|
+
|
|
63
|
+
n = len(X)
|
|
64
|
+
train_end = int(n * 0.70)
|
|
65
|
+
val_end = int(n * 0.85)
|
|
66
|
+
X_train, X_val, X_test = X[:train_end], X[train_end:val_end], X[val_end:]
|
|
67
|
+
y_train, y_val, y_test = y[:train_end], y[train_end:val_end], y[val_end:]
|
|
68
|
+
|
|
69
|
+
models = {
|
|
70
|
+
"q10": GradientBoostingRegressor(
|
|
71
|
+
loss="quantile", alpha=0.10, n_estimators=250, max_depth=3, random_state=42
|
|
72
|
+
),
|
|
73
|
+
"q50": GradientBoostingRegressor(
|
|
74
|
+
loss="absolute_error", n_estimators=250, max_depth=3, random_state=42
|
|
75
|
+
),
|
|
76
|
+
"q90": GradientBoostingRegressor(
|
|
77
|
+
loss="quantile", alpha=0.90, n_estimators=250, max_depth=3, random_state=42
|
|
78
|
+
),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for model in models.values():
|
|
82
|
+
model.fit(X_train, y_train)
|
|
83
|
+
|
|
84
|
+
low = models["q10"].predict(X_test)
|
|
85
|
+
base = models["q50"].predict(X_test)
|
|
86
|
+
high = models["q90"].predict(X_test)
|
|
87
|
+
coverage = float(np.mean((y_test >= low) & (y_test <= high)))
|
|
88
|
+
mae = float(mean_absolute_error(y_test, base))
|
|
89
|
+
|
|
90
|
+
self.artifact_path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
joblib.dump(
|
|
92
|
+
{
|
|
93
|
+
"models": models,
|
|
94
|
+
"feature_keys": feature_keys,
|
|
95
|
+
"trained_at": str(int(time.time())),
|
|
96
|
+
"accuracy": 1.0 / (1.0 + mae),
|
|
97
|
+
"mae": mae,
|
|
98
|
+
"coverage": coverage,
|
|
99
|
+
},
|
|
100
|
+
self.artifact_path,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"model": self.model_name,
|
|
105
|
+
"status": "success",
|
|
106
|
+
"metrics": {
|
|
107
|
+
"mae": mae,
|
|
108
|
+
"coverage": coverage,
|
|
109
|
+
"train_samples": len(X_train),
|
|
110
|
+
"val_samples": len(X_val),
|
|
111
|
+
"test_samples": len(X_test),
|
|
112
|
+
},
|
|
113
|
+
"duration_seconds": round(time.time() - start, 2),
|
|
114
|
+
"artifact_path": str(self.artifact_path),
|
|
115
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Trend scorer training on real historical OHLCV and snapshot-derived features."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .data_loader import load_trend_training_frame
|
|
9
|
+
from .pipeline import TrainingPipeline
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TrendTrainer(TrainingPipeline):
|
|
15
|
+
FEATURE_KEYS = [
|
|
16
|
+
"price_change_24h",
|
|
17
|
+
"price_change_7d",
|
|
18
|
+
"volume_24h",
|
|
19
|
+
"market_cap",
|
|
20
|
+
"volume_to_mcap_ratio",
|
|
21
|
+
"rank",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__("trend_scorer")
|
|
26
|
+
|
|
27
|
+
def load_data(self):
|
|
28
|
+
logger.info("Loading real trend scoring data from OHLCV history...")
|
|
29
|
+
frame = load_trend_training_frame(days=240, timeframe="4h")
|
|
30
|
+
if frame.empty:
|
|
31
|
+
raise RuntimeError("No historical trend training data available")
|
|
32
|
+
return frame
|
|
33
|
+
|
|
34
|
+
def preprocess(self, data):
|
|
35
|
+
data = data.sort_index().reset_index(drop=True)
|
|
36
|
+
X = data[self.FEATURE_KEYS].fillna(0.0).astype(np.float32).values
|
|
37
|
+
y = data["y"].astype(np.float32).values
|
|
38
|
+
|
|
39
|
+
n = len(X)
|
|
40
|
+
train_end = int(n * 0.70)
|
|
41
|
+
val_end = int(n * 0.85)
|
|
42
|
+
|
|
43
|
+
return X[:train_end], X[train_end:val_end], X[val_end:], y[:train_end], y[train_end:val_end], y[val_end:]
|
|
44
|
+
|
|
45
|
+
def train(self, X_train, y_train, X_val, y_val):
|
|
46
|
+
try:
|
|
47
|
+
import xgboost as xgb
|
|
48
|
+
|
|
49
|
+
model = xgb.XGBRegressor(
|
|
50
|
+
n_estimators=300,
|
|
51
|
+
max_depth=4,
|
|
52
|
+
learning_rate=0.05,
|
|
53
|
+
subsample=0.85,
|
|
54
|
+
colsample_bytree=0.85,
|
|
55
|
+
reg_alpha=0.05,
|
|
56
|
+
reg_lambda=1.0,
|
|
57
|
+
objective="reg:squarederror",
|
|
58
|
+
eval_metric="rmse",
|
|
59
|
+
early_stopping_rounds=25,
|
|
60
|
+
random_state=42,
|
|
61
|
+
)
|
|
62
|
+
model.fit(X_train, y_train, eval_set=[(X_val, y_val)], verbose=False)
|
|
63
|
+
return model
|
|
64
|
+
except Exception:
|
|
65
|
+
from sklearn.ensemble import RandomForestRegressor
|
|
66
|
+
|
|
67
|
+
model = RandomForestRegressor(
|
|
68
|
+
n_estimators=250,
|
|
69
|
+
max_depth=10,
|
|
70
|
+
min_samples_leaf=5,
|
|
71
|
+
random_state=42,
|
|
72
|
+
n_jobs=-1,
|
|
73
|
+
)
|
|
74
|
+
model.fit(X_train, y_train)
|
|
75
|
+
return model
|
|
76
|
+
|
|
77
|
+
def evaluate(self, model, X_test, y_test):
|
|
78
|
+
from sklearn.metrics import mean_absolute_error, r2_score
|
|
79
|
+
|
|
80
|
+
preds = np.clip(model.predict(X_test), 0, 100)
|
|
81
|
+
return {
|
|
82
|
+
"r2": float(r2_score(y_test, preds)),
|
|
83
|
+
"mae": float(mean_absolute_error(y_test, preds)),
|
|
84
|
+
"test_samples": len(y_test),
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def save(self, model, metrics):
|
|
88
|
+
import joblib
|
|
89
|
+
|
|
90
|
+
super().save(model, metrics)
|
|
91
|
+
artifact_path = self.artifact_dir.parent / "trend_scorer.joblib"
|
|
92
|
+
joblib.dump(
|
|
93
|
+
{
|
|
94
|
+
"model": model,
|
|
95
|
+
"trained_at": str(int(time.time())),
|
|
96
|
+
"accuracy": metrics.get("r2"),
|
|
97
|
+
"feature_keys": self.FEATURE_KEYS,
|
|
98
|
+
},
|
|
99
|
+
artifact_path,
|
|
100
|
+
)
|
|
101
|
+
return str(artifact_path)
|