aiden-shared-calculations-unified 1.0.86 → 1.0.87

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 (70) hide show
  1. package/calculations/core/asset-pnl-status.js +36 -106
  2. package/calculations/core/asset-position-size.js +40 -91
  3. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  4. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  5. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  6. package/calculations/core/average-daily-position-pnl.js +19 -49
  7. package/calculations/core/holding-duration-per-asset.js +25 -127
  8. package/calculations/core/instrument-price-change-1d.js +30 -49
  9. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  10. package/calculations/core/long-position-per-stock.js +39 -68
  11. package/calculations/core/overall-holding-duration.js +16 -87
  12. package/calculations/core/overall-profitability-ratio.js +11 -40
  13. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  15. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  16. package/calculations/core/platform-ownership-per-sector.js +45 -96
  17. package/calculations/core/platform-total-positions-held.js +20 -80
  18. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  19. package/calculations/core/price-metrics.js +95 -206
  20. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  21. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  22. package/calculations/core/profitability-skew-per-stock.js +41 -94
  23. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  24. package/calculations/core/sentiment-per-stock.js +24 -77
  25. package/calculations/core/short-position-per-stock.js +35 -43
  26. package/calculations/core/social-activity-aggregation.js +26 -49
  27. package/calculations/core/social-asset-posts-trend.js +38 -94
  28. package/calculations/core/social-event-correlation.js +26 -93
  29. package/calculations/core/social-sentiment-aggregation.js +20 -44
  30. package/calculations/core/social-top-mentioned-words.js +35 -87
  31. package/calculations/core/social-topic-interest-evolution.js +22 -111
  32. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  33. package/calculations/core/social-word-mentions-trend.js +27 -104
  34. package/calculations/core/speculator-asset-sentiment.js +31 -72
  35. package/calculations/core/speculator-danger-zone.js +48 -84
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  40. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  41. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  45. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  46. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  47. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  48. package/calculations/core/total-long-figures.js +16 -31
  49. package/calculations/core/total-long-per-sector.js +39 -61
  50. package/calculations/core/total-short-figures.js +13 -32
  51. package/calculations/core/total-short-per-sector.js +39 -61
  52. package/calculations/core/users-processed.js +11 -46
  53. package/calculations/gauss/cohort-capital-flow.js +54 -173
  54. package/calculations/gauss/cohort-definer.js +77 -163
  55. package/calculations/gauss/daily-dna-filter.js +29 -83
  56. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  57. package/calculations/gem/cohort-momentum-state.js +27 -72
  58. package/calculations/gem/cohort-skill-definition.js +36 -52
  59. package/calculations/gem/platform-conviction-divergence.js +18 -60
  60. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  61. package/calculations/gem/skilled-cohort-flow.js +67 -175
  62. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  63. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  64. package/calculations/helix/helix-contrarian-signal.js +20 -114
  65. package/calculations/helix/herd-consensus-score.js +42 -124
  66. package/calculations/helix/winner-loser-flow.js +36 -118
  67. package/calculations/pyro/risk-appetite-index.js +33 -74
  68. package/calculations/pyro/squeeze-potential.js +30 -87
  69. package/calculations/pyro/volatility-signal.js +33 -78
  70. package/package.json +1 -1
@@ -1,129 +1,78 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for daily ownership per sector.
3
- *
4
- * This is a 'type: "meta"' calculation. It runs ONCE.
5
- * It reads the pre-aggregated 'insights' data source (/daily_instrument_insights/)
6
- * and uses the sector mapping provider to aggregate the total number
7
- * of owners for each sector.
2
+ * @fileoverview Calculation (Pass 1) for sector ownership.
3
+ * REFACTORED: Uses exposure weight.
8
4
  */
9
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
10
-
11
- class DailyOwnershipPerSector {
12
-
13
- // --- STANDARD 2: ADDED ---
5
+ class PlatformOwnershipPerSector {
14
6
  constructor() {
15
- this.result = {};
7
+ this.sectorData = new Map();
8
+ this.sectorMap = null;
16
9
  }
17
10
 
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
21
11
  static getMetadata() {
22
12
  return {
23
- type: 'meta',
24
- rootDataDependencies: ['insights'], // Needs insights doc
13
+ type: 'standard',
14
+ rootDataDependencies: ['portfolio'],
25
15
  isHistorical: false,
26
- userType: 'n/a',
27
- category: 'core_metrics'
16
+ userType: 'all',
17
+ category: 'core'
28
18
  };
29
19
  }
30
20
 
31
- /**
32
- * Statically declare dependencies.
33
- */
34
- static getDependencies() {
35
- return [];
36
- }
21
+ static getDependencies() { return []; }
37
22
 
38
- /**
39
- * Defines the output schema for this calculation.
40
- */
41
23
  static getSchema() {
42
- const sectorSchema = {
24
+ const schema = {
43
25
  "type": "object",
44
- "description": "Aggregated ownership for a single sector.",
45
26
  "properties": {
46
- "total_owners": {
47
- "type": "number",
48
- "description": "The total number of unique owners for all assets in this sector."
49
- }
27
+ "total_exposure_weight": { "type": "number" },
28
+ "position_count": { "type": "number" }
50
29
  },
51
- "required": ["total_owners"]
52
- };
53
-
54
- return {
55
- "type": "object",
56
- "description": "Calculates the total unique owners per sector based on the 'insights' data source.",
57
- "patternProperties": {
58
- "^.*$": sectorSchema // Matches any string key (sector name)
59
- },
60
- "additionalProperties": sectorSchema
30
+ "required": ["total_exposure_weight", "position_count"]
61
31
  };
32
+ return { "type": "object", "patternProperties": { "^.*$": schema } };
62
33
  }
63
34
 
64
- /**
65
- * This is a 'meta' calculation. It runs once.
66
- */
67
- // --- STANDARD 1: UPDATED SIGNATURE ---
68
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
69
- // { [sectorName]: { total_owners: 0 } }
70
- const sectorOwners = new Map();
71
-
72
- // ---
73
- // FIX: The test harness ('worker.js') is our "ground truth".
74
- //
75
- // 1. Get Logger & Mappings:
76
- // 'worker.js' passes the context object as ARGUMENT 4 (named 'config').
77
- // This context *already contains* the sector map.
78
- // ---
79
- const { logger, sectorMapping } = config; // 'config' is Param 4
80
-
81
- // 2. Get the insights document
82
- // 'worker.js' passes the root data object as ARGUMENT 1 (named 'dateStr').
83
- // ---
84
- const rootDataToday = dateStr; // 'dateStr' is Param 1
85
- const insightsDoc = rootDataToday.insights;
86
-
87
- if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
88
- logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found.`);
89
- this.result = {};
90
- return;
35
+ _initSector(sector) {
36
+ if (!this.sectorData.has(sector)) {
37
+ this.sectorData.set(sector, { weight: 0, count: 0 });
91
38
  }
39
+ }
92
40
 
93
- // 3. Iterate over the pre-aggregated array
94
- for (const instrument of insightsDoc.insights) {
95
- const instrumentId = instrument.instrumentId;
96
- const totalOwners = instrument.total; // 'total' is the owner count
41
+ process(context) {
42
+ const { extract } = context.math;
43
+ const { mappings, user } = context;
44
+ if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
97
45
 
98
- if (!instrumentId || typeof totalOwners !== 'number' || totalOwners === 0) {
99
- continue;
100
- }
46
+ const positions = extract.getPositions(user.portfolio.today, user.type);
101
47
 
102
- // --- Use the map from Param 4 ---
103
- const sectorName = sectorMapping[instrumentId] || 'N/A';
48
+ for (const pos of positions) {
49
+ const instId = extract.getInstrumentId(pos);
50
+ if (!instId) continue;
104
51
 
105
- // Initialize if new
106
- if (!sectorOwners.has(sectorName)) {
107
- sectorOwners.set(sectorName, { total_owners: 0 });
108
- }
52
+ const sector = this.sectorMap[instId] || 'Unknown';
53
+ const weight = extract.getPositionWeight(pos, user.type);
109
54
 
110
- // Add this instrument's owners to the sector's total
111
- sectorOwners.get(sectorName).total_owners += totalOwners;
55
+ this._initSector(sector);
56
+ const data = this.sectorData.get(sector);
57
+ data.weight += weight;
58
+ data.count++;
112
59
  }
113
-
114
- // --- STANDARD 2: SET STATE, DO NOT RETURN ---
115
- this.result = Object.fromEntries(sectorOwners);
116
60
  }
117
61
 
118
- // --- STANDARD 2: ADDED ---
119
- async getResult(fetchedDependencies) {
120
- return this.result;
62
+ async getResult() {
63
+ const result = {};
64
+ for (const [sector, data] of this.sectorData.entries()) {
65
+ result[sector] = {
66
+ total_exposure_weight: data.weight,
67
+ position_count: data.count
68
+ };
69
+ }
70
+ return result;
121
71
  }
122
72
 
123
- // --- STANDARD 2: ADDED ---
124
73
  reset() {
125
- this.result = {};
74
+ this.sectorData.clear();
75
+ this.sectorMap = null;
126
76
  }
127
77
  }
128
-
129
- module.exports = DailyOwnershipPerSector;
78
+ module.exports = PlatformOwnershipPerSector;
@@ -1,105 +1,45 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for total positions held.
3
- *
4
- * REFACTOR: This is now a 'type: "meta"' calculation. It runs ONCE.
5
- * It reads the pre-aggregated 'insights' data source and sums the
6
- * 'total' field from all instruments to get the platform-wide total.
2
+ * @fileoverview Calculation (Pass 1) for total positions count.
3
+ * REFACTORED: Uses context.math.extract.
7
4
  */
8
- class DailyTotalPositionsHeld {
9
-
10
- // --- STANDARD 2: ADDED ---
5
+ class PlatformTotalPositionsHeld {
11
6
  constructor() {
12
- this.result = {};
7
+ this.totalPositions = 0;
13
8
  }
14
9
 
15
- /**
16
- * Statically defines all metadata for the manifest builder.
17
- */
18
10
  static getMetadata() {
19
11
  return {
20
- type: 'meta',
21
- rootDataDependencies: ['insights'], // Needs insights doc
12
+ type: 'standard',
13
+ rootDataDependencies: ['portfolio'],
22
14
  isHistorical: false,
23
- userType: 'n/a',
15
+ userType: 'all',
24
16
  category: 'core_metrics'
25
17
  };
26
18
  }
27
19
 
28
- /**
29
- * Statically declare dependencies.
30
- */
31
- static getDependencies() {
32
- return [];
33
- }
20
+ static getDependencies() { return []; }
34
21
 
35
- /**
36
- * Defines the output schema for this calculation.
37
- */
38
22
  static getSchema() {
39
23
  return {
40
24
  "type": "object",
41
- "description": "Calculates the total number of positions held across all users and instruments.",
42
25
  "properties": {
43
- "totalPositions": {
44
- "type": "number",
45
- "description": "The total aggregated count of all positions."
46
- }
26
+ "total_positions_count": { "type": "number" }
47
27
  },
48
- "required": ["totalPositions"]
28
+ "required": ["total_positions_count"]
49
29
  };
50
30
  }
51
31
 
52
- /**
53
- * This is a 'meta' calculation. It runs once.
54
- */
55
- // --- STANDARD 1: UPDATED SIGNATURE ---
56
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
57
- let totalPositions = 0;
58
-
59
- // ---
60
- // FIX: The test harness ('worker.js') is our "ground truth".
61
- //
62
- // 1. Get Logger:
63
- // 'worker.js' passes the context object as ARGUMENT 4 (named 'config').
64
- // ---
65
- const { logger } = config; // 'config' is Param 4
66
-
67
- // ---
68
- // 2. Get Data:
69
- // 'worker.js' passes the root data object as ARGUMENT 1 (named 'dateStr').
70
- // ---
71
- const rootDataToday = dateStr; // 'dateStr' is Param 1
72
- const insightsDoc = rootDataToday.insights;
73
-
74
- if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
75
- logger.log('WARN', `[daily-total-positions-held] No 'insights' data found.`);
76
- // --- STANDARD 2: SET STATE, DO NOT RETURN ---
77
- this.result = { totalPositions: 0 };
78
- return;
79
- }
80
-
81
- for (const instrument of insightsDoc.insights) {
82
- // The 'total' field from the doc is the total # of positions for that instrument
83
- if (typeof instrument.total === 'number') {
84
- totalPositions += instrument.total;
85
- }
86
- }
87
-
88
- // --- STANDARD 2: SET STATE, DO NOT RETURN ---
89
- this.result = {
90
- totalPositions: totalPositions
91
- };
92
- }
93
-
94
- // --- STANDARD 2: ADDED ---
95
- async getResult(fetchedDependencies) {
96
- return this.result;
32
+ process(context) {
33
+ const { extract } = context.math;
34
+ const { user } = context;
35
+ const positions = extract.getPositions(user.portfolio.today, user.type);
36
+ this.totalPositions += positions.length;
97
37
  }
98
38
 
99
- // --- STANDARD 2: ADDED ---
100
- reset() {
101
- this.result = {};
39
+ getResult() {
40
+ return { total_positions_count: this.totalPositions };
102
41
  }
103
- }
104
42
 
105
- module.exports = DailyTotalPositionsHeld;
43
+ reset() { this.totalPositions = 0; }
44
+ }
45
+ module.exports = PlatformTotalPositionsHeld;
@@ -1,44 +1,23 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 1) for P&L distribution per stock.
3
- *
4
- * This metric tracks the distribution of P&L percentages for all open
5
- * positions, grouped by instrument.
6
- *
7
- * REFACTOR: This calculation now aggregates the distribution into
8
- * predefined buckets on the server-side, returning a chart-ready
9
- * histogram object instead of raw arrays.
10
- *
11
- * --- FIX: 2025-11-12 ---
12
- * This calculation is a dependency for crowd_sharpe_ratio_proxy,
13
- * which requires sum, sumSq, and count for variance calculations.
14
- * This file has been updated to provide *both* the histogram
15
- * and a 'stats' object containing these required values.
16
- * ---------------------
3
+ * REFACTORED: Aggregates distribution of P&L Percentages.
17
4
  */
18
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
19
-
20
- // Define the P&L percentage buckets for the histogram
21
5
  const BUCKETS = [
22
- { label: 'loss_heavy', min: -Infinity, max: -50 }, // > 50% loss
23
- { label: 'loss_medium', min: -50, max: -25 }, // 25% to 50% loss
24
- { label: 'loss_light', min: -25, max: 0 }, // 0% to 25% loss
25
- { label: 'gain_light', min: 0, max: 25 }, // 0% to 25% gain
26
- { label: 'gain_medium', min: 25, max: 50 }, // 25% to 50% gain
27
- { label: 'gain_heavy', min: 50, max: 100 }, // 50% to 100% gain
28
- { label: 'gain_extreme', min: 100, max: Infinity } // > 100% gain
6
+ { label: 'loss_heavy', min: -Infinity, max: -50 },
7
+ { label: 'loss_medium', min: -50, max: -25 },
8
+ { label: 'loss_light', min: -25, max: 0 },
9
+ { label: 'gain_light', min: 0, max: 25 },
10
+ { label: 'gain_medium', min: 25, max: 50 },
11
+ { label: 'gain_heavy', min: 50, max: 100 },
12
+ { label: 'gain_extreme', min: 100, max: Infinity }
29
13
  ];
30
14
 
31
15
  class PnlDistributionPerStock {
32
16
  constructor() {
33
- // We will store { [instrumentId]: [pnlPercent1, pnlPercent2, ...] }
34
17
  this.pnlMap = new Map();
35
- // --- STANDARD 0: RENAMED ---
36
18
  this.tickerMap = null;
37
19
  }
38
20
 
39
- /**
40
- * Statically defines all metadata for the manifest builder.
41
- */
42
21
  static getMetadata() {
43
22
  return {
44
23
  type: 'standard',
@@ -49,58 +28,18 @@ class PnlDistributionPerStock {
49
28
  };
50
29
  }
51
30
 
52
- /**
53
- * Statically declare dependencies.
54
- */
55
- static getDependencies() {
56
- return [];
57
- }
31
+ static getDependencies() { return []; }
58
32
 
59
- /**
60
- * Defines the output schema for this calculation.
61
- */
62
33
  static getSchema() {
63
34
  const bucketSchema = {
64
35
  "type": "object",
65
- "description": "Histogram and stats of P&L distribution for a single asset.",
66
36
  "properties": {
67
- "histogram": {
68
- "type": "object",
69
- "description": "Histogram of P&L distribution.",
70
- "properties": {
71
- "loss_heavy": { "type": "number", "description": "Count of positions with > 50% loss" },
72
- "loss_medium": { "type": "number", "description": "Count of positions with 25-50% loss" },
73
- "loss_light": { "type": "number", "description": "Count of positions with 0-25% loss" },
74
- "gain_light": { "type": "number", "description": "Count of positions with 0-25% gain" },
75
- "gain_medium": { "type": "number", "description": "Count of positions with 25-50% gain" },
76
- "gain_heavy": { "type": "number", "description": "Count of positions with 50-100% gain" },
77
- "gain_extreme": { "type": "number", "description": "Count of positions with > 100% gain" },
78
- "total_positions": { "type": "number", "description": "Total positions counted" }
79
- },
80
- "required": ["total_positions"]
81
- },
82
- "stats": {
83
- "type": "object",
84
- "description": "Raw statistics needed for variance/Sharpe calculations.",
85
- "properties": {
86
- "sum": { "type": "number", "description": "Sum of all P&L percentages" },
87
- "sumSq": { "type": "number", "description": "Sum of all squared P&L percentages" },
88
- "count": { "type": "number", "description": "Total count of positions" }
89
- },
90
- "required": ["sum", "sumSq", "count"]
91
- }
37
+ "histogram": { "type": "object" },
38
+ "stats": { "type": "object", "required": ["sum", "sumSq", "count"] }
92
39
  },
93
40
  "required": ["histogram", "stats"]
94
41
  };
95
-
96
- return {
97
- "type": "object",
98
- "description": "Calculates a histogram and raw stats of P&L percentage distribution for all open positions, per asset.",
99
- "patternProperties": {
100
- "^.*$": bucketSchema // Ticker
101
- },
102
- "additionalProperties": bucketSchema
103
- };
42
+ return { "type": "object", "patternProperties": { "^.*$": bucketSchema } };
104
43
  }
105
44
 
106
45
  _initAsset(instrumentId) {
@@ -109,74 +48,37 @@ class PnlDistributionPerStock {
109
48
  }
110
49
  }
111
50
 
112
- // --- STANDARD 0: UPDATED SIGNATURE ---
113
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
114
- // --- STANDARD 0: ADDED ---
115
- if (!this.tickerMap) {
116
- this.tickerMap = context.instrumentToTicker;
117
- }
51
+ process(context) {
52
+ const { extract } = context.math;
53
+ const { mappings, user } = context;
54
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
118
55
 
119
- const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
120
- if (!positions || !Array.isArray(positions)) {
121
- return;
122
- }
56
+ const positions = extract.getPositions(user.portfolio.today, user.type);
123
57
 
124
58
  for (const pos of positions) {
125
- const instrumentId = pos.InstrumentID;
126
-
127
- // ---
128
- // FIX 1: The schema file (schema.md) shows the P&L field
129
- // is 'NetProfit', not 'ProfitRate'.
130
- // ---
131
- const pnlPercent = pos.NetProfit;
132
-
133
- if (!instrumentId || typeof pnlPercent !== 'number') {
134
- continue;
135
- }
59
+ const instId = extract.getInstrumentId(pos);
60
+ if (!instId) continue;
136
61
 
137
- this._initAsset(instrumentId);
62
+ const pnlPercent = extract.getNetProfit(pos);
138
63
 
139
- // ---
140
- // FIX 2: The 'NetProfit' field is already a full percentage
141
- // (e.g., 34.09), not a decimal (e.g., 0.34).
142
- // Do not multiply by 100.
143
- // ---
144
- this.pnlMap.get(instrumentId).push(pnlPercent);
64
+ this._initAsset(instId);
65
+ this.pnlMap.get(instId).push(pnlPercent);
145
66
  }
146
67
  }
147
68
 
148
- /**
149
- * REFACTOR: This method now calculates the distribution on the server.
150
- * --- FIX: 2025-11-12 ---
151
- * Also calculates and returns sum, sumSq, and count.
152
- */
153
69
  async getResult() {
154
- // --- STANDARD 0: REMOVED forbidden data load ---
155
-
156
- // Failsafe check
157
- if (!this.tickerMap) {
158
- return {}; // process() must run first
159
- }
70
+ if (!this.tickerMap) return {};
160
71
 
161
72
  const result = {};
162
-
163
- for (const [instrumentId, pnlValues] of this.pnlMap.entries()) {
164
- // --- STANDARD 0: SIMPLIFIED ---
165
- const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
73
+ for (const [instId, pnlValues] of this.pnlMap.entries()) {
74
+ const ticker = this.tickerMap[instId] || `id_${instId}`;
166
75
  const count = pnlValues.length;
167
76
 
168
- if (count === 0) {
169
- continue;
170
- }
77
+ if (count === 0) continue;
171
78
 
172
79
  const histogram = {
173
- loss_heavy: 0,
174
- loss_medium: 0,
175
- loss_light: 0,
176
- gain_light: 0,
177
- gain_medium: 0,
178
- gain_heavy: 0,
179
- gain_extreme: 0,
80
+ loss_heavy: 0, loss_medium: 0, loss_light: 0,
81
+ gain_light: 0, gain_medium: 0, gain_heavy: 0, gain_extreme: 0,
180
82
  total_positions: count
181
83
  };
182
84
 
@@ -195,15 +97,9 @@ class PnlDistributionPerStock {
195
97
  }
196
98
  }
197
99
 
198
- const stats = {
199
- sum: sum,
200
- sumSq: sumSq,
201
- count: count
202
- };
203
-
204
100
  result[ticker] = {
205
101
  histogram: histogram,
206
- stats: stats
102
+ stats: { sum, sumSq, count }
207
103
  };
208
104
  }
209
105
  return result;
@@ -211,9 +107,7 @@ class PnlDistributionPerStock {
211
107
 
212
108
  reset() {
213
109
  this.pnlMap.clear();
214
- // --- STANDARD 0: RENAMED ---
215
110
  this.tickerMap = null;
216
111
  }
217
112
  }
218
-
219
113
  module.exports = PnlDistributionPerStock;