aiden-shared-calculations-unified 1.0.84 → 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,179 +1,99 @@
1
1
  /**
2
2
  * @fileoverview HELIX Product Line (Pass 2)
3
- *
4
- * This metric answers: "For each asset, what is the
5
- * net 'conviction' (avg. position change) of the
6
- * 'Herd' (in-loss) cohort?"
3
+ * REFACTORED: Uses context.math.extract.
7
4
  */
8
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
9
-
10
-
11
5
  class HerdConsensusScore {
12
6
  constructor() {
13
- // { [instrumentId]: { ... } }
14
7
  this.assetConviction = new Map();
15
-
16
- // --- STANDARD 0: RENAMED ---
17
- this.tickerMap = null;
8
+ this.tickerMap = null;
18
9
  }
19
10
 
20
- /**
21
- * Defines the output schema for this calculation.
22
- */
23
11
  static getSchema() {
24
12
  const tickerSchema = {
25
13
  "type": "object",
26
14
  "properties": {
27
- "herd_conviction_score": {
28
- "type": "number",
29
- "description": "Avg. % change in position size for 'Loser' cohort. Positive = Buying the Dip, Negative = Capitulating."
30
- },
15
+ "herd_conviction_score": { "type": "number" },
31
16
  "user_count": { "type": "number" }
32
17
  },
33
18
  "required": ["herd_conviction_score", "user_count"]
34
19
  };
35
-
36
- return {
37
- "type": "object",
38
- "description": "Tracks the change in avg. position size for the 'Loser' (in-loss) cohort.",
39
- "patternProperties": { "^.*$": tickerSchema },
40
- "additionalProperties": tickerSchema
41
- };
20
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
42
21
  }
43
22
 
44
- /**
45
- * Statically defines all metadata for the manifest builder.
46
- */
47
23
  static getMetadata() {
48
24
  return {
49
25
  type: 'standard',
50
26
  rootDataDependencies: ['portfolio'],
51
- isHistorical: true, // Needs T-1 portfolio
27
+ isHistorical: true,
52
28
  userType: 'all',
53
29
  category: 'helix'
54
30
  };
55
31
  }
56
32
 
57
- /**
58
- * Statically declare dependencies.
59
- */
60
- static getDependencies() {
61
- return [];
62
- }
33
+ static getDependencies() { return []; }
63
34
 
64
- _getPortfolioPositions(portfolio) {
65
- return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
66
- }
67
-
68
35
  _initAsset(instrumentId) {
69
36
  if (!this.assetConviction.has(instrumentId)) {
70
37
  this.assetConviction.set(instrumentId, {
71
- total_pos_size_yesterday: 0,
72
- total_pos_size_today: 0,
73
- user_count_yesterday: 0,
74
- user_count_today: 0
38
+ total_pos_size_yesterday: 0, total_pos_size_today: 0,
39
+ user_count_yesterday: 0, user_count_today: 0
75
40
  });
76
41
  }
77
42
  }
78
43
 
79
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
80
- if (!todayPortfolio) {
81
- return;
82
- }
83
-
84
- // --- STANDARD 0: FIXED ---
85
- if (!this.tickerMap) {
86
- this.tickerMap = context.instrumentToTicker;
87
- }
44
+ process(context) {
45
+ const { user, mappings, math } = context;
46
+ const { extract } = math;
47
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
88
48
 
89
- const tPos = this._getPortfolioPositions(todayPortfolio);
90
- if (!tPos) return;
49
+ const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
50
+ const tPos = extract.getPositions(user.portfolio.today, user.type);
91
51
 
92
- // --- Determine Cohort ---
93
- // We *only* care about users who are *currently* in the
94
- // 'Loser' cohort for any given asset.
95
- const tPosMap = new Map(tPos.map(p => [p.InstrumentID, p]));
96
- const yPosMap = new Map((this._getPortfolioPositions(yesterdayPortfolio) || []).map(p => [p.InstrumentID, p]));
52
+ const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
53
+ const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
97
54
 
98
- const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
99
- if (allInstrumentIds.size === 0) {
100
- return;
101
- }
102
-
103
- for (const instrumentId of allInstrumentIds) {
104
- const tP = tPosMap.get(instrumentId);
105
- const yP = yPosMap.get(instrumentId);
55
+ const allIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
56
+
57
+ for (const instId of allIds) {
58
+ const tP = tPosMap.get(instId);
59
+ const yP = yPosMap.get(instId);
106
60
 
107
- // --- Check Cohort Status (In-Loss) ---
108
- const tPnl = tP?.NetProfit || 0;
109
- const isLoser = tPnl < 0;
61
+ if (extract.getNetProfit(tP) >= 0) continue; // Only Losers (Herd)
110
62
 
111
- // We only process conviction for assets this user
112
- // is *currently* losing on.
113
- if (!isLoser) {
114
- continue;
115
- }
116
-
117
- // --- This user is in the 'Herd' for this asset ---
118
- this._initAsset(instrumentId);
119
- const asset = this.assetConviction.get(instrumentId);
120
-
121
- // ---
122
- // FIX: The field 'PositionSizeInUnits' does not exist in schema.md.
123
- // For Normal users, the field is 'Invested'.
124
- // For Speculator users, the field is 'Amount'.
125
- // This line now correctly checks for either.
126
- // ---
127
- const ySize = yP?.Invested || yP?.Amount || 0;
128
- const tSize = tP?.Invested || tP?.Amount || 0;
63
+ this._initAsset(instId);
64
+ const asset = this.assetConviction.get(instId);
65
+ const yWeight = extract.getPositionWeight(yP, user.type);
66
+ const tWeight = extract.getPositionWeight(tP, user.type);
129
67
 
130
- if (ySize > 0) {
131
- asset.total_pos_size_yesterday += ySize;
68
+ if (yWeight > 0) {
69
+ asset.total_pos_size_yesterday += yWeight;
132
70
  asset.user_count_yesterday++;
133
71
  }
134
- if (tSize > 0) {
135
- asset.total_pos_size_today += tSize;
72
+ if (tWeight > 0) {
73
+ asset.total_pos_size_today += tWeight;
136
74
  asset.user_count_today++;
137
75
  }
138
76
  }
139
77
  }
140
78
 
141
79
  async getResult() {
142
- // --- STANDARD 0: REMOVED forbidden data load ---
143
-
144
- // Failsafe check
145
- if (!this.tickerMap) {
146
- return {}; // process() must run first
147
- }
148
-
80
+ if (!this.tickerMap) return {};
149
81
  const result = {};
150
- for (const [instrumentId, data] of this.assetConviction.entries()) {
151
- // --- STANDARD 0: SIMPLIFIED ---
152
- const ticker = this.tickerMap[instrumentId];
82
+ for (const [instId, data] of this.assetConviction.entries()) {
83
+ const ticker = this.tickerMap[instId];
153
84
  if (!ticker) continue;
154
85
 
155
- const {
156
- total_pos_size_yesterday, total_pos_size_today,
157
- user_count_yesterday, user_count_today
158
- } = data;
159
-
160
- let avg_position_change_pct = 0;
86
+ let avg_pos_change = 0;
87
+ if (data.user_count_yesterday > 0 && data.user_count_today > 0) {
88
+ const avg_y = data.total_pos_size_yesterday / data.user_count_yesterday;
89
+ const avg_t = data.total_pos_size_today / data.user_count_today;
90
+ if (avg_y > 0) avg_pos_change = ((avg_t - avg_y) / avg_y) * 100;
91
+ } else if (data.user_count_today > 0) avg_pos_change = Infinity;
161
92
 
162
- if (user_count_yesterday > 0 && user_count_today > 0) {
163
- const avg_pos_y = total_pos_size_yesterday / user_count_yesterday;
164
- const avg_pos_t = total_pos_size_today / user_count_today;
165
- if (avg_pos_y > 0) {
166
- avg_position_change_pct = ((avg_pos_t - avg_pos_y) / avg_pos_y) * 100;
167
- }
168
- } else if (user_count_today > 0) {
169
- // Pure inflow, conviction is max
170
- avg_position_change_pct = Infinity;
171
- }
172
-
173
- if (isFinite(avg_position_change_pct) && user_count_today > 0) {
93
+ if (isFinite(avg_pos_change) && data.user_count_today > 0) {
174
94
  result[ticker] = {
175
- herd_conviction_score: avg_position_change_pct,
176
- user_count: user_count_today
95
+ herd_conviction_score: avg_pos_change,
96
+ user_count: data.user_count_today
177
97
  };
178
98
  }
179
99
  }
@@ -182,9 +102,7 @@ class HerdConsensusScore {
182
102
 
183
103
  reset() {
184
104
  this.assetConviction.clear();
185
- // --- STANDARD 0: RENAMED ---
186
105
  this.tickerMap = null;
187
106
  }
188
107
  }
189
-
190
108
  module.exports = HerdConsensusScore;
@@ -1,158 +1,78 @@
1
1
  /**
2
2
  * @fileoverview HELIX Product Line (Pass 2)
3
- *
4
- * This metric answers: "For each asset, what is the net flow
5
- * of *unique users* into the 'Winner' (in-profit) and 'Loser'
6
- * (in-loss) cohorts?"
7
- *
8
- * --- MODIFIED ---
9
- * Removed dependency on 'asset-pnl-status' to fix 1MiB limit.
10
- * This calculation now determines "winner/loser" status internally
11
- * by reading the user's portfolio P&L directly.
3
+ * REFACTORED: Uses context.math.extract.
12
4
  */
13
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
14
-
15
-
16
5
  class WinnerLoserFlow {
17
6
  constructor() {
18
- // We will store { [ticker]: { winners_joined: 0, ... } }
19
7
  this.assetFlows = new Map();
20
- // --- STANDARD 0: RENAMED ---
21
- this.tickerMap = null; // Caches mappings
22
- }
23
-
24
- /**
25
- * Defines the output schema for this calculation.
26
- * @returns {object} JSON Schema object
27
- */
28
- static getSchema() {
29
- const tickerSchema = {
30
- "type": "object",
31
- "properties": {
32
- "net_winner_flow": {
33
- "type": "number",
34
- "description": "Net users joining the 'Winner' cohort (Joined - Left)."
35
- },
36
- "net_loser_flow": {
37
- "type": "number",
38
- "description": "Net users joining the 'Loser' cohort (Joined - Left)."
39
- },
40
- "raw_counts": {
41
- "type": "object",
42
- "properties": {
43
- "winners_joined": { "type": "number" },
44
- "winners_left": { "type": "number" },
45
- "losers_joined": { "type": "number" },
46
- "losers_left": { "type": "number" }
47
- }
48
- }
49
- },
50
- "required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
51
- };
52
-
53
- return {
54
- "type": "object",
55
- "description": "Tracks the net flow of unique users into/out of the Winner (in-profit) and Loser (in-loss) cohorts per asset.",
56
- "patternProperties": {
57
- "^.*$": tickerSchema // Ticker
58
- },
59
- "additionalProperties": tickerSchema
60
- };
8
+ this.tickerMap = null;
61
9
  }
62
10
 
63
- /**
64
- * Statically defines all metadata for the manifest builder.
65
- */
66
11
  static getMetadata() {
67
12
  return {
68
13
  type: 'standard',
69
14
  rootDataDependencies: ['portfolio'],
70
- isHistorical: true, // Needs T-1 portfolio
15
+ isHistorical: true,
71
16
  userType: 'all',
72
17
  category: 'helix'
73
18
  };
74
19
  }
75
20
 
76
- /**
77
- * Statically declare dependencies.
78
- */
79
- static getDependencies() {
80
- return [];
21
+ static getDependencies() { return []; }
22
+
23
+ static getSchema() {
24
+ const tickerSchema = {
25
+ "type": "object",
26
+ "properties": {
27
+ "net_winner_flow": { "type": "number" },
28
+ "net_loser_flow": { "type": "number" },
29
+ "raw_counts": { "type": "object" }
30
+ },
31
+ "required": ["net_winner_flow", "net_loser_flow", "raw_counts"]
32
+ };
33
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
81
34
  }
82
35
 
83
- /**
84
- * Helper to get a user's holdings (Instrument IDs) AND their P&L.
85
- * @returns {Map<InstrumentID, {pnl: number}>}
86
- */
87
- _getHoldingsWithPnl(portfolio) {
88
- const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
89
- if (!positions || !Array.isArray(positions)) {
90
- return new Map();
91
- }
36
+ _getHoldings(positions, extract) {
92
37
  const map = new Map();
93
38
  for (const pos of positions) {
94
- if (pos.InstrumentID) {
95
- map.set(pos.InstrumentID, { pnl: pos.NetProfit || 0 });
96
- }
39
+ const id = extract.getInstrumentId(pos);
40
+ if (id) map.set(id, { pnl: extract.getNetProfit(pos) });
97
41
  }
98
42
  return map;
99
43
  }
100
44
 
101
- /**
102
- * Initialize asset flow counters
103
- */
104
45
  _initAsset(ticker) {
105
46
  if (!this.assetFlows.has(ticker)) {
106
- this.assetFlows.set(ticker, {
107
- winners_joined: 0,
108
- winners_left: 0,
109
- losers_joined: 0,
110
- losers_left: 0
111
- });
47
+ this.assetFlows.set(ticker, { winners_joined: 0, winners_left: 0, losers_joined: 0, losers_left: 0 });
112
48
  }
113
49
  }
114
50
 
115
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
116
- if (!todayPortfolio || !yesterdayPortfolio) {
117
- return;
118
- }
51
+ process(context) {
52
+ const { user, mappings, math } = context;
53
+ const { extract } = math;
54
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
119
55
 
120
- // --- STANDARD 0: FIXED ---
121
- if (!this.tickerMap) {
122
- this.tickerMap = context.instrumentToTicker;
123
- }
56
+ const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
57
+ const tPos = extract.getPositions(user.portfolio.today, user.type);
124
58
 
125
- if (!this.tickerMap) {
126
- return; // Failsafe if context is missing map
127
- }
59
+ const yHoldings = this._getHoldings(yPos, extract);
60
+ const tHoldings = this._getHoldings(tPos, extract);
128
61
 
129
- const yHoldings = this._getHoldingsWithPnl(yesterdayPortfolio); // Map<InstID, {pnl}>
130
- const tHoldings = this._getHoldingsWithPnl(todayPortfolio); // Map<InstID, {pnl}>
62
+ const allIds = new Set([...yHoldings.keys(), ...tHoldings.keys()]);
131
63
 
132
- const allInstrumentIds = new Set([...yHoldings.keys(), ...tHoldings.keys()]);
133
- if (allInstrumentIds.size === 0) {
134
- return;
135
- }
136
-
137
- for (const instrumentId of allInstrumentIds) {
138
- // --- STANDARD 0: FIXED ---
139
- const ticker = this.tickerMap[instrumentId];
64
+ for (const instId of allIds) {
65
+ const ticker = this.tickerMap[instId];
140
66
  if (!ticker) continue;
141
67
 
142
- const tPnl = tHoldings.get(instrumentId)?.pnl;
68
+ const tPnl = tHoldings.get(instId)?.pnl || 0;
143
69
  const isWinner = tPnl > 0;
144
70
  const isLoser = tPnl < 0;
145
-
146
- if (!isWinner && !isLoser) {
147
- continue;
148
- }
149
-
150
- const heldYesterday = yHoldings.has(instrumentId);
151
- const holdsToday = tHoldings.has(instrumentId);
71
+ if (!isWinner && !isLoser) continue;
152
72
 
153
- if (heldYesterday === holdsToday) {
154
- continue;
155
- }
73
+ const heldYesterday = yHoldings.has(instId);
74
+ const holdsToday = tHoldings.has(instId);
75
+ if (heldYesterday === holdsToday) continue;
156
76
 
157
77
  this._initAsset(ticker);
158
78
  const stats = this.assetFlows.get(ticker);
@@ -182,9 +102,7 @@ class WinnerLoserFlow {
182
102
 
183
103
  reset() {
184
104
  this.assetFlows.clear();
185
- // --- STANDARD 0: RENAMED ---
186
105
  this.tickerMap = null;
187
106
  }
188
107
  }
189
-
190
108
  module.exports = WinnerLoserFlow;
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * @fileoverview PYRO Product Line (Pass 2)
3
- * --- FIX ---
4
- * - Updated 'process' signature to the 7-arg standard to get context.
5
- * - Logic is already schema-compliant (uses pos.Leverage).
3
+ * REFACTORED: Uses context.math.extract.
6
4
  */
7
-
8
5
  class RiskAppetiteIndex {
9
6
  constructor() {
10
7
  this.tickerLeverage = new Map();
@@ -18,14 +15,12 @@ class RiskAppetiteIndex {
18
15
  type: 'standard',
19
16
  rootDataDependencies: ['portfolio'],
20
17
  isHistorical: false,
21
- userType: 'speculator', // Only processes speculators
18
+ userType: 'speculator',
22
19
  category: 'pyro'
23
20
  };
24
21
  }
25
22
 
26
- static getDependencies() {
27
- return [];
28
- }
23
+ static getDependencies() { return []; }
29
24
 
30
25
  static getSchema() {
31
26
  const schema = {
@@ -36,108 +31,72 @@ class RiskAppetiteIndex {
36
31
  },
37
32
  "required": ["avg_leverage", "user_count"]
38
33
  };
39
-
40
34
  return {
41
35
  "type": "object",
42
- "description": "Calculates the average leverage used by speculators, aggregated by ticker and sector.",
43
36
  "properties": {
44
- "by_ticker": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema },
45
- "by_sector": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema }
46
- },
47
- "required": ["by_ticker", "by_sector"]
37
+ "by_ticker": { "type": "object", "patternProperties": { "^.*$": schema } },
38
+ "by_sector": { "type": "object", "patternProperties": { "^.*$": schema } }
39
+ }
48
40
  };
49
41
  }
50
42
 
51
43
  _init(map, key) {
52
- if (!map.has(key)) {
53
- map.set(key, { leverage_sum: 0, user_count: 0 });
54
- }
44
+ if (!map.has(key)) map.set(key, { leverage_sum: 0, user_count: 0 });
55
45
  }
56
46
 
57
- // --- THIS IS THE FIX ---
58
- // Updated signature to match the test worker's standard 7-arg call
59
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
60
- // --- END FIX ---
61
-
62
- if (!todayPortfolio?.PublicPositions) {
63
- return; // Not a speculator or no positions
64
- }
47
+ process(context) {
48
+ const { user, mappings, math } = context;
49
+ const { extract } = math;
65
50
 
66
51
  if (!this.tickerMap) {
67
- this.tickerMap = context.instrumentToTicker;
68
- this.sectorMap = context.sectorMapping;
69
- }
70
-
71
- if (!this.tickerMap || !this.sectorMap) {
72
- return;
52
+ this.tickerMap = mappings.instrumentToTicker;
53
+ this.sectorMap = mappings.sectorMapping;
73
54
  }
74
55
 
75
- const positions = todayPortfolio.PublicPositions;
76
-
56
+ const positions = extract.getPositions(user.portfolio.today, user.type);
77
57
  const seenTickers = new Set();
78
58
  const seenSectors = new Set();
79
59
 
80
60
  for (const pos of positions) {
81
- if (!pos.InstrumentID || !pos.Leverage) continue;
61
+ const instId = extract.getInstrumentId(pos);
62
+ if (!instId) continue;
82
63
 
83
- const leverage = pos.Leverage;
84
- if (leverage <= 1) continue; // Only care about leveraged positions
64
+ const leverage = extract.getLeverage(pos);
65
+ if (leverage <= 1) continue;
85
66
 
86
- const ticker = this.tickerMap[pos.InstrumentID];
87
- const sector = this.sectorMap[pos.InstrumentID];
67
+ const ticker = this.tickerMap[instId];
68
+ const sector = this.sectorMap[instId];
88
69
 
89
70
  if (ticker) {
90
71
  this._init(this.tickerLeverage, ticker);
91
72
  const asset = this.tickerLeverage.get(ticker);
92
73
  asset.leverage_sum += leverage;
93
-
94
- if (!seenTickers.has(ticker)) {
95
- asset.user_count++;
96
- seenTickers.add(ticker);
97
- }
74
+ if (!seenTickers.has(ticker)) { asset.user_count++; seenTickers.add(ticker); }
98
75
  }
99
76
 
100
77
  if (sector) {
101
78
  this._init(this.sectorLeverage, sector);
102
79
  const sec = this.sectorLeverage.get(sector);
103
80
  sec.leverage_sum += leverage;
104
-
105
- if (!seenSectors.has(sector)) {
106
- sec.user_count++;
107
- seenSectors.add(sector);
108
- }
81
+ if (!seenSectors.has(sector)) { sec.user_count++; seenSectors.add(sector); }
109
82
  }
110
83
  }
111
84
  }
112
85
 
113
86
  async getResult() {
114
- if (!this.tickerMap) {
115
- return { by_ticker: {}, by_sector: {} };
116
- }
117
-
118
- const result = {
119
- by_ticker: {},
120
- by_sector: {}
121
- };
122
-
123
- for (const [ticker, data] of this.tickerLeverage.entries()) {
124
- if (data.user_count > 0) {
125
- result.by_ticker[ticker] = {
126
- avg_leverage: data.leverage_sum / data.user_count,
127
- user_count: data.user_count
128
- };
129
- }
130
- }
131
-
132
- for (const [sector, data] of this.sectorLeverage.entries()) {
133
- if (data.user_count > 0) {
134
- result.by_sector[sector] = {
135
- avg_leverage: data.leverage_sum / data.user_count,
136
- user_count: data.user_count
137
- };
87
+ const result = { by_ticker: {}, by_sector: {} };
88
+ const build = (map, out) => {
89
+ for (const [key, data] of map.entries()) {
90
+ if (data.user_count > 0) {
91
+ out[key] = {
92
+ avg_leverage: data.leverage_sum / data.user_count,
93
+ user_count: data.user_count
94
+ };
95
+ }
138
96
  }
139
- }
140
-
97
+ };
98
+ build(this.tickerLeverage, result.by_ticker);
99
+ build(this.sectorLeverage, result.by_sector);
141
100
  return result;
142
101
  }
143
102