aiden-shared-calculations-unified 1.0.95 → 1.0.96

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 (54) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +1 -2
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +1 -2
  3. package/calculations/ghost-book/cost-basis-density.js +1 -2
  4. package/calculations/ghost-book/liquidity-vacuum.js +1 -2
  5. package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
  6. package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
  7. package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
  8. package/calculations/predicative-alpha/mimetic-latency.js +1 -2
  9. package/package.json +1 -1
  10. package/calculations/legacy/activity_by_pnl_status.js +0 -119
  11. package/calculations/legacy/asset_crowd_flow.js +0 -163
  12. package/calculations/legacy/capital_deployment_strategy.js +0 -108
  13. package/calculations/legacy/capital_liquidation_performance.js +0 -139
  14. package/calculations/legacy/capital_vintage_performance.js +0 -136
  15. package/calculations/legacy/cash-flow-deployment.js +0 -144
  16. package/calculations/legacy/cash-flow-liquidation.js +0 -146
  17. package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
  18. package/calculations/legacy/crowd_conviction_score.js +0 -261
  19. package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
  20. package/calculations/legacy/daily_asset_activity.js +0 -128
  21. package/calculations/legacy/daily_user_activity_tracker.js +0 -182
  22. package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
  23. package/calculations/legacy/diversification_pnl.js +0 -115
  24. package/calculations/legacy/drawdown_response.js +0 -137
  25. package/calculations/legacy/dumb-cohort-flow.js +0 -238
  26. package/calculations/legacy/gain_response.js +0 -137
  27. package/calculations/legacy/historical_performance_aggregator.js +0 -85
  28. package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
  29. package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
  30. package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
  31. package/calculations/legacy/new_allocation_percentage.js +0 -98
  32. package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
  33. package/calculations/legacy/position_count_pnl.js +0 -120
  34. package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
  35. package/calculations/legacy/profit_cohort_divergence.js +0 -115
  36. package/calculations/legacy/profitability_migration.js +0 -104
  37. package/calculations/legacy/reallocation_increase_percentage.js +0 -104
  38. package/calculations/legacy/risk_appetite_change.js +0 -97
  39. package/calculations/legacy/sector_rotation.js +0 -117
  40. package/calculations/legacy/shark_attack_signal.js +0 -112
  41. package/calculations/legacy/smart-cohort-flow.js +0 -238
  42. package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
  43. package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
  44. package/calculations/legacy/smart_money_flow.js +0 -198
  45. package/calculations/legacy/social-predictive-regime-state.js +0 -102
  46. package/calculations/legacy/social-topic-driver-index.js +0 -147
  47. package/calculations/legacy/social-topic-predictive-potential.js +0 -461
  48. package/calculations/legacy/social_flow_correlation.js +0 -112
  49. package/calculations/legacy/speculator_adjustment_activity.js +0 -103
  50. package/calculations/legacy/strategy-performance.js +0 -265
  51. package/calculations/legacy/tsl_effectiveness.js +0 -85
  52. package/calculations/legacy/user-investment-profile.js +0 -313
  53. package/calculations/legacy/user_expectancy_score.js +0 -106
  54. package/calculations/legacy/user_profitability_tracker.js +0 -131
@@ -1,238 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 3) for dumb cohort flow.
3
- *
4
- * This metric calculates the "Net Crowd Flow Percentage" for the
5
- * "Dumb Cohort" (bottom 20% of investors).
6
- *
7
- * This calculation *depends* on 'user_profitability_tracker'
8
- * to identify the cohort.
9
- */
10
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
-
12
- class DumbCohortFlow {
13
- constructor() {
14
- this.assetData = new Map();
15
- this.sectorData = new Map();
16
- this.mappings = null;
17
- this.dumbCohortUserIds = 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 'Dumb Cohort' (bottom 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 Dumb 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 ['user_profitability_tracker'];
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
- /**
92
- * Helper to get the cohort IDs from the dependency.
93
- */
94
- _getDumbCohort(fetchedDependencies) {
95
- if (this.dumbCohortUserIds) {
96
- return this.dumbCohortUserIds;
97
- }
98
-
99
- const profitabilityData = fetchedDependencies['user_profitability_tracker'];
100
- if (!profitabilityData || !profitabilityData.ranked_users) {
101
- return new Set();
102
- }
103
-
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 bottom 20% is the first 20%.
108
- this.dumbCohortUserIds = new Set(rankedUsers.slice(0, cohortSize).map(u => u.userId));
109
- return this.dumbCohortUserIds;
110
- }
111
-
112
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
113
- const dumbCohort = this._getDumbCohort(fetchedDependencies);
114
-
115
- // This user is not in the "dumb cohort", skip.
116
- if (!dumbCohort.has(userId)) {
117
- return;
118
- }
119
-
120
- if (!todayPortfolio || !yesterdayPortfolio) {
121
- return;
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()]);
131
-
132
- if (!this.mappings) {
133
- // Context contains the mappings loaded in Pass 1
134
- this.mappings = context.mappings;
135
- }
136
-
137
- for (const instrumentId of allInstrumentIds) {
138
- if (!instrumentId) continue;
139
-
140
- this._initAsset(instrumentId);
141
- const asset = this.assetData.get(instrumentId);
142
-
143
- const yP = yPosMap.get(instrumentId);
144
- const tP = tPosMap.get(instrumentId);
145
-
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);
153
-
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
- }
167
- }
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
- }
187
- }
188
- return result;
189
- }
190
-
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 dumbCohort = this._getDumbCohort(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
- }
218
- }
219
-
220
- // 2. Calculate Sector Flow
221
- const sectorResult = this._calculateFlow(this.sectorData);
222
-
223
- return {
224
- cohort_size: dumbCohort.size,
225
- assets: assetResult,
226
- sectors: sectorResult
227
- };
228
- }
229
-
230
- reset() {
231
- this.assetData.clear();
232
- this.sectorData.clear();
233
- this.mappings = null;
234
- this.dumbCohortUserIds = null;
235
- }
236
- }
237
-
238
- module.exports = DumbCohortFlow;
@@ -1,137 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for gain response.
3
- *
4
- * This metric answers: "How do users behave when a position has a
5
- * gain of over 10%?"
6
- *
7
- * It checks all positions from yesterday that were in >10% profit
8
- * and tracks whether the user:
9
- * 1. Held (position still open today, same size)
10
- * 2. Closed (position no longer open today)
11
- * 3. Reduced (position still open, but Invested % is smaller)
12
- */
13
- class GainResponse {
14
- constructor() {
15
- this.actions = {
16
- held: 0,
17
- closed: 0,
18
- reduced: 0
19
- };
20
- this.total_in_gain = 0;
21
- }
22
-
23
- /**
24
- * Defines the output schema for this calculation.
25
- * @returns {object} JSON Schema object
26
- */
27
- static getSchema() {
28
- return {
29
- "type": "object",
30
- "description": "Analyzes user behavior in response to a >10% position gain.",
31
- "properties": {
32
- "total_positions_in_gain": {
33
- "type": "number",
34
- "description": "Total positions from yesterday that were in >10% gain."
35
- },
36
- "action_held_pct": {
37
- "type": "number",
38
- "description": "Percentage of gain positions that were held."
39
- },
40
- "action_closed_pct": {
41
- "type": "number",
42
- "description": "Percentage of gain positions that were closed (profit-taking)."
43
- },
44
- "action_reduced_pct": {
45
- "type": "number",
46
- "description": "Percentage of gain positions that were reduced (scaling out)."
47
- },
48
- "raw_counts": {
49
- "type": "object",
50
- "properties": {
51
- "held": { "type": "number" },
52
- "closed": { "type": "number" },
53
- "reduced": { "type": "number" }
54
- },
55
- "required": ["held", "closed", "reduced"]
56
- }
57
- },
58
- "required": ["total_positions_in_gain", "action_held_pct", "action_closed_pct", "action_reduced_pct", "raw_counts"]
59
- };
60
- }
61
-
62
- _getPortfolioMap(portfolio) {
63
- // We MUST use PositionID to track specific trades
64
- const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
65
- if (!positions || !Array.isArray(positions)) {
66
- return new Map();
67
- }
68
- // Map<PositionID, { pnl: number, invested: number }>
69
- return new Map(positions.map(p => [p.PositionID, {
70
- pnl: (p.NetProfit || 0) / (p.InvestedAmount || p.Amount || 1), // PNL as %
71
- invested: p.InvestedAmount || p.Amount || 0
72
- }]));
73
- }
74
-
75
- process(todayPortfolio, yesterdayPortfolio) {
76
- if (!todayPortfolio || !yesterdayPortfolio) {
77
- return;
78
- }
79
-
80
- const yPosMap = this._getPortfolioMap(yesterdayPortfolio);
81
- const tPosMap = this._getPortfolioMap(todayPortfolio);
82
-
83
- if (yPosMap.size === 0) {
84
- return;
85
- }
86
-
87
- for (const [yPosId, yPosData] of yPosMap.entries()) {
88
- // Check if position was in >10% gain
89
- if (yPosData.pnl > 0.10) {
90
- this.total_in_gain++;
91
-
92
- // Now, check what happened today
93
- if (!tPosMap.has(yPosId)) {
94
- // 1. Position was closed (Profit Taking)
95
- this.actions.closed++;
96
- } else {
97
- const tPosData = tPosMap.get(yPosId);
98
- // 2. Position was reduced (check for > 1% reduction to avoid noise)
99
- if (tPosData.invested < (yPosData.invested * 0.99)) {
100
- this.actions.reduced++;
101
- } else {
102
- // 3. Position was held
103
- this.actions.held++;
104
- }
105
- }
106
- }
107
- }
108
- }
109
-
110
- getResult() {
111
- const total = this.total_in_gain;
112
- if (total === 0) {
113
- return {
114
- total_positions_in_gain: 0,
115
- action_held_pct: 0,
116
- action_closed_pct: 0,
117
- action_reduced_pct: 0,
118
- raw_counts: this.actions
119
- };
120
- }
121
-
122
- return {
123
- total_positions_in_gain: total,
124
- action_held_pct: (this.actions.held / total) * 100,
125
- action_closed_pct: (this.actions.closed / total) * 100,
126
- action_reduced_pct: (this.actions.reduced / total) * 100,
127
- raw_counts: this.actions
128
- };
129
- }
130
-
131
- reset() {
132
- this.actions = { held: 0, closed: 0, reduced: 0 };
133
- this.total_in_gain = 0;
134
- }
135
- }
136
-
137
- module.exports = GainResponse;
@@ -1,85 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for aggregating user performance.
3
- *
4
- * This class processes each user's daily P&L and aggregates it into
5
- * buckets for statistical analysis. It collects all individual P&Ls
6
- * to build a distribution.
7
- *
8
- * This is a foundational calculation needed by many Pass 3 metrics.
9
- */
10
- class HistoricalPerformanceAggregator {
11
- constructor() {
12
- // Stores the 7-day weighted P&L for every user.
13
- this.userPnlHistory = [];
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": "Aggregates historical P&L from 'user_profitability_tracker' for all users to build a performance distribution.",
24
- "properties": {
25
- "user_pnl_distribution": {
26
- "type": "array",
27
- "description": "An array of 7-day weighted average P&L values, one for each user.",
28
- "items": { "type": "number" }
29
- },
30
- "user_count": {
31
- "type": "number",
32
- "description": "Total number of users processed."
33
- }
34
- },
35
- "required": ["user_pnl_distribution", "user_count"]
36
- };
37
- }
38
-
39
- /**
40
- * Statically declare dependencies.
41
- */
42
- static getDependencies() {
43
- return ['user_profitability_tracker'];
44
- }
45
-
46
- /**
47
- * process() is a no-op. All logic is in getResult().
48
- * This calculation doesn't process portfolios; it processes
49
- * the *result* of another calculation.
50
- */
51
- process() {
52
- // No-op
53
- }
54
-
55
- /**
56
- * Aggregates results from the dependency.
57
- */
58
- getResult(fetchedDependencies) {
59
- const profitabilityData = fetchedDependencies['user_profitability_tracker'];
60
-
61
- if (!profitabilityData || !profitabilityData.user_details) {
62
- return {
63
- user_pnl_distribution: [],
64
- user_count: 0
65
- };
66
- }
67
-
68
- // Extract the 7-day weighted P&L for all users
69
- const pnlDistribution = Object.values(profitabilityData.user_details)
70
- .map(details => details.weighted_avg_pnl_7d)
71
- // Filter out any null/undefined/NaN values
72
- .filter(pnl => typeof pnl === 'number' && !isNaN(pnl));
73
-
74
- return {
75
- user_pnl_distribution: pnlDistribution,
76
- user_count: pnlDistribution.length
77
- };
78
- }
79
-
80
- reset() {
81
- this.userPnlHistory = [];
82
- }
83
- }
84
-
85
- module.exports = HistoricalPerformanceAggregator;
@@ -1,168 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 3) for "In Loss" cohort asset flow.
3
- *
4
- * --- MODIFIED ---
5
- * Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
6
- * This calculation now determines "in loss" status internally
7
- * by reading the *yesterday's* portfolio P&L directly.
8
- */
9
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
10
-
11
- class InLossAssetCrowdFlow {
12
- constructor() {
13
- this.assetData = new Map();
14
- this.mappings = null;
15
- // No longer need this:
16
- // this.inLossCohorts = null;
17
- }
18
-
19
- /**
20
- * Defines the output schema for this calculation.
21
- * @returns {object} JSON Schema object
22
- */
23
- static getSchema() {
24
- return {
25
- "type": "object",
26
- "description": "Calculates net capital flow % (price-adjusted) per asset, but only for the cohort of users currently in loss on that asset.",
27
- "patternProperties": {
28
- // Ticker
29
- "^.*$": {
30
- "type": "object",
31
- "description": "Net flow metrics for a specific asset ticker from its 'in loss' cohort.",
32
- "properties": {
33
- "net_flow_percentage": {
34
- "type": "number",
35
- "description": "Net capital flow % from the 'in loss' cohort, adjusted for price changes."
36
- },
37
- "total_invested_today": { "type": "number" },
38
- "total_invested_yesterday": { "type": "number" },
39
- "cohort_size": { "type": "number" }
40
- },
41
- "required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday", "cohort_size"]
42
- }
43
- },
44
- "additionalProperties": {
45
- "type": "object",
46
- "properties": {
47
- "net_flow_percentage": { "type": "number" },
48
- "total_invested_today": { "type": "number" },
49
- "total_invested_yesterday": { "type": "number" },
50
- "cohort_size": { "type": "number" }
51
- }
52
- }
53
- };
54
- }
55
-
56
- /**
57
- * Statically declare dependencies.
58
- * --- MODIFIED ---
59
- * Removed 'asset_pnl-status'
60
- */
61
- static getDependencies() {
62
- return [];
63
- }
64
-
65
- _getPortfolioPositions(portfolio) {
66
- return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
67
- }
68
-
69
- _initAsset(instrumentId) {
70
- if (!this.assetData.has(instrumentId)) {
71
- this.assetData.set(instrumentId, {
72
- total_invested_yesterday: 0,
73
- total_invested_today: 0,
74
- price_change_yesterday: 0,
75
- cohort: new Set()
76
- });
77
- }
78
- }
79
-
80
- // --- MODIFIED ---
81
- // Removed _getInLossCohorts helper
82
-
83
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
84
- if (!todayPortfolio || !yesterdayPortfolio) {
85
- return;
86
- }
87
-
88
- if (!this.mappings) {
89
- this.mappings = context.mappings;
90
- }
91
-
92
- const yPos = this._getPortfolioPositions(yesterdayPortfolio);
93
- const tPos = this._getPortfolioPositions(todayPortfolio);
94
-
95
- const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
96
- const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
97
-
98
- const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
99
-
100
- for (const instrumentId of allInstrumentIds) {
101
- if (!instrumentId) continue;
102
-
103
- const yP = yPosMap.get(instrumentId);
104
- const tP = tPosMap.get(instrumentId);
105
-
106
- // --- MODIFIED ---
107
- // Check P&L from YESTERDAY's position to define cohort
108
- const yPnl = yP?.NetProfit || 0;
109
- if (yPnl >= 0) {
110
- continue; // User was not in loss yesterday, skip.
111
- }
112
- // --- END MODIFIED ---
113
-
114
- // User *is* in the cohort, process their data
115
- this._initAsset(instrumentId);
116
- const asset = this.assetData.get(instrumentId);
117
- asset.cohort.add(userId); // Track cohort size
118
-
119
- const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
120
- const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
121
-
122
- if (yInvested > 0) {
123
- asset.total_invested_yesterday += yInvested;
124
- const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
125
- asset.price_change_yesterday += yPriceChange * yInvested;
126
- }
127
- if (tInvested > 0) {
128
- asset.total_invested_today += tInvested;
129
- }
130
- }
131
- }
132
-
133
- async getResult() {
134
- if (!this.mappings) {
135
- this.mappings = await loadInstrumentMappings();
136
- }
137
-
138
- const result = {};
139
-
140
- for (const [instrumentId, data] of this.assetData.entries()) {
141
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
142
-
143
- const { total_invested_yesterday, total_invested_today, price_change_yesterday, cohort } = data;
144
-
145
- if (total_invested_yesterday > 0) {
146
- const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
147
- const price_contribution = total_invested_yesterday * avg_price_change_pct;
148
- const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
149
- const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
150
-
151
- result[ticker] = {
152
- net_flow_percentage: net_flow_percentage,
153
- total_invested_today: total_invested_today,
154
- total_invested_yesterday: total_invested_yesterday,
155
- cohort_size: cohort.size
156
- };
157
- }
158
- }
159
- return result;
160
- }
161
-
162
- reset() {
163
- this.assetData.clear();
164
- this.mappings = null;
165
- }
166
- }
167
-
168
- module.exports = InLossAssetCrowdFlow;