aiden-shared-calculations-unified 1.0.86 → 1.0.88

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 (80) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +96 -0
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
  3. package/calculations/core/asset-cost-basis-profile.js +127 -0
  4. package/calculations/core/asset-pnl-status.js +36 -106
  5. package/calculations/core/asset-position-size.js +40 -91
  6. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  7. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  8. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  9. package/calculations/core/average-daily-position-pnl.js +19 -49
  10. package/calculations/core/holding-duration-per-asset.js +25 -127
  11. package/calculations/core/instrument-price-change-1d.js +30 -49
  12. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  13. package/calculations/core/long-position-per-stock.js +39 -68
  14. package/calculations/core/overall-holding-duration.js +16 -87
  15. package/calculations/core/overall-profitability-ratio.js +11 -40
  16. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  17. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  18. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  19. package/calculations/core/platform-ownership-per-sector.js +45 -96
  20. package/calculations/core/platform-total-positions-held.js +20 -80
  21. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  22. package/calculations/core/price-metrics.js +95 -206
  23. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  24. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  25. package/calculations/core/profitability-skew-per-stock.js +41 -94
  26. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  27. package/calculations/core/sentiment-per-stock.js +24 -77
  28. package/calculations/core/short-position-per-stock.js +35 -43
  29. package/calculations/core/social-activity-aggregation.js +26 -49
  30. package/calculations/core/social-asset-posts-trend.js +38 -94
  31. package/calculations/core/social-event-correlation.js +26 -93
  32. package/calculations/core/social-sentiment-aggregation.js +20 -44
  33. package/calculations/core/social-top-mentioned-words.js +35 -87
  34. package/calculations/core/social-topic-interest-evolution.js +22 -111
  35. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  36. package/calculations/core/social-word-mentions-trend.js +27 -104
  37. package/calculations/core/speculator-asset-sentiment.js +31 -72
  38. package/calculations/core/speculator-danger-zone.js +48 -84
  39. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  40. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  41. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  42. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  43. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  44. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  45. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  46. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  47. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  48. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  49. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  50. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  51. package/calculations/core/test..js +0 -0
  52. package/calculations/core/total-long-figures.js +16 -31
  53. package/calculations/core/total-long-per-sector.js +39 -61
  54. package/calculations/core/total-short-figures.js +13 -32
  55. package/calculations/core/total-short-per-sector.js +39 -61
  56. package/calculations/core/users-processed.js +11 -46
  57. package/calculations/gauss/cohort-capital-flow.js +54 -173
  58. package/calculations/gauss/cohort-definer.js +77 -163
  59. package/calculations/gauss/daily-dna-filter.js +29 -83
  60. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  61. package/calculations/gem/cohort-momentum-state.js +27 -72
  62. package/calculations/gem/cohort-skill-definition.js +36 -52
  63. package/calculations/gem/platform-conviction-divergence.js +18 -60
  64. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  65. package/calculations/gem/skilled-cohort-flow.js +67 -175
  66. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  67. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  68. package/calculations/ghost-book/cost-basis-density.js +79 -0
  69. package/calculations/ghost-book/liquidity-vacuum.js +52 -0
  70. package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
  71. package/calculations/helix/helix-contrarian-signal.js +20 -114
  72. package/calculations/helix/herd-consensus-score.js +42 -124
  73. package/calculations/helix/winner-loser-flow.js +36 -118
  74. package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
  75. package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
  76. package/calculations/predicative-alpha/mimetic-latency.js +124 -0
  77. package/calculations/pyro/risk-appetite-index.js +33 -74
  78. package/calculations/pyro/squeeze-potential.js +30 -87
  79. package/calculations/pyro/volatility-signal.js +33 -78
  80. package/package.json +1 -1
@@ -1,49 +1,24 @@
1
1
  /**
2
2
  * @fileoverview HELIX Product Line (Pass 4)
3
- *
4
- * This is the final, stateless signal generator for the HELIX line.
5
- * It answers: "Are 'Winners' (in-profit) and 'Losers'
6
- * (in-loss) cohorts flowing in opposite directions?"
3
+ * REFACTORED: Uses context.math.signals and process(context).
7
4
  */
8
5
  class HelixContrarianSignal {
6
+ constructor() { this.result = {}; }
9
7
 
10
- // --- STANDARD 2: ADDED ---
11
- constructor() {
12
- this.result = {};
13
- }
14
-
15
- /**
16
- * Defines the output schema for this calculation.
17
- */
18
8
  static getSchema() {
19
9
  const tickerSchema = {
20
10
  "type": "object",
21
11
  "properties": {
22
- "signal": {
23
- "type": "string",
24
- "enum": ["Strong Contrarian", "Contrarian", "Neutral", "Consensus"]
25
- },
26
- "helix_score": {
27
- "type": "number",
28
- "description": "Divergence score (-10 to +10). Positive = Contrarian (Winners sell, Losers buy), Negative = Consensus (Both buy/sell)."
29
- },
12
+ "signal": { "type": "string" },
13
+ "helix_score": { "type": "number" },
30
14
  "winner_net_flow": { "type": "number" },
31
15
  "loser_net_flow": { "type": "number" }
32
16
  },
33
- "required": ["signal", "helix_score", "winner_net_flow", "loser_net_flow"]
34
- };
35
-
36
- return {
37
- "type": "object",
38
- "description": "Generates a final contrarian signal based on Winner vs. Loser flow.",
39
- "patternProperties": { "^.*$": tickerSchema },
40
- "additionalProperties": tickerSchema
17
+ "required": ["signal", "helix_score"]
41
18
  };
19
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
42
20
  }
43
21
 
44
- /**
45
- * Statically defines all metadata for the manifest builder.
46
- */
47
22
  static getMetadata() {
48
23
  return {
49
24
  type: 'meta',
@@ -54,109 +29,40 @@ class HelixContrarianSignal {
54
29
  };
55
30
  }
56
31
 
57
- /**
58
- * Statically declare dependencies.
59
- */
60
32
  static getDependencies() {
61
- return [
62
- 'winner-loser-flow', // from helix (Pass 2)
63
- 'herd-consensus-score' // from helix (Pass 2)
64
- ];
33
+ return ['winner-loser-flow', 'herd-consensus-score'];
65
34
  }
66
35
 
67
- /**
68
- * Simple tanh normalization. Scales any number to a -10 to +10 range.
69
- */
70
- _normalize(score) {
71
- // Scale input score to tune sensitivity
72
- // A score of 50 will be ~9.9 (strong signal)
73
- // A score of 20 will be ~9.6
74
- // A score of 10 will be ~7.6
75
- return Math.tanh(score / 10.0) * 10;
76
- }
77
-
78
- /**
79
- * This is a 'meta' calculation. It runs once.
80
- */
81
- // --- STANDARD 1: UPDATED SIGNATURE (added rootData) ---
82
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
83
- const { logger } = dependencies;
36
+ process(context) {
37
+ const { computed, math } = context;
38
+ const { signals } = math;
84
39
 
85
- const cohortFlows = fetchedDependencies['winner-loser-flow'];
86
- const herdConviction = fetchedDependencies['herd-consensus-score'];
87
-
88
- if (!cohortFlows || !herdConviction) {
89
- logger.log('WARN', `[helix/helix-contrarian-signal] Missing dependencies for ${dateStr}.`);
90
- // --- STANDARD 2: DO NOT RETURN ---
91
- this.result = {};
92
- return;
93
- }
94
-
95
- const allTickers = new Set([
96
- ...Object.keys(cohortFlows),
97
- ...Object.keys(herdConviction)
98
- ]);
99
-
40
+ const tickers = signals.getUnionKeys(computed, ['winner-loser-flow', 'herd-consensus-score']);
100
41
  const result = {};
101
42
 
102
- for (const ticker of allTickers) {
103
- const flow = cohortFlows[ticker];
104
- const conviction = herdConviction[ticker];
43
+ for (const ticker of tickers) {
44
+ const winner_flow = signals.getMetric(computed, 'winner-loser-flow', ticker, 'net_winner_flow');
45
+ const loser_flow = signals.getMetric(computed, 'herd-consensus-score', ticker, 'herd_conviction_score');
105
46
 
106
- // 1. Get Winner Flow (using net_winner_flow)
107
- const winner_flow = flow?.net_winner_flow || 0;
108
-
109
- // 2. Get Loser Flow (using herd_conviction_score)
110
- // This is a proxy for "Loser Flow".
111
- // If conviction > 0, losers are buying.
112
- // If conviction < 0, losers are selling.
113
- const loser_flow = conviction?.herd_conviction_score || 0;
114
-
115
- // --- Signal Logic ---
116
- // A contrarian signal (positive score) happens when:
117
- // 1. Winners SELL (flow < 0) and Losers BUY (flow > 0)
118
- //
119
- // A consensus signal (negative score) happens when:
120
- // 2. Winners BUY (flow > 0) and Losers BUY (flow > 0)
121
- // 3. Winners SELL (flow < 0) and Losers SELL (flow < 0)
122
-
123
- // We can simplify this:
124
- // Score = Loser Flow - Winner Flow
125
- //
126
- // Case 1: Loser (Buy, +50) - Winner (Sell, -20) = +70 (Strong Contrarian)
127
- // Case 2: Loser (Buy, +50) - Winner (Buy, +20) = +30 (Weak Contrarian)
128
- // Case 3: Loser (Sell, -50) - Winner (Sell, -20) = -30 (Consensus)
129
- // Case 4: Loser (Sell, -50) - Winner (Buy, +20) = -70 (Strong Consensus)
130
-
131
47
  const divergence = loser_flow - winner_flow;
132
- const helix_score = this._normalize(divergence);
48
+ const helix_score = signals.normalizeTanh(divergence, 10, 10.0);
133
49
 
134
50
  let signal = "Neutral";
135
- if (helix_score > 7.5) signal = "Strong Contrarian"; // Losers buying, Winners selling
51
+ if (helix_score > 7.5) signal = "Strong Contrarian";
136
52
  else if (helix_score > 2.5) signal = "Contrarian";
137
- else if (helix_score < -2.5) signal = "Consensus"; // Both moving same way
53
+ else if (helix_score < -2.5) signal = "Consensus";
138
54
 
139
55
  result[ticker] = {
140
56
  signal: signal,
141
57
  helix_score: helix_score,
142
58
  winner_net_flow: winner_flow,
143
- loser_net_flow: loser_flow // Using conviction as flow proxy
59
+ loser_net_flow: loser_flow
144
60
  };
145
61
  }
146
-
147
- // --- STANDARD 2: SET STATE, DO NOT RETURN ---
148
62
  this.result = result;
149
63
  }
150
64
 
151
- // --- STANDARD 2: ADDED ---
152
- async getResult(fetchedDependencies) {
153
- return this.result;
154
- }
155
-
156
- // --- STANDARD 2: ADDED ---
157
- reset() {
158
- this.result = {};
159
- }
65
+ async getResult() { return this.result; }
66
+ reset() { this.result = {}; }
160
67
  }
161
-
162
68
  module.exports = HelixContrarianSignal;
@@ -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;