aiden-shared-calculations-unified 1.0.151 → 1.0.152
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.
|
@@ -15,8 +15,7 @@ class BehavioralAnomaly {
|
|
|
15
15
|
type: 'standard',
|
|
16
16
|
isHistorical: true,
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
// CachedDataLoader handles this efficiently via reference pointers.
|
|
18
|
+
// Request 60 days of history for baseline
|
|
20
19
|
rootDataSeries: {
|
|
21
20
|
portfolio: 60,
|
|
22
21
|
rankings: 60
|
|
@@ -57,7 +56,6 @@ class BehavioralAnomaly {
|
|
|
57
56
|
|
|
58
57
|
let sumSquares = 0;
|
|
59
58
|
let totalValue = 0;
|
|
60
|
-
|
|
61
59
|
positions.forEach(p => totalValue += DataExtractor.getPositionValue(p));
|
|
62
60
|
|
|
63
61
|
if (totalValue === 0) return 0;
|
|
@@ -73,7 +71,6 @@ class BehavioralAnomaly {
|
|
|
73
71
|
calculateMartingaleScore(tradeHistory) {
|
|
74
72
|
if (!tradeHistory || tradeHistory.length < 2) return 0;
|
|
75
73
|
|
|
76
|
-
// Trades are already filtered by date in the main loop logic
|
|
77
74
|
const sorted = [...tradeHistory].sort((a, b) => new Date(a.CloseDateTime) - new Date(b.CloseDateTime));
|
|
78
75
|
const recentTrades = sorted.slice(-30);
|
|
79
76
|
|
|
@@ -98,7 +95,6 @@ class BehavioralAnomaly {
|
|
|
98
95
|
|
|
99
96
|
getDailyVector(portfolio, rankings, history, math) {
|
|
100
97
|
const { RankingsExtractor } = math;
|
|
101
|
-
|
|
102
98
|
const hhi = this.calculateHHI(portfolio, math);
|
|
103
99
|
const mScore = this.calculateMartingaleScore(history);
|
|
104
100
|
|
|
@@ -115,7 +111,7 @@ class BehavioralAnomaly {
|
|
|
115
111
|
return [hhi, mScore, strain, risk];
|
|
116
112
|
}
|
|
117
113
|
|
|
118
|
-
process(context) {
|
|
114
|
+
async process(context) {
|
|
119
115
|
const { math, globalData } = context;
|
|
120
116
|
const { LinearAlgebra } = math;
|
|
121
117
|
const userId = context.user.id;
|
|
@@ -123,8 +119,6 @@ class BehavioralAnomaly {
|
|
|
123
119
|
const seriesData = globalData?.series?.root || {};
|
|
124
120
|
const portfolioSeries = seriesData.portfolio || {};
|
|
125
121
|
const rankingsSeries = seriesData.rankings || {};
|
|
126
|
-
|
|
127
|
-
// Full Trade History (Contains ALL time, so we must filter it)
|
|
128
122
|
const fullHistory = context.user.history || [];
|
|
129
123
|
|
|
130
124
|
const availableDates = Object.keys(portfolioSeries).sort();
|
|
@@ -135,56 +129,37 @@ class BehavioralAnomaly {
|
|
|
135
129
|
const datePortfolio = portfolioSeries[date][userId];
|
|
136
130
|
if (!datePortfolio) continue;
|
|
137
131
|
|
|
138
|
-
// CRITICAL FIX: Filter History to avoid "Time Travel"
|
|
139
|
-
// We only look at trades that had closed ON or BEFORE this specific loop date.
|
|
140
132
|
const loopDateObj = new Date(date);
|
|
141
133
|
const historyAsOfDate = fullHistory.filter(t => new Date(t.CloseDateTime) <= loopDateObj);
|
|
142
|
-
|
|
143
|
-
// Robust Lookup: Find ranking for this date (or closest recent date)
|
|
144
134
|
const rankingsSnapshot = this.findClosestData(date, rankingsSeries);
|
|
145
135
|
const userRanking = rankingsSnapshot ? rankingsSnapshot.find(r => String(r.CustomerId) === String(userId)) : null;
|
|
146
136
|
|
|
147
|
-
const vec = this.getDailyVector(
|
|
148
|
-
datePortfolio,
|
|
149
|
-
userRanking,
|
|
150
|
-
historyAsOfDate, // Pass filtered history
|
|
151
|
-
math
|
|
152
|
-
);
|
|
137
|
+
const vec = this.getDailyVector(datePortfolio, userRanking, historyAsOfDate, math);
|
|
153
138
|
trainingVectors.push(vec);
|
|
154
139
|
}
|
|
155
140
|
|
|
156
141
|
// 2. Minimum Viable Baseline
|
|
142
|
+
const localResults = {}; // Local container to satisfy Guard logic
|
|
157
143
|
const MIN_DATAPOINTS = 15;
|
|
144
|
+
|
|
158
145
|
if (trainingVectors.length < MIN_DATAPOINTS) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
163
|
-
return;
|
|
146
|
+
localResults[userId] = { status: 'INSUFFICIENT_BASELINE', dataPoints: trainingVectors.length };
|
|
147
|
+
this.results = localResults;
|
|
148
|
+
return this.results;
|
|
164
149
|
}
|
|
165
150
|
|
|
166
|
-
// 3. Compute Statistics
|
|
151
|
+
// 3. Compute Statistics
|
|
167
152
|
const stats = LinearAlgebra.covarianceMatrix(trainingVectors);
|
|
168
153
|
const inverseCov = LinearAlgebra.invertMatrix(stats.matrix);
|
|
169
154
|
|
|
170
|
-
// Singular Matrix Check (If behavior is perfectly identical every day)
|
|
171
155
|
if (!inverseCov) {
|
|
172
|
-
|
|
173
|
-
|
|
156
|
+
localResults[userId] = { status: 'STABLE_STATE', info: 'Variance too low' };
|
|
157
|
+
this.results = localResults;
|
|
158
|
+
return this.results;
|
|
174
159
|
}
|
|
175
160
|
|
|
176
161
|
// 4. Compute Today's Vector
|
|
177
|
-
|
|
178
|
-
const todayRanking = context.user.rankEntry ||
|
|
179
|
-
(this.findClosestData(context.date.today, rankingsSeries) || [])
|
|
180
|
-
.find(r => String(r.CustomerId) === String(userId));
|
|
181
|
-
|
|
182
|
-
const todayVector = this.getDailyVector(
|
|
183
|
-
context.user.portfolio.today,
|
|
184
|
-
todayRanking,
|
|
185
|
-
fullHistory, // Today includes all history
|
|
186
|
-
math
|
|
187
|
-
);
|
|
162
|
+
const todayVector = this.getDailyVector(context.user.portfolio.today, context.user.rankEntry, fullHistory, math);
|
|
188
163
|
|
|
189
164
|
// 5. Calculate Distance
|
|
190
165
|
const distance = LinearAlgebra.mahalanobisDistance(todayVector, stats.means, inverseCov);
|
|
@@ -204,31 +179,31 @@ class BehavioralAnomaly {
|
|
|
204
179
|
}
|
|
205
180
|
});
|
|
206
181
|
|
|
207
|
-
|
|
182
|
+
localResults[userId] = {
|
|
208
183
|
triggered: true,
|
|
209
184
|
anomalyScore: parseFloat(distance.toFixed(2)),
|
|
210
185
|
primaryDriver: primaryDriver,
|
|
211
186
|
driverSignificance: `${maxZ.toFixed(1)}σ`,
|
|
212
187
|
baselineDays: trainingVectors.length,
|
|
213
|
-
vectors: {
|
|
214
|
-
current: todayVector,
|
|
215
|
-
baselineMeans: stats.means
|
|
216
|
-
}
|
|
188
|
+
vectors: { current: todayVector, baselineMeans: stats.means }
|
|
217
189
|
};
|
|
218
190
|
|
|
219
191
|
// Add CID to index for downstream alert handlers
|
|
220
|
-
if (!
|
|
221
|
-
|
|
192
|
+
if (!localResults.cids) localResults.cids = [];
|
|
193
|
+
localResults.cids.push(userId);
|
|
222
194
|
} else {
|
|
223
|
-
|
|
224
|
-
triggered: false,
|
|
225
|
-
anomalyScore: parseFloat(distance.toFixed(2))
|
|
226
|
-
};
|
|
227
|
-
|
|
195
|
+
localResults[userId] = { triggered: false, anomalyScore: parseFloat(distance.toFixed(2)) };
|
|
228
196
|
}
|
|
197
|
+
|
|
198
|
+
// --- FINAL ASSIGNMENT ---
|
|
199
|
+
// Satisfies the Guard AND hands data to getResult()
|
|
200
|
+
this.results = localResults;
|
|
201
|
+
return this.results;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async getResult() {
|
|
205
|
+
return this.results;
|
|
229
206
|
}
|
|
230
|
-
async getResult() { return this.results; }
|
|
231
|
-
|
|
232
207
|
}
|
|
233
208
|
|
|
234
209
|
module.exports = BehavioralAnomaly;
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* @fileoverview Asset Recommendation Engine.
|
|
3
3
|
* Suggests assets based on Similar Users and Sector Gaps.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
class PiAssetRecommender {
|
|
7
|
-
constructor() {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.results = {};
|
|
8
|
+
}
|
|
8
9
|
|
|
9
10
|
static getMetadata() {
|
|
10
11
|
return {
|
|
@@ -15,6 +16,8 @@ class PiAssetRecommender {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
static getDependencies() {
|
|
19
|
+
// Expects these to be available in context.computed
|
|
20
|
+
// (Ensure these are Meta computations or small enough to be passed in memory)
|
|
18
21
|
return ['PiSectorAnalysis', 'PiSimilarityMatrix', 'PiSimilarityVector'];
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -25,15 +28,16 @@ class PiAssetRecommender {
|
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
process(context) {
|
|
31
|
+
async process(context) {
|
|
29
32
|
// 1. Access computed results from previous steps
|
|
30
33
|
const similarityMatrix = context.computed['PiSimilarityMatrix'];
|
|
31
34
|
const vectors = context.computed['PiSimilarityVector'];
|
|
32
|
-
const
|
|
35
|
+
const mappings = context.mappings || {};
|
|
33
36
|
|
|
34
37
|
// Defensive Check: If inputs missing, return empty object
|
|
35
38
|
if (!similarityMatrix || !vectors) {
|
|
36
|
-
|
|
39
|
+
this.results = {};
|
|
40
|
+
return this.results;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const recommendations = {};
|
|
@@ -75,10 +79,12 @@ class PiAssetRecommender {
|
|
|
75
79
|
recommendations[userId] = sorted;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
// --- CRITICAL FIX ---
|
|
83
|
+
// 1. Save to Class Property (Required by ResultCommitter via getResult)
|
|
84
|
+
this.results = recommendations;
|
|
85
|
+
|
|
86
|
+
// 2. Return to MetaExecutor (Required for wrapping)
|
|
87
|
+
return this.results;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
async getResult() {
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @fileoverview Similarity Matrix Meta-Computation.
|
|
3
3
|
* Calculates cosine similarity between all PI vectors to find nearest neighbors.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
class PiSimilarityMatrix {
|
|
7
6
|
constructor() { this.results = {}; }
|
|
8
7
|
|
|
@@ -24,12 +23,14 @@ class PiSimilarityMatrix {
|
|
|
24
23
|
};
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
process(context) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
async process(context) {
|
|
27
|
+
const vectorMap = context.computed['PiSimilarityVector'];
|
|
28
|
+
|
|
29
|
+
// [FIX] Ensure we have enough peers to actually compare
|
|
30
|
+
if (!vectorMap || Object.keys(vectorMap).length < 2) {
|
|
31
|
+
this.results = {}; // Explicitly set empty
|
|
32
|
+
return this.results;
|
|
33
|
+
}
|
|
33
34
|
|
|
34
35
|
const userIds = Object.keys(vectorMap);
|
|
35
36
|
const matrix = {};
|
|
@@ -87,8 +88,12 @@ class PiSimilarityMatrix {
|
|
|
87
88
|
matrix[u1] = matches.sort((a,b) => b.score - a.score).slice(0, 5);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
//
|
|
91
|
-
|
|
91
|
+
// --- CRITICAL FIX ---
|
|
92
|
+
// 1. Assign to class property (for getResult)
|
|
93
|
+
this.results = matrix;
|
|
94
|
+
|
|
95
|
+
// 2. Return to MetaExecutor (for wrapping)
|
|
96
|
+
return this.results;
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
async getResult() {
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
class PiTotalAum {
|
|
7
|
-
constructor() {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.results = {}; // Container for getResult()
|
|
9
|
+
}
|
|
8
10
|
|
|
9
11
|
static getMetadata() {
|
|
10
12
|
return {
|
|
@@ -14,32 +16,48 @@ class PiTotalAum {
|
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
static getDependencies() {
|
|
19
|
+
static getDependencies() {
|
|
20
|
+
return ['PiAumOverTime'];
|
|
21
|
+
}
|
|
18
22
|
|
|
19
23
|
static getSchema() {
|
|
20
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
type: 'number',
|
|
26
|
+
description: 'Total AUM of all tracked PIs'
|
|
27
|
+
};
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
process(context) {
|
|
30
|
+
async process(context) {
|
|
31
|
+
// 1. Get the map of CID -> AUM from the dependency
|
|
24
32
|
const aumMap = context.computed['PiAumOverTime'];
|
|
25
33
|
|
|
26
|
-
// [FIX] Return 0 if data missing
|
|
27
34
|
if (!aumMap) {
|
|
28
|
-
|
|
35
|
+
this.results = 0;
|
|
36
|
+
return this.results;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
let total = 0;
|
|
32
40
|
for (const val of Object.values(aumMap)) {
|
|
33
|
-
//
|
|
34
|
-
if (typeof val === 'number')
|
|
41
|
+
// Ensure we are adding numbers
|
|
42
|
+
if (typeof val === 'number') {
|
|
43
|
+
total += val;
|
|
44
|
+
}
|
|
35
45
|
}
|
|
36
46
|
|
|
47
|
+
// 2. Format result
|
|
37
48
|
const result = Number(total.toFixed(2));
|
|
38
49
|
|
|
39
|
-
//
|
|
40
|
-
|
|
50
|
+
// --- CRITICAL FIX ---
|
|
51
|
+
// A. Set the property for the Committer (ResultCommitter.js line 72)
|
|
52
|
+
this.results = result;
|
|
53
|
+
|
|
54
|
+
// B. Return for the Executor (MetaExecutor.js line 176)
|
|
55
|
+
return this.results;
|
|
41
56
|
}
|
|
42
57
|
|
|
58
|
+
/**
|
|
59
|
+
* REQUIRED: Interface for the storage layer.
|
|
60
|
+
*/
|
|
43
61
|
async getResult() {
|
|
44
62
|
return this.results;
|
|
45
63
|
}
|