aiden-shared-calculations-unified 1.0.64 → 1.0.66

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,218 +1,238 @@
1
1
  /**
2
- * @fileoverview Calculates "Net Crowd Flow" and "Sector Rotation"
3
- * *only* for the "Smart Cohort" (Top 20% of Investor Scores).
2
+ * @fileoverview Calculation (Pass 3) for smart 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
+ * "Smart Cohort" (top 20% of investors).
6
+ *
7
+ * This calculation *depends* on 'user_profitability_tracker'
8
+ * to identify the cohort.
9
9
  */
10
-
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.8; // Top 20%
19
- const PROFILE_CALC_ID = 'user-investment-profile'; // The calc to read IS scores from
10
+ const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
20
11
 
21
12
  class SmartCohortFlow {
13
+ constructor() {
14
+ this.assetData = new Map();
15
+ this.sectorData = new Map();
16
+ this.mappings = null;
17
+ this.smartCohortUserIds = 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 [PROFILE_CALC_ID];
28
- }
29
-
30
- constructor() {
31
- // Meta-calc, no constructor state needed
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 'Smart Cohort' (top 20% users), aggregated by asset and sector.",
38
+ "properties": {
39
+ "cohort_size": {
40
+ "type": "number",
41
+ "description": "The number of users identified as being in the Smart 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', '[SmartCohortFlow] Loading Investor Scores from fetched dependency...');
39
-
40
- const profileData = fetchedDependencies[PROFILE_CALC_ID];
41
-
42
- if (!profileData || !profileData.daily_investor_scores || Object.keys(profileData.daily_investor_scores).length === 0) {
43
- logger.log('WARN', `[SmartCohortFlow] Cannot find dependency in fetched data: ${PROFILE_CALC_ID}. Cohort will not be built.`);
44
- return null; // Return null to signal failure
45
- }
46
-
47
- const scores = profileData.daily_investor_scores;
48
- const allScores = Object.entries(scores).map(([userId, score]) => ({ userId, score }));
49
- allScores.sort((a, b) => a.score - b.score);
50
-
51
- const thresholdIndex = Math.floor(allScores.length * COHORT_PERCENTILE);
52
- const thresholdScore = allScores[thresholdIndex]?.score || 999;
53
-
54
- const smartCohortIds = new Set(
55
- allScores.filter(s => s.score >= thresholdScore).map(s => s.userId)
56
- );
57
-
58
- logger.log('INFO', `[SmartCohortFlow] Cohort built. ${smartCohortIds.size} users at or above ${thresholdScore.toFixed(2)} (80th percentile).`);
59
- return smartCohortIds;
63
+ static getDependencies() {
64
+ return ['user_profitability_tracker'];
60
65
  }
61
66
 
62
- // --- Asset Flow Helpers (unchanged) ---
63
- _initAsset(asset_values, instrumentId) {
64
- if (!asset_values[instrumentId]) {
65
- asset_values[instrumentId] = { day1_value_sum: 0, day2_value_sum: 0 };
66
- }
67
+ _getPortfolioPositions(portfolio) {
68
+ return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
67
69
  }
68
- _sumAssetValue(positions) {
69
- const valueMap = {};
70
- if (!positions || !Array.isArray(positions)) return valueMap;
71
- for (const pos of positions) {
72
- if (pos && pos.InstrumentID && pos.Value) {
73
- valueMap[pos.InstrumentID] = (valueMap[pos.InstrumentID] || 0) + pos.Value;
74
- }
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
+ });
75
78
  }
76
- return valueMap;
77
79
  }
78
- _accumulateSectorInvestment(portfolio, target, sectorMap) {
79
- if (portfolio && portfolio.AggregatedPositions) {
80
- for (const pos of portfolio.AggregatedPositions) {
81
- const sector = sectorMap[pos.InstrumentID] || 'N/A';
82
- target[sector] = (target[sector] || 0) + (pos.Invested || pos.Amount || 0);
83
- }
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
+ });
84
88
  }
85
89
  }
86
90
 
87
91
  /**
88
- * REFACTORED PROCESS METHOD
89
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
90
- * @param {object} dependencies The shared dependencies (db, logger, rootData, etc.).
91
- * @param {object} config The computation system configuration.
92
- * @param {object} fetchedDependencies In-memory results from previous passes.
93
- * @returns {Promise<object|null>} The analysis result or null.
92
+ * Helper to get the cohort IDs from the dependency.
94
93
  */
95
- async process(dateStr, dependencies, config, fetchedDependencies) {
96
- const { logger, db, rootData, calculationUtils } = dependencies;
97
- const { portfolioRefs } = rootData;
98
- logger.log('INFO', '[SmartCohortFlow] Starting meta-process...');
94
+ _getSmartCohort(fetchedDependencies) {
95
+ if (this.smartCohortUserIds) {
96
+ return this.smartCohortUserIds;
97
+ }
99
98
 
100
- // 1. Load Cohort from in-memory dependency
101
- const smartCohortIds = this._loadCohort(logger, fetchedDependencies);
102
- if (!smartCohortIds) {
103
- return null; // Dependency failed
99
+ const profitabilityData = fetchedDependencies['user_profitability_tracker'];
100
+ if (!profitabilityData || !profitabilityData.ranked_users) {
101
+ return new Set();
104
102
  }
105
103
 
106
- // 2. Load external dependencies (prices, sectors)
107
- const [priceMap, mappings, sectorMap] = await Promise.all([
108
- loadAllPriceData(),
109
- loadInstrumentMappings(),
110
- getInstrumentSectorMap()
111
- ]);
112
- if (!priceMap || !mappings || !sectorMap || Object.keys(priceMap).length === 0) {
113
- logger.log('ERROR', '[SmartCohortFlow] Failed to load critical price/mapping/sector data. Aborting.');
114
- return null;
115
- }
104
+ const rankedUsers = profitabilityData.ranked_users;
105
+ const cohortSize = Math.floor(rankedUsers.length * 0.20);
106
+
107
+ // The 'ranked_users' are sorted ascending by P&L, so top 20% is the *last* 20%.
108
+ this.smartCohortUserIds = new Set(rankedUsers.slice(-cohortSize).map(u => u.userId));
109
+ return this.smartCohortUserIds;
110
+ }
116
111
 
117
- // 3. Load "yesterday's" portfolio data for comparison
118
- const yesterdayDate = new Date(dateStr + 'T00:00:00Z');
119
- yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
120
- const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
121
- const yesterdayRefs = await getPortfolioPartRefs(config, dependencies, yesterdayStr);
122
- const yesterdayPortfolios = await loadFullDayMap(config, dependencies, yesterdayRefs);
123
- logger.log('INFO', `[SmartCohortFlow] Loaded ${yesterdayRefs.length} part refs for yesterday.`);
124
-
125
- // 4. Stream "today's" portfolio data and process
126
- const asset_values = {};
127
- const todaySectorInvestment = {};
128
- const yesterdaySectorInvestment = {};
129
- let user_count = 0;
130
-
131
- const batchSize = config.partRefBatchSize || 10;
132
- for (let i = 0; i < portfolioRefs.length; i += batchSize) {
133
- const batchRefs = portfolioRefs.slice(i, i + batchSize);
134
- const todayPortfoliosChunk = await loadDataByRefs(config, dependencies, batchRefs);
135
-
136
- for (const uid in todayPortfoliosChunk) {
137
-
138
- if (!smartCohortIds.has(uid)) continue; // --- Filter user ---
139
-
140
- const pToday = todayPortfoliosChunk[uid];
141
- const pYesterday = yesterdayPortfolios[uid];
142
-
143
- if (!pToday || !pYesterday || !pToday.AggregatedPositions || !pYesterday.AggregatedPositions) {
144
- continue;
145
- }
146
-
147
- // 4a. RUN ASSET FLOW LOGIC
148
- const yesterdayValues = this._sumAssetValue(pYesterday.AggregatedPositions);
149
- const todayValues = this._sumAssetValue(pToday.AggregatedPositions);
150
- const allInstrumentIds = new Set([...Object.keys(yesterdayValues), ...Object.keys(todayValues)]);
151
-
152
- for (const instrumentId of allInstrumentIds) {
153
- this._initAsset(asset_values, instrumentId);
154
- asset_values[instrumentId].day1_value_sum += (yesterdayValues[instrumentId] || 0);
155
- asset_values[instrumentId].day2_value_sum += (todayValues[instrumentId] || 0);
156
- }
112
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
113
+ const smartCohort = this._getSmartCohort(fetchedDependencies);
114
+
115
+ // This user is not in the "smart cohort", skip.
116
+ if (!smartCohort.has(userId)) {
117
+ return;
118
+ }
157
119
 
158
- // 4b. RUN SECTOR ROTATION LOGIC
159
- this._accumulateSectorInvestment(pToday, todaySectorInvestment, sectorMap);
160
- this._accumulateSectorInvestment(pYesterday, yesterdaySectorInvestment, sectorMap);
161
- user_count++;
162
- }
120
+ if (!todayPortfolio || !yesterdayPortfolio) {
121
+ return;
163
122
  }
123
+
124
+ const yPos = this._getPortfolioPositions(yesterdayPortfolio);
125
+ const tPos = this._getPortfolioPositions(todayPortfolio);
126
+
127
+ const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
128
+ const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
129
+
130
+ const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
164
131
 
165
- logger.log('INFO', `[SmartCohortFlow] Processed ${user_count} users in cohort.`);
166
-
167
- // --- 5. GETRESULT LOGIC ---
168
- if (user_count === 0) {
169
- logger.warn('[SmartCohortFlow] No users processed for smart cohort. Returning null.');
170
- return null;
132
+ if (!this.mappings) {
133
+ // Context contains the mappings loaded in Pass 1
134
+ this.mappings = context.mappings;
171
135
  }
172
136
 
173
- // 5a. Calculate Asset Flow
174
- const finalAssetFlow = {};
175
- for (const instrumentId in asset_values) {
176
- const ticker = mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
177
- const avg_day1_value = asset_values[instrumentId].day1_value_sum / user_count;
178
- const avg_day2_value = asset_values[instrumentId].day2_value_sum / user_count;
179
- const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, dateStr, priceMap);
137
+ for (const instrumentId of allInstrumentIds) {
138
+ if (!instrumentId) continue;
139
+
140
+ this._initAsset(instrumentId);
141
+ const asset = this.assetData.get(instrumentId);
180
142
 
181
- if (priceChangePct === null) continue;
143
+ const yP = yPosMap.get(instrumentId);
144
+ const tP = tPosMap.get(instrumentId);
182
145
 
183
- const expected_day2_value = avg_day1_value * (1 + priceChangePct);
184
- const net_crowd_flow_pct = avg_day2_value - expected_day2_value;
146
+ const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
147
+ const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
148
+
149
+ // Get sector and initialize it
150
+ const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
151
+ this._initSector(sector);
152
+ const sectorAsset = this.sectorData.get(sector);
185
153
 
186
- finalAssetFlow[ticker] = {
187
- net_crowd_flow_pct: net_crowd_flow_pct,
188
- avg_value_day1_pct: avg_day1_value,
189
- avg_value_day2_pct: avg_day2_value
190
- };
154
+ if (yInvested > 0) {
155
+ const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
156
+
157
+ asset.total_invested_yesterday += yInvested;
158
+ asset.price_change_yesterday += yPriceChange * yInvested;
159
+
160
+ sectorAsset.total_invested_yesterday += yInvested;
161
+ sectorAsset.price_change_yesterday += yPriceChange * yInvested;
162
+ }
163
+ if (tInvested > 0) {
164
+ asset.total_invested_today += tInvested;
165
+ sectorAsset.total_invested_today += tInvested;
166
+ }
191
167
  }
192
-
193
- // 5b. Calculate Sector Rotation
194
- const finalSectorRotation = {};
195
- const allSectors = new Set([...Object.keys(todaySectorInvestment), ...Object.keys(yesterdaySectorInvestment)]);
196
- for (const sector of allSectors) {
197
- const todayAmount = todaySectorInvestment[sector] || 0;
198
- const yesterdayAmount = yesterdaySectorInvestment[sector] || 0;
199
- finalSectorRotation[sector] = todayAmount - yesterdayAmount;
168
+ }
169
+
170
+ _calculateFlow(dataMap) {
171
+ const result = {};
172
+ for (const [key, data] of dataMap.entries()) {
173
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
174
+
175
+ if (total_invested_yesterday > 0) {
176
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
177
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
178
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
179
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
180
+
181
+ result[key] = {
182
+ net_flow_percentage: net_flow_percentage,
183
+ total_invested_today: total_invested_today,
184
+ total_invested_yesterday: total_invested_yesterday
185
+ };
186
+ }
200
187
  }
188
+ return result;
189
+ }
201
190
 
202
- if (Object.keys(finalAssetFlow).length === 0) {
203
- logger.warn('[SmartCohortFlow] No asset flow calculated (likely all price data missing). Returning null.');
204
- return null;
191
+ async getResult(fetchedDependencies) {
192
+ // Ensure mappings are loaded (can be from context or loaded now)
193
+ if (!this.mappings) {
194
+ this.mappings = await loadInstrumentMappings();
195
+ }
196
+
197
+ // Ensure cohort is calculated at least once
198
+ const smartCohort = this._getSmartCohort(fetchedDependencies);
199
+
200
+ // 1. Calculate Asset Flow
201
+ const assetResult = {};
202
+ for (const [instrumentId, data] of this.assetData.entries()) {
203
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
204
+ const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
205
+
206
+ if (total_invested_yesterday > 0) {
207
+ const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
208
+ const price_contribution = total_invested_yesterday * avg_price_change_pct;
209
+ const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
210
+ const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
211
+
212
+ assetResult[ticker] = {
213
+ net_flow_percentage: net_flow_percentage,
214
+ total_invested_today: total_invested_today,
215
+ total_invested_yesterday: total_invested_yesterday
216
+ };
217
+ }
205
218
  }
219
+
220
+ // 2. Calculate Sector Flow
221
+ const sectorResult = this._calculateFlow(this.sectorData);
206
222
 
207
223
  return {
208
- asset_flow: finalAssetFlow,
209
- sector_rotation: finalSectorRotation,
210
- user_sample_size: user_count
224
+ cohort_size: smartCohort.size,
225
+ assets: assetResult,
226
+ sectors: sectorResult
211
227
  };
212
228
  }
213
229
 
214
- async getResult() { return null; }
215
- reset() { }
230
+ reset() {
231
+ this.assetData.clear();
232
+ this.sectorData.clear();
233
+ this.mappings = null;
234
+ this.smartCohortUserIds = null;
235
+ }
216
236
  }
217
237
 
218
238
  module.exports = SmartCohortFlow;