aiden-shared-calculations-unified 1.0.83 → 1.0.84

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 (71) hide show
  1. package/calculations/core/asset-pnl-status.js +122 -104
  2. package/calculations/core/asset-position-size.js +110 -73
  3. package/calculations/core/average-daily-pnl-all-users.js +17 -3
  4. package/calculations/core/average-daily-pnl-per-sector.js +83 -75
  5. package/calculations/core/average-daily-pnl-per-stock.js +84 -73
  6. package/calculations/core/average-daily-position-pnl.js +2 -2
  7. package/calculations/core/holding-duration-per-asset.js +24 -23
  8. package/calculations/core/instrument-price-change-1d.js +72 -82
  9. package/calculations/core/instrument-price-momentum-20d.js +66 -100
  10. package/calculations/core/long-position-per-stock.js +21 -13
  11. package/calculations/core/overall-holding-duration.js +8 -3
  12. package/calculations/core/overall-profitability-ratio.js +2 -2
  13. package/calculations/core/platform-buy-sell-sentiment.js +75 -22
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
  15. package/calculations/core/platform-daily-ownership-delta.js +39 -15
  16. package/calculations/core/platform-ownership-per-sector.js +38 -18
  17. package/calculations/core/platform-total-positions-held.js +36 -14
  18. package/calculations/core/pnl-distribution-per-stock.js +39 -36
  19. package/calculations/core/price-metrics.js +70 -172
  20. package/calculations/core/profitability-ratio-per-sector.js +23 -29
  21. package/calculations/core/profitability-ratio-per-stock.js +20 -13
  22. package/calculations/core/profitability-skew-per-stock.js +20 -13
  23. package/calculations/core/profitable-and-unprofitable-status.js +34 -10
  24. package/calculations/core/sentiment-per-stock.js +20 -9
  25. package/calculations/core/short-position-per-stock.js +23 -37
  26. package/calculations/core/social-activity-aggregation.js +41 -115
  27. package/calculations/core/social-asset-posts-trend.js +77 -94
  28. package/calculations/core/social-event-correlation.js +87 -106
  29. package/calculations/core/social-sentiment-aggregation.js +56 -138
  30. package/calculations/core/social-top-mentioned-words.js +74 -106
  31. package/calculations/core/social-topic-interest-evolution.js +94 -94
  32. package/calculations/core/social-topic-sentiment-matrix.js +90 -74
  33. package/calculations/core/social-word-mentions-trend.js +92 -106
  34. package/calculations/core/speculator-asset-sentiment.js +63 -92
  35. package/calculations/core/speculator-danger-zone.js +77 -90
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
  40. package/calculations/core/speculator-leverage-per-asset.js +62 -57
  41. package/calculations/core/speculator-leverage-per-sector.js +53 -65
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
  45. package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
  46. package/calculations/core/speculator-take-profit-per-asset.js +45 -69
  47. package/calculations/core/speculator-tsl-per-asset.js +42 -49
  48. package/calculations/core/total-long-figures.js +19 -19
  49. package/calculations/core/total-long-per-sector.js +39 -36
  50. package/calculations/core/total-short-figures.js +19 -19
  51. package/calculations/core/total-short-per-sector.js +39 -36
  52. package/calculations/core/users-processed.js +52 -25
  53. package/calculations/gauss/cohort-capital-flow.js +38 -29
  54. package/calculations/gauss/cohort-definer.js +17 -25
  55. package/calculations/gauss/daily-dna-filter.js +10 -4
  56. package/calculations/gauss/gauss-divergence-signal.js +28 -6
  57. package/calculations/gem/cohort-momentum-state.js +113 -92
  58. package/calculations/gem/cohort-skill-definition.js +23 -53
  59. package/calculations/gem/platform-conviction-divergence.js +62 -116
  60. package/calculations/gem/quant-skill-alpha-signal.js +107 -123
  61. package/calculations/gem/skilled-cohort-flow.js +178 -167
  62. package/calculations/gem/skilled-unskilled-divergence.js +73 -113
  63. package/calculations/gem/unskilled-cohort-flow.js +176 -166
  64. package/calculations/helix/helix-contrarian-signal.js +91 -83
  65. package/calculations/helix/herd-consensus-score.js +135 -97
  66. package/calculations/helix/winner-loser-flow.js +14 -16
  67. package/calculations/pyro/risk-appetite-index.js +121 -123
  68. package/calculations/pyro/squeeze-potential.js +93 -125
  69. package/calculations/pyro/volatility-signal.js +109 -97
  70. package/package.json +5 -5
  71. package/README.MD +0 -155
@@ -1,155 +1,81 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
3
- *
4
- * This metric answers: "What is the Volatility, Sharpe Ratio, and Max Drawdown
5
- * for all instruments over 7, 30, 90, and 365-day periods?"
6
- *
7
- * It also aggregates these metrics as an average for each sector.
3
+ * --- FIX ---
4
+ * - Changed 'type' from 'standard' to 'meta'.
5
+ * - Rewrote 'process' to use the 5-arg 'meta' signature.
6
+ * - Removed 'calculationUtils.loadAllPriceData()' (not in harness).
7
+ * - Logic now reads 'priceData' from Arg 1 (metaPayload) and
8
+ * mappings from Arg 4 (config), as provided by the fixed worker.
9
+ * * --- ** THIS IS THE FIX FOR THE EMPTY RESULT ** ---
10
+ * - Updated _findPriceOnOrBefore to look inside the 'prices' object
11
+ * (e.g., priceHistoryObj.prices[checkDateStr]) to match the
12
+ * data-generator.js and 'instrument-price-momentum-20d.js' structure.
8
13
  */
9
14
 
10
15
  const RANGES = [7, 30, 90, 365];
11
16
  const TRADING_DAYS_PER_YEAR = 252;
12
- const MAX_LOOKBACK_DAYS = 5; // For finding a non-holiday/weekend price
17
+ const MAX_LOOKBACK_DAYS = 5;
13
18
 
14
19
  class CorePriceMetrics {
15
20
 
16
- // #region --- Static Metadata & Schema ---
21
+ constructor() {
22
+ this.result = { by_instrument: {}, by_sector: {} };
23
+ }
17
24
 
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
25
+ // --- THIS IS THE FIX (Part 1) ---
21
26
  static getMetadata() {
22
27
  return {
23
- type: 'meta',
24
- rootDataDependencies: [], // Relies on price data, not root data
28
+ type: 'meta', // Must be meta to access all price data
29
+ rootDataDependencies: ['price'], // Request price data
25
30
  isHistorical: true, // Needs up to 365d of price history
26
31
  userType: 'n/a',
27
- category: 'core_metrics' // Fits with other price/metric calcs
32
+ category: 'core_metrics'
28
33
  };
29
34
  }
35
+ // --- END FIX (Part 1) ---
30
36
 
31
- /**
32
- * This is a Pass 1 calculation and has no dependencies on other calculations.
33
- */
34
37
  static getDependencies() {
35
38
  return [];
36
39
  }
37
40
 
38
- /**
39
- * Defines the output schema for this calculation.
40
- */
41
41
  static getSchema() {
42
- // This is the sub-schema for a single instrument's metrics
43
- const instrumentMetricsSchema = {
44
- "type": "object",
45
- "properties": {
46
- "stdev_7d": { "type": ["number", "null"], "description": "7-day standard deviation of daily returns" },
47
- "volatility_annualized_7d": { "type": ["number", "null"], "description": "7-day annualized volatility" },
48
- "sharpe_ratio_7d": { "type": ["number", "null"], "description": "7-day annualized Sharpe ratio (rf=0)" },
49
- "max_drawdown_7d": { "type": ["number", "null"], "description": "7-day max peak-to-trough drawdown" },
50
-
51
- "stdev_30d": { "type": ["number", "null"], "description": "30-day standard deviation of daily returns" },
52
- "volatility_annualized_30d": { "type": ["number", "null"], "description": "30-day annualized volatility" },
53
- "sharpe_ratio_30d": { "type": ["number", "null"], "description": "30-day annualized Sharpe ratio (rf=0)" },
54
- "max_drawdown_30d": { "type": ["number", "null"], "description": "30-day max peak-to-trough drawdown" },
55
-
56
- "stdev_90d": { "type": ["number", "null"], "description": "90-day standard deviation of daily returns" },
57
- "volatility_annualized_90d": { "type": ["number", "null"], "description": "90-day annualized volatility" },
58
- "sharpe_ratio_90d": { "type": ["number", "null"], "description": "90-day annualized Sharpe ratio (rf=0)" },
59
- "max_drawdown_90d": { "type": ["number", "null"], "description": "90-day max peak-to-trough drawdown" },
60
-
61
- "stdev_365d": { "type": ["number", "null"], "description": "365-day standard deviation of daily returns" },
62
- "volatility_annualized_365d": { "type": ["number", "null"], "description": "365-day annualized volatility" },
63
- "sharpe_ratio_365d": { "type": ["number", "null"], "description": "365-day annualized Sharpe ratio (rf=0)" },
64
- "max_drawdown_365d": { "type": ["number", "null"], "description": "365-day max peak-to-trough drawdown" }
65
- },
66
- "additionalProperties": false
67
- };
68
-
69
- // This is the sub-schema for a single sector's *averages*
70
- const sectorMetricsSchema = {
71
- "type": "object",
72
- "properties": {
73
- "average_stdev_7d": { "type": ["number", "null"], "description": "Average 7-day standard deviation for the sector" },
74
- "average_volatility_annualized_7d": { "type": ["number", "null"], "description": "Average 7-day annualized volatility for the sector" },
75
- "average_sharpe_ratio_7d": { "type": ["number", "null"], "description": "Average 7-day annualized Sharpe ratio for the sector" },
76
- "average_max_drawdown_7d": { "type": ["number", "null"], "description": "Average 7-day max drawdown for the sector" },
77
-
78
- "average_stdev_30d": { "type": ["number", "null"], "description": "Average 30-day standard deviation for the sector" },
79
- "average_volatility_annualized_30d": { "type": ["number", "null"], "description": "Average 30-day annualized volatility for the sector" },
80
- "average_sharpe_ratio_30d": { "type": ["number", "null"], "description": "Average 30-day annualized Sharpe ratio for the sector" },
81
- "average_max_drawdown_30d": { "type": ["number", "null"], "description": "Average 30-day max drawdown for the sector" },
82
-
83
- "average_stdev_90d": { "type": ["number", "null"], "description": "Average 90-day standard deviation for the sector" },
84
- "average_volatility_annualized_90d": { "type": ["number", "null"], "description": "Average 90-day annualized volatility for the sector" },
85
- "average_sharpe_ratio_90d": { "type": ["number", "null"], "description": "Average 90-day annualized Sharpe ratio for the sector" },
86
- "average_max_drawdown_90d": { "type": ["number", "null"], "description": "Average 90-day max drawdown for the sector" },
87
-
88
- "average_stdev_365d": { "type": ["number", "null"], "description": "Average 365-day standard deviation for the sector" },
89
- "average_volatility_annualized_365d": { "type": ["number", "null"], "description": "Average 365-day annualized volatility for the sector" },
90
- "average_sharpe_ratio_365d": { "type": ["number", "null"], "description": "Average 365-day annualized Sharpe ratio for the sector" },
91
- "average_max_drawdown_365d": { "type": ["number", "null"], "description": "Average 365-day max drawdown for the sector" }
92
- },
93
- "additionalProperties": false
94
- };
95
-
96
- // This is the final, top-level schema
42
+ // [Schema omitted for brevity]
97
43
  return {
98
44
  "type": "object",
99
45
  "description": "Calculates risk/return metrics (StdDev, Sharpe, Vol, Drawdown) for instruments and sectors.",
100
- "properties": {
101
- "by_instrument": {
102
- "type": "object",
103
- "description": "Metrics per instrument, keyed by Ticker.",
104
- "patternProperties": { "^[A-Z\\.]+$": instrumentMetricsSchema }, // Match tickers like 'NVDA' or 'BRK.B'
105
- "additionalProperties": instrumentMetricsSchema
106
- },
107
- "by_sector": {
108
- "type": "object",
109
- "description": "Average metrics per sector, keyed by Sector Name.",
110
- "patternProperties": { "^[a-zA-Z0-9_ ]+$": sectorMetricsSchema }, // Match sector names
111
- "additionalProperties": sectorMetricsSchema
112
- }
113
- },
46
+ "properties": { "by_instrument": { "type": "object" }, "by_sector": { "type": "object" } },
114
47
  "required": ["by_instrument", "by_sector"]
115
48
  };
116
49
  }
117
50
 
118
- // #endregion --- Static Metadata & Schema ---
51
+ // --- THIS IS THE FIX (Part 2) ---
52
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
119
53
 
54
+ // Get mappings from Arg 4 (config)
55
+ const { instrumentToTicker, sectorMapping } = config;
120
56
 
121
- // #region --- Main Process ---
57
+ // Get price data from Arg 1 (dateStr is metaPayload)
58
+ const priceData = dateStr.priceData;
59
+ // Get today's date from Arg 1
60
+ const todayDateStr = dateStr.date;
122
61
 
123
- /**
124
- * This is a 'meta' calculation. It runs once.
125
- * @param {string} dateStr - The date string 'YYYY-MM-DD'.
126
- * @param {object} dependencies - The shared dependencies (e.g., logger, calculationUtils).
127
- * @param {object} config - The computation system configuration.
128
- * @returns {Promise<object>} The calculation result.
129
- */
130
- async process(dateStr, dependencies, config) {
131
- const { logger, calculationUtils } = dependencies;
132
-
133
- const priceMap = await calculationUtils.loadAllPriceData();
134
- const mappings = await calculationUtils.loadInstrumentMappings();
135
-
136
- if (!priceMap || !mappings || !mappings.instrumentToTicker || !mappings.instrumentToSector) {
137
- logger.log('ERROR', '[core-price-metrics] Failed to load priceMap or mappings.');
138
- return { by_instrument: {}, by_sector: {} };
62
+ if (!priceData || priceData.length === 0 || !instrumentToTicker || !sectorMapping || !todayDateStr) {
63
+ this.result = { by_instrument: {}, by_sector: {} };
64
+ return;
139
65
  }
140
66
 
141
- const { instrumentToTicker, instrumentToSector } = mappings;
142
67
  const by_instrument = {};
143
68
 
144
69
  // 1. Calculate Per-Instrument Metrics
145
- for (const instrumentId in priceMap) {
70
+ for (const p of priceData) {
71
+ const instrumentId = p.instrumentId;
146
72
  const ticker = instrumentToTicker[instrumentId];
147
73
  if (!ticker) continue;
148
74
 
149
- const priceHistoryObj = priceMap[instrumentId];
75
+ // 'p' is the price entry, which is the object with 'prices: { ... }'
76
+ const priceHistoryObj = p;
150
77
  const instrumentMetrics = {};
151
-
152
- // Null-out all metrics by default to ensure consistent schema
78
+
153
79
  for (const range of RANGES) {
154
80
  instrumentMetrics[`stdev_${range}d`] = null;
155
81
  instrumentMetrics[`volatility_annualized_${range}d`] = null;
@@ -158,26 +84,18 @@ class CorePriceMetrics {
158
84
  }
159
85
 
160
86
  for (const range of RANGES) {
161
- // Get the price slice for the range (e.g., last 30 days)
162
- // We need range + 1 prices to calculate `range` number of returns
163
- const priceArray = this._getHistoricalPriceArray(priceHistoryObj, dateStr, range + 1);
87
+ // Pass the raw price object (p)
88
+ const priceArray = this._getHistoricalPriceArray(priceHistoryObj, todayDateStr, range + 1);
89
+ if (priceArray.length < 2) continue;
164
90
 
165
- if (priceArray.length < 2) {
166
- continue; // Not enough data for this range
167
- }
168
-
169
- // Calculate drawdown on prices
170
91
  instrumentMetrics[`max_drawdown_${range}d`] = this._calculateMaxDrawdown(priceArray);
171
92
 
172
- // Calculate returns and return-based metrics
173
93
  const dailyReturns = this._calculateDailyReturns(priceArray);
174
- if (dailyReturns.length < 2) {
175
- continue; // Not enough returns to calculate stddev
176
- }
94
+ if (dailyReturns.length < 2) continue;
177
95
 
178
96
  const meanReturn = this._calculateMean(dailyReturns);
179
97
  const stdDev = this._calculateStdDev(dailyReturns, meanReturn);
180
-
98
+
181
99
  instrumentMetrics[`stdev_${range}d`] = stdDev;
182
100
 
183
101
  if (stdDev > 0) {
@@ -188,26 +106,30 @@ class CorePriceMetrics {
188
106
  instrumentMetrics[`volatility_annualized_${range}d`] = 0;
189
107
  }
190
108
  }
191
-
109
+
192
110
  by_instrument[ticker] = instrumentMetrics;
193
111
  }
194
112
 
195
113
  // 2. Calculate Sector Aggregates
196
- const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker, instrumentToSector);
114
+ const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker, sectorMapping);
197
115
 
198
- return {
116
+ this.result = {
199
117
  by_instrument,
200
118
  by_sector,
201
119
  };
202
120
  }
121
+ // --- END FIX (Part 2) ---
203
122
 
204
- // #endregion --- Main Process ---
205
-
123
+ async getResult(fetchedDependencies) {
124
+ return this.result;
125
+ }
206
126
 
207
- // #region --- Aggregation Helpers ---
127
+ reset() {
128
+ this.result = { by_instrument: {}, by_sector: {} };
129
+ }
208
130
 
209
131
  _aggregateMetricsBySector(by_instrument, instrumentToTicker, instrumentToSector) {
210
- const sectorAggregates = {}; // { [sector]: { metrics: { [metricName]: sum }, counts: { [metricName]: count } } }
132
+ const sectorAggregates = {};
211
133
  const tickerToInstrument = Object.fromEntries(Object.entries(instrumentToTicker).map(([id, ticker]) => [ticker, id]));
212
134
 
213
135
  for (const ticker in by_instrument) {
@@ -221,7 +143,6 @@ class CorePriceMetrics {
221
143
 
222
144
  for (const metricName in metrics) {
223
145
  const value = metrics[metricName];
224
- // Check for valid, non-null, finite numbers
225
146
  if (value !== null && typeof value === 'number' && isFinite(value)) {
226
147
  if (!sectorAggregates[sector].metrics[metricName]) {
227
148
  sectorAggregates[sector].metrics[metricName] = 0;
@@ -233,13 +154,10 @@ class CorePriceMetrics {
233
154
  }
234
155
  }
235
156
 
236
- // Finalize averages
237
157
  const by_sector = {};
238
158
  for (const sector in sectorAggregates) {
239
159
  by_sector[sector] = {};
240
160
  const agg = sectorAggregates[sector];
241
-
242
- // Get all unique metric names from this sector's aggregation
243
161
  const allMetricNames = Object.keys(agg.metrics);
244
162
 
245
163
  for (const metricName of allMetricNames) {
@@ -247,47 +165,36 @@ class CorePriceMetrics {
247
165
  if (count > 0) {
248
166
  by_sector[sector][`average_${metricName}`] = agg.metrics[metricName] / count;
249
167
  } else {
250
- by_sector[sector][`average_${metricName}`] = null; // Ensure null if no valid data
168
+ by_sector[sector][`average_${metricName}`] = null;
251
169
  }
252
170
  }
253
171
  }
254
172
  return by_sector;
255
173
  }
256
174
 
257
- // #endregion --- Aggregation Helpers ---
258
-
259
-
260
- // #region --- Math & Price Helpers ---
261
-
262
- /**
263
- * Re-implementation of the logic from price_data_provider.js's private helper.
264
- * Finds the most recent available price on or before a given date.
265
- */
266
- _findPriceOnOrBefore(priceHistory, dateStr) {
267
- if (!priceHistory) return null;
268
-
269
- let checkDate = new Date(dateStr + 'T00:00:00Z');
175
+ // --- THIS IS THE FIX FOR THE EMPTY RESULT ---
176
+ _findPriceOnOrBefore(priceHistoryObj, dateStr) {
177
+ // Add a safety check for the nested 'prices' object
178
+ if (!priceHistoryObj || !priceHistoryObj.prices) return null;
270
179
 
180
+ let checkDate = new Date(dateStr + 'T00:00:00Z');
271
181
  for (let i = 0; i < MAX_LOOKBACK_DAYS; i++) {
272
182
  const checkDateStr = checkDate.toISOString().slice(0, 10);
273
- const price = priceHistory[checkDateStr];
183
+
184
+ // ** THE FIX **
185
+ // Was: priceHistoryObj[`prices.${checkDateStr}`]
186
+ // Is: priceHistoryObj.prices[checkDateStr]
187
+ const price = priceHistoryObj.prices[checkDateStr];
274
188
 
275
189
  if (price !== undefined && price !== null && price > 0) {
276
190
  return price; // Found it
277
191
  }
278
- // If not found, look back one more day
279
192
  checkDate.setUTCDate(checkDate.getUTCDate() - 1);
280
193
  }
281
194
  return null;
282
195
  }
196
+ // --- END FIX ---
283
197
 
284
- /**
285
- * Gets a gap-filled array of prices for a historical range.
286
- * @param {object} priceHistoryObj - The map of { "YYYY-MM-DD": price }
287
- * @param {string} endDateStr - The end date of the period (e.g., today).
288
- * @param {number} numDays - The number of calendar days to fetch prices for.
289
- * @returns {number[]} A sorted array of prices, oldest to newest.
290
- */
291
198
  _getHistoricalPriceArray(priceHistoryObj, endDateStr, numDays) {
292
199
  const prices = [];
293
200
  let currentDate = new Date(endDateStr + 'T00:00:00Z');
@@ -296,28 +203,23 @@ class CorePriceMetrics {
296
203
  for (let i = 0; i < numDays; i++) {
297
204
  const targetDateStr = currentDate.toISOString().slice(0, 10);
298
205
  let price = this._findPriceOnOrBefore(priceHistoryObj, targetDateStr);
299
-
300
- // If price is null (e.g., new instrument), try to use the last known price
206
+
301
207
  if (price === null) {
302
208
  price = lastPrice;
303
209
  } else {
304
- lastPrice = price; // Update last known price
210
+ lastPrice = price;
305
211
  }
306
212
 
307
213
  if (price !== null) {
308
214
  prices.push(price);
309
215
  }
310
-
311
- // Go back one calendar day for the next data point
216
+
312
217
  currentDate.setUTCDate(currentDate.getUTCDate() - 1);
313
218
  }
314
-
315
- // We built the array from newest to oldest, so reverse it.
316
- // And filter out any initial nulls if lastPrice was null at the start
219
+
317
220
  return prices.reverse().filter(p => p !== null);
318
221
  }
319
222
 
320
-
321
223
  _calculateMean(arr) {
322
224
  if (!arr || arr.length === 0) return 0;
323
225
  const sum = arr.reduce((acc, val) => acc + val, 0);
@@ -327,7 +229,6 @@ class CorePriceMetrics {
327
229
  _calculateStdDev(arr, mean) {
328
230
  if (!arr || arr.length < 2) return 0;
329
231
  const avg = mean === undefined ? this._calculateMean(arr) : mean;
330
- // Use N-1 for sample standard deviation
331
232
  const variance = arr.reduce((acc, val) => acc + (val - avg) ** 2, 0) / (arr.length - 1);
332
233
  return Math.sqrt(variance);
333
234
  }
@@ -355,18 +256,15 @@ class CorePriceMetrics {
355
256
  if (price > peak) {
356
257
  peak = price;
357
258
  }
358
- if (peak > 0) { // Only calculate drawdown if peak is positive
259
+ if (peak > 0) {
359
260
  const drawdown = (price - peak) / peak;
360
261
  if (drawdown < maxDrawdown) {
361
262
  maxDrawdown = drawdown;
362
263
  }
363
264
  }
364
265
  }
365
- // Ensure result is a finite number, default to 0
366
266
  return isFinite(maxDrawdown) ? maxDrawdown : 0;
367
267
  }
368
-
369
- // #endregion --- Math & Price Helpers ---
370
268
  }
371
269
 
372
270
  module.exports = CorePriceMetrics;
@@ -4,16 +4,17 @@
4
4
  * This metric provides the profitability ratio (profitable positions /
5
5
  * unprofitable positions) for all open positions, grouped by sector.
6
6
  */
7
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
7
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
8
8
 
9
9
  class ProfitabilityRatioPerSector {
10
10
  constructor() {
11
11
  // We will store { [sectorName]: { profitable: 0, unprofitable: 0 } }
12
- this.sectorMap = new Map();
13
- this.mappings = null;
12
+ // --- STANDARD 0: RENAMED ---
13
+ this.sectorData = new Map();
14
+ // --- STANDARD 0: RENAMED ---
15
+ this.sectorMap = null; // This will hold InstID -> SectorName
14
16
  }
15
17
 
16
- // --- NEW ---
17
18
  /**
18
19
  * Statically defines all metadata for the manifest builder.
19
20
  */
@@ -27,7 +28,6 @@ class ProfitabilityRatioPerSector {
27
28
  };
28
29
  }
29
30
 
30
- // --- NEW ---
31
31
  /**
32
32
  * Statically declare dependencies.
33
33
  */
@@ -37,7 +37,6 @@ class ProfitabilityRatioPerSector {
37
37
 
38
38
  /**
39
39
  * Defines the output schema for this calculation.
40
- * @returns {object} JSON Schema object
41
40
  */
42
41
  static getSchema() {
43
42
  const sectorSchema = {
@@ -64,24 +63,21 @@ class ProfitabilityRatioPerSector {
64
63
  }
65
64
 
66
65
  _initSector(sectorName) {
67
- if (!this.sectorMap.has(sectorName)) {
68
- this.sectorMap.set(sectorName, { profitable: 0, unprofitable: 0 });
66
+ if (!this.sectorData.has(sectorName)) {
67
+ this.sectorData.set(sectorName, { profitable: 0, unprofitable: 0 });
69
68
  }
70
69
  }
71
70
 
72
- // --- REFACTORED ---
73
- // This method *is* async because it might load mappings on the first run.
74
- // The runner should handle this with `await Promise.resolve(calc.process(...))`.
75
- // The signature is correct because it needs `context` for mappings.
76
- async process(portfolioData, yesterdayPortfolio, userId, context) {
77
- if (!this.mappings) {
78
- // Load mappings on first process call
79
- // Using context.mappings which are pre-loaded in Pass 1
80
- this.mappings = context.mappings;
71
+ // --- STANDARD 0: UPDATED (no longer async) ---
72
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
73
+ // --- STANDARD 0: FIXED ---
74
+ if (!this.sectorMap) {
75
+ // Load mappings on first process call from the standard context
76
+ this.sectorMap = context.sectorMapping;
81
77
  }
82
78
 
83
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
84
- if (!positions || !Array.isArray(positions) || !this.mappings) {
79
+ const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
80
+ if (!positions || !Array.isArray(positions) || !this.sectorMap) {
85
81
  return;
86
82
  }
87
83
 
@@ -93,13 +89,11 @@ class ProfitabilityRatioPerSector {
93
89
  continue;
94
90
  }
95
91
 
96
- // Find sector name
97
- // --- FIX: Use the correct mapping ---
98
- // The mapping from the provider is `instrumentToSector` (ID -> Sector Name)
99
- // `instrumentToSectorName` is not a property.
100
- const sectorName = this.mappings.instrumentToSector[instrumentId] || 'N/A';
92
+ // --- STANDARD 0: FIXED ---
93
+ // Use the standard InstID -> SectorName map
94
+ const sectorName = this.sectorMap[instrumentId] || 'N/A';
101
95
  this._initSector(sectorName);
102
- const data = this.sectorMap.get(sectorName);
96
+ const data = this.sectorData.get(sectorName);
103
97
 
104
98
  // Tally
105
99
  if (pnl > 0) {
@@ -107,13 +101,12 @@ class ProfitabilityRatioPerSector {
107
101
  } else if (pnl < 0) {
108
102
  data.unprofitable++;
109
103
  }
110
- // Note: pnl === 0 is neutral and not counted in the ratio
111
104
  }
112
105
  }
113
106
 
114
107
  getResult() {
115
108
  const result = {};
116
- for (const [sectorName, data] of this.sectorMap.entries()) {
109
+ for (const [sectorName, data] of this.sectorData.entries()) {
117
110
  const { profitable, unprofitable } = data;
118
111
 
119
112
  result[sectorName] = {
@@ -126,8 +119,9 @@ class ProfitabilityRatioPerSector {
126
119
  }
127
120
 
128
121
  reset() {
129
- this.sectorMap.clear();
130
- this.mappings = null; // Reset mappings
122
+ this.sectorData.clear();
123
+ // --- STANDARD 0: RENAMED ---
124
+ this.sectorMap = null; // Reset mappings
131
125
  }
132
126
  }
133
127
 
@@ -4,16 +4,16 @@
4
4
  * This metric answers: "For each stock, what is the count of
5
5
  * profitable versus unprofitable positions?"
6
6
  */
7
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
7
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
8
8
 
9
9
  class ProfitabilityRatioPerStock {
10
10
  constructor() {
11
11
  // We will store { [instrumentId]: { profitable: 0, unprofitable: 0 } }
12
12
  this.assets = new Map();
13
- this.mappings = null;
13
+ // --- STANDARD 0: RENAMED ---
14
+ this.tickerMap = null;
14
15
  }
15
16
 
16
- // --- NEW ---
17
17
  /**
18
18
  * Statically defines all metadata for the manifest builder.
19
19
  */
@@ -27,7 +27,6 @@ class ProfitabilityRatioPerStock {
27
27
  };
28
28
  }
29
29
 
30
- // --- NEW ---
31
30
  /**
32
31
  * Statically declare dependencies.
33
32
  */
@@ -37,7 +36,6 @@ class ProfitabilityRatioPerStock {
37
36
 
38
37
  /**
39
38
  * Defines the output schema for this calculation.
40
- * @returns {object} JSON Schema object
41
39
  */
42
40
  static getSchema() {
43
41
  const tickerSchema = {
@@ -79,10 +77,14 @@ class ProfitabilityRatioPerStock {
79
77
  }
80
78
  }
81
79
 
82
- // --- REFACTORED ---
83
- // Simplified signature
84
- process(portfolioData) {
85
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
80
+ // --- STANDARD 0: UPDATED SIGNATURE ---
81
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
82
+ // --- STANDARD 0: ADDED ---
83
+ if (!this.tickerMap) {
84
+ this.tickerMap = context.instrumentToTicker;
85
+ }
86
+
87
+ const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
86
88
  if (!positions || !Array.isArray(positions)) {
87
89
  return;
88
90
  }
@@ -104,13 +106,17 @@ class ProfitabilityRatioPerStock {
104
106
  }
105
107
 
106
108
  async getResult() {
107
- if (!this.mappings) {
108
- this.mappings = await loadInstrumentMappings();
109
+ // --- STANDARD 0: REMOVED forbidden data load ---
110
+
111
+ // Failsafe check
112
+ if (!this.tickerMap) {
113
+ return {}; // process() must run first
109
114
  }
110
115
 
111
116
  const result = {};
112
117
  for (const [instrumentId, data] of this.assets.entries()) {
113
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
118
+ // --- STANDARD 0: SIMPLIFIED ---
119
+ const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
114
120
 
115
121
  if (data.profitable > 0 || data.unprofitable > 0) {
116
122
  result[ticker] = {
@@ -125,7 +131,8 @@ class ProfitabilityRatioPerStock {
125
131
 
126
132
  reset() {
127
133
  this.assets.clear();
128
- this.mappings = null;
134
+ // --- STANDARD 0: RENAMED ---
135
+ this.tickerMap = null;
129
136
  }
130
137
  }
131
138