aiden-shared-calculations-unified 1.0.83 → 1.0.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/calculations/core/asset-pnl-status.js +122 -104
  2. package/calculations/core/asset-position-size.js +110 -73
  3. package/calculations/core/average-daily-pnl-all-users.js +17 -3
  4. package/calculations/core/average-daily-pnl-per-sector.js +83 -75
  5. package/calculations/core/average-daily-pnl-per-stock.js +84 -73
  6. package/calculations/core/average-daily-position-pnl.js +2 -2
  7. package/calculations/core/holding-duration-per-asset.js +24 -23
  8. package/calculations/core/instrument-price-change-1d.js +72 -82
  9. package/calculations/core/instrument-price-momentum-20d.js +66 -100
  10. package/calculations/core/long-position-per-stock.js +21 -13
  11. package/calculations/core/overall-holding-duration.js +8 -3
  12. package/calculations/core/overall-profitability-ratio.js +2 -2
  13. package/calculations/core/platform-buy-sell-sentiment.js +75 -22
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
  15. package/calculations/core/platform-daily-ownership-delta.js +39 -15
  16. package/calculations/core/platform-ownership-per-sector.js +38 -18
  17. package/calculations/core/platform-total-positions-held.js +36 -14
  18. package/calculations/core/pnl-distribution-per-stock.js +39 -36
  19. package/calculations/core/price-metrics.js +70 -172
  20. package/calculations/core/profitability-ratio-per-sector.js +23 -29
  21. package/calculations/core/profitability-ratio-per-stock.js +20 -13
  22. package/calculations/core/profitability-skew-per-stock.js +20 -13
  23. package/calculations/core/profitable-and-unprofitable-status.js +34 -10
  24. package/calculations/core/sentiment-per-stock.js +20 -9
  25. package/calculations/core/short-position-per-stock.js +23 -37
  26. package/calculations/core/social-activity-aggregation.js +41 -115
  27. package/calculations/core/social-asset-posts-trend.js +77 -94
  28. package/calculations/core/social-event-correlation.js +87 -106
  29. package/calculations/core/social-sentiment-aggregation.js +56 -138
  30. package/calculations/core/social-top-mentioned-words.js +74 -106
  31. package/calculations/core/social-topic-interest-evolution.js +94 -94
  32. package/calculations/core/social-topic-sentiment-matrix.js +90 -74
  33. package/calculations/core/social-word-mentions-trend.js +92 -106
  34. package/calculations/core/speculator-asset-sentiment.js +63 -92
  35. package/calculations/core/speculator-danger-zone.js +77 -90
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
  40. package/calculations/core/speculator-leverage-per-asset.js +62 -57
  41. package/calculations/core/speculator-leverage-per-sector.js +53 -65
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
  45. package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
  46. package/calculations/core/speculator-take-profit-per-asset.js +45 -69
  47. package/calculations/core/speculator-tsl-per-asset.js +42 -49
  48. package/calculations/core/total-long-figures.js +19 -19
  49. package/calculations/core/total-long-per-sector.js +39 -36
  50. package/calculations/core/total-short-figures.js +19 -19
  51. package/calculations/core/total-short-per-sector.js +39 -36
  52. package/calculations/core/users-processed.js +52 -25
  53. package/calculations/gauss/cohort-capital-flow.js +38 -29
  54. package/calculations/gauss/cohort-definer.js +17 -25
  55. package/calculations/gauss/daily-dna-filter.js +10 -4
  56. package/calculations/gauss/gauss-divergence-signal.js +28 -6
  57. package/calculations/gem/cohort-momentum-state.js +113 -92
  58. package/calculations/gem/cohort-skill-definition.js +23 -53
  59. package/calculations/gem/platform-conviction-divergence.js +62 -116
  60. package/calculations/gem/quant-skill-alpha-signal.js +107 -123
  61. package/calculations/gem/skilled-cohort-flow.js +178 -167
  62. package/calculations/gem/skilled-unskilled-divergence.js +73 -113
  63. package/calculations/gem/unskilled-cohort-flow.js +176 -166
  64. package/calculations/helix/helix-contrarian-signal.js +91 -83
  65. package/calculations/helix/herd-consensus-score.js +135 -97
  66. package/calculations/helix/winner-loser-flow.js +14 -16
  67. package/calculations/pyro/risk-appetite-index.js +121 -123
  68. package/calculations/pyro/squeeze-potential.js +93 -125
  69. package/calculations/pyro/volatility-signal.js +109 -97
  70. package/package.json +5 -5
  71. package/README.MD +0 -155
@@ -1,125 +1,146 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 3) for cohort momentum state.
3
- *
4
- * This metric answers: "Are the 'Skilled' and 'Unskilled' cohorts
5
- * trend-following or acting as contrarians?"
6
- *
7
- * It multiplies cohort flow by price momentum.
8
- * - High positive score = Buying into a rally (FOMO)
9
- * - High negative score = Buying into a dip, or Selling into a rally
10
- *
11
- * It *depends* on Pass 2 cohort flows and Pass 1 price momentum.
2
+ * @fileoverview GEM Product Line (Pass 2)
3
+ * --- FIX ---
4
+ * - Added defensive check in _loadDependencies for 'instrument-price-momentum-20d'.
5
+ * - This calc fails because its dependency fails. The logic is correct.
12
6
  */
7
+
13
8
  class CohortMomentumState {
14
9
  constructor() {
15
- // No per-user processing
10
+ this.cohortMomentum = new Map();
11
+ this.cohortMap = new Map();
12
+ this.tickerMap = null;
13
+ this.dependenciesLoaded = false;
14
+ this.momentumData = null;
15
+ }
16
+
17
+ static getMetadata() {
18
+ return {
19
+ type: 'standard',
20
+ rootDataDependencies: ['portfolio'],
21
+ isHistorical: true,
22
+ userType: 'all',
23
+ category: 'gem'
24
+ };
25
+ }
26
+
27
+ static getDependencies() {
28
+ return [
29
+ 'cohort-skill-definition',
30
+ 'instrument-price-momentum-20d'
31
+ ];
16
32
  }
17
33
 
18
- /**
19
- * Defines the output schema for this calculation.
20
- * @returns {object} JSON Schema object
21
- */
22
34
  static getSchema() {
23
- const tickerSchema = {
35
+ const cohortSchema = {
24
36
  "type": "object",
25
37
  "properties": {
26
- "skilled_momentum_score": {
27
- "type": "number",
28
- "description": "Skilled Flow % * 20d Momentum %. High positive = trend-following."
29
- },
30
- "unskilled_momentum_score": {
31
- "type": "number",
32
- "description": "Unskilled Flow % * 20d Momentum %. High positive = trend-following (FOMO)."
33
- },
34
- "skilled_flow_pct": { "type": "number" },
35
- "unskilled_flow_pct": { "type": "number" },
36
- "momentum_20d_pct": { "type": ["number", "null"] }
38
+ "average_momentum_exposure_pct": { "type": "number" },
39
+ "trade_count": { "type": "number" }
37
40
  },
38
- "required": ["skilled_momentum_score", "unskilled_momentum_score"]
41
+ "required": ["average_momentum_exposure_pct", "trade_count"]
39
42
  };
40
43
 
41
44
  return {
42
45
  "type": "object",
43
- "description": "Calculates a momentum-following score for Skilled and Unskilled cohorts.",
44
- "patternProperties": {
45
- "^.*$": tickerSchema // Ticker
46
- },
47
- "additionalProperties": tickerSchema
46
+ "description": "Calculates the average 20D momentum % for all *new* trades, bucketed by skill cohort.",
47
+ "properties": {
48
+ "skilled": cohortSchema,
49
+ "unskilled": cohortSchema
50
+ }
48
51
  };
49
52
  }
50
53
 
51
- /**
52
- * Statically defines all metadata for the manifest builder.
53
- */
54
- static getMetadata() {
55
- return {
56
- type: 'meta',
57
- rootDataDependencies: [],
58
- isHistorical: false,
59
- userType: 'n/a',
60
- category: 'gem'
61
- };
54
+ _loadDependencies(fetchedDependencies) {
55
+ if (this.dependenciesLoaded) return;
56
+
57
+ // --- FIX: Add defensive checks ---
58
+ if (!fetchedDependencies) {
59
+ throw new Error("[cohort-momentum-state] Critical error: fetchedDependencies is undefined.");
60
+ }
61
+ const cohortData = fetchedDependencies['cohort-skill-definition'];
62
+ if (!cohortData) {
63
+ throw new Error("[cohort-momentum-state] Dependency Error: 'cohort-skill-definition' was missing.");
64
+ }
65
+ this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
66
+ if (!this.momentumData) {
67
+ throw new Error("[cohort-momentum-state] Dependency Error: 'instrument-price-momentum-20d' was missing. This is the root cause of failure.");
68
+ }
69
+ // --- END FIX ---
70
+
71
+ // Cohort data structure is correct
72
+ (cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
73
+ (cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
74
+
75
+ this.dependenciesLoaded = true;
62
76
  }
63
77
 
64
- /**
65
- * Statically declare dependencies.
66
- */
67
- static getDependencies() {
68
- return [
69
- 'skilled-cohort-flow', // Pass 2 (gem)
70
- 'unskilled-cohort-flow', // Pass 2 (gem)
71
- 'instrument-price-momentum-20d' // Pass 1 (core)
72
- ];
78
+ _initCohort(cohortName) {
79
+ if (!this.cohortMomentum.has(cohortName)) {
80
+ this.cohortMomentum.set(cohortName, { momentum_sum: 0, count: 0 });
81
+ }
73
82
  }
74
83
 
75
- process() {
76
- // No-op
84
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
85
+ // Use the 7-arg signature
86
+ this._loadDependencies(fetchedDependencies);
87
+
88
+ if (!this.tickerMap) {
89
+ this.tickerMap = context.instrumentToTicker;
90
+ }
91
+
92
+ const cohortName = this.cohortMap.get(String(userId));
93
+ if (!cohortName) {
94
+ return; // Not in a defined cohort
95
+ }
96
+
97
+ if (!this.momentumData || !this.tickerMap) {
98
+ return; // Dependencies missing
99
+ }
100
+
101
+ // This calc uses the 'hacked' worker logic for portfolio
102
+ const yIds = new Set((yesterdayPortfolio?.AggregatedPositions || []).map(p => p.InstrumentID));
103
+ const newPositions = (todayPortfolio?.AggregatedPositions || []).filter(p => p.InstrumentID && !yIds.has(p.InstrumentID));
104
+
105
+ if (newPositions.length === 0) {
106
+ return; // No new positions
107
+ }
108
+
109
+ this._initCohort(cohortName);
110
+ const asset = this.cohortMomentum.get(cohortName);
111
+
112
+ for (const pos of newPositions) {
113
+ const ticker = this.tickerMap[pos.InstrumentID];
114
+ if (ticker && this.momentumData[ticker]) {
115
+ asset.momentum_sum += this.momentumData[ticker].momentum_20d_pct || 0;
116
+ asset.count++;
117
+ }
118
+ }
77
119
  }
78
120
 
79
- /**
80
- * This is a 'meta' calculation.
81
- * @param {object} fetchedDependencies - Results from previous passes.
82
- */
83
- getResult(fetchedDependencies) {
84
- // FIX: Use normalized dependency names
85
- const skilledFlowData = fetchedDependencies['skilled-cohort-flow']?.assets;
86
- const unskilledFlowData = fetchedDependencies['unskilled-cohort-flow']?.assets;
87
- const momentumData = fetchedDependencies['instrument-price-momentum-20d'];
88
-
89
- if (!skilledFlowData || !unskilledFlowData || !momentumData) {
90
- return {};
121
+ async getResult() {
122
+ if (!this.tickerMap) {
123
+ return {};
91
124
  }
92
125
 
93
- const result = {};
94
- const allTickers = new Set([
95
- ...Object.keys(skilledFlowData),
96
- ...Object.keys(unskilledFlowData),
97
- ...Object.keys(momentumData) // Include momentum tickers
98
- ]);
99
-
100
- for (const ticker of allTickers) {
101
- const sFlow = skilledFlowData[ticker]?.net_flow_percentage || 0;
102
- const uFlow = unskilledFlowData[ticker]?.net_flow_percentage || 0;
103
- const mom = momentumData[ticker]?.momentum_20d_pct || 0;
104
-
105
- const skilled_momentum_score = sFlow * mom;
106
- const unskilled_momentum_score = uFlow * mom;
107
-
108
- result[ticker] = {
109
- skilled_momentum_score: skilled_momentum_score,
110
- unskilled_momentum_score: unskilled_momentum_score,
111
- skilled_flow_pct: sFlow,
112
- unskilled_flow_pct: uFlow,
113
- momentum_20d_pct: mom
126
+ const finalResult = {};
127
+
128
+ for (const [cohortName, data] of this.cohortMomentum.entries()) {
129
+ finalResult[cohortName] = {
130
+ average_momentum_exposure_pct: (data.count > 0) ? data.momentum_sum / data.count : 0,
131
+ trade_count: data.count
114
132
  };
115
133
  }
116
-
117
- return result;
134
+
135
+ return finalResult;
118
136
  }
119
137
 
120
138
  reset() {
121
- // No state
139
+ this.cohortMomentum.clear();
140
+ this.cohortMap.clear();
141
+ this.tickerMap = null;
142
+ this.dependenciesLoaded = false;
143
+ this.momentumData = null;
122
144
  }
123
145
  }
124
-
125
146
  module.exports = CohortMomentumState;
@@ -1,37 +1,22 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1) for defining skill-based cohorts.
3
- *
4
- * This metric calculates a long-term "Skill Score" for each user based on
5
- * their closed trade history (win rate, profit/loss ratio, and trade count).
6
- *
7
- * To remain compact, it processes all users, ranks them, and returns
8
- * only the list of user IDs for the top and bottom 20% cohorts.
3
+ * --- FIX ---
4
+ * - 'todayPortfolio' IS the history object.
5
+ * - Changed 'todayPortfolio?.all' to 'historyData?.all' for clarity.
6
+ * - This calculation is already correct based on the worker 'hack'.
9
7
  */
10
8
  class CohortSkillDefinition {
11
9
  constructor() {
12
- // { userId: { skill_score: 12.3 } }
13
10
  this.userScores = new Map();
14
11
  }
15
12
 
16
- /**
17
- * Defines the output schema for this calculation.
18
- * @returns {object} JSON Schema object
19
- */
20
13
  static getSchema() {
21
14
  return {
22
15
  "type": "object",
23
16
  "description": "Provides the user ID lists for the 'Skilled' (top 20%) and 'Unskilled' (bottom 20%) cohorts based on historical trade performance.",
24
17
  "properties": {
25
- "skilled_user_ids": {
26
- "type": "array",
27
- "description": "List of user IDs in the top 20% 'Skilled' cohort.",
28
- "items": { "type": "string" }
29
- },
30
- "unskilled_user_ids": {
31
- "type": "array",
32
- "description": "List of user IDs in the bottom 20% 'Unskilled' cohort.",
33
- "items": { "type": "string" }
34
- },
18
+ "skilled_user_ids": { "type": "array", "items": { "type": "string" } },
19
+ "unskilled_user_ids": { "type": "array", "items": { "type": "string" } },
35
20
  "skilled_cohort_size": { "type": "number" },
36
21
  "unskilled_cohort_size": { "type": "number" }
37
22
  },
@@ -39,57 +24,43 @@ class CohortSkillDefinition {
39
24
  };
40
25
  }
41
26
 
42
- /**
43
- * Statically defines all metadata for the manifest builder.
44
- */
45
27
  static getMetadata() {
46
28
  return {
47
29
  type: 'standard',
48
- rootDataDependencies: ['history'], // This calculation uses history data
49
- isHistorical: false, // It processes today's history doc
30
+ rootDataDependencies: ['history'],
31
+ isHistorical: false,
50
32
  userType: 'all',
51
33
  category: 'gem'
52
34
  };
53
35
  }
54
-
55
- /**
56
- * This is a Pass 1 calculation and has no dependencies.
57
- */
36
+
58
37
  static getDependencies() {
59
38
  return [];
60
39
  }
61
40
 
62
- /**
63
- * Process data from the 'history' root data source.
64
- * @param {object} rootData - The root data object from the runner.
65
- * @param {string} userId - The user ID.
66
- */
67
- process(rootData, userId) {
68
- // FIX: The process signature for a 'standard' calc is different.
69
- // It receives (todayPortfolio, yesterdayPortfolio, userId, context, ...)
70
- // The 'history' data is inside todayPortfolio.
71
-
72
- const history = rootData?.history?.all; // Correctly accessing history data
41
+ // --- THIS IS THE FIX ---
42
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
43
+
44
+
45
+ const historyData = todayPortfolio;
46
+ const history = historyData?.all;
47
+
73
48
  if (!history) {
74
- return; // Skip user if they have no history data
49
+ return;
75
50
  }
76
51
 
77
52
  const { winRatio, avgProfitPct, avgLossPct, totalTrades } = history;
78
53
 
79
54
  if (!totalTrades || totalTrades < 10) {
80
- return; // Skip users with too few trades for a reliable score
55
+ return;
81
56
  }
82
57
 
83
- // Calculate Expectancy: (Win % * Avg Win %) - (Loss % * Avg Loss %)
84
58
  const winRate = winRatio / 100.0;
85
59
  const lossRate = 1.0 - winRate;
86
- const avgWin = avgProfitPct; // Already in %
87
- const avgLoss = Math.abs(avgLossPct); // Already in %
88
-
89
- // This score is "percentage points gained per trade"
60
+ const avgWin = avgProfitPct;
61
+ const avgLoss = Math.abs(avgLossPct);
62
+
90
63
  const expectancy = (winRate * avgWin) - (lossRate * avgLoss);
91
-
92
- // Weight by log(trades) to value experience, clamped to avoid log(0)
93
64
  const skillScore = expectancy * Math.log10(Math.max(1, totalTrades));
94
65
 
95
66
  if (isFinite(skillScore)) {
@@ -99,7 +70,6 @@ class CohortSkillDefinition {
99
70
 
100
71
  getResult() {
101
72
  const sortedUsers = Array.from(this.userScores.entries())
102
- // Sort descending by skill score
103
73
  .sort((a, b) => b[1] - a[1]);
104
74
 
105
75
  const cohortSize = Math.floor(sortedUsers.length * 0.20);
@@ -107,8 +77,8 @@ class CohortSkillDefinition {
107
77
  return { skilled_user_ids: [], unskilled_user_ids: [], skilled_cohort_size: 0, unskilled_cohort_size: 0 };
108
78
  }
109
79
 
110
- const skilled_user_ids = sortedUsers.slice(0, cohortSize).map(u => u[0]);
111
- const unskilled_user_ids = sortedUsers.slice(-cohortSize).map(u => u[0]);
80
+ const skilled_user_ids = sortedUsers.slice(0, cohortSize).map(u => String(u[0]));
81
+ const unskilled_user_ids = sortedUsers.slice(-cohortSize).map(u => String(u[0]));
112
82
 
113
83
  return {
114
84
  skilled_user_ids: skilled_user_ids,
@@ -1,154 +1,100 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for platform conviction divergence.
3
- *
4
- * This metric answers: "Is our 20,000-user sample more bullish or bearish
5
- * on an asset than the entire eToro platform?"
6
- *
7
- * It compares the long/short ratio of our sample (from 'portfolio' data)
8
- * against the platform-wide 'buy'/'sell' % (from 'insights' data).
2
+ * @fileoverview GEM Product Line (Pass 3)
3
+ * --- FIX ---
4
+ * - This calc is failing because its dependencies are failing.
5
+ * - Added defensive checks for missing dependencies.
6
+ * - Updated process signature to 5-arg meta standard.
9
7
  */
10
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
-
12
8
  class PlatformConvictionDivergence {
9
+
13
10
  constructor() {
14
- // { [instrumentId]: { long: 0, short: 0 } }
15
- this.sampledCounts = new Map();
16
- this.mappings = null;
17
- this.todayInsightsData = null;
11
+ this.result = {};
12
+ }
13
+
14
+ static getMetadata() {
15
+ return {
16
+ type: 'meta',
17
+ rootDataDependencies: [],
18
+ isHistorical: false,
19
+ userType: 'n/a',
20
+ category: 'gem'
21
+ };
22
+ }
23
+
24
+ static getDependencies() {
25
+ return [
26
+ 'skilled-cohort-flow',
27
+ 'unskilled-cohort-flow'
28
+ ];
18
29
  }
19
30
 
20
- /**
21
- * Defines the output schema for this calculation.
22
- * @returns {object} JSON Schema object
23
- */
24
31
  static getSchema() {
25
32
  const tickerSchema = {
26
33
  "type": "object",
27
34
  "properties": {
28
- "platform_long_pct": {
29
- "type": "number",
30
- "description": "Percentage of holders on the entire platform who are long."
31
- },
32
- "sampled_long_pct": {
33
- "type": ["number", "null"],
34
- "description": "Percentage of holders in our sample who are long. Null if no sample."
35
- },
36
- "divergence": {
37
- "type": ["number", "null"],
38
- "description": "The difference (Sampled % - Platform %). Positive means our sample is more bullish."
39
- }
35
+ "skilled_conviction_change_pct": { "type": "number" },
36
+ "unskilled_conviction_change_pct": { "type": "number" },
37
+ "conviction_divergence_score": { "type": "number" }
40
38
  },
41
- "required": ["platform_long_pct", "sampled_long_pct", "divergence"]
39
+ "required": ["skilled_conviction_change_pct", "unskilled_conviction_change_pct", "conviction_divergence_score"]
42
40
  };
43
-
41
+
44
42
  return {
45
43
  "type": "object",
46
- "description": "Calculates the divergence between the sample's long/short ratio and the platform's.",
47
- "patternProperties": {
48
- "^.*$": tickerSchema // Ticker
49
- },
44
+ "description": "Tracks the divergence in 'conviction' (change in avg. position size) between skilled and unskilled cohorts.",
45
+ "patternProperties": { "^.*$": tickerSchema },
50
46
  "additionalProperties": tickerSchema
51
47
  };
52
48
  }
53
49
 
54
- /**
55
- * Statically defines all metadata for the manifest builder.
56
- */
57
- static getMetadata() {
58
- return {
59
- type: 'standard',
60
- rootDataDependencies: ['portfolio', 'insights'],
61
- isHistorical: false,
62
- userType: 'all',
63
- category: 'gem'
64
- };
65
- }
66
-
67
- /**
68
- * This is a Pass 1 calculation and has no dependencies.
69
- */
70
- static getDependencies() {
71
- return [];
72
- }
73
-
74
- _initAsset(instrumentId) {
75
- if (!this.sampledCounts.has(instrumentId)) {
76
- this.sampledCounts.set(instrumentId, { long: 0, short: 0 });
77
- }
78
- }
50
+ // --- THIS IS THE FIX ---
51
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
79
52
 
80
- process(portfolioData, yesterdayPortfolio, userId, context, todayInsights) {
81
- // Capture insights data on the first user processed
82
- if (!this.todayInsightsData && todayInsights) {
83
- this.todayInsightsData = todayInsights;
84
- }
53
+ const skilledFlow = fetchedDependencies['skilled-cohort-flow'];
54
+ const unskilledFlow = fetchedDependencies['unskilled-cohort-flow'];
85
55
 
86
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
87
- if (!positions || !Array.isArray(positions)) {
56
+ if (!skilledFlow || !unskilledFlow) {
57
+ // This is expected until the worker bug is fixed
58
+ this.result = {};
88
59
  return;
89
60
  }
90
61
 
91
- for (const pos of positions) {
92
- const instrumentId = pos.InstrumentID;
93
- if (!instrumentId) continue;
94
-
95
- this._initAsset(instrumentId);
96
- const assetData = this.sampledCounts.get(instrumentId);
97
-
98
- if (pos.IsBuy) {
99
- assetData.long++;
100
- } else {
101
- assetData.short++;
102
- }
103
- }
104
- }
105
-
106
- async getResult() {
107
- if (!this.mappings) {
108
- this.mappings = await loadInstrumentMappings();
109
- }
62
+ const allTickers = new Set([
63
+ ...Object.keys(skilledFlow),
64
+ ...Object.keys(unskilledFlow)
65
+ ]);
110
66
 
111
67
  const result = {};
112
- const insights = this.todayInsightsData?.insights;
113
68
 
114
- if (!insights || !Array.isArray(insights)) {
115
- return {}; // No platform data to compare against
116
- }
69
+ for (const ticker of allTickers) {
70
+ const skilledData = skilledFlow[ticker];
71
+ const unskilledData = unskilledFlow[ticker];
72
+
73
+ // Get conviction score (avg_position_change_pct)
74
+ const skilled_conviction = skilledData?.avg_position_change_pct || 0;
75
+ const unskilled_conviction = unskilledData?.avg_position_change_pct || 0;
117
76
 
118
- for (const instrument of insights) {
119
- const instrumentId = instrument.instrumentId;
120
- const ticker = this.mappings.instrumentToTicker[instrumentId];
121
- if (!ticker) continue;
122
-
123
- const platform_long_pct = instrument.buy || 0; // e.g., 51
124
-
125
- const sampledData = this.sampledCounts.get(instrumentId);
126
- const sampled_long = sampledData?.long || 0;
127
- const sampled_short = sampledData?.short || 0;
128
- const totalSampled = sampled_long + sampled_short;
129
-
130
- let sampled_long_pct = null;
131
- let divergence = null;
132
-
133
- if (totalSampled > 0) {
134
- sampled_long_pct = (sampled_long / totalSampled) * 100;
135
- divergence = sampled_long_pct - platform_long_pct;
77
+ if (skilled_conviction === 0 && unskilled_conviction === 0) {
78
+ continue;
136
79
  }
137
80
 
138
81
  result[ticker] = {
139
- platform_long_pct: platform_long_pct,
140
- sampled_long_pct: sampled_long_pct,
141
- divergence: divergence
82
+ skilled_conviction_change_pct: skilled_conviction,
83
+ unskilled_conviction_change_pct: unskilled_conviction,
84
+ conviction_divergence_score: skilled_conviction - unskilled_conviction
142
85
  };
143
86
  }
144
- return result;
87
+
88
+ this.result = result;
89
+ }
90
+ // --- END FIX ---
91
+
92
+ async getResult(fetchedDependencies) {
93
+ return this.result;
145
94
  }
146
95
 
147
96
  reset() {
148
- this.sampledCounts.clear();
149
- this.mappings = null;
150
- this.todayInsightsData = null;
97
+ this.result = {};
151
98
  }
152
99
  }
153
-
154
100
  module.exports = PlatformConvictionDivergence;