aiden-shared-calculations-unified 1.0.82 → 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 -14
  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 +9 -9
  71. package/README.MD +0 -78
@@ -1,129 +1,137 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for average daily P&L per sector.
2
+ * @fileoverview Core Metric (Pass 2)
3
3
  *
4
- * This metric answers: "What was the average daily P&L for each sector?"
5
- *
6
- * This helps identify which parts of the market the crowd
7
- * is performing well or poorly in.
4
+ * This 'standard' calculation streams all user portfolios
5
+ * and calculates the average daily P&L (in %) for all
6
+ * users, bucketed by sector.
8
7
  */
9
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
8
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
10
9
 
11
10
  class AverageDailyPnlPerSector {
12
11
  constructor() {
13
- // We will store { [sector]: { pnl_sum: 0, user_count: 0 } }
14
- this.sectors = new Map();
15
- this.mappings = null;
12
+ // { [sector]: { pnl_sum: 0, count: 0 } }
13
+ this.sectorBuckets = new Map();
14
+
15
+ // --- STANDARD 0: RENAMED ---
16
+ this.sectorMap = null;
16
17
  }
17
18
 
18
- // --- NEW ---
19
- /**
20
- * Statically defines all metadata for the manifest builder.
21
- */
19
+ /** Statically defines metadata */
22
20
  static getMetadata() {
23
21
  return {
24
22
  type: 'standard',
25
- rootDataDependencies: ['portfolio'], // Needs portfolio positions
26
- isHistorical: false,
23
+ rootDataDependencies: ['portfolio'],
24
+ isHistorical: false, // Reads today's portfolio
27
25
  userType: 'all',
28
- category: 'core_pnl'
26
+ category: 'core'
29
27
  };
30
28
  }
31
29
 
32
- // --- NEW ---
33
- /**
34
- * Statically declare dependencies.
35
- */
30
+ /** Statically declare dependencies */
36
31
  static getDependencies() {
37
32
  return [];
38
33
  }
39
34
 
40
35
  /**
41
36
  * Defines the output schema for this calculation.
42
- * @returns {object} JSON Schema object
43
37
  */
44
38
  static getSchema() {
45
- const sectorSchema = {
39
+ const schema = {
46
40
  "type": "object",
47
- "description": "P&L metrics for a specific sector.",
48
41
  "properties": {
49
- "average_pnl": {
50
- "type": "number",
51
- "description": "Average P&L for this sector (Sum / User Count)."
52
- },
53
- "pnl_sum": {
54
- "type": "number",
55
- "description": "Sum of all P&L for this sector."
56
- },
57
- "user_count": {
58
- "type": "number",
59
- "description": "Count of users with positions in this sector."
60
- }
42
+ "avg_daily_pnl_pct": { "type": "number" },
43
+ "total_pnl_pct": { "type": "number" },
44
+ "user_count": { "type": "number" }
61
45
  },
62
- "required": ["average_pnl", "pnl_sum", "user_count"]
46
+ "required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
63
47
  };
64
-
48
+
65
49
  return {
66
50
  "type": "object",
67
- "description": "Calculates the average daily P&L per sector.",
68
- "patternProperties": {
69
- "^.*$": sectorSchema // Sector name
70
- },
71
- "additionalProperties": sectorSchema
51
+ "description": "Calculates the avg. daily P&L % for all users, bucketed by sector.",
52
+ "patternProperties": { "^.*$": schema },
53
+ "additionalProperties": schema
72
54
  };
73
55
  }
74
-
75
- _initSector(sector) {
76
- if (!this.sectors.has(sector)) {
77
- this.sectors.set(sector, {
78
- pnl_sum: 0,
79
- users: new Set() // Use Set to count unique users
80
- });
56
+
57
+ _init(map, key) {
58
+ if (!map.has(key)) {
59
+ map.set(key, { pnl_sum: 0, count: 0 });
81
60
  }
82
61
  }
83
62
 
84
- process(portfolioData, yesterdayPortfolio, userId, context) {
85
- // This calculation needs the sector mappings from Pass 1 context
86
- if (!this.mappings) {
87
- this.mappings = context.mappings;
63
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
64
+ // --- STANDARD 0: FIXED ---
65
+ if (!this.sectorMap) {
66
+ this.sectorMap = context.sectorMapping;
88
67
  }
68
+
69
+ if (!this.sectorMap) {
70
+ return; // Failsafe if context is missing map
71
+ }
72
+
73
+ let dailyPnl = 0;
74
+
75
+ // Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
76
+ if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
77
+
78
+ dailyPnl = todayPortfolio.NetProfit;
89
79
 
90
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
91
- if (!positions || !Array.isArray(positions) || !this.mappings) {
80
+ // Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
81
+ } else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
82
+
83
+ // Sum the P&L by calculating (Value - Invested) from the aggregates
84
+ dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
85
+ return sum + (agg.Value - agg.Invested);
86
+ }, 0);
87
+
88
+ }
89
+ if (dailyPnl === 0) return; // No P&L
90
+
91
+ const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
92
+ if (!positions || positions.length === 0) {
92
93
  return;
93
94
  }
94
95
 
96
+ // We only care about sectors this user *holds*
97
+ const heldSectors = new Set();
95
98
  for (const pos of positions) {
96
- const instrumentId = pos.InstrumentID;
97
- if (!instrumentId) continue;
98
-
99
- const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
100
- this._initSector(sector);
101
-
102
- const sectorData = this.sectors.get(sector);
103
- sectorData.pnl_sum += pos.NetProfit || 0;
104
- sectorData.users.add(userId);
99
+ if (!pos.InstrumentID) continue;
100
+ // --- STANDARD 0: SIMPLIFIED ---
101
+ const sector = this.sectorMap[pos.InstrumentID];
102
+ if (sector) {
103
+ heldSectors.add(sector);
104
+ }
105
+ }
106
+
107
+ for (const sector of heldSectors) {
108
+ this._init(this.sectorBuckets, sector);
109
+ const sec = this.sectorBuckets.get(sector);
110
+ sec.pnl_sum += dailyPnl;
111
+ sec.count++;
105
112
  }
106
113
  }
107
114
 
108
- getResult() {
115
+ async getResult() {
109
116
  const result = {};
110
- for (const [sector, data] of this.sectors.entries()) {
111
- const userCount = data.users.size;
112
- if (userCount > 0) {
117
+
118
+ for (const [sector, data] of this.sectorBuckets.entries()) {
119
+ if (data.count > 0) {
113
120
  result[sector] = {
114
- average_pnl: data.pnl_sum / userCount,
115
- pnl_sum: data.pnl_sum,
116
- user_count: userCount
121
+ avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
122
+ total_pnl_pct: data.pnl_sum * 100,
123
+ user_count: data.count
117
124
  };
118
125
  }
119
126
  }
127
+
120
128
  return result;
121
129
  }
122
-
130
+
123
131
  reset() {
124
- this.sectors.clear();
125
- this.mappings = null;
132
+ this.sectorBuckets.clear();
133
+ // --- STANDARD 0: RENAMED ---
134
+ this.sectorMap = null;
126
135
  }
127
136
  }
128
-
129
137
  module.exports = AverageDailyPnlPerSector;
@@ -1,126 +1,137 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for average daily P&L per stock.
2
+ * @fileoverview Core Metric (Pass 2)
3
3
  *
4
- * This metric answers: "What was the average daily P&L for a
5
- * position in each specific stock?"
4
+ * This 'standard' calculation streams all user portfolios
5
+ * and calculates the average daily P&L (in %) for all
6
+ * users, bucketed by stock ticker.
6
7
  */
7
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
8
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
8
9
 
9
10
  class AverageDailyPnlPerStock {
10
11
  constructor() {
11
- // We will store { [instrumentId]: { pnl_sum: 0, position_count: 0 } }
12
- this.assets = new Map();
13
- this.mappings = null;
12
+ // { [ticker]: { pnl_sum: 0, count: 0 } }
13
+ this.tickerBuckets = new Map();
14
+
15
+ // --- STANDARD 0: RENAMED ---
16
+ this.tickerMap = null;
14
17
  }
15
18
 
16
- // --- NEW ---
17
- /**
18
- * Statically defines all metadata for the manifest builder.
19
- */
19
+ /** Statically defines metadata */
20
20
  static getMetadata() {
21
21
  return {
22
22
  type: 'standard',
23
- rootDataDependencies: ['portfolio'], // Needs portfolio positions
24
- isHistorical: false,
23
+ rootDataDependencies: ['portfolio'],
24
+ isHistorical: false, // Reads today's portfolio
25
25
  userType: 'all',
26
- category: 'core_pnl'
26
+ category: 'core'
27
27
  };
28
28
  }
29
29
 
30
- // --- NEW ---
31
- /**
32
- * Statically declare dependencies.
33
- */
30
+ /** Statically declare dependencies */
34
31
  static getDependencies() {
35
32
  return [];
36
33
  }
37
34
 
38
35
  /**
39
36
  * Defines the output schema for this calculation.
40
- * @returns {object} JSON Schema object
41
37
  */
42
38
  static getSchema() {
43
- const tickerSchema = {
39
+ const schema = {
44
40
  "type": "object",
45
- "description": "P&L metrics for a specific asset ticker.",
46
41
  "properties": {
47
- "average_pnl": {
48
- "type": "number",
49
- "description": "Average P&L per position (Sum / Count)."
50
- },
51
- "pnl_sum": {
52
- "type": "number",
53
- "description": "Sum of all P&L for this asset."
54
- },
55
- "position_count": {
56
- "type": "number",
57
- "description": "Count of positions in this asset."
58
- }
42
+ "avg_daily_pnl_pct": { "type": "number" },
43
+ "total_pnl_pct": { "type": "number" },
44
+ "user_count": { "type": "number" }
59
45
  },
60
- "required": ["average_pnl", "pnl_sum", "position_count"]
46
+ "required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
61
47
  };
62
-
48
+
63
49
  return {
64
50
  "type": "object",
65
- "description": "Calculates the average daily P&L per stock/asset.",
66
- "patternProperties": {
67
- "^.*$": tickerSchema // Ticker
68
- },
69
- "additionalProperties": tickerSchema
51
+ "description": "Calculates the avg. daily P&L % for all users, bucketed by stock.",
52
+ "patternProperties": { "^.*$": schema },
53
+ "additionalProperties": schema
70
54
  };
71
55
  }
72
-
73
- _initAsset(instrumentId) {
74
- if (!this.assets.has(instrumentId)) {
75
- this.assets.set(instrumentId, {
76
- pnl_sum: 0,
77
- position_count: 0
78
- });
56
+
57
+ _init(map, key) {
58
+ if (!map.has(key)) {
59
+ map.set(key, { pnl_sum: 0, count: 0 });
79
60
  }
80
61
  }
81
62
 
82
- process(portfolioData) {
83
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
84
- if (!positions || !Array.isArray(positions)) {
85
- return;
63
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
64
+ // --- STANDARD 0: FIXED ---
65
+ if (!this.tickerMap) {
66
+ this.tickerMap = context.instrumentToTicker;
67
+ }
68
+
69
+ if (!this.tickerMap) {
70
+ return; // Failsafe if context is missing map
86
71
  }
72
+
73
+ let dailyPnl = 0;
87
74
 
88
- for (const pos of positions) {
89
- const instrumentId = pos.InstrumentID;
90
- if (!instrumentId) continue;
75
+ // Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
76
+ if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
77
+
78
+ dailyPnl = todayPortfolio.NetProfit;
79
+
80
+ // Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
81
+ } else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
91
82
 
92
- this._initAsset(instrumentId);
83
+ // Sum the P&L by calculating (Value - Invested) from the aggregates
84
+ dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
85
+ return sum + (agg.Value - agg.Invested);
86
+ }, 0);
93
87
 
94
- const assetData = this.assets.get(instrumentId);
95
- assetData.pnl_sum += pos.NetProfit || 0;
96
- assetData.position_count++;
97
88
  }
98
- }
89
+ if (dailyPnl === 0) return; // No P&L
90
+
91
+ const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
92
+ if (!positions || positions.length === 0) {
93
+ return;
94
+ }
99
95
 
100
- async getResult() {
101
- if (!this.mappings) {
102
- this.mappings = await loadInstrumentMappings();
96
+ // We only care about tickers this user *holds*
97
+ const heldTickers = new Set();
98
+ for (const pos of positions) {
99
+ if (!pos.InstrumentID) continue;
100
+ // --- STANDARD 0: SIMPLIFIED ---
101
+ const ticker = this.tickerMap[pos.InstrumentID];
102
+ if (ticker) {
103
+ heldTickers.add(ticker);
104
+ }
103
105
  }
106
+
107
+ for (const ticker of heldTickers) {
108
+ this._init(this.tickerBuckets, ticker);
109
+ const asset = this.tickerBuckets.get(ticker);
110
+ asset.pnl_sum += dailyPnl;
111
+ asset.count++;
112
+ }
113
+ }
104
114
 
115
+ async getResult() {
105
116
  const result = {};
106
- for (const [instrumentId, data] of this.assets.entries()) {
107
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
108
-
109
- if (data.position_count > 0) {
117
+
118
+ for (const [ticker, data] of this.tickerBuckets.entries()) {
119
+ if (data.count > 0) {
110
120
  result[ticker] = {
111
- average_pnl: data.pnl_sum / data.position_count,
112
- pnl_sum: data.pnl_sum,
113
- position_count: data.position_count
121
+ avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
122
+ total_pnl_pct: data.pnl_sum * 100,
123
+ user_count: data.count
114
124
  };
115
125
  }
116
126
  }
127
+
117
128
  return result;
118
129
  }
119
-
130
+
120
131
  reset() {
121
- this.assets.clear();
122
- this.mappings = null;
132
+ this.tickerBuckets.clear();
133
+ // --- STANDARD 0: RENAMED ---
134
+ this.tickerMap = null;
123
135
  }
124
136
  }
125
-
126
137
  module.exports = AverageDailyPnlPerStock;
@@ -61,8 +61,8 @@ class AverageDailyPositionPnl {
61
61
  };
62
62
  }
63
63
 
64
- process(portfolioData) {
65
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
64
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
65
+ const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
66
66
  if (!positions || !Array.isArray(positions)) {
67
67
  return;
68
68
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * This calculation now uses the 'history' data source, not 'portfolio'.
8
8
  */
9
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
9
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
10
10
 
11
11
 
12
12
  class HoldingDurationPerAsset {
@@ -15,10 +15,10 @@ class HoldingDurationPerAsset {
15
15
  // 'sum_hours' will be the sum of user-level averages
16
16
  // 'count' will be the number of users who traded that asset
17
17
  this.assets = new Map();
18
- this.mappings = null;
18
+ // --- STANDARD 0: RENAMED ---
19
+ this.tickerMap = null;
19
20
  }
20
21
 
21
- // --- NEW ---
22
22
  /**
23
23
  * Statically defines all metadata for the manifest builder.
24
24
  */
@@ -32,7 +32,6 @@ class HoldingDurationPerAsset {
32
32
  };
33
33
  }
34
34
 
35
- // --- NEW ---
36
35
  /**
37
36
  * Statically declare dependencies.
38
37
  */
@@ -42,7 +41,6 @@ class HoldingDurationPerAsset {
42
41
 
43
42
  /**
44
43
  * Defines the output schema for this calculation.
45
- * @returns {object} JSON Schema object
46
44
  */
47
45
  static getSchema() {
48
46
  const tickerSchema = {
@@ -76,18 +74,8 @@ class HoldingDurationPerAsset {
76
74
  }
77
75
  }
78
76
 
79
- // --- REFACTORED ---
80
77
  /**
81
78
  * Process data from the 'history' root data source.
82
- * @param {object} todayPortfolio - (Not used)
83
- * @param {object} yesterdayPortfolio - (Not used)
84
- * @param {string} userId - The user ID.
85
- * @param {object} context - (Not used)
86
- * @param {object} todayInsights - (Not used)
87
- * @param {object} yesterdayInsights - (Not used)
88
- * @param {object} todaySocialPostInsights - (Not used)
89
- * @param {object} yesterdaySocialPostInsights - (Not used)
90
- * @param {object} todayHistory - The user's history doc for today.
91
79
  */
92
80
  process(
93
81
  todayPortfolio,
@@ -100,8 +88,17 @@ class HoldingDurationPerAsset {
100
88
  yesterdaySocialPostInsights,
101
89
  todayHistory
102
90
  ) {
103
- // 1. Get the history data from the 9th argument
104
- const historyData = todayHistory;
91
+ // --- STANDARD 0: FIXED ---
92
+ if (!this.tickerMap) {
93
+ this.tickerMap = context.instrumentToTicker;
94
+ }
95
+
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
105
102
  if (!historyData || !Array.isArray(historyData.assets)) {
106
103
  return;
107
104
  }
@@ -125,21 +122,24 @@ class HoldingDurationPerAsset {
125
122
  // Convert minutes to hours and add to the sum
126
123
  assetData.sum_hours += (durationMinutes / 60);
127
124
 
128
- // Increment count (this now counts *users* who have
129
- // an average for this asset, not individual positions)
125
+ // Increment count
130
126
  assetData.count++;
131
127
  }
132
128
  }
133
129
  }
134
130
 
135
131
  async getResult() {
136
- if (!this.mappings) {
137
- this.mappings = await loadInstrumentMappings();
132
+ // --- STANDARD 0: REMOVED forbidden data load ---
133
+
134
+ // Failsafe check
135
+ if (!this.tickerMap) {
136
+ return {}; // process() must run first
138
137
  }
139
138
 
140
139
  const result = {};
141
140
  for (const [instrumentId, data] of this.assets.entries()) {
142
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
141
+ // --- STANDARD 0: SIMPLIFIED ---
142
+ const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
143
143
 
144
144
  if (data.count > 0) {
145
145
  result[ticker] = {
@@ -154,7 +154,8 @@ class HoldingDurationPerAsset {
154
154
 
155
155
  reset() {
156
156
  this.assets.clear();
157
- this.mappings = null;
157
+ // --- STANDARD 0: RENAMED ---
158
+ this.tickerMap = null;
158
159
  }
159
160
  }
160
161