aiden-shared-calculations-unified 1.0.91 → 1.0.93

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview CORE Product Line (Pass 1 - Meta)
3
3
  * Calculates annualized volatility using the new priceExtractor.
4
+ * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
5
  */
5
6
  class AssetVolatilityEstimator {
6
7
  constructor() {
@@ -33,21 +34,20 @@ class AssetVolatilityEstimator {
33
34
  }
34
35
 
35
36
  process(context) {
36
- // FIXED: Destructure 'mappings' to resolve real tickers
37
37
  const { math, prices, mappings } = context;
38
-
39
- // FIXED: Destructure 'priceExtractor' directly (matches worker.js injection)
40
38
  const { compute, priceExtractor } = math;
39
+
40
+ if (!prices || !prices.history) return;
41
41
 
42
- // 1. Get ALL histories properly sorted and parsed
42
+ // 1. Get THIS SHARD'S histories
43
43
  const allHistories = priceExtractor.getAllHistories(prices);
44
44
 
45
+ const batchResult = {};
46
+
45
47
  for (const [key, candles] of allHistories.entries()) {
46
- // RESOLUTION FIX:
47
- // 'key' is likely an index string ("0") because mock data is an array.
48
- // We must resolve this to the real Ticker Symbol for the result map.
49
48
  let ticker = key;
50
49
 
50
+ // Resolve ticker (logic maintained from original)
51
51
  if (prices.history && prices.history[key] && prices.history[key].instrumentId) {
52
52
  const instId = prices.history[key].instrumentId;
53
53
  if (mappings && mappings.instrumentToTicker && mappings.instrumentToTicker[instId]) {
@@ -57,7 +57,6 @@ class AssetVolatilityEstimator {
57
57
 
58
58
  if (candles.length < 10) continue;
59
59
 
60
- // 2. Calculate Log Returns
61
60
  const logReturns = [];
62
61
  let lastPrice = 0;
63
62
 
@@ -71,22 +70,23 @@ class AssetVolatilityEstimator {
71
70
  }
72
71
  }
73
72
 
74
- // 3. Filter 30-Day Lookback
75
73
  const LOOKBACK = 30;
76
74
  const relevantReturns = logReturns.slice(-LOOKBACK);
77
75
 
78
76
  if (relevantReturns.length < 5) continue;
79
77
 
80
- // 4. Calculate Stats
81
78
  const stdDev = compute.standardDeviation(relevantReturns);
82
79
  const annualizedVol = stdDev * Math.sqrt(365);
83
80
 
84
- this.result[ticker] = {
81
+ batchResult[ticker] = {
85
82
  volatility_30d: annualizedVol,
86
83
  last_price: lastPrice,
87
84
  data_points: relevantReturns.length
88
85
  };
89
86
  }
87
+
88
+ // Accumulate
89
+ Object.assign(this.result, batchResult);
90
90
  }
91
91
 
92
92
  async getResult() { return this.result; }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for 1-day price change.
3
- * REFACTORED: Uses process(context) solely.
3
+ * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
4
  */
5
5
  class InstrumentPriceChange1D {
6
6
  constructor() {
@@ -29,54 +29,54 @@ class InstrumentPriceChange1D {
29
29
  };
30
30
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
31
31
  }
32
-
33
- _getPrices(priceData) {
34
- if (!priceData || !priceData.prices) return [];
35
- return Object.entries(priceData.prices)
36
- .map(([date, price]) => ({ date, price }))
37
- .sort((a, b) => new Date(a.date) - new Date(b.date));
38
- }
39
32
 
40
33
  process(context) {
41
- const { mappings, prices } = context;
34
+ const { mappings, prices, date, math } = context;
42
35
  const { instrumentToTicker } = mappings;
36
+ const { priceExtractor } = math;
43
37
 
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 = {};
38
+ if (!prices || !prices.history) {
39
+ // Do NOT reset result here, just return if this shard is empty
40
+ return;
41
+ }
49
42
 
50
- const todayPriceMap = new Map(todayPrices.map(p => {
51
- const pList = this._getPrices(p);
52
- const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
53
- return [p.instrumentId, lastPrice];
54
- }));
43
+ const todayStr = date.today;
44
+ const todayDate = new Date(todayStr + 'T00:00:00Z');
45
+ const yesterdayDate = new Date(todayDate);
46
+ yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
47
+ const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
55
48
 
56
- const yesterdayPriceMap = new Map(yesterdayPrices.map(p => {
57
- const pList = this._getPrices(p);
58
- const lastPrice = pList.length > 0 ? pList[pList.length - 1].price : 0;
59
- return [p.instrumentId, lastPrice];
60
- }));
49
+ const batchResults = {};
61
50
 
62
- for (const [instrumentId, todayPrice] of todayPriceMap) {
63
- const yesterdayPrice = yesterdayPriceMap.get(instrumentId);
51
+ // Iterate over THIS SHARD'S instruments
52
+ for (const [instrumentId, priceData] of Object.entries(prices.history)) {
64
53
  const ticker = instrumentToTicker[instrumentId];
65
54
  if (!ticker) continue;
66
55
 
56
+ // Note: getHistory works on the partial 'prices' object just fine
57
+ const history = priceExtractor.getHistory(prices, instrumentId);
58
+ if (history.length === 0) continue;
59
+
60
+ const todayPrice = priceData.prices?.[todayStr];
61
+ const yesterdayPrice = priceData.prices?.[yesterdayStr];
62
+
67
63
  if (todayPrice && yesterdayPrice && yesterdayPrice > 0) {
68
64
  const changePct = ((todayPrice - yesterdayPrice) / yesterdayPrice) * 100;
69
- results[ticker] = {
65
+ batchResults[ticker] = {
70
66
  change_1d_pct: isFinite(changePct) ? changePct : 0
71
67
  };
72
68
  } else {
73
- results[ticker] = { change_1d_pct: 0 };
69
+ batchResults[ticker] = { change_1d_pct: 0 };
74
70
  }
75
71
  }
76
- this.result = results;
72
+
73
+ // Merge batch results into main result
74
+ Object.assign(this.result, batchResults);
77
75
  }
78
76
 
79
77
  async getResult() { return this.result; }
78
+
79
+ // Explicit reset only called by system between full runs
80
80
  reset() { this.result = {}; }
81
81
  }
82
82
  module.exports = InstrumentPriceChange1D;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for 20-day momentum.
3
- * REFACTORED: Uses process(context) solely.
3
+ * FIXED: Supports Sharded/Batched Execution (Accumulates results).
4
4
  */
5
5
  class InstrumentPriceMomentum20D {
6
6
  constructor() {
@@ -30,13 +30,14 @@ class InstrumentPriceMomentum20D {
30
30
  return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
31
31
  }
32
32
 
33
- _findPriceOnOrBefore(priceHistoryObj, targetDateStr) {
34
- if (!priceHistoryObj || !priceHistoryObj.prices) return null;
33
+ _findPriceOnOrBefore(prices, instrumentId, targetDateStr) {
34
+ const priceData = prices.history[instrumentId];
35
+ if (!priceData || !priceData.prices) return null;
35
36
 
36
37
  let checkDate = new Date(targetDateStr + 'T00:00:00Z');
37
38
  for (let i = 0; i < 5; i++) {
38
39
  const str = checkDate.toISOString().slice(0, 10);
39
- const price = priceHistoryObj.prices[str];
40
+ const price = priceData.prices[str];
40
41
  if (price !== undefined && price !== null && price > 0) return price;
41
42
  checkDate.setUTCDate(checkDate.getUTCDate() - 1);
42
43
  }
@@ -47,8 +48,9 @@ class InstrumentPriceMomentum20D {
47
48
  const { mappings, prices, date } = context;
48
49
  const { instrumentToTicker } = mappings;
49
50
 
50
- // Use historical price data available in context
51
- const priceData = prices?.history || [];
51
+ if (!prices || !prices.history) {
52
+ return;
53
+ }
52
54
 
53
55
  const todayStr = date.today;
54
56
  const todayDate = new Date(todayStr + 'T00:00:00Z');
@@ -56,26 +58,28 @@ class InstrumentPriceMomentum20D {
56
58
  twentyDaysAgo.setUTCDate(todayDate.getUTCDate() - 20);
57
59
  const oldStr = twentyDaysAgo.toISOString().slice(0, 10);
58
60
 
59
- const results = {};
61
+ const batchResults = {};
60
62
 
61
- for (const p of priceData) {
62
- const instrumentId = p.instrumentId;
63
+ // Iterate over THIS SHARD'S instruments
64
+ for (const [instrumentId, priceData] of Object.entries(prices.history)) {
63
65
  const ticker = instrumentToTicker[instrumentId];
64
66
  if (!ticker) continue;
65
67
 
66
- const currentPrice = this._findPriceOnOrBefore(p, todayStr);
67
- const oldPrice = this._findPriceOnOrBefore(p, oldStr);
68
+ const currentPrice = this._findPriceOnOrBefore(prices, instrumentId, todayStr);
69
+ const oldPrice = this._findPriceOnOrBefore(prices, instrumentId, oldStr);
68
70
 
69
71
  if (currentPrice && oldPrice && oldPrice > 0) {
70
72
  const momPct = ((currentPrice - oldPrice) / oldPrice) * 100;
71
- results[ticker] = {
73
+ batchResults[ticker] = {
72
74
  momentum_20d_pct: isFinite(momPct) ? momPct : 0
73
75
  };
74
76
  } else {
75
- results[ticker] = { momentum_20d_pct: 0 };
77
+ batchResults[ticker] = { momentum_20d_pct: 0 };
76
78
  }
77
79
  }
78
- this.result = results;
80
+
81
+ // Merge
82
+ Object.assign(this.result, batchResults);
79
83
  }
80
84
 
81
85
  async getResult() { return this.result; }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
3
- * REFACTORED: Uses process(context) solely.
3
+ * FIXED: Supports Sharded/Batched Execution.
4
+ * MOVED: Sector aggregation logic moved to getResult() to handle sharding correctly.
4
5
  */
5
6
  const RANGES = [7, 30, 90, 365];
6
7
  const TRADING_DAYS_PER_YEAR = 252;
@@ -8,6 +9,8 @@ const TRADING_DAYS_PER_YEAR = 252;
8
9
  class CorePriceMetrics {
9
10
  constructor() {
10
11
  this.result = { by_instrument: {}, by_sector: {} };
12
+ // We persist mappings here because we process shard-by-shard
13
+ this.mappings = null;
11
14
  }
12
15
 
13
16
  static getMetadata() {
@@ -87,25 +90,22 @@ class CorePriceMetrics {
87
90
 
88
91
  process(context) {
89
92
  const { mappings, prices, date } = context;
90
- // FIX 1: Use the correct property name 'instrumentToSector'
91
- const { instrumentToTicker, instrumentToSector } = mappings;
93
+ const { instrumentToTicker } = mappings;
94
+
95
+ // Save mappings for the final aggregation step
96
+ if (!this.mappings) this.mappings = mappings;
92
97
 
93
98
  const priceData = prices?.history;
94
99
  const todayDateStr = date.today;
95
100
 
96
101
  if (!priceData || !todayDateStr) {
97
- this.result = { by_instrument: {}, by_sector: {} };
98
102
  return;
99
103
  }
100
104
 
101
- const by_instrument = {};
102
- const tickerToInstrument = {};
103
-
104
- // FIX 2: Iterate over Object.values() since priceData is a map, not an array
105
+ // Iterate over THIS SHARD'S data
105
106
  for (const p of Object.values(priceData)) {
106
107
  const ticker = instrumentToTicker[p.instrumentId];
107
108
  if (!ticker) continue;
108
- tickerToInstrument[ticker] = p.instrumentId;
109
109
 
110
110
  const metrics = {};
111
111
  for (const range of RANGES) {
@@ -119,14 +119,26 @@ class CorePriceMetrics {
119
119
  ? (stats.mean / stats.stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR)
120
120
  : 0;
121
121
  }
122
- by_instrument[ticker] = metrics;
122
+ // Accumulate into by_instrument
123
+ this.result.by_instrument[ticker] = metrics;
124
+ }
125
+ }
126
+
127
+ async getResult() {
128
+ // Perform Sector Aggregation HERE (after all shards are processed)
129
+ const by_instrument = this.result.by_instrument;
130
+ const instrumentToSector = this.mappings?.instrumentToSector || {};
131
+ const instrumentToTicker = this.mappings?.instrumentToTicker || {};
132
+
133
+ // Reverse map ticker -> instrumentId
134
+ const tickerToInstrument = {};
135
+ for(const [id, tick] of Object.entries(instrumentToTicker)) {
136
+ tickerToInstrument[tick] = id;
123
137
  }
124
138
 
125
- // Sector Aggregation
126
139
  const sectorAggs = {};
127
140
  for (const ticker in by_instrument) {
128
141
  const instId = tickerToInstrument[ticker];
129
- // FIX 1 (Usage): Use the corrected variable name
130
142
  const sector = instrumentToSector[instId] || "Unknown";
131
143
 
132
144
  if (!sectorAggs[sector]) sectorAggs[sector] = { metrics: {}, counts: {} };
@@ -152,11 +164,14 @@ class CorePriceMetrics {
152
164
  by_sector[sector][`average_${key}`] = count > 0 ? sectorAggs[sector].metrics[key] / count : null;
153
165
  }
154
166
  }
155
-
156
- this.result = { by_instrument, by_sector };
167
+
168
+ this.result.by_sector = by_sector;
169
+ return this.result;
157
170
  }
158
171
 
159
- async getResult() { return this.result; }
160
- reset() { this.result = { by_instrument: {}, by_sector: {} }; }
172
+ reset() {
173
+ this.result = { by_instrument: {}, by_sector: {} };
174
+ this.mappings = null;
175
+ }
161
176
  }
162
177
  module.exports = CorePriceMetrics;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.91",
3
+ "version": "1.0.93",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -41,7 +41,6 @@ async function withRetry(asyncOperation, operationName = 'Firestore operation')
41
41
  const code = error.code || error.details;
42
42
 
43
43
  if (RETRYABLE_ERROR_CODES.has(code)) {
44
- // Assuming logger is available globally or passed in somehow in the package context
45
44
  if (typeof logger !== 'undefined' && logger.log) {
46
45
  logger.log('WARN', `[Retry ${attempt}/${MAX_RETRIES}] Retrying ${operationName} due to ${code}. Waiting ${backoff}ms...`);
47
46
  } else {