aiden-shared-calculations-unified 1.0.113 → 1.0.115

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.
@@ -0,0 +1,66 @@
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_meta'
15
+ };
16
+ }
17
+
18
+ static getDependencies() { return ['UserAUMPerAsset']; }
19
+
20
+ static getSchema() {
21
+ return {
22
+ type: 'object',
23
+ additionalProperties: { type: 'number' },
24
+ description: 'Map of instrumentId -> total AUM across all users'
25
+ };
26
+ }
27
+
28
+ process(context) {
29
+ const aumPerAssetData = context.computed['UserAUMPerAsset'];
30
+
31
+ if (!aumPerAssetData) {
32
+ this.results['meta'] = {};
33
+ return;
34
+ }
35
+
36
+ // Aggregate AUM per instrument across all users
37
+ const instrumentAUM = new Map();
38
+
39
+ for (const [userId, userAssets] of Object.entries(aumPerAssetData)) {
40
+ if (!userAssets || typeof userAssets !== 'object') continue;
41
+
42
+ for (const [instrumentIdStr, aum] of Object.entries(userAssets)) {
43
+ const instrumentId = Number(instrumentIdStr);
44
+ if (isNaN(instrumentId) || !aum) continue;
45
+
46
+ const current = instrumentAUM.get(instrumentId) || 0;
47
+ instrumentAUM.set(instrumentId, current + aum);
48
+ }
49
+ }
50
+
51
+ // Convert to object
52
+ const result = {};
53
+ for (const [instrumentId, totalAUM] of instrumentAUM.entries()) {
54
+ result[instrumentId] = Number(totalAUM.toFixed(2));
55
+ }
56
+
57
+ this.results['meta'] = result;
58
+ }
59
+
60
+ async getResult() {
61
+ return this.results;
62
+ }
63
+ }
64
+
65
+ module.exports = AggregateAssetAUM;
66
+
@@ -0,0 +1,60 @@
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_meta'
15
+ };
16
+ }
17
+
18
+ static getDependencies() { return ['UserAUM30Day']; }
19
+
20
+ static getSchema() {
21
+ return {
22
+ type: 'object',
23
+ additionalProperties: { type: 'number' },
24
+ description: 'Map of date -> total AUM across all users'
25
+ };
26
+ }
27
+
28
+ process(context) {
29
+ const aumData = context.computed['UserAUM30Day'];
30
+ if (!aumData) {
31
+ this.results['meta'] = {};
32
+ return;
33
+ }
34
+
35
+ // Aggregate AUM by date
36
+ const dateMap = {};
37
+
38
+ for (const [userId, userData] of Object.entries(aumData)) {
39
+ if (userData && typeof userData === 'object' && userData.date && typeof userData.aum === 'number') {
40
+ const date = userData.date;
41
+ dateMap[date] = (dateMap[date] || 0) + userData.aum;
42
+ }
43
+ }
44
+
45
+ // Round values
46
+ const rounded = {};
47
+ for (const [date, total] of Object.entries(dateMap)) {
48
+ rounded[date] = Number(total.toFixed(2));
49
+ }
50
+
51
+ this.results['meta'] = rounded;
52
+ }
53
+
54
+ async getResult() {
55
+ return this.results;
56
+ }
57
+ }
58
+
59
+ module.exports = AggregateDailyAUM;
60
+
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @fileoverview Aggregate Sector Ranking by AUM.
3
+ * Returns the ranked order of sectors invested in each day across all users (based on AUM).
4
+ */
5
+
6
+ class AggregateSectorRanking {
7
+ constructor() {
8
+ this.results = {};
9
+ }
10
+
11
+ static getMetadata() {
12
+ return {
13
+ type: 'meta',
14
+ category: 'popular_investor_meta'
15
+ };
16
+ }
17
+
18
+ static getDependencies() { return ['UserAUMPerAsset']; }
19
+
20
+ static getSchema() {
21
+ return {
22
+ type: 'array',
23
+ items: {
24
+ type: 'object',
25
+ properties: {
26
+ sector: { type: 'string' },
27
+ aum: { type: 'number' },
28
+ rank: { type: 'number' }
29
+ }
30
+ },
31
+ description: 'Ranked list of sectors by total AUM invested'
32
+ };
33
+ }
34
+
35
+ process(context) {
36
+ const aumPerAssetData = context.computed['UserAUMPerAsset'];
37
+ const mappings = context.mappings;
38
+
39
+ if (!aumPerAssetData || !mappings?.instrumentToSector) {
40
+ this.results['meta'] = [];
41
+ return;
42
+ }
43
+
44
+ // Aggregate AUM per sector across all users
45
+ // Use instrument-to-sector mapping to calculate accurate sector AUM
46
+ const sectorAUM = new Map();
47
+
48
+ for (const [userId, userAssets] of Object.entries(aumPerAssetData)) {
49
+ if (!userAssets || typeof userAssets !== 'object') continue;
50
+
51
+ for (const [instrumentIdStr, aum] of Object.entries(userAssets)) {
52
+ const instrumentId = Number(instrumentIdStr);
53
+ if (isNaN(instrumentId) || !aum) continue;
54
+
55
+ // Map instrument to sector
56
+ const sector = mappings.instrumentToSector[instrumentId] || 'Unknown';
57
+
58
+ // Add AUM to sector total
59
+ const current = sectorAUM.get(sector) || 0;
60
+ sectorAUM.set(sector, current + aum);
61
+ }
62
+ }
63
+
64
+ // Convert to array and rank
65
+ const ranked = Array.from(sectorAUM.entries())
66
+ .map(([sector, aum]) => ({ sector, aum: Number(aum.toFixed(2)) }))
67
+ .sort((a, b) => b.aum - a.aum)
68
+ .map((item, index) => ({ ...item, rank: index + 1 }));
69
+
70
+ this.results['meta'] = ranked;
71
+ }
72
+
73
+ async getResult() {
74
+ return this.results;
75
+ }
76
+ }
77
+
78
+ module.exports = AggregateSectorRanking;
79
+
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Aggregate Stock Ranking by AUM.
3
+ * Returns the ranked order of most invested in stocks each day across all users (based on AUM).
4
+ */
5
+
6
+ class AggregateStockRanking {
7
+ constructor() {
8
+ this.results = {};
9
+ }
10
+
11
+ static getMetadata() {
12
+ return {
13
+ type: 'meta',
14
+ category: 'popular_investor_meta'
15
+ };
16
+ }
17
+
18
+ static getDependencies() { return ['UserAUMPerAsset']; }
19
+
20
+ static getSchema() {
21
+ return {
22
+ type: 'array',
23
+ items: {
24
+ type: 'object',
25
+ properties: {
26
+ instrumentId: { type: 'number' },
27
+ ticker: { type: 'string' },
28
+ aum: { type: 'number' },
29
+ rank: { type: 'number' }
30
+ }
31
+ },
32
+ description: 'Ranked list of stocks by total AUM invested'
33
+ };
34
+ }
35
+
36
+ process(context) {
37
+ const aumPerAssetData = context.computed['UserAUMPerAsset'];
38
+ const mappings = context.mappings;
39
+
40
+ if (!aumPerAssetData) {
41
+ this.results['meta'] = [];
42
+ return;
43
+ }
44
+
45
+ // Aggregate AUM per instrument across all users
46
+ const instrumentAUM = new Map();
47
+
48
+ for (const [userId, userAssets] of Object.entries(aumPerAssetData)) {
49
+ if (!userAssets || typeof userAssets !== 'object') continue;
50
+
51
+ for (const [instrumentIdStr, aum] of Object.entries(userAssets)) {
52
+ const instrumentId = Number(instrumentIdStr);
53
+ if (isNaN(instrumentId) || !aum) continue;
54
+
55
+ const current = instrumentAUM.get(instrumentId) || 0;
56
+ instrumentAUM.set(instrumentId, current + aum);
57
+ }
58
+ }
59
+
60
+ // Convert to array, add ticker, and rank
61
+ const ranked = Array.from(instrumentAUM.entries())
62
+ .map(([instrumentId, aum]) => {
63
+ const ticker = mappings?.instrumentToTicker?.[instrumentId] || `ID${instrumentId}`;
64
+ return {
65
+ instrumentId,
66
+ ticker,
67
+ aum: Number(aum.toFixed(2))
68
+ };
69
+ })
70
+ .sort((a, b) => b.aum - a.aum)
71
+ .map((item, index) => ({ ...item, rank: index + 1 }));
72
+
73
+ this.results['meta'] = ranked;
74
+ }
75
+
76
+ async getResult() {
77
+ return this.results;
78
+ }
79
+ }
80
+
81
+ module.exports = AggregateStockRanking;
82
+
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview Popular Investors Who Bought Crypto
3
+ * Returns a list of PI CIDs who bought crypto instruments in the last N days.
4
+ * Used for dynamic watchlists and alerts.
5
+ */
6
+
7
+ class PiCryptoBuyers {
8
+ constructor() {
9
+ this.results = {};
10
+ }
11
+
12
+ static getMetadata() {
13
+ return {
14
+ type: 'meta',
15
+ category: 'popular_investor',
16
+ rootDataDependencies: ['portfolio', 'history'],
17
+ userType: 'POPULAR_INVESTOR'
18
+ };
19
+ }
20
+
21
+ static getDependencies() { return []; }
22
+
23
+ static getSchema() {
24
+ return {
25
+ type: 'object',
26
+ properties: {
27
+ cids: {
28
+ type: 'array',
29
+ items: { type: 'number' },
30
+ description: 'List of PI CIDs who bought crypto'
31
+ },
32
+ metadata: {
33
+ type: 'object',
34
+ properties: {
35
+ timeWindow: { type: 'number' },
36
+ count: { type: 'number' },
37
+ timestamp: { type: 'string' }
38
+ }
39
+ }
40
+ },
41
+ description: 'PIs who bought crypto in the last N days'
42
+ };
43
+ }
44
+
45
+ process(context) {
46
+ const { DataExtractor, HistoryExtractor } = context.math;
47
+ const portfolio = context.user?.portfolio?.today;
48
+ const historyDoc = context.user?.history?.today;
49
+ const mappings = context.mappings;
50
+
51
+ // Default time window: 7 days
52
+ const TIME_WINDOW_DAYS = 7;
53
+ const cutoffDate = new Date();
54
+ cutoffDate.setDate(cutoffDate.getDate() - TIME_WINDOW_DAYS);
55
+
56
+ // Crypto instrument type IDs (common crypto types in eToro)
57
+ // InstrumentTypeID 28 = Crypto
58
+ const CRYPTO_INSTRUMENT_TYPES = [28]; // Can be expanded
59
+
60
+ const cryptoBuyerCIDs = new Set();
61
+
62
+ // Check current portfolio for crypto positions
63
+ if (portfolio) {
64
+ const positions = DataExtractor.getPositions(portfolio, 'POPULAR_INVESTOR');
65
+
66
+ for (const pos of positions) {
67
+ const instrumentType = DataExtractor.getInstrumentTypeId(pos);
68
+
69
+ if (CRYPTO_INSTRUMENT_TYPES.includes(instrumentType)) {
70
+ const userId = context.user?.id;
71
+ if (userId) {
72
+ cryptoBuyerCIDs.add(Number(userId));
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ // Check trade history for recent crypto purchases
79
+ if (historyDoc) {
80
+ const historyPositions = HistoryExtractor.getHistoryPositions(historyDoc);
81
+
82
+ for (const trade of historyPositions) {
83
+ const openDate = trade.OpenDateTime ? new Date(trade.OpenDateTime) : null;
84
+
85
+ if (!openDate || openDate < cutoffDate) {
86
+ continue; // Too old
87
+ }
88
+
89
+ // Check if this is a crypto instrument
90
+ // For now, we'll check if the instrument type is crypto
91
+ // In a full implementation, we'd use instrument type mapping
92
+ const instrumentType = trade.InstrumentTypeID || 0;
93
+
94
+ if (CRYPTO_INSTRUMENT_TYPES.includes(instrumentType)) {
95
+ const userId = context.user?.id;
96
+ if (userId) {
97
+ cryptoBuyerCIDs.add(Number(userId));
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ this.results['meta'] = {
104
+ cids: Array.from(cryptoBuyerCIDs),
105
+ metadata: {
106
+ timeWindow: TIME_WINDOW_DAYS,
107
+ count: cryptoBuyerCIDs.size,
108
+ timestamp: new Date().toISOString()
109
+ }
110
+ };
111
+ }
112
+
113
+ async getResult() {
114
+ return this.results;
115
+ }
116
+ }
117
+
118
+ module.exports = PiCryptoBuyers;
119
+
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @fileoverview Popular Investors with High Volatility Changes
3
+ * Returns a list of PI CIDs with significant volatility changes.
4
+ * Used for dynamic watchlists and alerts.
5
+ */
6
+
7
+ class PiHighVolatility {
8
+ constructor() {
9
+ this.results = {};
10
+ }
11
+
12
+ static getMetadata() {
13
+ return {
14
+ type: 'meta',
15
+ category: 'popular_investor',
16
+ rootDataDependencies: ['rankings', 'portfolio'],
17
+ userType: 'POPULAR_INVESTOR'
18
+ };
19
+ }
20
+
21
+ static getDependencies() { return []; }
22
+
23
+ static getSchema() {
24
+ return {
25
+ type: 'object',
26
+ properties: {
27
+ cids: {
28
+ type: 'array',
29
+ items: { type: 'number' },
30
+ description: 'List of PI CIDs with high volatility changes'
31
+ },
32
+ metadata: {
33
+ type: 'object',
34
+ properties: {
35
+ volatilityThreshold: { type: 'number' },
36
+ timeWindow: { type: 'number' },
37
+ count: { type: 'number' },
38
+ timestamp: { type: 'string' }
39
+ }
40
+ }
41
+ },
42
+ description: 'PIs with high volatility changes'
43
+ };
44
+ }
45
+
46
+ process(context) {
47
+ const { RankingsExtractor, DataExtractor } = context.math;
48
+
49
+ // Default threshold: 50% volatility change
50
+ const VOLATILITY_THRESHOLD = 0.5; // 50%
51
+
52
+ const rankings = context.user?.rankings;
53
+ const portfolio = context.user?.portfolio?.today;
54
+
55
+ if (!rankings || !portfolio) {
56
+ this.results['meta'] = {
57
+ cids: [],
58
+ metadata: {
59
+ volatilityThreshold: VOLATILITY_THRESHOLD,
60
+ timeWindow: 1,
61
+ count: 0,
62
+ timestamp: new Date().toISOString()
63
+ }
64
+ };
65
+ return;
66
+ }
67
+
68
+ const todayRankings = rankings.today || {};
69
+ const yesterdayRankings = rankings.yesterday || {};
70
+
71
+ const todayItems = RankingsExtractor.getItems(todayRankings);
72
+ const yesterdayItems = RankingsExtractor.getItems(yesterdayRankings);
73
+
74
+ // Create maps for quick lookup
75
+ const todayMap = new Map();
76
+ for (const item of todayItems) {
77
+ if (item.CustomerId) {
78
+ todayMap.set(String(item.CustomerId), item);
79
+ }
80
+ }
81
+
82
+ const yesterdayMap = new Map();
83
+ for (const item of yesterdayItems) {
84
+ if (item.CustomerId) {
85
+ yesterdayMap.set(String(item.CustomerId), item);
86
+ }
87
+ }
88
+
89
+ const highVolatilityCIDs = [];
90
+
91
+ // Check each PI for volatility changes
92
+ for (const [cid, todayItem] of todayMap.entries()) {
93
+ const yesterdayItem = yesterdayMap.get(cid);
94
+
95
+ if (!yesterdayItem) {
96
+ continue; // New PI, skip
97
+ }
98
+
99
+ // Calculate volatility from DailyDD (Daily Drawdown) or WeeklyDD
100
+ // Using DailyDD as a proxy for volatility
101
+ const todayDD = todayItem.DailyDD || 0;
102
+ const yesterdayDD = yesterdayItem.DailyDD || 0;
103
+
104
+ if (yesterdayDD === 0) {
105
+ continue; // Can't calculate change from zero
106
+ }
107
+
108
+ // Calculate percentage change in drawdown (volatility proxy)
109
+ const volatilityChange = Math.abs((todayDD - yesterdayDD) / Math.abs(yesterdayDD));
110
+
111
+ if (volatilityChange >= VOLATILITY_THRESHOLD) {
112
+ highVolatilityCIDs.push(Number(cid));
113
+ }
114
+ }
115
+
116
+ this.results['meta'] = {
117
+ cids: highVolatilityCIDs,
118
+ metadata: {
119
+ volatilityThreshold: VOLATILITY_THRESHOLD,
120
+ timeWindow: 1,
121
+ count: highVolatilityCIDs.length,
122
+ timestamp: new Date().toISOString()
123
+ }
124
+ };
125
+ }
126
+
127
+ async getResult() {
128
+ return this.results;
129
+ }
130
+ }
131
+
132
+ module.exports = PiHighVolatility;
133
+
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @fileoverview Popular Investors with Risk Score Increase
3
+ * Returns a list of PI CIDs whose risk score increased by a threshold amount.
4
+ * Used for dynamic watchlists and alerts.
5
+ */
6
+
7
+ class PiRiskIncrease {
8
+ constructor() {
9
+ this.results = {};
10
+ }
11
+
12
+ static getMetadata() {
13
+ return {
14
+ type: 'meta',
15
+ category: 'popular_investor',
16
+ rootDataDependencies: ['rankings'],
17
+ userType: 'POPULAR_INVESTOR'
18
+ };
19
+ }
20
+
21
+ static getDependencies() { return []; }
22
+
23
+ static getSchema() {
24
+ return {
25
+ type: 'object',
26
+ properties: {
27
+ cids: {
28
+ type: 'array',
29
+ items: { type: 'number' },
30
+ description: 'List of PI CIDs whose risk score increased'
31
+ },
32
+ metadata: {
33
+ type: 'object',
34
+ properties: {
35
+ threshold: { type: 'number' },
36
+ timeWindow: { type: 'number' },
37
+ count: { type: 'number' },
38
+ timestamp: { type: 'string' }
39
+ }
40
+ }
41
+ },
42
+ description: 'PIs with risk score increase meeting threshold'
43
+ };
44
+ }
45
+
46
+ process(context) {
47
+ const { RankingsExtractor } = context.math;
48
+
49
+ // Default threshold: 2 points increase
50
+ // This can be parameterized later via computation parameters
51
+ const RISK_INCREASE_THRESHOLD = 2;
52
+
53
+ // Meta executors process all users, but we need rankings data
54
+ // For now, we'll access rankings from context.user.rankings
55
+ // In a full implementation, this would iterate through all PIs
56
+ const rankings = context.user?.rankings;
57
+
58
+ if (!rankings) {
59
+ this.results['meta'] = {
60
+ cids: [],
61
+ metadata: {
62
+ threshold: RISK_INCREASE_THRESHOLD,
63
+ timeWindow: 1,
64
+ count: 0,
65
+ timestamp: new Date().toISOString()
66
+ }
67
+ };
68
+ return;
69
+ }
70
+
71
+ const todayRankings = rankings.today || {};
72
+ const yesterdayRankings = rankings.yesterday || {};
73
+
74
+ const todayItems = RankingsExtractor.getItems(todayRankings);
75
+ const yesterdayItems = RankingsExtractor.getItems(yesterdayRankings);
76
+
77
+ // Create maps for quick lookup
78
+ const todayMap = new Map();
79
+ for (const item of todayItems) {
80
+ if (item.CustomerId) {
81
+ todayMap.set(String(item.CustomerId), item);
82
+ }
83
+ }
84
+
85
+ const yesterdayMap = new Map();
86
+ for (const item of yesterdayItems) {
87
+ if (item.CustomerId) {
88
+ yesterdayMap.set(String(item.CustomerId), item);
89
+ }
90
+ }
91
+
92
+ const increasedRiskCIDs = [];
93
+
94
+ // Check each PI in today's rankings
95
+ for (const [cid, todayItem] of todayMap.entries()) {
96
+ const yesterdayItem = yesterdayMap.get(cid);
97
+
98
+ if (!yesterdayItem) {
99
+ // New PI in rankings, skip (no previous data)
100
+ continue;
101
+ }
102
+
103
+ const todayRisk = RankingsExtractor.getRiskScore(todayItem);
104
+ const yesterdayRisk = RankingsExtractor.getRiskScore(yesterdayItem);
105
+
106
+ if (todayRisk !== null && yesterdayRisk !== null) {
107
+ const riskIncrease = todayRisk - yesterdayRisk;
108
+
109
+ if (riskIncrease >= RISK_INCREASE_THRESHOLD) {
110
+ increasedRiskCIDs.push(Number(cid));
111
+ }
112
+ }
113
+ }
114
+
115
+ this.results['meta'] = {
116
+ cids: increasedRiskCIDs,
117
+ metadata: {
118
+ threshold: RISK_INCREASE_THRESHOLD,
119
+ timeWindow: 1, // 1 day comparison
120
+ count: increasedRiskCIDs.length,
121
+ timestamp: new Date().toISOString()
122
+ }
123
+ };
124
+ }
125
+
126
+ async getResult() {
127
+ return this.results;
128
+ }
129
+ }
130
+
131
+ module.exports = PiRiskIncrease;
132
+