aiden-shared-calculations-unified 1.0.86 → 1.0.87

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 (70) hide show
  1. package/calculations/core/asset-pnl-status.js +36 -106
  2. package/calculations/core/asset-position-size.js +40 -91
  3. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  4. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  5. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  6. package/calculations/core/average-daily-position-pnl.js +19 -49
  7. package/calculations/core/holding-duration-per-asset.js +25 -127
  8. package/calculations/core/instrument-price-change-1d.js +30 -49
  9. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  10. package/calculations/core/long-position-per-stock.js +39 -68
  11. package/calculations/core/overall-holding-duration.js +16 -87
  12. package/calculations/core/overall-profitability-ratio.js +11 -40
  13. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  15. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  16. package/calculations/core/platform-ownership-per-sector.js +45 -96
  17. package/calculations/core/platform-total-positions-held.js +20 -80
  18. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  19. package/calculations/core/price-metrics.js +95 -206
  20. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  21. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  22. package/calculations/core/profitability-skew-per-stock.js +41 -94
  23. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  24. package/calculations/core/sentiment-per-stock.js +24 -77
  25. package/calculations/core/short-position-per-stock.js +35 -43
  26. package/calculations/core/social-activity-aggregation.js +26 -49
  27. package/calculations/core/social-asset-posts-trend.js +38 -94
  28. package/calculations/core/social-event-correlation.js +26 -93
  29. package/calculations/core/social-sentiment-aggregation.js +20 -44
  30. package/calculations/core/social-top-mentioned-words.js +35 -87
  31. package/calculations/core/social-topic-interest-evolution.js +22 -111
  32. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  33. package/calculations/core/social-word-mentions-trend.js +27 -104
  34. package/calculations/core/speculator-asset-sentiment.js +31 -72
  35. package/calculations/core/speculator-danger-zone.js +48 -84
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  40. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  41. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  45. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  46. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  47. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  48. package/calculations/core/total-long-figures.js +16 -31
  49. package/calculations/core/total-long-per-sector.js +39 -61
  50. package/calculations/core/total-short-figures.js +13 -32
  51. package/calculations/core/total-short-per-sector.js +39 -61
  52. package/calculations/core/users-processed.js +11 -46
  53. package/calculations/gauss/cohort-capital-flow.js +54 -173
  54. package/calculations/gauss/cohort-definer.js +77 -163
  55. package/calculations/gauss/daily-dna-filter.js +29 -83
  56. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  57. package/calculations/gem/cohort-momentum-state.js +27 -72
  58. package/calculations/gem/cohort-skill-definition.js +36 -52
  59. package/calculations/gem/platform-conviction-divergence.js +18 -60
  60. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  61. package/calculations/gem/skilled-cohort-flow.js +67 -175
  62. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  63. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  64. package/calculations/helix/helix-contrarian-signal.js +20 -114
  65. package/calculations/helix/herd-consensus-score.js +42 -124
  66. package/calculations/helix/winner-loser-flow.js +36 -118
  67. package/calculations/pyro/risk-appetite-index.js +33 -74
  68. package/calculations/pyro/squeeze-potential.js +30 -87
  69. package/calculations/pyro/volatility-signal.js +33 -78
  70. package/package.json +1 -1
@@ -1,199 +1,46 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
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.
3
+ * REFACTORED: Uses process(context) solely.
13
4
  */
14
-
15
5
  const RANGES = [7, 30, 90, 365];
16
6
  const TRADING_DAYS_PER_YEAR = 252;
17
- const MAX_LOOKBACK_DAYS = 5;
18
7
 
19
8
  class CorePriceMetrics {
20
-
21
9
  constructor() {
22
10
  this.result = { by_instrument: {}, by_sector: {} };
23
11
  }
24
12
 
25
- // --- THIS IS THE FIX (Part 1) ---
26
13
  static getMetadata() {
27
14
  return {
28
- type: 'meta', // Must be meta to access all price data
29
- rootDataDependencies: ['price'], // Request price data
30
- isHistorical: true, // Needs up to 365d of price history
15
+ type: 'meta',
16
+ rootDataDependencies: ['price'],
17
+ isHistorical: true,
31
18
  userType: 'n/a',
32
19
  category: 'core_metrics'
33
20
  };
34
21
  }
35
- // --- END FIX (Part 1) ---
36
22
 
37
- static getDependencies() {
38
- return [];
39
- }
23
+ static getDependencies() { return []; }
40
24
 
41
25
  static getSchema() {
42
- // [Schema omitted for brevity]
43
26
  return {
44
27
  "type": "object",
45
- "description": "Calculates risk/return metrics (StdDev, Sharpe, Vol, Drawdown) for instruments and sectors.",
46
28
  "properties": { "by_instrument": { "type": "object" }, "by_sector": { "type": "object" } },
47
29
  "required": ["by_instrument", "by_sector"]
48
30
  };
49
31
  }
50
32
 
51
- // --- THIS IS THE FIX (Part 2) ---
52
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
53
-
54
- // Get mappings from Arg 4 (config)
55
- const { instrumentToTicker, sectorMapping } = config;
56
-
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;
61
-
62
- if (!priceData || priceData.length === 0 || !instrumentToTicker || !sectorMapping || !todayDateStr) {
63
- this.result = { by_instrument: {}, by_sector: {} };
64
- return;
65
- }
66
-
67
- const by_instrument = {};
68
-
69
- // 1. Calculate Per-Instrument Metrics
70
- for (const p of priceData) {
71
- const instrumentId = p.instrumentId;
72
- const ticker = instrumentToTicker[instrumentId];
73
- if (!ticker) continue;
74
-
75
- // 'p' is the price entry, which is the object with 'prices: { ... }'
76
- const priceHistoryObj = p;
77
- const instrumentMetrics = {};
78
-
79
- for (const range of RANGES) {
80
- instrumentMetrics[`stdev_${range}d`] = null;
81
- instrumentMetrics[`volatility_annualized_${range}d`] = null;
82
- instrumentMetrics[`sharpe_ratio_${range}d`] = null;
83
- instrumentMetrics[`max_drawdown_${range}d`] = null;
84
- }
85
-
86
- for (const range of RANGES) {
87
- // Pass the raw price object (p)
88
- const priceArray = this._getHistoricalPriceArray(priceHistoryObj, todayDateStr, range + 1);
89
- if (priceArray.length < 2) continue;
90
-
91
- instrumentMetrics[`max_drawdown_${range}d`] = this._calculateMaxDrawdown(priceArray);
92
-
93
- const dailyReturns = this._calculateDailyReturns(priceArray);
94
- if (dailyReturns.length < 2) continue;
95
-
96
- const meanReturn = this._calculateMean(dailyReturns);
97
- const stdDev = this._calculateStdDev(dailyReturns, meanReturn);
98
-
99
- instrumentMetrics[`stdev_${range}d`] = stdDev;
100
-
101
- if (stdDev > 0) {
102
- instrumentMetrics[`sharpe_ratio_${range}d`] = (meanReturn / stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR);
103
- instrumentMetrics[`volatility_annualized_${range}d`] = stdDev * Math.sqrt(TRADING_DAYS_PER_YEAR);
104
- } else {
105
- instrumentMetrics[`sharpe_ratio_${range}d`] = 0;
106
- instrumentMetrics[`volatility_annualized_${range}d`] = 0;
107
- }
108
- }
109
-
110
- by_instrument[ticker] = instrumentMetrics;
111
- }
112
-
113
- // 2. Calculate Sector Aggregates
114
- const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker, sectorMapping);
115
-
116
- this.result = {
117
- by_instrument,
118
- by_sector,
119
- };
120
- }
121
- // --- END FIX (Part 2) ---
122
-
123
- async getResult(fetchedDependencies) {
124
- return this.result;
125
- }
126
-
127
- reset() {
128
- this.result = { by_instrument: {}, by_sector: {} };
129
- }
130
-
131
- _aggregateMetricsBySector(by_instrument, instrumentToTicker, instrumentToSector) {
132
- const sectorAggregates = {};
133
- const tickerToInstrument = Object.fromEntries(Object.entries(instrumentToTicker).map(([id, ticker]) => [ticker, id]));
134
-
135
- for (const ticker in by_instrument) {
136
- const instrumentId = tickerToInstrument[ticker];
137
- const sector = instrumentToSector[instrumentId] || "Unknown";
138
- const metrics = by_instrument[ticker];
139
-
140
- if (!sectorAggregates[sector]) {
141
- sectorAggregates[sector] = { metrics: {}, counts: {} };
142
- }
143
-
144
- for (const metricName in metrics) {
145
- const value = metrics[metricName];
146
- if (value !== null && typeof value === 'number' && isFinite(value)) {
147
- if (!sectorAggregates[sector].metrics[metricName]) {
148
- sectorAggregates[sector].metrics[metricName] = 0;
149
- sectorAggregates[sector].counts[metricName] = 0;
150
- }
151
- sectorAggregates[sector].metrics[metricName] += value;
152
- sectorAggregates[sector].counts[metricName]++;
153
- }
154
- }
155
- }
156
-
157
- const by_sector = {};
158
- for (const sector in sectorAggregates) {
159
- by_sector[sector] = {};
160
- const agg = sectorAggregates[sector];
161
- const allMetricNames = Object.keys(agg.metrics);
162
-
163
- for (const metricName of allMetricNames) {
164
- const count = agg.counts[metricName];
165
- if (count > 0) {
166
- by_sector[sector][`average_${metricName}`] = agg.metrics[metricName] / count;
167
- } else {
168
- by_sector[sector][`average_${metricName}`] = null;
169
- }
170
- }
171
- }
172
- return by_sector;
173
- }
174
-
175
- // --- THIS IS THE FIX FOR THE EMPTY RESULT ---
176
33
  _findPriceOnOrBefore(priceHistoryObj, dateStr) {
177
- // Add a safety check for the nested 'prices' object
178
34
  if (!priceHistoryObj || !priceHistoryObj.prices) return null;
179
-
180
35
  let checkDate = new Date(dateStr + 'T00:00:00Z');
181
- for (let i = 0; i < MAX_LOOKBACK_DAYS; i++) {
36
+ for (let i = 0; i < 5; i++) {
182
37
  const checkDateStr = checkDate.toISOString().slice(0, 10);
183
-
184
- // ** THE FIX **
185
- // Was: priceHistoryObj[`prices.${checkDateStr}`]
186
- // Is: priceHistoryObj.prices[checkDateStr]
187
- const price = priceHistoryObj.prices[checkDateStr];
188
-
189
- if (price !== undefined && price !== null && price > 0) {
190
- return price; // Found it
191
- }
38
+ const price = priceHistoryObj.prices[checkDateStr];
39
+ if (price !== undefined && price !== null && price > 0) return price;
192
40
  checkDate.setUTCDate(checkDate.getUTCDate() - 1);
193
41
  }
194
42
  return null;
195
43
  }
196
- // --- END FIX ---
197
44
 
198
45
  _getHistoricalPriceArray(priceHistoryObj, endDateStr, numDays) {
199
46
  const prices = [];
@@ -203,68 +50,110 @@ class CorePriceMetrics {
203
50
  for (let i = 0; i < numDays; i++) {
204
51
  const targetDateStr = currentDate.toISOString().slice(0, 10);
205
52
  let price = this._findPriceOnOrBefore(priceHistoryObj, targetDateStr);
53
+ if (price === null) price = lastPrice;
54
+ else lastPrice = price;
206
55
 
207
- if (price === null) {
208
- price = lastPrice;
209
- } else {
210
- lastPrice = price;
211
- }
212
-
213
- if (price !== null) {
214
- prices.push(price);
215
- }
216
-
56
+ if (price !== null) prices.push(price);
217
57
  currentDate.setUTCDate(currentDate.getUTCDate() - 1);
218
58
  }
219
-
220
59
  return prices.reverse().filter(p => p !== null);
221
60
  }
222
61
 
223
- _calculateMean(arr) {
224
- if (!arr || arr.length === 0) return 0;
225
- const sum = arr.reduce((acc, val) => acc + val, 0);
226
- return sum / arr.length;
227
- }
62
+ _calculateStats(priceArray) {
63
+ if (priceArray.length < 2) return { stdDev: 0, mean: 0, drawdown: 0 };
228
64
 
229
- _calculateStdDev(arr, mean) {
230
- if (!arr || arr.length < 2) return 0;
231
- const avg = mean === undefined ? this._calculateMean(arr) : mean;
232
- const variance = arr.reduce((acc, val) => acc + (val - avg) ** 2, 0) / (arr.length - 1);
233
- return Math.sqrt(variance);
234
- }
65
+ // Drawdown
66
+ let maxDrawdown = 0, peak = -Infinity;
67
+ for (const price of priceArray) {
68
+ if (price > peak) peak = price;
69
+ if (peak > 0) {
70
+ const dd = (price - peak) / peak;
71
+ if (dd < maxDrawdown) maxDrawdown = dd;
72
+ }
73
+ }
235
74
 
236
- _calculateDailyReturns(prices) {
75
+ // Returns & StdDev
237
76
  const returns = [];
238
- for (let i = 1; i < prices.length; i++) {
239
- const prevPrice = prices[i - 1];
240
- const currPrice = prices[i];
241
- if (prevPrice !== 0 && prevPrice !== null && currPrice !== null) {
242
- returns.push((currPrice - prevPrice) / prevPrice);
243
- } else {
244
- returns.push(0);
245
- }
77
+ for (let i = 1; i < priceArray.length; i++) {
78
+ const prev = priceArray[i - 1];
79
+ const curr = priceArray[i];
80
+ returns.push(prev > 0 ? (curr - prev) / prev : 0);
246
81
  }
247
- return returns;
82
+ const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
83
+ const variance = returns.reduce((a, b) => a + (b - mean) ** 2, 0) / (returns.length - 1);
84
+
85
+ return { stdDev: Math.sqrt(variance), mean, drawdown: maxDrawdown };
248
86
  }
249
87
 
250
- _calculateMaxDrawdown(prices) {
251
- if (!prices || prices.length < 2) return 0;
252
- let maxDrawdown = 0;
253
- let peak = -Infinity;
88
+ process(context) {
89
+ const { mappings, prices, date } = context;
90
+ const { instrumentToTicker, sectorMapping } = mappings;
91
+
92
+ // Uses history from context
93
+ const priceData = prices?.history;
94
+ const todayDateStr = date.today;
95
+
96
+ if (!priceData || !todayDateStr) {
97
+ this.result = { by_instrument: {}, by_sector: {} };
98
+ return;
99
+ }
254
100
 
255
- for (const price of prices) {
256
- if (price > peak) {
257
- peak = price;
101
+ const by_instrument = {};
102
+ const tickerToInstrument = {};
103
+
104
+ for (const p of priceData) {
105
+ const ticker = instrumentToTicker[p.instrumentId];
106
+ if (!ticker) continue;
107
+ tickerToInstrument[ticker] = p.instrumentId;
108
+
109
+ const metrics = {};
110
+ for (const range of RANGES) {
111
+ const priceArray = this._getHistoricalPriceArray(p, todayDateStr, range + 1);
112
+ const stats = this._calculateStats(priceArray);
113
+
114
+ metrics[`stdev_${range}d`] = stats.stdDev;
115
+ metrics[`max_drawdown_${range}d`] = stats.drawdown;
116
+ metrics[`volatility_annualized_${range}d`] = stats.stdDev * Math.sqrt(TRADING_DAYS_PER_YEAR);
117
+ metrics[`sharpe_ratio_${range}d`] = stats.stdDev > 0
118
+ ? (stats.mean / stats.stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR)
119
+ : 0;
258
120
  }
259
- if (peak > 0) {
260
- const drawdown = (price - peak) / peak;
261
- if (drawdown < maxDrawdown) {
262
- maxDrawdown = drawdown;
121
+ by_instrument[ticker] = metrics;
122
+ }
123
+
124
+ // Sector Aggregation
125
+ const sectorAggs = {};
126
+ for (const ticker in by_instrument) {
127
+ const instId = tickerToInstrument[ticker];
128
+ const sector = sectorMapping[instId] || "Unknown";
129
+ if (!sectorAggs[sector]) sectorAggs[sector] = { metrics: {}, counts: {} };
130
+
131
+ const data = by_instrument[ticker];
132
+ for (const key in data) {
133
+ if (!sectorAggs[sector].metrics[key]) {
134
+ sectorAggs[sector].metrics[key] = 0;
135
+ sectorAggs[sector].counts[key] = 0;
136
+ }
137
+ if (data[key] !== null) {
138
+ sectorAggs[sector].metrics[key] += data[key];
139
+ sectorAggs[sector].counts[key]++;
263
140
  }
264
141
  }
265
142
  }
266
- return isFinite(maxDrawdown) ? maxDrawdown : 0;
143
+
144
+ const by_sector = {};
145
+ for (const sector in sectorAggs) {
146
+ by_sector[sector] = {};
147
+ for (const key in sectorAggs[sector].metrics) {
148
+ const count = sectorAggs[sector].counts[key];
149
+ by_sector[sector][`average_${key}`] = count > 0 ? sectorAggs[sector].metrics[key] / count : null;
150
+ }
151
+ }
152
+
153
+ this.result = { by_instrument, by_sector };
267
154
  }
268
- }
269
155
 
156
+ async getResult() { return this.result; }
157
+ reset() { this.result = { by_instrument: {}, by_sector: {} }; }
158
+ }
270
159
  module.exports = CorePriceMetrics;
@@ -1,23 +1,13 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for P&L.
3
- *
4
- * This metric provides the profitability ratio (profitable positions /
5
- * unprofitable positions) for all open positions, grouped by sector.
2
+ * @fileoverview Calculation (Pass 1) for profitability ratio per sector.
3
+ * REFACTORED: Counts (No USD involved).
6
4
  */
7
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
8
-
9
5
  class ProfitabilityRatioPerSector {
10
6
  constructor() {
11
- // We will store { [sectorName]: { profitable: 0, unprofitable: 0 } }
12
- // --- STANDARD 0: RENAMED ---
13
7
  this.sectorData = new Map();
14
- // --- STANDARD 0: RENAMED ---
15
- this.sectorMap = null; // This will hold InstID -> SectorName
8
+ this.sectorMap = null;
16
9
  }
17
10
 
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
21
11
  static getMetadata() {
22
12
  return {
23
13
  type: 'standard',
@@ -28,101 +18,66 @@ class ProfitabilityRatioPerSector {
28
18
  };
29
19
  }
30
20
 
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
- */
41
23
  static getSchema() {
42
24
  const sectorSchema = {
43
25
  "type": "object",
44
26
  "properties": {
45
- "ratio": {
46
- "type": ["number", "null"],
47
- "description": "Ratio of profitable to unprofitable positions (profitable / unprofitable). Null if 0 unprofitable."
48
- },
27
+ "profitability_ratio": { "type": ["number", "null"] },
49
28
  "profitable_count": { "type": "number" },
50
29
  "unprofitable_count": { "type": "number" }
51
30
  },
52
- "required": ["ratio", "profitable_count", "unprofitable_count"]
53
- };
54
-
55
- return {
56
- "type": "object",
57
- "description": "Calculates the profitability ratio (profitable/unprofitable positions) per sector.",
58
- "patternProperties": {
59
- "^.*$": sectorSchema // Sector Name
60
- },
61
- "additionalProperties": sectorSchema
31
+ "required": ["profitability_ratio", "profitable_count", "unprofitable_count"]
62
32
  };
33
+ return { "type": "object", "patternProperties": { "^.*$": sectorSchema } };
63
34
  }
64
35
 
65
- _initSector(sectorName) {
66
- if (!this.sectorData.has(sectorName)) {
67
- this.sectorData.set(sectorName, { profitable: 0, unprofitable: 0 });
36
+ _initSector(sector) {
37
+ if (!this.sectorData.has(sector)) {
38
+ this.sectorData.set(sector, { profitable: 0, unprofitable: 0 });
68
39
  }
69
40
  }
70
41
 
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;
77
- }
42
+ process(context) {
43
+ const { extract } = context.math;
44
+ const { mappings, user } = context;
45
+ if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
78
46
 
79
- const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
80
- if (!positions || !Array.isArray(positions) || !this.sectorMap) {
81
- return;
82
- }
47
+ const positions = extract.getPositions(user.portfolio.today, user.type);
83
48
 
84
49
  for (const pos of positions) {
85
- const instrumentId = pos.InstrumentID;
86
- const pnl = pos.NetProfit;
50
+ const instId = extract.getInstrumentId(pos);
51
+ if (!instId) continue;
87
52
 
88
- if (!instrumentId || typeof pnl !== 'number') {
89
- continue;
90
- }
91
-
92
- // --- STANDARD 0: FIXED ---
93
- // Use the standard InstID -> SectorName map
94
- const sectorName = this.sectorMap[instrumentId] || 'N/A';
95
- this._initSector(sectorName);
96
- const data = this.sectorData.get(sectorName);
53
+ const sector = this.sectorMap[instId] || 'Unknown';
54
+ const pnl = extract.getNetProfit(pos);
97
55
 
98
- // Tally
99
- if (pnl > 0) {
100
- data.profitable++;
101
- } else if (pnl < 0) {
102
- data.unprofitable++;
103
- }
56
+ this._initSector(sector);
57
+ const data = this.sectorData.get(sector);
58
+
59
+ if (pnl > 0) data.profitable++;
60
+ else if (pnl < 0) data.unprofitable++;
104
61
  }
105
62
  }
106
63
 
107
- getResult() {
64
+ async getResult() {
108
65
  const result = {};
109
- for (const [sectorName, data] of this.sectorData.entries()) {
110
- const { profitable, unprofitable } = data;
111
-
112
- result[sectorName] = {
113
- ratio: (unprofitable > 0) ? (profitable / unprofitable) : null,
114
- profitable_count: profitable,
115
- unprofitable_count: unprofitable
116
- };
66
+ for (const [sector, data] of this.sectorData.entries()) {
67
+ if (data.profitable > 0 || data.unprofitable > 0) {
68
+ result[sector] = {
69
+ profitability_ratio: (data.unprofitable > 0) ? (data.profitable / data.unprofitable) : null,
70
+ profitable_count: data.profitable,
71
+ unprofitable_count: data.unprofitable
72
+ };
73
+ }
117
74
  }
118
75
  return result;
119
76
  }
120
77
 
121
78
  reset() {
122
79
  this.sectorData.clear();
123
- // --- STANDARD 0: RENAMED ---
124
- this.sectorMap = null; // Reset mappings
80
+ this.sectorMap = null;
125
81
  }
126
82
  }
127
-
128
83
  module.exports = ProfitabilityRatioPerSector;