aiden-shared-calculations-unified 1.0.86 → 1.0.88

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 (80) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +96 -0
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
  3. package/calculations/core/asset-cost-basis-profile.js +127 -0
  4. package/calculations/core/asset-pnl-status.js +36 -106
  5. package/calculations/core/asset-position-size.js +40 -91
  6. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  7. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  8. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  9. package/calculations/core/average-daily-position-pnl.js +19 -49
  10. package/calculations/core/holding-duration-per-asset.js +25 -127
  11. package/calculations/core/instrument-price-change-1d.js +30 -49
  12. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  13. package/calculations/core/long-position-per-stock.js +39 -68
  14. package/calculations/core/overall-holding-duration.js +16 -87
  15. package/calculations/core/overall-profitability-ratio.js +11 -40
  16. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  17. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  18. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  19. package/calculations/core/platform-ownership-per-sector.js +45 -96
  20. package/calculations/core/platform-total-positions-held.js +20 -80
  21. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  22. package/calculations/core/price-metrics.js +95 -206
  23. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  24. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  25. package/calculations/core/profitability-skew-per-stock.js +41 -94
  26. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  27. package/calculations/core/sentiment-per-stock.js +24 -77
  28. package/calculations/core/short-position-per-stock.js +35 -43
  29. package/calculations/core/social-activity-aggregation.js +26 -49
  30. package/calculations/core/social-asset-posts-trend.js +38 -94
  31. package/calculations/core/social-event-correlation.js +26 -93
  32. package/calculations/core/social-sentiment-aggregation.js +20 -44
  33. package/calculations/core/social-top-mentioned-words.js +35 -87
  34. package/calculations/core/social-topic-interest-evolution.js +22 -111
  35. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  36. package/calculations/core/social-word-mentions-trend.js +27 -104
  37. package/calculations/core/speculator-asset-sentiment.js +31 -72
  38. package/calculations/core/speculator-danger-zone.js +48 -84
  39. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  40. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  41. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  42. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  43. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  44. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  45. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  46. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  47. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  48. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  49. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  50. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  51. package/calculations/core/test..js +0 -0
  52. package/calculations/core/total-long-figures.js +16 -31
  53. package/calculations/core/total-long-per-sector.js +39 -61
  54. package/calculations/core/total-short-figures.js +13 -32
  55. package/calculations/core/total-short-per-sector.js +39 -61
  56. package/calculations/core/users-processed.js +11 -46
  57. package/calculations/gauss/cohort-capital-flow.js +54 -173
  58. package/calculations/gauss/cohort-definer.js +77 -163
  59. package/calculations/gauss/daily-dna-filter.js +29 -83
  60. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  61. package/calculations/gem/cohort-momentum-state.js +27 -72
  62. package/calculations/gem/cohort-skill-definition.js +36 -52
  63. package/calculations/gem/platform-conviction-divergence.js +18 -60
  64. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  65. package/calculations/gem/skilled-cohort-flow.js +67 -175
  66. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  67. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  68. package/calculations/ghost-book/cost-basis-density.js +79 -0
  69. package/calculations/ghost-book/liquidity-vacuum.js +52 -0
  70. package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
  71. package/calculations/helix/helix-contrarian-signal.js +20 -114
  72. package/calculations/helix/herd-consensus-score.js +42 -124
  73. package/calculations/helix/winner-loser-flow.js +36 -118
  74. package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
  75. package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
  76. package/calculations/predicative-alpha/mimetic-latency.js +124 -0
  77. package/calculations/pyro/risk-appetite-index.js +33 -74
  78. package/calculations/pyro/squeeze-potential.js +30 -87
  79. package/calculations/pyro/volatility-signal.js +33 -78
  80. package/package.json +1 -1
@@ -1,145 +1,97 @@
1
1
  /**
2
2
  * @fileoverview Core Metric (Pass 2)
3
- *
4
- * This 'standard' calculation streams all user portfolios
5
- * and calculates the *average position size in $USD* for
6
- * all holders of each asset, bucketed by ticker and sector.
3
+ * REFACTORED: Calculates average portfolio weight (%) per asset.
7
4
  */
8
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
9
-
10
5
  class AssetPositionSize {
11
6
  constructor() {
12
- // { [ticker]: { invested_sum: 0, user_count: 0 } }
13
7
  this.tickerBuckets = new Map();
14
- // { [sector]: { invested_sum: 0, user_count: 0 } }
15
8
  this.sectorBuckets = new Map();
16
-
17
- // --- STANDARD 0: RENAMED ---
18
- this.tickerMap = null;
19
- this.sectorMap = null;
20
-
21
9
  }
22
10
 
23
- /** Statically defines metadata */
24
11
  static getMetadata() {
25
12
  return {
26
13
  type: 'standard',
27
14
  rootDataDependencies: ['portfolio'],
28
- isHistorical: false, // Reads today's portfolio
15
+ isHistorical: false,
29
16
  userType: 'all',
30
17
  category: 'core'
31
18
  };
32
19
  }
33
20
 
34
- /** Statically declare dependencies */
35
- static getDependencies() {
36
- return [];
37
- }
21
+ static getDependencies() { return []; }
38
22
 
39
- /**
40
- * Defines the output schema for this calculation.
41
- */
42
23
  static getSchema() {
43
24
  const schema = {
44
25
  "type": "object",
45
26
  "properties": {
46
- "avg_position_usd": { "type": "number" },
47
- "total_invested_usd": { "type": "number" },
27
+ "avg_position_weight": { "type": "number", "description": "Average % of portfolio allocated to this asset." },
28
+ "total_exposure_weight": { "type": "number", "description": "Sum of all user allocation percentages." },
48
29
  "user_count": { "type": "number" }
49
30
  },
50
- "required": ["avg_position_usd", "total_invested_usd", "user_count"]
31
+ "required": ["avg_position_weight", "total_exposure_weight", "user_count"]
51
32
  };
52
-
53
33
  return {
54
34
  "type": "object",
55
- "description": "Calculates the avg. $USD position size for all holders per asset/sector.",
56
35
  "properties": {
57
- "by_ticker": {
58
- "type": "object",
59
- "patternProperties": { "^.*$": schema },
60
- "additionalProperties": schema
61
- },
62
- "by_sector": {
63
- "type": "object",
64
- "patternProperties": { "^.*$": schema },
65
- "additionalProperties": schema
66
- }
67
- },
68
- "required": ["by_ticker", "by_sector"]
36
+ "by_ticker": { "type": "object", "patternProperties": { "^.*$": schema } },
37
+ "by_sector": { "type": "object", "patternProperties": { "^.*$": schema } }
38
+ }
69
39
  };
70
40
  }
71
41
 
72
42
  _init(map, key) {
73
- if (!map.has(key)) {
74
- map.set(key, { invested_sum: 0, user_count: 0 });
75
- }
43
+ if (!map.has(key)) map.set(key, { weight_sum: 0, user_count: 0 });
76
44
  }
77
45
 
78
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
79
- // --- STANDARD 0: FIXED ---
80
- if (!this.tickerMap) {
81
- this.tickerMap = context.instrumentToTicker;
82
- this.sectorMap = context.sectorMapping;
83
- }
84
-
85
- if (!this.tickerMap || !this.sectorMap) {
86
- return; // Failsafe if context is missing maps
87
- }
88
-
89
- const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
90
- if (!positions || positions.length === 0) {
91
- return;
92
- }
46
+ process(context) {
47
+ const { extract } = context.math;
48
+ const { mappings, user } = context;
49
+
50
+ const positions = extract.getPositions(user.portfolio.today, user.type);
93
51
 
94
52
  for (const pos of positions) {
95
- const invested = pos.Invested || 0;
96
- if (!pos.InstrumentID || invested <= 0) continue;
53
+ const weight = extract.getPositionWeight(pos, user.type);
54
+ if (weight <= 0) continue;
55
+
56
+ const instId = extract.getInstrumentId(pos);
57
+ if (!instId) continue;
97
58
 
98
- // --- STANDARD 0: SIMPLIFIED ---
99
- const ticker = this.tickerMap[pos.InstrumentID];
100
- const sector = this.sectorMap[pos.InstrumentID];
59
+ const ticker = mappings.instrumentToTicker[instId];
60
+ const sector = mappings.sectorMapping[instId];
101
61
 
102
62
  if (ticker) {
103
63
  this._init(this.tickerBuckets, ticker);
104
64
  const asset = this.tickerBuckets.get(ticker);
105
- asset.invested_sum += invested;
106
- asset.user_count++; // Count each position
65
+ asset.weight_sum += weight;
66
+ asset.user_count++;
107
67
  }
108
68
 
109
69
  if (sector) {
110
70
  this._init(this.sectorBuckets, sector);
111
71
  const sec = this.sectorBuckets.get(sector);
112
- sec.invested_sum += invested;
113
- sec.user_count++; // Count each position
72
+ sec.weight_sum += weight;
73
+ sec.user_count++;
114
74
  }
115
75
  }
116
76
  }
117
77
 
118
78
  async getResult() {
119
- const result = {
120
- by_ticker: {},
121
- by_sector: {}
122
- };
123
-
124
- for (const [ticker, data] of this.tickerBuckets.entries()) {
125
- if (data.user_count > 0) {
126
- result.by_ticker[ticker] = {
127
- avg_position_usd: data.invested_sum / data.user_count,
128
- total_invested_usd: data.invested_sum,
129
- user_count: data.user_count
130
- };
131
- }
132
- }
79
+ const result = { by_ticker: {}, by_sector: {} };
133
80
 
134
- for (const [sector, data] of this.sectorBuckets.entries()) {
135
- if (data.user_count > 0) {
136
- result.by_sector[sector] = {
137
- avg_position_usd: data.invested_sum / data.user_count,
138
- total_invested_usd: data.invested_sum,
139
- user_count: data.user_count
140
- };
81
+ const buildResult = (map, out) => {
82
+ for (const [key, data] of map.entries()) {
83
+ if (data.user_count > 0) {
84
+ out[key] = {
85
+ avg_position_weight: data.weight_sum / data.user_count,
86
+ total_exposure_weight: data.weight_sum,
87
+ user_count: data.user_count
88
+ };
89
+ }
141
90
  }
142
- }
91
+ };
92
+
93
+ buildResult(this.tickerBuckets, result.by_ticker);
94
+ buildResult(this.sectorBuckets, result.by_sector);
143
95
 
144
96
  return result;
145
97
  }
@@ -147,9 +99,6 @@ class AssetPositionSize {
147
99
  reset() {
148
100
  this.tickerBuckets.clear();
149
101
  this.sectorBuckets.clear();
150
- // --- STANDARD 0: RENAMED ---
151
- this.tickerMap = null;
152
- this.sectorMap = null;
153
102
  }
154
103
  }
155
104
  module.exports = AssetPositionSize;
@@ -1,97 +1,58 @@
1
1
  /**
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.
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L %.
3
+ * REFACTORED: Uses standardized P&L % access.
8
4
  */
9
5
  class AverageDailyPnlAllUsers {
10
6
  constructor() {
11
- this.totalPnl = 0;
7
+ this.totalPnlPct = 0;
12
8
  this.userCount = 0;
13
9
  }
14
10
 
15
- // --- NEW ---
16
- /**
17
- * Statically defines all metadata for the manifest builder.
18
- */
19
11
  static getMetadata() {
20
12
  return {
21
13
  type: 'standard',
22
- rootDataDependencies: ['portfolio'], // Needs portfolio.Summary
14
+ rootDataDependencies: ['portfolio'],
23
15
  isHistorical: false,
24
16
  userType: 'all',
25
17
  category: 'core_pnl'
26
18
  };
27
19
  }
28
20
 
29
- // --- NEW ---
30
- /**
31
- * Statically declare dependencies.
32
- */
33
- static getDependencies() {
34
- return [];
35
- }
21
+ static getDependencies() { return []; }
36
22
 
37
- /**
38
- * Defines the output schema for this calculation.
39
- * @returns {object} JSON Schema object
40
- */
41
23
  static getSchema() {
42
24
  return {
43
25
  "type": "object",
44
- "description": "Calculates the average daily P&L across all users.",
45
26
  "properties": {
46
- "average_daily_pnl": {
47
- "type": "number",
48
- "description": "The average daily P&L per user (Total P&L / User Count)."
49
- },
50
- "total_pnl": {
51
- "type": "number",
52
- "description": "The sum of all daily P&L from all users."
53
- },
54
- "user_count": {
55
- "type": "number",
56
- "description": "The total number of users processed."
57
- }
27
+ "average_daily_pnl_pct": { "type": "number" },
28
+ "total_aggregate_pnl_pct": { "type": "number", "description": "Sum of all user daily P&L percentages." },
29
+ "user_count": { "type": "number" }
58
30
  },
59
- "required": ["average_daily_pnl", "total_pnl", "user_count"]
31
+ "required": ["average_daily_pnl_pct", "total_aggregate_pnl_pct", "user_count"]
60
32
  };
61
33
  }
62
34
 
63
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
64
- let dailyPnl = 0;
65
-
66
- // Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
67
- if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
68
-
69
- dailyPnl = todayPortfolio.NetProfit;
35
+ process(context) {
36
+ const { extract } = context.math;
37
+ const { user } = context;
70
38
 
71
- // Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
72
- } else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
73
-
74
- // Sum the P&L by calculating (Value - Invested) from the aggregates
75
- dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
76
- return sum + (agg.Value - agg.Invested);
77
- }, 0);
78
-
79
- }
39
+ // extract.getPortfolioDailyPnl returns the daily P&L as a percentage for the user
40
+ const dailyPnl = extract.getPortfolioDailyPnl(user.portfolio.today, user.type);
80
41
 
81
- this.totalPnl += dailyPnl;
42
+ this.totalPnlPct += dailyPnl;
82
43
  this.userCount++;
83
44
  }
84
45
 
85
46
  getResult() {
86
47
  return {
87
- average_daily_pnl: (this.userCount > 0) ? (this.totalPnl / this.userCount) : 0,
88
- total_pnl: this.totalPnl,
48
+ average_daily_pnl_pct: (this.userCount > 0) ? (this.totalPnlPct / this.userCount) : 0,
49
+ total_aggregate_pnl_pct: this.totalPnlPct,
89
50
  user_count: this.userCount
90
51
  };
91
52
  }
92
53
 
93
54
  reset() {
94
- this.totalPnl = 0;
55
+ this.totalPnlPct = 0;
95
56
  this.userCount = 0;
96
57
  }
97
58
  }
@@ -1,136 +1,89 @@
1
1
  /**
2
- * @fileoverview Core Metric (Pass 2)
3
- *
4
- * This 'standard' calculation streams all user portfolios
5
- * and calculates the average daily P&L (in %) for all
6
- * users, bucketed by sector.
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L % per sector.
3
+ * REFACTORED: Aggregates P&L percentages.
7
4
  */
8
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
9
-
10
5
  class AverageDailyPnlPerSector {
11
6
  constructor() {
12
- // { [sector]: { pnl_sum: 0, count: 0 } }
13
- this.sectorBuckets = new Map();
14
-
15
- // --- STANDARD 0: RENAMED ---
7
+ this.sectorData = new Map();
16
8
  this.sectorMap = null;
17
9
  }
18
10
 
19
- /** Statically defines metadata */
20
11
  static getMetadata() {
21
12
  return {
22
13
  type: 'standard',
23
14
  rootDataDependencies: ['portfolio'],
24
- isHistorical: false, // Reads today's portfolio
15
+ isHistorical: false,
25
16
  userType: 'all',
26
- category: 'core'
17
+ category: 'core_pnl'
27
18
  };
28
19
  }
29
20
 
30
- /** Statically declare dependencies */
31
- static getDependencies() {
32
- return [];
33
- }
21
+ static getDependencies() { return []; }
34
22
 
35
- /**
36
- * Defines the output schema for this calculation.
37
- */
38
23
  static getSchema() {
39
- const schema = {
24
+ const sectorSchema = {
40
25
  "type": "object",
41
26
  "properties": {
42
27
  "avg_daily_pnl_pct": { "type": "number" },
43
- "total_pnl_pct": { "type": "number" },
28
+ "aggregate_pnl_pct_sum": { "type": "number", "description": "Sum of P&L % points for sector." },
44
29
  "user_count": { "type": "number" }
45
30
  },
46
- "required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
47
- };
48
-
49
- return {
50
- "type": "object",
51
- "description": "Calculates the avg. daily P&L % for all users, bucketed by sector.",
52
- "patternProperties": { "^.*$": schema },
53
- "additionalProperties": schema
31
+ "required": ["avg_daily_pnl_pct", "aggregate_pnl_pct_sum", "user_count"]
54
32
  };
55
- }
56
-
57
- _init(map, key) {
58
- if (!map.has(key)) {
59
- map.set(key, { pnl_sum: 0, count: 0 });
60
- }
33
+ return { "type": "object", "patternProperties": { "^.*$": sectorSchema } };
61
34
  }
62
35
 
63
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
64
- // --- STANDARD 0: FIXED ---
65
- if (!this.sectorMap) {
66
- this.sectorMap = context.sectorMapping;
67
- }
68
-
69
- if (!this.sectorMap) {
70
- return; // Failsafe if context is missing map
36
+ _initSector(sector) {
37
+ if (!this.sectorData.has(sector)) {
38
+ this.sectorData.set(sector, { total_pnl_pct: 0, count: 0 });
71
39
  }
72
-
73
- let dailyPnl = 0;
40
+ }
74
41
 
75
- // Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
76
- if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
77
-
78
- dailyPnl = todayPortfolio.NetProfit;
42
+ process(context) {
43
+ const { extract } = context.math;
44
+ const { mappings, user } = context;
45
+ if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
79
46
 
80
- // Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
81
- } else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
82
-
83
- // Sum the P&L by calculating (Value - Invested) from the aggregates
84
- dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
85
- return sum + (agg.Value - agg.Invested);
86
- }, 0);
87
-
88
- }
89
- if (dailyPnl === 0) return; // No P&L
90
-
91
- const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
92
- if (!positions || positions.length === 0) {
93
- return;
94
- }
47
+ const positions = extract.getPositions(user.portfolio.today, user.type);
48
+ const userSectorPnL = new Map();
95
49
 
96
- // We only care about sectors this user *holds*
97
- const heldSectors = new Set();
98
50
  for (const pos of positions) {
99
- if (!pos.InstrumentID) continue;
100
- // --- STANDARD 0: SIMPLIFIED ---
101
- const sector = this.sectorMap[pos.InstrumentID];
102
- if (sector) {
103
- heldSectors.add(sector);
104
- }
51
+ const instId = extract.getInstrumentId(pos);
52
+ if (!instId) continue;
53
+
54
+ const sector = this.sectorMap[instId] || 'Unknown';
55
+ // NetProfit is the % profit relative to invested capital for that position
56
+ // We sum these to get the user's "sector performance points"
57
+ const pnlPct = extract.getNetProfit(pos);
58
+
59
+ if (!userSectorPnL.has(sector)) userSectorPnL.set(sector, 0);
60
+ userSectorPnL.set(sector, userSectorPnL.get(sector) + pnlPct);
105
61
  }
106
-
107
- for (const sector of heldSectors) {
108
- this._init(this.sectorBuckets, sector);
109
- const sec = this.sectorBuckets.get(sector);
110
- sec.pnl_sum += dailyPnl;
111
- sec.count++;
62
+
63
+ for (const [sector, pnl] of userSectorPnL.entries()) {
64
+ this._initSector(sector);
65
+ const data = this.sectorData.get(sector);
66
+ data.total_pnl_pct += pnl;
67
+ data.count++;
112
68
  }
113
69
  }
114
70
 
115
71
  async getResult() {
116
72
  const result = {};
117
-
118
- for (const [sector, data] of this.sectorBuckets.entries()) {
73
+ for (const [sector, data] of this.sectorData.entries()) {
119
74
  if (data.count > 0) {
120
75
  result[sector] = {
121
- avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
122
- total_pnl_pct: data.pnl_sum * 100,
76
+ avg_daily_pnl_pct: data.total_pnl_pct / data.count,
77
+ aggregate_pnl_pct_sum: data.total_pnl_pct,
123
78
  user_count: data.count
124
79
  };
125
80
  }
126
81
  }
127
-
128
82
  return result;
129
83
  }
130
-
84
+
131
85
  reset() {
132
- this.sectorBuckets.clear();
133
- // --- STANDARD 0: RENAMED ---
86
+ this.sectorData.clear();
134
87
  this.sectorMap = null;
135
88
  }
136
89
  }
@@ -1,136 +1,83 @@
1
1
  /**
2
- * @fileoverview Core Metric (Pass 2)
3
- *
4
- * This 'standard' calculation streams all user portfolios
5
- * and calculates the average daily P&L (in %) for all
6
- * users, bucketed by stock ticker.
2
+ * @fileoverview Calculation (Pass 1) for average daily P&L % per stock.
3
+ * REFACTORED: Aggregates P&L percentages.
7
4
  */
8
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
9
-
10
5
  class AverageDailyPnlPerStock {
11
6
  constructor() {
12
- // { [ticker]: { pnl_sum: 0, count: 0 } }
13
- this.tickerBuckets = new Map();
14
-
15
- // --- STANDARD 0: RENAMED ---
7
+ this.stockData = new Map();
16
8
  this.tickerMap = null;
17
9
  }
18
10
 
19
- /** Statically defines metadata */
20
11
  static getMetadata() {
21
12
  return {
22
13
  type: 'standard',
23
14
  rootDataDependencies: ['portfolio'],
24
- isHistorical: false, // Reads today's portfolio
15
+ isHistorical: false,
25
16
  userType: 'all',
26
- category: 'core'
17
+ category: 'core_pnl'
27
18
  };
28
19
  }
29
20
 
30
- /** Statically declare dependencies */
31
- static getDependencies() {
32
- return [];
33
- }
21
+ static getDependencies() { return []; }
34
22
 
35
- /**
36
- * Defines the output schema for this calculation.
37
- */
38
23
  static getSchema() {
39
- const schema = {
24
+ const stockSchema = {
40
25
  "type": "object",
41
26
  "properties": {
42
27
  "avg_daily_pnl_pct": { "type": "number" },
43
- "total_pnl_pct": { "type": "number" },
44
- "user_count": { "type": "number" }
28
+ "aggregate_pnl_pct_sum": { "type": "number" },
29
+ "position_count": { "type": "number" }
45
30
  },
46
- "required": ["avg_daily_pnl_pct", "total_pnl_pct", "user_count"]
31
+ "required": ["avg_daily_pnl_pct", "aggregate_pnl_pct_sum", "position_count"]
47
32
  };
48
-
49
- return {
50
- "type": "object",
51
- "description": "Calculates the avg. daily P&L % for all users, bucketed by stock.",
52
- "patternProperties": { "^.*$": schema },
53
- "additionalProperties": schema
54
- };
55
- }
56
-
57
- _init(map, key) {
58
- if (!map.has(key)) {
59
- map.set(key, { pnl_sum: 0, count: 0 });
60
- }
33
+ return { "type": "object", "patternProperties": { "^.*$": stockSchema } };
61
34
  }
62
35
 
63
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
64
- // --- STANDARD 0: FIXED ---
65
- if (!this.tickerMap) {
66
- this.tickerMap = context.instrumentToTicker;
67
- }
68
-
69
- if (!this.tickerMap) {
70
- return; // Failsafe if context is missing map
36
+ _initStock(instId) {
37
+ if (!this.stockData.has(instId)) {
38
+ this.stockData.set(instId, { total_pnl_pct: 0, count: 0 });
71
39
  }
72
-
73
- let dailyPnl = 0;
40
+ }
74
41
 
75
- // Check if it's a SPECULATOR portfolio (it has a root 'NetProfit' and 'PublicPositions')
76
- if (todayPortfolio.NetProfit !== undefined && todayPortfolio.PublicPositions) {
77
-
78
- dailyPnl = todayPortfolio.NetProfit;
42
+ process(context) {
43
+ const { extract } = context.math;
44
+ const { mappings, user } = context;
45
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
79
46
 
80
- // Check if it's a NORMAL user portfolio (it has 'AggregatedPositionsByInstrumentTypeID')
81
- } else if (todayPortfolio.AggregatedPositionsByInstrumentTypeID) {
82
-
83
- // Sum the P&L by calculating (Value - Invested) from the aggregates
84
- dailyPnl = todayPortfolio.AggregatedPositionsByInstrumentTypeID.reduce((sum, agg) => {
85
- return sum + (agg.Value - agg.Invested);
86
- }, 0);
87
-
88
- }
89
- if (dailyPnl === 0) return; // No P&L
90
-
91
- const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
92
- if (!positions || positions.length === 0) {
93
- return;
94
- }
47
+ const positions = extract.getPositions(user.portfolio.today, user.type);
95
48
 
96
- // We only care about tickers this user *holds*
97
- const heldTickers = new Set();
98
49
  for (const pos of positions) {
99
- if (!pos.InstrumentID) continue;
100
- // --- STANDARD 0: SIMPLIFIED ---
101
- const ticker = this.tickerMap[pos.InstrumentID];
102
- if (ticker) {
103
- heldTickers.add(ticker);
104
- }
105
- }
106
-
107
- for (const ticker of heldTickers) {
108
- this._init(this.tickerBuckets, ticker);
109
- const asset = this.tickerBuckets.get(ticker);
110
- asset.pnl_sum += dailyPnl;
111
- asset.count++;
50
+ const instId = extract.getInstrumentId(pos);
51
+ if (!instId) continue;
52
+
53
+ // NetProfit is %
54
+ const pnlPct = extract.getNetProfit(pos);
55
+
56
+ this._initStock(instId);
57
+ const data = this.stockData.get(instId);
58
+ data.total_pnl_pct += pnlPct;
59
+ data.count++;
112
60
  }
113
61
  }
114
62
 
115
63
  async getResult() {
64
+ if (!this.tickerMap) return {};
116
65
  const result = {};
117
-
118
- for (const [ticker, data] of this.tickerBuckets.entries()) {
66
+ for (const [instId, data] of this.stockData.entries()) {
67
+ const ticker = this.tickerMap[instId] || `id_${instId}`;
119
68
  if (data.count > 0) {
120
69
  result[ticker] = {
121
- avg_daily_pnl_pct: (data.pnl_sum / data.count) * 100, // Convert decimal to %
122
- total_pnl_pct: data.pnl_sum * 100,
123
- user_count: data.count
70
+ avg_daily_pnl_pct: data.total_pnl_pct / data.count,
71
+ aggregate_pnl_pct_sum: data.total_pnl_pct,
72
+ position_count: data.count
124
73
  };
125
74
  }
126
75
  }
127
-
128
76
  return result;
129
77
  }
130
-
78
+
131
79
  reset() {
132
- this.tickerBuckets.clear();
133
- // --- STANDARD 0: RENAMED ---
80
+ this.stockData.clear();
134
81
  this.tickerMap = null;
135
82
  }
136
83
  }