aiden-shared-calculations-unified 1.0.148 → 1.0.149
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/calculations/popular-investor/AumLeaderboard.js +3 -4
- package/calculations/popular-investor/GlobalAumPerAsset30D.js +26 -17
- package/calculations/popular-investor/PIDailyAssetAUM.js +37 -36
- package/calculations/popular-investor/RecommendedPopularInvestors.js +85 -45
- package/calculations/popular-investor/RiskLeaderboard.js +2 -3
- package/package.json +1 -1
- package/calculations/popular-investor/AggregateAssetAUM.js +0 -70
- package/calculations/popular-investor/AggregateDailyAUM.js +0 -72
- package/calculations/popular-investor/UserAUM30Day.js +0 -67
- package/calculations/popular-investor/UserAUMPerAsset.js +0 -67
|
@@ -5,7 +5,7 @@ class AumLeaderboard {
|
|
|
5
5
|
static getMetadata() {
|
|
6
6
|
return {
|
|
7
7
|
type: 'meta',
|
|
8
|
-
category: '
|
|
8
|
+
category: 'analytics',
|
|
9
9
|
// We only need the rankings root data
|
|
10
10
|
rootDataDependencies: ['rankings'],
|
|
11
11
|
mandatoryRoots: ['rankings'],
|
|
@@ -58,9 +58,8 @@ class AumLeaderboard {
|
|
|
58
58
|
|
|
59
59
|
// 4. Output Global Result
|
|
60
60
|
this.results = rankedResult;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
async getResult() {
|
|
61
|
+
|
|
62
|
+
// CRITICAL FIX: MetaExecutor requires the data to be returned
|
|
64
63
|
return this.results;
|
|
65
64
|
}
|
|
66
65
|
}
|
|
@@ -1,42 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Aggregates PI Asset AUM over the last 30 days.
|
|
3
|
+
*/
|
|
4
|
+
class GlobalAumPerAsset30D {
|
|
2
5
|
static getMetadata() {
|
|
3
6
|
return {
|
|
4
7
|
type: 'meta',
|
|
5
8
|
category: 'analytics',
|
|
6
9
|
rootDataDependencies: [],
|
|
7
|
-
// No dependencySeries needed! We just need Today's results.
|
|
10
|
+
// No dependencySeries needed! We just need Today's results via getDependencies.
|
|
8
11
|
schedule: { type: 'DAILY' }
|
|
9
12
|
};
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
static getDependencies() {
|
|
13
16
|
// Force running AFTER the Standard comp
|
|
14
|
-
|
|
17
|
+
// CRITICAL: Must match the File Name of the standard computation
|
|
18
|
+
return ['PIDailyAssetAUM'];
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
async process(context) {
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
const allUserResults = context.computed['
|
|
22
|
+
// Access the results injected by getDependencies()
|
|
23
|
+
// CRITICAL: Must match the File Name
|
|
24
|
+
const allUserResults = context.computed['PIDailyAssetAUM'];
|
|
21
25
|
|
|
22
26
|
const globalAverages = {};
|
|
23
27
|
let userCount = 0;
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
if (allUserResults) {
|
|
30
|
+
Object.values(allUserResults).forEach(userOutput => {
|
|
31
|
+
// userOutput is: { dailyMap, averageMap }
|
|
32
|
+
const avgMap = userOutput.averageMap;
|
|
33
|
+
if (!avgMap) return;
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
userCount++;
|
|
36
|
+
Object.entries(avgMap).forEach(([ticker, avgVal]) => {
|
|
37
|
+
if (!globalAverages[ticker]) globalAverages[ticker] = 0;
|
|
38
|
+
globalAverages[ticker] += avgVal;
|
|
39
|
+
});
|
|
34
40
|
});
|
|
35
|
-
}
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
// Result: The Sum of everyone's 30-Day Average Allocations
|
|
38
44
|
this.results = globalAverages;
|
|
45
|
+
|
|
46
|
+
// CRITICAL FIX: MetaExecutor requires the data to be returned
|
|
47
|
+
return this.results;
|
|
39
48
|
}
|
|
49
|
+
}
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
}
|
|
51
|
+
module.exports = GlobalAumPerAsset30D;
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Calculates the $ AUM allocated to each asset for a single PI using a Rolling 30-Day Window.
|
|
3
|
+
*/
|
|
4
|
+
class PIDailyAssetAUM {
|
|
2
5
|
static getMetadata() {
|
|
3
6
|
return {
|
|
4
7
|
type: 'standard',
|
|
5
8
|
category: 'analytics',
|
|
6
9
|
userType: 'POPULAR_INVESTOR',
|
|
7
10
|
|
|
8
|
-
// 1.
|
|
11
|
+
// 1. Core Data
|
|
9
12
|
rootDataDependencies: ['portfolio', 'rankings'],
|
|
10
13
|
mandatoryRoots: ['portfolio', 'rankings'],
|
|
11
14
|
|
|
12
|
-
// 2. Load MY OWN
|
|
13
|
-
//
|
|
15
|
+
// 2. History: Load MY OWN results from the last 29 days
|
|
16
|
+
// CRITICAL: Name must match the File Name (PIDailyAssetAUM)
|
|
14
17
|
dependencySeries: {
|
|
15
|
-
'
|
|
16
|
-
}
|
|
18
|
+
'PIDailyAssetAUM': 29
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
// 3. Bootstrap Flag: Allows running even if history is empty (First Run)
|
|
22
|
+
canHaveMissingSeries: true
|
|
17
23
|
};
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
static getDependencies() { return []; }
|
|
21
27
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
28
|
async process(context) {
|
|
25
29
|
const { extract, RankingsExtractor } = context.math;
|
|
26
30
|
const userId = context.user.id;
|
|
@@ -47,55 +51,52 @@ class PIDailyRollingAUM {
|
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
// --- STEP B: Load History (T-1 to T-29) ---
|
|
50
|
-
//
|
|
51
|
-
const historyMap = context.globalData.series?.results?.['
|
|
54
|
+
// Access using the FILE NAME key
|
|
55
|
+
const historyMap = context.globalData.series?.results?.['PIDailyAssetAUM'] || {};
|
|
52
56
|
|
|
53
|
-
// Collect all daily maps (History + Today)
|
|
54
57
|
const rollingWindow = [];
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
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
|
+
});
|
|
66
|
+
}
|
|
65
67
|
|
|
66
|
-
//
|
|
68
|
+
// Always add Today
|
|
67
69
|
rollingWindow.push(todayAllocation);
|
|
68
70
|
|
|
69
71
|
// --- STEP C: Compute Rolling Average ---
|
|
70
|
-
const summedStats = {};
|
|
72
|
+
const summedStats = {};
|
|
71
73
|
|
|
72
74
|
rollingWindow.forEach(dayAllocations => {
|
|
73
75
|
Object.entries(dayAllocations).forEach(([ticker, val]) => {
|
|
74
76
|
if (!summedStats[ticker]) summedStats[ticker] = { total: 0, count: 0 };
|
|
75
77
|
summedStats[ticker].total += val;
|
|
76
|
-
summedStats[ticker].count += 1;
|
|
78
|
+
summedStats[ticker].count += 1;
|
|
77
79
|
});
|
|
78
80
|
});
|
|
79
81
|
|
|
80
82
|
const rollingAverage = {};
|
|
81
|
-
const validDays = rollingWindow.length;
|
|
83
|
+
const validDays = rollingWindow.length;
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
85
|
+
if (validDays > 0) {
|
|
86
|
+
Object.entries(summedStats).forEach(([ticker, stat]) => {
|
|
87
|
+
rollingAverage[ticker] = stat.total / validDays;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
89
90
|
|
|
90
|
-
// --- STEP D:
|
|
91
|
-
//
|
|
91
|
+
// --- STEP D: Output Results ---
|
|
92
|
+
// StandardExecutor reads 'this.results', it does NOT use the return value.
|
|
92
93
|
this.results = {
|
|
93
94
|
[userId]: {
|
|
94
|
-
dailyMap: todayAllocation,
|
|
95
|
-
averageMap: rollingAverage
|
|
95
|
+
dailyMap: todayAllocation,
|
|
96
|
+
averageMap: rollingAverage
|
|
96
97
|
}
|
|
97
98
|
};
|
|
98
99
|
}
|
|
100
|
+
}
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
+
module.exports = PIDailyAssetAUM;
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Recommends PIs to Signed-In Users
|
|
2
|
+
* @fileoverview Recommends PIs to Signed-In Users using concrete ID matching.
|
|
3
|
+
* MATCHING STRATEGY:
|
|
4
|
+
* 1. Exclusion: Filter PIs the user already copies (ParentCID).
|
|
5
|
+
* 2. Interest: Page Views (30D History) using 'viewsByUser' schema.
|
|
6
|
+
* 3. Asset Class: Match User's holdings (InstrumentTypeID) vs PI's TopTradedAssetClassId.
|
|
7
|
+
* 4. Instrument: Match User's holdings (InstrumentID) vs PI's TopTradedInstrumentId.
|
|
3
8
|
*/
|
|
4
9
|
class RecommendedPopularInvestors {
|
|
5
10
|
static getMetadata() {
|
|
6
11
|
return {
|
|
7
12
|
type: 'standard',
|
|
8
13
|
category: 'recommendations',
|
|
9
|
-
userType: 'SIGNED_IN_USER',
|
|
14
|
+
userType: 'SIGNED_IN_USER',
|
|
10
15
|
|
|
11
|
-
// We need Portfolio (
|
|
16
|
+
// We need User Portfolio (Deep) and Global Rankings (Summary)
|
|
12
17
|
rootDataDependencies: ['portfolio', 'rankings'],
|
|
13
18
|
mandatoryRoots: ['portfolio', 'rankings'],
|
|
14
|
-
canHaveMissingRoots: true, // Allow running even if pageViews
|
|
19
|
+
canHaveMissingRoots: true, // Allow running even if pageViews/ratings missing
|
|
15
20
|
|
|
16
21
|
// 30-Day History of Page Views for Interest Graph
|
|
22
|
+
// System loads the global daily docs: { [PiCID]: { viewsByUser: { [UserCID]: { viewCount: N } } } }
|
|
17
23
|
rootDataSeries: {
|
|
18
24
|
pageViews: 30
|
|
19
25
|
}
|
|
@@ -26,7 +32,7 @@ class RecommendedPopularInvestors {
|
|
|
26
32
|
return {
|
|
27
33
|
type: 'object',
|
|
28
34
|
patternProperties: {
|
|
29
|
-
'^[0-9]+$': {
|
|
35
|
+
'^[0-9]+$': {
|
|
30
36
|
type: 'array',
|
|
31
37
|
items: {
|
|
32
38
|
type: 'object',
|
|
@@ -43,90 +49,124 @@ class RecommendedPopularInvestors {
|
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
async process(context) {
|
|
46
|
-
const {
|
|
52
|
+
const { RankingsExtractor, CopyTradingExtractor, PageViewsExtractor, DataExtractor } = context.math;
|
|
47
53
|
const userPortfolio = context.user.portfolio.today;
|
|
48
54
|
const allRankings = context.globalData.rankings || [];
|
|
49
55
|
const pageViewSeries = context.globalData.series.root.pageViews || {};
|
|
50
56
|
|
|
51
|
-
//
|
|
57
|
+
// --- STEP 1: USER PROFILE EXTRACTION ---
|
|
58
|
+
|
|
59
|
+
// A. Who do I copy? (Exclusion List)
|
|
60
|
+
// Schema: AggregatedMirrors -> ParentCID (e.g., 5125148)
|
|
52
61
|
const mirrors = CopyTradingExtractor.getAggregatedMirrors(userPortfolio);
|
|
53
62
|
const copiedCids = new Set(mirrors.map(m => String(CopyTradingExtractor.getParentCID(m))));
|
|
54
63
|
copiedCids.add(String(context.user.id)); // Exclude self
|
|
55
64
|
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const current = piInterestScore.get(piCid) || 0;
|
|
73
|
-
piInterestScore.set(piCid, current + (userViews * 2)); // 2 points per view
|
|
74
|
-
}
|
|
75
|
-
}
|
|
65
|
+
// B. What Asset Classes do I hold?
|
|
66
|
+
// Schema: AggregatedPositionsByInstrumentTypeID -> InstrumentTypeID (e.g., 5=Crypto, 4=Stocks)
|
|
67
|
+
const userAssetClasses = new Set();
|
|
68
|
+
if (userPortfolio && Array.isArray(userPortfolio.AggregatedPositionsByInstrumentTypeID)) {
|
|
69
|
+
userPortfolio.AggregatedPositionsByInstrumentTypeID.forEach(grp => {
|
|
70
|
+
if (grp.InstrumentTypeID) userAssetClasses.add(Number(grp.InstrumentTypeID));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// C. What Instruments do I hold?
|
|
75
|
+
// Schema: AggregatedPositions -> InstrumentID (e.g., 1353)
|
|
76
|
+
const userInstruments = new Set();
|
|
77
|
+
const positions = DataExtractor.getPositions(userPortfolio, context.user.type);
|
|
78
|
+
positions.forEach(pos => {
|
|
79
|
+
const id = DataExtractor.getInstrumentId(pos);
|
|
80
|
+
if (id) userInstruments.add(Number(id));
|
|
76
81
|
});
|
|
77
82
|
|
|
78
|
-
//
|
|
79
|
-
|
|
83
|
+
// --- STEP 2: INTEREST GRAPH (Page Views) ---
|
|
84
|
+
// Iterate through 30 days of global page view maps
|
|
85
|
+
const piInterestScore = new Map();
|
|
86
|
+
if (pageViewSeries) {
|
|
87
|
+
Object.values(pageViewSeries).forEach(dailyGlobalData => {
|
|
88
|
+
// dailyGlobalData matches your schema: { "31075566": { viewsByUser: { ... } } }
|
|
89
|
+
|
|
90
|
+
// Extractor handles filtering 'lastUpdated' and iterating PIs
|
|
91
|
+
const allPiCids = PageViewsExtractor.getAllPIs(dailyGlobalData);
|
|
92
|
+
|
|
93
|
+
for (const piCid of allPiCids) {
|
|
94
|
+
if (copiedCids.has(piCid)) continue;
|
|
95
|
+
|
|
96
|
+
// Extractor looks inside 'viewsByUser' -> [UserCID] -> 'viewCount'
|
|
97
|
+
const userViews = PageViewsExtractor.getUserViewCount(dailyGlobalData, piCid, context.user.id);
|
|
98
|
+
|
|
99
|
+
if (userViews > 0) {
|
|
100
|
+
const current = piInterestScore.get(piCid) || 0;
|
|
101
|
+
// Score logic: 2 points per view
|
|
102
|
+
piInterestScore.set(piCid, current + (userViews * 2));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
80
107
|
|
|
81
|
-
//
|
|
108
|
+
// --- STEP 3: MATCHING LOGIC ---
|
|
82
109
|
const recommendations = [];
|
|
83
110
|
|
|
84
111
|
for (const piEntry of allRankings) {
|
|
85
112
|
const piCid = String(piEntry.CustomerId || piEntry.cid);
|
|
113
|
+
|
|
114
|
+
// 1. Exclusion
|
|
86
115
|
if (copiedCids.has(piCid)) continue;
|
|
87
116
|
|
|
88
117
|
let score = 0;
|
|
89
118
|
const reasons = [];
|
|
90
119
|
|
|
91
|
-
//
|
|
120
|
+
// 2. Interest (Page Views)
|
|
92
121
|
const viewScore = piInterestScore.get(piCid) || 0;
|
|
93
122
|
if (viewScore > 0) {
|
|
94
|
-
score += Math.min(20, viewScore);
|
|
123
|
+
score += Math.min(20, viewScore);
|
|
95
124
|
reasons.push('Recently Viewed');
|
|
96
125
|
}
|
|
97
126
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
127
|
+
// 3. Asset Class Match (Hard ID Match)
|
|
128
|
+
// Schema: Rankings -> TopTradedAssetClassId (e.g., 5)
|
|
129
|
+
const piAssetClass = piEntry.TopTradedAssetClassId;
|
|
130
|
+
if (piAssetClass && userAssetClasses.has(piAssetClass)) {
|
|
102
131
|
score += 15;
|
|
103
|
-
reasons.push('
|
|
132
|
+
reasons.push('Trades Your Asset Classes');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. Top Instrument Match (Hard ID Match)
|
|
136
|
+
// Schema: Rankings -> TopTradedInstrumentId (e.g., 1155)
|
|
137
|
+
const piTopInst = piEntry.TopTradedInstrumentId;
|
|
138
|
+
if (piTopInst && userInstruments.has(piTopInst)) {
|
|
139
|
+
score += 10;
|
|
140
|
+
reasons.push('Trades Your Top Assets');
|
|
104
141
|
}
|
|
105
142
|
|
|
106
|
-
//
|
|
143
|
+
// 5. Risk Filter (Baseline Quality)
|
|
144
|
+
// Schema: Rankings -> RiskScore
|
|
107
145
|
const risk = RankingsExtractor.getRiskScore(piEntry);
|
|
108
|
-
if (risk <= 6)
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
if (risk <= 6) {
|
|
147
|
+
score += 5;
|
|
148
|
+
reasons.push('Stable Risk Score');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Push if there is a signal
|
|
111
152
|
if (score > 0) {
|
|
112
153
|
recommendations.push({
|
|
113
154
|
cid: piCid,
|
|
114
155
|
username: piEntry.UserName || "Unknown",
|
|
115
156
|
matchScore: score,
|
|
116
|
-
reason
|
|
157
|
+
// Ensure a default reason exists
|
|
158
|
+
reason: reasons[0] || 'Recommended Strategy'
|
|
117
159
|
});
|
|
118
160
|
}
|
|
119
161
|
}
|
|
120
162
|
|
|
121
|
-
//
|
|
163
|
+
// --- STEP 4: SORT & LIMIT ---
|
|
122
164
|
recommendations.sort((a, b) => b.matchScore - a.matchScore);
|
|
123
165
|
|
|
124
166
|
this.results = {
|
|
125
167
|
[context.user.id]: recommendations.slice(0, 5)
|
|
126
168
|
};
|
|
127
169
|
}
|
|
128
|
-
|
|
129
|
-
async getResult() { return this.results; }
|
|
130
170
|
}
|
|
131
171
|
|
|
132
172
|
module.exports = RecommendedPopularInvestors;
|
package/package.json
CHANGED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Aggregate AUM per Asset Across All Users.
|
|
3
|
-
* Returns the AUM invested into each asset total across all users.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
class AggregateAssetAUM {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.results = {};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static getMetadata() {
|
|
12
|
-
return {
|
|
13
|
-
type: 'meta',
|
|
14
|
-
category: 'popular_investor',
|
|
15
|
-
userType: 'POPULAR_INVESTOR',
|
|
16
|
-
// [FIX] Enforce strict availability check.
|
|
17
|
-
// Prevents execution if PI data is missing, even if upstream calc exists.
|
|
18
|
-
rootDataDependencies: ['portfolio']
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static getDependencies() { return ['UserAUMPerAsset']; }
|
|
23
|
-
|
|
24
|
-
static getSchema() {
|
|
25
|
-
return {
|
|
26
|
-
type: 'object',
|
|
27
|
-
additionalProperties: { type: 'number' },
|
|
28
|
-
description: 'Map of instrumentId -> total AUM across all users'
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
process(context) {
|
|
33
|
-
// Input: Map of { userId: { instrumentId: aum } }
|
|
34
|
-
const aumPerAssetData = context.computed['UserAUMPerAsset'];
|
|
35
|
-
|
|
36
|
-
if (!aumPerAssetData) {
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Aggregate AUM per instrument across all users
|
|
41
|
-
const instrumentAUM = new Map();
|
|
42
|
-
|
|
43
|
-
for (const [userId, userAssets] of Object.entries(aumPerAssetData)) {
|
|
44
|
-
if (!userAssets || typeof userAssets !== 'object') continue;
|
|
45
|
-
|
|
46
|
-
for (const [instrumentIdStr, aum] of Object.entries(userAssets)) {
|
|
47
|
-
const instrumentId = Number(instrumentIdStr);
|
|
48
|
-
// Validate AUM is a number and valid instrument ID
|
|
49
|
-
if (isNaN(instrumentId) || typeof aum !== 'number' || aum <= 0) continue;
|
|
50
|
-
|
|
51
|
-
const current = instrumentAUM.get(instrumentId) || 0;
|
|
52
|
-
instrumentAUM.set(instrumentId, current + aum);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Convert Map to output Object
|
|
57
|
-
const result = {};
|
|
58
|
-
for (const [instrumentId, totalAUM] of instrumentAUM.entries()) {
|
|
59
|
-
result[instrumentId] = Number(totalAUM.toFixed(2));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async getResult() {
|
|
66
|
-
return this.results;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
module.exports = AggregateAssetAUM;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Aggregate Daily AUM Across All Users.
|
|
3
|
-
* Returns the overall AUM values each day total across all users.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
class AggregateDailyAUM {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.results = {};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static getMetadata() {
|
|
12
|
-
return {
|
|
13
|
-
type: 'meta',
|
|
14
|
-
category: 'popular_investor',
|
|
15
|
-
userType: 'POPULAR_INVESTOR'
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
static getDependencies() { return ['UserAUM30Day']; }
|
|
20
|
-
|
|
21
|
-
static getSchema() {
|
|
22
|
-
return {
|
|
23
|
-
type: 'object',
|
|
24
|
-
additionalProperties: { type: 'number' },
|
|
25
|
-
description: 'Map of date -> total AUM across all users'
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
process(context) {
|
|
30
|
-
const aumData = context.computed['UserAUM30Day'];
|
|
31
|
-
|
|
32
|
-
// [FIX] Defensive check
|
|
33
|
-
if (!aumData) {
|
|
34
|
-
return {};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Aggregate AUM by date
|
|
38
|
-
const dateMap = {};
|
|
39
|
-
|
|
40
|
-
for (const [userId, userHistory] of Object.entries(aumData)) {
|
|
41
|
-
// [FIX] Validate userHistory exists
|
|
42
|
-
if (!userHistory) continue;
|
|
43
|
-
|
|
44
|
-
// [FIX] "30Day" data is an Array of daily entries.
|
|
45
|
-
// We must iterate the array. We also handle the edge case where it might be a single object.
|
|
46
|
-
const entries = Array.isArray(userHistory) ? userHistory : [userHistory];
|
|
47
|
-
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
if (entry && typeof entry === 'object' && entry.date && typeof entry.aum === 'number') {
|
|
50
|
-
const date = entry.date;
|
|
51
|
-
// Sum the AUM for this specific date
|
|
52
|
-
dateMap[date] = (dateMap[date] || 0) + entry.aum;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Round values to 2 decimals
|
|
58
|
-
const rounded = {};
|
|
59
|
-
for (const [date, total] of Object.entries(dateMap)) {
|
|
60
|
-
rounded[date] = Number(total.toFixed(2));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// [FIX] Must RETURN the result for MetaExecutor
|
|
64
|
-
return rounded;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async getResult() {
|
|
68
|
-
return this.results;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = AggregateDailyAUM;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview AUM Value Over Last 30 Days for Users.
|
|
3
|
-
* Returns the AUM value for the current day.
|
|
4
|
-
* (Historical aggregation handled by Database/API or 'isHistorical' mechanisms in Meta scripts).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
class UserAUM30Day {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.results = {};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
static getMetadata() {
|
|
13
|
-
return {
|
|
14
|
-
type: 'standard',
|
|
15
|
-
category: 'popular_investor',
|
|
16
|
-
rootDataDependencies: ['portfolio', 'rankings'],
|
|
17
|
-
userType: 'POPULAR_INVESTOR'
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static getDependencies() { return []; }
|
|
22
|
-
|
|
23
|
-
static getSchema() {
|
|
24
|
-
return {
|
|
25
|
-
type: 'object',
|
|
26
|
-
properties: {
|
|
27
|
-
date: { type: 'string', format: 'date' },
|
|
28
|
-
aum: { type: 'number' }
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
process(context) {
|
|
34
|
-
const { RankingsExtractor, PopularInvestorExtractor } = context.math;
|
|
35
|
-
const userId = context.user.id;
|
|
36
|
-
const portfolio = context.user.portfolio.today;
|
|
37
|
-
const rankEntry = context.user.rankEntry;
|
|
38
|
-
const dateStr = context.date.today;
|
|
39
|
-
|
|
40
|
-
let aum = 0;
|
|
41
|
-
|
|
42
|
-
// 1. Priority: Official Rankings Data
|
|
43
|
-
if (rankEntry) {
|
|
44
|
-
aum = RankingsExtractor.getAUMValue(rankEntry) || 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// 2. Fallback: Portfolio Snapshot Data
|
|
48
|
-
if ((!aum || aum === 0) && portfolio) {
|
|
49
|
-
if (typeof portfolio.AUMValue === 'number') {
|
|
50
|
-
aum = portfolio.AUMValue;
|
|
51
|
-
} else {
|
|
52
|
-
aum = PopularInvestorExtractor.getEquity(portfolio);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
this.results[userId] = {
|
|
57
|
-
date: dateStr,
|
|
58
|
-
aum: Number(aum.toFixed(2))
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async getResult() {
|
|
63
|
-
return this.results;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
module.exports = UserAUM30Day;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview AUM per Asset for Users.
|
|
3
|
-
* Returns the AUM invested into each asset per user.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
class UserAUMPerAsset {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.results = {};
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
static getMetadata() {
|
|
12
|
-
return {
|
|
13
|
-
type: 'standard',
|
|
14
|
-
category: 'popular_investor',
|
|
15
|
-
rootDataDependencies: ['portfolio'],
|
|
16
|
-
userType: 'POPULAR_INVESTOR'
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
static getDependencies() { return []; }
|
|
21
|
-
|
|
22
|
-
static getSchema() {
|
|
23
|
-
return {
|
|
24
|
-
type: 'object',
|
|
25
|
-
additionalProperties: { type: 'number' },
|
|
26
|
-
description: 'Map of instrumentId -> AUM value invested'
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
process(context) {
|
|
31
|
-
const { DataExtractor } = context.math;
|
|
32
|
-
const userId = context.user.id;
|
|
33
|
-
const portfolio = context.user.portfolio.today;
|
|
34
|
-
|
|
35
|
-
if (!portfolio) {
|
|
36
|
-
this.results[userId] = {};
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// [FIX] Inline aggregation logic
|
|
41
|
-
const positions = DataExtractor.getPositions(portfolio, 'POPULAR_INVESTOR');
|
|
42
|
-
const aumMap = {};
|
|
43
|
-
|
|
44
|
-
for (const pos of positions) {
|
|
45
|
-
const instId = DataExtractor.getInstrumentId(pos);
|
|
46
|
-
const value = DataExtractor.getPositionValue(pos); // Value in USD
|
|
47
|
-
|
|
48
|
-
if (instId && value > 0) {
|
|
49
|
-
// Ensure key is numeric or string consistent with consumption
|
|
50
|
-
aumMap[instId] = (aumMap[instId] || 0) + value;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Round values
|
|
55
|
-
for (const key in aumMap) {
|
|
56
|
-
aumMap[key] = Number(aumMap[key].toFixed(2));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
this.results[userId] = aumMap;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async getResult() {
|
|
63
|
-
return this.results;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
module.exports = UserAUMPerAsset;
|