aiden-shared-calculations-unified 1.0.151 → 1.0.153

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
- // RESTORED: Request full 60-day history for both Portfolio and Rankings.
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
- this.results[userId] = {
160
- status: 'INSUFFICIENT_BASELINE',
161
- dataPoints: trainingVectors.length
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 (Covariance Matrix)
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
- this.results[userId] = { status: 'STABLE_STATE', info: 'Variance too low' };
173
- return;
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
- // We use the explicit 'today' injection, or fallback to the series
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
- this.results[userId] = {
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 (!this.results.cids) this.results.cids = [];
221
- this.results.cids.push(userId);
192
+ if (!localResults.cids) localResults.cids = [];
193
+ localResults.cids.push(userId);
222
194
  } else {
223
- this.results[userId] = {
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;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * @fileoverview Aggregates PI Asset AUM over the last 30 days.
2
+ * @fileoverview Aggregates the $ AUM invested in every asset across all Popular Investors.
3
+ * Reads the results from PIDailyAssetAUM.
3
4
  */
4
5
  class GlobalAumPerAsset30D {
5
6
  static getMetadata() {
@@ -7,45 +8,42 @@ class GlobalAumPerAsset30D {
7
8
  type: 'meta',
8
9
  category: 'analytics',
9
10
  rootDataDependencies: [],
10
- // No dependencySeries needed! We just need Today's results via getDependencies.
11
11
  schedule: { type: 'DAILY' }
12
12
  };
13
13
  }
14
14
 
15
15
  static getDependencies() {
16
- // Force running AFTER the Standard comp
17
- // CRITICAL: Must match the File Name of the standard computation
18
16
  return ['PIDailyAssetAUM'];
19
17
  }
20
18
 
21
19
  async process(context) {
22
- // Access the results injected by getDependencies()
23
- // CRITICAL: Must match the File Name
20
+ // Access the map of results: { UserID: { Ticker: $Amount } }
24
21
  const allUserResults = context.computed['PIDailyAssetAUM'];
25
22
 
26
- const globalAverages = {};
27
- let userCount = 0;
23
+ const globalTotals = {};
24
+ let piCount = 0;
28
25
 
29
26
  if (allUserResults) {
30
- Object.values(allUserResults).forEach(userOutput => {
31
- // userOutput is: { dailyMap, averageMap }
32
- const avgMap = userOutput.averageMap;
33
- if (!avgMap) return;
34
-
35
- userCount++;
36
- Object.entries(avgMap).forEach(([ticker, avgVal]) => {
37
- if (!globalAverages[ticker]) globalAverages[ticker] = 0;
38
- globalAverages[ticker] += avgVal;
27
+ Object.values(allUserResults).forEach(userAllocationMap => {
28
+ if (!userAllocationMap) return;
29
+
30
+ piCount++;
31
+
32
+ // userAllocationMap is { AAPL: 5000, TSLA: 2000, ... }
33
+ Object.entries(userAllocationMap).forEach(([ticker, usdValue]) => {
34
+ if (!globalTotals[ticker]) globalTotals[ticker] = 0;
35
+ globalTotals[ticker] += usdValue;
39
36
  });
40
37
  });
41
38
  }
42
39
 
43
- // Result: The Sum of everyone's 30-Day Average Allocations
44
- this.results = globalAverages;
40
+ // Result: { AAPL: 5000000, TSLA: ... }
41
+ this.results = globalTotals;
45
42
 
46
- // CRITICAL FIX: MetaExecutor requires the data to be returned
43
+ // Return for potential debugging or further chaining
47
44
  return this.results;
48
45
  }
46
+
49
47
  async getResult() { return this.results; }
50
48
  }
51
49
 
@@ -1,25 +1,23 @@
1
1
  /**
2
- * @fileoverview Calculates the $ AUM allocated to each asset for a single PI using a Rolling 30-Day Window.
2
+ * @fileoverview Calculates the $ AUM allocated to each asset for a single PI.
3
+ * Includes a lookback mechanism: If AUM is missing today, it checks the last 30 days of rankings.
3
4
  */
4
5
  class PIDailyAssetAUM {
5
6
  static getMetadata() {
6
7
  return {
7
8
  type: 'standard',
8
9
  category: 'analytics',
9
- userType: 'POPULAR_INVESTOR',
10
+ userType: 'POPULAR_INVESTOR', // Ensures we only run for PIs
10
11
 
11
12
  // 1. Core Data
12
13
  rootDataDependencies: ['portfolio', 'rankings'],
13
- mandatoryRoots: ['portfolio', 'rankings'],
14
+ mandatoryRoots: ['portfolio'], // Rankings are mandatory but handled via lookback if missing today
14
15
 
15
- // 2. History: Load MY OWN results from the last 29 days
16
- // CRITICAL: Name must match the File Name (PIDailyAssetAUM)
17
- dependencySeries: {
18
- 'PIDailyAssetAUM': 29
19
- },
20
-
21
- // 3. Bootstrap Flag: Allows running even if history is empty (First Run)
22
- canHaveMissingSeries: true
16
+ // 2. Rankings Lookback Series
17
+ // This requests the last 30 days of rankings to be available in context.seriesData.root.rankings
18
+ rootDataSeries: {
19
+ rankings: 30
20
+ }
23
21
  };
24
22
  }
25
23
 
@@ -29,73 +27,63 @@ class PIDailyAssetAUM {
29
27
  const { extract, RankingsExtractor } = context.math;
30
28
  const userId = context.user.id;
31
29
 
32
- // --- STEP A: Calculate TODAY (T-0) ---
33
- const piAum = RankingsExtractor.getAUMValue(context.user.rankEntry);
34
- const todayAllocation = {};
35
-
36
- if (piAum > 0) {
37
- const positions = extract.getPositions(context.user.portfolio.today, context.user.type);
38
- positions.forEach(pos => {
39
- const instId = extract.getInstrumentId(pos);
40
- const ticker = context.mappings.instrumentToTicker[instId] || `ID:${instId}`;
41
- const pct = extract.getPositionValue(pos) || 0;
42
-
43
- // Convert % to $
44
- const usdValue = piAum * (pct / 100);
30
+ // --- STEP A: Determine AUM (With Lookback) ---
31
+ // 1. Try Today
32
+ let piAum = RankingsExtractor.getAUMValue(context.user.rankEntry);
33
+
34
+ // 2. If missing, look back up to 30 days
35
+ if (!piAum || piAum === 0) {
36
+ const rankingsSeries = context.seriesData?.root?.rankings || {};
37
+
38
+ // Get dates sorted newest to oldest
39
+ const availableDates = Object.keys(rankingsSeries).sort((a, b) => b.localeCompare(a));
40
+
41
+ for (const dateKey of availableDates) {
42
+ const dailyRankings = rankingsSeries[dateKey];
43
+ const historicalEntry = RankingsExtractor.getEntry(dailyRankings, userId);
44
+ const historicalAum = RankingsExtractor.getAUMValue(historicalEntry);
45
45
 
46
- if (usdValue > 0) {
47
- if (!todayAllocation[ticker]) todayAllocation[ticker] = 0;
48
- todayAllocation[ticker] += usdValue;
46
+ if (historicalAum > 0) {
47
+ piAum = historicalAum;
48
+ // Optional: Log that we used historical data
49
+ // context.logger.log('INFO', `Using historical AUM for ${userId} from ${dateKey}: $${piAum}`);
50
+ break;
49
51
  }
50
- });
52
+ }
51
53
  }
52
54
 
53
- // --- STEP B: Load History (T-1 to T-29) ---
54
- // Access using the FILE NAME key
55
- const historyMap = context.globalData.series?.results?.['PIDailyAssetAUM'] || {};
56
-
57
- const rollingWindow = [];
58
-
59
- if (historyMap) {
60
- Object.values(historyMap).forEach(dayResult => {
61
- const userResult = dayResult[userId];
62
- if (userResult && userResult.dailyMap) {
63
- rollingWindow.push(userResult.dailyMap);
64
- }
65
- });
55
+ // If still no AUM found, we cannot compute $ allocation
56
+ if (!piAum || piAum <= 0) {
57
+ return;
66
58
  }
67
59
 
68
- // Always add Today
69
- rollingWindow.push(todayAllocation);
60
+ // --- STEP B: Calculate $ Allocation Per Asset ---
61
+ const allocationMap = {};
62
+ const positions = extract.getPositions(context.user.portfolio.today, context.user.type);
70
63
 
71
- // --- STEP C: Compute Rolling Average ---
72
- const summedStats = {};
73
-
74
- rollingWindow.forEach(dayAllocations => {
75
- Object.entries(dayAllocations).forEach(([ticker, val]) => {
76
- if (!summedStats[ticker]) summedStats[ticker] = { total: 0, count: 0 };
77
- summedStats[ticker].total += val;
78
- summedStats[ticker].count += 1;
79
- });
80
- });
81
-
82
- const rollingAverage = {};
83
- const validDays = rollingWindow.length;
64
+ positions.forEach(pos => {
65
+ const instId = extract.getInstrumentId(pos);
66
+ const ticker = context.mappings.instrumentToTicker[instId] || `ID:${instId}`;
67
+
68
+ // Get Weight (%)
69
+ const pct = extract.getPositionWeight(pos); // Uses Invested or Amount based on schema
84
70
 
85
- if (validDays > 0) {
86
- Object.entries(summedStats).forEach(([ticker, stat]) => {
87
- rollingAverage[ticker] = stat.total / validDays;
88
- });
89
- }
71
+ // Calculate $ Value: AUM * (Weight / 100)
72
+ // Note: Ensure pct is treated as percentage (e.g. 5.5 = 5.5%)
73
+ if (pct > 0) {
74
+ const usdValue = piAum * (pct / 100);
75
+
76
+ if (!allocationMap[ticker]) allocationMap[ticker] = 0;
77
+ allocationMap[ticker] += usdValue;
78
+ }
79
+ });
90
80
 
91
- // --- STEP D: Output Results ---
92
- // StandardExecutor reads 'this.results', it does NOT use the return value.
81
+ // --- STEP C: Output Results ---
82
+ // Format: { [UserId]: { Ticker: $Amount } }
93
83
  this.results = {
94
- [userId]: {
95
- dailyMap: todayAllocation,
96
- averageMap: rollingAverage
97
- }
84
+ [userId]: allocationMap
98
85
  };
86
+
99
87
  return this.results;
100
88
  }
101
89
 
@@ -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() { this.results = {}; }
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 { mappings } = context;
35
+ const mappings = context.mappings || {};
33
36
 
34
37
  // Defensive Check: If inputs missing, return empty object
35
38
  if (!similarityMatrix || !vectors) {
36
- return {};
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
- // [FIX] CLEANER APPROACH:
79
- // Instead of assigning to a confusing 'meta' key, we simply RETURN the data.
80
- // The MetaExecutor will automatically save this under the correct 'global' key.
81
- return recommendations;
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
- const vectorMap = context.computed['PiSimilarityVector'];
29
- // [FIX] Ensure we have enough peers to actually compare
30
- if (!vectorMap || Object.keys(vectorMap).length < 2) {
31
- return {}; // Return TRUE empty so ResultCommitter skips the write
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
- // [FIX] MetaExecutor requires return value
91
- return matrix;
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() { this.results = {}; }
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() { return ['PiAumOverTime']; }
19
+ static getDependencies() {
20
+ return ['PiAumOverTime'];
21
+ }
18
22
 
19
23
  static getSchema() {
20
- return { type: 'number', description: 'Total AUM of all tracked PIs' };
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
- return 0;
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
- // Validate it's a number
34
- if (typeof val === 'number') total += val;
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
- // [FIX] MetaExecutor requires return value
40
- return result;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.151",
3
+ "version": "1.0.153",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [