bulltrackers-module 1.0.800 → 1.0.802
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/alert-system-v3/handlers/ingest.js +3 -1
- package/functions/computation-system-v3/computations/GlobalAumPerAsset30D.js +4 -5
- package/functions/computation-system-v3/computations/PIDailyAssetAUM.js +1 -4
- package/functions/computation-system-v3/computations/PopularInvestorProfileMetrics.js +24 -23
- package/functions/computation-system-v3/computations/SignedInUserProfileMetrics.js +19 -18
- package/functions/computation-system-v3/framework/adapters/StateRepository.js +3 -1
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ async function handleComputationResultWrite(change, context, config, dependencie
|
|
|
16
16
|
|
|
17
17
|
if (!isAlertComputation(cachedAlertTypes, computationName)) return;
|
|
18
18
|
|
|
19
|
+
console.log("The computations assigned to on document write are," + computationName)
|
|
20
|
+
|
|
19
21
|
const payload = {
|
|
20
22
|
date,
|
|
21
23
|
computationName,
|
|
@@ -32,4 +34,4 @@ async function handleComputationResultWrite(change, context, config, dependencie
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
module.exports = { handleComputationResultWrite };
|
|
37
|
+
module.exports = { handleComputationResultWrite };
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const config = {
|
|
7
|
-
name: 'GlobalAumPerAsset30D',
|
|
7
|
+
name: 'GlobalAumPerAsset30D',
|
|
8
8
|
type: 'global',
|
|
9
9
|
category: 'market_insights',
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ const config = {
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
|
|
22
|
-
dependencies: ['pidailyassetaum'],
|
|
22
|
+
dependencies: ['pidailyassetaum'],
|
|
23
23
|
|
|
24
24
|
storage: {
|
|
25
25
|
bigquery: true
|
|
@@ -32,11 +32,11 @@ async function process(context) {
|
|
|
32
32
|
let rows = data['results'];
|
|
33
33
|
if (!rows || (Array.isArray(rows) && rows.length === 0)) {
|
|
34
34
|
if (log) log('[GlobalAumPerAsset] No input data found.');
|
|
35
|
-
return { result: null };
|
|
35
|
+
return { result: null };
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (!Array.isArray(rows) && typeof rows === 'object') {
|
|
39
|
-
rows = Object.values(rows);
|
|
39
|
+
rows = Object.values(rows);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (log) log(`[GlobalAumPerAsset] Aggregating ${rows.length} PI portfolios...`);
|
|
@@ -77,7 +77,6 @@ async function process(context) {
|
|
|
77
77
|
|
|
78
78
|
if (log) log(`[GlobalAumPerAsset] Generated stats for ${materializedViewData.length} unique assets from ${piCount} PIs.`);
|
|
79
79
|
|
|
80
|
-
// V3 expects return value, not this.setResult
|
|
81
80
|
return {
|
|
82
81
|
status: 'completed',
|
|
83
82
|
result: materializedViewData
|
|
@@ -24,7 +24,6 @@ const config = {
|
|
|
24
24
|
'ticker_mappings': {
|
|
25
25
|
mandatory: false,
|
|
26
26
|
fields: ['instrument_id', 'ticker'],
|
|
27
|
-
// [CRITICAL] Disable batch filtering to get global map
|
|
28
27
|
entityField: null
|
|
29
28
|
},
|
|
30
29
|
'pi_master_list': {
|
|
@@ -53,7 +52,6 @@ async function process(context) {
|
|
|
53
52
|
};
|
|
54
53
|
|
|
55
54
|
// --- HELPER: V3 Data Normalizer ---
|
|
56
|
-
// Handles difference between Batch (keyed map) and Global (array) returns
|
|
57
55
|
const getEntityRows = (dataset) => {
|
|
58
56
|
if (!dataset) return [];
|
|
59
57
|
if (dataset[entityId]) {
|
|
@@ -107,7 +105,6 @@ async function process(context) {
|
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
// 5. Calculation using V3 Libs
|
|
110
|
-
// Uses lib.portfolio.* and lib.rankings.* instead of V2 rules
|
|
111
108
|
const pData = portfolio.extractPortfolioData(validPortfolio);
|
|
112
109
|
const rData = rankings.extractRankingsData(validRanking);
|
|
113
110
|
|
|
@@ -123,7 +120,7 @@ async function process(context) {
|
|
|
123
120
|
positions.forEach(pos => {
|
|
124
121
|
const id = portfolio.getInstrumentId(pos);
|
|
125
122
|
const ticker = resolveTicker(id);
|
|
126
|
-
const weight = portfolio.getInvested(pos);
|
|
123
|
+
const weight = portfolio.getInvested(pos);
|
|
127
124
|
|
|
128
125
|
if (weight > 0) {
|
|
129
126
|
const dollarValue = (weight / 100) * totalAum;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @fileoverview Popular Investor Profile Metrics
|
|
3
3
|
* Migrated to V3 System.
|
|
4
4
|
* FIXED: Corrected function call 'getTradesCount' and 'getTotalTrades' typo.
|
|
5
|
+
* OPTIMIZED: Uses 1-day lookback for trade history (rolling window) to prevent cost violations.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
/** @typedef {import('../framework/core/ContextBuilder').ComputationContext} ComputationContext */
|
|
@@ -20,7 +21,7 @@ exports.config = {
|
|
|
20
21
|
fields: ['user_id', 'portfolio_data', 'date']
|
|
21
22
|
},
|
|
22
23
|
'trade_history_snapshots': {
|
|
23
|
-
lookback:
|
|
24
|
+
lookback: 1, // OPTIMIZED: Rolling history contained in single snapshot
|
|
24
25
|
mandatory: false,
|
|
25
26
|
fields: ['user_id', 'history_data', 'date']
|
|
26
27
|
},
|
|
@@ -221,38 +222,38 @@ exports.process = (ctx) => {
|
|
|
221
222
|
gain: lib.rankings.getTotalGain(rData),
|
|
222
223
|
copiers: lib.rankings.getCopiers(rData),
|
|
223
224
|
winRatio: lib.rankings.getWinRatio(rData),
|
|
224
|
-
// [FIX] Changed from getTotalTrades to getTradesCount
|
|
225
225
|
trades: lib.rankings.getTradesCount(rData)
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// ==========================================================================================
|
|
231
|
-
// 4. TRADES
|
|
231
|
+
// 4. TRADES (OPTIMIZED)
|
|
232
232
|
// ==========================================================================================
|
|
233
233
|
const profitableMap = new Map();
|
|
234
234
|
const allClosedTrades = [];
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
236
|
+
// Only process the latest history snapshot as it contains the rolling history
|
|
237
|
+
const latestHistory = historyData.length > 0 ? historyData[historyData.length - 1] : null;
|
|
238
|
+
const trades = latestHistory ? lib.trades.extractTrades(latestHistory) : [];
|
|
239
|
+
|
|
240
|
+
trades.forEach(trade => {
|
|
241
|
+
const closeDate = lib.trades.getCloseDate(trade);
|
|
242
|
+
if (!closeDate) return;
|
|
243
|
+
|
|
244
|
+
const dKey = closeDate.toISOString().split('T')[0];
|
|
245
|
+
const profit = lib.trades.getNetProfit(trade);
|
|
246
|
+
|
|
247
|
+
const entry = profitableMap.get(dKey) || { date: dKey, profitableCount: 0, totalCount: 0 };
|
|
248
|
+
entry.totalCount++;
|
|
249
|
+
if (profit > 0) entry.profitableCount++;
|
|
250
|
+
profitableMap.set(dKey, entry);
|
|
251
|
+
|
|
252
|
+
allClosedTrades.push({
|
|
253
|
+
ticker: resolveTicker(lib.trades.getInstrumentId(trade)),
|
|
254
|
+
closeDate: closeDate,
|
|
255
|
+
netProfit: profit,
|
|
256
|
+
direction: lib.trades.isBuy(trade) ? 'Buy' : 'Sell'
|
|
256
257
|
});
|
|
257
258
|
});
|
|
258
259
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @fileoverview Signed-In User Profile Metrics (V3 Recipe)
|
|
3
3
|
* Migrated from V2 computation.
|
|
4
4
|
* FIXED: Explicit fields, global lookups, and PI exclusion logic.
|
|
5
|
+
* OPTIMIZED: Uses 1-day lookback for trade history (rolling window) to prevent cost violations.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
/** @typedef {import('../framework/core/ContextBuilder').ComputationContext} ComputationContext */
|
|
@@ -22,7 +23,7 @@ exports.config = {
|
|
|
22
23
|
filter: { user_type: 'SIGNED_IN_USER' }
|
|
23
24
|
},
|
|
24
25
|
'trade_history_snapshots': {
|
|
25
|
-
lookback:
|
|
26
|
+
lookback: 1, // OPTIMIZED: Rolling history contained in single snapshot
|
|
26
27
|
fields: ['user_id', 'history_data', 'date'],
|
|
27
28
|
filter: { user_type: 'SIGNED_IN_USER' }
|
|
28
29
|
},
|
|
@@ -95,7 +96,6 @@ exports.process = (ctx) => {
|
|
|
95
96
|
const piMasterList = toArray(data['pi_master_list']);
|
|
96
97
|
|
|
97
98
|
// Check if the current entity is actually a PI (Source of Truth: Master List)
|
|
98
|
-
// This handles cases where user_type might be incorrect in snapshots
|
|
99
99
|
const isPopularInvestor = piMasterList.some(pi => String(pi.cid) === String(entityId));
|
|
100
100
|
|
|
101
101
|
if (isPopularInvestor) {
|
|
@@ -185,30 +185,31 @@ exports.process = (ctx) => {
|
|
|
185
185
|
});
|
|
186
186
|
result.myPosts.data = socialFeed.sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 20);
|
|
187
187
|
|
|
188
|
-
// --- 7. Trades Processing ---
|
|
188
|
+
// --- 7. Trades Processing (OPTIMIZED) ---
|
|
189
189
|
const dailyPnL = new Map();
|
|
190
190
|
const winLoss = { wins: 0, losses: 0, profit: 0, loss: 0 };
|
|
191
191
|
const tradeStats = new Map();
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const closeDate = lib.trades.getCloseDate(t);
|
|
197
|
-
if (!closeDate) return;
|
|
193
|
+
// Only process the latest history snapshot as it contains the rolling history
|
|
194
|
+
const latestHistory = historyData.length > 0 ? historyData[historyData.length - 1] : null;
|
|
195
|
+
const trades = latestHistory ? lib.trades.extractTrades(latestHistory) : [];
|
|
198
196
|
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
trades.forEach(t => {
|
|
198
|
+
const closeDate = lib.trades.getCloseDate(t);
|
|
199
|
+
if (!closeDate) return;
|
|
201
200
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (profit > 0) entry.profitableCount++;
|
|
205
|
-
tradeStats.set(dKey, entry);
|
|
201
|
+
const dKey = closeDate.toISOString().split('T')[0];
|
|
202
|
+
const profit = lib.trades.getNetProfit(t);
|
|
206
203
|
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
const entry = tradeStats.get(dKey) || { date: dKey, profitableCount: 0, totalCount: 0 };
|
|
205
|
+
entry.totalCount++;
|
|
206
|
+
if (profit > 0) entry.profitableCount++;
|
|
207
|
+
tradeStats.set(dKey, entry);
|
|
209
208
|
|
|
210
|
-
|
|
211
|
-
|
|
209
|
+
if (profit > 0) { winLoss.wins++; winLoss.profit += profit; }
|
|
210
|
+
else if (profit < 0) { winLoss.losses++; winLoss.loss += profit; }
|
|
211
|
+
|
|
212
|
+
dailyPnL.set(dKey, (dailyPnL.get(dKey) || 0) + profit);
|
|
212
213
|
});
|
|
213
214
|
|
|
214
215
|
result.profitablePositions.data = Array.from(tradeStats.values())
|
|
@@ -161,7 +161,9 @@ class StateRepository {
|
|
|
161
161
|
|
|
162
162
|
return { completedBatches: snapshot.data().count };
|
|
163
163
|
} catch (e) {
|
|
164
|
-
|
|
164
|
+
// [FIX] Log the error!
|
|
165
|
+
this.logger.error(`[StateRepo] getCheckpointProgress failed for ${checkpointId}: ${e.message}`);
|
|
166
|
+
return { completedBatches: 0 };
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|