aiden-shared-calculations-unified 1.0.71 → 1.0.73

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.
@@ -102,8 +102,8 @@ class AssetCrowdFlow {
102
102
  const yP = yPosMap.get(instrumentId);
103
103
  const tP = tPosMap.get(instrumentId);
104
104
 
105
- const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
106
- const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
105
+ const yInvested = yP?.Invested || 0;
106
+ const tInvested = tP?.Invested || 0;
107
107
 
108
108
  if (yInvested > 0) {
109
109
  asset.total_invested_yesterday += yInvested;
@@ -61,7 +61,7 @@ class DumbCohortFlow {
61
61
  * Statically declare dependencies.
62
62
  */
63
63
  static getDependencies() {
64
- return ['user_profitability_tracker'];
64
+ return ['user_profitability_tracker'];
65
65
  }
66
66
 
67
67
  _getPortfolioPositions(portfolio) {
@@ -0,0 +1,109 @@
1
+ /**
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.
9
+ */
10
+ class CohortSkillDefinition {
11
+ constructor() {
12
+ // { userId: { skill_score: 12.3 } }
13
+ this.userScores = new Map();
14
+ }
15
+
16
+ /**
17
+ * Defines the output schema for this calculation.
18
+ * @returns {object} JSON Schema object
19
+ */
20
+ static getSchema() {
21
+ return {
22
+ "type": "object",
23
+ "description": "Provides the user ID lists for the 'Skilled' (top 20%) and 'Unskilled' (bottom 20%) cohorts based on historical trade performance.",
24
+ "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
+ },
35
+ "skilled_cohort_size": { "type": "number" },
36
+ "unskilled_cohort_size": { "type": "number" }
37
+ },
38
+ "required": ["skilled_user_ids", "unskilled_user_ids", "skilled_cohort_size", "unskilled_cohort_size"]
39
+ };
40
+ }
41
+
42
+ /**
43
+ * This is a Pass 1 calculation and has no dependencies.
44
+ */
45
+ static getDependencies() {
46
+ return [];
47
+ }
48
+
49
+ /**
50
+ * Process data from the 'history' root data source.
51
+ * @param {object} rootData - The root data object from the runner.
52
+ * @param {string} userId - The user ID.
53
+ */
54
+ process(rootData, userId) {
55
+ const history = rootData?.history?.all;
56
+ if (!history) {
57
+ return; // Skip user if they have no history data
58
+ }
59
+
60
+ const { winRatio, avgProfitPct, avgLossPct, totalTrades } = history;
61
+
62
+ if (!totalTrades || totalTrades < 10) {
63
+ return; // Skip users with too few trades for a reliable score
64
+ }
65
+
66
+ // Calculate Expectancy: (Win % * Avg Win %) - (Loss % * Avg Loss %)
67
+ const winRate = winRatio / 100.0;
68
+ const lossRate = 1.0 - winRate;
69
+ const avgWin = avgProfitPct; // Already in %
70
+ const avgLoss = Math.abs(avgLossPct); // Already in %
71
+
72
+ // This score is "percentage points gained per trade"
73
+ const expectancy = (winRate * avgWin) - (lossRate * avgLoss);
74
+
75
+ // Weight by log(trades) to value experience, clamped to avoid log(0)
76
+ const skillScore = expectancy * Math.log10(Math.max(1, totalTrades));
77
+
78
+ if (isFinite(skillScore)) {
79
+ this.userScores.set(userId, skillScore);
80
+ }
81
+ }
82
+
83
+ getResult() {
84
+ const sortedUsers = Array.from(this.userScores.entries())
85
+ // Sort descending by skill score
86
+ .sort((a, b) => b[1] - a[1]);
87
+
88
+ const cohortSize = Math.floor(sortedUsers.length * 0.20);
89
+ if (cohortSize === 0) {
90
+ return { skilled_user_ids: [], unskilled_user_ids: [], skilled_cohort_size: 0, unskilled_cohort_size: 0 };
91
+ }
92
+
93
+ const skilled_user_ids = sortedUsers.slice(0, cohortSize).map(u => u[0]);
94
+ const unskilled_user_ids = sortedUsers.slice(-cohortSize).map(u => u[0]);
95
+
96
+ return {
97
+ skilled_user_ids: skilled_user_ids,
98
+ unskilled_user_ids: unskilled_user_ids,
99
+ skilled_cohort_size: skilled_user_ids.length,
100
+ unskilled_cohort_size: unskilled_user_ids.length
101
+ };
102
+ }
103
+
104
+ reset() {
105
+ this.userScores.clear();
106
+ }
107
+ }
108
+
109
+ module.exports = CohortSkillDefinition;
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @fileoverview Calculation (Pass 2) for "Skilled Cohort" flow.
3
+ *
4
+ * This metric calculates the "Net Crowd Flow Percentage" for the
5
+ * "Skilled Cohort" (top 20% by long-term skill score).
6
+ *
7
+ * This calculation *depends* on 'cohort-skill-definition'
8
+ * to identify the cohort.
9
+ */
10
+ const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
11
+
12
+ class SkilledCohortFlow {
13
+ constructor() {
14
+ this.assetData = new Map();
15
+ this.sectorData = new Map();
16
+ this.mappings = null;
17
+ this.skilledCohortUserIds = null;
18
+ }
19
+
20
+ /**
21
+ * Defines the output schema for this calculation.
22
+ * @returns {object} JSON Schema object
23
+ */
24
+ static getSchema() {
25
+ const flowSchema = {
26
+ "type": "object",
27
+ "properties": {
28
+ "net_flow_percentage": { "type": "number" },
29
+ "total_invested_today": { "type": "number" },
30
+ "total_invested_yesterday": { "type": "number" }
31
+ },
32
+ "required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
33
+ };
34
+
35
+ return {
36
+ "type": "object",
37
+ "description": "Calculates net capital flow % (price-adjusted) for the 'Skilled Cohort' (top 20% by skill score), aggregated by asset and sector.",
38
+ "properties": {
39
+ "cohort_size": {
40
+ "type": "number",
41
+ "description": "The number of users identified as being in the Skilled Cohort."
42
+ },
43
+ "assets": {
44
+ "type": "object",
45
+ "description": "Price-adjusted net flow per asset.",
46
+ "patternProperties": { "^.*$": flowSchema }, // Ticker
47
+ "additionalProperties": flowSchema
48
+ },
49
+ "sectors": {
50
+ "type": "object",
51
+ "description": "Price-adjusted net flow per sector.",
52
+ "patternProperties": { "^.*$": flowSchema }, // Sector
53
+ "additionalProperties": flowSchema
54
+ }
55
+ },
56
+ "required": ["cohort_size", "assets", "sectors"]
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Statically declare dependencies.
62
+ */
63
+ static getDependencies() {
64
+ return ['gem_cohort-skill-definition']; // Pass 1
65
+ }
66
+
67
+ _getPortfolioPositions(portfolio) {
68
+ return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
69
+ }
70
+
71
+ _initAsset(instrumentId) {
72
+ if (!this.assetData.has(instrumentId)) {
73
+ this.assetData.set(instrumentId, {
74
+ total_invested_yesterday: 0,
75
+ total_invested_today: 0,
76
+ price_change_yesterday: 0,
77
+ });
78
+ }
79
+ }
80
+
81
+ _initSector(sector) {
82
+ if (!this.sectorData.has(sector)) {
83
+ this.sectorData.set(sector, {
84
+ total_invested_yesterday: 0,
85
+ total_invested_today: 0,
86
+ price_change_yesterday: 0,
87
+ });
88
+ }
89
+ }
90
+
91
+ _getSkilledCohort(fetchedDependencies) {
92
+ if (this.skilledCohortUserIds) {
93
+ return this.skilledCohortUserIds;
94
+ }
95
+
96
+ const cohortData = fetchedDependencies['cohort-skill-definition'];
97
+ if (!cohortData || !cohortData.skilled_user_ids) {
98
+ return new Set();
99
+ }
100
+
101
+ this.skilledCohortUserIds = new Set(cohortData.skilled_user_ids);
102
+ return this.skilledCohortUserIds;
103
+ }
104
+
105
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
106
+ const skilledCohort = this._getSkilledCohort(fetchedDependencies);
107
+
108
+ if (!skilledCohort.has(userId)) {
109
+ return; // This user is not in the "skilled cohort", skip.
110
+ }
111
+
112
+ if (!todayPortfolio || !yesterdayPortfolio) {
113
+ return;
114
+ }
115
+
116
+ // Logic from here is identical to smart-cohort-flow.js
117
+ const yPos = this._getPortfolioPositions(yesterdayPortfolio);
118
+ const tPos = this._getPortfolioPositions(todayPortfolio);
119
+ const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
120
+ const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
121
+ const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
122
+
123
+ if (!this.mappings) {
124
+ this.mappings = context.mappings;
125
+ }
126
+
127
+ for (const instrumentId of allInstrumentIds) {
128
+ if (!instrumentId) continue;
129
+
130
+ this._initAsset(instrumentId);
131
+ const asset = this.assetData.get(instrumentId);
132
+ const yP = yPosMap.get(instrumentId);
133
+ const tP = tPosMap.get(instrumentId);
134
+
135
+ // Use 'Invested' for % portfolio, not 'Amount'
136
+ const yInvested = yP?.Invested || 0;
137
+ const tInvested = tP?.Invested || 0;
138
+
139
+ const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
140
+ this._initSector(sector);
141
+ const sectorAsset = this.sectorData.get(sector);
142
+
143
+ if (yInvested > 0) {
144
+ // Use P&L % from portfolio 'NetProfit'
145
+ const yPriceChange = (yP?.NetProfit || 0) / (yP?.Invested || 1);
146
+
147
+ asset.total_invested_yesterday += yInvested;
148
+ asset.price_change_yesterday += yPriceChange * yInvested;
149
+
150
+ sectorAsset.total_invested_yesterday += yInvested;
151
+ sectorAsset.price_change_yesterday += yPriceChange * yInvested;
152
+ }
153
+ if (tInvested > 0) {
154
+ asset.total_invested_today += tInvested;
155
+ sectorAsset.total_invested_today += tInvested;
156
+ }
157
+ }
158
+ }
159
+
160
+ _calculateFlow(dataMap) {
161
+ const result = {};
162
+ for (const [key, data] of dataMap.entries()) {
163
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
164
+
165
+ if (total_invested_yesterday > 0) {
166
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
167
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
168
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
169
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
170
+
171
+ result[key] = {
172
+ net_flow_percentage: net_flow_percentage,
173
+ total_invested_today: total_invested_today,
174
+ total_invested_yesterday: total_invested_yesterday
175
+ };
176
+ }
177
+ }
178
+ return result;
179
+ }
180
+
181
+ async getResult(fetchedDependencies) {
182
+ if (!this.mappings) {
183
+ this.mappings = await loadInstrumentMappings();
184
+ }
185
+
186
+ const skilledCohort = this._getSkilledCohort(fetchedDependencies);
187
+
188
+ // 1. Calculate Asset Flow
189
+ const assetResult = {};
190
+ for (const [instrumentId, data] of this.assetData.entries()) {
191
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
192
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
193
+
194
+ if (total_invested_yesterday > 0) {
195
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
196
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
197
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
198
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
199
+
200
+ assetResult[ticker] = {
201
+ net_flow_percentage: net_flow_percentage,
202
+ total_invested_today: total_invested_today,
203
+ total_invested_yesterday: total_invested_yesterday
204
+ };
205
+ }
206
+ }
207
+
208
+ // 2. Calculate Sector Flow
209
+ const sectorResult = this._calculateFlow(this.sectorData);
210
+
211
+ return {
212
+ cohort_size: skilledCohort.size,
213
+ assets: assetResult,
214
+ sectors: sectorResult
215
+ };
216
+ }
217
+
218
+ reset() {
219
+ this.assetData.clear();
220
+ this.sectorData.clear();
221
+ this.mappings = null;
222
+ this.skilledCohortUserIds = null;
223
+ }
224
+ }
225
+
226
+ module.exports = SkilledCohortFlow;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * @fileoverview Calculation (Pass 2) for "Unskilled Cohort" flow.
3
+ *
4
+ * This metric calculates the "Net Crowd Flow Percentage" for the
5
+ * "Unskilled Cohort" (bottom 20% by long-term skill score).
6
+ *
7
+ * This calculation *depends* on 'cohort-skill-definition'
8
+ * to identify the cohort.
9
+ */
10
+ const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
11
+
12
+ class UnskilledCohortFlow {
13
+ constructor() {
14
+ this.assetData = new Map();
15
+ this.sectorData = new Map();
16
+ this.mappings = null;
17
+ this.unskilledCohortUserIds = null;
18
+ }
19
+
20
+ /**
21
+ * Defines the output schema for this calculation.
22
+ * @returns {object} JSON Schema object
23
+ */
24
+ static getSchema() {
25
+ const flowSchema = {
26
+ "type": "object",
27
+ "properties": {
28
+ "net_flow_percentage": { "type": "number" },
29
+ "total_invested_today": { "type": "number" },
30
+ "total_invested_yesterday": { "type": "number" }
31
+ },
32
+ "required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
33
+ };
34
+
35
+ return {
36
+ "type": "object",
37
+ "description": "Calculates net capital flow % (price-adjusted) for the 'Unskilled Cohort' (bottom 20% by skill score), aggregated by asset and sector.",
38
+ "properties": {
39
+ "cohort_size": {
40
+ "type": "number",
41
+ "description": "The number of users identified as being in the Unskilled Cohort."
42
+ },
43
+ "assets": {
44
+ "type": "object",
45
+ "description": "Price-adjusted net flow per asset.",
46
+ "patternProperties": { "^.*$": flowSchema }, // Ticker
47
+ "additionalProperties": flowSchema
48
+ },
49
+ "sectors": {
50
+ "type": "object",
51
+ "description": "Price-adjusted net flow per sector.",
52
+ "patternProperties": { "^.*$": flowSchema }, // Sector
53
+ "additionalProperties": flowSchema
54
+ }
55
+ },
56
+ "required": ["cohort_size", "assets", "sectors"]
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Statically declare dependencies.
62
+ */
63
+ static getDependencies() {
64
+ return ['gem_cohort-skill-definition']; // Pass 1
65
+ }
66
+
67
+ _getPortfolioPositions(portfolio) {
68
+ return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
69
+ }
70
+
71
+ _initAsset(instrumentId) {
72
+ if (!this.assetData.has(instrumentId)) {
73
+ this.assetData.set(instrumentId, {
74
+ total_invested_yesterday: 0,
75
+ total_invested_today: 0,
76
+ price_change_yesterday: 0,
77
+ });
78
+ }
79
+ }
80
+
81
+ _initSector(sector) {
82
+ if (!this.sectorData.has(sector)) {
83
+ this.sectorData.set(sector, {
84
+ total_invested_yesterday: 0,
85
+ total_invested_today: 0,
86
+ price_change_yesterday: 0,
87
+ });
88
+ }
89
+ }
90
+
91
+ _getUnskilledCohort(fetchedDependencies) {
92
+ if (this.unskilledCohortUserIds) {
93
+ return this.unskilledCohortUserIds;
94
+ }
95
+
96
+ const cohortData = fetchedDependencies['cohort-skill-definition'];
97
+ if (!cohortData || !cohortData.unskilled_user_ids) {
98
+ return new Set();
99
+ }
100
+
101
+ this.unskilledCohortUserIds = new Set(cohortData.unskilled_user_ids);
102
+ return this.unskilledCohortUserIds;
103
+ }
104
+
105
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
106
+ const unskilledCohort = this._getUnskilledCohort(fetchedDependencies);
107
+
108
+ if (!unskilledCohort.has(userId)) {
109
+ return; // This user is not in the "unskilled cohort", skip.
110
+ }
111
+
112
+ if (!todayPortfolio || !yesterdayPortfolio) {
113
+ return;
114
+ }
115
+
116
+ // Logic from here is identical to smart-cohort-flow.js
117
+ const yPos = this._getPortfolioPositions(yesterdayPortfolio);
118
+ const tPos = this._getPortfolioPositions(todayPortfolio);
119
+ const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
120
+ const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
121
+ const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
122
+
123
+ if (!this.mappings) {
124
+ this.mappings = context.mappings;
125
+ }
126
+
127
+ for (const instrumentId of allInstrumentIds) {
128
+ if (!instrumentId) continue;
129
+
130
+ this._initAsset(instrumentId);
131
+ const asset = this.assetData.get(instrumentId);
132
+ const yP = yPosMap.get(instrumentId);
133
+ const tP = tPosMap.get(instrumentId);
134
+
135
+ const yInvested = yP?.Invested || 0;
136
+ const tInvested = tP?.Invested || 0;
137
+
138
+ const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
139
+ this._initSector(sector);
140
+ const sectorAsset = this.sectorData.get(sector);
141
+
142
+ if (yInvested > 0) {
143
+ const yPriceChange = (yP?.NetProfit || 0) / (yP?.Invested || 1);
144
+
145
+ asset.total_invested_yesterday += yInvested;
146
+ asset.price_change_yesterday += yPriceChange * yInvested;
147
+
148
+ sectorAsset.total_invested_yesterday += yInvested;
149
+ sectorAsset.price_change_yesterday += yPriceChange * yInvested;
150
+ }
151
+ if (tInvested > 0) {
152
+ asset.total_invested_today += tInvested;
153
+ sectorAsset.total_invested_today += tInvested;
154
+ }
155
+ }
156
+ }
157
+
158
+ _calculateFlow(dataMap) {
159
+ // This helper is identical to the one in skilled-cohort-flow
160
+ const result = {};
161
+ for (const [key, data] of dataMap.entries()) {
162
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
163
+
164
+ if (total_invested_yesterday > 0) {
165
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
166
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
167
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
168
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
169
+
170
+ result[key] = {
171
+ net_flow_percentage: net_flow_percentage,
172
+ total_invested_today: total_invested_today,
173
+ total_invested_yesterday: total_invested_yesterday
174
+ };
175
+ }
176
+ }
177
+ return result;
178
+ }
179
+
180
+ async getResult(fetchedDependencies) {
181
+ if (!this.mappings) {
182
+ this.mappings = await loadInstrumentMappings();
183
+ }
184
+
185
+ const unskilledCohort = this._getUnskilledCohort(fetchedDependencies);
186
+
187
+ // 1. Calculate Asset Flow
188
+ const assetResult = {};
189
+ for (const [instrumentId, data] of this.assetData.entries()) {
190
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
191
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
192
+
193
+ if (total_invested_yesterday > 0) {
194
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
195
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
196
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
197
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
198
+
199
+ assetResult[ticker] = {
200
+ net_flow_percentage: net_flow_percentage,
201
+ total_invested_today: total_invested_today,
202
+ total_invested_yesterday: total_invested_yesterday
203
+ };
204
+ }
205
+ }
206
+
207
+ // 2. Calculate Sector Flow
208
+ const sectorResult = this._calculateFlow(this.sectorData);
209
+
210
+ return {
211
+ cohort_size: unskilledCohort.size,
212
+ assets: assetResult,
213
+ sectors: sectorResult
214
+ };
215
+ }
216
+
217
+ reset() {
218
+ this.assetData.clear();
219
+ this.sectorData.clear();
220
+ this.mappings = null;
221
+ this.unskilledCohortUserIds = null;
222
+ }
223
+ }
224
+
225
+ module.exports = UnskilledCohortFlow;
@@ -54,7 +54,7 @@ class DailyOwnershipPerSector {
54
54
  // 2. Get the insights document
55
55
  const insightsDoc = rootData.insights;
56
56
  if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
57
- dependencies.logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found for ${dateStr}.`);
57
+ // dependencies.logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found for ${dateStr}.`); TODO : This is broken, returns log undefined
58
58
  return {};
59
59
  }
60
60
 
@@ -6,14 +6,19 @@
6
6
  *
7
7
  * It uses the distribution of P&L from 'pnl_distribution_per_stock'
8
8
  * to calculate variance (risk).
9
+ *
10
+ * --- FIX: 2025-11-12 ---
11
+ * Refactored this file to be a "meta" calculation.
12
+ * 1. Removed constructor, getResult, reset, and the no-op 'process'.
13
+ * 2. Added the required `async process(dStr, deps, config, fetchedDeps)` method.
14
+ * 3. Moved all logic into `process`.
15
+ * 4. Updated logic to read from `fetchedDeps['pnl_distribution_per_stock']`.
16
+ * 5. Updated data access to read from the new `data.stats` object
17
+ * provided by the fixed dependency.
9
18
  */
10
19
  const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
20
 
12
21
  class CrowdSharpeRatioProxy {
13
- constructor() {
14
- this.mappings = null;
15
- }
16
-
17
22
  /**
18
23
  * Defines the output schema for this calculation.
19
24
  * @returns {object} JSON Schema object
@@ -54,25 +59,36 @@ class CrowdSharpeRatioProxy {
54
59
  ];
55
60
  }
56
61
 
57
- process() {
58
- // No-op
59
- }
60
-
61
- async getResult(fetchedDependencies) {
62
+ /**
63
+ * --- FIX: This is the new main execution method for meta-calcs ---
64
+ * It receives all dependencies from the orchestrator.
65
+ */
66
+ async process(dateStr, dependencies, config, fetchedDependencies) {
67
+ // --- FIX: Load dependency data from the argument ---
62
68
  const pnlDistData = fetchedDependencies['pnl_distribution_per_stock'];
63
69
 
64
70
  if (!pnlDistData) {
71
+ dependencies.logger.log('WARN', `[crowd_sharpe_ratio_proxy] Missing dependency 'pnl_distribution_per_stock' for ${dateStr}. Skipping.`);
65
72
  return {};
66
73
  }
67
74
 
68
- if (!this.mappings) {
69
- this.mappings = await loadInstrumentMappings();
75
+ // --- FIX: Load mappings inside the process method ---
76
+ const mappings = await loadInstrumentMappings();
77
+ if (!mappings || !mappings.instrumentToTicker) {
78
+ dependencies.logger.log('ERROR', `[crowd_sharpe_ratio_proxy] Failed to load instrument mappings.`);
79
+ return {};
70
80
  }
71
81
 
72
82
  const result = {};
73
83
 
74
- for (const [instrumentId, data] of Object.entries(pnlDistData)) {
75
- const { sum, sumSq, count } = data;
84
+ for (const [ticker, data] of Object.entries(pnlDistData)) {
85
+
86
+ // --- FIX: Read from the new 'stats' sub-object ---
87
+ if (!data.stats) {
88
+ continue; // Skip if data is malformed
89
+ }
90
+
91
+ const { sum, sumSq, count } = data.stats;
76
92
 
77
93
  if (count < 2) {
78
94
  continue; // Need at least 2 data points for variance
@@ -102,7 +118,8 @@ class CrowdSharpeRatioProxy {
102
118
  // Sharpe = Mean(Return) / StdDev(Return)
103
119
  const sharpeProxy = mean / stdDev;
104
120
 
105
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
121
+ // --- FIX: Data is already keyed by ticker, no mapping needed ---
122
+ // const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
106
123
 
107
124
  result[ticker] = {
108
125
  sharpe_ratio_proxy: sharpeProxy,
@@ -115,10 +132,6 @@ class CrowdSharpeRatioProxy {
115
132
 
116
133
  return result;
117
134
  }
118
-
119
- reset() {
120
- this.mappings = null;
121
- }
122
135
  }
123
136
 
124
137
  module.exports = CrowdSharpeRatioProxy;