bulltrackers-module 1.0.770 → 1.0.772
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/computations/GlobalAumPerAsset30D.js +11 -13
- package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +17 -19
- package/functions/computation-system-v2/computations/PiFeatureVectors.js +44 -90
- package/functions/computation-system-v2/computations/PiRecommender.js +155 -226
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +20 -20
- package/functions/computation-system-v2/computations/SectorCorrelations.js +228 -0
- package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +15 -32
- package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +31 -31
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +3 -2
- package/functions/computation-system-v2/framework/data/DataFetcher.js +1 -1
- package/functions/computation-system-v2/framework/storage/StateRepository.js +13 -11
- package/functions/computation-system-v2/handlers/scheduler.js +43 -19
- package/package.json +1 -1
- package/functions/computation-system-v2/docs/Agents.MD +0 -964
|
@@ -13,27 +13,25 @@ class GlobalAumPerAsset extends Computation {
|
|
|
13
13
|
name: 'GlobalAumPerAsset',
|
|
14
14
|
type: 'global',
|
|
15
15
|
category: 'market_insights',
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
requires: {
|
|
18
18
|
'computation_results': {
|
|
19
|
-
lookback: 0,
|
|
19
|
+
lookback: 0,
|
|
20
20
|
mandatory: true,
|
|
21
21
|
fields: ['result_data', 'computation_name', 'date'],
|
|
22
22
|
// Lowercase to match BigQuery storage
|
|
23
|
-
filter: {
|
|
24
|
-
computation_name: 'pidailyassetaum'
|
|
23
|
+
filter: {
|
|
24
|
+
computation_name: 'pidailyassetaum'
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
dependencies: ['PIDailyAssetAUM'],
|
|
30
30
|
|
|
31
31
|
storage: {
|
|
32
32
|
bigquery: true,
|
|
33
33
|
firestore: {
|
|
34
|
-
enabled:
|
|
35
|
-
path: 'global_metrics/aum_per_asset_{date}',
|
|
36
|
-
merge: true
|
|
34
|
+
enabled: false,
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
37
|
};
|
|
@@ -44,8 +42,8 @@ class GlobalAumPerAsset extends Computation {
|
|
|
44
42
|
|
|
45
43
|
let rows = data['computation_results'];
|
|
46
44
|
if (!rows) {
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
console.log('[GlobalAumPerAsset] No input data found.');
|
|
46
|
+
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
if (!Array.isArray(rows) && typeof rows === 'object') {
|
|
@@ -64,15 +62,15 @@ class GlobalAumPerAsset extends Computation {
|
|
|
64
62
|
|
|
65
63
|
for (const row of rows) {
|
|
66
64
|
let assetMap = row.result_data;
|
|
67
|
-
|
|
65
|
+
|
|
68
66
|
if (typeof assetMap === 'string') {
|
|
69
67
|
try { assetMap = JSON.parse(assetMap); } catch (e) { continue; }
|
|
70
68
|
}
|
|
71
|
-
|
|
69
|
+
|
|
72
70
|
if (!assetMap) continue;
|
|
73
71
|
|
|
74
72
|
piCount++;
|
|
75
|
-
|
|
73
|
+
|
|
76
74
|
for (const [ticker, value] of Object.entries(assetMap)) {
|
|
77
75
|
if (typeof value === 'number') {
|
|
78
76
|
const current = globalTotals.get(ticker) || 0;
|
|
@@ -8,7 +8,7 @@ class PIDailyAssetAUM extends Computation {
|
|
|
8
8
|
type: 'per-entity',
|
|
9
9
|
category: 'popular_investor',
|
|
10
10
|
isHistorical: true,
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
requires: {
|
|
13
13
|
// Driver: Only process Popular Investors
|
|
14
14
|
'portfolio_snapshots': {
|
|
@@ -22,22 +22,20 @@ class PIDailyAssetAUM extends Computation {
|
|
|
22
22
|
mandatory: true,
|
|
23
23
|
fields: ['pi_id', 'rankings_data', 'date']
|
|
24
24
|
},
|
|
25
|
-
'ticker_mappings': {
|
|
25
|
+
'ticker_mappings': {
|
|
26
26
|
mandatory: false,
|
|
27
|
-
fields: ['instrument_id', 'ticker']
|
|
28
|
-
},
|
|
29
|
-
'pi_master_list': {
|
|
27
|
+
fields: ['instrument_id', 'ticker']
|
|
28
|
+
},
|
|
29
|
+
'pi_master_list': {
|
|
30
30
|
mandatory: false,
|
|
31
|
-
fields: ['cid', 'username']
|
|
31
|
+
fields: ['cid', 'username']
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
|
|
35
35
|
storage: {
|
|
36
36
|
bigquery: true,
|
|
37
37
|
firestore: {
|
|
38
|
-
enabled:
|
|
39
|
-
path: 'popular_investors/{entityId}/metrics/asset_aum_{date}',
|
|
40
|
-
merge: true
|
|
38
|
+
enabled: false
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
};
|
|
@@ -62,7 +60,7 @@ class PIDailyAssetAUM extends Computation {
|
|
|
62
60
|
if (Array.isArray(dataset)) return dataset.filter(r => String(r.user_id || r.pi_id || r.cid) === String(entityId));
|
|
63
61
|
return [];
|
|
64
62
|
};
|
|
65
|
-
|
|
63
|
+
|
|
66
64
|
// Mappings
|
|
67
65
|
const tickerMap = new Map();
|
|
68
66
|
const mappingsList = Array.isArray(context.data['ticker_mappings']) ? context.data['ticker_mappings'] : Object.values(context.data['ticker_mappings'] || {});
|
|
@@ -77,8 +75,8 @@ class PIDailyAssetAUM extends Computation {
|
|
|
77
75
|
let validRanking = null;
|
|
78
76
|
|
|
79
77
|
// Sort descending using safe date parsing
|
|
80
|
-
portfolios.sort((a,b) => parseDate(b.date) - parseDate(a.date));
|
|
81
|
-
rankings.sort((a,b) => parseDate(b.date) - parseDate(a.date));
|
|
78
|
+
portfolios.sort((a, b) => parseDate(b.date) - parseDate(a.date));
|
|
79
|
+
rankings.sort((a, b) => parseDate(b.date) - parseDate(a.date));
|
|
82
80
|
|
|
83
81
|
if (portfolios.length > 0) {
|
|
84
82
|
validPortfolio = portfolios[0];
|
|
@@ -88,7 +86,7 @@ class PIDailyAssetAUM extends Computation {
|
|
|
88
86
|
const rDate = parseDate(r.date);
|
|
89
87
|
const diffTime = Math.abs(pDate - rDate);
|
|
90
88
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
91
|
-
|
|
89
|
+
|
|
92
90
|
// Allow up to 14 days gap (matches your data reality)
|
|
93
91
|
return diffDays <= 14;
|
|
94
92
|
});
|
|
@@ -101,10 +99,10 @@ class PIDailyAssetAUM extends Computation {
|
|
|
101
99
|
// 3. Calculation
|
|
102
100
|
const pData = rules.portfolio.extractPortfolioData(validPortfolio);
|
|
103
101
|
const rData = rules.rankings.extractRankingsData(validRanking);
|
|
104
|
-
|
|
102
|
+
|
|
105
103
|
// FIX: Use 'getAUM' (checked from rules/rankings.js)
|
|
106
|
-
const totalAum = rules.rankings.getAUM(rData);
|
|
107
|
-
|
|
104
|
+
const totalAum = rules.rankings.getAUM(rData);
|
|
105
|
+
|
|
108
106
|
if (!totalAum || totalAum <= 0) return;
|
|
109
107
|
|
|
110
108
|
const positions = rules.portfolio.extractPositions(pData);
|
|
@@ -113,10 +111,10 @@ class PIDailyAssetAUM extends Computation {
|
|
|
113
111
|
positions.forEach(pos => {
|
|
114
112
|
const id = rules.portfolio.getInstrumentId(pos);
|
|
115
113
|
const ticker = resolveTicker(id);
|
|
116
|
-
|
|
114
|
+
|
|
117
115
|
// FIX: Use 'getInvested' (checked from rules/portfolio.js)
|
|
118
|
-
const weight = rules.portfolio.getInvested(pos);
|
|
119
|
-
|
|
116
|
+
const weight = rules.portfolio.getInvested(pos);
|
|
117
|
+
|
|
120
118
|
if (weight > 0) {
|
|
121
119
|
// weight is a percentage (e.g., 5.5 for 5.5%), so divide by 100
|
|
122
120
|
const dollarValue = (weight / 100) * totalAum;
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview PI Feature Vectors
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
* - Risk profile (volatility, risk score)
|
|
10
|
-
* - Performance metrics (gain %, win ratio)
|
|
11
|
-
* - Quality indicators (AUM tier, copiers)
|
|
2
|
+
* @fileoverview PI Feature Vectors (v2 - Direction Aware)
|
|
3
|
+
* * Global computation that builds normalized feature vectors for all Popular Investors.
|
|
4
|
+
* Calculates Net Exposure (Long - Short) based on Portfolio Value %.
|
|
5
|
+
* * Features computed per PI:
|
|
6
|
+
* - Net Sector exposure (Signed %: + for Long, - for Short)
|
|
7
|
+
* - Leverage utilization
|
|
8
|
+
* - Quality indicators (Risk, Gain, Copiers)
|
|
12
9
|
*/
|
|
13
10
|
const { Computation } = require('../framework');
|
|
14
11
|
|
|
@@ -16,7 +13,7 @@ class PiFeatureVectors extends Computation {
|
|
|
16
13
|
static getConfig() {
|
|
17
14
|
return {
|
|
18
15
|
name: 'PiFeatureVectors',
|
|
19
|
-
description: 'Computes feature vectors for all PIs
|
|
16
|
+
description: 'Computes signed feature vectors for all PIs (Net Exposure)',
|
|
20
17
|
type: 'global',
|
|
21
18
|
category: 'popular_investor',
|
|
22
19
|
isHistorical: false,
|
|
@@ -36,10 +33,10 @@ class PiFeatureVectors extends Computation {
|
|
|
36
33
|
'pi_master_list': {
|
|
37
34
|
lookback: 1,
|
|
38
35
|
mandatory: false,
|
|
39
|
-
fields: ['cid', 'username', '
|
|
36
|
+
fields: ['cid', 'username', 'last_updated']
|
|
40
37
|
},
|
|
41
|
-
'ticker_mappings': { mandatory: false },
|
|
42
|
-
'sector_mappings': { mandatory: false }
|
|
38
|
+
'ticker_mappings': { lookback: 0, mandatory: false, fields: ['instrument_id', 'ticker'] },
|
|
39
|
+
'sector_mappings': { lookback: 0, mandatory: false, fields: ['symbol', 'sector'] }
|
|
43
40
|
},
|
|
44
41
|
|
|
45
42
|
storage: {
|
|
@@ -53,16 +50,10 @@ class PiFeatureVectors extends Computation {
|
|
|
53
50
|
const { data, rules, targetDate } = context;
|
|
54
51
|
|
|
55
52
|
// ===========================================================================
|
|
56
|
-
// SETUP:
|
|
53
|
+
// SETUP: Mappings
|
|
57
54
|
// ===========================================================================
|
|
58
|
-
const toArray = (input) =>
|
|
59
|
-
if (!input) return [];
|
|
60
|
-
if (Array.isArray(input)) return input;
|
|
61
|
-
// Handle Map-like structure from DataFetcher
|
|
62
|
-
return Object.values(input).flat();
|
|
63
|
-
};
|
|
55
|
+
const toArray = (input) => Array.isArray(input) ? input : Object.values(input).flat();
|
|
64
56
|
|
|
65
|
-
// Build sector lookup
|
|
66
57
|
const tickerMap = new Map();
|
|
67
58
|
toArray(data['ticker_mappings']).forEach(row => {
|
|
68
59
|
if (row.instrument_id && row.ticker) {
|
|
@@ -82,7 +73,6 @@ class PiFeatureVectors extends Computation {
|
|
|
82
73
|
return ticker ? (sectorMap.get(ticker) || 'Other') : 'Other';
|
|
83
74
|
};
|
|
84
75
|
|
|
85
|
-
// All known sectors for consistent vector dimensions
|
|
86
76
|
const ALL_SECTORS = [
|
|
87
77
|
'Technology', 'Healthcare', 'Financial Services', 'Consumer Cyclical',
|
|
88
78
|
'Industrials', 'Energy', 'Utilities', 'Real Estate', 'Communication Services',
|
|
@@ -90,28 +80,22 @@ class PiFeatureVectors extends Computation {
|
|
|
90
80
|
];
|
|
91
81
|
|
|
92
82
|
// ===========================================================================
|
|
93
|
-
// GATHER
|
|
83
|
+
// GATHER DATA
|
|
94
84
|
// ===========================================================================
|
|
95
85
|
const rankings = toArray(data['pi_rankings']);
|
|
96
86
|
const portfolios = data['portfolio_snapshots']; // Map { piId: [rows] }
|
|
97
87
|
const masterList = toArray(data['pi_master_list']);
|
|
98
88
|
|
|
99
|
-
// Build username lookup
|
|
100
89
|
const usernameMap = new Map();
|
|
101
|
-
rankings.forEach(r =>
|
|
102
|
-
const id = String(r.pi_id || r.CustomerId);
|
|
103
|
-
usernameMap.set(id, r.username || r.UserName);
|
|
104
|
-
});
|
|
90
|
+
rankings.forEach(r => usernameMap.set(String(r.pi_id || r.CustomerId), r.username || r.UserName));
|
|
105
91
|
masterList.forEach(m => {
|
|
106
92
|
if (m.cid && m.username) usernameMap.set(String(m.cid), m.username);
|
|
107
93
|
});
|
|
108
94
|
|
|
109
95
|
// ===========================================================================
|
|
110
|
-
// COMPUTE
|
|
96
|
+
// COMPUTE VECTORS
|
|
111
97
|
// ===========================================================================
|
|
112
98
|
const featureVectors = {};
|
|
113
|
-
|
|
114
|
-
// Process each PI's portfolio
|
|
115
99
|
const piIds = Object.keys(portfolios || {});
|
|
116
100
|
|
|
117
101
|
for (const piId of piIds) {
|
|
@@ -119,55 +103,37 @@ class PiFeatureVectors extends Computation {
|
|
|
119
103
|
const latestRow = rows[rows.length - 1];
|
|
120
104
|
if (!latestRow) continue;
|
|
121
105
|
|
|
122
|
-
// Extract positions
|
|
123
106
|
const pData = rules.portfolio.extractPortfolioData(latestRow);
|
|
124
107
|
const positions = rules.portfolio.extractPositions(pData);
|
|
125
108
|
|
|
126
109
|
if (positions.length === 0) continue;
|
|
127
110
|
|
|
128
|
-
// Calculate
|
|
129
|
-
|
|
130
|
-
|
|
111
|
+
// 1. Calculate Signed Sector Exposure (Net Exposure)
|
|
112
|
+
// Value is % of Total Portfolio Value.
|
|
113
|
+
const sectorNetValue = {};
|
|
114
|
+
let totalGrossExposure = 0; // Sum of abs(value)
|
|
131
115
|
|
|
132
116
|
positions.forEach(pos => {
|
|
133
117
|
const instrumentId = rules.portfolio.getInstrumentId(pos);
|
|
134
|
-
const
|
|
118
|
+
const value = rules.portfolio.getValue(pos) || 0; // Already a %
|
|
135
119
|
const sector = resolveSector(instrumentId);
|
|
120
|
+
const isShort = rules.portfolio.isShort(pos);
|
|
121
|
+
|
|
122
|
+
// Signed Value: Long is +, Short is -
|
|
123
|
+
const signedValue = isShort ? -value : value;
|
|
136
124
|
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
sectorNetValue[sector] = (sectorNetValue[sector] || 0) + signedValue;
|
|
126
|
+
totalGrossExposure += value; // Utilization of equity
|
|
139
127
|
});
|
|
140
128
|
|
|
141
|
-
//
|
|
129
|
+
// Populate vector with Net Exposure % (e.g., -15.5 for Short Tech)
|
|
142
130
|
const sectorVector = {};
|
|
143
131
|
ALL_SECTORS.forEach(s => {
|
|
144
|
-
sectorVector[s] =
|
|
145
|
-
? Number(((sectorWeights[s] || 0) / totalValue).toFixed(4))
|
|
146
|
-
: 0;
|
|
132
|
+
sectorVector[s] = Number((sectorNetValue[s] || 0).toFixed(4));
|
|
147
133
|
});
|
|
148
134
|
|
|
149
|
-
//
|
|
150
|
-
if (piIds.indexOf(piId) === 0) {
|
|
151
|
-
const availableIds = rankings.map(r => r.pi_id || 'undefined').join(',');
|
|
152
|
-
console.log(`[DEBUG] Available IDs in rankings: ${availableIds}`);
|
|
153
|
-
console.log(`[DEBUG] First ranking row keys: ${Object.keys(rankings[0] || {}).join(',')}`);
|
|
154
|
-
if (rankings[0]) console.log(`[DEBUG] First ranking row pi_id: ${rankings[0].pi_id}`);
|
|
155
|
-
}
|
|
135
|
+
// 2. Extract Quality Metrics
|
|
156
136
|
const rankRow = rankings.find(r => String(r.pi_id || r.CustomerId) === String(piId));
|
|
157
|
-
|
|
158
|
-
if (piIds.indexOf(piId) === 0) {
|
|
159
|
-
console.log(`[DEBUG] PiId: ${piId}`);
|
|
160
|
-
console.log(`[DEBUG] RankRow found: ${!!rankRow}`);
|
|
161
|
-
if (rankRow) {
|
|
162
|
-
console.log(`[DEBUG] RankRow keys: ${Object.keys(rankRow).join(',')}`);
|
|
163
|
-
console.log(`[DEBUG] RankingsData type: ${typeof rankRow.rankings_data}`);
|
|
164
|
-
if (typeof rankRow.rankings_data === 'object') {
|
|
165
|
-
console.log(`[DEBUG] RankingsData keys: ${Object.keys(rankRow.rankings_data).join(',')}`);
|
|
166
|
-
console.log(`[DEBUG] RankingsData.Gain: ${rankRow.rankings_data.Gain}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
137
|
let riskScore = 5, gain = 0, copiers = 0, winRatio = 0, aumTier = 0;
|
|
172
138
|
|
|
173
139
|
if (rankRow) {
|
|
@@ -181,40 +147,28 @@ class PiFeatureVectors extends Computation {
|
|
|
181
147
|
}
|
|
182
148
|
}
|
|
183
149
|
|
|
184
|
-
//
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
150
|
+
// Concentration (HHI based on gross exposure per sector)
|
|
151
|
+
const grossWeights = Object.values(sectorNetValue).map(Math.abs);
|
|
152
|
+
const sumGross = grossWeights.reduce((a, b) => a + b, 0);
|
|
153
|
+
const hhi = sumGross > 0
|
|
154
|
+
? grossWeights.reduce((sum, w) => sum + Math.pow(w / sumGross, 2), 0)
|
|
188
155
|
: 1;
|
|
189
156
|
|
|
190
|
-
// Assemble feature vector
|
|
191
157
|
featureVectors[piId] = {
|
|
192
158
|
piId: String(piId),
|
|
193
159
|
username: usernameMap.get(String(piId)) || 'Unknown',
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// Performance
|
|
203
|
-
gain: gain,
|
|
204
|
-
winRatio: winRatio,
|
|
205
|
-
|
|
206
|
-
// Quality
|
|
207
|
-
copiers: copiers,
|
|
208
|
-
aumTier: aumTier,
|
|
209
|
-
positionCount: positions.length,
|
|
210
|
-
|
|
211
|
-
// Metadata
|
|
212
|
-
totalValue: Number(totalValue.toFixed(2)),
|
|
160
|
+
sectors: sectorVector, // Signed Net Exposure
|
|
161
|
+
riskScore,
|
|
162
|
+
concentration: Number(hhi.toFixed(4)),
|
|
163
|
+
gain,
|
|
164
|
+
winRatio,
|
|
165
|
+
copiers,
|
|
166
|
+
aumTier,
|
|
167
|
+
grossExposure: Number(totalGrossExposure.toFixed(2)),
|
|
213
168
|
computedAt: targetDate
|
|
214
169
|
};
|
|
215
170
|
}
|
|
216
171
|
|
|
217
|
-
// Store all vectors as a single global result
|
|
218
172
|
this.setResult('_global', {
|
|
219
173
|
vectors: featureVectors,
|
|
220
174
|
piCount: Object.keys(featureVectors).length,
|
|
@@ -224,4 +178,4 @@ class PiFeatureVectors extends Computation {
|
|
|
224
178
|
}
|
|
225
179
|
}
|
|
226
180
|
|
|
227
|
-
module.exports = PiFeatureVectors;
|
|
181
|
+
module.exports = PiFeatureVectors;
|