@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.
Files changed (53) hide show
  1. package/README.md +250 -192
  2. package/chronovisor-engine/pyproject.toml +31 -0
  3. package/chronovisor-engine/src/__init__.py +0 -0
  4. package/chronovisor-engine/src/inference/__init__.py +0 -0
  5. package/chronovisor-engine/src/inference/predict.py +44 -0
  6. package/chronovisor-engine/src/model_catalog.py +219 -0
  7. package/chronovisor-engine/src/models/__init__.py +0 -0
  8. package/chronovisor-engine/src/models/anomaly_detector.py +104 -0
  9. package/chronovisor-engine/src/models/blockchain_cycle_analyzer.py +217 -0
  10. package/chronovisor-engine/src/models/catalyst_event_model.py +70 -0
  11. package/chronovisor-engine/src/models/conformal_interval.py +50 -0
  12. package/chronovisor-engine/src/models/divergence_detector.py +247 -0
  13. package/chronovisor-engine/src/models/drift_monitor.py +51 -0
  14. package/chronovisor-engine/src/models/intent_classifier.py +189 -0
  15. package/chronovisor-engine/src/models/lstm_predictor.py +143 -0
  16. package/chronovisor-engine/src/models/microstructure_specialist.py +65 -0
  17. package/chronovisor-engine/src/models/narrative_detector.py +418 -0
  18. package/chronovisor-engine/src/models/portfolio_optimizer.py +162 -0
  19. package/chronovisor-engine/src/models/project_risk_scorer.py +184 -0
  20. package/chronovisor-engine/src/models/pump_detector.py +344 -0
  21. package/chronovisor-engine/src/models/regime_detector.py +127 -0
  22. package/chronovisor-engine/src/models/rug_detector.py +197 -0
  23. package/chronovisor-engine/src/models/sentiment_analyzer.py +257 -0
  24. package/chronovisor-engine/src/models/signal_classifier.py +191 -0
  25. package/chronovisor-engine/src/models/stacking_meta.py +56 -0
  26. package/chronovisor-engine/src/models/strategy_bandit.py +191 -0
  27. package/chronovisor-engine/src/models/ta_interpreter.py +341 -0
  28. package/chronovisor-engine/src/models/target_quantile.py +96 -0
  29. package/chronovisor-engine/src/models/trend_scorer.py +107 -0
  30. package/chronovisor-engine/src/models/wallet_classifier.py +261 -0
  31. package/chronovisor-engine/src/server.py +1686 -0
  32. package/chronovisor-engine/src/training/__init__.py +0 -0
  33. package/chronovisor-engine/src/training/data_loader.py +635 -0
  34. package/chronovisor-engine/src/training/pipeline.py +130 -0
  35. package/chronovisor-engine/src/training/train_catalyst.py +169 -0
  36. package/chronovisor-engine/src/training/train_classifier.py +159 -0
  37. package/chronovisor-engine/src/training/train_conformal.py +106 -0
  38. package/chronovisor-engine/src/training/train_direction.py +215 -0
  39. package/chronovisor-engine/src/training/train_drift.py +57 -0
  40. package/chronovisor-engine/src/training/train_isotonic.py +58 -0
  41. package/chronovisor-engine/src/training/train_lstm.py +217 -0
  42. package/chronovisor-engine/src/training/train_microstructure.py +102 -0
  43. package/chronovisor-engine/src/training/train_narrative.py +168 -0
  44. package/chronovisor-engine/src/training/train_pump.py +109 -0
  45. package/chronovisor-engine/src/training/train_regime.py +116 -0
  46. package/chronovisor-engine/src/training/train_rug.py +58 -0
  47. package/chronovisor-engine/src/training/train_sentiment.py +63 -0
  48. package/chronovisor-engine/src/training/train_stacking_meta.py +74 -0
  49. package/chronovisor-engine/src/training/train_target_quantile.py +115 -0
  50. package/chronovisor-engine/src/training/train_trend.py +101 -0
  51. package/dist/index.js +19124 -11698
  52. package/dist/index.js.map +1 -1
  53. package/package.json +3 -1
@@ -0,0 +1,191 @@
1
+ """Random Forest signal classifier.
2
+
3
+ Input: AgentSignals + derived features → buy/sell/hold classification.
4
+ Labels derived from outcome: did price go up/down in next N hours?
5
+ Replaces hardcoded thresholds in strategies.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+
13
+ MODEL_DIR = Path(os.getenv("MODEL_DIR", "models"))
14
+
15
+ # Feature keys matching FeatureVector from Node.js side
16
+ FEATURE_KEYS = [
17
+ "rsi", "macdHistogram", "bollingerPercentB", "ema12", "ema26",
18
+ "atr", "obv", "fundingRate", "fearGreed", "priceChange24h",
19
+ "rsiSlope", "volumeRatio", "emaCrossoverPct", "atrPct",
20
+ ]
21
+
22
+
23
+ class SignalClassifier:
24
+ def __init__(self):
25
+ self.version = "0.1.0"
26
+ self.is_loaded = False
27
+ self.last_trained: str | None = None
28
+ self.accuracy: float | None = None
29
+ self.model = None
30
+ self.xgb_models: dict = {} # profile → XGBClassifier
31
+
32
+ def load(self):
33
+ """Load trained model: prefer XGBoost > Random Forest > heuristic."""
34
+ # Try XGBoost direction models first (one per profile)
35
+ self._load_xgboost_models()
36
+ if self.xgb_models:
37
+ self.is_loaded = True
38
+ self.version = "0.2.0-xgboost"
39
+ return
40
+
41
+ # Fall back to Random Forest
42
+ model_path = MODEL_DIR / "signal_classifier.joblib"
43
+ if model_path.exists():
44
+ try:
45
+ import joblib
46
+ self.model = joblib.load(model_path)
47
+ self.is_loaded = True
48
+ self.last_trained = str(model_path.stat().st_mtime)
49
+ except Exception:
50
+ self._init_heuristic()
51
+ else:
52
+ self._init_heuristic()
53
+
54
+ def _load_xgboost_models(self):
55
+ """Load XGBoost direction models for each horizon profile."""
56
+ for profile in ("scalp", "standard", "position"):
57
+ model_path = MODEL_DIR / f"xgb_direction_{profile}.json"
58
+ if model_path.exists():
59
+ try:
60
+ import xgboost as xgb
61
+ m = xgb.XGBClassifier()
62
+ m.load_model(str(model_path))
63
+ self.xgb_models[profile] = m
64
+ self.last_trained = str(model_path.stat().st_mtime)
65
+ except Exception:
66
+ pass # Skip this profile, fall through to RF or heuristic
67
+
68
+ def _init_heuristic(self):
69
+ self.is_loaded = True
70
+ self.version = "0.1.0-heuristic"
71
+
72
+ def predict(self, features: dict) -> dict:
73
+ """Classify signals into buy/sell/hold.
74
+
75
+ Returns:
76
+ dict with keys: direction, probability, model
77
+ """
78
+ # Prefer XGBoost when available for the given horizon profile
79
+ if self.xgb_models:
80
+ return self._predict_xgboost(features)
81
+ if self.model is not None:
82
+ return self._predict_model(features)
83
+ return self._predict_heuristic(features)
84
+
85
+ def _predict_xgboost(self, features: dict) -> dict:
86
+ """Run inference through trained XGBoost direction model."""
87
+ horizon = features.get("horizon", "4h")
88
+ # Determine profile from horizon
89
+ scalp = {"5m", "15m", "30m"}
90
+ position = {"1d", "7d"}
91
+ if horizon in scalp:
92
+ profile = "scalp"
93
+ elif horizon in position:
94
+ profile = "position"
95
+ else:
96
+ profile = "standard"
97
+
98
+ model = self.xgb_models.get(profile) or self.xgb_models.get("standard")
99
+ if model is None:
100
+ return self._predict_heuristic(features)
101
+
102
+ from ..training.train_direction import ALL_FEATURE_KEYS
103
+ X = np.array([[features.get(k, 0) for k in ALL_FEATURE_KEYS]])
104
+ proba = model.predict_proba(X)[0]
105
+ idx = int(np.argmax(proba))
106
+ direction_map = {0: "down", 1: "sideways", 2: "up"}
107
+ return {
108
+ "direction": direction_map.get(idx, "sideways"),
109
+ "probability": float(proba[idx]),
110
+ "model": f"xgb-direction-{profile}",
111
+ }
112
+
113
+ def _predict_model(self, features: dict) -> dict:
114
+ """Run inference through trained Random Forest."""
115
+ X = np.array([[features.get(k, 0) for k in FEATURE_KEYS]])
116
+ proba = self.model.predict_proba(X)[0]
117
+ classes = self.model.classes_
118
+ idx = int(np.argmax(proba))
119
+ direction_map = {"buy": "up", "sell": "down", "hold": "sideways"}
120
+ direction = direction_map.get(classes[idx], "sideways")
121
+ return {
122
+ "direction": direction,
123
+ "probability": float(proba[idx]),
124
+ "model": f"signal-classifier-{self.version}",
125
+ }
126
+
127
+ def _predict_heuristic(self, features: dict) -> dict:
128
+ """Rule-based classification until model is trained.
129
+
130
+ Conservative by design: high direction threshold (±40) and capped
131
+ probability (0.70) so the heuristic doesn't feed overconfident
132
+ directional signal into the CF algebra ensemble.
133
+ """
134
+ rsi = features.get("rsi", 50)
135
+ macd = features.get("macdHistogram", 0)
136
+ bb_pct = features.get("bollingerPercentB", 0.5)
137
+ funding = features.get("fundingRate", 0)
138
+ fg = features.get("fearGreed", 50)
139
+ rsi_slope = features.get("rsiSlope", 0)
140
+ vol_ratio = features.get("volumeRatio", 1)
141
+ ema_cross = features.get("emaCrossoverPct", 0)
142
+
143
+ score = 0.0
144
+
145
+ # RSI signal — only extreme values trigger
146
+ if rsi < 25:
147
+ score += 25
148
+ elif rsi < 35:
149
+ score += 10
150
+ elif rsi > 75:
151
+ score -= 25
152
+ elif rsi > 65:
153
+ score -= 10
154
+
155
+ # MACD — capped contribution to ±15
156
+ macd_contrib = max(-15, min(15, macd * 10))
157
+ score += macd_contrib
158
+
159
+ # Bollinger Bands — only extremes
160
+ if bb_pct < 0.1:
161
+ score += 12
162
+ elif bb_pct > 0.9:
163
+ score -= 12
164
+
165
+ # Funding rate (contrarian)
166
+ if funding > 0.0005:
167
+ score -= 8
168
+ elif funding < -0.0003:
169
+ score += 8
170
+
171
+ # Fear & Greed (contrarian at extremes only)
172
+ if fg < 15:
173
+ score += 8
174
+ elif fg > 85:
175
+ score -= 8
176
+
177
+ # Momentum signals — reduced contribution
178
+ score += rsi_slope * 1.2
179
+ if vol_ratio > 2.5:
180
+ score += 6 if score > 0 else -6
181
+ score += ema_cross * 3
182
+
183
+ # Flatter probability curve: divisor 300 → max ~0.70 at extreme scores
184
+ probability = min(0.70, max(0.3, 0.5 + abs(score) / 300))
185
+
186
+ # Higher direction threshold: ±40 → more sideways classifications
187
+ if score > 40:
188
+ return {"direction": "up", "probability": probability, "model": "rf-heuristic"}
189
+ elif score < -40:
190
+ return {"direction": "down", "probability": probability, "model": "rf-heuristic"}
191
+ return {"direction": "sideways", "probability": probability, "model": "rf-heuristic"}
@@ -0,0 +1,56 @@
1
+ """Meta-confidence model for champion/challenger promotion and filtering."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import joblib
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ MODEL_DIR = Path(os.getenv("MODEL_DIR", "models"))
11
+
12
+
13
+ class StackingMetaModel:
14
+ def __init__(self) -> None:
15
+ self.version = "0.1.0"
16
+ self.is_loaded = False
17
+ self.last_trained: str | None = None
18
+ self.accuracy: float | None = None
19
+ self.model = None
20
+ self.columns: list[str] = []
21
+
22
+ def load(self) -> None:
23
+ path = MODEL_DIR / "meta_stacking.joblib"
24
+ try:
25
+ data = joblib.load(path)
26
+ self.model = data["model"]
27
+ self.columns = data.get("columns", [])
28
+ self.last_trained = data.get("trained_at")
29
+ self.accuracy = data.get("accuracy")
30
+ self.is_loaded = True
31
+ except Exception:
32
+ self.model = None
33
+ self.columns = []
34
+ self.is_loaded = True
35
+
36
+ def predict(self, features: dict) -> dict:
37
+ if self.model is None:
38
+ probability = float(features.get("probability", 0.5))
39
+ return {
40
+ "correctness_probability": round(probability, 4),
41
+ "verdict": "take" if probability >= 0.6 else "skip",
42
+ "model": "heuristic-meta-stacking",
43
+ }
44
+
45
+ frame = pd.DataFrame([features])
46
+ frame = pd.get_dummies(frame, columns=["model", "horizon"], dtype=float)
47
+ for column in self.columns:
48
+ if column not in frame:
49
+ frame[column] = 0.0
50
+ frame = frame[self.columns]
51
+ probability = float(self.model.predict_proba(frame)[0][1])
52
+ return {
53
+ "correctness_probability": round(probability, 4),
54
+ "verdict": "take" if probability >= 0.6 else "skip",
55
+ "model": "meta_stacking",
56
+ }
@@ -0,0 +1,191 @@
1
+ """Strategy Bandit — Contextual bandit for trading action selection."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import joblib
7
+ import numpy as np
8
+
9
+ MODEL_DIR = Path(os.getenv("MODEL_DIR", "models"))
10
+
11
+
12
+ class StrategyBandit:
13
+ """Selects buy/sell/hold actions using contextual bandit or heuristic fallback."""
14
+
15
+ FEATURE_KEYS = [
16
+ "rsi",
17
+ "macd_histogram",
18
+ "ema12",
19
+ "ema26",
20
+ "bollinger_pct_b",
21
+ "atr",
22
+ "obv",
23
+ "funding_rate",
24
+ "fear_greed",
25
+ "price_change_24h",
26
+ "price",
27
+ "regime",
28
+ ]
29
+
30
+ REGIME_MAP = {
31
+ "trending_bull": 1.0,
32
+ "trending_bear": -1.0,
33
+ "ranging": 0.0,
34
+ "volatile": 0.5,
35
+ "capitulation": -2.0,
36
+ }
37
+
38
+ def __init__(self) -> None:
39
+ self.version = "0.1.0"
40
+ self.is_loaded = False
41
+ self.last_trained: str | None = None
42
+ self.accuracy: float | None = None
43
+ self.model = None
44
+ self.epsilon = 0.1
45
+
46
+ def load(self) -> None:
47
+ model_path = MODEL_DIR / "strategy_bandit.joblib"
48
+ try:
49
+ data = joblib.load(model_path)
50
+ self.model = data["model"]
51
+ self.epsilon = data.get("epsilon", 0.1)
52
+ self.last_trained = data.get("trained_at")
53
+ self.accuracy = data.get("accuracy")
54
+ self.is_loaded = True
55
+ except Exception:
56
+ self.model = None
57
+ self.is_loaded = True # heuristic fallback ready
58
+
59
+ def predict(self, features: dict) -> dict:
60
+ if self.model is not None:
61
+ return self._predict_model(features)
62
+ return self._predict_heuristic(features)
63
+
64
+ def _predict_model(self, features: dict) -> dict:
65
+ # Encode regime as numeric
66
+ regime_val = self.REGIME_MAP.get(str(features.get("regime", "ranging")), 0.0)
67
+ x_raw = []
68
+ for k in self.FEATURE_KEYS:
69
+ if k == "regime":
70
+ x_raw.append(regime_val)
71
+ else:
72
+ x_raw.append(features.get(k, 0))
73
+
74
+ x = np.array([x_raw])
75
+
76
+ # Model predicts action probabilities
77
+ proba = self.model.predict_proba(x)[0]
78
+ classes = list(self.model.classes_)
79
+
80
+ pred_idx = int(np.argmax(proba))
81
+ action = classes[pred_idx] if pred_idx < len(classes) else "hold"
82
+ confidence = float(proba[pred_idx]) * 100
83
+
84
+ # Position size based on confidence
85
+ position_size_pct = min(25, confidence * 0.25)
86
+
87
+ reasoning = [
88
+ f"ML bandit: {action} with {confidence:.0f}% confidence",
89
+ ]
90
+
91
+ return {
92
+ "action": action,
93
+ "confidence": round(min(100, confidence), 2),
94
+ "position_size_pct": round(position_size_pct, 2),
95
+ "reasoning": reasoning,
96
+ "model": "contextual-bandit",
97
+ }
98
+
99
+ def _predict_heuristic(self, features: dict) -> dict:
100
+ reasoning = []
101
+ score = 0
102
+
103
+ rsi = features.get("rsi", 50)
104
+ macd_hist = features.get("macd_histogram", 0)
105
+ ema12 = features.get("ema12", 0)
106
+ ema26 = features.get("ema26", 0)
107
+ bb_pct_b = features.get("bollinger_pct_b", 0.5)
108
+ funding = features.get("funding_rate", 0)
109
+ fg = features.get("fear_greed", 50)
110
+ pc24h = features.get("price_change_24h", 0)
111
+
112
+ # RSI with adaptive thresholds
113
+ if rsi < 25:
114
+ score += 35
115
+ reasoning.append(f"RSI deeply oversold ({rsi:.0f})")
116
+ elif rsi < 35:
117
+ score += 20
118
+ reasoning.append(f"RSI oversold zone ({rsi:.0f})")
119
+ elif rsi > 75:
120
+ score -= 35
121
+ reasoning.append(f"RSI deeply overbought ({rsi:.0f})")
122
+ elif rsi > 65:
123
+ score -= 20
124
+ reasoning.append(f"RSI overbought zone ({rsi:.0f})")
125
+
126
+ # MACD
127
+ if macd_hist > 0:
128
+ score += 15
129
+ reasoning.append("MACD bullish momentum")
130
+ elif macd_hist < 0:
131
+ score -= 15
132
+ reasoning.append("MACD bearish momentum")
133
+
134
+ # EMA crossover
135
+ if ema26 != 0:
136
+ cross_pct = ((ema12 - ema26) / ema26) * 100
137
+ if cross_pct > 0.5:
138
+ score += 20
139
+ reasoning.append(f"Golden cross (EMA gap {cross_pct:.2f}%)")
140
+ elif cross_pct < -0.5:
141
+ score -= 20
142
+ reasoning.append(f"Death cross (EMA gap {cross_pct:.2f}%)")
143
+
144
+ # Bollinger Bands
145
+ if bb_pct_b < 0.1:
146
+ score += 15
147
+ reasoning.append("Price at lower Bollinger Band")
148
+ elif bb_pct_b > 0.9:
149
+ score -= 15
150
+ reasoning.append("Price at upper Bollinger Band")
151
+
152
+ # Funding rate (contrarian)
153
+ if funding > 0.0005:
154
+ score -= 10
155
+ reasoning.append("High funding rate — overleveraged longs")
156
+ elif funding < -0.0003:
157
+ score += 10
158
+ reasoning.append("Negative funding — capitulation signal")
159
+
160
+ # Fear & Greed
161
+ if fg < 20:
162
+ score += 10
163
+ reasoning.append("Extreme fear — contrarian bullish")
164
+ elif fg > 80:
165
+ score -= 10
166
+ reasoning.append("Extreme greed — contrarian bearish")
167
+
168
+ # 24h trend
169
+ if pc24h > 5:
170
+ score += 10
171
+ elif pc24h < -5:
172
+ score -= 10
173
+
174
+ confidence = min(95, abs(score))
175
+ position_size_pct = min(25, confidence * 0.25)
176
+
177
+ if score > 20:
178
+ action = "buy"
179
+ elif score < -20:
180
+ action = "sell"
181
+ else:
182
+ action = "hold"
183
+ reasoning.append("Mixed signals — holding")
184
+
185
+ return {
186
+ "action": action,
187
+ "confidence": round(confidence, 2),
188
+ "position_size_pct": round(position_size_pct, 2),
189
+ "reasoning": reasoning,
190
+ "model": "heuristic-strategy-bandit",
191
+ }