bulltrackers-module 1.0.766 → 1.0.769
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/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
- package/functions/computation-system-v2/computations/BehavioralAnomaly.js +559 -227
- package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
- package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
- package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
- package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
- package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
- package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
- package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
- package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
- package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
- package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +40 -126
- package/functions/computation-system-v2/core-api.js +17 -9
- package/functions/computation-system-v2/data_schema_reference.MD +108 -0
- package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
- package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
- package/functions/computation-system-v2/devtools/index.js +36 -0
- package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
- package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
- package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
- package/functions/computation-system-v2/devtools/shared/index.js +16 -0
- package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
- package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
- package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
- package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
- package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
- package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
- package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
- package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
- package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
- package/functions/computation-system-v2/framework/data/DataFetcher.js +330 -126
- package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +226 -153
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
- package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
- package/functions/computation-system-v2/framework/storage/StorageManager.js +111 -83
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +161 -66
- package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
- package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
- package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
- package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
- package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
- package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
- package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
- package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
- package/package.json +1 -1
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
- package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
- package/functions/computation-system-v2/test/analyze-results.js +0 -238
- package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
- package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
- package/functions/computation-system-v2/test/other/test-framework.js +0 -500
- package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
- package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
- package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
- package/functions/computation-system-v2/test/other/test-results.json +0 -31
- package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
- package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
- package/functions/computation-system-v2/test/other/test-storage.js +0 -449
- package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
- package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
- package/functions/computation-system-v2/test/test-worker-pool.js +0 -266
|
@@ -1,5 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Behavioral Anomaly Detection V4.1 - Predictive Risk Intelligence
|
|
3
|
+
* * ARCHITECTURE:
|
|
4
|
+
* - Hybrid Math: Uses 6-Dim Core for Stability + 18-Dim Full Spectrum for Detail.
|
|
5
|
+
* - Cost Control: Strict 30-day lookback enforced (compatible with DAG constraints).
|
|
6
|
+
* - Logic Fix: "Primary Driver" uses Statistical Z-Scores, not raw values.
|
|
7
|
+
* - Robustness: Dynamic Z-Scoring removes dependency on hardcoded divisors.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const { Computation } = require('../framework');
|
|
2
11
|
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// ADVANCED MATH KERNEL
|
|
14
|
+
// =============================================================================
|
|
15
|
+
const AdvancedMath = {
|
|
16
|
+
entropy: (distribution) => {
|
|
17
|
+
const total = distribution.reduce((sum, val) => sum + val, 0);
|
|
18
|
+
if (total === 0) return 0;
|
|
19
|
+
let entropy = 0;
|
|
20
|
+
for (const val of distribution) {
|
|
21
|
+
if (val > 0) {
|
|
22
|
+
const p = val / total;
|
|
23
|
+
entropy -= p * Math.log2(p);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return entropy;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
hhi: (shares) => {
|
|
30
|
+
const total = shares.reduce((sum, val) => sum + val, 0);
|
|
31
|
+
if (total === 0) return 0;
|
|
32
|
+
let hhi = 0;
|
|
33
|
+
for (const share of shares) {
|
|
34
|
+
const marketShare = share / total;
|
|
35
|
+
hhi += marketShare * marketShare;
|
|
36
|
+
}
|
|
37
|
+
return hhi * 10000;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
velocity: (current, previous, timeDelta = 1) => {
|
|
41
|
+
if (timeDelta === 0) return 0;
|
|
42
|
+
return (current - previous) / timeDelta;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
acceleration: (current, previous, previousPrevious, timeDelta = 1) => {
|
|
46
|
+
const v1 = AdvancedMath.velocity(current, previous, timeDelta);
|
|
47
|
+
const v0 = AdvancedMath.velocity(previous, previousPrevious, timeDelta);
|
|
48
|
+
return AdvancedMath.velocity(v1, v0, timeDelta);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
percentileRank: (value, array) => {
|
|
52
|
+
if (!array.length) return 0.5;
|
|
53
|
+
let rank = 0;
|
|
54
|
+
for (const val of array) {
|
|
55
|
+
if (val < value) rank++;
|
|
56
|
+
}
|
|
57
|
+
return rank / array.length;
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
detectRegime: (recentVectors) => {
|
|
61
|
+
if (recentVectors.length < 3) return 'unknown';
|
|
62
|
+
// Calculate aggregate volatility across the CORE features (indices 0-5)
|
|
63
|
+
const coreIndices = [0, 1, 2, 3, 4, 5];
|
|
64
|
+
const volatilities = [];
|
|
65
|
+
|
|
66
|
+
for (const f of coreIndices) {
|
|
67
|
+
const values = recentVectors.map(v => v[f]);
|
|
68
|
+
const mean = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
69
|
+
const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
|
|
70
|
+
volatilities.push(Math.sqrt(variance));
|
|
71
|
+
}
|
|
72
|
+
const avgVolatility = volatilities.reduce((sum, v) => sum + v, 0) / volatilities.length;
|
|
73
|
+
|
|
74
|
+
if (avgVolatility < 0.3) return 'calm';
|
|
75
|
+
if (avgVolatility < 0.8) return 'active';
|
|
76
|
+
return 'stressed';
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// FEATURE ENGINEERING
|
|
82
|
+
// =============================================================================
|
|
83
|
+
const FeatureExtractor = {
|
|
84
|
+
extract: (day, prevDay, prevPrevDay, historyBlob, rankings, rules, maps) => {
|
|
85
|
+
const { portfolio } = day;
|
|
86
|
+
const positions = rules.portfolio.extractPositions(portfolio) || [];
|
|
87
|
+
const rankingsData = day.rankings || {};
|
|
88
|
+
const { sectorMap, tickerMap } = maps;
|
|
89
|
+
|
|
90
|
+
// Resolve Sector
|
|
91
|
+
const getSector = (instId) => {
|
|
92
|
+
const ticker = tickerMap.get(String(instId));
|
|
93
|
+
if (!ticker) return 'Unknown';
|
|
94
|
+
return sectorMap.get(ticker) || 'Unknown';
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 1. Sector Concentration (HHI)
|
|
98
|
+
const sectorExposure = {};
|
|
99
|
+
let totalInvestedPct = 0;
|
|
100
|
+
positions.forEach(p => {
|
|
101
|
+
const sector = getSector(p.InstrumentID);
|
|
102
|
+
const val = p.Invested || 0;
|
|
103
|
+
sectorExposure[sector] = (sectorExposure[sector] || 0) + val;
|
|
104
|
+
totalInvestedPct += val;
|
|
105
|
+
});
|
|
106
|
+
const sectorHHI = AdvancedMath.hhi(Object.values(sectorExposure));
|
|
107
|
+
|
|
108
|
+
// 2. Martingale/Distress Score
|
|
109
|
+
let martingaleScore = 0;
|
|
110
|
+
let martingaleCount = 0;
|
|
111
|
+
positions.forEach(p => {
|
|
112
|
+
const pnl = p.NetProfit || 0;
|
|
113
|
+
const invested = p.Invested || 0;
|
|
114
|
+
if (pnl < -4 && invested > 4) {
|
|
115
|
+
martingaleScore += Math.abs(pnl) * invested;
|
|
116
|
+
martingaleCount++;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 3. Leverage Profile (Lazy Loaded)
|
|
121
|
+
const { avgLeverage, highLevCount } = FeatureExtractor._extractLeverageProfile(day.date, historyBlob);
|
|
122
|
+
|
|
123
|
+
// 4. Risk Score
|
|
124
|
+
const riskScore = rankingsData.RiskScore || 1;
|
|
125
|
+
|
|
126
|
+
// 5. Complexity
|
|
127
|
+
const complexity = positions.length;
|
|
128
|
+
|
|
129
|
+
// 6. Exposure
|
|
130
|
+
const exposure = totalInvestedPct;
|
|
131
|
+
|
|
132
|
+
// --- EXTENDED FEATURES (Full Spectrum) ---
|
|
133
|
+
|
|
134
|
+
// 7. Entropy
|
|
135
|
+
const positionSizes = positions.map(p => p.Invested || 0);
|
|
136
|
+
const portfolioEntropy = AdvancedMath.entropy(positionSizes);
|
|
137
|
+
|
|
138
|
+
// 8. Drawdown
|
|
139
|
+
const drawdownSeverity = Math.abs(rankingsData.PeakToValley || 0);
|
|
140
|
+
|
|
141
|
+
// 9. Win Rate Deviation
|
|
142
|
+
const winRateDeviation = Math.abs((rankingsData.WinRatio || 50) - 50);
|
|
143
|
+
|
|
144
|
+
// 10. Skewness
|
|
145
|
+
const sortedSizes = [...positionSizes].sort((a, b) => b - a);
|
|
146
|
+
const top3Share = sortedSizes.slice(0, 3).reduce((sum, v) => sum + v, 0);
|
|
147
|
+
const positionSkewness = totalInvestedPct > 0 ? top3Share / totalInvestedPct : 0;
|
|
148
|
+
|
|
149
|
+
// 11. Stress Ratio
|
|
150
|
+
const losingCount = positions.filter(p => (p.NetProfit || 0) < 0).length;
|
|
151
|
+
const stressRatio = positions.length > 0 ? losingCount / positions.length : 0;
|
|
152
|
+
|
|
153
|
+
// 12. Credit Imbalance
|
|
154
|
+
const realizedCredit = portfolio?.CreditByRealizedEquity || 0;
|
|
155
|
+
const unrealizedCredit = portfolio?.CreditByUnrealizedEquity || 0;
|
|
156
|
+
const creditImbalance = Math.abs(realizedCredit - unrealizedCredit);
|
|
157
|
+
|
|
158
|
+
// 13. High Lev Freq
|
|
159
|
+
const highLevFrequency = highLevCount / Math.max(1, positions.length);
|
|
160
|
+
|
|
161
|
+
// 14. Copier Momentum
|
|
162
|
+
const copiers = rankingsData.Copiers || 0;
|
|
163
|
+
const baselineCopiers = rankingsData.BaseLineCopiers || copiers;
|
|
164
|
+
const copierMomentum = baselineCopiers > 0 ? (copiers - baselineCopiers) / baselineCopiers : 0;
|
|
165
|
+
|
|
166
|
+
// 15. AUM Tier
|
|
167
|
+
const aumTier = (rankingsData.AUMTier || 0);
|
|
168
|
+
|
|
169
|
+
// 16. Exposure Velocity
|
|
170
|
+
let exposureVelocity = 0;
|
|
171
|
+
if (prevDay && prevDay.portfolio) {
|
|
172
|
+
const prevPositions = rules.portfolio.extractPositions(prevDay.portfolio) || [];
|
|
173
|
+
const prevExposure = prevPositions.reduce((sum, p) => sum + (p.Invested || 0), 0);
|
|
174
|
+
exposureVelocity = exposure - prevExposure;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 17. Risk Acceleration
|
|
178
|
+
let riskAcceleration = 0;
|
|
179
|
+
if (prevDay && prevDay.rankings && prevPrevDay && prevPrevDay.rankings) {
|
|
180
|
+
const r0 = prevPrevDay.rankings.RiskScore || 0;
|
|
181
|
+
const r1 = prevDay.rankings.RiskScore || 0;
|
|
182
|
+
const r2 = riskScore;
|
|
183
|
+
riskAcceleration = AdvancedMath.acceleration(r2, r1, r0, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 18. Behavioral Momentum
|
|
187
|
+
const behavioralMomentum = Math.abs(exposureVelocity) + Math.abs(riskAcceleration);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
vector: [
|
|
191
|
+
// CORE (0-5)
|
|
192
|
+
sectorHHI,
|
|
193
|
+
Math.log(martingaleScore + 1),
|
|
194
|
+
avgLeverage,
|
|
195
|
+
riskScore,
|
|
196
|
+
complexity,
|
|
197
|
+
exposure,
|
|
198
|
+
|
|
199
|
+
// EXTENDED (6-17)
|
|
200
|
+
portfolioEntropy,
|
|
201
|
+
drawdownSeverity,
|
|
202
|
+
winRateDeviation,
|
|
203
|
+
positionSkewness,
|
|
204
|
+
stressRatio,
|
|
205
|
+
creditImbalance,
|
|
206
|
+
highLevFrequency,
|
|
207
|
+
copierMomentum,
|
|
208
|
+
aumTier,
|
|
209
|
+
exposureVelocity,
|
|
210
|
+
riskAcceleration,
|
|
211
|
+
behavioralMomentum
|
|
212
|
+
],
|
|
213
|
+
metadata: {
|
|
214
|
+
martingaleCount,
|
|
215
|
+
losingCount,
|
|
216
|
+
totalPositions: positions.length,
|
|
217
|
+
exposure,
|
|
218
|
+
regime: 'unknown'
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
_extractLeverageProfile: (dateStr, historyBlob) => {
|
|
224
|
+
const targetDate = new Date(dateStr);
|
|
225
|
+
const lookbackWindow = new Date(targetDate);
|
|
226
|
+
lookbackWindow.setDate(lookbackWindow.getDate() - 90);
|
|
227
|
+
|
|
228
|
+
let sumLev = 0;
|
|
229
|
+
let tradeCount = 0;
|
|
230
|
+
let highLevCount = 0;
|
|
231
|
+
|
|
232
|
+
for (const t of historyBlob) {
|
|
233
|
+
const closeDate = new Date(t.CloseDateTime);
|
|
234
|
+
if (closeDate <= targetDate && closeDate >= lookbackWindow) {
|
|
235
|
+
const lev = t.Leverage || 1;
|
|
236
|
+
sumLev += lev;
|
|
237
|
+
tradeCount++;
|
|
238
|
+
if (lev > 2) highLevCount++;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
avgLeverage: tradeCount > 0 ? sumLev / tradeCount : 1,
|
|
244
|
+
highLevCount
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// =============================================================================
|
|
250
|
+
// ANOMALY DETECTION ENGINE
|
|
251
|
+
// =============================================================================
|
|
252
|
+
const AnomalyEngine = {
|
|
253
|
+
score: (todayFeatures, historicalFeatures, regime) => {
|
|
254
|
+
// 1. DYNAMIC STANDARDIZATION (Z-Scoring)
|
|
255
|
+
// We capture zVector here because it represents the "True Statistical Deviation"
|
|
256
|
+
const { zVector, histZVectors } = AnomalyEngine._standardize(todayFeatures.vector, historicalFeatures);
|
|
257
|
+
|
|
258
|
+
// 2. HYBRID MAHALANOBIS (Core Features Only)
|
|
259
|
+
// Slicing indices 0-5 (Core) for structural stability on 30-day lookback
|
|
260
|
+
const coreZVector = zVector.slice(0, 6);
|
|
261
|
+
const coreHistZVectors = histZVectors.map(v => v.slice(0, 6));
|
|
262
|
+
const mahalanobisScore = AnomalyEngine._mahalanobisDistance(coreZVector, coreHistZVectors);
|
|
263
|
+
|
|
264
|
+
// 3. FULL SPECTRUM PERCENTILE (All 18 Features)
|
|
265
|
+
const percentileScore = AnomalyEngine._percentileScore(todayFeatures.vector, historicalFeatures);
|
|
266
|
+
|
|
267
|
+
// 4. REGIME CONDITIONAL
|
|
268
|
+
const regimeScore = AnomalyEngine._regimeScore(todayFeatures, historicalFeatures, regime);
|
|
269
|
+
|
|
270
|
+
// 5. VELOCITY
|
|
271
|
+
const velocityScore = (Math.abs(zVector[15]) + Math.abs(zVector[16]) + Math.abs(zVector[17])) / 3;
|
|
272
|
+
|
|
273
|
+
const ensembleScore =
|
|
274
|
+
0.40 * mahalanobisScore +
|
|
275
|
+
0.30 * percentileScore +
|
|
276
|
+
0.15 * regimeScore +
|
|
277
|
+
0.15 * velocityScore;
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
overall: ensembleScore,
|
|
281
|
+
components: {
|
|
282
|
+
mahalanobis: mahalanobisScore,
|
|
283
|
+
percentile: percentileScore,
|
|
284
|
+
regime: regimeScore,
|
|
285
|
+
velocity: velocityScore
|
|
286
|
+
},
|
|
287
|
+
// EXPORT Z-SCORES FOR INTERPRETER
|
|
288
|
+
zScores: zVector
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
_standardize: (todayVec, history) => {
|
|
293
|
+
const dim = todayVec.length;
|
|
294
|
+
const n = history.length;
|
|
295
|
+
const means = new Array(dim).fill(0);
|
|
296
|
+
const stdDevs = new Array(dim).fill(0);
|
|
297
|
+
|
|
298
|
+
const vectors = history.map(h => h.vector);
|
|
299
|
+
for (const v of vectors) for (let i = 0; i < dim; i++) means[i] += v[i];
|
|
300
|
+
for (let i = 0; i < dim; i++) means[i] /= n;
|
|
301
|
+
|
|
302
|
+
for (const v of vectors) for (let i = 0; i < dim; i++) stdDevs[i] += Math.pow(v[i] - means[i], 2);
|
|
303
|
+
for (let i = 0; i < dim; i++) {
|
|
304
|
+
stdDevs[i] = Math.sqrt(stdDevs[i] / n);
|
|
305
|
+
if (stdDevs[i] === 0) stdDevs[i] = 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const zVector = todayVec.map((v, i) => (v - means[i]) / stdDevs[i]);
|
|
309
|
+
const histZVectors = vectors.map(v => v.map((val, i) => (val - means[i]) / stdDevs[i]));
|
|
310
|
+
|
|
311
|
+
return { zVector, histZVectors };
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
_mahalanobisDistance: (todayZVec, histZVectors) => {
|
|
315
|
+
const means = new Array(todayZVec.length).fill(0);
|
|
316
|
+
const cov = AnomalyEngine._covariance(histZVectors, means);
|
|
317
|
+
const invCov = AnomalyEngine._invert(cov);
|
|
318
|
+
|
|
319
|
+
if (!invCov) return 0;
|
|
320
|
+
|
|
321
|
+
let sum = 0;
|
|
322
|
+
for (let i = 0; i < todayZVec.length; i++) {
|
|
323
|
+
for (let j = 0; j < todayZVec.length; j++) {
|
|
324
|
+
sum += todayZVec[i] * invCov[i][j] * todayZVec[j];
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return Math.sqrt(Math.max(0, sum));
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
_percentileScore: (todayVec, historicalFeatures) => {
|
|
331
|
+
let totalDeviation = 0;
|
|
332
|
+
const featureCount = todayVec.length;
|
|
333
|
+
for (let i = 0; i < featureCount; i++) {
|
|
334
|
+
const historicalValues = historicalFeatures.map(f => f.vector[i]);
|
|
335
|
+
const percentile = AdvancedMath.percentileRank(todayVec[i], historicalValues);
|
|
336
|
+
totalDeviation += Math.abs(percentile - 0.5) * 2;
|
|
337
|
+
}
|
|
338
|
+
return totalDeviation / featureCount;
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
_regimeScore: (todayFeatures, historicalFeatures, regime) => {
|
|
342
|
+
const sameRegimeFeatures = historicalFeatures.filter(f => f.metadata.regime === regime);
|
|
343
|
+
const comparisonSet = sameRegimeFeatures.length < 5 ? historicalFeatures : sameRegimeFeatures;
|
|
344
|
+
return AnomalyEngine._percentileScore(todayFeatures.vector, comparisonSet);
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
_covariance: (vectors, means) => {
|
|
348
|
+
const dim = vectors[0].length;
|
|
349
|
+
const n = vectors.length;
|
|
350
|
+
const matrix = Array(dim).fill(0).map(() => Array(dim).fill(0));
|
|
351
|
+
for (const v of vectors) {
|
|
352
|
+
for (let i = 0; i < dim; i++) {
|
|
353
|
+
for (let j = 0; j < dim; j++) {
|
|
354
|
+
matrix[i][j] += (v[i] - means[i]) * (v[j] - means[j]);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Ridge Regularization
|
|
359
|
+
return matrix.map((row, i) => row.map((val, j) => (val / (n - 1)) + (i === j ? 1e-3 : 0)));
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
_invert: (M) => {
|
|
363
|
+
try {
|
|
364
|
+
const n = M.length;
|
|
365
|
+
const A = M.map(row => [...row]);
|
|
366
|
+
const I = M.map((_, i) => M.map((__, j) => (i === j ? 1 : 0)));
|
|
367
|
+
for (let i = 0; i < n; i++) {
|
|
368
|
+
let pivot = A[i][i];
|
|
369
|
+
if (Math.abs(pivot) < 1e-9) pivot = 1e-9;
|
|
370
|
+
for (let j = 0; j < n; j++) { A[i][j] /= pivot; I[i][j] /= pivot; }
|
|
371
|
+
for (let k = 0; k < n; k++) {
|
|
372
|
+
if (k !== i) {
|
|
373
|
+
const f = A[k][i];
|
|
374
|
+
for (let j = 0; j < n; j++) { A[k][j] -= f * A[i][j]; I[k][j] -= f * I[i][j]; }
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return I;
|
|
379
|
+
} catch (e) { return null; }
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// =============================================================================
|
|
384
|
+
// PREDICTIVE ENGINE & INTERPRETER
|
|
385
|
+
// =============================================================================
|
|
386
|
+
const PredictiveEngine = {
|
|
387
|
+
forecastRisk: (todayFeatures, historicalFeatures, anomalyScore) => {
|
|
388
|
+
const momentum = todayFeatures.vector[17];
|
|
389
|
+
const riskAccel = todayFeatures.vector[16];
|
|
390
|
+
|
|
391
|
+
let baseProbability = 1 / (1 + Math.exp(-(anomalyScore - 3)));
|
|
392
|
+
|
|
393
|
+
if (momentum > 1.0) baseProbability = Math.min(0.99, baseProbability * 1.2);
|
|
394
|
+
if (riskAccel > 1.0) baseProbability = Math.min(0.99, baseProbability * 1.15);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
probability7d: baseProbability,
|
|
398
|
+
confidence: historicalFeatures.length >= 25 ? 'high' : 'medium',
|
|
399
|
+
trajectory: momentum > 0 ? 'escalating' : 'stable'
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const SemanticInterpreter = {
|
|
405
|
+
interpret: (scores, features, prediction, regime, featureNames) => {
|
|
406
|
+
// ROBUST LOGIC: Use the Z-Scores passed from AnomalyEngine.
|
|
407
|
+
const zScores = scores.zScores || [];
|
|
408
|
+
|
|
409
|
+
let maxIdx = 0;
|
|
410
|
+
let maxZ = 0;
|
|
411
|
+
|
|
412
|
+
// Find feature with highest ABSOLUTE Z-score (Statistically most significant)
|
|
413
|
+
zScores.forEach((z, i) => {
|
|
414
|
+
if (Math.abs(z) > maxZ) {
|
|
415
|
+
maxZ = Math.abs(z);
|
|
416
|
+
maxIdx = i;
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const primaryDriver = featureNames[maxIdx];
|
|
421
|
+
const driverZ = zScores[maxIdx];
|
|
422
|
+
const rawValue = features.vector[maxIdx];
|
|
423
|
+
|
|
424
|
+
let description = '';
|
|
425
|
+
let severity = 'low';
|
|
426
|
+
|
|
427
|
+
if (scores.overall > 5.0) { severity = 'critical'; description = `🚨 CRITICAL: Severe anomaly in ${regime} regime. `; }
|
|
428
|
+
else if (scores.overall > 4.0) { severity = 'high'; description = `⚠️ HIGH RISK: Significant deviation. `; }
|
|
429
|
+
else if (scores.overall > 3.0) { severity = 'medium'; description = `⚡ MODERATE: Unusual pattern. `; }
|
|
430
|
+
else { description = `ℹ️ NOTICE: Minor shift. `; }
|
|
431
|
+
|
|
432
|
+
const direction = driverZ > 0 ? "increased" : "decreased";
|
|
433
|
+
|
|
434
|
+
description += `Driven by ${primaryDriver}. `;
|
|
435
|
+
description += `Value ${direction} to ${rawValue.toFixed(2)} (${driverZ > 0 ? '+' : ''}${maxZ.toFixed(1)}σ). `;
|
|
436
|
+
|
|
437
|
+
if (prediction.probability7d > 0.7) description += `High escalation risk (${(prediction.probability7d*100).toFixed(0)}%).`;
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
description,
|
|
441
|
+
severity,
|
|
442
|
+
primaryDriver,
|
|
443
|
+
driverValue: rawValue.toFixed(2),
|
|
444
|
+
driverSigma: (driverZ > 0 ? '+' : '-') + maxZ.toFixed(1) + 'σ',
|
|
445
|
+
regime
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// =============================================================================
|
|
451
|
+
// MAIN COMPUTATION
|
|
452
|
+
// =============================================================================
|
|
3
453
|
class BehavioralAnomaly extends Computation {
|
|
4
454
|
|
|
5
455
|
static getConfig() {
|
|
@@ -10,271 +460,153 @@ class BehavioralAnomaly extends Computation {
|
|
|
10
460
|
isHistorical: true,
|
|
11
461
|
|
|
12
462
|
requires: {
|
|
463
|
+
// COST CONTROL: 30-day limit
|
|
13
464
|
'portfolio_snapshots': {
|
|
14
|
-
lookback:
|
|
465
|
+
lookback: 30,
|
|
15
466
|
mandatory: true,
|
|
16
467
|
fields: ['user_id', 'portfolio_data', 'date']
|
|
17
468
|
},
|
|
18
469
|
'pi_rankings': {
|
|
19
|
-
lookback:
|
|
20
|
-
mandatory: true,
|
|
470
|
+
lookback: 30,
|
|
471
|
+
mandatory: true,
|
|
21
472
|
fields: ['pi_id', 'rankings_data', 'date']
|
|
22
473
|
},
|
|
23
474
|
'trade_history_snapshots': {
|
|
24
|
-
lookback:
|
|
25
|
-
mandatory: false,
|
|
475
|
+
lookback: 0,
|
|
476
|
+
mandatory: false,
|
|
26
477
|
fields: ['user_id', 'history_data', 'date']
|
|
27
|
-
}
|
|
478
|
+
},
|
|
479
|
+
'ticker_mappings': { mandatory: false, fields: ['instrument_id', 'ticker'] },
|
|
480
|
+
'sector_mappings': { mandatory: false, fields: ['symbol', 'sector'] },
|
|
481
|
+
'pi_master_list': { mandatory: false, fields: ['cid', 'username'] }
|
|
28
482
|
},
|
|
29
483
|
|
|
30
484
|
storage: {
|
|
31
485
|
bigquery: true,
|
|
32
|
-
firestore: {
|
|
33
|
-
enabled: true,
|
|
34
|
-
path: 'alerts/{date}/BehavioralAnomaly/{entityId}',
|
|
35
|
-
merge: true
|
|
486
|
+
firestore: {
|
|
487
|
+
enabled: true,
|
|
488
|
+
path: 'alerts/{date}/BehavioralAnomaly/{entityId}',
|
|
489
|
+
merge: true
|
|
36
490
|
}
|
|
37
491
|
},
|
|
38
|
-
|
|
39
|
-
// LEGACY METADATA PRESERVED
|
|
492
|
+
|
|
40
493
|
userType: 'POPULAR_INVESTOR',
|
|
41
494
|
alert: {
|
|
42
|
-
id: '
|
|
43
|
-
frontendName: 'Behavioral
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
severity: 'high',
|
|
47
|
-
configKey: 'behavioralAnomaly',
|
|
48
|
-
isDynamic: true,
|
|
49
|
-
thresholds: [
|
|
50
|
-
{
|
|
51
|
-
key: 'anomalyScoreThreshold',
|
|
52
|
-
type: 'number',
|
|
53
|
-
label: 'Anomaly Sensitivity',
|
|
54
|
-
description: 'Alert when anomaly score exceeds this threshold (higher = less sensitive)',
|
|
55
|
-
default: 3.5,
|
|
56
|
-
min: 2.0,
|
|
57
|
-
max: 5.0,
|
|
58
|
-
step: 0.5,
|
|
59
|
-
unit: 'σ (standard deviations)'
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
conditions: [
|
|
63
|
-
{
|
|
64
|
-
key: 'watchedDrivers',
|
|
65
|
-
type: 'array',
|
|
66
|
-
label: 'Watched Behavior Drivers',
|
|
67
|
-
description: 'Only alert for these specific behavioral factors (empty = all)',
|
|
68
|
-
default: [],
|
|
69
|
-
options: [
|
|
70
|
-
{ value: 'Concentration (HHI)', label: 'Portfolio Concentration' },
|
|
71
|
-
{ value: 'Martingale Behavior', label: 'Martingale Behavior' },
|
|
72
|
-
{ value: 'Capacity Strain', label: 'Capacity Strain' },
|
|
73
|
-
{ value: 'Risk Score', label: 'Risk Score Changes' }
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
],
|
|
77
|
-
resultKeys: ['triggered', 'anomalyScore', 'primaryDriver', 'driverSignificance', 'baselineDays']
|
|
495
|
+
id: 'behavioral_anomaly_v4',
|
|
496
|
+
frontendName: 'Behavioral Risk Intelligence',
|
|
497
|
+
severity: 'high',
|
|
498
|
+
isDynamic: true
|
|
78
499
|
}
|
|
79
500
|
};
|
|
80
501
|
}
|
|
81
502
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
mean(data) {
|
|
85
|
-
const sum = data.reduce((a, b) => a + b, 0);
|
|
86
|
-
return sum / data.length;
|
|
87
|
-
},
|
|
88
|
-
covarianceMatrix(vectors) {
|
|
89
|
-
const n = vectors.length;
|
|
90
|
-
const dim = vectors[0].length;
|
|
91
|
-
const means = Array(dim).fill(0).map((_, i) => this.mean(vectors.map(v => v[i])));
|
|
92
|
-
const matrix = Array(dim).fill(0).map(() => Array(dim).fill(0));
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < dim; i++) {
|
|
95
|
-
for (let j = 0; j < dim; j++) {
|
|
96
|
-
let sum = 0;
|
|
97
|
-
for (let k = 0; k < n; k++) {
|
|
98
|
-
sum += (vectors[k][i] - means[i]) * (vectors[k][j] - means[j]);
|
|
99
|
-
}
|
|
100
|
-
matrix[i][j] = sum / (n - 1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return { matrix, means };
|
|
104
|
-
},
|
|
105
|
-
invertMatrix(M) {
|
|
106
|
-
// Simple 4x4 inversion or generic Gaussian elimination
|
|
107
|
-
// For brevity, assuming 4x4 or small dimensions suitable for JS
|
|
108
|
-
// (Implementation omitted for brevity, essentially standard Gaussian elimination)
|
|
109
|
-
// Simplified check for diagonal/stability
|
|
110
|
-
if(M.length === 0) return null;
|
|
111
|
-
// Placeholder for full inversion logic - in production use a library
|
|
112
|
-
// For this migration, we check for zero variance on diagonal
|
|
113
|
-
for(let i=0; i<M.length; i++) if(M[i][i] === 0) return null;
|
|
114
|
-
return M; // Mock return: In real usage, implement numeric.js or similar
|
|
115
|
-
},
|
|
116
|
-
mahalanobisDistance(vector, means, inverseCov) {
|
|
117
|
-
// D^2 = (x - u)^T * S^-1 * (x - u)
|
|
118
|
-
// Simplified Euclidean for fallback if inversion fails, or full calc
|
|
119
|
-
// For this output, we assume full calculation logic exists here.
|
|
120
|
-
let diff = vector.map((v, i) => v - means[i]);
|
|
121
|
-
let sum = 0;
|
|
122
|
-
// Mock calculation
|
|
123
|
-
return Math.sqrt(diff.reduce((acc, val) => acc + (val * val), 0));
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
calculateHHI(portfolioData) {
|
|
128
|
-
const positions = this.rules.portfolio.extractPositions(portfolioData);
|
|
129
|
-
if (!positions || positions.length === 0) return 0;
|
|
130
|
-
|
|
131
|
-
let sumSquares = 0;
|
|
132
|
-
const totalValue = this.rules.portfolio.calculateTotalValue(positions);
|
|
133
|
-
|
|
134
|
-
positions.forEach(p => {
|
|
135
|
-
// Calculate actual % weight based on Value
|
|
136
|
-
const val = this.rules.portfolio.getValue(p);
|
|
137
|
-
const weight = totalValue > 0 ? (val / totalValue) * 100 : 0;
|
|
138
|
-
sumSquares += (weight * weight);
|
|
139
|
-
});
|
|
140
|
-
return sumSquares;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
calculateMartingaleScore(historyData) {
|
|
144
|
-
const trades = this.rules.trades.extractTrades(historyData);
|
|
145
|
-
if (!trades || trades.length < 2) return 0;
|
|
146
|
-
|
|
147
|
-
// Sort by close date
|
|
148
|
-
const sorted = [...trades].sort((a, b) =>
|
|
149
|
-
(this.rules.trades.getCloseDate(a) || 0) - (this.rules.trades.getCloseDate(b) || 0)
|
|
150
|
-
);
|
|
151
|
-
const recent = sorted.slice(-30);
|
|
152
|
-
|
|
153
|
-
let lossEvents = 0;
|
|
154
|
-
let martingaleResponses = 0;
|
|
155
|
-
|
|
156
|
-
for (let i = 0; i < recent.length - 1; i++) {
|
|
157
|
-
const current = recent[i];
|
|
158
|
-
const next = recent[i+1];
|
|
503
|
+
async process(context) {
|
|
504
|
+
const { data, entityId, date, rules, references } = context;
|
|
159
505
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if (nextLev > currentLev) martingaleResponses++;
|
|
165
|
-
}
|
|
506
|
+
// 1. Map Building
|
|
507
|
+
const tickerMap = new Map();
|
|
508
|
+
if (references.ticker_mappings) {
|
|
509
|
+
Object.values(references.ticker_mappings).forEach(r => tickerMap.set(String(r.instrument_id), r.ticker));
|
|
166
510
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
getDailyVector(portfolioData, rankingsData, historyData) {
|
|
171
|
-
const hhi = this.calculateHHI(portfolioData);
|
|
172
|
-
const mScore = this.calculateMartingaleScore(historyData);
|
|
173
|
-
|
|
174
|
-
let strain = 0;
|
|
175
|
-
let risk = 1;
|
|
176
|
-
|
|
177
|
-
if (rankingsData) {
|
|
178
|
-
const copiers = this.rules.rankings.getCopiers(rankingsData);
|
|
179
|
-
const aum = this.rules.rankings.getAUM(rankingsData);
|
|
180
|
-
strain = aum > 0 ? (copiers / (aum / 1000)) : 0;
|
|
181
|
-
risk = this.rules.rankings.getRiskScore(rankingsData);
|
|
511
|
+
const sectorMap = new Map();
|
|
512
|
+
if (references.sector_mappings) {
|
|
513
|
+
Object.values(references.sector_mappings).forEach(r => sectorMap.set(r.symbol, r.sector));
|
|
182
514
|
}
|
|
515
|
+
const maps = { tickerMap, sectorMap };
|
|
516
|
+
|
|
517
|
+
// 2. Data Alignment
|
|
518
|
+
const toDateStr = (d) => d && d.value ? d.value : (d instanceof Date ? d.toISOString().slice(0, 10) : String(d));
|
|
519
|
+
const getRows = (dataset) => {
|
|
520
|
+
if (!dataset) return [];
|
|
521
|
+
if (dataset[entityId]) return Array.isArray(dataset[entityId]) ? dataset[entityId] : [dataset[entityId]];
|
|
522
|
+
if (Array.isArray(dataset)) return dataset.filter(r => String(r.user_id || r.pi_id || r.cid) === String(entityId));
|
|
523
|
+
return [];
|
|
524
|
+
};
|
|
183
525
|
|
|
184
|
-
|
|
185
|
-
|
|
526
|
+
const portfolios = getRows(data.portfolio_snapshots);
|
|
527
|
+
const rankings = getRows(data.pi_rankings);
|
|
528
|
+
const historyRows = getRows(data.trade_history_snapshots);
|
|
529
|
+
const currentHistoryBlob = historyRows.length > 0 ? (historyRows[0].history_data?.PublicHistoryPositions || []) : [];
|
|
530
|
+
const identityRows = getRows(data.pi_master_list);
|
|
531
|
+
const username = identityRows.length > 0 ? identityRows[0].username : "Unknown";
|
|
532
|
+
|
|
533
|
+
const dailyData = new Map();
|
|
534
|
+
portfolios.forEach(p => {
|
|
535
|
+
const d = toDateStr(p.date);
|
|
536
|
+
if (!dailyData.has(d)) dailyData.set(d, {});
|
|
537
|
+
dailyData.get(d).portfolio = p.portfolio_data;
|
|
538
|
+
dailyData.get(d).date = d;
|
|
539
|
+
});
|
|
540
|
+
rankings.forEach(r => {
|
|
541
|
+
const d = toDateStr(r.date);
|
|
542
|
+
if (dailyData.has(d)) dailyData.get(d).rankings = r.rankings_data;
|
|
543
|
+
});
|
|
186
544
|
|
|
187
|
-
|
|
188
|
-
const { data, entityId, rules } = context;
|
|
189
|
-
this.rules = rules; // Attach rules for helper access
|
|
545
|
+
if (!dailyData.has(date)) return;
|
|
190
546
|
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
const
|
|
547
|
+
// 3. Historical Extraction (30 days)
|
|
548
|
+
const historicalFeatures = [];
|
|
549
|
+
const lookbackDate = new Date(date);
|
|
550
|
+
lookbackDate.setDate(lookbackDate.getDate() - 30);
|
|
194
551
|
|
|
195
|
-
|
|
196
|
-
|
|
552
|
+
let prevDay = null;
|
|
553
|
+
let prevPrevDay = null;
|
|
197
554
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Build Baseline
|
|
202
|
-
for (const portRow of portfolios) {
|
|
203
|
-
const date = portRow.date;
|
|
555
|
+
for (let d = new Date(lookbackDate); d < new Date(date); d.setDate(d.getDate() + 1)) {
|
|
556
|
+
const dStr = d.toISOString().slice(0, 10);
|
|
557
|
+
const dayData = dailyData.get(dStr);
|
|
204
558
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const rankData = rules.rankings.extractRankingsData(rankRow);
|
|
211
|
-
|
|
212
|
-
// Note: History logic simplified; V1 filtered full history,
|
|
213
|
-
// here we rely on the daily snapshots which usually contain accumulated history
|
|
214
|
-
const histData = histRow;
|
|
215
|
-
|
|
216
|
-
if (portData) {
|
|
217
|
-
const vec = this.getDailyVector(portData, rankData, histData);
|
|
218
|
-
trainingVectors.push(vec);
|
|
559
|
+
if (dayData && dayData.portfolio) {
|
|
560
|
+
const features = FeatureExtractor.extract(dayData, prevDay, prevPrevDay, currentHistoryBlob, dayData.rankings, rules, maps);
|
|
561
|
+
historicalFeatures.push(features);
|
|
562
|
+
prevPrevDay = prevDay;
|
|
563
|
+
prevDay = dayData;
|
|
219
564
|
}
|
|
220
565
|
}
|
|
221
566
|
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
|
|
567
|
+
if (historicalFeatures.length < 15) {
|
|
568
|
+
this.setResult(entityId, { triggered: false, status: 'INSUFFICIENT_HISTORY' });
|
|
569
|
+
return;
|
|
225
570
|
}
|
|
226
571
|
|
|
227
|
-
//
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
// If variance is 0, inversion fails.
|
|
231
|
-
const inverseCov = BehavioralAnomaly.LinearAlgebra.invertMatrix(stats.matrix);
|
|
232
|
-
|
|
233
|
-
if (!inverseCov) {
|
|
234
|
-
this.setResult(entityId, { status: 'STABLE_STATE', info: 'Variance too low' });
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
572
|
+
// 4. Today's Features
|
|
573
|
+
const todayData = dailyData.get(date);
|
|
574
|
+
const todayFeatures = FeatureExtractor.extract(todayData, prevDay, prevPrevDay, currentHistoryBlob, todayData.rankings, rules, maps);
|
|
237
575
|
|
|
238
|
-
//
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
rules.rankings.extractRankingsData(todayRankRow),
|
|
246
|
-
todayHistRow
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const distance = BehavioralAnomaly.LinearAlgebra.mahalanobisDistance(todayVector, stats.means, inverseCov);
|
|
250
|
-
const IS_ANOMALY = distance > 3.5;
|
|
251
|
-
|
|
252
|
-
const result = {
|
|
253
|
-
triggered: IS_ANOMALY,
|
|
254
|
-
anomalyScore: parseFloat(distance.toFixed(2)),
|
|
255
|
-
baselineDays: trainingVectors.length
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
if (IS_ANOMALY) {
|
|
259
|
-
const featureNames = ['Concentration (HHI)', 'Martingale Behavior', 'Capacity Strain', 'Risk Score'];
|
|
260
|
-
let maxZ = 0;
|
|
261
|
-
let primaryDriver = 'Unknown';
|
|
262
|
-
|
|
263
|
-
todayVector.forEach((val, i) => {
|
|
264
|
-
const stdDev = Math.sqrt(stats.matrix[i][i]);
|
|
265
|
-
const z = stdDev > 0 ? Math.abs((val - stats.means[i]) / stdDev) : 0;
|
|
266
|
-
if (z > maxZ) {
|
|
267
|
-
maxZ = z;
|
|
268
|
-
primaryDriver = featureNames[i];
|
|
269
|
-
}
|
|
270
|
-
});
|
|
576
|
+
// 5. Detection & Scoring
|
|
577
|
+
const recentVectors = historicalFeatures.slice(-5).map(f => f.vector);
|
|
578
|
+
const regime = AdvancedMath.detectRegime(recentVectors);
|
|
579
|
+
historicalFeatures.forEach(f => f.metadata.regime = regime);
|
|
580
|
+
|
|
581
|
+
const scores = AnomalyEngine.score(todayFeatures, historicalFeatures, regime);
|
|
582
|
+
const prediction = PredictiveEngine.forecastRisk(todayFeatures, historicalFeatures, scores.overall);
|
|
271
583
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
584
|
+
const featureNames = [
|
|
585
|
+
'Sector HHI', 'Martingale', 'Avg Leverage', 'Risk Score', 'Complexity', 'Exposure',
|
|
586
|
+
'Entropy', 'Drawdown', 'Win Rate Dev', 'Skewness', 'Stress Ratio', 'Credit Imbal',
|
|
587
|
+
'High Lev Freq', 'Copier Mom', 'AUM Tier', 'Exp Velocity', 'Risk Accel', 'Behav Mom'
|
|
588
|
+
];
|
|
276
589
|
|
|
277
|
-
|
|
590
|
+
const interpretation = SemanticInterpreter.interpret(scores, todayFeatures, prediction, regime, featureNames);
|
|
591
|
+
|
|
592
|
+
const THRESHOLD = 3.5;
|
|
593
|
+
const triggered = scores.overall > THRESHOLD || prediction.probability7d > 0.75;
|
|
594
|
+
|
|
595
|
+
this.setResult(entityId, {
|
|
596
|
+
triggered,
|
|
597
|
+
score: Number(scores.overall.toFixed(2)),
|
|
598
|
+
severity: interpretation.severity,
|
|
599
|
+
regime,
|
|
600
|
+
description: interpretation.description,
|
|
601
|
+
username,
|
|
602
|
+
driver: interpretation.primaryDriver,
|
|
603
|
+
driverValue: interpretation.driverValue,
|
|
604
|
+
prediction: {
|
|
605
|
+
probability: (prediction.probability7d * 100).toFixed(1) + '%',
|
|
606
|
+
trajectory: prediction.trajectory
|
|
607
|
+
},
|
|
608
|
+
features: todayFeatures.vector.map(v => Number(v.toFixed(3)))
|
|
609
|
+
});
|
|
278
610
|
}
|
|
279
611
|
}
|
|
280
612
|
|