aiden-shared-calculations-unified 1.0.82 → 1.0.84

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 (71) hide show
  1. package/calculations/core/asset-pnl-status.js +122 -104
  2. package/calculations/core/asset-position-size.js +110 -73
  3. package/calculations/core/average-daily-pnl-all-users.js +17 -3
  4. package/calculations/core/average-daily-pnl-per-sector.js +83 -75
  5. package/calculations/core/average-daily-pnl-per-stock.js +84 -73
  6. package/calculations/core/average-daily-position-pnl.js +2 -2
  7. package/calculations/core/holding-duration-per-asset.js +24 -23
  8. package/calculations/core/instrument-price-change-1d.js +72 -82
  9. package/calculations/core/instrument-price-momentum-20d.js +66 -100
  10. package/calculations/core/long-position-per-stock.js +21 -13
  11. package/calculations/core/overall-holding-duration.js +8 -3
  12. package/calculations/core/overall-profitability-ratio.js +2 -2
  13. package/calculations/core/platform-buy-sell-sentiment.js +75 -22
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
  15. package/calculations/core/platform-daily-ownership-delta.js +39 -15
  16. package/calculations/core/platform-ownership-per-sector.js +38 -18
  17. package/calculations/core/platform-total-positions-held.js +36 -14
  18. package/calculations/core/pnl-distribution-per-stock.js +39 -36
  19. package/calculations/core/price-metrics.js +70 -172
  20. package/calculations/core/profitability-ratio-per-sector.js +23 -29
  21. package/calculations/core/profitability-ratio-per-stock.js +20 -13
  22. package/calculations/core/profitability-skew-per-stock.js +20 -13
  23. package/calculations/core/profitable-and-unprofitable-status.js +34 -10
  24. package/calculations/core/sentiment-per-stock.js +20 -9
  25. package/calculations/core/short-position-per-stock.js +23 -37
  26. package/calculations/core/social-activity-aggregation.js +41 -115
  27. package/calculations/core/social-asset-posts-trend.js +77 -94
  28. package/calculations/core/social-event-correlation.js +87 -106
  29. package/calculations/core/social-sentiment-aggregation.js +56 -138
  30. package/calculations/core/social-top-mentioned-words.js +74 -106
  31. package/calculations/core/social-topic-interest-evolution.js +94 -94
  32. package/calculations/core/social-topic-sentiment-matrix.js +90 -74
  33. package/calculations/core/social-word-mentions-trend.js +92 -106
  34. package/calculations/core/speculator-asset-sentiment.js +63 -92
  35. package/calculations/core/speculator-danger-zone.js +77 -90
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
  40. package/calculations/core/speculator-leverage-per-asset.js +62 -57
  41. package/calculations/core/speculator-leverage-per-sector.js +53 -65
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
  45. package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
  46. package/calculations/core/speculator-take-profit-per-asset.js +45 -69
  47. package/calculations/core/speculator-tsl-per-asset.js +42 -49
  48. package/calculations/core/total-long-figures.js +19 -19
  49. package/calculations/core/total-long-per-sector.js +39 -36
  50. package/calculations/core/total-short-figures.js +19 -19
  51. package/calculations/core/total-short-per-sector.js +39 -36
  52. package/calculations/core/users-processed.js +52 -25
  53. package/calculations/gauss/cohort-capital-flow.js +38 -29
  54. package/calculations/gauss/cohort-definer.js +17 -25
  55. package/calculations/gauss/daily-dna-filter.js +10 -4
  56. package/calculations/gauss/gauss-divergence-signal.js +28 -6
  57. package/calculations/gem/cohort-momentum-state.js +113 -92
  58. package/calculations/gem/cohort-skill-definition.js +23 -53
  59. package/calculations/gem/platform-conviction-divergence.js +62 -116
  60. package/calculations/gem/quant-skill-alpha-signal.js +107 -123
  61. package/calculations/gem/skilled-cohort-flow.js +178 -167
  62. package/calculations/gem/skilled-unskilled-divergence.js +73 -113
  63. package/calculations/gem/unskilled-cohort-flow.js +176 -166
  64. package/calculations/helix/helix-contrarian-signal.js +91 -83
  65. package/calculations/helix/herd-consensus-score.js +135 -97
  66. package/calculations/helix/winner-loser-flow.js +14 -14
  67. package/calculations/pyro/risk-appetite-index.js +121 -123
  68. package/calculations/pyro/squeeze-potential.js +93 -125
  69. package/calculations/pyro/volatility-signal.js +109 -97
  70. package/package.json +9 -9
  71. package/README.MD +0 -78
@@ -7,17 +7,17 @@
7
7
  * This is different from 'daily_asset_activity' because it counts
8
8
  * *positions*, not *unique users*.
9
9
  */
10
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
10
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
11
11
 
12
12
 
13
13
  class DailyBoughtVsSoldCount {
14
14
  constructor() {
15
15
  // We will store { [instrumentId]: { new: 0, closed: 0 } }
16
16
  this.assetActivity = new Map();
17
- this.mappings = null;
17
+ // --- STANDARD 0: RENAMED ---
18
+ this.tickerMap = null;
18
19
  }
19
20
 
20
- // --- NEW ---
21
21
  /**
22
22
  * Statically defines all metadata for the manifest builder.
23
23
  */
@@ -31,7 +31,6 @@ class DailyBoughtVsSoldCount {
31
31
  };
32
32
  }
33
33
 
34
- // --- NEW ---
35
34
  /**
36
35
  * Statically declare dependencies.
37
36
  */
@@ -41,7 +40,6 @@ class DailyBoughtVsSoldCount {
41
40
 
42
41
  /**
43
42
  * Defines the output schema for this calculation.
44
- * @returns {object} JSON Schema object
45
43
  */
46
44
  static getSchema() {
47
45
  const tickerSchema = {
@@ -93,7 +91,13 @@ class DailyBoughtVsSoldCount {
93
91
  return new Map(positions.map(p => [p.PositionID, p.InstrumentID]).filter(p => p[0] && p[1]));
94
92
  }
95
93
 
96
- process(todayPortfolio, yesterdayPortfolio) {
94
+ // --- STANDARD 0: UPDATED SIGNATURE (added context) ---
95
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
96
+ // --- STANDARD 0: ADDED ---
97
+ if (!this.tickerMap) {
98
+ this.tickerMap = context.instrumentToTicker;
99
+ }
100
+
97
101
  if (!todayPortfolio || !yesterdayPortfolio) {
98
102
  return;
99
103
  }
@@ -119,13 +123,17 @@ class DailyBoughtVsSoldCount {
119
123
  }
120
124
 
121
125
  async getResult() {
122
- if (!this.mappings) {
123
- this.mappings = await loadInstrumentMappings();
126
+ // --- STANDARD 0: REMOVED forbidden data load ---
127
+
128
+ // Failsafe check
129
+ if (!this.tickerMap) {
130
+ return {}; // process() must run first
124
131
  }
125
132
 
126
133
  const result = {};
127
134
  for (const [instrumentId, data] of this.assetActivity.entries()) {
128
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
135
+ // --- STANDARD 0: SIMPLIFIED ---
136
+ const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
129
137
 
130
138
  const openCount = data.new;
131
139
  const closeCount = data.closed;
@@ -143,7 +151,8 @@ class DailyBoughtVsSoldCount {
143
151
 
144
152
  reset() {
145
153
  this.assetActivity.clear();
146
- this.mappings = null;
154
+ // --- STANDARD 0: RENAMED ---
155
+ this.tickerMap = null;
147
156
  }
148
157
  }
149
158
 
@@ -8,12 +8,16 @@
8
8
  * It runs ONCE, loads today's and yesterday's pre-aggregated 'insights' docs,
9
9
  * and calculates the delta based on the 'total' field.
10
10
  */
11
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
12
12
 
13
13
 
14
14
  class DailyOwnershipDelta {
15
15
 
16
- // --- NEW ---
16
+ // --- STANDARD 2: ADDED ---
17
+ constructor() {
18
+ this.result = {};
19
+ }
20
+
17
21
  /**
18
22
  * Statically defines all metadata for the manifest builder.
19
23
  */
@@ -27,7 +31,6 @@ class DailyOwnershipDelta {
27
31
  };
28
32
  }
29
33
 
30
- // --- NEW ---
31
34
  /**
32
35
  * Statically declare dependencies.
33
36
  */
@@ -37,7 +40,6 @@ class DailyOwnershipDelta {
37
40
 
38
41
  /**
39
42
  * Defines the output schema for this calculation.
40
- * @returns {object} JSON Schema object
41
43
  */
42
44
  static getSchema() {
43
45
  const tickerSchema = {
@@ -76,20 +78,33 @@ class DailyOwnershipDelta {
76
78
 
77
79
  /**
78
80
  * This is a 'meta' and 'historical' calculation. It runs once.
79
- * @param {string} dateStr - The date string 'YYYY-MM-DD'.
80
- * @param {object} todayRootData - Root data for today. We expect todayRootData.insights.
81
- * @param {object} yesterdayRootData - Root data for yesterday. We expect yesterdayRootData.insights.
82
- * @returns {Promise<object>} The calculation result.
83
81
  */
84
- async process(dateStr, todayRootData, yesterdayRootData) {
85
- const mappings = await loadInstrumentMappings();
82
+ // --- STANDARD 1: UPDATED SIGNATURE ---
83
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
84
+
85
+ // ---
86
+ // FIX: The test harness ('worker.js') is now our "ground truth".
87
+ //
88
+ // 1. Get Ticker Mappings:
89
+ // 'worker.js' passes the context object as ARGUMENT 4 (named 'config').
90
+ // This context *already contains* the ticker map.
91
+ // ---
92
+ const { instrumentToTicker } = config; // 'config' is Param 4
86
93
 
87
94
  const todayOwners = new Map();
88
95
  const yesterdayOwners = new Map();
89
96
  const allInstrumentIds = new Set();
90
97
 
98
+ // ---
99
+ // 2. Get Data:
100
+ // 'worker.js' passes the root data object as ARGUMENT 1 (named 'dateStr').
101
+ // ---
102
+ const rootDataToday = dateStr; // 'dateStr' is Param 1
103
+
104
+ const todayDoc = rootDataToday?.insights;
105
+ const yesterdayDoc = rootDataToday?.yesterdayInsights;
106
+
91
107
  // 1. Process today's insights doc
92
- const todayDoc = todayRootData.insights;
93
108
  if (todayDoc && Array.isArray(todayDoc.insights)) {
94
109
  for (const instrument of todayDoc.insights) {
95
110
  const id = instrument.instrumentId;
@@ -100,7 +115,6 @@ class DailyOwnershipDelta {
100
115
  }
101
116
 
102
117
  // 2. Process yesterday's insights doc
103
- const yesterdayDoc = yesterdayRootData.insights;
104
118
  if (yesterdayDoc && Array.isArray(yesterdayDoc.insights)) {
105
119
  for (const instrument of yesterdayDoc.insights) {
106
120
  const id = instrument.instrumentId;
@@ -113,7 +127,8 @@ class DailyOwnershipDelta {
113
127
  // 3. Calculate deltas
114
128
  const result = {};
115
129
  for (const instrumentId of allInstrumentIds) {
116
- const ticker = mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
130
+ // --- Use the map from Param 4 ---
131
+ const ticker = instrumentToTicker[instrumentId] || `id_${instrumentId}`;
117
132
 
118
133
  const tOwners = todayOwners.get(instrumentId) || 0;
119
134
  const yOwners = yesterdayOwners.get(instrumentId) || 0;
@@ -128,10 +143,19 @@ class DailyOwnershipDelta {
128
143
  }
129
144
  }
130
145
 
131
- return result;
146
+ // --- STANDARD 2: SET STATE, DO NOT RETURN ---
147
+ this.result = result;
148
+ }
149
+
150
+ // --- STANDARD 2: ADDED ---
151
+ async getResult(fetchedDependencies) {
152
+ return this.result;
132
153
  }
133
154
 
134
- // No getResult() or reset() methods are needed
155
+ // --- STANDARD 2: ADDED ---
156
+ reset() {
157
+ this.result = {};
158
+ }
135
159
  }
136
160
 
137
161
  module.exports = DailyOwnershipDelta;
@@ -6,11 +6,15 @@
6
6
  * and uses the sector mapping provider to aggregate the total number
7
7
  * of owners for each sector.
8
8
  */
9
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
9
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
10
10
 
11
11
  class DailyOwnershipPerSector {
12
12
 
13
- // --- NEW ---
13
+ // --- STANDARD 2: ADDED ---
14
+ constructor() {
15
+ this.result = {};
16
+ }
17
+
14
18
  /**
15
19
  * Statically defines all metadata for the manifest builder.
16
20
  */
@@ -24,7 +28,6 @@ class DailyOwnershipPerSector {
24
28
  };
25
29
  }
26
30
 
27
- // --- NEW ---
28
31
  /**
29
32
  * Statically declare dependencies.
30
33
  */
@@ -34,7 +37,6 @@ class DailyOwnershipPerSector {
34
37
 
35
38
  /**
36
39
  * Defines the output schema for this calculation.
37
- * @returns {object} JSON Schema object
38
40
  */
39
41
  static getSchema() {
40
42
  const sectorSchema = {
@@ -61,23 +63,31 @@ class DailyOwnershipPerSector {
61
63
 
62
64
  /**
63
65
  * This is a 'meta' calculation. It runs once.
64
- * @param {string} dateStr - The date string 'YYYY-MM-DD'.
65
- * @param {object} rootData - The root data object. We expect rootData.insights.
66
- * @param {object} dependencies - The shared dependencies (e.g., logger).
67
- * @returns {Promise<object>} The calculation result.
68
66
  */
69
- async process(dateStr, rootData, dependencies) {
67
+ // --- STANDARD 1: UPDATED SIGNATURE ---
68
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
70
69
  // { [sectorName]: { total_owners: 0 } }
71
70
  const sectorOwners = new Map();
72
71
 
73
- // 1. Load mappings
74
- const mappings = await loadInstrumentMappings();
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
75
80
 
76
81
  // 2. Get the insights document
77
- const insightsDoc = rootData.insights;
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
+
78
87
  if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
79
- // dependencies.logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found for ${dateStr}.`); TODO : This is broken, returns log undefined
80
- return {};
88
+ logger.log('WARN', `[daily-ownership-per-sector] No 'insights' data found.`);
89
+ this.result = {};
90
+ return;
81
91
  }
82
92
 
83
93
  // 3. Iterate over the pre-aggregated array
@@ -89,8 +99,8 @@ class DailyOwnershipPerSector {
89
99
  continue;
90
100
  }
91
101
 
92
- // Find the sector for this instrument
93
- const sectorName = mappings.instrumentToSectorName[instrumentId] || 'N/A';
102
+ // --- Use the map from Param 4 ---
103
+ const sectorName = sectorMapping[instrumentId] || 'N/A';
94
104
 
95
105
  // Initialize if new
96
106
  if (!sectorOwners.has(sectorName)) {
@@ -101,8 +111,18 @@ class DailyOwnershipPerSector {
101
111
  sectorOwners.get(sectorName).total_owners += totalOwners;
102
112
  }
103
113
 
104
- // Convert Map to plain object for Firestore
105
- return Object.fromEntries(sectorOwners);
114
+ // --- STANDARD 2: SET STATE, DO NOT RETURN ---
115
+ this.result = Object.fromEntries(sectorOwners);
116
+ }
117
+
118
+ // --- STANDARD 2: ADDED ---
119
+ async getResult(fetchedDependencies) {
120
+ return this.result;
121
+ }
122
+
123
+ // --- STANDARD 2: ADDED ---
124
+ reset() {
125
+ this.result = {};
106
126
  }
107
127
  }
108
128
 
@@ -7,7 +7,11 @@
7
7
  */
8
8
  class DailyTotalPositionsHeld {
9
9
 
10
- // --- NEW ---
10
+ // --- STANDARD 2: ADDED ---
11
+ constructor() {
12
+ this.result = {};
13
+ }
14
+
11
15
  /**
12
16
  * Statically defines all metadata for the manifest builder.
13
17
  */
@@ -21,7 +25,6 @@ class DailyTotalPositionsHeld {
21
25
  };
22
26
  }
23
27
 
24
- // --- NEW ---
25
28
  /**
26
29
  * Statically declare dependencies.
27
30
  */
@@ -31,7 +34,6 @@ class DailyTotalPositionsHeld {
31
34
 
32
35
  /**
33
36
  * Defines the output schema for this calculation.
34
- * @returns {object} JSON Schema object
35
37
  */
36
38
  static getSchema() {
37
39
  return {
@@ -49,20 +51,31 @@ class DailyTotalPositionsHeld {
49
51
 
50
52
  /**
51
53
  * This is a 'meta' calculation. It runs once.
52
- * @param {string} dateStr - The date string 'YYYY-MM-DD'.
53
- * @param {object} rootData - The root data object. We expect rootData.insights.
54
- * @param {object} dependencies - The shared dependencies (e.g., logger).
55
- * @returns {object} The calculation result.
56
54
  */
57
- process(dateStr, rootData, dependencies) {
55
+ // --- STANDARD 1: UPDATED SIGNATURE ---
56
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
58
57
  let totalPositions = 0;
59
58
 
60
- // rootData.insights contains the document from /daily_instrument_insights/
61
- const insightsDoc = rootData.insights;
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;
62
73
 
63
74
  if (!insightsDoc || !Array.isArray(insightsDoc.insights)) {
64
- dependencies.logger.log('WARN', `[daily-total-positions-held] No 'insights' data found for ${dateStr}.`);
65
- return { totalPositions: 0 };
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;
66
79
  }
67
80
 
68
81
  for (const instrument of insightsDoc.insights) {
@@ -72,12 +85,21 @@ class DailyTotalPositionsHeld {
72
85
  }
73
86
  }
74
87
 
75
- return {
88
+ // --- STANDARD 2: SET STATE, DO NOT RETURN ---
89
+ this.result = {
76
90
  totalPositions: totalPositions
77
91
  };
78
92
  }
79
93
 
80
- // No getResult() or reset() methods are needed for a 'meta' calculation
94
+ // --- STANDARD 2: ADDED ---
95
+ async getResult(fetchedDependencies) {
96
+ return this.result;
97
+ }
98
+
99
+ // --- STANDARD 2: ADDED ---
100
+ reset() {
101
+ this.result = {};
102
+ }
81
103
  }
82
104
 
83
105
  module.exports = DailyTotalPositionsHeld;
@@ -15,16 +15,16 @@
15
15
  * and a 'stats' object containing these required values.
16
16
  * ---------------------
17
17
  */
18
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
18
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
19
19
 
20
20
  // Define the P&L percentage buckets for the histogram
21
21
  const BUCKETS = [
22
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
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
28
  { label: 'gain_extreme', min: 100, max: Infinity } // > 100% gain
29
29
  ];
30
30
 
@@ -32,10 +32,10 @@ class PnlDistributionPerStock {
32
32
  constructor() {
33
33
  // We will store { [instrumentId]: [pnlPercent1, pnlPercent2, ...] }
34
34
  this.pnlMap = new Map();
35
- this.mappings = null;
35
+ // --- STANDARD 0: RENAMED ---
36
+ this.tickerMap = null;
36
37
  }
37
38
 
38
- // --- NEW ---
39
39
  /**
40
40
  * Statically defines all metadata for the manifest builder.
41
41
  */
@@ -49,7 +49,6 @@ class PnlDistributionPerStock {
49
49
  };
50
50
  }
51
51
 
52
- // --- NEW ---
53
52
  /**
54
53
  * Statically declare dependencies.
55
54
  */
@@ -59,11 +58,6 @@ class PnlDistributionPerStock {
59
58
 
60
59
  /**
61
60
  * Defines the output schema for this calculation.
62
- * REFACTOR: Schema now describes the server-calculated histogram.
63
- *
64
- * --- FIX: 2025-11-12 ---
65
- * Added 'stats' object to the schema to support downstream
66
- * meta-calculations like crowd_sharpe_ratio_proxy.
67
61
  */
68
62
  static getSchema() {
69
63
  const bucketSchema = {
@@ -115,52 +109,66 @@ class PnlDistributionPerStock {
115
109
  }
116
110
  }
117
111
 
118
- // --- REFACTORED ---
119
- // Simplified signature
120
- process(portfolioData) {
121
- const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
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
+ }
118
+
119
+ const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
122
120
  if (!positions || !Array.isArray(positions)) {
123
121
  return;
124
122
  }
125
123
 
126
124
  for (const pos of positions) {
127
125
  const instrumentId = pos.InstrumentID;
128
- const pnlPercent = pos.ProfitRate; // ProfitRate is P&L % (e.g., 0.5 = +50%)
129
126
 
130
- // Ensure we have valid data
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
+
131
133
  if (!instrumentId || typeof pnlPercent !== 'number') {
132
134
  continue;
133
135
  }
134
136
 
135
137
  this._initAsset(instrumentId);
136
- // Convert ProfitRate (0.5) to percentage (50)
137
- this.pnlMap.get(instrumentId).push(pnlPercent * 100);
138
+
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);
138
145
  }
139
146
  }
140
147
 
141
148
  /**
142
149
  * REFACTOR: This method now calculates the distribution on the server.
143
- * It transforms the raw P&L arrays into histogram bucket counts.
144
- *
145
150
  * --- FIX: 2025-11-12 ---
146
151
  * Also calculates and returns sum, sumSq, and count.
147
152
  */
148
153
  async getResult() {
149
- if (!this.mappings) {
150
- this.mappings = await loadInstrumentMappings();
154
+ // --- STANDARD 0: REMOVED forbidden data load ---
155
+
156
+ // Failsafe check
157
+ if (!this.tickerMap) {
158
+ return {}; // process() must run first
151
159
  }
152
160
 
153
161
  const result = {};
154
162
 
155
163
  for (const [instrumentId, pnlValues] of this.pnlMap.entries()) {
156
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
164
+ // --- STANDARD 0: SIMPLIFIED ---
165
+ const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
157
166
  const count = pnlValues.length;
158
167
 
159
168
  if (count === 0) {
160
169
  continue;
161
170
  }
162
171
 
163
- // 1. Initialize the histogram object for this ticker
164
172
  const histogram = {
165
173
  loss_heavy: 0,
166
174
  loss_medium: 0,
@@ -172,33 +180,27 @@ class PnlDistributionPerStock {
172
180
  total_positions: count
173
181
  };
174
182
 
175
- // --- FIX: Initialize stats ---
176
183
  let sum = 0;
177
184
  let sumSq = 0;
178
185
 
179
- // 2. Process all P&L values into buckets and calculate stats
180
186
  for (const pnl of pnlValues) {
181
- // --- FIX: Add to stats ---
182
187
  sum += pnl;
183
188
  sumSq += (pnl * pnl);
184
189
 
185
- // Add to histogram
186
190
  for (const bucket of BUCKETS) {
187
191
  if (pnl >= bucket.min && pnl < bucket.max) {
188
192
  histogram[bucket.label]++;
189
- break; // Move to the next P&L value
193
+ break;
190
194
  }
191
195
  }
192
196
  }
193
197
 
194
- // --- FIX: Create stats object ---
195
198
  const stats = {
196
199
  sum: sum,
197
200
  sumSq: sumSq,
198
201
  count: count
199
202
  };
200
203
 
201
- // 3. Add the aggregated histogram and stats to the final result
202
204
  result[ticker] = {
203
205
  histogram: histogram,
204
206
  stats: stats
@@ -209,7 +211,8 @@ class PnlDistributionPerStock {
209
211
 
210
212
  reset() {
211
213
  this.pnlMap.clear();
212
- this.mappings = null;
214
+ // --- STANDARD 0: RENAMED ---
215
+ this.tickerMap = null;
213
216
  }
214
217
  }
215
218