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
@@ -1,147 +1,134 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for speculator metric.
3
- *
4
- * This metric answers: "For each asset, how many long and
5
- * short speculator positions are within 5% of their stop loss?"
2
+ * @fileoverview Calculation (Pass 1) for speculator "danger zone".
3
+ * --- FIX ---
4
+ * - Rewritten logic to calculate SL distance from raw schema fields
5
+ * (StopLossRate, CurrentRate, OpenRate) instead of 'PctToStopLoss'.
6
+ * - "Danger Zone" is defined as being >= 90% of the way to the SL.
6
7
  */
7
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
8
8
 
9
9
  class SpeculatorDangerZone {
10
10
  constructor() {
11
- // { [instrumentId]: { long_in_danger: 0, short_in_danger: 0, long_total: 0, short_total: 0 } }
12
11
  this.assets = new Map();
13
- this.mappings = null;
12
+ this.tickerMap = null;
14
13
  }
15
14
 
16
- /**
17
- * Defines the output schema for this calculation.
18
- * @returns {object} JSON Schema object
19
- */
20
- static getSchema() {
21
- const tickerSchema = {
22
- "type": "object",
23
- "properties": {
24
- "long_in_danger": { "type": "number" },
25
- "short_in_danger": { "type": "number" },
26
- "long_total": { "type": "number" },
27
- "short_total": { "type": "number" },
28
- "long_danger_pct": {
29
- "type": "number",
30
- "description": "Percentage of long positions in the danger zone."
31
- },
32
- "short_danger_pct": {
33
- "type": "number",
34
- "description": "Percentage of short positions in the danger zone."
35
- }
36
- },
37
- "required": ["long_in_danger", "short_in_danger", "long_total", "short_total", "long_danger_pct", "short_danger_pct"]
38
- };
39
-
40
- return {
41
- "type": "object",
42
- "description": "Tracks speculator positions within 5% of their Stop Loss.",
43
- "patternProperties": {
44
- "^.*$": tickerSchema // Ticker
45
- },
46
- "additionalProperties": tickerSchema
47
- };
48
- }
49
-
50
- /**
51
- * Statically defines all metadata for the manifest builder.
52
- */
53
15
  static getMetadata() {
54
16
  return {
55
17
  type: 'standard',
56
18
  rootDataDependencies: ['portfolio'],
57
19
  isHistorical: false,
58
- userType: 'speculator', // This calc only runs for speculators
20
+ userType: 'speculator',
59
21
  category: 'core_speculator'
60
22
  };
61
23
  }
62
24
 
63
- /**
64
- * Statically declare dependencies.
65
- */
66
25
  static getDependencies() {
67
26
  return [];
68
27
  }
69
28
 
29
+ static getSchema() {
30
+ const tickerSchema = {
31
+ "type": "object",
32
+ "properties": {
33
+ "danger_zone_pct": { "type": "number" },
34
+ "danger_zone_count": { "type": "number" },
35
+ "total_count": { "type": "number" }
36
+ },
37
+ "required": ["danger_zone_pct", "danger_zone_count", "total_count"]
38
+ };
39
+
40
+ return {
41
+ "type": "object",
42
+ "description": "Calculates the percentage of speculators in the 'danger zone' (90-100% to stop-loss).",
43
+ "patternProperties": { "^.*$": tickerSchema },
44
+ "additionalProperties": tickerSchema
45
+ };
46
+ }
47
+
70
48
  _initAsset(instrumentId) {
71
49
  if (!this.assets.has(instrumentId)) {
72
- this.assets.set(instrumentId, {
73
- long_in_danger: 0,
74
- short_in_danger: 0,
75
- long_total: 0,
76
- short_total: 0
77
- });
50
+ this.assets.set(instrumentId, { danger_zone: 0, total: 0 });
78
51
  }
79
52
  }
80
53
 
81
- process(portfolioData) {
82
- // This check is now technically redundant due to `userType` in metadata,
83
- // but it's good practice to keep it for safety.
84
- if (portfolioData?.context?.userType !== 'speculator') {
85
- return;
54
+ // --- THIS IS THE FIX ---
55
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
56
+ if (!this.tickerMap) {
57
+ this.tickerMap = context.instrumentToTicker;
86
58
  }
87
-
88
- const positions = portfolioData.PublicPositions;
59
+
60
+ const positions = todayPortfolio.PublicPositions;
89
61
  if (!positions || !Array.isArray(positions)) {
90
62
  return;
91
63
  }
92
64
 
93
65
  for (const pos of positions) {
94
66
  const instrumentId = pos.InstrumentID;
95
- const sl_rate = pos.StopLossRate || 0;
96
- const current_price = pos.LastCloseRate || 0;
97
-
98
- if (!instrumentId || sl_rate === 0 || current_price === 0) {
99
- continue;
67
+ const slRate = pos.StopLossRate;
68
+ const openRate = pos.OpenRate;
69
+ const currentRate = pos.CurrentRate;
70
+
71
+ if (!instrumentId || !slRate || slRate <= 0 || !openRate || openRate <= 0 || !currentRate || currentRate <= 0) {
72
+ continue; // No SL or invalid data
100
73
  }
101
-
74
+
102
75
  this._initAsset(instrumentId);
103
76
  const assetData = this.assets.get(instrumentId);
104
-
105
- const distance = Math.abs(current_price - sl_rate);
106
- const distance_pct = (distance / current_price);
107
-
108
- const isInDanger = distance_pct <= 0.05; // 5% danger zone
77
+ assetData.total++;
78
+
79
+ let totalRiskDistance = 0;
80
+ let currentDistance = 0;
109
81
 
110
82
  if (pos.IsBuy) {
111
- assetData.long_total++;
112
- if (isInDanger) {
113
- assetData.long_in_danger++;
114
- }
83
+ // Long
84
+ totalRiskDistance = openRate - slRate;
85
+ currentDistance = currentRate - slRate;
115
86
  } else {
116
- assetData.short_total++;
117
- if (isInDanger) {
118
- assetData.short_in_danger++;
119
- }
87
+ // Short
88
+ totalRiskDistance = slRate - openRate;
89
+ currentDistance = slRate - currentRate;
90
+ }
91
+
92
+ if (totalRiskDistance <= 0) {
93
+ continue; // Invalid SL (at or beyond open price)
94
+ }
95
+
96
+ // This is the percentage of the "risk buffer" that has been used up.
97
+ // e.g., Open=100, SL=80. RiskDist=20.
98
+ // Current=82. CurrentDist=2. PctUsed = (20-2)/20 = 18/20 = 90%.
99
+ const percentOfRiskUsed = (totalRiskDistance - currentDistance) / totalRiskDistance;
100
+
101
+ // "Danger Zone" = 90% or more of the way to the SL
102
+ if (percentOfRiskUsed >= 0.90) {
103
+ assetData.danger_zone++;
120
104
  }
121
105
  }
122
106
  }
107
+ // --- END FIX ---
123
108
 
124
109
  async getResult() {
125
- if (!this.mappings) {
126
- this.mappings = await loadInstrumentMappings();
110
+ if (!this.tickerMap) {
111
+ return {};
127
112
  }
128
113
 
129
114
  const result = {};
130
115
  for (const [instrumentId, data] of this.assets.entries()) {
131
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
132
-
133
- result[ticker] = {
134
- ...data,
135
- long_danger_pct: (data.long_total > 0) ? (data.long_in_danger / data.long_total) * 100 : 0,
136
- short_danger_pct: (data.short_total > 0) ? (data.short_in_danger / data.short_total) * 100 : 0
137
- };
116
+ const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
117
+
118
+ if (data.total > 0) {
119
+ result[ticker] = {
120
+ danger_zone_pct: (data.danger_zone / data.total) * 100,
121
+ danger_zone_count: data.danger_zone,
122
+ total_count: data.total
123
+ };
124
+ }
138
125
  }
139
126
  return result;
140
127
  }
141
128
 
142
129
  reset() {
143
130
  this.assets.clear();
144
- this.mappings = null;
131
+ this.tickerMap = null;
145
132
  }
146
133
  }
147
134
 
@@ -1,141 +1,126 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for speculator metric.
3
- *
4
- * This metric answers: "For each asset and leverage level,
5
- * what is the average percentage distance from the *current price*
6
- * to the stop loss?"
2
+ * @fileoverview Calculation (Pass 1) for speculator SL distance.
3
+ * --- FIX ---
4
+ * - Rewritten logic to calculate SL distance from raw schema fields
5
+ * (StopLossRate, CurrentRate) instead of the non-existent
6
+ * 'PctToStopLoss' field.
7
+ * - Updated process signature to match worker.
7
8
  */
8
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
9
+ const BUCKETS = [1, 2, 5, 10, 20, 30]; // Leverage buckets
9
10
 
10
- class DistanceToStopLossPerLeverage {
11
+ class SpeculatorDistanceToStopLossPerLeverage {
11
12
  constructor() {
12
- // { [instrumentId]: { [leverage]: { sum_dist: 0, count: 0 } } }
13
- this.assets = new Map();
14
- this.mappings = null;
13
+ this.buckets = new Map();
14
+ this._initBuckets();
15
+ }
16
+
17
+ _initBuckets() {
18
+ this.buckets.clear();
19
+ for (const b of BUCKETS) {
20
+ this.buckets.set(b, { sum: 0, count: 0 });
21
+ }
22
+ this.buckets.set('other', { sum: 0, count: 0 });
15
23
  }
16
24
 
17
- // --- NEW ---
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
21
25
  static getMetadata() {
22
26
  return {
23
27
  type: 'standard',
24
28
  rootDataDependencies: ['portfolio'],
25
29
  isHistorical: false,
26
- userType: 'speculator',
30
+ userType: 'speculator', // <-- KEY: Only runs for speculators
27
31
  category: 'core_speculator'
28
32
  };
29
33
  }
30
34
 
31
- // --- NEW ---
32
- /**
33
- * Statically declare dependencies.
34
- */
35
35
  static getDependencies() {
36
36
  return [];
37
37
  }
38
38
 
39
- /**
40
- * Defines the output schema for this calculation.
41
- * @returns {object} JSON Schema object
42
- */
43
39
  static getSchema() {
44
- const leverageSchema = {
40
+ const bucketSchema = {
45
41
  "type": "object",
46
- "description": "Average SL distance grouped by leverage.",
47
- "patternProperties": {
48
- // Leverage level, e.g., "1x", "5x", "10x"
49
- "^[0-9]+x$": {
50
- "type": "number",
51
- "description": "The average percentage distance from current price to Stop Loss for this leverage level."
52
- }
53
- },
54
- "additionalProperties": { "type": "number" }
42
+ "properties": {
43
+ "avg_distance_pct": { "type": "number" },
44
+ "position_count": { "type": "number" }
45
+ }
55
46
  };
56
47
 
57
48
  return {
58
49
  "type": "object",
59
- "description": "Calculates avg % distance from current price to SL, bucketed by asset and leverage.",
60
- "patternProperties": {
61
- "^.*$": leverageSchema // Ticker
62
- },
63
- "additionalProperties": leverageSchema
50
+ "description": "Calculates the average distance to stop-loss, bucketed by leverage.",
51
+ "properties": {
52
+ "x1": bucketSchema,
53
+ "x2": bucketSchema,
54
+ "x5": bucketSchema,
55
+ "x10": bucketSchema,
56
+ "x20": bucketSchema,
57
+ "x30": bucketSchema,
58
+ "other": bucketSchema
59
+ }
64
60
  };
65
61
  }
66
62
 
67
- _initLeverage(assetData, leverage) {
68
- const key = `${leverage}x`;
69
- if (!assetData[key]) {
70
- assetData[key] = { sum_dist: 0, count: 0 };
63
+ _getBucket(leverage) {
64
+ if (this.buckets.has(leverage)) {
65
+ return this.buckets.get(leverage);
71
66
  }
72
- return key;
67
+ return this.buckets.get('other');
73
68
  }
74
69
 
75
- // --- REFACTORED ---
76
- // Simplified signature
77
- process(portfolioData) {
78
- // This calculation is only for speculators
79
- if (portfolioData?.context?.userType !== 'speculator') {
80
- return;
81
- }
82
-
83
- const positions = portfolioData.PublicPositions;
70
+ // --- THIS IS THE FIX ---
71
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
72
+ const positions = todayPortfolio.PublicPositions;
84
73
  if (!positions || !Array.isArray(positions)) {
85
74
  return;
86
75
  }
87
76
 
88
77
  for (const pos of positions) {
89
- const instrumentId = pos.InstrumentID;
90
- const sl_rate = pos.StopLossRate || 0;
91
- const current_price = pos.LastCloseRate || 0; // Use last close as current price
92
-
93
- if (!instrumentId || sl_rate === 0 || current_price === 0) {
94
- continue; // Skip positions without SL or price
78
+ const leverage = pos.Leverage;
79
+ const slRate = pos.StopLossRate;
80
+ const currentRate = pos.CurrentRate;
81
+
82
+ // Check if SL is set (slRate > 0) and we have valid prices
83
+ if (!leverage || !slRate || slRate <= 0 || !currentRate || currentRate <= 0) {
84
+ continue;
95
85
  }
96
86
 
97
- if (!this.assets.has(instrumentId)) {
98
- this.assets.set(instrumentId, {});
87
+ // Manually calculate the percentage distance to SL
88
+ let pctToSL = 0;
89
+ if (pos.IsBuy) {
90
+ // Long: (Current - SL) / Current
91
+ pctToSL = (currentRate - slRate) / currentRate;
92
+ } else {
93
+ // Short: (SL - Current) / Current
94
+ pctToSL = (slRate - currentRate) / currentRate;
99
95
  }
100
- const assetData = this.assets.get(instrumentId);
101
-
102
- const leverage = pos.Leverage || 1;
103
- const key = this._initLeverage(assetData, leverage);
104
-
105
- // Calculate distance
106
- const distance = Math.abs(current_price - sl_rate);
107
- const distance_pct = (distance / current_price);
108
-
109
- assetData[key].sum_dist += distance_pct;
110
- assetData[key].count++;
111
- }
112
- }
113
96
 
114
- async getResult() {
115
- if (!this.mappings) {
116
- this.mappings = await loadInstrumentMappings();
97
+ // We only care about open SLs (distance > 0)
98
+ if (pctToSL <= 0) {
99
+ continue;
100
+ }
101
+
102
+ const bucket = this._getBucket(leverage);
103
+ bucket.sum += (pctToSL * 100); // Convert to percentage
104
+ bucket.count++;
117
105
  }
106
+ }
107
+ // --- END FIX ---
118
108
 
109
+ getResult() {
119
110
  const result = {};
120
- for (const [instrumentId, leverageData] of this.assets.entries()) {
121
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
122
- result[ticker] = {};
123
-
124
- for (const leverageKey in leverageData) {
125
- const data = leverageData[leverageKey];
126
- if (data.count > 0) {
127
- // Store the final average distance * 100 for percentage
128
- result[ticker][leverageKey] = (data.sum_dist / data.count) * 100;
129
- }
130
- }
111
+ for (const [key, data] of this.buckets.entries()) {
112
+ const label = (key === 'other') ? 'other' : `x${key}`;
113
+ result[label] = {
114
+ avg_distance_pct: (data.count > 0) ? (data.sum / data.count) : 0,
115
+ position_count: data.count
116
+ };
131
117
  }
132
118
  return result;
133
119
  }
134
120
 
135
121
  reset() {
136
- this.assets.clear();
137
- this.mappings = null;
122
+ this._initBuckets();
138
123
  }
139
124
  }
140
125
 
141
- module.exports = DistanceToStopLossPerLeverage;
126
+ module.exports = SpeculatorDistanceToStopLossPerLeverage;
@@ -1,139 +1,126 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for speculator metric.
3
- *
4
- * This metric answers: "For each asset and leverage level,
5
- * what is the average percentage distance from the *current price*
6
- * to the take profit?"
2
+ * @fileoverview Calculation (Pass 1) for speculator TP distance.
3
+ * --- FIX ---
4
+ * - Rewritten logic to calculate TP distance from raw schema fields
5
+ * (TakeProfitRate, CurrentRate) instead of the non-existent
6
+ * 'PctToTakeProfit' field.
7
+ * - Updated process signature to match worker.
7
8
  */
8
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
9
+ const BUCKETS = [1, 2, 5, 10, 20, 30]; // Leverage buckets
9
10
 
10
- class DistanceToTakeProfitPerLeverage {
11
+ class SpeculatorDistanceToTakeProfitPerLeverage {
11
12
  constructor() {
12
- // { [instrumentId]: { [leverage]: { sum_dist: 0, count: 0 } } }
13
- this.assets = new Map();
14
- this.mappings = null;
13
+ this.buckets = new Map();
14
+ this._initBuckets();
15
+ }
16
+
17
+ _initBuckets() {
18
+ this.buckets.clear();
19
+ for (const b of BUCKETS) {
20
+ this.buckets.set(b, { sum: 0, count: 0 });
21
+ }
22
+ this.buckets.set('other', { sum: 0, count: 0 });
15
23
  }
16
24
 
17
- // --- NEW ---
18
- /**
19
- * Statically defines all metadata for the manifest builder.
20
- */
21
25
  static getMetadata() {
22
26
  return {
23
27
  type: 'standard',
24
28
  rootDataDependencies: ['portfolio'],
25
29
  isHistorical: false,
26
- userType: 'speculator',
30
+ userType: 'speculator', // <-- KEY: Only runs for speculators
27
31
  category: 'core_speculator'
28
32
  };
29
33
  }
30
34
 
31
- // --- NEW ---
32
- /**
33
- * Statically declare dependencies.
34
- */
35
35
  static getDependencies() {
36
36
  return [];
37
37
  }
38
38
 
39
- /**
40
- * Defines the output schema for this calculation.
41
- * @returns {object} JSON Schema object
42
- */
43
39
  static getSchema() {
44
- const leverageSchema = {
40
+ const bucketSchema = {
45
41
  "type": "object",
46
- "description": "Average TP distance grouped by leverage.",
47
- "patternProperties": {
48
- // Leverage level, e.g., "1x", "5x", "10x"
49
- "^[0-9]+x$": {
50
- "type": "number",
51
- "description": "The average percentage distance from current price to Take Profit for this leverage level."
52
- }
53
- },
54
- "additionalProperties": { "type": "number" }
42
+ "properties": {
43
+ "avg_distance_pct": { "type": "number" },
44
+ "position_count": { "type": "number" }
45
+ }
55
46
  };
56
47
 
57
48
  return {
58
49
  "type": "object",
59
- "description": "Calculates avg % distance from current price to TP, bucketed by asset and leverage.",
60
- "patternProperties": {
61
- "^.*$": leverageSchema // Ticker
62
- },
63
- "additionalProperties": leverageSchema
50
+ "description": "Calculates the average distance to take-profit, bucketed by leverage.",
51
+ "properties": {
52
+ "x1": bucketSchema,
53
+ "x2": bucketSchema,
54
+ "x5": bucketSchema,
55
+ "x10": bucketSchema,
56
+ "x20": bucketSchema,
57
+ "x30": bucketSchema,
58
+ "other": bucketSchema
59
+ }
64
60
  };
65
61
  }
66
62
 
67
- _initLeverage(assetData, leverage) {
68
- const key = `${leverage}x`;
69
- if (!assetData[key]) {
70
- assetData[key] = { sum_dist: 0, count: 0 };
63
+ _getBucket(leverage) {
64
+ if (this.buckets.has(leverage)) {
65
+ return this.buckets.get(leverage);
71
66
  }
72
- return key;
67
+ return this.buckets.get('other');
73
68
  }
74
69
 
75
- // --- REFACTORED ---
76
- // Simplified signature
77
- process(portfolioData) {
78
- // This calculation is only for speculators
79
- if (portfolioData?.context?.userType !== 'speculator') {
80
- return;
81
- }
82
-
83
- const positions = portfolioData.PublicPositions;
70
+ // --- THIS IS THE FIX ---
71
+ process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
72
+ const positions = todayPortfolio.PublicPositions;
84
73
  if (!positions || !Array.isArray(positions)) {
85
74
  return;
86
75
  }
87
76
 
88
77
  for (const pos of positions) {
89
- const instrumentId = pos.InstrumentID;
90
- const tp_rate = pos.TakeProfitRate || 0;
91
- const current_price = pos.LastCloseRate || 0;
92
-
93
- if (!instrumentId || tp_rate === 0 || current_price === 0) {
94
- continue; // Skip positions without TP or price
78
+ const leverage = pos.Leverage;
79
+ const tpRate = pos.TakeProfitRate;
80
+ const currentRate = pos.CurrentRate;
81
+
82
+ // Check if TP is set (tpRate > 0) and we have valid prices
83
+ if (!leverage || !tpRate || tpRate <= 0 || !currentRate || currentRate <= 0) {
84
+ continue;
95
85
  }
96
86
 
97
- if (!this.assets.has(instrumentId)) {
98
- this.assets.set(instrumentId, {});
87
+ // Manually calculate the percentage distance to TP
88
+ let pctToTP = 0;
89
+ if (pos.IsBuy) {
90
+ // Long: (TP - Current) / Current
91
+ pctToTP = (tpRate - currentRate) / currentRate;
92
+ } else {
93
+ // Short: (Current - TP) / Current
94
+ pctToTP = (currentRate - tpRate) / currentRate;
99
95
  }
100
- const assetData = this.assets.get(instrumentId);
101
-
102
- const leverage = pos.Leverage || 1;
103
- const key = this._initLeverage(assetData, leverage);
104
-
105
- const distance = Math.abs(tp_rate - current_price);
106
- const distance_pct = (distance / current_price);
107
-
108
- assetData[key].sum_dist += distance_pct;
109
- assetData[key].count++;
110
- }
111
- }
112
96
 
113
- async getResult() {
114
- if (!this.mappings) {
115
- this.mappings = await loadInstrumentMappings();
97
+ // We only care about open TPs (distance > 0)
98
+ if (pctToTP <= 0) {
99
+ continue;
100
+ }
101
+
102
+ const bucket = this._getBucket(leverage);
103
+ bucket.sum += (pctToTP * 100); // Convert to percentage
104
+ bucket.count++;
116
105
  }
106
+ }
107
+ // --- END FIX ---
117
108
 
109
+ getResult() {
118
110
  const result = {};
119
- for (const [instrumentId, leverageData] of this.assets.entries()) {
120
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
121
- result[ticker] = {};
122
-
123
- for (const leverageKey in leverageData) {
124
- const data = leverageData[leverageKey];
125
- if (data.count > 0) {
126
- result[ticker][leverageKey] = (data.sum_dist / data.count) * 100;
127
- }
128
- }
111
+ for (const [key, data] of this.buckets.entries()) {
112
+ const label = (key === 'other') ? 'other' : `x${key}`;
113
+ result[label] = {
114
+ avg_distance_pct: (data.count > 0) ? (data.sum / data.count) : 0,
115
+ position_count: data.count
116
+ };
129
117
  }
130
118
  return result;
131
119
  }
132
120
 
133
121
  reset() {
134
- this.assets.clear();
135
- this.mappings = null;
122
+ this._initBuckets();
136
123
  }
137
124
  }
138
125
 
139
- module.exports = DistanceToTakeProfitPerLeverage;
126
+ module.exports = SpeculatorDistanceToTakeProfitPerLeverage;