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
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @fileoverview CORE Product Line (Pass 1 - Meta)
3
+ * Calculates annualized volatility using the new priceExtractor.
4
+ */
5
+ class AssetVolatilityEstimator {
6
+ constructor() {
7
+ this.result = {};
8
+ }
9
+
10
+ static getMetadata() {
11
+ return {
12
+ type: 'meta',
13
+ rootDataDependencies: ['price'],
14
+ isHistorical: false,
15
+ userType: 'n/a',
16
+ category: 'market_stats'
17
+ };
18
+ }
19
+
20
+ static getDependencies() { return []; }
21
+
22
+ static getSchema() {
23
+ const tickerSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "volatility_30d": { "type": "number" },
27
+ "last_price": { "type": "number" },
28
+ "data_points": { "type": "number" }
29
+ },
30
+ "required": ["volatility_30d"]
31
+ };
32
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
33
+ }
34
+
35
+ process(context) {
36
+ // FIXED: Destructure 'mappings' to resolve real tickers
37
+ const { math, prices, mappings } = context;
38
+
39
+ // FIXED: Destructure 'priceExtractor' directly (matches worker.js injection)
40
+ const { compute, priceExtractor } = math;
41
+
42
+ // 1. Get ALL histories properly sorted and parsed
43
+ const allHistories = priceExtractor.getAllHistories(prices);
44
+
45
+ for (const [key, candles] of allHistories.entries()) {
46
+ // RESOLUTION FIX:
47
+ // 'key' is likely an index string ("0") because mock data is an array.
48
+ // We must resolve this to the real Ticker Symbol for the result map.
49
+ let ticker = key;
50
+
51
+ if (prices.history && prices.history[key] && prices.history[key].instrumentId) {
52
+ const instId = prices.history[key].instrumentId;
53
+ if (mappings && mappings.instrumentToTicker && mappings.instrumentToTicker[instId]) {
54
+ ticker = mappings.instrumentToTicker[instId];
55
+ }
56
+ }
57
+
58
+ if (candles.length < 10) continue;
59
+
60
+ // 2. Calculate Log Returns
61
+ const logReturns = [];
62
+ let lastPrice = 0;
63
+
64
+ for (let i = 1; i < candles.length; i++) {
65
+ const prev = candles[i-1].price;
66
+ const curr = candles[i].price;
67
+
68
+ if (prev > 0 && curr > 0) {
69
+ logReturns.push(Math.log(curr / prev));
70
+ lastPrice = curr;
71
+ }
72
+ }
73
+
74
+ // 3. Filter 30-Day Lookback
75
+ const LOOKBACK = 30;
76
+ const relevantReturns = logReturns.slice(-LOOKBACK);
77
+
78
+ if (relevantReturns.length < 5) continue;
79
+
80
+ // 4. Calculate Stats
81
+ const stdDev = compute.standardDeviation(relevantReturns);
82
+ const annualizedVol = stdDev * Math.sqrt(365);
83
+
84
+ this.result[ticker] = {
85
+ volatility_30d: annualizedVol,
86
+ last_price: lastPrice,
87
+ data_points: relevantReturns.length
88
+ };
89
+ }
90
+ }
91
+
92
+ async getResult() { return this.result; }
93
+ reset() { this.result = {}; }
94
+ }
95
+
96
+ module.exports = AssetVolatilityEstimator;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @fileoverview CORE Product Line (Pass 2 - Standard)
3
+ * Calculates capitulation risk using DYNAMIC volatility from asset-volatility-estimator.
4
+ * Relies on 'schema.md' definitions for portfolio extraction via MathPrimitives.
5
+ */
6
+
7
+ class RetailCapitulationRiskForecast {
8
+ constructor() {
9
+ this.assetRiskProfiles = new Map();
10
+ this.tickerMap = null;
11
+
12
+ // FIX: Use a container object (like the Maps in other calculations)
13
+ // This allows us to MUTATE 'deps.mathLib' instead of RE-ASSIGNING 'this.mathLib'
14
+ this.deps = { mathLib: null };
15
+ }
16
+
17
+ static getMetadata() {
18
+ return {
19
+ type: 'standard',
20
+ rootDataDependencies: ['portfolio', 'history'],
21
+ isHistorical: false,
22
+ userType: 'all',
23
+ category: 'risk_models'
24
+ };
25
+ }
26
+
27
+ static getDependencies() {
28
+ return ['asset-volatility-estimator'];
29
+ }
30
+
31
+ static getSchema() {
32
+ const tickerSchema = {
33
+ "type": "object",
34
+ "properties": {
35
+ "capitulation_probability": { "type": "number" },
36
+ "at_risk_user_count": { "type": "number" },
37
+ "average_pain_threshold_pct": { "type": "number" },
38
+ "used_volatility": { "type": "number" }
39
+ },
40
+ "required": ["capitulation_probability", "at_risk_user_count"]
41
+ };
42
+
43
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
44
+ }
45
+
46
+ _initAsset(ticker, currentPrice, volatility) {
47
+ if (!this.assetRiskProfiles.has(ticker)) {
48
+ this.assetRiskProfiles.set(ticker, {
49
+ currentPrice: currentPrice,
50
+ volatility: volatility,
51
+ profiles: []
52
+ });
53
+ }
54
+ }
55
+
56
+ process(context) {
57
+ const { user, mappings, math, computed } = context;
58
+ const { extract, history, compute, signals } = math;
59
+
60
+ // 1. Capture Libraries via MUTATION
61
+ // We are retrieving the 'deps' object (GET) and mutating its property.
62
+ // This bypasses the Proxy SET trap entirely.
63
+ if (!this.deps.mathLib && compute) {
64
+ this.deps.mathLib = compute;
65
+ }
66
+
67
+ if (!this.tickerMap) {
68
+ this.tickerMap = mappings.instrumentToTicker;
69
+ }
70
+
71
+ // 2. Determine User's "Pain Threshold"
72
+ const historyDoc = history.getDailyHistory(user);
73
+ const summary = history.getSummary(historyDoc);
74
+ let personalPainThreshold = (summary && summary.avgLossPct < 0)
75
+ ? summary.avgLossPct
76
+ : -25.0;
77
+
78
+ // 3. Analyze Positions
79
+ const positions = extract.getPositions(user.portfolio.today, user.type);
80
+
81
+ for (const pos of positions) {
82
+ const instId = extract.getInstrumentId(pos);
83
+
84
+ if (!this.tickerMap) continue;
85
+
86
+ const ticker = this.tickerMap[instId];
87
+
88
+ if (!ticker) continue;
89
+
90
+ // Fetch Dependency
91
+ const assetStats = signals.getPreviousState(computed, 'asset-volatility-estimator', ticker);
92
+
93
+ const dynamicVol = assetStats ? assetStats.volatility_30d : 0.60;
94
+ const currentPrice = assetStats ? assetStats.last_price : 0;
95
+
96
+ if (currentPrice <= 0) continue;
97
+
98
+ // Get P&L from Position Schema
99
+ const netProfit = extract.getNetProfit(pos);
100
+
101
+ // Calculate Entry Price using the Dependency Price
102
+ const entryPrice = extract.deriveEntryPrice(currentPrice, netProfit);
103
+
104
+ if (entryPrice > 0) {
105
+ this._initAsset(ticker, currentPrice, dynamicVol);
106
+ this.assetRiskProfiles.get(ticker).profiles.push({
107
+ entryPrice: entryPrice,
108
+ thresholdPct: personalPainThreshold
109
+ });
110
+ }
111
+ }
112
+ }
113
+
114
+ async getResult() {
115
+ const result = {};
116
+ const TIME_HORIZON_DAYS = 3;
117
+ const SIMULATION_COUNT = 1000;
118
+
119
+ // Access the library from the container
120
+ const mathLib = this.deps.mathLib;
121
+
122
+ if (!mathLib || !mathLib.simulateGBM) {
123
+ console.log('[DEBUG RCRF] MathLib missing in deps container!');
124
+ return {};
125
+ }
126
+
127
+ for (const [ticker, data] of this.assetRiskProfiles.entries()) {
128
+ if (data.profiles.length < 1) {
129
+ continue;
130
+ }
131
+
132
+ try {
133
+ // 1. Generate Price Paths
134
+ const pricePaths = mathLib.simulateGBM(
135
+ data.currentPrice,
136
+ data.volatility,
137
+ TIME_HORIZON_DAYS,
138
+ SIMULATION_COUNT
139
+ );
140
+
141
+ if (!pricePaths || pricePaths.length === 0) continue;
142
+
143
+ // 2. Run Population Breakdown
144
+ const capitulationProb = mathLib.simulatePopulationBreakdown(
145
+ pricePaths,
146
+ data.profiles
147
+ );
148
+
149
+ const totalThreshold = data.profiles.reduce((acc, p) => acc + p.thresholdPct, 0);
150
+ const avgThreshold = totalThreshold / data.profiles.length;
151
+
152
+ result[ticker] = {
153
+ capitulation_probability: parseFloat(capitulationProb.toFixed(4)),
154
+ at_risk_user_count: data.profiles.length,
155
+ average_pain_threshold_pct: parseFloat(avgThreshold.toFixed(2)),
156
+ used_volatility: parseFloat(data.volatility.toFixed(4))
157
+ };
158
+ } catch (err) {
159
+ console.log(`[DEBUG RCRF] Error processing ${ticker}: ${err.message}`);
160
+ }
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ reset() {
167
+ this.assetRiskProfiles.clear();
168
+ this.tickerMap = null;
169
+ this.deps.mathLib = null; // Reset the container
170
+ }
171
+ }
172
+
173
+ module.exports = RetailCapitulationRiskForecast;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Core Calculation (Pass 2)
3
+ * Generates the "Ghost Book" - KDE of Holder Cost Basis.
4
+ * ARCHITECTURE COMPLIANT: Uses Context for Math & Prices.
5
+ */
6
+ class AssetCostBasisProfile {
7
+ constructor() {
8
+ // Map<Ticker, Map<PriceKey, Weight>>
9
+ this.assetBins = new Map();
10
+ this.tickerMap = null;
11
+ // FIX 1: Change state name to clearly indicate storage of the function pointer
12
+ this.computeKDE_func = null;
13
+ }
14
+
15
+ static getMetadata() {
16
+ return {
17
+ type: 'standard',
18
+ rootDataDependencies: ['portfolio'],
19
+ dependencies: [],
20
+ isHistorical: false,
21
+ userType: 'all',
22
+ category: 'core_distribution'
23
+ };
24
+ }
25
+
26
+ static getDependencies() { return []; }
27
+
28
+ static getSchema() {
29
+ const profileSchema = {
30
+ "type": "object",
31
+ "properties": {
32
+ "profile": {
33
+ "type": "array",
34
+ "items": { "type": "object", "properties": { "price": {"type": "number"}, "density": {"type": "number"} } }
35
+ },
36
+ "current_price": { "type": "number" },
37
+ "total_inventory_weight": { "type": "number" }
38
+ }
39
+ };
40
+ return { "type": "object", "patternProperties": { "^.*$": profileSchema } };
41
+ }
42
+
43
+ process(context) {
44
+ const { user, mappings, math } = context;
45
+
46
+ // FIX 2 (CRITICAL): Store the *function pointer* itself from math.distribution.computeKDE.
47
+ // This is necessary because storing the class object (math.distribution) breaks the static method reference when serialized/deserialized by worker threads.
48
+ if (!this.computeKDE_func) {
49
+ this.computeKDE_func = math.distribution.computeKDE;
50
+ }
51
+
52
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
53
+
54
+ const { extract } = math;
55
+
56
+ const positions = extract.getPositions(user.portfolio.today, user.type);
57
+
58
+ for (const pos of positions) {
59
+ const instId = extract.getInstrumentId(pos);
60
+ const ticker = this.tickerMap[instId];
61
+
62
+ if (!ticker) continue;
63
+
64
+ const currentPrice = extract.getCurrentRate(pos);
65
+ if (currentPrice <= 0) continue;
66
+
67
+ const netProfitPct = extract.getNetProfit(pos);
68
+ const weight = extract.getPositionWeight(pos, user.type);
69
+
70
+ if (netProfitPct <= -99.9) continue;
71
+
72
+ const entryPrice = currentPrice / (1 + (netProfitPct / 100.0));
73
+
74
+ const priceKey = Number(entryPrice.toPrecision(6));
75
+
76
+ if (!this.assetBins.has(ticker)) {
77
+ this.assetBins.set(ticker, { bins: new Map(), currentPrice });
78
+ }
79
+ const asset = this.assetBins.get(ticker);
80
+
81
+ asset.currentPrice = currentPrice;
82
+
83
+ const currentWeight = asset.bins.get(priceKey) || 0;
84
+ asset.bins.set(priceKey, currentWeight + weight);
85
+ }
86
+ }
87
+
88
+ async getResult() {
89
+ const result = {};
90
+
91
+ // Use the function pointer stored in state. No longer need the corrupted 'DistributionAnalytics' variable.
92
+ const computeKDE = this.computeKDE_func;
93
+
94
+ if (!computeKDE) return {}; // Safety guard
95
+
96
+ for (const [ticker, data] of this.assetBins.entries()) {
97
+ const points = [];
98
+ let totalWeight = 0;
99
+ for (const [price, weight] of data.bins.entries()) {
100
+ points.push({ value: price, weight: weight });
101
+ totalWeight += weight;
102
+ }
103
+
104
+ if (points.length < 2) continue;
105
+
106
+ const bandwidth = data.currentPrice * 0.02;
107
+
108
+ // FIX 3: Call the stored function directly
109
+ const profile = computeKDE(points, bandwidth, 60);
110
+
111
+ result[ticker] = {
112
+ profile: profile,
113
+ current_price: data.currentPrice,
114
+ total_inventory_weight: totalWeight
115
+ };
116
+ }
117
+ return result;
118
+ }
119
+
120
+ reset() {
121
+ this.assetBins.clear();
122
+ this.tickerMap = null;
123
+ this.computeKDE_func = null;
124
+ }
125
+ }
126
+
127
+ module.exports = AssetCostBasisProfile;
@@ -1,170 +1,100 @@
1
1
  /**
2
2
  * @fileoverview Core Metric (Pass 2)
3
- *
4
- * This 'standard' calculation streams all user portfolios
5
- * and, for each asset, buckets users into "Winner" (in-profit)
6
- * and "Loser" (in-loss) cohorts.
7
- *
8
- * It is a dependency for calculations that need to know
9
- * the full list of users in each cohort (e.g., 'helix-contrarian-signal')
10
- * but is too large to be a dependency for 'winner-loser-flow'.
3
+ * REFACTORED: Buckets users by P&L status (Win/Loss).
11
4
  */
12
- // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
13
-
14
5
  class AssetPnlStatus {
15
6
  constructor() {
16
- // { [ticker]: { winners: [uid1, uid2], losers: [uid1] } }
17
7
  this.tickerBuckets = new Map();
18
- // { [sector]: { winners: [uid1, uid2], losers: [uid1] } }
19
8
  this.sectorBuckets = new Map();
20
-
21
- // --- STANDARD 0: RENAMED ---
22
- this.tickerMap = null;
23
- this.sectorMap = null;
24
-
25
9
  }
26
10
 
27
- /** Statically defines metadata */
28
11
  static getMetadata() {
29
12
  return {
30
13
  type: 'standard',
31
14
  rootDataDependencies: ['portfolio'],
32
- isHistorical: false, // Reads today's portfolio
15
+ isHistorical: false,
33
16
  userType: 'all',
34
17
  category: 'core'
35
18
  };
36
19
  }
37
20
 
38
- /** Statically declare dependencies */
39
- static getDependencies() {
40
- return []; // This is a Pass 2 calculation
41
- }
21
+ static getDependencies() { return []; }
42
22
 
43
- /**
44
- * Defines the output schema for this calculation.
45
- */
46
23
  static getSchema() {
47
24
  const cohortSchema = {
48
25
  "type": "object",
49
26
  "properties": {
50
- "winners": {
51
- "type": "array",
52
- "items": { "type": "string" },
53
- "description": "List of User IDs in the 'Winner' (in-profit) cohort."
54
- },
55
- "losers": {
56
- "type": "array",
57
- "items": { "type": "string" },
58
- "description": "List of User IDs in the 'Loser' (in-loss) cohort."
59
- }
27
+ "winners": { "type": "array", "items": { "type": "string" } },
28
+ "losers": { "type": "array", "items": { "type": "string" } }
60
29
  },
61
30
  "required": ["winners", "losers"]
62
31
  };
63
-
64
32
  return {
65
33
  "type": "object",
66
- "description": "Buckets all users into 'Winner' or 'Loser' cohorts per asset and sector.",
67
34
  "properties": {
68
- "by_ticker": {
69
- "type": "object",
70
- "patternProperties": { "^.*$": cohortSchema },
71
- "additionalProperties": cohortSchema
72
- },
73
- "by_sector": {
74
- "type": "object",
75
- "patternProperties": { "^.*$": cohortSchema },
76
- "additionalProperties": cohortSchema
77
- }
78
- },
79
- "required": ["by_ticker", "by_sector"]
35
+ "by_ticker": { "type": "object", "patternProperties": { "^.*$": cohortSchema } },
36
+ "by_sector": { "type": "object", "patternProperties": { "^.*$": cohortSchema } }
37
+ }
80
38
  };
81
39
  }
82
40
 
83
41
  _init(map, key) {
84
- if (!map.has(key)) {
85
- map.set(key, { winners: new Set(), losers: new Set() });
86
- }
42
+ if (!map.has(key)) map.set(key, { winners: new Set(), losers: new Set() });
87
43
  }
88
44
 
89
- process(todayPortfolio, yesterdayPortfolio, userId, context) {
90
- // --- STANDARD 0: FIXED ---
91
- if (!this.tickerMap) {
92
- this.tickerMap = context.instrumentToTicker;
93
- this.sectorMap = context.sectorMapping;
94
- }
95
-
96
- if (!this.tickerMap || !this.sectorMap) {
97
- return; // Failsafe if context is missing maps
98
- }
99
-
100
- const positions = todayPortfolio?.AggregatedPositions || todayPortfolio?.PublicPositions;
101
- if (!positions || positions.length === 0) {
102
- return;
103
- }
45
+ process(context) {
46
+ const { extract } = context.math;
47
+ const { mappings, user } = context;
48
+
49
+ const positions = extract.getPositions(user.portfolio.today, user.type);
104
50
 
105
51
  for (const pos of positions) {
106
- if (!pos.InstrumentID) continue;
52
+ const instId = extract.getInstrumentId(pos);
53
+ if (!instId) continue;
107
54
 
108
- const pnl = pos.NetProfit || 0;
109
- if (pnl === 0) continue; // Ignore neutral
55
+ const pnl = extract.getNetProfit(pos);
56
+ if (pnl === 0) continue;
110
57
 
111
- // --- STANDARD 0: SIMPLIFIED ---
112
- const ticker = this.tickerMap[pos.InstrumentID];
113
- const sector = this.sectorMap[pos.InstrumentID];
58
+ const ticker = mappings.instrumentToTicker[instId];
59
+ const sector = mappings.sectorMapping[instId];
114
60
  const isWinner = pnl > 0;
115
61
 
116
62
  if (ticker) {
117
63
  this._init(this.tickerBuckets, ticker);
118
64
  const asset = this.tickerBuckets.get(ticker);
119
- if (isWinner) asset.winners.add(userId);
120
- else asset.losers.add(userId);
65
+ if (isWinner) asset.winners.add(user.id);
66
+ else asset.losers.add(user.id);
121
67
  }
122
68
 
123
69
  if (sector) {
124
70
  this._init(this.sectorBuckets, sector);
125
71
  const sec = this.sectorBuckets.get(sector);
126
- if (isWinner) sec.winners.add(userId);
127
- else sec.losers.add(userId);
72
+ if (isWinner) sec.winners.add(user.id);
73
+ else sec.losers.add(user.id);
128
74
  }
129
75
  }
130
76
  }
131
77
 
132
78
  async getResult() {
133
- // --- STANDARD 0: REMOVED forbidden data load ---
134
-
135
- // Failsafe check
136
- if (!this.tickerMap) {
137
- return { by_ticker: {}, by_sector: {} };
138
- }
139
-
140
- const result = {
141
- by_ticker: {},
142
- by_sector: {}
143
- };
144
-
145
- for (const [ticker, data] of this.tickerBuckets.entries()) {
146
- result.by_ticker[ticker] = {
147
- winners: Array.from(data.winners),
148
- losers: Array.from(data.losers)
149
- };
150
- }
151
-
152
- for (const [sector, data] of this.sectorBuckets.entries()) {
153
- result.by_sector[sector] = {
154
- winners: Array.from(data.winners),
155
- losers: Array.from(data.losers)
156
- };
157
- }
79
+ const result = { by_ticker: {}, by_sector: {} };
158
80
 
81
+ const buildResult = (map, out) => {
82
+ for (const [key, data] of map.entries()) {
83
+ out[key] = {
84
+ winners: Array.from(data.winners),
85
+ losers: Array.from(data.losers)
86
+ };
87
+ }
88
+ };
89
+
90
+ buildResult(this.tickerBuckets, result.by_ticker);
91
+ buildResult(this.sectorBuckets, result.by_sector);
159
92
  return result;
160
93
  }
161
94
 
162
95
  reset() {
163
96
  this.tickerBuckets.clear();
164
97
  this.sectorBuckets.clear();
165
- // --- STANDARD 0: RENAMED ---
166
- this.tickerMap = null;
167
- this.sectorMap = null;
168
98
  }
169
99
  }
170
100
  module.exports = AssetPnlStatus;