bulltrackers-module 1.0.654 → 1.0.655
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.
|
@@ -1222,7 +1222,7 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
|
|
|
1222
1222
|
const shardCount = pointerData._shardCount || 0;
|
|
1223
1223
|
console.log(`[Computation] Reassembling ${shardCount} shards for ${computationName}`);
|
|
1224
1224
|
|
|
1225
|
-
if (shardCount === 0) return
|
|
1225
|
+
if (shardCount === 0) return {}; // Return empty object for object-based results
|
|
1226
1226
|
|
|
1227
1227
|
// Create an array of promises to fetch all shards in parallel
|
|
1228
1228
|
const shardPromises = [];
|
|
@@ -1233,10 +1233,9 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
|
|
|
1233
1233
|
|
|
1234
1234
|
const shardSnaps = await Promise.all(shardPromises);
|
|
1235
1235
|
|
|
1236
|
-
// Reassemble data
|
|
1237
|
-
//
|
|
1238
|
-
|
|
1239
|
-
let reassembledData = [];
|
|
1236
|
+
// Reassemble data by merging objects (for object-based results like GlobalAumPerAsset30D)
|
|
1237
|
+
// Shards contain partial objects that need to be merged together
|
|
1238
|
+
let reassembledData = {};
|
|
1240
1239
|
|
|
1241
1240
|
shardSnaps.forEach((snap, index) => {
|
|
1242
1241
|
if (snap.exists) {
|
|
@@ -1244,9 +1243,14 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
|
|
|
1244
1243
|
// If the shard itself is compressed (common in big data), decompress it
|
|
1245
1244
|
const content = (data._compressed) ? tryDecompress(data) : data;
|
|
1246
1245
|
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
|
|
1246
|
+
// Merge shard contents, ignoring internal metadata fields
|
|
1247
|
+
if (content && typeof content === 'object') {
|
|
1248
|
+
Object.entries(content).forEach(([k, v]) => {
|
|
1249
|
+
if (!k.startsWith('_')) {
|
|
1250
|
+
reassembledData[k] = v;
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1250
1254
|
} else {
|
|
1251
1255
|
console.warn(`[Computation] Missing shard_${index} for ${computationName}`);
|
|
1252
1256
|
}
|
|
@@ -1273,6 +1277,62 @@ const getComputationResults = async (db, computationName, dateStr, userId = null
|
|
|
1273
1277
|
}
|
|
1274
1278
|
};
|
|
1275
1279
|
|
|
1280
|
+
// 10.5. Fetch GlobalAumPerAsset30D with 7-day lookback
|
|
1281
|
+
// Returns the last 7 days of successfully stored data for a specific ticker or all assets
|
|
1282
|
+
const fetchGlobalAumPerAssetWithLookback = async (db, dateStr, ticker = null, lookbackDays = 7) => {
|
|
1283
|
+
try {
|
|
1284
|
+
const endDate = new Date(dateStr);
|
|
1285
|
+
const results = [];
|
|
1286
|
+
|
|
1287
|
+
// Fetch data for the last 7 days, handling missing data gracefully
|
|
1288
|
+
for (let i = 0; i < lookbackDays; i++) {
|
|
1289
|
+
const checkDate = new Date(endDate);
|
|
1290
|
+
checkDate.setDate(endDate.getDate() - i);
|
|
1291
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
1292
|
+
|
|
1293
|
+
try {
|
|
1294
|
+
const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', dateKey);
|
|
1295
|
+
|
|
1296
|
+
// If we got valid data (not null/empty), add it to results
|
|
1297
|
+
if (computationData && typeof computationData === 'object' && Object.keys(computationData).length > 0) {
|
|
1298
|
+
if (ticker) {
|
|
1299
|
+
// If searching for a specific ticker, only include if it exists
|
|
1300
|
+
const tickerUpper = ticker.toUpperCase();
|
|
1301
|
+
if (computationData[tickerUpper] !== undefined) {
|
|
1302
|
+
results.push({
|
|
1303
|
+
date: dateKey,
|
|
1304
|
+
aum: computationData[tickerUpper],
|
|
1305
|
+
ticker: tickerUpper
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
} else {
|
|
1309
|
+
// Return all assets for this date
|
|
1310
|
+
results.push({
|
|
1311
|
+
date: dateKey,
|
|
1312
|
+
data: computationData
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
// Missing data for this date - skip it (graceful handling)
|
|
1318
|
+
console.log(`[GlobalAum] No data found for ${dateKey}, skipping`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Sort by date descending (newest first)
|
|
1323
|
+
results.sort((a, b) => {
|
|
1324
|
+
const dateA = new Date(a.date);
|
|
1325
|
+
const dateB = new Date(b.date);
|
|
1326
|
+
return dateB.getTime() - dateA.getTime();
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
return results;
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
console.error(`[GlobalAum] Error fetching with lookback:`, error);
|
|
1332
|
+
throw error;
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
|
|
1276
1336
|
// 11. Fetch User Notifications
|
|
1277
1337
|
const fetchNotifications = async (firestore, userId, options = {}) => {
|
|
1278
1338
|
const { limit = 20, unreadOnly = false, excludeTypes = ['watchlistAlerts'] } = options;
|
|
@@ -2913,6 +2973,8 @@ const subscribeToAllWatchlistPIs = async (db, userId, watchlistId, alertTypes =
|
|
|
2913
2973
|
module.exports = {
|
|
2914
2974
|
latestUserCentricSnapshot,
|
|
2915
2975
|
pageCollection,
|
|
2976
|
+
fetchGlobalAumPerAssetWithLookback,
|
|
2977
|
+
getComputationResults,
|
|
2916
2978
|
fetchPopularInvestorMasterList,
|
|
2917
2979
|
isDeveloper,
|
|
2918
2980
|
lookupCidByEmail,
|
|
@@ -8,10 +8,12 @@ const {
|
|
|
8
8
|
getComputationResults,
|
|
9
9
|
requestPopularInvestorAddition,
|
|
10
10
|
trackPopularInvestorView,
|
|
11
|
-
pageCollection
|
|
11
|
+
pageCollection,
|
|
12
|
+
fetchGlobalAumPerAssetWithLookback
|
|
12
13
|
} = require('../helpers/data-fetchers/firestore.js');
|
|
13
14
|
const { sanitizeCid } = require('../helpers/security_utils.js');
|
|
14
15
|
const { timeouts } = require('../helpers/timeout_utils.js');
|
|
16
|
+
const { requireFirebaseAuth } = require('../middleware/firebase_auth_middleware.js');
|
|
15
17
|
|
|
16
18
|
const router = express.Router();
|
|
17
19
|
|
|
@@ -255,5 +257,93 @@ router.get('/:piId/analytics', async (req, res) => {
|
|
|
255
257
|
}
|
|
256
258
|
});
|
|
257
259
|
|
|
260
|
+
/**
|
|
261
|
+
* GLOBAL AUM PER ASSET - Public for all signed-in users
|
|
262
|
+
*
|
|
263
|
+
* These routes show how much $ is invested in each asset based on AUM totals of all Popular Investors.
|
|
264
|
+
* - Uses GlobalAumPerAsset30D computation
|
|
265
|
+
* - Access: Public (any signed-in user can view)
|
|
266
|
+
* - Requires Firebase authentication
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
// GET /popular-investors/global-aum - Get top assets by AUM (default: top 10)
|
|
270
|
+
router.get('/global-aum', requireFirebaseAuth, async (req, res, next) => {
|
|
271
|
+
try {
|
|
272
|
+
const { db } = req.dependencies;
|
|
273
|
+
const { date, limit = 10 } = req.query;
|
|
274
|
+
|
|
275
|
+
// Default to today if no date provided
|
|
276
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
277
|
+
|
|
278
|
+
// Fetch the latest computation result
|
|
279
|
+
const computationData = await getComputationResults(db, 'GlobalAumPerAsset30D', targetDate);
|
|
280
|
+
|
|
281
|
+
if (!computationData || typeof computationData !== 'object' || Object.keys(computationData).length === 0) {
|
|
282
|
+
return res.status(404).json({
|
|
283
|
+
success: false,
|
|
284
|
+
error: 'No AUM data available for the specified date'
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Convert to array and sort by AUM (descending)
|
|
289
|
+
const assetsArray = Object.entries(computationData)
|
|
290
|
+
.map(([ticker, aum]) => ({ ticker, aum: Number(aum) }))
|
|
291
|
+
.sort((a, b) => b.aum - a.aum)
|
|
292
|
+
.slice(0, parseInt(limit) || 10);
|
|
293
|
+
|
|
294
|
+
res.json({
|
|
295
|
+
success: true,
|
|
296
|
+
date: targetDate,
|
|
297
|
+
count: assetsArray.length,
|
|
298
|
+
data: assetsArray
|
|
299
|
+
});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
next(error);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// GET /popular-investors/global-aum/:ticker - Get 7-day AUM history for a specific ticker
|
|
306
|
+
router.get('/global-aum/:ticker', requireFirebaseAuth, async (req, res, next) => {
|
|
307
|
+
try {
|
|
308
|
+
const { db } = req.dependencies;
|
|
309
|
+
const { ticker } = req.params;
|
|
310
|
+
const { date, lookback = 7 } = req.query;
|
|
311
|
+
|
|
312
|
+
// Validate ticker
|
|
313
|
+
if (!ticker || typeof ticker !== 'string' || ticker.length === 0) {
|
|
314
|
+
return res.status(400).json({
|
|
315
|
+
success: false,
|
|
316
|
+
error: 'Invalid ticker symbol'
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Default to today if no date provided
|
|
321
|
+
const targetDate = date || new Date().toISOString().split('T')[0];
|
|
322
|
+
|
|
323
|
+
// Validate lookback parameter
|
|
324
|
+
const lookbackDays = Math.min(Math.max(parseInt(lookback) || 7, 1), 30); // Between 1 and 30 days
|
|
325
|
+
|
|
326
|
+
// Fetch 7-day lookback data for the ticker
|
|
327
|
+
const results = await fetchGlobalAumPerAssetWithLookback(db, targetDate, ticker, lookbackDays);
|
|
328
|
+
|
|
329
|
+
if (results.length === 0) {
|
|
330
|
+
return res.status(404).json({
|
|
331
|
+
success: false,
|
|
332
|
+
error: `No AUM data found for ticker ${ticker.toUpperCase()} in the last ${lookbackDays} days`
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
res.json({
|
|
337
|
+
success: true,
|
|
338
|
+
ticker: ticker.toUpperCase(),
|
|
339
|
+
lookbackDays: lookbackDays,
|
|
340
|
+
count: results.length,
|
|
341
|
+
data: results
|
|
342
|
+
});
|
|
343
|
+
} catch (error) {
|
|
344
|
+
next(error);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
258
348
|
|
|
259
349
|
module.exports = router;
|