bulltrackers-module 1.0.733 → 1.0.734

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 (56) hide show
  1. package/functions/computation-system-v2/README.md +152 -0
  2. package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +720 -0
  3. package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +176 -0
  4. package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +294 -0
  5. package/functions/computation-system-v2/computations/TestComputation.js +46 -0
  6. package/functions/computation-system-v2/computations/UserPortfolioSummary.js +172 -0
  7. package/functions/computation-system-v2/config/bulltrackers.config.js +317 -0
  8. package/functions/computation-system-v2/framework/core/Computation.js +73 -0
  9. package/functions/computation-system-v2/framework/core/Manifest.js +223 -0
  10. package/functions/computation-system-v2/framework/core/RuleInjector.js +53 -0
  11. package/functions/computation-system-v2/framework/core/Rules.js +231 -0
  12. package/functions/computation-system-v2/framework/core/RunAnalyzer.js +163 -0
  13. package/functions/computation-system-v2/framework/cost/CostTracker.js +154 -0
  14. package/functions/computation-system-v2/framework/data/DataFetcher.js +399 -0
  15. package/functions/computation-system-v2/framework/data/QueryBuilder.js +232 -0
  16. package/functions/computation-system-v2/framework/data/SchemaRegistry.js +287 -0
  17. package/functions/computation-system-v2/framework/execution/Orchestrator.js +498 -0
  18. package/functions/computation-system-v2/framework/execution/TaskRunner.js +35 -0
  19. package/functions/computation-system-v2/framework/execution/middleware/CostTrackerMiddleware.js +32 -0
  20. package/functions/computation-system-v2/framework/execution/middleware/LineageMiddleware.js +32 -0
  21. package/functions/computation-system-v2/framework/execution/middleware/Middleware.js +14 -0
  22. package/functions/computation-system-v2/framework/execution/middleware/ProfilerMiddleware.js +47 -0
  23. package/functions/computation-system-v2/framework/index.js +45 -0
  24. package/functions/computation-system-v2/framework/lineage/LineageTracker.js +147 -0
  25. package/functions/computation-system-v2/framework/monitoring/Profiler.js +80 -0
  26. package/functions/computation-system-v2/framework/resilience/Checkpointer.js +66 -0
  27. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +327 -0
  28. package/functions/computation-system-v2/framework/storage/StateRepository.js +286 -0
  29. package/functions/computation-system-v2/framework/storage/StorageManager.js +469 -0
  30. package/functions/computation-system-v2/framework/storage/index.js +9 -0
  31. package/functions/computation-system-v2/framework/testing/ComputationTester.js +86 -0
  32. package/functions/computation-system-v2/framework/utils/Graph.js +205 -0
  33. package/functions/computation-system-v2/handlers/dispatcher.js +109 -0
  34. package/functions/computation-system-v2/handlers/index.js +23 -0
  35. package/functions/computation-system-v2/handlers/onDemand.js +289 -0
  36. package/functions/computation-system-v2/handlers/scheduler.js +327 -0
  37. package/functions/computation-system-v2/index.js +163 -0
  38. package/functions/computation-system-v2/rules/index.js +49 -0
  39. package/functions/computation-system-v2/rules/instruments.js +465 -0
  40. package/functions/computation-system-v2/rules/metrics.js +304 -0
  41. package/functions/computation-system-v2/rules/portfolio.js +534 -0
  42. package/functions/computation-system-v2/rules/rankings.js +655 -0
  43. package/functions/computation-system-v2/rules/social.js +562 -0
  44. package/functions/computation-system-v2/rules/trades.js +545 -0
  45. package/functions/computation-system-v2/scripts/migrate-sectors.js +73 -0
  46. package/functions/computation-system-v2/test/test-dispatcher.js +317 -0
  47. package/functions/computation-system-v2/test/test-framework.js +500 -0
  48. package/functions/computation-system-v2/test/test-real-execution.js +166 -0
  49. package/functions/computation-system-v2/test/test-real-integration.js +194 -0
  50. package/functions/computation-system-v2/test/test-refactor-e2e.js +131 -0
  51. package/functions/computation-system-v2/test/test-results.json +31 -0
  52. package/functions/computation-system-v2/test/test-risk-metrics-computation.js +329 -0
  53. package/functions/computation-system-v2/test/test-scheduler.js +204 -0
  54. package/functions/computation-system-v2/test/test-storage.js +449 -0
  55. package/functions/orchestrator/index.js +18 -26
  56. package/package.json +3 -2
@@ -0,0 +1,534 @@
1
+ /**
2
+ * @fileoverview Portfolio Business Rules
3
+ *
4
+ * For extracting and analyzing portfolio_snapshots data.
5
+ *
6
+ * Portfolio data structure (portfolio_data JSON):
7
+ * - AggregatedPositions: Array of positions per instrument
8
+ * - AggregatedMirrors: Array of copied positions
9
+ * - AggregatedPositionsByInstrumentTypeID: Positions by asset class
10
+ * - CreditByRealizedEquity: % cash relative to invested sum
11
+ * - CreditByUnrealizedEquity: % cash relative to current balance
12
+ *
13
+ * Position data (percentages, not absolute values):
14
+ * - Direction: 'Buy' or 'Sell'
15
+ * - InstrumentID: Asset ID
16
+ * - Invested: % of initial investment
17
+ * - NetProfit: % P&L
18
+ * - Value: % of unrealized portfolio value
19
+ *
20
+ * Usage in computation:
21
+ * ```javascript
22
+ * const data = rules.portfolio.extractPortfolioData(row);
23
+ * const positions = rules.portfolio.extractPositions(data);
24
+ * const totalValue = rules.portfolio.calculateTotalValue(positions);
25
+ * ```
26
+ */
27
+
28
+ // =============================================================================
29
+ // DATA EXTRACTION
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Extract portfolio_data from a portfolio_snapshots row.
34
+ * @param {Object} row - portfolio_snapshots row
35
+ * @returns {Object|null}
36
+ */
37
+ function extractPortfolioData(row) {
38
+ if (!row) return null;
39
+
40
+ let data = row.portfolio_data;
41
+
42
+ if (typeof data === 'string') {
43
+ try {
44
+ data = JSON.parse(data);
45
+ } catch (e) {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ return data || null;
51
+ }
52
+
53
+ /**
54
+ * Get user ID from portfolio row.
55
+ * @param {Object} row - portfolio_snapshots row
56
+ * @returns {string|null}
57
+ */
58
+ function getUserId(row) {
59
+ if (!row) return null;
60
+
61
+ if (row.user_id) return String(row.user_id);
62
+
63
+ const data = extractPortfolioData(row);
64
+ return data?.cid ? String(data.cid) : null;
65
+ }
66
+
67
+ /**
68
+ * Get username from portfolio data.
69
+ * @param {Object} data - portfolio_data object
70
+ * @returns {string|null}
71
+ */
72
+ function getUsername(data) {
73
+ return data?.username || null;
74
+ }
75
+
76
+ /**
77
+ * Extract positions from portfolio data.
78
+ * Handles different portfolio data structures.
79
+ *
80
+ * @param {Object|Array} portfolioData - Raw portfolio data from BigQuery
81
+ * @returns {Array} Array of position objects
82
+ */
83
+ function extractPositions(portfolioData) {
84
+ if (!portfolioData) return [];
85
+
86
+ // Handle array directly
87
+ if (Array.isArray(portfolioData)) return portfolioData;
88
+
89
+ // Handle nested structures (actual data structure)
90
+ if (portfolioData.AggregatedPositions) return portfolioData.AggregatedPositions;
91
+ if (portfolioData.Positions) return portfolioData.Positions;
92
+ if (portfolioData.positions) return portfolioData.positions;
93
+
94
+ // Handle JSON string
95
+ if (typeof portfolioData === 'string') {
96
+ try {
97
+ const parsed = JSON.parse(portfolioData);
98
+ return extractPositions(parsed);
99
+ } catch (e) {
100
+ return [];
101
+ }
102
+ }
103
+
104
+ return [];
105
+ }
106
+
107
+ /**
108
+ * Extract mirrored (copied) positions.
109
+ * @param {Object} portfolioData - portfolio_data object
110
+ * @returns {Array}
111
+ */
112
+ function extractMirrors(portfolioData) {
113
+ if (!portfolioData) return [];
114
+ return portfolioData.AggregatedMirrors || [];
115
+ }
116
+
117
+ /**
118
+ * Extract positions by instrument type/asset class.
119
+ * @param {Object} portfolioData - portfolio_data object
120
+ * @returns {Array}
121
+ */
122
+ function extractPositionsByType(portfolioData) {
123
+ if (!portfolioData) return [];
124
+ return portfolioData.AggregatedPositionsByInstrumentTypeID || [];
125
+ }
126
+
127
+ /**
128
+ * Extract positions by stock industry.
129
+ * @param {Object} portfolioData - portfolio_data object
130
+ * @returns {Array}
131
+ */
132
+ function extractPositionsByIndustry(portfolioData) {
133
+ if (!portfolioData) return [];
134
+ return portfolioData.AggregatedPositionsByStockIndustryID || [];
135
+ }
136
+
137
+ // =============================================================================
138
+ // CASH BALANCE
139
+ // =============================================================================
140
+
141
+ /**
142
+ * Get cash balance as % of realized equity (invested sum).
143
+ * @param {Object} portfolioData - portfolio_data object
144
+ * @returns {number}
145
+ */
146
+ function getCreditByRealizedEquity(portfolioData) {
147
+ return portfolioData?.CreditByRealizedEquity ?? 0;
148
+ }
149
+
150
+ /**
151
+ * Get cash balance as % of unrealized equity (current balance).
152
+ * @param {Object} portfolioData - portfolio_data object
153
+ * @returns {number}
154
+ */
155
+ function getCreditByUnrealizedEquity(portfolioData) {
156
+ return portfolioData?.CreditByUnrealizedEquity ?? 0;
157
+ }
158
+
159
+ /**
160
+ * Calculate exposure percentage (100 - cash %).
161
+ * @param {Object} portfolioData - portfolio_data object
162
+ * @returns {number}
163
+ */
164
+ function getExposure(portfolioData) {
165
+ return 100 - getCreditByUnrealizedEquity(portfolioData);
166
+ }
167
+
168
+ // =============================================================================
169
+ // POSITION ACCESSORS
170
+ // =============================================================================
171
+
172
+ /**
173
+ * Get the instrument ID from a position.
174
+ * Handles different field names.
175
+ *
176
+ * @param {Object} position - Position object
177
+ * @returns {string|number|null} Instrument ID
178
+ */
179
+ function getInstrumentId(position) {
180
+ return position?.InstrumentID ||
181
+ position?.instrumentId ||
182
+ position?.instrument_id ||
183
+ null;
184
+ }
185
+
186
+ /**
187
+ * Get the instrument type ID from a position.
188
+ * @param {Object} position - Position object
189
+ * @returns {number|null}
190
+ */
191
+ function getInstrumentTypeId(position) {
192
+ return position?.InstrumentTypeID || null;
193
+ }
194
+
195
+ /**
196
+ * Get the direction of a position ('Buy' or 'Sell').
197
+ * @param {Object} position - Position object
198
+ * @returns {string}
199
+ */
200
+ function getDirection(position) {
201
+ return position?.Direction || 'Buy';
202
+ }
203
+
204
+ /**
205
+ * Check if position is long (Buy).
206
+ * @param {Object} position - Position object
207
+ * @returns {boolean}
208
+ */
209
+ function isLong(position) {
210
+ return getDirection(position) === 'Buy';
211
+ }
212
+
213
+ /**
214
+ * Check if position is short (Sell).
215
+ * @param {Object} position - Position object
216
+ * @returns {boolean}
217
+ */
218
+ function isShort(position) {
219
+ return getDirection(position) === 'Sell';
220
+ }
221
+
222
+ /**
223
+ * Get the invested percentage (% of initial investment).
224
+ * NOTE: This is a percentage, not an absolute value.
225
+ *
226
+ * @param {Object} position - Position object
227
+ * @returns {number} Invested percentage
228
+ */
229
+ function getInvested(position) {
230
+ return position?.Invested ||
231
+ position?.Amount ||
232
+ position?.amount ||
233
+ position?.invested ||
234
+ position?.Investment ||
235
+ 0;
236
+ }
237
+
238
+ /**
239
+ * Get the net profit percentage (% P&L).
240
+ * NOTE: This is a percentage, not an absolute value.
241
+ *
242
+ * @param {Object} position - Position object
243
+ * @returns {number} Net profit percentage
244
+ */
245
+ function getNetProfit(position) {
246
+ return position?.NetProfit ||
247
+ position?.netProfit ||
248
+ position?.net_profit ||
249
+ position?.Profit ||
250
+ 0;
251
+ }
252
+
253
+ /**
254
+ * Get the current value of a position.
255
+ *
256
+ * @param {Object} position - Position object
257
+ * @returns {number} Current value
258
+ */
259
+ function getValue(position) {
260
+ const value = position?.Value || position?.value;
261
+ if (value) return value;
262
+
263
+ // Calculate from invested + profit
264
+ const invested = getInvested(position);
265
+ const profit = getNetProfit(position);
266
+ return invested + (invested * profit / 100);
267
+ }
268
+
269
+ /**
270
+ * Calculate total portfolio value.
271
+ *
272
+ * @param {Array} positions - Array of positions
273
+ * @returns {number} Total portfolio value
274
+ */
275
+ function calculateTotalValue(positions) {
276
+ if (!Array.isArray(positions)) return 0;
277
+ return positions.reduce((sum, pos) => sum + getValue(pos), 0);
278
+ }
279
+
280
+ /**
281
+ * Calculate total invested amount.
282
+ *
283
+ * @param {Array} positions - Array of positions
284
+ * @returns {number} Total invested
285
+ */
286
+ function calculateTotalInvested(positions) {
287
+ if (!Array.isArray(positions)) return 0;
288
+ return positions.reduce((sum, pos) => sum + getInvested(pos), 0);
289
+ }
290
+
291
+ /**
292
+ * Calculate weighted average profit percentage.
293
+ *
294
+ * @param {Array} positions - Array of positions
295
+ * @returns {number} Weighted average profit percentage
296
+ */
297
+ function calculateWeightedProfitPercent(positions) {
298
+ if (!Array.isArray(positions) || positions.length === 0) return 0;
299
+
300
+ let totalWeightedProfit = 0;
301
+ let totalInvested = 0;
302
+
303
+ for (const pos of positions) {
304
+ const invested = getInvested(pos);
305
+ const profit = getNetProfit(pos);
306
+
307
+ if (invested > 0) {
308
+ totalWeightedProfit += invested * (profit / 100);
309
+ totalInvested += invested;
310
+ }
311
+ }
312
+
313
+ return totalInvested > 0
314
+ ? (totalWeightedProfit / totalInvested) * 100
315
+ : 0;
316
+ }
317
+
318
+ /**
319
+ * Group positions by sector.
320
+ *
321
+ * @param {Array} positions - Array of positions
322
+ * @param {Object} sectorMap - Map of instrument ID -> sector name
323
+ * @returns {Object} Map of sector -> [positions]
324
+ */
325
+ function groupBySector(positions, sectorMap) {
326
+ const groups = {};
327
+
328
+ for (const pos of (positions || [])) {
329
+ const instrumentId = getInstrumentId(pos);
330
+ const sector = sectorMap?.[String(instrumentId)] || 'Unknown';
331
+
332
+ if (!groups[sector]) groups[sector] = [];
333
+ groups[sector].push(pos);
334
+ }
335
+
336
+ return groups;
337
+ }
338
+
339
+ /**
340
+ * Calculate sector exposure percentages.
341
+ *
342
+ * @param {Array} positions - Array of positions
343
+ * @param {Object} sectorMap - Map of instrument ID -> sector name
344
+ * @returns {Object} Map of sector -> percentage
345
+ */
346
+ function calculateSectorExposure(positions, sectorMap) {
347
+ const groups = groupBySector(positions, sectorMap);
348
+ const totalValue = calculateTotalValue(positions);
349
+
350
+ if (totalValue === 0) return {};
351
+
352
+ const exposure = {};
353
+ for (const [sector, sectorPositions] of Object.entries(groups)) {
354
+ const sectorValue = calculateTotalValue(sectorPositions);
355
+ exposure[sector] = Number(((sectorValue / totalValue) * 100).toFixed(2));
356
+ }
357
+
358
+ return exposure;
359
+ }
360
+
361
+ /**
362
+ * Get top N positions by value.
363
+ *
364
+ * @param {Array} positions - Array of positions
365
+ * @param {number} n - Number of top positions to return
366
+ * @returns {Array} Top N positions sorted by value descending
367
+ */
368
+ function getTopPositions(positions, n = 10) {
369
+ if (!Array.isArray(positions)) return [];
370
+
371
+ return [...positions]
372
+ .sort((a, b) => getValue(b) - getValue(a))
373
+ .slice(0, n);
374
+ }
375
+
376
+ /**
377
+ * Get profitable positions.
378
+ *
379
+ * @param {Array} positions - Array of positions
380
+ * @returns {Array} Positions with positive net profit
381
+ */
382
+ function getProfitablePositions(positions) {
383
+ if (!Array.isArray(positions)) return [];
384
+ return positions.filter(pos => getNetProfit(pos) > 0);
385
+ }
386
+
387
+ /**
388
+ * Calculate win ratio (% of profitable positions).
389
+ *
390
+ * @param {Array} positions - Array of positions
391
+ * @returns {number} Win ratio between 0 and 1
392
+ */
393
+ function calculateWinRatio(positions) {
394
+ if (!Array.isArray(positions) || positions.length === 0) return 0;
395
+
396
+ const profitable = getProfitablePositions(positions);
397
+ return profitable.length / positions.length;
398
+ }
399
+
400
+ /**
401
+ * Filter long positions.
402
+ * @param {Array} positions - Array of positions
403
+ * @returns {Array}
404
+ */
405
+ function filterLong(positions) {
406
+ if (!Array.isArray(positions)) return [];
407
+ return positions.filter(isLong);
408
+ }
409
+
410
+ /**
411
+ * Filter short positions.
412
+ * @param {Array} positions - Array of positions
413
+ * @returns {Array}
414
+ */
415
+ function filterShort(positions) {
416
+ if (!Array.isArray(positions)) return [];
417
+ return positions.filter(isShort);
418
+ }
419
+
420
+ /**
421
+ * Calculate long/short exposure breakdown.
422
+ * @param {Array} positions - Array of positions
423
+ * @returns {Object} { longPct, shortPct, longCount, shortCount }
424
+ */
425
+ function getLongShortBreakdown(positions) {
426
+ if (!Array.isArray(positions) || positions.length === 0) {
427
+ return { longPct: 0, shortPct: 0, longCount: 0, shortCount: 0 };
428
+ }
429
+
430
+ const long = filterLong(positions);
431
+ const short = filterShort(positions);
432
+
433
+ const longValue = calculateTotalValue(long);
434
+ const shortValue = calculateTotalValue(short);
435
+ const totalValue = longValue + shortValue;
436
+
437
+ return {
438
+ longPct: totalValue > 0 ? (longValue / totalValue) * 100 : 0,
439
+ shortPct: totalValue > 0 ? (shortValue / totalValue) * 100 : 0,
440
+ longCount: long.length,
441
+ shortCount: short.length
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Summarize portfolio snapshot.
447
+ * @param {Object} row - portfolio_snapshots row
448
+ * @returns {Object}
449
+ */
450
+ function summarize(row) {
451
+ const data = extractPortfolioData(row);
452
+ if (!data) return null;
453
+
454
+ const positions = extractPositions(data);
455
+ const longShort = getLongShortBreakdown(positions);
456
+
457
+ return {
458
+ userId: getUserId(row),
459
+ username: getUsername(data),
460
+
461
+ // Positions
462
+ positionCount: positions.length,
463
+ totalValue: calculateTotalValue(positions),
464
+ totalInvested: calculateTotalInvested(positions),
465
+
466
+ // Performance
467
+ profitPercent: calculateWeightedProfitPercent(positions),
468
+ winRatio: calculateWinRatio(positions),
469
+
470
+ // Exposure
471
+ exposure: getExposure(data),
472
+ cashPercent: getCreditByUnrealizedEquity(data),
473
+
474
+ // Direction
475
+ longPct: longShort.longPct,
476
+ shortPct: longShort.shortPct,
477
+
478
+ // Top holdings
479
+ topPositions: getTopPositions(positions, 5).map(pos => ({
480
+ instrumentId: getInstrumentId(pos),
481
+ value: getValue(pos),
482
+ profit: getNetProfit(pos),
483
+ direction: getDirection(pos)
484
+ }))
485
+ };
486
+ }
487
+
488
+ module.exports = {
489
+ // Data extraction
490
+ extractPortfolioData,
491
+ getUserId,
492
+ getUsername,
493
+ extractPositions,
494
+ extractMirrors,
495
+ extractPositionsByType,
496
+ extractPositionsByIndustry,
497
+
498
+ // Cash balance
499
+ getCreditByRealizedEquity,
500
+ getCreditByUnrealizedEquity,
501
+ getExposure,
502
+
503
+ // Position accessors
504
+ getInstrumentId,
505
+ getInstrumentTypeId,
506
+ getDirection,
507
+ isLong,
508
+ isShort,
509
+ getInvested,
510
+ getNetProfit,
511
+ getValue,
512
+
513
+ // Calculations
514
+ calculateTotalValue,
515
+ calculateTotalInvested,
516
+ calculateWeightedProfitPercent,
517
+ calculateWinRatio,
518
+
519
+ // Grouping
520
+ groupBySector,
521
+ calculateSectorExposure,
522
+
523
+ // Filtering
524
+ filterLong,
525
+ filterShort,
526
+ getLongShortBreakdown,
527
+
528
+ // Ranking
529
+ getTopPositions,
530
+ getProfitablePositions,
531
+
532
+ // Summary
533
+ summarize
534
+ };