aiden-shared-calculations-unified 1.0.95 → 1.0.97

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 (61) 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/core/Insights-total-long-per-stock +56 -0
  4. package/calculations/core/insights-daily-bought-vs-sold-count.js +74 -0
  5. package/calculations/core/insights-daily-ownership-delta.js +70 -0
  6. package/calculations/core/insights-sentimet-per-stock.js +68 -0
  7. package/calculations/core/insights-total-long-per-sector +73 -0
  8. package/calculations/core/insights-total-positions-held.js +49 -0
  9. package/calculations/ghost-book/cost-basis-density.js +1 -2
  10. package/calculations/ghost-book/liquidity-vacuum.js +4 -4
  11. package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
  12. package/calculations/helix/winner-loser-flow.js +1 -1
  13. package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
  14. package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
  15. package/calculations/predicative-alpha/mimetic-latency.js +1 -2
  16. package/package.json +1 -1
  17. package/calculations/legacy/activity_by_pnl_status.js +0 -119
  18. package/calculations/legacy/asset_crowd_flow.js +0 -163
  19. package/calculations/legacy/capital_deployment_strategy.js +0 -108
  20. package/calculations/legacy/capital_liquidation_performance.js +0 -139
  21. package/calculations/legacy/capital_vintage_performance.js +0 -136
  22. package/calculations/legacy/cash-flow-deployment.js +0 -144
  23. package/calculations/legacy/cash-flow-liquidation.js +0 -146
  24. package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
  25. package/calculations/legacy/crowd_conviction_score.js +0 -261
  26. package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
  27. package/calculations/legacy/daily_asset_activity.js +0 -128
  28. package/calculations/legacy/daily_user_activity_tracker.js +0 -182
  29. package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
  30. package/calculations/legacy/diversification_pnl.js +0 -115
  31. package/calculations/legacy/drawdown_response.js +0 -137
  32. package/calculations/legacy/dumb-cohort-flow.js +0 -238
  33. package/calculations/legacy/gain_response.js +0 -137
  34. package/calculations/legacy/historical_performance_aggregator.js +0 -85
  35. package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
  36. package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
  37. package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
  38. package/calculations/legacy/new_allocation_percentage.js +0 -98
  39. package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
  40. package/calculations/legacy/position_count_pnl.js +0 -120
  41. package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
  42. package/calculations/legacy/profit_cohort_divergence.js +0 -115
  43. package/calculations/legacy/profitability_migration.js +0 -104
  44. package/calculations/legacy/reallocation_increase_percentage.js +0 -104
  45. package/calculations/legacy/risk_appetite_change.js +0 -97
  46. package/calculations/legacy/sector_rotation.js +0 -117
  47. package/calculations/legacy/shark_attack_signal.js +0 -112
  48. package/calculations/legacy/smart-cohort-flow.js +0 -238
  49. package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
  50. package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
  51. package/calculations/legacy/smart_money_flow.js +0 -198
  52. package/calculations/legacy/social-predictive-regime-state.js +0 -102
  53. package/calculations/legacy/social-topic-driver-index.js +0 -147
  54. package/calculations/legacy/social-topic-predictive-potential.js +0 -461
  55. package/calculations/legacy/social_flow_correlation.js +0 -112
  56. package/calculations/legacy/speculator_adjustment_activity.js +0 -103
  57. package/calculations/legacy/strategy-performance.js +0 -265
  58. package/calculations/legacy/tsl_effectiveness.js +0 -85
  59. package/calculations/legacy/user-investment-profile.js +0 -313
  60. package/calculations/legacy/user_expectancy_score.js +0 -106
  61. 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;