aiden-shared-calculations-unified 1.0.63 → 1.0.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.MD +1 -1
  2. package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
  3. package/calculations/activity/historical/daily_asset_activity.js +42 -0
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
  6. package/calculations/asset_metrics/asset_position_size.js +36 -0
  7. package/calculations/backtests/strategy-performance.js +41 -0
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
  9. package/calculations/behavioural/historical/drawdown_response.js +113 -35
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
  11. package/calculations/behavioural/historical/gain_response.js +113 -34
  12. package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
  13. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
  14. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
  15. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
  16. package/calculations/behavioural/historical/position_count_pnl.js +91 -39
  17. package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
  18. package/calculations/behavioural/historical/smart_money_flow.js +160 -151
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
  22. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
  24. package/calculations/insights/daily_total_positions_held.js +28 -24
  25. package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
  26. package/calculations/insights/historical/daily_ownership_delta.js +95 -32
  27. package/calculations/meta/capital_deployment_strategy.js +78 -110
  28. package/calculations/meta/capital_liquidation_performance.js +114 -111
  29. package/calculations/meta/cash-flow-deployment.js +114 -107
  30. package/calculations/meta/cash-flow-liquidation.js +114 -107
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
  32. package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
  33. package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
  34. package/calculations/meta/profit_cohort_divergence.js +83 -59
  35. package/calculations/meta/shark_attack_signal.js +91 -39
  36. package/calculations/meta/smart-dumb-divergence-index.js +114 -98
  37. package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
  38. package/calculations/meta/social-predictive-regime-state.js +76 -155
  39. package/calculations/meta/social-topic-driver-index.js +74 -127
  40. package/calculations/meta/user_expectancy_score.js +83 -31
  41. package/calculations/pnl/asset_pnl_status.js +120 -31
  42. package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
  43. package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
  44. package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
  45. package/calculations/pnl/average_daily_position_pnl.js +49 -21
  46. package/calculations/pnl/historical/profitability_migration.js +81 -35
  47. package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
  48. package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
  49. package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
  50. package/calculations/pnl/profitability_skew_per_stock.js +86 -31
  51. package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
  52. package/calculations/sanity/users_processed.js +24 -1
  53. package/calculations/sectors/historical/diversification_pnl.js +104 -42
  54. package/calculations/sectors/historical/sector_rotation.js +94 -45
  55. package/calculations/sectors/total_long_per_sector.js +55 -20
  56. package/calculations/sectors/total_short_per_sector.js +55 -20
  57. package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
  58. package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
  59. package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
  60. package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
  61. package/calculations/short_and_long_stats/total_long_figures.js +34 -13
  62. package/calculations/short_and_long_stats/total_short_figures.js +34 -14
  63. package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
  64. package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
  65. package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
  66. package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
  67. package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
  68. package/calculations/socialPosts/social_activity_aggregation.js +106 -77
  69. package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
  70. package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
  71. package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
  72. package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
  73. package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
  74. package/calculations/speculators/historical/risk_appetite_change.js +89 -32
  75. package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
  76. package/calculations/speculators/holding_duration_per_asset.js +83 -23
  77. package/calculations/speculators/leverage_per_asset.js +68 -19
  78. package/calculations/speculators/leverage_per_sector.js +86 -25
  79. package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
  80. package/calculations/speculators/speculator_asset_sentiment.js +100 -48
  81. package/calculations/speculators/speculator_danger_zone.js +101 -33
  82. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
  83. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
  84. package/calculations/speculators/stop_loss_per_asset.js +94 -26
  85. package/calculations/speculators/take_profit_per_asset.js +95 -27
  86. package/calculations/speculators/tsl_per_asset.js +77 -23
  87. package/package.json +1 -1
  88. package/utils/price_data_provider.js +142 -142
  89. package/utils/sector_mapping_provider.js +74 -74
@@ -1,46 +1,135 @@
1
1
  /**
2
- * Counts the number of users in profit vs. loss for each asset (from our sample).
3
- * This data is used for extrapolation with the ground truth owner count.
2
+ * @fileoverview Calculation (Pass 1) for asset P&L status.
3
+ *
4
+ * This metric answers: "For each asset, how many users in our
5
+ * sample are in profit versus in loss?"
6
+ *
7
+ * This provides a crowd-wide P&L status for each instrument.
4
8
  */
9
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
10
+
5
11
  class AssetPnlStatus {
6
12
  constructor() {
7
- this.assets = {};
13
+ // We will store { [instrumentId]: { in_profit: Set(), in_loss: Set() } }
14
+ this.assets = new Map();
15
+ this.mappings = null;
16
+ }
17
+
18
+ /**
19
+ * Defines the output schema for this calculation.
20
+ * @returns {object} JSON Schema object
21
+ */
22
+ static getSchema() {
23
+ const userSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "userId": { "type": "string" },
27
+ "pnl": { "type": "number" }
28
+ },
29
+ "required": ["userId", "pnl"]
30
+ };
31
+
32
+ const tickerSchema = {
33
+ "type": "object",
34
+ "description": "P&L status for a specific asset.",
35
+ "properties": {
36
+ "in_profit_count": {
37
+ "type": "number",
38
+ "description": "Count of users currently in profit on this asset."
39
+ },
40
+ "in_loss_count": {
41
+ "type": "number",
42
+ "description": "Count of users currently in loss on this asset."
43
+ },
44
+ "profit_ratio": {
45
+ "type": "number",
46
+ "description": "Percentage of users in profit (In Profit / Total)."
47
+ },
48
+ "users_in_profit": {
49
+ "type": "array",
50
+ "description": "List of users in profit.",
51
+ "items": userSchema
52
+ },
53
+ "users_in_loss": {
54
+ "type": "array",
55
+ "description": "List of users in loss.",
56
+ "items": userSchema
57
+ }
58
+ },
59
+ "required": ["in_profit_count", "in_loss_count", "profit_ratio", "users_in_profit", "users_in_loss"]
60
+ };
61
+
62
+ return {
63
+ "type": "object",
64
+ "description": "Tracks the number of users in profit vs. in loss for each asset.",
65
+ "patternProperties": {
66
+ "^.*$": tickerSchema // Ticker
67
+ },
68
+ "additionalProperties": tickerSchema
69
+ };
8
70
  }
9
71
 
10
- _initAsset(ticker) {
11
- if (!this.assets[ticker]) {
12
- this.assets[ticker] = { profit_count: 0, loss_count: 0 };
72
+ _initAsset(instrumentId) {
73
+ if (!this.assets.has(instrumentId)) {
74
+ this.assets.set(instrumentId, {
75
+ in_profit: new Map(), // Map<userId, pnl>
76
+ in_loss: new Map() // Map<userId, pnl>
77
+ });
13
78
  }
14
79
  }
15
-
16
- process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
17
- const { instrumentMappings } = context;
18
- const processedTickers = new Set(); // Ensure one user is only counted once per ticker
19
-
20
- // FIX: Use the correct portfolio position properties
21
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
22
- if (!positions || !Array.isArray(positions)) return;
23
-
24
- for (const position of positions) {
25
- // FIX: Use the correct PascalCase InstrumentID
26
- const ticker = instrumentMappings[position.InstrumentID];
27
- if (!ticker || processedTickers.has(ticker)) continue;
28
-
29
- this._initAsset(ticker);
30
-
31
- // FIX: Use the correct PascalCase NetProfit
32
- if (position.NetProfit > 0) {
33
- this.assets[ticker].profit_count++;
34
- } else {
35
- this.assets[ticker].loss_count++;
80
+
81
+ process(portfolioData, yesterdayPortfolio, userId) {
82
+ const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
83
+ if (!positions || !Array.isArray(positions)) {
84
+ return;
85
+ }
86
+
87
+ for (const pos of positions) {
88
+ const instrumentId = pos.InstrumentID;
89
+ if (!instrumentId) continue;
90
+
91
+ this._initAsset(instrumentId);
92
+ const asset = this.assets.get(instrumentId);
93
+ const pnl = pos.NetProfit || 0;
94
+
95
+ if (pnl > 0) {
96
+ asset.in_profit.set(userId, pnl);
97
+ } else if (pnl < 0) {
98
+ asset.in_loss.set(userId, pnl);
99
+ }
100
+ }
101
+ }
102
+
103
+ async getResult() {
104
+ if (!this.mappings) {
105
+ this.mappings = await loadInstrumentMappings();
106
+ }
107
+
108
+ const result = {};
109
+ for (const [instrumentId, data] of this.assets.entries()) {
110
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
111
+
112
+ const profitCount = data.in_profit.size;
113
+ const lossCount = data.in_loss.size;
114
+ const total = profitCount + lossCount;
115
+
116
+ if (total > 0) {
117
+ result[ticker] = {
118
+ in_profit_count: profitCount,
119
+ in_loss_count: lossCount,
120
+ profit_ratio: (profitCount / total) * 100,
121
+ // Convert Maps to arrays of objects for the final result
122
+ users_in_profit: Array.from(data.in_profit, ([userId, pnl]) => ({ userId, pnl })),
123
+ users_in_loss: Array.from(data.in_loss, ([userId, pnl]) => ({ userId, pnl }))
124
+ };
36
125
  }
37
- processedTickers.add(ticker);
38
126
  }
127
+ return result;
39
128
  }
40
129
 
41
- getResult() {
42
- // Returns raw counts for frontend extrapolation
43
- return this.assets;
130
+ reset() {
131
+ this.assets.clear();
132
+ this.mappings = null;
44
133
  }
45
134
  }
46
135
 
@@ -1,48 +1,63 @@
1
1
  /**
2
- * @fileoverview Calculates the average PnL (NetProfit %) on a per-user basis, and then averages those user PnLs.
3
- * This metric answers: "What was the average daily PnL for the average user?"
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L.
3
+ *
4
+ * This metric answers: "What was the average daily P&L
5
+ * for the average user?"
6
+ *
7
+ * This is a global health metric for the crowd.
4
8
  */
5
-
6
9
  class AverageDailyPnlAllUsers {
7
10
  constructor() {
8
- this.totalOfUserAveragePnls = 0;
11
+ this.totalPnl = 0;
9
12
  this.userCount = 0;
10
13
  }
11
14
 
12
- process(portfolioData, yesterdayPortfolio, userId, context) {
13
- if (!portfolioData || !portfolioData.AggregatedPositions || portfolioData.AggregatedPositions.length === 0) {
14
- return;
15
- }
16
-
17
- let userTotalPnl = 0;
18
- const positionCount = portfolioData.AggregatedPositions.length;
19
-
20
- for (const position of portfolioData.AggregatedPositions) {
21
- userTotalPnl += position.NetProfit;
22
- }
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 daily P&L across all users.",
23
+ "properties": {
24
+ "average_daily_pnl": {
25
+ "type": "number",
26
+ "description": "The average daily P&L per user (Total P&L / User Count)."
27
+ },
28
+ "total_pnl": {
29
+ "type": "number",
30
+ "description": "The sum of all daily P&L from all users."
31
+ },
32
+ "user_count": {
33
+ "type": "number",
34
+ "description": "The total number of users processed."
35
+ }
36
+ },
37
+ "required": ["average_daily_pnl", "total_pnl", "user_count"]
38
+ };
39
+ }
23
40
 
24
- const userAveragePnl = userTotalPnl / positionCount;
25
- this.totalOfUserAveragePnls += userAveragePnl;
41
+ process(portfolioData) {
42
+ // Use the P&L from the summary, which is for the *day*
43
+ const dailyPnl = portfolioData.Summary?.NetProfit || 0;
44
+
45
+ this.totalPnl += dailyPnl;
26
46
  this.userCount++;
27
47
  }
28
48
 
29
49
  getResult() {
30
- if (this.userCount === 0) {
31
- return {};
32
- }
33
-
34
- // REFACTOR: Perform the final calculation directly.
35
- const average_daily_pnl = this.totalOfUserAveragePnls / this.userCount;
36
-
37
50
  return {
38
- average_daily_pnl: average_daily_pnl
51
+ average_daily_pnl: (this.userCount > 0) ? (this.totalPnl / this.userCount) : 0,
52
+ total_pnl: this.totalPnl,
53
+ user_count: this.userCount
39
54
  };
40
55
  }
41
56
 
42
57
  reset() {
43
- this.totalOfUserAveragePnls = 0;
58
+ this.totalPnl = 0;
44
59
  this.userCount = 0;
45
60
  }
46
61
  }
47
62
 
48
- module.exports = AverageDailyPnlAllUsers;
63
+ module.exports = AverageDailyPnlAllUsers;
@@ -1,49 +1,107 @@
1
1
  /**
2
- * @fileoverview Calculates the average daily PnL (NetProfit %) for each sector.
3
- * This is a simple average, not weighted by position size.
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L per sector.
3
+ *
4
+ * This metric answers: "What was the average daily P&L for each sector?"
5
+ *
6
+ * This helps identify which parts of the market the crowd
7
+ * is performing well or poorly in.
4
8
  */
5
- const { getInstrumentSectorMap } = require('../../utils/sector_mapping_provider');
9
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
10
 
7
11
  class AverageDailyPnlPerSector {
8
12
  constructor() {
9
- this.positions = [];
13
+ // We will store { [sector]: { pnl_sum: 0, user_count: 0 } }
14
+ this.sectors = new Map();
15
+ this.mappings = null;
10
16
  }
11
17
 
12
- process(portfolioData, yesterdayPortfolio, userId, context) {
13
- if (portfolioData && portfolioData.AggregatedPositions) {
14
- this.positions.push(...portfolioData.AggregatedPositions);
15
- }
18
+ /**
19
+ * Defines the output schema for this calculation.
20
+ * @returns {object} JSON Schema object
21
+ */
22
+ static getSchema() {
23
+ const sectorSchema = {
24
+ "type": "object",
25
+ "description": "P&L metrics for a specific sector.",
26
+ "properties": {
27
+ "average_pnl": {
28
+ "type": "number",
29
+ "description": "Average P&L for this sector (Sum / User Count)."
30
+ },
31
+ "pnl_sum": {
32
+ "type": "number",
33
+ "description": "Sum of all P&L for this sector."
34
+ },
35
+ "user_count": {
36
+ "type": "number",
37
+ "description": "Count of users with positions in this sector."
38
+ }
39
+ },
40
+ "required": ["average_pnl", "pnl_sum", "user_count"]
41
+ };
42
+
43
+ return {
44
+ "type": "object",
45
+ "description": "Calculates the average daily P&L per sector.",
46
+ "patternProperties": {
47
+ "^.*$": sectorSchema // Sector name
48
+ },
49
+ "additionalProperties": sectorSchema
50
+ };
16
51
  }
17
52
 
18
- async getResult() {
19
- if (this.positions.length === 0) return {};
53
+ _initSector(sector) {
54
+ if (!this.sectors.has(sector)) {
55
+ this.sectors.set(sector, {
56
+ pnl_sum: 0,
57
+ users: new Set() // Use Set to count unique users
58
+ });
59
+ }
60
+ }
20
61
 
21
- const sectorPnlSum = {};
22
- const positionCountBySector = {};
23
- const sectorMap = await getInstrumentSectorMap();
62
+ process(portfolioData, yesterdayPortfolio, userId, context) {
63
+ // This calculation needs the sector mappings from Pass 1 context
64
+ if (!this.mappings) {
65
+ this.mappings = context.mappings;
66
+ }
24
67
 
25
- for (const position of this.positions) {
26
- const instrumentId = position.InstrumentID;
27
- const sector = sectorMap[instrumentId] || 'N/A';
68
+ const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
69
+ if (!positions || !Array.isArray(positions) || !this.mappings) {
70
+ return;
71
+ }
28
72
 
29
- sectorPnlSum[sector] = (sectorPnlSum[sector] || 0) + position.NetProfit;
30
- positionCountBySector[sector] = (positionCountBySector[sector] || 0) + 1;
73
+ for (const pos of positions) {
74
+ const instrumentId = pos.InstrumentID;
75
+ if (!instrumentId) continue;
76
+
77
+ const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
78
+ this._initSector(sector);
79
+
80
+ const sectorData = this.sectors.get(sector);
81
+ sectorData.pnl_sum += pos.NetProfit || 0;
82
+ sectorData.users.add(userId);
31
83
  }
84
+ }
32
85
 
86
+ getResult() {
33
87
  const result = {};
34
- for (const sector in sectorPnlSum) {
35
- // REFACTOR: Calculate the final average directly.
36
- result[sector] = {
37
- average_pnl: sectorPnlSum[sector] / positionCountBySector[sector]
38
- };
88
+ for (const [sector, data] of this.sectors.entries()) {
89
+ const userCount = data.users.size;
90
+ if (userCount > 0) {
91
+ result[sector] = {
92
+ average_pnl: data.pnl_sum / userCount,
93
+ pnl_sum: data.pnl_sum,
94
+ user_count: userCount
95
+ };
96
+ }
39
97
  }
40
-
41
98
  return result;
42
99
  }
43
100
 
44
101
  reset() {
45
- this.positions = [];
102
+ this.sectors.clear();
103
+ this.mappings = null;
46
104
  }
47
105
  }
48
106
 
49
- module.exports = AverageDailyPnlPerSector;
107
+ module.exports = AverageDailyPnlPerSector;
@@ -1,34 +1,77 @@
1
1
  /**
2
- * @fileoverview Calculates the average daily PnL (NetProfit) for each individual stock.
3
- * This metric answers: "What was the average PnL for a position in this specific stock?"
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L per stock.
3
+ *
4
+ * This metric answers: "What was the average daily P&L for a
5
+ * position in each specific stock?"
4
6
  */
5
7
  const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
8
 
7
9
  class AverageDailyPnlPerStock {
8
10
  constructor() {
9
- this.pnlData = {};
11
+ // We will store { [instrumentId]: { pnl_sum: 0, position_count: 0 } }
12
+ this.assets = new Map();
10
13
  this.mappings = null;
11
14
  }
12
15
 
13
- process(portfolioData, yesterdayPortfolio, userId, context) {
14
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
- if (!positions) {
16
- return;
17
- }
16
+ /**
17
+ * Defines the output schema for this calculation.
18
+ * @returns {object} JSON Schema object
19
+ */
20
+ static getSchema() {
21
+ const tickerSchema = {
22
+ "type": "object",
23
+ "description": "P&L metrics for a specific asset ticker.",
24
+ "properties": {
25
+ "average_pnl": {
26
+ "type": "number",
27
+ "description": "Average P&L per position (Sum / Count)."
28
+ },
29
+ "pnl_sum": {
30
+ "type": "number",
31
+ "description": "Sum of all P&L for this asset."
32
+ },
33
+ "position_count": {
34
+ "type": "number",
35
+ "description": "Count of positions in this asset."
36
+ }
37
+ },
38
+ "required": ["average_pnl", "pnl_sum", "position_count"]
39
+ };
18
40
 
19
- for (const position of positions) {
20
- const instrumentId = position.InstrumentID;
21
- const netProfit = position.NetProfit;
41
+ return {
42
+ "type": "object",
43
+ "description": "Calculates the average daily P&L per stock/asset.",
44
+ "patternProperties": {
45
+ "^.*$": tickerSchema // Ticker
46
+ },
47
+ "additionalProperties": tickerSchema
48
+ };
49
+ }
22
50
 
23
- if (!this.pnlData[instrumentId]) {
24
- this.pnlData[instrumentId] = {
25
- pnl_sum: 0,
26
- position_count: 0
27
- };
28
- }
51
+ _initAsset(instrumentId) {
52
+ if (!this.assets.has(instrumentId)) {
53
+ this.assets.set(instrumentId, {
54
+ pnl_sum: 0,
55
+ position_count: 0
56
+ });
57
+ }
58
+ }
29
59
 
30
- this.pnlData[instrumentId].pnl_sum += netProfit;
31
- this.pnlData[instrumentId].position_count++;
60
+ process(portfolioData) {
61
+ const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
62
+ if (!positions || !Array.isArray(positions)) {
63
+ return;
64
+ }
65
+
66
+ for (const pos of positions) {
67
+ const instrumentId = pos.InstrumentID;
68
+ if (!instrumentId) continue;
69
+
70
+ this._initAsset(instrumentId);
71
+
72
+ const assetData = this.assets.get(instrumentId);
73
+ assetData.pnl_sum += pos.NetProfit || 0;
74
+ assetData.position_count++;
32
75
  }
33
76
  }
34
77
 
@@ -38,25 +81,24 @@ class AverageDailyPnlPerStock {
38
81
  }
39
82
 
40
83
  const result = {};
41
- for (const instrumentId in this.pnlData) {
42
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
43
- const data = this.pnlData[instrumentId];
84
+ for (const [instrumentId, data] of this.assets.entries()) {
85
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
44
86
 
45
- // REFACTOR: Perform the final calculation directly.
46
87
  if (data.position_count > 0) {
47
- result[ticker] = {
48
- average_daily_pnl: data.pnl_sum / data.position_count
49
- };
88
+ result[ticker] = {
89
+ average_pnl: data.pnl_sum / data.position_count,
90
+ pnl_sum: data.pnl_sum,
91
+ position_count: data.position_count
92
+ };
50
93
  }
51
94
  }
52
-
53
95
  return result;
54
96
  }
55
97
 
56
98
  reset() {
57
- this.pnlData = {};
99
+ this.assets.clear();
58
100
  this.mappings = null;
59
101
  }
60
102
  }
61
103
 
62
- module.exports = AverageDailyPnlPerStock;
104
+ module.exports = AverageDailyPnlPerStock;
@@ -1,40 +1,68 @@
1
1
  /**
2
- * @fileoverview Calculates the overall average PnL (NetProfit %) across every single position held by the crowd.
3
- * This gives a high-level view of whether the average position is in profit or loss.
2
+ * @fileoverview Calculation (Pass 1) for average P&L per position.
3
+ *
4
+ * This metric answers: "What was the average P&L across all
5
+ * individual positions held by the crowd today?"
6
+ *
7
+ * This is different from "average P&L per user" as a user
8
+ * can have multiple positions.
4
9
  */
5
-
6
10
  class AverageDailyPositionPnl {
7
11
  constructor() {
8
- this.totalNetProfit = 0;
9
- this.totalPositions = 0;
12
+ this.totalPnl = 0;
13
+ this.positionCount = 0;
10
14
  }
11
15
 
12
- process(portfolioData, yesterdayPortfolio, userId, context) {
13
- if (portfolioData && portfolioData.AggregatedPositions) {
14
- for (const position of portfolioData.AggregatedPositions) {
15
- this.totalNetProfit += position.NetProfit;
16
- this.totalPositions++;
17
- }
18
- }
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 average daily P&L across all individual positions.",
24
+ "properties": {
25
+ "average_pnl": {
26
+ "type": "number",
27
+ "description": "The average daily P&L per position (Total P&L / Position Count)."
28
+ },
29
+ "total_pnl": {
30
+ "type": "number",
31
+ "description": "The sum of all daily P&L from all positions."
32
+ },
33
+ "position_count": {
34
+ "type": "number",
35
+ "description": "The total number of positions processed."
36
+ }
37
+ },
38
+ "required": ["average_pnl", "total_pnl", "position_count"]
39
+ };
19
40
  }
20
41
 
21
- getResult() {
22
- if (this.totalPositions === 0) {
23
- return {};
42
+ process(portfolioData) {
43
+ const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
44
+ if (!positions || !Array.isArray(positions)) {
45
+ return;
24
46
  }
25
47
 
26
- // REFACTOR: Perform the final calculation directly.
27
- const average_daily_position_pnl = this.totalNetProfit / this.totalPositions;
48
+ for (const pos of positions) {
49
+ this.totalPnl += pos.NetProfit || 0;
50
+ this.positionCount++;
51
+ }
52
+ }
28
53
 
54
+ getResult() {
29
55
  return {
30
- average_daily_position_pnl
56
+ average_pnl: (this.positionCount > 0) ? (this.totalPnl / this.positionCount) : 0,
57
+ total_pnl: this.totalPnl,
58
+ position_count: this.positionCount
31
59
  };
32
60
  }
33
61
 
34
62
  reset() {
35
- this.totalNetProfit = 0;
36
- this.totalPositions = 0;
63
+ this.totalPnl = 0;
64
+ this.positionCount = 0;
37
65
  }
38
66
  }
39
67
 
40
- module.exports = AverageDailyPositionPnl;
68
+ module.exports = AverageDailyPositionPnl;