aiden-shared-calculations-unified 1.0.86 → 1.0.88

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 (80) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +96 -0
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
  3. package/calculations/core/asset-cost-basis-profile.js +127 -0
  4. package/calculations/core/asset-pnl-status.js +36 -106
  5. package/calculations/core/asset-position-size.js +40 -91
  6. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  7. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  8. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  9. package/calculations/core/average-daily-position-pnl.js +19 -49
  10. package/calculations/core/holding-duration-per-asset.js +25 -127
  11. package/calculations/core/instrument-price-change-1d.js +30 -49
  12. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  13. package/calculations/core/long-position-per-stock.js +39 -68
  14. package/calculations/core/overall-holding-duration.js +16 -87
  15. package/calculations/core/overall-profitability-ratio.js +11 -40
  16. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  17. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  18. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  19. package/calculations/core/platform-ownership-per-sector.js +45 -96
  20. package/calculations/core/platform-total-positions-held.js +20 -80
  21. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  22. package/calculations/core/price-metrics.js +95 -206
  23. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  24. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  25. package/calculations/core/profitability-skew-per-stock.js +41 -94
  26. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  27. package/calculations/core/sentiment-per-stock.js +24 -77
  28. package/calculations/core/short-position-per-stock.js +35 -43
  29. package/calculations/core/social-activity-aggregation.js +26 -49
  30. package/calculations/core/social-asset-posts-trend.js +38 -94
  31. package/calculations/core/social-event-correlation.js +26 -93
  32. package/calculations/core/social-sentiment-aggregation.js +20 -44
  33. package/calculations/core/social-top-mentioned-words.js +35 -87
  34. package/calculations/core/social-topic-interest-evolution.js +22 -111
  35. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  36. package/calculations/core/social-word-mentions-trend.js +27 -104
  37. package/calculations/core/speculator-asset-sentiment.js +31 -72
  38. package/calculations/core/speculator-danger-zone.js +48 -84
  39. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  40. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  41. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  42. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  43. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  44. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  45. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  46. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  47. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  48. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  49. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  50. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  51. package/calculations/core/test..js +0 -0
  52. package/calculations/core/total-long-figures.js +16 -31
  53. package/calculations/core/total-long-per-sector.js +39 -61
  54. package/calculations/core/total-short-figures.js +13 -32
  55. package/calculations/core/total-short-per-sector.js +39 -61
  56. package/calculations/core/users-processed.js +11 -46
  57. package/calculations/gauss/cohort-capital-flow.js +54 -173
  58. package/calculations/gauss/cohort-definer.js +77 -163
  59. package/calculations/gauss/daily-dna-filter.js +29 -83
  60. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  61. package/calculations/gem/cohort-momentum-state.js +27 -72
  62. package/calculations/gem/cohort-skill-definition.js +36 -52
  63. package/calculations/gem/platform-conviction-divergence.js +18 -60
  64. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  65. package/calculations/gem/skilled-cohort-flow.js +67 -175
  66. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  67. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  68. package/calculations/ghost-book/cost-basis-density.js +79 -0
  69. package/calculations/ghost-book/liquidity-vacuum.js +52 -0
  70. package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
  71. package/calculations/helix/helix-contrarian-signal.js +20 -114
  72. package/calculations/helix/herd-consensus-score.js +42 -124
  73. package/calculations/helix/winner-loser-flow.js +36 -118
  74. package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
  75. package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
  76. package/calculations/predicative-alpha/mimetic-latency.js +124 -0
  77. package/calculations/pyro/risk-appetite-index.js +33 -74
  78. package/calculations/pyro/squeeze-potential.js +30 -87
  79. package/calculations/pyro/volatility-signal.js +33 -78
  80. package/package.json +1 -1
@@ -1,90 +1,60 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for average P&L per position.
3
- *
4
- * This metric answers: "What was the average P&L across all
5
- * individual positions held by the crowd today?"
6
- *
7
- * This is different from "average P&L per user" as a user
8
- * can have multiple positions.
2
+ * @fileoverview Calculation (Pass 1) for average P&L % per position.
3
+ * REFACTORED: Uses P&L %.
9
4
  */
10
5
  class AverageDailyPositionPnl {
11
6
  constructor() {
12
- this.totalPnl = 0;
7
+ this.totalPnlPct = 0;
13
8
  this.positionCount = 0;
14
9
  }
15
10
 
16
- // --- NEW ---
17
- /**
18
- * Statically defines all metadata for the manifest builder.
19
- */
20
11
  static getMetadata() {
21
12
  return {
22
13
  type: 'standard',
23
- rootDataDependencies: ['portfolio'], // Needs portfolio positions
14
+ rootDataDependencies: ['portfolio'],
24
15
  isHistorical: false,
25
16
  userType: 'all',
26
17
  category: 'core_pnl'
27
18
  };
28
19
  }
29
20
 
30
- // --- NEW ---
31
- /**
32
- * Statically declare dependencies.
33
- */
34
- static getDependencies() {
35
- return [];
36
- }
21
+ static getDependencies() { return []; }
37
22
 
38
- /**
39
- * Defines the output schema for this calculation.
40
- * @returns {object} JSON Schema object
41
- */
42
23
  static getSchema() {
43
24
  return {
44
25
  "type": "object",
45
- "description": "Calculates the average daily P&L across all individual positions.",
46
26
  "properties": {
47
- "average_pnl": {
48
- "type": "number",
49
- "description": "The average daily P&L per position (Total P&L / Position Count)."
50
- },
51
- "total_pnl": {
52
- "type": "number",
53
- "description": "The sum of all daily P&L from all positions."
54
- },
55
- "position_count": {
56
- "type": "number",
57
- "description": "The total number of positions processed."
58
- }
27
+ "avg_position_pnl_pct": { "type": "number" },
28
+ "aggregate_pnl_pct_sum": { "type": "number" },
29
+ "total_positions": { "type": "number" }
59
30
  },
60
- "required": ["average_pnl", "total_pnl", "position_count"]
31
+ "required": ["avg_position_pnl_pct", "aggregate_pnl_pct_sum", "total_positions"]
61
32
  };
62
33
  }
63
34
 
64
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
65
- const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
66
- if (!positions || !Array.isArray(positions)) {
67
- return;
68
- }
35
+ process(context) {
36
+ const { extract } = context.math;
37
+ const { user } = context;
38
+
39
+ const positions = extract.getPositions(user.portfolio.today, user.type);
69
40
 
70
41
  for (const pos of positions) {
71
- this.totalPnl += pos.NetProfit || 0;
42
+ this.totalPnlPct += extract.getNetProfit(pos);
72
43
  this.positionCount++;
73
44
  }
74
45
  }
75
46
 
76
47
  getResult() {
77
48
  return {
78
- average_pnl: (this.positionCount > 0) ? (this.totalPnl / this.positionCount) : 0,
79
- total_pnl: this.totalPnl,
80
- position_count: this.positionCount
49
+ avg_position_pnl_pct: (this.positionCount > 0) ? (this.totalPnlPct / this.positionCount) : 0,
50
+ aggregate_pnl_pct_sum: this.totalPnlPct,
51
+ total_positions: this.positionCount
81
52
  };
82
53
  }
83
54
 
84
55
  reset() {
85
- this.totalPnl = 0;
56
+ this.totalPnlPct = 0;
86
57
  this.positionCount = 0;
87
58
  }
88
59
  }
89
-
90
60
  module.exports = AverageDailyPositionPnl;
@@ -1,162 +1,60 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1) for user behaviour.
3
- *
4
- * REFACTOR: This metric now answers: "What is the average holding duration
5
- * (in hours) for *closed* positions, averaged across all users, grouped by asset?"
6
- *
7
- * This calculation now uses the 'history' data source, not 'portfolio'.
3
+ * REFACTORED: Uses context.math.history.getDailyHistory().
8
4
  */
9
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
10
-
11
-
12
5
  class HoldingDurationPerAsset {
13
6
  constructor() {
14
- // { [instrumentId]: { sum_hours: 0, count: 0 } }
15
- // 'sum_hours' will be the sum of user-level averages
16
- // 'count' will be the number of users who traded that asset
17
7
  this.assets = new Map();
18
- // --- STANDARD 0: RENAMED ---
19
8
  this.tickerMap = null;
20
9
  }
21
10
 
22
- /**
23
- * Statically defines all metadata for the manifest builder.
24
- */
25
11
  static getMetadata() {
26
- return {
27
- type: 'standard',
28
- rootDataDependencies: ['history'], // It only needs the history doc
29
- isHistorical: false, // It only needs today's history doc
30
- userType: 'all',
31
- category: 'core_metrics'
32
- };
12
+ return { type: 'standard', rootDataDependencies: ['history'], isHistorical: false, userType: 'all', category: 'core_metrics' };
33
13
  }
34
-
35
- /**
36
- * Statically declare dependencies.
37
- */
38
- static getDependencies() {
39
- return [];
40
- }
41
-
42
- /**
43
- * Defines the output schema for this calculation.
44
- */
14
+ static getDependencies() { return []; }
45
15
  static getSchema() {
46
- const tickerSchema = {
47
- "type": "object",
48
- "properties": {
49
- "avg_duration_hours": {
50
- "type": "number",
51
- "description": "Average holding duration in hours (averaged across all users)."
52
- },
53
- "count": {
54
- "type": "number",
55
- "description": "Count of users used in average."
56
- }
57
- },
58
- "required": ["avg_duration_hours", "count"]
59
- };
60
-
61
- return {
62
- "type": "object",
63
- "description": "Calculates the average holding duration (in hours) for closed positions per asset, averaged across all users.",
64
- "patternProperties": {
65
- "^.*$": tickerSchema // Ticker
66
- },
67
- "additionalProperties": tickerSchema
68
- };
16
+ const tickerSchema = { "type": "object", "properties": { "avg_duration_hours": { "type": "number" }, "count": { "type": "number" } }, "required": ["avg_duration_hours", "count"] };
17
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
69
18
  }
70
19
 
71
20
  _initAsset(instrumentId) {
72
- if (!this.assets.has(instrumentId)) {
73
- this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
74
- }
21
+ if (!this.assets.has(instrumentId)) this.assets.set(instrumentId, { sum_hours: 0, count: 0 });
75
22
  }
76
23
 
77
- /**
78
- * Process data from the 'history' root data source.
79
- */
80
- process(
81
- todayPortfolio,
82
- yesterdayPortfolio,
83
- userId,
84
- context,
85
- todayInsights,
86
- yesterdayInsights,
87
- todaySocialPostInsights,
88
- yesterdaySocialPostInsights,
89
- todayHistory
90
- ) {
91
- // --- STANDARD 0: FIXED ---
92
- if (!this.tickerMap) {
93
- this.tickerMap = context.instrumentToTicker;
94
- }
24
+ process(context) {
25
+ const { history } = context.math;
26
+ const { mappings, user } = context;
27
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
95
28
 
96
- // ---
97
- // FIX: The test harness injects data based on getMetadata().
98
- // Because rootDataDependencies is ONLY ['history'], the test harness
99
- // places the history object in the *first* argument (todayPortfolio).
100
- // ---
101
- const historyData = todayPortfolio; // NOT todayHistory
102
- if (!historyData || !Array.isArray(historyData.assets)) {
103
- return;
104
- }
29
+ // V4: Strict Access via Math Layer
30
+ const historyDoc = history.getDailyHistory(user);
31
+ const tradedAssets = history.getTradedAssets(historyDoc);
105
32
 
106
- // 2. Iterate over the aggregated assets in the history doc
107
- for (const asset of historyData.assets) {
108
- const instrumentId = asset.instrumentId;
33
+ for (const asset of tradedAssets) {
34
+ const instId = history.getInstrumentId(asset);
35
+ if (!instId || instId === -1) continue;
109
36
 
110
- // Skip the "all" aggregate entry
111
- if (!instrumentId || instrumentId === -1) {
112
- continue;
113
- }
114
-
115
- const durationMinutes = asset.avgHoldingTimeInMinutes;
116
-
117
- if (typeof durationMinutes === 'number' && durationMinutes > 0) {
118
- this._initAsset(instrumentId);
119
-
120
- const assetData = this.assets.get(instrumentId);
121
-
122
- // Convert minutes to hours and add to the sum
37
+ const durationMinutes = history.getAvgHoldingTimeMinutes(asset);
38
+ if (durationMinutes > 0) {
39
+ this._initAsset(instId);
40
+ const assetData = this.assets.get(instId);
123
41
  assetData.sum_hours += (durationMinutes / 60);
124
-
125
- // Increment count
126
42
  assetData.count++;
127
43
  }
128
44
  }
129
45
  }
130
46
 
131
47
  async getResult() {
132
- // --- STANDARD 0: REMOVED forbidden data load ---
133
-
134
- // Failsafe check
135
- if (!this.tickerMap) {
136
- return {}; // process() must run first
137
- }
138
-
48
+ if (!this.tickerMap) return {};
139
49
  const result = {};
140
- for (const [instrumentId, data] of this.assets.entries()) {
141
- // --- STANDARD 0: SIMPLIFIED ---
142
- const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
143
-
50
+ for (const [instId, data] of this.assets.entries()) {
51
+ const ticker = this.tickerMap[instId] || `id_${instId}`;
144
52
  if (data.count > 0) {
145
- result[ticker] = {
146
- // Calculate the final average (avg of avgs)
147
- avg_duration_hours: data.sum_hours / data.count,
148
- count: data.count
149
- };
53
+ result[ticker] = { avg_duration_hours: data.sum_hours / data.count, count: data.count };
150
54
  }
151
55
  }
152
56
  return result;
153
57
  }
154
-
155
- reset() {
156
- this.assets.clear();
157
- // --- STANDARD 0: RENAMED ---
158
- this.tickerMap = null;
159
- }
58
+ reset() { this.assets.clear(); this.tickerMap = null; }
160
59
  }
161
-
162
60
  module.exports = HoldingDurationPerAsset;
@@ -1,75 +1,64 @@
1
1
  /**
2
- * @fileoverview CORE Product Line (Pass 1)
3
- *
4
- * This 'meta' calculation computes the 1-day percentage price
5
- * change for all instruments.
2
+ * @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
3
+ * REFACTORED: Uses process(context) solely.
6
4
  */
7
-
8
5
  class InstrumentPriceChange1D {
9
6
  constructor() {
10
7
  this.result = {};
11
8
  }
12
9
 
13
- /** Statically defines metadata */
14
10
  static getMetadata() {
15
11
  return {
16
12
  type: 'meta',
17
13
  rootDataDependencies: ['price'],
18
14
  isHistorical: true,
19
- userType: null, // n/a
15
+ userType: 'n/a',
20
16
  category: 'core'
21
17
  };
22
18
  }
23
19
 
24
- /** Statically declare dependencies */
25
- static getDependencies() {
26
- return [];
20
+ static getDependencies() { return []; }
21
+
22
+ static getSchema() {
23
+ const tickerSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "change_1d_pct": { "type": "number" }
27
+ },
28
+ "required": ["change_1d_pct"]
29
+ };
30
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
27
31
  }
28
32
 
29
- /**
30
- * Helper to parse price data from the { instrumentId, prices: { '2025-10-01': 123 } } format
31
- * and return a sorted list.
32
- */
33
33
  _getPrices(priceData) {
34
- if (!priceData || !priceData.prices) {
35
- return [];
36
- }
37
- // FIX: Correctly parse and sort price data
34
+ if (!priceData || !priceData.prices) return [];
38
35
  return Object.entries(priceData.prices)
39
36
  .map(([date, price]) => ({ date, price }))
40
37
  .sort((a, b) => new Date(a.date) - new Date(b.date));
41
38
  }
42
39
 
43
- /**
44
- * process (FIXED for 5-arg META signature)
45
- * Arg 1: metaPayload (contains all root data from worker's "HACK" fix)
46
- * Arg 2: metaRootData (production compliance, unused in test)
47
- * Arg 3: dependencies (logger, etc., unused in test)
48
- * Arg 4: config (mappings)
49
- * Arg 5: fetchedDependencies (unused)
50
- */
51
- process(metaPayload, metaRootData, dependencies, config, fetchedDependencies) {
52
- const results = {};
40
+ process(context) {
41
+ const { mappings, prices } = context;
42
+ const { instrumentToTicker } = mappings;
53
43
 
54
- // FIX: Read from metaPayload (Arg 1) passed by worker.js
55
- const todayPrices = metaPayload.priceData || [];
56
- const yesterdayPrices = metaPayload.yesterdayPriceData || [];
44
+ // Expecting context.prices to be populated by the controller for 'meta' + 'price' dependency
45
+ const todayPrices = prices?.today || [];
46
+ const yesterdayPrices = prices?.yesterday || [];
47
+
48
+ const results = {};
57
49
 
58
- // Create lookup maps for today's and yesterday's prices
59
50
  const todayPriceMap = new Map(todayPrices.map(p => {
60
- const prices = this._getPrices(p);
61
- const lastPrice = prices.length > 0 ? prices[prices.length - 1].price : 0;
51
+ const pList = this._getPrices(p);
52
+ const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
62
53
  return [p.instrumentId, lastPrice];
63
54
  }));
64
55
 
65
56
  const yesterdayPriceMap = new Map(yesterdayPrices.map(p => {
66
- const prices = this._getPrices(p);
67
- const lastPrice = prices.length > 0 ? prices[prices.length - 1].price : 0;
57
+ const pList = this._getPrices(p);
58
+ const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
68
59
  return [p.instrumentId, lastPrice];
69
60
  }));
70
61
 
71
- const { instrumentToTicker } = config; // Read from Arg 4
72
-
73
62
  for (const [instrumentId, todayPrice] of todayPriceMap) {
74
63
  const yesterdayPrice = yesterdayPriceMap.get(instrumentId);
75
64
  const ticker = instrumentToTicker[instrumentId];
@@ -81,21 +70,13 @@ class InstrumentPriceChange1D {
81
70
  change_1d_pct: isFinite(changePct) ? changePct : 0
82
71
  };
83
72
  } else {
84
- results[ticker] = {
85
- change_1d_pct: 0
86
- };
73
+ results[ticker] = { change_1d_pct: 0 };
87
74
  }
88
75
  }
89
76
  this.result = results;
90
77
  }
91
78
 
92
- async getResult() {
93
- return this.result;
94
- }
95
-
96
- reset() {
97
- this.result = {};
98
- }
79
+ async getResult() { return this.result; }
80
+ reset() { this.result = {}; }
99
81
  }
100
-
101
82
  module.exports = InstrumentPriceChange1D;
@@ -1,94 +1,84 @@
1
1
  /**
2
- * @fileoverview CORE Product Line (Pass 1)
3
- *
4
- * This 'meta' calculation computes the 20-day percentage price
5
- * change (momentum) for all instruments.
2
+ * @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
3
+ * REFACTORED: Uses process(context) solely.
6
4
  */
7
-
8
5
  class InstrumentPriceMomentum20D {
9
6
  constructor() {
10
7
  this.result = {};
11
8
  }
12
9
 
13
- /** Statically defines metadata */
14
10
  static getMetadata() {
15
11
  return {
16
12
  type: 'meta',
17
13
  rootDataDependencies: ['price'],
18
- isHistorical: true, // Needs history, but just from one 'today' file
19
- userType: null, // n/a
14
+ isHistorical: true,
15
+ userType: 'n/a',
20
16
  category: 'core'
21
17
  };
22
18
  }
23
19
 
24
- /** Statically declare dependencies */
25
- static getDependencies() {
26
- return [];
20
+ static getDependencies() { return []; }
21
+
22
+ static getSchema() {
23
+ const tickerSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "momentum_20d_pct": { "type": "number" }
27
+ },
28
+ "required": ["momentum_20d_pct"]
29
+ };
30
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
27
31
  }
28
32
 
29
- /**
30
- * Helper to parse price data from the { instrumentId, prices: { '2025-10-01': 123 } } format
31
- * and return a sorted list.
32
- */
33
- _getPrices(priceData) {
34
- if (!priceData || !priceData.prices) {
35
- return [];
33
+ _findPriceOnOrBefore(priceHistoryObj, targetDateStr) {
34
+ if (!priceHistoryObj || !priceHistoryObj.prices) return null;
35
+
36
+ let checkDate = new Date(targetDateStr + 'T00:00:00Z');
37
+ for (let i = 0; i < 5; i++) {
38
+ const str = checkDate.toISOString().slice(0, 10);
39
+ const price = priceHistoryObj.prices[str];
40
+ if (price !== undefined && price !== null && price > 0) return price;
41
+ checkDate.setUTCDate(checkDate.getUTCDate() - 1);
36
42
  }
37
- // FIX: Correctly parse and sort price data
38
- return Object.entries(priceData.prices)
39
- .map(([date, price]) => ({ date, price }))
40
- .sort((a, b) => new Date(a.date) - new Date(b.date));
43
+ return null;
41
44
  }
42
45
 
43
- /**
44
- * process (FIXED for 5-arg META signature)
45
- * Arg 1: metaPayload (contains all root data from worker's "HACK" fix)
46
- * Arg 2: metaRootData (production compliance, unused in test)
47
- * Arg 3: dependencies (logger, etc., unused in test)
48
- * Arg 4: config (mappings)
49
- * Arg 5: fetchedDependencies (unused)
50
- */
51
- process(metaPayload, metaRootData, dependencies, config, fetchedDependencies) {
52
- const results = {};
46
+ process(context) {
47
+ const { mappings, prices, date } = context;
48
+ const { instrumentToTicker } = mappings;
49
+
50
+ // Use historical price data available in context
51
+ const priceData = prices?.history || [];
53
52
 
54
- // FIX: Read from metaPayload (Arg 1) passed by worker.js
55
- // We only need today's data, which contains the 90-day history
56
- const allPriceData = metaPayload.priceData || [];
57
- const { instrumentToTicker } = config; // Read from Arg 4
53
+ const todayStr = date.today;
54
+ const todayDate = new Date(todayStr + 'T00:00:00Z');
55
+ const twentyDaysAgo = new Date(todayDate);
56
+ twentyDaysAgo.setUTCDate(todayDate.getUTCDate() - 20);
57
+ const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
58
58
 
59
- for (const instrumentData of allPriceData) {
60
- const { instrumentId } = instrumentData;
59
+ const results = {};
60
+
61
+ for (const p of priceData) {
62
+ const instrumentId = p.instrumentId;
61
63
  const ticker = instrumentToTicker[instrumentId];
62
64
  if (!ticker) continue;
63
-
64
- const prices = this._getPrices(instrumentData);
65
-
66
- if (prices.length >= 20) {
67
- const currentPrice = prices[prices.length - 1].price;
68
- const price20DaysAgo = prices[prices.length - 20].price;
69
65
 
70
- if (price20DaysAgo > 0) {
71
- const changePct = ((currentPrice - price20DaysAgo) / price20DaysAgo) * 100;
72
- results[ticker] = {
73
- momentum_20d_pct: isFinite(changePct) ? changePct : 0
74
- };
75
- } else {
76
- results[ticker] = { momentum_20d_pct: 0 };
77
- }
66
+ const currentPrice = this._findPriceOnOrBefore(p, todayStr);
67
+ const oldPrice = this._findPriceOnOrBefore(p, oldStr);
68
+
69
+ if (currentPrice && oldPrice && oldPrice > 0) {
70
+ const momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
71
+ results[ticker] = {
72
+ momentum_20d_pct: isFinite(momPct) ? momPct : 0
73
+ };
78
74
  } else {
79
75
  results[ticker] = { momentum_20d_pct: 0 };
80
76
  }
81
77
  }
82
78
  this.result = results;
83
79
  }
84
-
85
- async getResult() {
86
- return this.result;
87
- }
88
80
 
89
- reset() {
90
- this.result = {};
91
- }
81
+ async getResult() { return this.result; }
82
+ reset() { this.result = {}; }
92
83
  }
93
-
94
84
  module.exports = InstrumentPriceMomentum20D;