aiden-shared-calculations-unified 1.0.64 → 1.0.65

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 (89) hide show
  1. package/README.MD +1 -1
  2. package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
  3. package/calculations/activity/historical/daily_asset_activity.js +42 -0
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
  6. package/calculations/asset_metrics/asset_position_size.js +36 -0
  7. package/calculations/backtests/strategy-performance.js +41 -0
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
  9. package/calculations/behavioural/historical/drawdown_response.js +113 -35
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
  11. package/calculations/behavioural/historical/gain_response.js +113 -34
  12. package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
  13. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
  14. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
  15. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
  16. package/calculations/behavioural/historical/position_count_pnl.js +91 -39
  17. package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
  18. package/calculations/behavioural/historical/smart_money_flow.js +160 -151
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
  22. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
  24. package/calculations/insights/daily_total_positions_held.js +28 -24
  25. package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
  26. package/calculations/insights/historical/daily_ownership_delta.js +95 -32
  27. package/calculations/meta/capital_deployment_strategy.js +78 -110
  28. package/calculations/meta/capital_liquidation_performance.js +114 -111
  29. package/calculations/meta/cash-flow-deployment.js +114 -107
  30. package/calculations/meta/cash-flow-liquidation.js +114 -107
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
  32. package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
  33. package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
  34. package/calculations/meta/profit_cohort_divergence.js +83 -59
  35. package/calculations/meta/shark_attack_signal.js +91 -39
  36. package/calculations/meta/smart-dumb-divergence-index.js +114 -98
  37. package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
  38. package/calculations/meta/social-predictive-regime-state.js +76 -155
  39. package/calculations/meta/social-topic-driver-index.js +74 -127
  40. package/calculations/meta/user_expectancy_score.js +83 -31
  41. package/calculations/pnl/asset_pnl_status.js +120 -31
  42. package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
  43. package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
  44. package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
  45. package/calculations/pnl/average_daily_position_pnl.js +49 -21
  46. package/calculations/pnl/historical/profitability_migration.js +81 -35
  47. package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
  48. package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
  49. package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
  50. package/calculations/pnl/profitability_skew_per_stock.js +86 -31
  51. package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
  52. package/calculations/sanity/users_processed.js +24 -1
  53. package/calculations/sectors/historical/diversification_pnl.js +104 -42
  54. package/calculations/sectors/historical/sector_rotation.js +94 -45
  55. package/calculations/sectors/total_long_per_sector.js +55 -20
  56. package/calculations/sectors/total_short_per_sector.js +55 -20
  57. package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
  58. package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
  59. package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
  60. package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
  61. package/calculations/short_and_long_stats/total_long_figures.js +34 -13
  62. package/calculations/short_and_long_stats/total_short_figures.js +34 -14
  63. package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
  64. package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
  65. package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
  66. package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
  67. package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
  68. package/calculations/socialPosts/social_activity_aggregation.js +106 -77
  69. package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
  70. package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
  71. package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
  72. package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
  73. package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
  74. package/calculations/speculators/historical/risk_appetite_change.js +89 -32
  75. package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
  76. package/calculations/speculators/holding_duration_per_asset.js +83 -23
  77. package/calculations/speculators/leverage_per_asset.js +68 -19
  78. package/calculations/speculators/leverage_per_sector.js +86 -25
  79. package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
  80. package/calculations/speculators/speculator_asset_sentiment.js +100 -48
  81. package/calculations/speculators/speculator_danger_zone.js +101 -33
  82. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
  83. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
  84. package/calculations/speculators/stop_loss_per_asset.js +94 -26
  85. package/calculations/speculators/take_profit_per_asset.js +95 -27
  86. package/calculations/speculators/tsl_per_asset.js +77 -23
  87. package/package.json +1 -1
  88. package/utils/price_data_provider.js +142 -142
  89. package/utils/sector_mapping_provider.js +74 -74
@@ -1,224 +1,232 @@
1
1
  /**
2
- * @fileoverview Calculates "Net Crowd Flow" and "Sector Rotation"
3
- * *only* for the "Dumb Cohort" (Bottom 20% of Investor Scores).
2
+ * @fileoverview Calculation (Pass 4) for negative expectancy cohort flow.
4
3
  *
5
- * --- META REFACTOR (v2) ---
6
- * This calculation is `type: "meta"` and expects its dependencies
7
- * (the user-investment-profile results) to be fetched by the pass runner.
8
- * It then streams root portfolio data.
4
+ * This metric calculates the "Net Crowd Flow Percentage" for the
5
+ * "Negative Expectancy Cohort" (users with a low expectancy score).
6
+ *
7
+ * This calculation *depends* on 'user_expectancy_score'
8
+ * to identify the cohort.
9
9
  */
10
+ const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
10
11
 
11
- const { Firestore } = require('@google-cloud/firestore');
12
- const firestore = new Firestore();
13
- const { loadAllPriceData, getDailyPriceChange } = require('../../utils/price_data_provider');
14
- const { loadInstrumentMappings, getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
15
- // NOTE: Corrected relative path for data_loader
16
- const { loadDataByRefs, getPortfolioPartRefs, loadFullDayMap } = require('../../../bulltrackers-module/functions/computation-system/utils/data_loader');
17
-
18
- const COHORT_PERCENTILE = 0.2; // Bottom 20%
19
- const PROFILE_CALC_ID = 'user-investment-profile'; // The calc to read IS scores from
20
-
21
- class DumbCohortFlow {
12
+ class NegativeExpectancyCohortFlow {
13
+ constructor() {
14
+ this.assetData = new Map();
15
+ this.sectorData = new Map();
16
+ this.mappings = null;
17
+ this.negExpCohortUserIds = null;
18
+ }
22
19
 
23
20
  /**
24
- * (NEW) Statically declare dependencies.
21
+ * Defines the output schema for this calculation.
22
+ * @returns {object} JSON Schema object
25
23
  */
26
- static getDependencies() {
27
- return ['user_expectancy_score'];
28
- }
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
+ };
29
34
 
30
- constructor() {
31
- // Meta-calc, no constructor state needed
35
+ return {
36
+ "type": "object",
37
+ "description": "Calculates net capital flow % (price-adjusted) for the 'Negative Expectancy' cohort (score < 0.2), aggregated by asset and sector.",
38
+ "properties": {
39
+ "cohort_size": {
40
+ "type": "number",
41
+ "description": "The number of users identified as being in the Negative Expectancy 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
+ };
32
58
  }
33
59
 
34
60
  /**
35
- * Loads the Investor Scores from the fetched dependency.
61
+ * Statically declare dependencies.
36
62
  */
37
- _loadCohort(logger, fetchedDependencies) {
38
- logger.log('INFO', '[NegativeExpectancyCohortFlow] Loading Expectancy Scores from fetched dependency...');
39
-
40
- const profileData = fetchedDependencies['user-expectancy-score'];
41
-
42
- if (!profileData || Object.keys(profileData).length === 0) {
43
- logger.log('WARN', `[NegativeExpectancyCohortFlow] Cannot find dependency 'user-expectancy-score'. Cohort will not be built.`);
44
- return null;
63
+ static getDependencies() {
64
+ return ['user_expectancy_score']; // Pass 3
45
65
  }
46
66
 
47
- const allScores = Object.entries(profileData).map(([userId, data]) => ({
48
- userId,
49
- score: data.expectancy_score
50
- }));
51
-
52
- allScores.sort((a, b) => a.score - b.score);
53
-
54
- // Find the 20th percentile (Bottom 20%)
55
- const thresholdIndex = Math.floor(allScores.length * 0.20);
56
- const thresholdScore = allScores[thresholdIndex]?.score || 0;
57
-
58
- // Filter for users with a *negative expectancy score* AND are in the bottom 20%
59
- const cohortIds = new Set(
60
- allScores.filter(s => s.score <= thresholdScore && s.score < 0)
61
- .map(s => s.userId)
62
- );
63
-
64
- logger.log('INFO', `[NegativeExpectancyCohortFlow] Cohort built. ${cohortIds.size} users at or below ${thresholdScore.toFixed(2)} (20th percentile) and < 0.`);
65
- return cohortIds;
67
+ _getPortfolioPositions(portfolio) {
68
+ return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
66
69
  }
67
70
 
68
- // --- Asset Flow Helpers (unchanged) ---
69
- _initAsset(asset_values, instrumentId) {
70
- if (!asset_values[instrumentId]) {
71
- asset_values[instrumentId] = { day1_value_sum: 0, day2_value_sum: 0 };
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
+ });
72
78
  }
73
79
  }
74
- _sumAssetValue(positions) {
75
- const valueMap = {};
76
- if (!positions || !Array.isArray(positions)) return valueMap;
77
- for (const pos of positions) {
78
- if (pos && pos.InstrumentID && pos.Value) {
79
- valueMap[pos.InstrumentID] = (valueMap[pos.InstrumentID] || 0) + pos.Value;
80
- }
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
+ });
81
88
  }
82
- return valueMap;
83
89
  }
84
- _accumulateSectorInvestment(portfolio, target, sectorMap) {
85
- if (portfolio && portfolio.AggregatedPositions) {
86
- for (const pos of portfolio.AggregatedPositions) {
87
- const sector = sectorMap[pos.InstrumentID] || 'N/A';
88
- target[sector] = (target[sector] || 0) + (pos.Invested || pos.Amount || 0);
90
+
91
+ _getNegExpCohort(fetchedDependencies) {
92
+ if (this.negExpCohortUserIds) {
93
+ return this.negExpCohortUserIds;
94
+ }
95
+
96
+ const expectancyData = fetchedDependencies['user_expectancy_score'];
97
+ if (!expectancyData) {
98
+ return new Set();
99
+ }
100
+
101
+ this.negExpCohortUserIds = new Set();
102
+ for (const [userId, data] of Object.entries(expectancyData)) {
103
+ // Definition: Expectancy score < 0.2
104
+ if (data.expectancy_score < 0.2) {
105
+ this.negExpCohortUserIds.add(userId);
89
106
  }
90
107
  }
108
+ return this.negExpCohortUserIds;
91
109
  }
92
110
 
93
- /**
94
- * REFACTORED PROCESS METHOD
95
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
96
- * @param {object} dependencies The shared dependencies (db, logger, rootData, etc.).
97
- * @param {object} config The computation system configuration.
98
- * @param {object} fetchedDependencies In-memory results from previous passes.
99
- * @returns {Promise<object|null>} The analysis result or null.
100
- */
101
- async process(dateStr, dependencies, config, fetchedDependencies) {
102
- const { logger, db, rootData, calculationUtils } = dependencies;
103
- const { portfolioRefs } = rootData;
104
- logger.log('INFO', '[DumbCohortFlow] Starting meta-process...');
111
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
112
+ const cohort = this._getNegExpCohort(fetchedDependencies);
105
113
 
106
- // 1. Load Cohort from in-memory dependency
107
- const dumbCohortIds = this._loadCohort(logger, fetchedDependencies);
108
- if (!dumbCohortIds) {
109
- return null; // Dependency failed
114
+ if (!cohort.has(userId)) {
115
+ return;
110
116
  }
111
117
 
112
- // 2. Load external dependencies (prices, sectors)
113
- const [priceMap, mappings, sectorMap] = await Promise.all([
114
- loadAllPriceData(),
115
- loadInstrumentMappings(),
116
- getInstrumentSectorMap()
117
- ]);
118
- if (!priceMap || !mappings || !sectorMap || Object.keys(priceMap).length === 0) {
119
- logger.log('ERROR', '[DumbCohortFlow] Failed to load critical price/mapping/sector data. Aborting.');
120
- return null;
118
+ if (!todayPortfolio || !yesterdayPortfolio) {
119
+ return;
121
120
  }
122
121
 
123
- // 3. Load "yesterday's" portfolio data for comparison
124
- const yesterdayDate = new Date(dateStr + 'T00:00:00Z');
125
- yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
126
- const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
127
- const yesterdayRefs = await getPortfolioPartRefs(config, dependencies, yesterdayStr);
128
- const yesterdayPortfolios = await loadFullDayMap(config, dependencies, yesterdayRefs);
129
- logger.log('INFO', `[DumbCohortFlow] Loaded ${yesterdayRefs.length} part refs for yesterday.`);
130
-
131
- // 4. Stream "today's" portfolio data and process
132
- const asset_values = {};
133
- const todaySectorInvestment = {};
134
- const yesterdaySectorInvestment = {};
135
- let user_count = 0;
136
-
137
- const batchSize = config.partRefBatchSize || 10;
138
- for (let i = 0; i < portfolioRefs.length; i += batchSize) {
139
- const batchRefs = portfolioRefs.slice(i, i + batchSize);
140
- const todayPortfoliosChunk = await loadDataByRefs(config, dependencies, batchRefs);
141
-
142
- for (const uid in todayPortfoliosChunk) {
143
-
144
- if (!dumbCohortIds.has(uid)) continue; // --- Filter user ---
145
-
146
- const pToday = todayPortfoliosChunk[uid];
147
- const pYesterday = yesterdayPortfolios[uid];
148
-
149
- if (!pToday || !pYesterday || !pToday.AggregatedPositions || !pYesterday.AggregatedPositions) {
150
- continue;
151
- }
152
-
153
- // 4a. RUN ASSET FLOW LOGIC
154
- const yesterdayValues = this._sumAssetValue(pYesterday.AggregatedPositions);
155
- const todayValues = this._sumAssetValue(pToday.AggregatedPositions);
156
- const allInstrumentIds = new Set([...Object.keys(yesterdayValues), ...Object.keys(todayValues)]);
157
-
158
- for (const instrumentId of allInstrumentIds) {
159
- this._initAsset(asset_values, instrumentId);
160
- asset_values[instrumentId].day1_value_sum += (yesterdayValues[instrumentId] || 0);
161
- asset_values[instrumentId].day2_value_sum += (todayValues[instrumentId] || 0);
162
- }
122
+ const yPos = this._getPortfolioPositions(yesterdayPortfolio);
123
+ const tPos = this._getPortfolioPositions(todayPortfolio);
163
124
 
164
- // 4b. RUN SECTOR ROTATION LOGIC
165
- this._accumulateSectorInvestment(pToday, todaySectorInvestment, sectorMap);
166
- this._accumulateSectorInvestment(pYesterday, yesterdaySectorInvestment, sectorMap);
167
- user_count++;
168
- }
169
- }
170
-
171
- logger.log('INFO', `[DumbCohortFlow] Processed ${user_count} users in cohort.`);
125
+ const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
126
+ const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
127
+
128
+ const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
172
129
 
173
- // --- 5. GETRESULT LOGIC ---
174
- if (user_count === 0) {
175
- logger.warn('[DumbCohortFlow] No users processed for dumb cohort. Returning null.');
176
- return null;
130
+ if (!this.mappings) {
131
+ this.mappings = context.mappings;
177
132
  }
178
133
 
179
- // 5a. Calculate Asset Flow
180
- const finalAssetFlow = {};
181
- for (const instrumentId in asset_values) {
182
- const ticker = mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
183
- const avg_day1_value = asset_values[instrumentId].day1_value_sum / user_count;
184
- const avg_day2_value = asset_values[instrumentId].day2_value_sum / user_count;
185
- const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, dateStr, priceMap);
134
+ for (const instrumentId of allInstrumentIds) {
135
+ if (!instrumentId) continue;
136
+
137
+ this._initAsset(instrumentId);
138
+ const asset = this.assetData.get(instrumentId);
186
139
 
187
- if (priceChangePct === null) continue;
140
+ const yP = yPosMap.get(instrumentId);
141
+ const tP = tPosMap.get(instrumentId);
188
142
 
189
- const expected_day2_value = avg_day1_value * (1 + priceChangePct);
190
- const net_crowd_flow_pct = avg_day2_value - expected_day2_value;
143
+ const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
144
+ const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
145
+
146
+ const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
147
+ this._initSector(sector);
148
+ const sectorAsset = this.sectorData.get(sector);
191
149
 
192
- finalAssetFlow[ticker] = {
193
- net_crowd_flow_pct: net_crowd_flow_pct,
194
- avg_value_day1_pct: avg_day1_value,
195
- avg_value_day2_pct: avg_day2_value
196
- };
150
+ if (yInvested > 0) {
151
+ const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
152
+
153
+ asset.total_invested_yesterday += yInvested;
154
+ asset.price_change_yesterday += yPriceChange * yInvested;
155
+
156
+ sectorAsset.total_invested_yesterday += yInvested;
157
+ sectorAsset.price_change_yesterday += yPriceChange * yInvested;
158
+ }
159
+ if (tInvested > 0) {
160
+ asset.total_invested_today += tInvested;
161
+ sectorAsset.total_invested_today += tInvested;
162
+ }
197
163
  }
198
-
199
- // 5b. Calculate Sector Rotation
200
- const finalSectorRotation = {};
201
- const allSectors = new Set([...Object.keys(todaySectorInvestment), ...Object.keys(yesterdaySectorInvestment)]);
202
- for (const sector of allSectors) {
203
- const todayAmount = todaySectorInvestment[sector] || 0;
204
- const yesterdayAmount = yesterdaySectorInvestment[sector] || 0;
205
- finalSectorRotation[sector] = todayAmount - yesterdayAmount;
164
+ }
165
+
166
+ _calculateFlow(dataMap) {
167
+ const result = {};
168
+ for (const [key, data] of dataMap.entries()) {
169
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
170
+
171
+ if (total_invested_yesterday > 0) {
172
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
173
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
174
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
175
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
176
+
177
+ result[key] = {
178
+ net_flow_percentage: net_flow_percentage,
179
+ total_invested_today: total_invested_today,
180
+ total_invested_yesterday: total_invested_yesterday
181
+ };
182
+ }
206
183
  }
184
+ return result;
185
+ }
207
186
 
208
- if (Object.keys(finalAssetFlow).length === 0) {
209
- logger.warn('[DumbCohortFlow] No asset flow calculated (likely all price data missing). Returning null.');
210
- return null;
187
+ async getResult(fetchedDependencies) {
188
+ if (!this.mappings) {
189
+ this.mappings = await loadInstrumentMappings();
211
190
  }
191
+
192
+ const cohort = this._getNegExpCohort(fetchedDependencies);
193
+
194
+ // 1. Calculate Asset Flow
195
+ const assetResult = {};
196
+ for (const [instrumentId, data] of this.assetData.entries()) {
197
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
198
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
199
+
200
+ if (total_invested_yesterday > 0) {
201
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
202
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
203
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
204
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
205
+
206
+ assetResult[ticker] = {
207
+ net_flow_percentage: net_flow_percentage,
208
+ total_invested_today: total_invested_today,
209
+ total_invested_yesterday: total_invested_yesterday
210
+ };
211
+ }
212
+ }
213
+
214
+ // 2. Calculate Sector Flow
215
+ const sectorResult = this._calculateFlow(this.sectorData);
212
216
 
213
217
  return {
214
- asset_flow: finalAssetFlow,
215
- sector_rotation: finalSectorRotation,
216
- user_sample_size: user_count
218
+ cohort_size: cohort.size,
219
+ assets: assetResult,
220
+ sectors: sectorResult
217
221
  };
218
222
  }
219
223
 
220
- async getResult() { return null; }
221
- reset() { }
224
+ reset() {
225
+ this.assetData.clear();
226
+ this.sectorData.clear();
227
+ this.mappings = null;
228
+ this.negExpCohortUserIds = null;
229
+ }
222
230
  }
223
231
 
224
- module.exports = DumbCohortFlow;
232
+ module.exports = NegativeExpectancyCohortFlow;