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,168 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 3) for "In Profit" cohort asset flow.
3
- *
4
- * --- MODIFIED ---
5
- * Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
6
- * This calculation now determines "in profit" status internally
7
- * by reading the *yesterday's* portfolio P&L directly.
8
- */
9
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
10
-
11
- class InProfitAssetCrowdFlow {
12
- constructor() {
13
- this.assetData = new Map();
14
- this.mappings = null;
15
- // No longer need this:
16
- // this.inProfitCohorts = 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 profit on that asset.",
27
- "patternProperties": {
28
- // Ticker
29
- "^.*$": {
30
- "type": "object",
31
- "description": "Net flow metrics for a specific asset ticker from its 'in profit' cohort.",
32
- "properties": {
33
- "net_flow_percentage": {
34
- "type": "number",
35
- "description": "Net capital flow % from the 'in profit' 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 _getInProfitCohorts 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 profit 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 = InProfitAssetCrowdFlow;
@@ -1,232 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 4) for negative expectancy cohort flow.
3
- *
4
- * This metric calculates the "Net Crowd Flow Percentage" for the
5
- * "Negative Expectancy Cohort" (users with a low expectancy score).
6
- *
7
- * This calculation *depends* on 'user_expectancy_score'
8
- * to identify the cohort.
9
- */
10
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
-
12
- class NegativeExpectancyCohortFlow {
13
- constructor() {
14
- this.assetData = new Map();
15
- this.sectorData = new Map();
16
- this.mappings = null;
17
- this.negExpCohortUserIds = 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 'Negative Expectancy' cohort (score < 0.2), aggregated by asset and sector.",
38
- "properties": {
39
- "cohort_size": {
40
- "type": "number",
41
- "description": "The number of users identified as being in the Negative Expectancy Cohort."
42
- },
43
- "assets": {
44
- "type": "object",
45
- "description": "Price-adjusted net flow per asset.",
46
- "patternProperties": { "^.*$": flowSchema }, // Ticker
47
- "additionalProperties": flowSchema
48
- },
49
- "sectors": {
50
- "type": "object",
51
- "description": "Price-adjusted net flow per sector.",
52
- "patternProperties": { "^.*$": flowSchema }, // Sector
53
- "additionalProperties": flowSchema
54
- }
55
- },
56
- "required": ["cohort_size", "assets", "sectors"]
57
- };
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
- _getNegExpCohort(fetchedDependencies) {
92
- if (this.negExpCohortUserIds) {
93
- return this.negExpCohortUserIds;
94
- }
95
-
96
- const expectancyData = fetchedDependencies['user_expectancy_score'];
97
- if (!expectancyData) {
98
- return new Set();
99
- }
100
-
101
- this.negExpCohortUserIds = new Set();
102
- for (const [userId, data] of Object.entries(expectancyData)) {
103
- // Definition: Expectancy score < 0.2
104
- if (data.expectancy_score < 0.2) {
105
- this.negExpCohortUserIds.add(userId);
106
- }
107
- }
108
- return this.negExpCohortUserIds;
109
- }
110
-
111
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
112
- const cohort = this._getNegExpCohort(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._getNegExpCohort(fetchedDependencies);
193
-
194
- // 1. Calculate Asset Flow
195
- const assetResult = {};
196
- for (const [instrumentId, data] of this.assetData.entries()) {
197
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
198
- const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
199
-
200
- if (total_invested_yesterday > 0) {
201
- const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
202
- const price_contribution = total_invested_yesterday * avg_price_change_pct;
203
- const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
204
- const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
205
-
206
- assetResult[ticker] = {
207
- net_flow_percentage: net_flow_percentage,
208
- total_invested_today: total_invested_today,
209
- total_invested_yesterday: total_invested_yesterday
210
- };
211
- }
212
- }
213
-
214
- // 2. Calculate Sector Flow
215
- const sectorResult = this._calculateFlow(this.sectorData);
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.negExpCohortUserIds = null;
229
- }
230
- }
231
-
232
- module.exports = NegativeExpectancyCohortFlow;
@@ -1,98 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for new allocation percentage.
3
- *
4
- * This metric answers: "On average, what percentage of a user's
5
- * portfolio was allocated to *new* positions today?"
6
- *
7
- * This measures the flow of capital into new ideas vs. existing ones.
8
- */
9
- class NewAllocationPercentage {
10
- constructor() {
11
- this.total_new_allocation_pct = 0;
12
- this.users_with_new_positions = 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 allocated to *new* positions today.",
23
- "properties": {
24
- "average_new_allocation_pct": {
25
- "type": "number",
26
- "description": "The average percentage of a portfolio allocated to new positions, for users who opened new positions."
27
- },
28
- "total_new_allocation_pct_sum": {
29
- "type": "number",
30
- "description": "The sum of all new allocation percentages."
31
- },
32
- "user_count_with_new_positions": {
33
- "type": "number",
34
- "description": "The count of users who opened at least one new position."
35
- }
36
- },
37
- "required": ["average_new_allocation_pct", "total_new_allocation_pct_sum", "user_count_with_new_positions"]
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) {
62
- return; // No positions today
63
- }
64
-
65
- let userNewAllocationPct = 0;
66
-
67
- for (const [tId, tPosData] of tPosMap.entries()) {
68
- // Check if this position is new (in today but not yesterday)
69
- if (!yPosMap.has(tId)) {
70
- userNewAllocationPct += tPosData.invested;
71
- }
72
- }
73
-
74
- if (userNewAllocationPct > 0) {
75
- this.total_new_allocation_pct += userNewAllocationPct;
76
- this.users_with_new_positions++;
77
- }
78
- }
79
-
80
- getResult() {
81
- const avg_pct = (this.users_with_new_positions > 0)
82
- ? (this.total_new_allocation_pct / this.users_with_new_positions)
83
- : 0;
84
-
85
- return {
86
- average_new_allocation_pct: avg_pct,
87
- total_new_allocation_pct_sum: this.total_new_allocation_pct,
88
- user_count_with_new_positions: this.users_with_new_positions
89
- };
90
- }
91
-
92
- reset() {
93
- this.total_new_allocation_pct = 0;
94
- this.users_with_new_positions = 0;
95
- }
96
- }
97
-
98
- module.exports = NewAllocationPercentage;
@@ -1,107 +0,0 @@
1
- /**
2
- * @fileoverview Calculation (Pass 2) for Paper vs Diamond Hands.
3
- *
4
- * This metric provides a simple ratio of positions that were
5
- * closed today ("paper hands") vs. positions that were held
6
- * ("diamond hands").
7
- *
8
- * It gives a general sense of market turnover.
9
- */
10
- class PaperVsDiamondHands {
11
- constructor() {
12
- this.paper_hands = 0; // Positions closed
13
- this.diamond_hands = 0; // Positions held
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": "Calculates the ratio of positions closed ('paper hands') vs. held ('diamond hands') from yesterday to today.",
24
- "properties": {
25
- "paper_hands_count": {
26
- "type": "number",
27
- "description": "Total count of positions that existed yesterday but were closed today."
28
- },
29
- "diamond_hands_count": {
30
- "type": "number",
31
- "description": "Total count of positions that existed yesterday and are still held today."
32
- },
33
- "total_positions_yesterday": {
34
- "type": "number",
35
- "description": "The sum of paper and diamond hands counts."
36
- },
37
- "paper_hands_ratio": {
38
- "type": "number",
39
- "description": "Ratio of paper hands to diamond hands (Paper / Diamond). Null if no diamond hands."
40
- },
41
- "paper_hands_pct": {
42
- "type": "number",
43
- "description": "Percentage of positions that were 'paper handed' (closed)."
44
- },
45
- "diamond_hands_pct": {
46
- "type": "number",
47
- "description": "Percentage of positions that were 'diamond handed' (held)."
48
- }
49
- },
50
- "required": ["paper_hands_count", "diamond_hands_count", "total_positions_yesterday", "paper_hands_pct", "diamond_hands_pct"]
51
- };
52
- }
53
-
54
- _getPortfolioPositionIds(portfolio) {
55
- // We MUST use PositionID to track specific trades
56
- const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
57
- if (!positions || !Array.isArray(positions)) {
58
- return new Set();
59
- }
60
- return new Set(positions.map(p => p.PositionID).filter(Boolean));
61
- }
62
-
63
- process(todayPortfolio, yesterdayPortfolio) {
64
- if (!todayPortfolio || !yesterdayPortfolio) {
65
- return;
66
- }
67
-
68
- const yPosIds = this._getPortfolioPositionIds(yesterdayPortfolio);
69
- const tPosIds = this._getPortfolioPositionIds(todayPortfolio);
70
-
71
- if (yPosIds.size === 0) {
72
- return; // No positions yesterday to analyze
73
- }
74
-
75
- for (const yPosId of yPosIds) {
76
- if (tPosIds.has(yPosId)) {
77
- // Position was held
78
- this.diamond_hands++;
79
- } else {
80
- // Position was closed
81
- this.paper_hands++;
82
- }
83
- }
84
- }
85
-
86
- getResult() {
87
- const total = this.paper_hands + this.diamond_hands;
88
-
89
- return {
90
- paper_hands_count: this.paper_hands,
91
- diamond_hands_count: this.diamond_hands,
92
- total_positions_yesterday: total,
93
- // Ratio of paper-to-diamond. Can be null if diamond_hands is 0.
94
- paper_hands_ratio: (this.diamond_hands > 0) ? (this.paper_hands / this.diamond_hands) : null,
95
- // Percentage of total positions
96
- paper_hands_pct: (total > 0) ? (this.paper_hands / total) * 100 : 0,
97
- diamond_hands_pct: (total > 0) ? (this.diamond_hands / total) * 100 : 0,
98
- };
99
- }
100
-
101
- reset() {
102
- this.paper_hands = 0;
103
- this.diamond_hands = 0;
104
- }
105
- }
106
-
107
- module.exports = PaperVsDiamondHands;