bulltrackers-module 1.0.731 → 1.0.733

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.
Files changed (52) hide show
  1. package/functions/orchestrator/index.js +19 -17
  2. package/index.js +8 -29
  3. package/package.json +6 -5
  4. package/functions/computation-system/WorkflowOrchestrator.js +0 -213
  5. package/functions/computation-system/config/monitoring_config.js +0 -31
  6. package/functions/computation-system/config/validation_overrides.js +0 -10
  7. package/functions/computation-system/context/ContextFactory.js +0 -132
  8. package/functions/computation-system/context/ManifestBuilder.js +0 -379
  9. package/functions/computation-system/data/AvailabilityChecker.js +0 -236
  10. package/functions/computation-system/data/CachedDataLoader.js +0 -325
  11. package/functions/computation-system/data/DependencyFetcher.js +0 -455
  12. package/functions/computation-system/executors/MetaExecutor.js +0 -279
  13. package/functions/computation-system/executors/PriceBatchExecutor.js +0 -108
  14. package/functions/computation-system/executors/StandardExecutor.js +0 -465
  15. package/functions/computation-system/helpers/computation_dispatcher.js +0 -750
  16. package/functions/computation-system/helpers/computation_worker.js +0 -375
  17. package/functions/computation-system/helpers/monitor.js +0 -64
  18. package/functions/computation-system/helpers/on_demand_helpers.js +0 -154
  19. package/functions/computation-system/layers/extractors.js +0 -1097
  20. package/functions/computation-system/layers/index.js +0 -40
  21. package/functions/computation-system/layers/mathematics.js +0 -522
  22. package/functions/computation-system/layers/profiling.js +0 -537
  23. package/functions/computation-system/layers/validators.js +0 -170
  24. package/functions/computation-system/legacy/AvailabilityCheckerOld.js +0 -388
  25. package/functions/computation-system/legacy/CachedDataLoaderOld.js +0 -357
  26. package/functions/computation-system/legacy/DependencyFetcherOld.js +0 -478
  27. package/functions/computation-system/legacy/MetaExecutorold.js +0 -364
  28. package/functions/computation-system/legacy/StandardExecutorold.js +0 -476
  29. package/functions/computation-system/legacy/computation_dispatcherold.js +0 -944
  30. package/functions/computation-system/logger/logger.js +0 -297
  31. package/functions/computation-system/persistence/ContractValidator.js +0 -81
  32. package/functions/computation-system/persistence/FirestoreUtils.js +0 -56
  33. package/functions/computation-system/persistence/ResultCommitter.js +0 -283
  34. package/functions/computation-system/persistence/ResultsValidator.js +0 -130
  35. package/functions/computation-system/persistence/RunRecorder.js +0 -142
  36. package/functions/computation-system/persistence/StatusRepository.js +0 -52
  37. package/functions/computation-system/reporter_epoch.js +0 -6
  38. package/functions/computation-system/scripts/UpdateContracts.js +0 -128
  39. package/functions/computation-system/services/SnapshotService.js +0 -148
  40. package/functions/computation-system/simulation/Fabricator.js +0 -285
  41. package/functions/computation-system/simulation/SeededRandom.js +0 -41
  42. package/functions/computation-system/simulation/SimRunner.js +0 -51
  43. package/functions/computation-system/system_epoch.js +0 -2
  44. package/functions/computation-system/tools/BuildReporter.js +0 -531
  45. package/functions/computation-system/tools/ContractDiscoverer.js +0 -144
  46. package/functions/computation-system/tools/DeploymentValidator.js +0 -536
  47. package/functions/computation-system/tools/FinalSweepReporter.js +0 -322
  48. package/functions/computation-system/topology/HashManager.js +0 -55
  49. package/functions/computation-system/topology/ManifestLoader.js +0 -47
  50. package/functions/computation-system/utils/data_loader.js +0 -597
  51. package/functions/computation-system/utils/schema_capture.js +0 -121
  52. package/functions/computation-system/utils/utils.js +0 -188
@@ -1,1097 +0,0 @@
1
- /**
2
- * @fileoverview Extractors Layer
3
- * Core access methods to raw data schemas (Social, Portfolio, History, Rankings, Verification).
4
- * UPDATED: Aligned with corrected schemas for PublicHistoryPositions and Deep PublicPositions.
5
- */
6
-
7
- const { SCHEMAS } = require('./profiling');
8
-
9
- class TradeSeriesBuilder {
10
- static buildReturnSeries(historyTrades) {
11
- if (!historyTrades || !Array.isArray(historyTrades)) return [];
12
- // Sort by Close Date Ascending
13
- const closedTrades = [...historyTrades].sort((a, b) => new Date(a.CloseDateTime) - new Date(b.CloseDateTime));
14
- return closedTrades.map(t => t.NetProfit);
15
- }
16
-
17
- static buildCumulativeCurve(returnSeries, startValue = 100) {
18
- const curve = [startValue];
19
- let current = startValue;
20
- for (const ret of returnSeries) {
21
- // Assuming returns are absolute $ values here based on schema "NetProfit": 73.1666
22
- current += ret;
23
- curve.push(current);
24
- }
25
- return curve;
26
- }
27
-
28
- static calculateMaxDrawdown(curve) {
29
- if (!curve || curve.length < 2) return { maxDrawdownPct: 0, peakIndex: 0, troughIndex: 0 };
30
-
31
- let maxDrawdown = 0;
32
- let peakValue = curve[0];
33
- let peakIndex = 0;
34
- let tempPeakIndex = 0;
35
- let troughIndex = 0;
36
-
37
- for (let i = 1; i < curve.length; i++) {
38
- const currentVal = curve[i];
39
-
40
- if (currentVal > peakValue) {
41
- peakValue = currentVal;
42
- tempPeakIndex = i;
43
- } else {
44
- if (peakValue === 0) continue;
45
- const drawdown = (peakValue - currentVal) / peakValue;
46
- if (drawdown > maxDrawdown) {
47
- maxDrawdown = drawdown;
48
- peakIndex = tempPeakIndex;
49
- troughIndex = i;
50
- }
51
- }
52
- }
53
- return { maxDrawdownPct: maxDrawdown * 100, peakIndex, troughIndex };
54
- }
55
- }
56
-
57
-
58
- class SocialExtractor {
59
- static getDiscussions(socialData) {
60
- return socialData?.discussions || [];
61
- }
62
-
63
- static getPostId(discussion) {
64
- return discussion?.post?.id || discussion?.id || null;
65
- }
66
-
67
- static getMessage(discussion) {
68
- return discussion?.post?.message?.text || "";
69
- }
70
-
71
- static getLanguage(discussion) {
72
- return discussion?.post?.message?.languageCode || "en";
73
- }
74
-
75
- static getCreatedDate(discussion) {
76
- const dateStr = discussion?.post?.created;
77
- return dateStr ? new Date(dateStr) : null;
78
- }
79
-
80
- static getOwner(discussion) {
81
- return discussion?.post?.owner || null;
82
- }
83
-
84
- static getOwnerUsername(discussion) {
85
- return discussion?.post?.owner?.username || "Unknown";
86
- }
87
-
88
- static isOwnerVerified(discussion) {
89
- const roles = discussion?.post?.owner?.roles || [];
90
- return roles.includes('Verified') || roles.includes('PopularInvestor');
91
- }
92
-
93
- static getPostContextType(discussion) {
94
- if (discussion?.post?.tags && discussion.post.tags.length > 0) return 'INSTRUMENT_TAGGED';
95
- if (discussion?.reason) return 'USER_TIMELINE';
96
- return 'UNKNOWN';
97
- }
98
-
99
- static getRelatedInstruments(discussion) {
100
- const tags = discussion?.post?.tags || [];
101
- return tags.map(t => ({
102
- id: t.market?.internalId || t.market?.id,
103
- symbol: t.market?.symbolName,
104
- displayName: t.market?.displayName
105
- })).filter(i => i.id);
106
- }
107
-
108
- static getFeedReason(discussion) {
109
- if (!discussion?.reason) return null;
110
- return {
111
- type: discussion.reason.type,
112
- sourceId: discussion.reason.sourceId
113
- };
114
- }
115
-
116
- static getCommentCount(discussion) {
117
- return discussion?.summary?.totalCommentsAndReplies || 0;
118
- }
119
-
120
- static getLikeCount(discussion) {
121
- return discussion?.emotionsData?.like?.paging?.totalCount || 0;
122
- }
123
-
124
- // [NEW] Added for social accuracy calcs
125
- static getQualityScore(discussion) {
126
- return discussion?.aiAnalysis?.qualityScore || 0.5; // Default neutral
127
- }
128
-
129
- static getAccuracyFromAnalysis(discussion) {
130
- return discussion?.aiAnalysis?.accuracyScore || null;
131
- }
132
- }
133
-
134
- class PopularInvestorExtractor {
135
- static getPositions(data) {
136
- // [FIX] Prioritize DeepPositions (merged from deep shard)
137
- if (data && Array.isArray(data.DeepPositions)) {
138
- return data.DeepPositions;
139
- }
140
-
141
- if (data && Array.isArray(data.PublicPositions)) {
142
- return data.PublicPositions;
143
- }
144
-
145
- if (data && Array.isArray(data.AggregatedPositions)) {
146
- return data.AggregatedPositions;
147
- }
148
-
149
- return [];
150
- }
151
-
152
- static getEquity(data) {
153
- return data.Equity || data.CreditByRealizedEquity || 0;
154
- }
155
- }
156
-
157
- class DataExtractor {
158
- static getPositions(portfolio, userType) {
159
- if (!portfolio) return [];
160
-
161
- // Speculators/PIs might have the Deep Schema
162
- if (userType === 'POPULAR_INVESTOR' || userType === SCHEMAS.USER_TYPES.SPECULATOR) {
163
- return PopularInvestorExtractor.getPositions(portfolio);
164
- }
165
-
166
- // Standard/Signed-In Users usually have the Overall Schema
167
- return portfolio.AggregatedPositions || [];
168
- }
169
-
170
- static getInstrumentId(position) {
171
- if (!position) return null;
172
- return position.InstrumentID || position.instrumentId || null;
173
- }
174
-
175
- static getPositionId(position) {
176
- if (!position) return null;
177
- // Deep Schema: PositionID
178
- if (position.PositionID) return String(position.PositionID);
179
-
180
- // Overall Schema: InstrumentID + Direction (Proxy ID)
181
- const instId = this.getInstrumentId(position);
182
- const dir = this.getDirection(position);
183
- if (instId) return `${instId}_${dir}`;
184
- return null;
185
- }
186
-
187
- static getNetProfit(position) {
188
- return position ? (position.NetProfit || 0) : 0;
189
- }
190
-
191
- static getPositionWeight(position) {
192
- if (!position) return 0;
193
- // Deep: Amount / Invested? Schema shows 'Amount' and 'Invested' at top level.
194
- // Inside PublicPositions (Deep), it uses 'Amount'.
195
- // Inside AggregatedPositions (Overall), it uses 'Invested'.
196
- return position.Invested || position.Amount || 0;
197
- }
198
-
199
- static getPositionValue(position) {
200
- return position.Value || 0;
201
- }
202
-
203
- static getDirection(position) {
204
- if (!position) return "Buy";
205
- if (position.Direction) return position.Direction;
206
- if (typeof position.IsBuy === 'boolean') return position.IsBuy ? "Buy" : "Sell";
207
- return "Buy";
208
- }
209
-
210
- static getLeverage(position) { return position ? (position.Leverage || 1) : 1; }
211
- static getOpenRate(position) { return position ? (position.OpenRate || 0) : 0; }
212
- static getCurrentRate(position) { return position ? (position.CurrentRate || 0) : 0; }
213
-
214
- static getStopLossRate(position) {
215
- const rate = position ? (position.StopLossRate || 0) : 0;
216
- if (rate > 0 && rate <= 0.01) return 0;
217
- if (rate < 0) return 0;
218
- return rate;
219
- }
220
-
221
- static getTakeProfitRate(position) {
222
- const rate = position ? (position.TakeProfitRate || 0) : 0;
223
- if (rate > 0 && rate <= 0.01) return 0;
224
- return rate;
225
- }
226
-
227
- static getOpenDateTime(position) {
228
- return (!position || !position.OpenDateTime) ? null : new Date(position.OpenDateTime);
229
- }
230
-
231
- /**
232
- * Extract current sectors from portfolio positions.
233
- * @param {Object} portfolio - Portfolio data
234
- * @param {Object} mappings - Context mappings containing instrumentToSector
235
- * @param {string} userType - User type
236
- * @returns {string[]} Array of unique sector names
237
- */
238
- static getCurrentSectors(portfolio, mappings, userType) {
239
- const positions = this.getPositions(portfolio, userType);
240
- const sectors = new Set();
241
- const sectorMap = mappings?.instrumentToSector || {};
242
-
243
- for (const pos of positions) {
244
- const instId = this.getInstrumentId(pos);
245
- if (instId) {
246
- const sector = sectorMap[instId] || 'Unknown';
247
- sectors.add(sector);
248
- }
249
- }
250
- return Array.from(sectors);
251
- }
252
-
253
- /**
254
- * Extract AUM per asset from portfolio.
255
- * @param {Object} portfolio - Portfolio data
256
- * @param {string} userType - User type
257
- * @returns {Object} Map of instrumentId -> AUM value
258
- */
259
- static getAUMPerAsset(portfolio, userType) {
260
- const positions = this.getPositions(portfolio, userType);
261
- const assetMap = new Map();
262
-
263
- for (const pos of positions) {
264
- const instId = this.getInstrumentId(pos);
265
- if (instId) {
266
- const current = assetMap.get(instId) || 0;
267
- assetMap.set(instId, current + this.getPositionValue(pos));
268
- }
269
- }
270
-
271
- const result = {};
272
- for (const [instId, value] of assetMap.entries()) {
273
- result[instId] = value;
274
- }
275
- return result;
276
- }
277
- }
278
-
279
- class CopyTradingExtractor {
280
- static getAggregatedMirrors(portfolio) {
281
- if (!portfolio) return [];
282
- return portfolio.AggregatedMirrors || [];
283
- }
284
-
285
- static getMirrorId(mirror) { return mirror?.MirrorID; }
286
- static getParentCID(mirror) { return mirror?.ParentCID || null; }
287
- static getParentUsername(mirror) { return mirror?.ParentUsername; }
288
- static getInvested(mirror) { return mirror?.Invested || 0; }
289
- static getNetProfit(mirror) { return mirror?.NetProfit || 0; }
290
- }
291
-
292
- class RankingsExtractor {
293
- static getItems(rankingsData) {
294
- if (!rankingsData) return [];
295
- if (rankingsData.Items && Array.isArray(rankingsData.Items)) return rankingsData.Items;
296
- // Sometimes the root object IS the array in other schemas
297
- if (Array.isArray(rankingsData)) return rankingsData;
298
- return [];
299
- }
300
-
301
- static getEntry(rankingsData, userId) {
302
- const items = this.getItems(rankingsData);
303
- return items.find(r => String(r.CustomerId) === String(userId)) || null;
304
- }
305
-
306
- static getGain(entry) { return entry?.Gain || 0; }
307
- static getRiskScore(entry) { return entry?.RiskScore || 0; }
308
- static getMaxDailyRiskScore(entry) { return entry?.MaxDailyRiskScore || 0; }
309
- static getMaxMonthlyRiskScore(entry) { return entry?.MaxMonthlyRiskScore || 0; }
310
- static getCopiers(entry) { return entry?.Copiers || 0; }
311
- static getAUMTierDesc(entry) { return entry?.AUMTierDesc || "N/A"; }
312
- // [NEW]
313
- static getAUMValue(entry) { return entry?.AUMValue || 0; }
314
- static getTags(entry) { return entry?.Tags || []; }
315
- }
316
-
317
- class VerificationExtractor {
318
- static getProfile(verificationMap, userId) {
319
- if (!verificationMap) return null;
320
- return verificationMap[userId] || null;
321
- }
322
-
323
- // [FIX] Now relies on normalized data from loader
324
- static isVerified(profile) {
325
- return profile?.isVerified === true;
326
- }
327
-
328
- static getRealCID(profile) {
329
- return profile?.cid || null;
330
- }
331
-
332
- static getUsername(profile) {
333
- return profile?.username || null;
334
- }
335
-
336
- static getAboutMe(profile) {
337
- return profile?.aboutMe || "";
338
- }
339
-
340
- static getAboutMeShort(profile) {
341
- return profile?.aboutMeShort || "";
342
- }
343
-
344
- static getRestrictions(profile) {
345
- return profile?.restrictions || [];
346
- }
347
-
348
- static hasRestriction(profile, typeId) {
349
- const restrictions = this.getRestrictions(profile);
350
- return restrictions.some(r => r.RestrictionTypeID === typeId);
351
- }
352
- }
353
-
354
- class priceExtractor {
355
- static getHistory(pricesContext, tickerOrId) {
356
- if (!pricesContext || !pricesContext.history) return [];
357
- let assetData = pricesContext.history[tickerOrId];
358
- if (!assetData) {
359
- const id = Object.keys(pricesContext.history).find(key => {
360
- const data = pricesContext.history[key];
361
- return data.ticker === tickerOrId;
362
- });
363
- if (id) assetData = pricesContext.history[id];
364
- }
365
- if (!assetData || !assetData.prices) return [];
366
- const priceMap = assetData.prices;
367
- const sortedDates = Object.keys(priceMap).sort((a, b) => a.localeCompare(b));
368
- return sortedDates.map(date => ({
369
- date: date,
370
- price: priceMap[date]
371
- })).filter(item => item.price > 0);
372
- }
373
- }
374
-
375
- class HistoryExtractor {
376
- static getTrades(historyDoc) {
377
- if (historyDoc && Array.isArray(historyDoc.PublicHistoryPositions)) {
378
- return historyDoc.PublicHistoryPositions;
379
- }
380
- return [];
381
- }
382
-
383
- static getRealizedEquity(historyDoc) {
384
- return historyDoc?.CreditByRealizedEquity || 0;
385
- }
386
-
387
- // --- [RESTORED METHOD] ---
388
- static getDailyHistory(historyDoc) {
389
- const trades = this.getTrades(historyDoc);
390
- if (!trades.length) return [];
391
-
392
- const dailyMap = new Map();
393
-
394
- for (const t of trades) {
395
- if (!t.CloseDateTime) continue;
396
- // Extract YYYY-MM-DD
397
- const date = new Date(t.CloseDateTime).toISOString().slice(0, 10);
398
- const pnl = t.NetProfit || 0;
399
- const invested = t.Amount || t.Invested || 0; // Handle schema variance
400
-
401
- if (!dailyMap.has(date)) {
402
- dailyMap.set(date, {
403
- date: date,
404
- netProfit: 0,
405
- investedVolume: 0,
406
- tradesCount: 0
407
- });
408
- }
409
-
410
- const entry = dailyMap.get(date);
411
- entry.netProfit += pnl;
412
- entry.investedVolume += invested;
413
- entry.tradesCount++;
414
- }
415
-
416
- // Return sorted array
417
- return Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date));
418
- }
419
- // -------------------------
420
-
421
- static getTradedAssets(historyDoc) {
422
- const trades = this.getTrades(historyDoc);
423
- if (!trades.length) return [];
424
-
425
- const assetsMap = new Map();
426
- for (const t of trades) {
427
- const instId = t.InstrumentID;
428
- if (!instId) continue;
429
-
430
- if (!assetsMap.has(instId)) {
431
- assetsMap.set(instId, { instrumentId: instId, totalDuration: 0, count: 0 });
432
- }
433
- const asset = assetsMap.get(instId);
434
-
435
- if (t.OpenDateTime && t.CloseDateTime) {
436
- const open = new Date(t.OpenDateTime);
437
- const close = new Date(t.CloseDateTime);
438
- const durationMins = (close - open) / 60000;
439
- if (durationMins > 0) {
440
- asset.totalDuration += durationMins;
441
- asset.count++;
442
- }
443
- }
444
- }
445
-
446
- return Array.from(assetsMap.values()).map(a => ({
447
- instrumentId: a.instrumentId,
448
- avgHoldingTimeInMinutes: a.count > 0 ? (a.totalDuration / a.count) : 0
449
- }));
450
- }
451
-
452
- static getSummary(historyDoc) {
453
- const trades = this.getTrades(historyDoc);
454
- if (!trades.length) return null;
455
-
456
- let totalTrades = trades.length;
457
- let wins = 0;
458
- let totalProf = 0;
459
- let totalLoss = 0;
460
- let profCount = 0;
461
- let lossCount = 0;
462
- let totalDur = 0;
463
- let validDurCount = 0;
464
-
465
- for (const t of trades) {
466
- const netProfit = t.NetProfit || 0;
467
-
468
- if (netProfit > 0) {
469
- wins++;
470
- totalProf += netProfit;
471
- profCount++;
472
- } else if (netProfit < 0) {
473
- totalLoss += netProfit;
474
- lossCount++;
475
- }
476
-
477
- if (t.OpenDateTime && t.CloseDateTime) {
478
- const open = new Date(t.OpenDateTime);
479
- const close = new Date(t.CloseDateTime);
480
- totalDur += (close - open) / 60000;
481
- validDurCount++;
482
- }
483
- }
484
-
485
- return {
486
- totalTrades: totalTrades,
487
- winRatio: totalTrades > 0 ? (wins / totalTrades) * 100 : 0,
488
- avgProfit: profCount > 0 ? totalProf / profCount : 0,
489
- avgLoss: lossCount > 0 ? totalLoss / lossCount : 0,
490
- avgHoldingTimeInMinutes: validDurCount > 0 ? totalDur / validDurCount : 0
491
- };
492
- }
493
-
494
- /**
495
- * Extract sectors from historical trades.
496
- * @param {Object} historyDoc - Trade history document
497
- * @param {Object} mappings - Context mappings containing instrumentToSector
498
- * @returns {string[]} Array of unique sector names
499
- */
500
- static getTradedSectors(historyDoc, mappings) {
501
- const trades = this.getTrades(historyDoc);
502
- const sectors = new Set();
503
- const sectorMap = mappings?.instrumentToSector || {};
504
-
505
- for (const trade of trades) {
506
- const instId = trade.InstrumentID;
507
- if (instId) {
508
- const sector = sectorMap[instId] || 'Unknown';
509
- sectors.add(sector);
510
- }
511
- }
512
- return Array.from(sectors);
513
- }
514
-
515
- /**
516
- * Extract list of Popular Investors that user has copied (from trade history).
517
- * @param {Object} historyDoc - Trade history document
518
- * @returns {number[]} Array of ParentCID values
519
- */
520
- static getCopiedUsers(historyDoc) {
521
- const trades = this.getTrades(historyDoc);
522
- const copiedUsers = new Set();
523
-
524
- for (const trade of trades) {
525
- if (trade.ParentCID && trade.ParentCID > 0) {
526
- copiedUsers.add(trade.ParentCID);
527
- }
528
- }
529
- return Array.from(copiedUsers);
530
- }
531
- }
532
-
533
- class InsightsExtractor {
534
- static getInsights(context, timeframe = 'today') {
535
- const insightsData = context.insights;
536
- if (!insightsData) return [];
537
- const doc = insightsData[timeframe];
538
- if (!doc) return [];
539
- if (doc.insights && Array.isArray(doc.insights)) { return doc.insights; }
540
- return [];
541
- }
542
-
543
- static getInsightForInstrument(insights, instrumentId) {
544
- if (!insights || !Array.isArray(insights)) return null;
545
- return insights.find(i => i.instrumentId === instrumentId) || null;
546
- }
547
-
548
- static getTotalOwners(insight) { return insight ? (insight.total || 0) : 0; }
549
- static getLongPercent(insight) { return insight ? (insight.buy || 0) : 0; }
550
- static getShortPercent(insight) { return insight ? (insight.sell || 0) : 0; }
551
- static getGrowthPercent(insight) { return insight ? (insight.growth || 0) : 0; }
552
- }
553
-
554
- /**
555
- * [NEW] Extractor for PI Ratings Data
556
- * Access via: context.globalData.ratings
557
- * Schema: { piCid: { averageRating, totalRatings, ratingsByUser: { userId: rating } } }
558
- */
559
- class RatingsExtractor {
560
- /**
561
- * Get ratings data for a specific PI
562
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
563
- * @param {string|number} piCid - Popular Investor CID
564
- * @returns {Object|null} PI ratings data or null if not found
565
- */
566
- static getPIRatings(ratingsData, piCid) {
567
- if (!ratingsData || !piCid) return null;
568
- return ratingsData[String(piCid)] || null;
569
- }
570
-
571
- /**
572
- * Get average rating for a PI
573
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
574
- * @param {string|number} piCid - Popular Investor CID
575
- * @returns {number} Average rating (0-5) or 0 if not available
576
- */
577
- static getAverageRating(ratingsData, piCid) {
578
- const piRatings = this.getPIRatings(ratingsData, piCid);
579
- return piRatings?.averageRating || 0;
580
- }
581
-
582
- /**
583
- * Get total number of ratings for a PI
584
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
585
- * @param {string|number} piCid - Popular Investor CID
586
- * @returns {number} Total ratings count or 0 if not available
587
- */
588
- static getTotalRatings(ratingsData, piCid) {
589
- const piRatings = this.getPIRatings(ratingsData, piCid);
590
- return piRatings?.totalRatings || 0;
591
- }
592
-
593
- /**
594
- * Get ratings by user map for a PI
595
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
596
- * @param {string|number} piCid - Popular Investor CID
597
- * @returns {Object} Map of userId -> rating, or empty object if not available
598
- */
599
- static getRatingsByUser(ratingsData, piCid) {
600
- const piRatings = this.getPIRatings(ratingsData, piCid);
601
- return piRatings?.ratingsByUser || {};
602
- }
603
-
604
- /**
605
- * Get a specific user's rating for a PI
606
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
607
- * @param {string|number} piCid - Popular Investor CID
608
- * @param {string|number} userId - User CID
609
- * @returns {number|null} User's rating (1-5) or null if not found
610
- */
611
- static getUserRating(ratingsData, piCid, userId) {
612
- const ratingsByUser = this.getRatingsByUser(ratingsData, piCid);
613
- const rating = ratingsByUser[String(userId)];
614
- return rating !== undefined ? rating : null;
615
- }
616
-
617
- /**
618
- * Get all PIs with ratings data
619
- * @param {Object} ratingsData - The ratings data from context.globalData.ratings
620
- * @returns {string[]} Array of PI CIDs that have ratings
621
- */
622
- static getAllPIs(ratingsData) {
623
- if (!ratingsData || typeof ratingsData !== 'object') return [];
624
- return Object.keys(ratingsData).filter(key => key !== 'date' && key !== 'lastUpdated');
625
- }
626
- }
627
-
628
- /**
629
- * [NEW] Extractor for PI Page Views Data
630
- * Access via: context.globalData.pageViews
631
- * Schema: { piCid: { totalViews, uniqueViewers, viewsByUser: { userId: { viewCount, lastViewed } } } }
632
- */
633
- class PageViewsExtractor {
634
- /**
635
- * Get page views data for a specific PI
636
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
637
- * @param {string|number} piCid - Popular Investor CID
638
- * @returns {Object|null} PI page views data or null if not found
639
- */
640
- static getPIPageViews(pageViewsData, piCid) {
641
- if (!pageViewsData || !piCid) return null;
642
- return pageViewsData[String(piCid)] || null;
643
- }
644
-
645
- /**
646
- * Get total page views for a PI
647
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
648
- * @param {string|number} piCid - Popular Investor CID
649
- * @returns {number} Total views count or 0 if not available
650
- */
651
- static getTotalViews(pageViewsData, piCid) {
652
- const piViews = this.getPIPageViews(pageViewsData, piCid);
653
- return piViews?.totalViews || 0;
654
- }
655
-
656
- /**
657
- * Get unique viewers count for a PI
658
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
659
- * @param {string|number} piCid - Popular Investor CID
660
- * @returns {number} Unique viewers count or 0 if not available
661
- */
662
- static getUniqueViewers(pageViewsData, piCid) {
663
- const piViews = this.getPIPageViews(pageViewsData, piCid);
664
- return piViews?.uniqueViewers || 0;
665
- }
666
-
667
- /**
668
- * Get views by user map for a PI
669
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
670
- * @param {string|number} piCid - Popular Investor CID
671
- * @returns {Object} Map of userId -> { viewCount, lastViewed }, or empty object if not available
672
- */
673
- static getViewsByUser(pageViewsData, piCid) {
674
- const piViews = this.getPIPageViews(pageViewsData, piCid);
675
- return piViews?.viewsByUser || {};
676
- }
677
-
678
- /**
679
- * Get a specific user's view data for a PI
680
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
681
- * @param {string|number} piCid - Popular Investor CID
682
- * @param {string|number} userId - User CID
683
- * @returns {Object|null} User's view data { viewCount, lastViewed } or null if not found
684
- */
685
- static getUserViews(pageViewsData, piCid, userId) {
686
- const viewsByUser = this.getViewsByUser(pageViewsData, piCid);
687
- return viewsByUser[String(userId)] || null;
688
- }
689
-
690
- /**
691
- * Get view count for a specific user
692
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
693
- * @param {string|number} piCid - Popular Investor CID
694
- * @param {string|number} userId - User CID
695
- * @returns {number} User's view count or 0 if not found
696
- */
697
- static getUserViewCount(pageViewsData, piCid, userId) {
698
- const userViews = this.getUserViews(pageViewsData, piCid, userId);
699
- return userViews?.viewCount || 0;
700
- }
701
-
702
- /**
703
- * Get last viewed timestamp for a specific user
704
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
705
- * @param {string|number} piCid - Popular Investor CID
706
- * @param {string|number} userId - User CID
707
- * @returns {Date|null} Last viewed date or null if not found
708
- */
709
- static getUserLastViewed(pageViewsData, piCid, userId) {
710
- const userViews = this.getUserViews(pageViewsData, piCid, userId);
711
- if (!userViews || !userViews.lastViewed) return null;
712
- return userViews.lastViewed.toDate ? userViews.lastViewed.toDate() : new Date(userViews.lastViewed);
713
- }
714
-
715
- /**
716
- * Get all PIs with page views data
717
- * @param {Object} pageViewsData - The page views data from context.globalData.pageViews
718
- * @returns {string[]} Array of PI CIDs that have page views
719
- */
720
- static getAllPIs(pageViewsData) {
721
- if (!pageViewsData || typeof pageViewsData !== 'object') return [];
722
- return Object.keys(pageViewsData).filter(key => key !== 'date' && key !== 'lastUpdated');
723
- }
724
- }
725
-
726
- /**
727
- * [NEW] Extractor for Watchlist Membership Data
728
- * Access via: context.globalData.watchlistMembership
729
- * Schema: { piCid: { totalUsers, users: [userId], publicWatchlistCount, privateWatchlistCount } }
730
- */
731
- class WatchlistMembershipExtractor {
732
- /**
733
- * Get watchlist membership data for a specific PI
734
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
735
- * @param {string|number} piCid - Popular Investor CID
736
- * @returns {Object|null} PI watchlist membership data or null if not found
737
- */
738
- static getPIMembership(watchlistData, piCid) {
739
- if (!watchlistData || !piCid) return null;
740
- return watchlistData[String(piCid)] || null;
741
- }
742
-
743
- /**
744
- * Get total users who have this PI in their watchlist
745
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
746
- * @param {string|number} piCid - Popular Investor CID
747
- * @returns {number} Total users count or 0 if not available
748
- */
749
- static getTotalUsers(watchlistData, piCid) {
750
- const membership = this.getPIMembership(watchlistData, piCid);
751
- return membership?.totalUsers || 0;
752
- }
753
-
754
- /**
755
- * Get array of user CIDs who have this PI in their watchlist
756
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
757
- * @param {string|number} piCid - Popular Investor CID
758
- * @returns {string[]} Array of user CIDs, or empty array if not available
759
- */
760
- static getUsers(watchlistData, piCid) {
761
- const membership = this.getPIMembership(watchlistData, piCid);
762
- if (!membership || !Array.isArray(membership.users)) return [];
763
- return membership.users.map(u => String(u));
764
- }
765
-
766
- /**
767
- * Check if a specific user has this PI in their watchlist
768
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
769
- * @param {string|number} piCid - Popular Investor CID
770
- * @param {string|number} userId - User CID
771
- * @returns {boolean} True if user has PI in watchlist
772
- */
773
- static hasUser(watchlistData, piCid, userId) {
774
- const users = this.getUsers(watchlistData, piCid);
775
- return users.includes(String(userId));
776
- }
777
-
778
- /**
779
- * Get public watchlist count for a PI
780
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
781
- * @param {string|number} piCid - Popular Investor CID
782
- * @returns {number} Public watchlist count or 0 if not available
783
- */
784
- static getPublicWatchlistCount(watchlistData, piCid) {
785
- const membership = this.getPIMembership(watchlistData, piCid);
786
- return membership?.publicWatchlistCount || 0;
787
- }
788
-
789
- /**
790
- * Get private watchlist count for a PI
791
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
792
- * @param {string|number} piCid - Popular Investor CID
793
- * @returns {number} Private watchlist count or 0 if not available
794
- */
795
- static getPrivateWatchlistCount(watchlistData, piCid) {
796
- const membership = this.getPIMembership(watchlistData, piCid);
797
- return membership?.privateWatchlistCount || 0;
798
- }
799
-
800
- /**
801
- * Get all PIs with watchlist membership data
802
- * @param {Object} watchlistData - The watchlist data from context.globalData.watchlistMembership
803
- * @returns {string[]} Array of PI CIDs that have watchlist membership
804
- */
805
- static getAllPIs(watchlistData) {
806
- if (!watchlistData || typeof watchlistData !== 'object') return [];
807
- return Object.keys(watchlistData).filter(key => key !== 'date' && key !== 'lastUpdated');
808
- }
809
- }
810
-
811
- /**
812
- * [NEW] Extractor for PI-Centric Watchlist Data
813
- * Access via: loader.loadPIWatchlistData(piCid) or context-specific loading
814
- * Schema: { totalUsers, userCids: [], dailyAdditions: { date: { count, userCids: [], timestamp } }, lastUpdated }
815
- */
816
- class PIWatchlistDataExtractor {
817
- /**
818
- * Get total users who have this PI in their watchlist
819
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
820
- * @returns {number} Total users count or 0 if not available
821
- */
822
- static getTotalUsers(piWatchlistData) {
823
- if (!piWatchlistData) return 0;
824
- return piWatchlistData.totalUsers || 0;
825
- }
826
-
827
- /**
828
- * Get array of user CIDs who have this PI in their watchlist
829
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
830
- * @returns {string[]} Array of user CIDs, or empty array if not available
831
- */
832
- static getUserCids(piWatchlistData) {
833
- if (!piWatchlistData || !Array.isArray(piWatchlistData.userCids)) return [];
834
- return piWatchlistData.userCids.map(cid => String(cid));
835
- }
836
-
837
- /**
838
- * Check if a specific user has this PI in their watchlist
839
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
840
- * @param {string|number} userId - User CID
841
- * @returns {boolean} True if user has PI in watchlist
842
- */
843
- static hasUser(piWatchlistData, userId) {
844
- const userCids = this.getUserCids(piWatchlistData);
845
- return userCids.includes(String(userId));
846
- }
847
-
848
- /**
849
- * Get daily additions data for a specific date
850
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
851
- * @param {string} date - Date string in YYYY-MM-DD format
852
- * @returns {Object|null} Daily addition data { count, userCids: [], timestamp } or null if not found
853
- */
854
- static getDailyAdditions(piWatchlistData, date) {
855
- if (!piWatchlistData || !piWatchlistData.dailyAdditions || typeof piWatchlistData.dailyAdditions !== 'object') {
856
- return null;
857
- }
858
- return piWatchlistData.dailyAdditions[date] || null;
859
- }
860
-
861
- /**
862
- * Get count of users who added this PI to their watchlist on a specific date
863
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
864
- * @param {string} date - Date string in YYYY-MM-DD format
865
- * @returns {number} Count of users who added on this date, or 0 if not available
866
- */
867
- static getDailyAdditionCount(piWatchlistData, date) {
868
- const dailyData = this.getDailyAdditions(piWatchlistData, date);
869
- return dailyData?.count || 0;
870
- }
871
-
872
- /**
873
- * Get user CIDs who added this PI to their watchlist on a specific date
874
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
875
- * @param {string} date - Date string in YYYY-MM-DD format
876
- * @returns {string[]} Array of user CIDs who added on this date, or empty array if not available
877
- */
878
- static getDailyAdditionUserCids(piWatchlistData, date) {
879
- const dailyData = this.getDailyAdditions(piWatchlistData, date);
880
- if (!dailyData || !Array.isArray(dailyData.userCids)) return [];
881
- return dailyData.userCids.map(cid => String(cid));
882
- }
883
-
884
- /**
885
- * Get all dates with daily addition data
886
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
887
- * @returns {string[]} Array of date strings (YYYY-MM-DD format), or empty array if not available
888
- */
889
- static getAllDates(piWatchlistData) {
890
- if (!piWatchlistData || !piWatchlistData.dailyAdditions || typeof piWatchlistData.dailyAdditions !== 'object') {
891
- return [];
892
- }
893
- return Object.keys(piWatchlistData.dailyAdditions);
894
- }
895
-
896
- /**
897
- * Get last updated timestamp
898
- * @param {Object} piWatchlistData - The PI watchlist data from PopularInvestors/{piCid}/watchlistData/current
899
- * @returns {Object|null} Timestamp object or null if not available
900
- */
901
- static getLastUpdated(piWatchlistData) {
902
- if (!piWatchlistData) return null;
903
- return piWatchlistData.lastUpdated || null;
904
- }
905
- }
906
-
907
- /**
908
- * [NEW] Extractor for PI Alert History Data
909
- * Access via: context.globalData.alertHistory
910
- * Schema: { piCid: { alertType: { triggered, count, triggeredFor: [userId], metadata: {...}, lastTriggered } } }
911
- */
912
- class AlertHistoryExtractor {
913
- /**
914
- * Get alert history data for a specific PI
915
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
916
- * @param {string|number} piCid - Popular Investor CID
917
- * @returns {Object|null} PI alert history data or null if not found
918
- */
919
- static getPIAlertHistory(alertHistoryData, piCid) {
920
- if (!alertHistoryData || !piCid) return null;
921
- return alertHistoryData[String(piCid)] || null;
922
- }
923
-
924
- /**
925
- * Get all alert types for a PI
926
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
927
- * @param {string|number} piCid - Popular Investor CID
928
- * @returns {string[]} Array of alert type names, or empty array if not available
929
- */
930
- static getAlertTypes(alertHistoryData, piCid) {
931
- const piAlerts = this.getPIAlertHistory(alertHistoryData, piCid);
932
- if (!piAlerts || typeof piAlerts !== 'object') return [];
933
- return Object.keys(piAlerts).filter(key => key !== 'date' && key !== 'lastUpdated');
934
- }
935
-
936
- /**
937
- * Get alert data for a specific alert type
938
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
939
- * @param {string|number} piCid - Popular Investor CID
940
- * @param {string} alertType - Alert type name (e.g., "RiskScoreIncrease")
941
- * @returns {Object|null} Alert data or null if not found
942
- */
943
- static getAlertData(alertHistoryData, piCid, alertType) {
944
- const piAlerts = this.getPIAlertHistory(alertHistoryData, piCid);
945
- if (!piAlerts || !alertType) return null;
946
- return piAlerts[alertType] || null;
947
- }
948
-
949
- /**
950
- * Check if an alert type was triggered for a PI
951
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
952
- * @param {string|number} piCid - Popular Investor CID
953
- * @param {string} alertType - Alert type name
954
- * @returns {boolean} True if alert was triggered
955
- */
956
- static isTriggered(alertHistoryData, piCid, alertType) {
957
- const alertData = this.getAlertData(alertHistoryData, piCid, alertType);
958
- return alertData?.triggered === true;
959
- }
960
-
961
- /**
962
- * Get trigger count for an alert type
963
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
964
- * @param {string|number} piCid - Popular Investor CID
965
- * @param {string} alertType - Alert type name
966
- * @returns {number} Trigger count or 0 if not available
967
- */
968
- static getTriggerCount(alertHistoryData, piCid, alertType) {
969
- const alertData = this.getAlertData(alertHistoryData, piCid, alertType);
970
- return alertData?.count || 0;
971
- }
972
-
973
- /**
974
- * Get array of user CIDs for whom the alert was triggered
975
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
976
- * @param {string|number} piCid - Popular Investor CID
977
- * @param {string} alertType - Alert type name
978
- * @returns {string[]} Array of user CIDs, or empty array if not available
979
- */
980
- static getTriggeredFor(alertHistoryData, piCid, alertType) {
981
- const alertData = this.getAlertData(alertHistoryData, piCid, alertType);
982
- if (!alertData || !Array.isArray(alertData.triggeredFor)) return [];
983
- return alertData.triggeredFor.map(u => String(u));
984
- }
985
-
986
- /**
987
- * Check if alert was triggered for a specific user
988
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
989
- * @param {string|number} piCid - Popular Investor CID
990
- * @param {string} alertType - Alert type name
991
- * @param {string|number} userId - User CID
992
- * @returns {boolean} True if alert was triggered for this user
993
- */
994
- static isTriggeredForUser(alertHistoryData, piCid, alertType, userId) {
995
- const triggeredFor = this.getTriggeredFor(alertHistoryData, piCid, alertType);
996
- return triggeredFor.includes(String(userId));
997
- }
998
-
999
- /**
1000
- * Get metadata for an alert type
1001
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
1002
- * @param {string|number} piCid - Popular Investor CID
1003
- * @param {string} alertType - Alert type name
1004
- * @returns {Object} Alert metadata or empty object if not available
1005
- */
1006
- static getMetadata(alertHistoryData, piCid, alertType) {
1007
- const alertData = this.getAlertData(alertHistoryData, piCid, alertType);
1008
- return alertData?.metadata || {};
1009
- }
1010
-
1011
- /**
1012
- * Get last triggered timestamp for an alert type
1013
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
1014
- * @param {string|number} piCid - Popular Investor CID
1015
- * @param {string} alertType - Alert type name
1016
- * @returns {Date|null} Last triggered date or null if not found
1017
- */
1018
- static getLastTriggered(alertHistoryData, piCid, alertType) {
1019
- const alertData = this.getAlertData(alertHistoryData, piCid, alertType);
1020
- if (!alertData || !alertData.lastTriggered) return null;
1021
- return alertData.lastTriggered.toDate ? alertData.lastTriggered.toDate() : new Date(alertData.lastTriggered);
1022
- }
1023
-
1024
- /**
1025
- * Get all triggered alerts for a PI
1026
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
1027
- * @param {string|number} piCid - Popular Investor CID
1028
- * @returns {string[]} Array of alert type names that were triggered
1029
- */
1030
- static getTriggeredAlerts(alertHistoryData, piCid) {
1031
- const alertTypes = this.getAlertTypes(alertHistoryData, piCid);
1032
- return alertTypes.filter(type => this.isTriggered(alertHistoryData, piCid, type));
1033
- }
1034
-
1035
- /**
1036
- * Get all PIs with alert history data
1037
- * @param {Object} alertHistoryData - The alert history data from context.globalData.alertHistory
1038
- * @returns {string[]} Array of PI CIDs that have alert history
1039
- */
1040
- static getAllPIs(alertHistoryData) {
1041
- if (!alertHistoryData || typeof alertHistoryData !== 'object') return [];
1042
- return Object.keys(alertHistoryData).filter(key => key !== 'date' && key !== 'lastUpdated');
1043
- }
1044
- }
1045
-
1046
- /**
1047
- * [NEW] Extractor for Popular Investor Master List (Global Backup)
1048
- * Access via: context.globalData.piMasterList
1049
- */
1050
- class PIMasterListExtractor {
1051
- /**
1052
- * Get the master record for a PI
1053
- * @param {Object} masterList - context.globalData.piMasterList
1054
- * @param {string} cid - The User CID
1055
- */
1056
- static getRecord(masterList, cid) {
1057
- if (!masterList || !cid) return null;
1058
- return masterList[String(cid)] || null;
1059
- }
1060
-
1061
- /**
1062
- * Resolve Username from Master List
1063
- * @param {Object} masterList
1064
- * @param {string} cid
1065
- */
1066
- static getUsername(masterList, cid) {
1067
- const record = this.getRecord(masterList, cid);
1068
- return record ? record.username : null;
1069
- }
1070
-
1071
- /**
1072
- * Check if CID exists in master list
1073
- */
1074
- static exists(masterList, cid) {
1075
- return !!this.getRecord(masterList, cid);
1076
- }
1077
- }
1078
-
1079
- module.exports = {
1080
- DataExtractor,
1081
- priceExtractor,
1082
- HistoryExtractor,
1083
- InsightsExtractor,
1084
- TradeSeriesBuilder,
1085
- PopularInvestorExtractor,
1086
- CopyTradingExtractor,
1087
- RankingsExtractor,
1088
- VerificationExtractor,
1089
- SocialExtractor,
1090
- // [NEW] New Root Data Extractors
1091
- RatingsExtractor,
1092
- PageViewsExtractor,
1093
- WatchlistMembershipExtractor,
1094
- PIWatchlistDataExtractor,
1095
- AlertHistoryExtractor,
1096
- PIMasterListExtractor
1097
- };