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,120 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for P&L by position count.
3
- *
4
- * This metric answers: "What is the average daily P&L for users,
5
- * bucketed by the number of positions they hold?"
6
- *
7
- * This helps determine if holding more positions (diversifying)
8
- * correlates with better or worse P&L.
9
- */
10
- class PositionCountPnl {
11
- constructor() {
12
- // We will store { [count_bucket]: { pnl_sum: 0, user_count: 0 } }
13
- this.buckets = {
14
- '1': { pnl_sum: 0, user_count: 0 },
15
- '2-5': { pnl_sum: 0, user_count: 0 },
16
- '6-10': { pnl_sum: 0, user_count: 0 },
17
- '11-20': { pnl_sum: 0, user_count: 0 },
18
- '21+': { pnl_sum: 0, user_count: 0 },
19
- };
20
- }
21
-
22
- /**
23
- * Defines the output schema for this calculation.
24
- * @returns {object} JSON Schema object
25
- */
26
- static getSchema() {
27
- const bucketSchema = {
28
- "type": "object",
29
- "description": "Aggregated P&L metrics for a position count bucket.",
30
- "properties": {
31
- "average_daily_pnl": {
32
- "type": "number",
33
- "description": "The average daily P&L for users in this bucket."
34
- },
35
- "user_count": {
36
- "type": "number",
37
- "description": "The number of users in this bucket."
38
- },
39
- "pnl_sum": {
40
- "type": "number",
41
- "description": "The sum of all P&L for users in this bucket."
42
- }
43
- },
44
- "required": ["average_daily_pnl", "user_count", "pnl_sum"]
45
- };
46
-
47
- return {
48
- "type": "object",
49
- "description": "Average daily P&L bucketed by the number of positions a user holds.",
50
- "properties": {
51
- "1": bucketSchema,
52
- "2-5": bucketSchema,
53
- "6-10": bucketSchema,
54
- "11-20": bucketSchema,
55
- "21+": bucketSchema
56
- },
57
- "required": ["1", "2-5", "6-10", "11-20", "21+"]
58
- };
59
- }
60
-
61
- _getBucket(count) {
62
- if (count === 1) return '1';
63
- if (count >= 2 && count <= 5) return '2-5';
64
- if (count >= 6 && count <= 10) return '6-10';
65
- if (count >= 11 && count <= 20) return '11-20';
66
- if (count >= 21) return '21+';
67
- return null;
68
- }
69
-
70
- process(todayPortfolio, yesterdayPortfolio) {
71
- // This calculation only needs today's portfolio state
72
- if (!todayPortfolio) {
73
- return;
74
- }
75
-
76
- const positions = todayPortfolio.AggregatedPositions || todayPortfolio.PublicPositions;
77
- const positionCount = Array.isArray(positions) ? positions.length : 0;
78
-
79
- if (positionCount === 0) {
80
- return;
81
- }
82
-
83
- const bucketKey = this._getBucket(positionCount);
84
- if (!bucketKey) {
85
- return;
86
- }
87
-
88
- // Use the P&L from the summary, which is for the *day*
89
- const dailyPnl = todayPortfolio.Summary?.NetProfit || 0;
90
-
91
- const bucket = this.buckets[bucketKey];
92
- bucket.pnl_sum += dailyPnl;
93
- bucket.user_count++;
94
- }
95
-
96
- getResult() {
97
- const result = {};
98
- for (const key in this.buckets) {
99
- const bucket = this.buckets[key];
100
- result[key] = {
101
- average_daily_pnl: (bucket.user_count > 0) ? (bucket.pnl_sum / bucket.user_count) : 0,
102
- user_count: bucket.user_count,
103
- pnl_sum: bucket.pnl_sum
104
- };
105
- }
106
- return result;
107
- }
108
-
109
- reset() {
110
- this.buckets = {
111
- '1': { pnl_sum: 0, user_count: 0 },
112
- '2-5': { pnl_sum: 0, user_count: 0 },
113
- '6-10': { pnl_sum: 0, user_count: 0 },
114
- '11-20': { pnl_sum: 0, user_count: 0 },
115
- '21+': { pnl_sum: 0, user_count: 0 },
116
- };
117
- }
118
- }
119
-
120
- module.exports = PositionCountPnl;
@@ -1,232 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 4) for positive expectancy cohort flow.
3
- *
4
- * This metric calculates the "Net Crowd Flow Percentage" for the
5
- * "Positive Expectancy Cohort" (users with a high expectancy score).
6
- *
7
- * This calculation *depends* on 'user_expectancy_score'
8
- * to identify the cohort.
9
- */
10
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
-
12
- class PositiveExpectancyCohortFlow {
13
- constructor() {
14
- this.assetData = new Map();
15
- this.sectorData = new Map();
16
- this.mappings = null;
17
- this.posExpCohortUserIds = 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 'Positive Expectancy' cohort (score > 0.5), aggregated by asset and sector.",
38
- "properties": {
39
- "cohort_size": {
40
- "type": "number",
41
- "description": "The number of users identified as being in the Positive 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
- };
58
- }
59
-
60
- /**
61
- * Statically declare dependencies.
62
- */
63
- static getDependencies() {
64
- return ['user_expectancy_score']; // Pass 3
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
- _getPosExpCohort(fetchedDependencies) {
92
- if (this.posExpCohortUserIds) {
93
- return this.posExpCohortUserIds;
94
- }
95
-
96
- const expectancyData = fetchedDependencies['user_expectancy_score'];
97
- if (!expectancyData) {
98
- return new Set();
99
- }
100
-
101
- this.posExpCohortUserIds = new Set();
102
- for (const [userId, data] of Object.entries(expectancyData)) {
103
- // Definition: Expectancy score > 0.5
104
- if (data.expectancy_score > 0.5) {
105
- this.posExpCohortUserIds.add(userId);
106
- }
107
- }
108
- return this.posExpCohortUserIds;
109
- }
110
-
111
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
112
- const cohort = this._getPosExpCohort(fetchedDependencies);
113
-
114
- if (!cohort.has(userId)) {
115
- return;
116
- }
117
-
118
- if (!todayPortfolio || !yesterdayPortfolio) {
119
- return;
120
- }
121
-
122
- const yPos = this._getPortfolioPositions(yesterdayPortfolio);
123
- const tPos = this._getPortfolioPositions(todayPortfolio);
124
-
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()]);
129
-
130
- if (!this.mappings) {
131
- this.mappings = context.mappings;
132
- }
133
-
134
- for (const instrumentId of allInstrumentIds) {
135
- if (!instrumentId) continue;
136
-
137
- this._initAsset(instrumentId);
138
- const asset = this.assetData.get(instrumentId);
139
-
140
- const yP = yPosMap.get(instrumentId);
141
- const tP = tPosMap.get(instrumentId);
142
-
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);
149
-
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
- }
163
- }
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
- }
183
- }
184
- return result;
185
- }
186
-
187
- async getResult(fetchedDependencies) {
188
- if (!this.mappings) {
189
- this.mappings = await loadInstrumentMappings();
190
- }
191
-
192
- const cohort = this._getPosExpCohort(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);
216
-
217
- return {
218
- cohort_size: cohort.size,
219
- assets: assetResult,
220
- sectors: sectorResult
221
- };
222
- }
223
-
224
- reset() {
225
- this.assetData.clear();
226
- this.sectorData.clear();
227
- this.mappings = null;
228
- this.posExpCohortUserIds = null;
229
- }
230
- }
231
-
232
- module.exports = PositiveExpectancyCohortFlow;
@@ -1,115 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 4) for profit cohort divergence.
3
- *
4
- * This metric answers: "What divergence signals can be found by
5
- * comparing the net asset flow of the 'in-profit' cohort vs.
6
- * the 'in-loss' cohort?"
7
- *
8
- * e.g.,
9
- * - Profit cohort selling, Loss cohort holding = "Profit Taking"
10
- * - Profit cohort holding, Loss cohort selling = "Capitulation"
11
- * - Both buying = "Confirmation"
12
- * - Both selling = "Exodus"
13
- *
14
- * It *depends* on 'in_profit_asset_crowd_flow' and
15
- * 'in_loss_asset_crowd_flow'.
16
- */
17
- class ProfitCohortDivergence {
18
- constructor() {
19
- // No per-user processing
20
- }
21
-
22
- /**
23
- * Defines the output schema for this calculation.
24
- * @returns {object} JSON Schema object
25
- */
26
- static getSchema() {
27
- const signalSchema = {
28
- "type": "object",
29
- "properties": {
30
- "status": {
31
- "type": "string",
32
- "enum": ["Profit Taking", "Capitulation", "Confirmation (Buy)", "Confirmation (Sell)", "Divergence (Profit Buy / Loss Sell)", "Divergence (Profit Sell / Loss Buy)", "Neutral"]
33
- },
34
- "profit_cohort_flow_pct": { "type": "number" },
35
- "loss_cohort_flow_pct": { "type": "number" }
36
- },
37
- "required": ["status", "profit_cohort_flow_pct", "loss_cohort_flow_pct"]
38
- };
39
-
40
- return {
41
- "type": "object",
42
- "description": "Generates divergence signals by comparing net flow of 'in-profit' vs. 'in-loss' cohorts.",
43
- "patternProperties": {
44
- "^.*$": signalSchema // Ticker
45
- },
46
- "additionalProperties": signalSchema
47
- };
48
- }
49
-
50
- /**
51
- * Statically declare dependencies.
52
- */
53
- static getDependencies() {
54
- return [
55
- 'in_profit_asset_crowd_flow', // Pass 3
56
- 'in_loss_asset_crowd_flow' // Pass 3
57
- ];
58
- }
59
-
60
- process() {
61
- // No-op
62
- }
63
-
64
- getResult(fetchedDependencies) {
65
- const profitFlowData = fetchedDependencies['in_profit_asset_crowd_flow'];
66
- const lossFlowData = fetchedDependencies['in_loss_asset_crowd_flow'];
67
-
68
- if (!profitFlowData || !lossFlowData) {
69
- return {};
70
- }
71
-
72
- const allTickers = new Set([...Object.keys(profitFlowData), ...Object.keys(lossFlowData)]);
73
- const result = {};
74
- const THRESHOLD = 1; // Min flow % to be considered 'active'
75
-
76
- for (const ticker of allTickers) {
77
- const pFlow = profitFlowData[ticker]?.net_flow_percentage || 0;
78
- const lFlow = lossFlowData[ticker]?.net_flow_percentage || 0;
79
-
80
- let status = 'Neutral';
81
-
82
- if (pFlow > THRESHOLD && lFlow > THRESHOLD) {
83
- status = 'Confirmation (Buy)';
84
- } else if (pFlow < -THRESHOLD && lFlow < -THRESHOLD) {
85
- status = 'Confirmation (Sell)';
86
- } else if (pFlow > THRESHOLD && Math.abs(lFlow) < THRESHOLD) {
87
- status = 'Divergence (Profit Buy / Loss Sell)'; // Profit cohort buying, loss cohort holding
88
- } else if (pFlow < -THRESHOLD && Math.abs(lFlow) < THRESHOLD) {
89
- status = 'Profit Taking';
90
- } else if (Math.abs(pFlow) < THRESHOLD && lFlow < -THRESHOLD) {
91
- status = 'Capitulation';
92
- } else if (Math.abs(pFlow) < THRESHOLD && lFlow > THRESHOLD) {
93
- status = 'Divergence (Profit Sell / Loss Buy)'; // Loss cohort buying, profit cohort holding
94
- } else if (pFlow > THRESHOLD && lFlow < -THRESHOLD) {
95
- status = 'Divergence (Profit Buy / Loss Sell)';
96
- } else if (pFlow < -THRESHOLD && lFlow > THRESHOLD) {
97
- status = 'Divergence (Profit Sell / Loss Buy)';
98
- }
99
-
100
- result[ticker] = {
101
- status: status,
102
- profit_cohort_flow_pct: pFlow,
103
- loss_cohort_flow_pct: lFlow
104
- };
105
- }
106
-
107
- return result;
108
- }
109
-
110
- reset() {
111
- // No state
112
- }
113
- }
114
-
115
- module.exports = ProfitCohortDivergence;
@@ -1,104 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for profitability migration.
3
- *
4
- * This metric answers: "How many users migrated between
5
- * profitable and unprofitable states today?"
6
- *
7
- * It tracks the "churn" between P&L states.
8
- */
9
- class ProfitabilityMigration {
10
- constructor() {
11
- this.to_profit_count = 0;
12
- this.to_loss_count = 0;
13
- this.remained_profit_count = 0;
14
- this.remained_loss_count = 0;
15
- this.total_processed = 0;
16
- }
17
-
18
- /**
19
- * Defines the output schema for this calculation.
20
- * @returns {object} JSON Schema object
21
- */
22
- static getSchema() {
23
- return {
24
- "type": "object",
25
- "description": "Tracks the migration of users between profitable and unprofitable states day-over-day.",
26
- "properties": {
27
- "to_profit_count": {
28
- "type": "number",
29
- "description": "Count of users who were in loss yesterday and are in profit today."
30
- },
31
- "to_loss_count": {
32
- "type": "number",
33
- "description": "Count of users who were in profit yesterday and are in loss today."
34
- },
35
- "remained_profit_count": {
36
- "type": "number",
37
- "description": "Count of users who were in profit yesterday and today."
38
- },
39
- "remained_loss_count": {
40
- "type": "number",
41
- "description": "Count of users who were in loss yesterday and today."
42
- },
43
- "total_processed": {
44
- "type": "number",
45
- "description": "Total users who had a P&L status on both days."
46
- }
47
- },
48
- "required": ["to_profit_count", "to_loss_count", "remained_profit_count", "remained_loss_count", "total_processed"]
49
- };
50
- }
51
-
52
- _getPnlState(portfolio) {
53
- // This checks *overall portfolio* P&L for the day
54
- const dailyPnl = portfolio?.Summary?.NetProfit || 0;
55
- if (dailyPnl > 0) return 'profit';
56
- if (dailyPnl < 0) return 'loss';
57
- return 'neutral';
58
- }
59
-
60
- process(todayPortfolio, yesterdayPortfolio) {
61
- if (!todayPortfolio || !yesterdayPortfolio) {
62
- return;
63
- }
64
-
65
- const yState = this._getPnlState(yesterdayPortfolio);
66
- const tState = this._getPnlState(todayPortfolio);
67
-
68
- if (yState === 'neutral' || tState === 'neutral') {
69
- return; // Only track transitions between profit/loss
70
- }
71
-
72
- this.total_processed++;
73
-
74
- if (yState === 'profit' && tState === 'profit') {
75
- this.remained_profit_count++;
76
- } else if (yState === 'loss' && tState === 'loss') {
77
- this.remained_loss_count++;
78
- } else if (yState === 'loss' && tState === 'profit') {
79
- this.to_profit_count++;
80
- } else if (yState === 'profit' && tState === 'loss') {
81
- this.to_loss_count++;
82
- }
83
- }
84
-
85
- getResult() {
86
- return {
87
- to_profit_count: this.to_profit_count,
88
- to_loss_count: this.to_loss_count,
89
- remained_profit_count: this.remained_profit_count,
90
- remained_loss_count: this.remained_loss_count,
91
- total_processed: this.total_processed
92
- };
93
- }
94
-
95
- reset() {
96
- this.to_profit_count = 0;
97
- this.to_loss_count = 0;
98
- this.remained_profit_count = 0;
99
- this.remained_loss_count = 0;
100
- this.total_processed = 0;
101
- }
102
- }
103
-
104
- module.exports = ProfitabilityMigration;
@@ -1,104 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for reallocation increase percentage.
3
- *
4
- * This metric answers: "On average, what was the total percentage
5
- * increase in allocation to *existing* positions today?"
6
- *
7
- * This measures "doubling down" or adding to winners/losers.
8
- */
9
- class ReallocationIncreasePercentage {
10
- constructor() {
11
- this.total_reallocation_increase_pct = 0;
12
- this.users_with_reallocations = 0;
13
- }
14
-
15
- /**
16
- * Defines the output schema for this calculation.
17
- * @returns {object} JSON Schema object
18
- */
19
- static getSchema() {
20
- return {
21
- "type": "object",
22
- "description": "Calculates the average portfolio percentage *added* to *existing* positions today.",
23
- "properties": {
24
- "average_reallocation_increase_pct": {
25
- "type": "number",
26
- "description": "The average percentage of a portfolio added to existing positions, for users who reallocated."
27
- },
28
- "total_reallocation_increase_pct_sum": {
29
- "type": "number",
30
- "description": "The sum of all reallocation increase percentages."
31
- },
32
- "user_count_with_reallocations": {
33
- "type": "number",
34
- "description": "The count of users who added to at least one existing position."
35
- }
36
- },
37
- "required": ["average_reallocation_increase_pct", "total_reallocation_increase_pct_sum", "user_count_with_reallocations"]
38
- };
39
- }
40
-
41
- _getPortfolioPositionMap(portfolio) {
42
- // We MUST use AggregatedPositions for the 'Invested' %
43
- const positions = portfolio?.AggregatedPositions;
44
- if (!positions || !Array.isArray(positions)) {
45
- return new Map();
46
- }
47
- // Map<InstrumentID, { invested: number }>
48
- return new Map(positions.map(p => [p.InstrumentID, {
49
- invested: p.InvestedAmount || p.Invested || 0
50
- }]));
51
- }
52
-
53
- process(todayPortfolio, yesterdayPortfolio) {
54
- if (!todayPortfolio || !yesterdayPortfolio) {
55
- return;
56
- }
57
-
58
- const yPosMap = this._getPortfolioPositionMap(yesterdayPortfolio);
59
- const tPosMap = this._getPortfolioPositionMap(todayPortfolio);
60
-
61
- if (tPosMap.size === 0 || yPosMap.size === 0) {
62
- return; // No positions to compare
63
- }
64
-
65
- let userReallocationIncrease = 0;
66
-
67
- for (const [tId, tPosData] of tPosMap.entries()) {
68
- // Check if this position also existed yesterday
69
- if (yPosMap.has(tId)) {
70
- const yPosData = yPosMap.get(tId);
71
- const increase = tPosData.invested - yPosData.invested;
72
-
73
- // We only care about *increases*
74
- if (increase > 0) {
75
- userReallocationIncrease += increase;
76
- }
77
- }
78
- }
79
-
80
- if (userReallocationIncrease > 0) {
81
- this.total_reallocation_increase_pct += userReallocationIncrease;
82
- this.users_with_reallocations++;
83
- }
84
- }
85
-
86
- getResult() {
87
- const avg_pct = (this.users_with_reallocations > 0)
88
- ? (this.total_reallocation_increase_pct / this.users_with_reallocations)
89
- : 0;
90
-
91
- return {
92
- average_reallocation_increase_pct: avg_pct,
93
- total_reallocation_increase_pct_sum: this.total_reallocation_increase_pct,
94
- user_count_with_reallocations: this.users_with_reallocations
95
- };
96
- }
97
-
98
- reset() {
99
- this.total_reallocation_increase_pct = 0;
100
- this.users_with_reallocations = 0;
101
- }
102
- }
103
-
104
- module.exports = ReallocationIncreasePercentage;