aiden-shared-calculations-unified 1.0.86 → 1.0.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/calculations/capitulation/asset-volatility-estimator.js +96 -0
  2. package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
  3. package/calculations/core/asset-cost-basis-profile.js +127 -0
  4. package/calculations/core/asset-pnl-status.js +36 -106
  5. package/calculations/core/asset-position-size.js +40 -91
  6. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  7. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  8. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  9. package/calculations/core/average-daily-position-pnl.js +19 -49
  10. package/calculations/core/holding-duration-per-asset.js +25 -127
  11. package/calculations/core/instrument-price-change-1d.js +30 -49
  12. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  13. package/calculations/core/long-position-per-stock.js +39 -68
  14. package/calculations/core/overall-holding-duration.js +16 -87
  15. package/calculations/core/overall-profitability-ratio.js +11 -40
  16. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  17. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  18. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  19. package/calculations/core/platform-ownership-per-sector.js +45 -96
  20. package/calculations/core/platform-total-positions-held.js +20 -80
  21. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  22. package/calculations/core/price-metrics.js +95 -206
  23. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  24. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  25. package/calculations/core/profitability-skew-per-stock.js +41 -94
  26. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  27. package/calculations/core/sentiment-per-stock.js +24 -77
  28. package/calculations/core/short-position-per-stock.js +35 -43
  29. package/calculations/core/social-activity-aggregation.js +26 -49
  30. package/calculations/core/social-asset-posts-trend.js +38 -94
  31. package/calculations/core/social-event-correlation.js +26 -93
  32. package/calculations/core/social-sentiment-aggregation.js +20 -44
  33. package/calculations/core/social-top-mentioned-words.js +35 -87
  34. package/calculations/core/social-topic-interest-evolution.js +22 -111
  35. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  36. package/calculations/core/social-word-mentions-trend.js +27 -104
  37. package/calculations/core/speculator-asset-sentiment.js +31 -72
  38. package/calculations/core/speculator-danger-zone.js +48 -84
  39. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  40. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  41. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  42. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  43. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  44. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  45. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  46. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  47. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  48. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  49. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  50. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  51. package/calculations/core/test..js +0 -0
  52. package/calculations/core/total-long-figures.js +16 -31
  53. package/calculations/core/total-long-per-sector.js +39 -61
  54. package/calculations/core/total-short-figures.js +13 -32
  55. package/calculations/core/total-short-per-sector.js +39 -61
  56. package/calculations/core/users-processed.js +11 -46
  57. package/calculations/gauss/cohort-capital-flow.js +54 -173
  58. package/calculations/gauss/cohort-definer.js +77 -163
  59. package/calculations/gauss/daily-dna-filter.js +29 -83
  60. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  61. package/calculations/gem/cohort-momentum-state.js +27 -72
  62. package/calculations/gem/cohort-skill-definition.js +36 -52
  63. package/calculations/gem/platform-conviction-divergence.js +18 -60
  64. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  65. package/calculations/gem/skilled-cohort-flow.js +67 -175
  66. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  67. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  68. package/calculations/ghost-book/cost-basis-density.js +79 -0
  69. package/calculations/ghost-book/liquidity-vacuum.js +52 -0
  70. package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
  71. package/calculations/helix/helix-contrarian-signal.js +20 -114
  72. package/calculations/helix/herd-consensus-score.js +42 -124
  73. package/calculations/helix/winner-loser-flow.js +36 -118
  74. package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
  75. package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
  76. package/calculations/predicative-alpha/mimetic-latency.js +124 -0
  77. package/calculations/pyro/risk-appetite-index.js +33 -74
  78. package/calculations/pyro/squeeze-potential.js +30 -87
  79. package/calculations/pyro/volatility-signal.js +33 -78
  80. package/package.json +1 -1
@@ -1,17 +1,13 @@
1
1
  /**
2
2
  * @fileoverview GEM Product Line (Pass 2)
3
- * --- FIX ---
4
- * - Added defensive check in _loadDependencies for 'instrument-price-momentum-20d'.
5
- * - This calc fails because its dependency fails. The logic is correct.
3
+ * REFACTORED: Uses context.computed and context.math.
6
4
  */
7
-
8
5
  class CohortMomentumState {
9
6
  constructor() {
10
7
  this.cohortMomentum = new Map();
11
8
  this.cohortMap = new Map();
12
- this.tickerMap = null;
13
9
  this.dependenciesLoaded = false;
14
- this.momentumData = null;
10
+ this.tickerMap = null; // Need to store this
15
11
  }
16
12
 
17
13
  static getMetadata() {
@@ -25,10 +21,7 @@ class CohortMomentumState {
25
21
  }
26
22
 
27
23
  static getDependencies() {
28
- return [
29
- 'cohort-skill-definition',
30
- 'instrument-price-momentum-20d'
31
- ];
24
+ return ['cohort-skill-definition', 'instrument-price-momentum-20d'];
32
25
  }
33
26
 
34
27
  static getSchema() {
@@ -40,107 +33,69 @@ class CohortMomentumState {
40
33
  },
41
34
  "required": ["average_momentum_exposure_pct", "trade_count"]
42
35
  };
43
-
44
- return {
45
- "type": "object",
46
- "description": "Calculates the average 20D momentum % for all *new* trades, bucketed by skill cohort.",
47
- "properties": {
48
- "skilled": cohortSchema,
49
- "unskilled": cohortSchema
50
- }
51
- };
36
+ return { "type": "object", "properties": { "skilled": cohortSchema, "unskilled": cohortSchema } };
52
37
  }
53
38
 
54
- _loadDependencies(fetchedDependencies) {
39
+ _loadDependencies(computed) {
55
40
  if (this.dependenciesLoaded) return;
56
-
57
- // --- FIX: Add defensive checks ---
58
- if (!fetchedDependencies) {
59
- throw new Error("[cohort-momentum-state] Critical error: fetchedDependencies is undefined.");
60
- }
61
- const cohortData = fetchedDependencies['cohort-skill-definition'];
62
- if (!cohortData) {
63
- throw new Error("[cohort-momentum-state] Dependency Error: 'cohort-skill-definition' was missing.");
41
+ const cohortData = computed['cohort-skill-definition'];
42
+ if (cohortData) {
43
+ (cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
44
+ (cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
64
45
  }
65
- this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
66
- if (!this.momentumData) {
67
- throw new Error("[cohort-momentum-state] Dependency Error: 'instrument-price-momentum-20d' was missing. This is the root cause of failure.");
68
- }
69
- // --- END FIX ---
70
-
71
- // Cohort data structure is correct
72
- (cohortData.skilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'skilled'));
73
- (cohortData.unskilled_user_ids || []).forEach(uid => this.cohortMap.set(String(uid), 'unskilled'));
74
-
75
46
  this.dependenciesLoaded = true;
76
47
  }
77
48
 
78
49
  _initCohort(cohortName) {
79
- if (!this.cohortMomentum.has(cohortName)) {
80
- this.cohortMomentum.set(cohortName, { momentum_sum: 0, count: 0 });
81
- }
50
+ if (!this.cohortMomentum.has(cohortName)) this.cohortMomentum.set(cohortName, { momentum_sum: 0, count: 0 });
82
51
  }
83
52
 
84
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
85
- // Use the 7-arg signature
86
- this._loadDependencies(fetchedDependencies);
87
-
88
- if (!this.tickerMap) {
89
- this.tickerMap = context.instrumentToTicker;
90
- }
53
+ process(context) {
54
+ const { user, computed, mappings, math } = context;
55
+ const { extract } = math;
56
+
57
+ this._loadDependencies(computed);
58
+ if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
91
59
 
92
- const cohortName = this.cohortMap.get(String(userId));
93
- if (!cohortName) {
94
- return; // Not in a defined cohort
95
- }
60
+ const cohortName = this.cohortMap.get(user.id);
61
+ if (!cohortName) return;
96
62
 
97
- if (!this.momentumData || !this.tickerMap) {
98
- return; // Dependencies missing
99
- }
63
+ const momentumData = computed['instrument-price-momentum-20d'];
64
+ if (!momentumData) return;
100
65
 
101
- // This calc uses the 'hacked' worker logic for portfolio
102
- const yIds = new Set((yesterdayPortfolio?.AggregatedPositions || []).map(p => p.InstrumentID));
103
- const newPositions = (todayPortfolio?.AggregatedPositions || []).filter(p => p.InstrumentID && !yIds.has(p.InstrumentID));
66
+ const yIds = new Set(extract.getPositions(user.portfolio.yesterday, user.type).map(p => extract.getInstrumentId(p)));
67
+ const newPositions = extract.getPositions(user.portfolio.today, user.type).filter(p => !yIds.has(extract.getInstrumentId(p)));
104
68
 
105
- if (newPositions.length === 0) {
106
- return; // No new positions
107
- }
69
+ if (newPositions.length === 0) return;
108
70
 
109
71
  this._initCohort(cohortName);
110
72
  const asset = this.cohortMomentum.get(cohortName);
111
73
 
112
74
  for (const pos of newPositions) {
113
- const ticker = this.tickerMap[pos.InstrumentID];
114
- if (ticker && this.momentumData[ticker]) {
115
- asset.momentum_sum += this.momentumData[ticker].momentum_20d_pct || 0;
75
+ const ticker = mappings.instrumentToTicker[extract.getInstrumentId(pos)];
76
+ if (ticker && momentumData[ticker]) {
77
+ asset.momentum_sum += momentumData[ticker].momentum_20d_pct || 0;
116
78
  asset.count++;
117
79
  }
118
80
  }
119
81
  }
120
82
 
121
83
  async getResult() {
122
- if (!this.tickerMap) {
123
- return {};
124
- }
125
-
126
84
  const finalResult = {};
127
-
128
85
  for (const [cohortName, data] of this.cohortMomentum.entries()) {
129
86
  finalResult[cohortName] = {
130
87
  average_momentum_exposure_pct: (data.count > 0) ? data.momentum_sum / data.count : 0,
131
88
  trade_count: data.count
132
89
  };
133
90
  }
134
-
135
91
  return finalResult;
136
92
  }
137
93
 
138
94
  reset() {
139
95
  this.cohortMomentum.clear();
140
96
  this.cohortMap.clear();
141
- this.tickerMap = null;
142
97
  this.dependenciesLoaded = false;
143
- this.momentumData = null;
98
+ this.tickerMap = null;
144
99
  }
145
100
  }
146
101
  module.exports = CohortMomentumState;
@@ -1,26 +1,20 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 1) for defining skill-based cohorts.
3
- * --- FIX ---
4
- * - 'todayPortfolio' IS the history object.
5
- * - Changed 'todayPortfolio?.all' to 'historyData?.all' for clarity.
6
- * - This calculation is already correct based on the worker 'hack'.
2
+ * @fileoverview GEM Product Line (Pass 1)
3
+ * REFACTORED: Strictly uses context.math.history to access user history.
7
4
  */
8
5
  class CohortSkillDefinition {
9
- constructor() {
10
- this.userScores = new Map();
11
- }
6
+ constructor() { this.userScores = new Map(); }
12
7
 
13
8
  static getSchema() {
14
9
  return {
15
10
  "type": "object",
16
- "description": "Provides the user ID lists for the 'Skilled' (top 20%) and 'Unskilled' (bottom 20%) cohorts based on historical trade performance.",
17
11
  "properties": {
18
12
  "skilled_user_ids": { "type": "array", "items": { "type": "string" } },
19
13
  "unskilled_user_ids": { "type": "array", "items": { "type": "string" } },
20
14
  "skilled_cohort_size": { "type": "number" },
21
15
  "unskilled_cohort_size": { "type": "number" }
22
16
  },
23
- "required": ["skilled_user_ids", "unskilled_user_ids", "skilled_cohort_size", "unskilled_cohort_size"]
17
+ "required": ["skilled_user_ids", "unskilled_user_ids"]
24
18
  };
25
19
  }
26
20
 
@@ -34,63 +28,53 @@ class CohortSkillDefinition {
34
28
  };
35
29
  }
36
30
 
37
- static getDependencies() {
38
- return [];
39
- }
40
-
41
- // --- THIS IS THE FIX ---
42
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
31
+ static getDependencies() { return []; }
43
32
 
33
+ process(context) {
34
+ const { user, math } = context;
35
+ const { history } = math;
36
+
37
+ // 1. Get strict daily history
38
+ const historyDoc = history.getDailyHistory(user);
39
+
40
+ // 2. Get strict summary DTO
41
+ const summary = history.getSummary(historyDoc);
44
42
 
45
- const historyData = todayPortfolio;
46
- const history = historyData?.all;
43
+ if (!summary || summary.totalTrades < 10) return;
47
44
 
48
- if (!history) {
49
- return;
50
- }
51
-
52
- const { winRatio, avgProfitPct, avgLossPct, totalTrades } = history;
53
-
54
- if (!totalTrades || totalTrades < 10) {
55
- return;
56
- }
57
-
58
- const winRate = winRatio / 100.0;
45
+ // 3. Valid DTO usage
46
+ const winRate = summary.winRatio / 100.0;
59
47
  const lossRate = 1.0 - winRate;
60
- const avgWin = avgProfitPct;
61
- const avgLoss = Math.abs(avgLossPct);
62
-
63
- const expectancy = (winRate * avgWin) - (lossRate * avgLoss);
64
- const skillScore = expectancy * Math.log10(Math.max(1, totalTrades));
48
+
49
+ const expectancy = (winRate * summary.avgProfitPct) - (lossRate * Math.abs(summary.avgLossPct));
50
+
51
+ const skillScore = expectancy * Math.log10(Math.max(1, summary.totalTrades));
65
52
 
66
- if (isFinite(skillScore)) {
67
- this.userScores.set(userId, skillScore);
68
- }
53
+ if (isFinite(skillScore)) this.userScores.set(user.id, skillScore);
69
54
  }
70
55
 
71
56
  getResult() {
72
- const sortedUsers = Array.from(this.userScores.entries())
73
- .sort((a, b) => b[1] - a[1]);
74
-
75
- const cohortSize = Math.floor(sortedUsers.length * 0.20);
57
+ const sorted = Array.from(this.userScores.entries()).sort((a, b) => b[1] - a[1]);
58
+ const cohortSize = Math.floor(sorted.length * 0.20);
59
+
76
60
  if (cohortSize === 0) {
77
- return { skilled_user_ids: [], unskilled_user_ids: [], skilled_cohort_size: 0, unskilled_cohort_size: 0 };
61
+ return {
62
+ skilled_user_ids: [], unskilled_user_ids: [],
63
+ skilled_cohort_size: 0, unskilled_cohort_size: 0
64
+ };
78
65
  }
79
66
 
80
- const skilled_user_ids = sortedUsers.slice(0, cohortSize).map(u => String(u[0]));
81
- const unskilled_user_ids = sortedUsers.slice(-cohortSize).map(u => String(u[0]));
67
+ const skilled = sorted.slice(0, cohortSize).map(u => String(u[0]));
68
+ const unskilled = sorted.slice(-cohortSize).map(u => String(u[0]));
82
69
 
83
70
  return {
84
- skilled_user_ids: skilled_user_ids,
85
- unskilled_user_ids: unskilled_user_ids,
86
- skilled_cohort_size: skilled_user_ids.length,
87
- unskilled_cohort_size: unskilled_user_ids.length
71
+ skilled_user_ids: skilled,
72
+ unskilled_user_ids: unskilled,
73
+ skilled_cohort_size: skilled.length,
74
+ unskilled_cohort_size: unskilled.length
88
75
  };
89
76
  }
90
77
 
91
- reset() {
92
- this.userScores.clear();
93
- }
78
+ reset() { this.userScores.clear(); }
94
79
  }
95
-
96
80
  module.exports = CohortSkillDefinition;
@@ -1,15 +1,9 @@
1
1
  /**
2
2
  * @fileoverview GEM Product Line (Pass 3)
3
- * --- FIX ---
4
- * - This calc is failing because its dependencies are failing.
5
- * - Added defensive checks for missing dependencies.
6
- * - Updated process signature to 5-arg meta standard.
3
+ * REFACTORED: Uses context.math.signals and process(context).
7
4
  */
8
5
  class PlatformConvictionDivergence {
9
-
10
- constructor() {
11
- this.result = {};
12
- }
6
+ constructor() { this.result = {}; }
13
7
 
14
8
  static getMetadata() {
15
9
  return {
@@ -21,12 +15,7 @@ class PlatformConvictionDivergence {
21
15
  };
22
16
  }
23
17
 
24
- static getDependencies() {
25
- return [
26
- 'skilled-cohort-flow',
27
- 'unskilled-cohort-flow'
28
- ];
29
- }
18
+ static getDependencies() { return ['skilled-cohort-flow', 'unskilled-cohort-flow']; }
30
19
 
31
20
  static getSchema() {
32
21
  const tickerSchema = {
@@ -38,63 +27,32 @@ class PlatformConvictionDivergence {
38
27
  },
39
28
  "required": ["skilled_conviction_change_pct", "unskilled_conviction_change_pct", "conviction_divergence_score"]
40
29
  };
41
-
42
- return {
43
- "type": "object",
44
- "description": "Tracks the divergence in 'conviction' (change in avg. position size) between skilled and unskilled cohorts.",
45
- "patternProperties": { "^.*$": tickerSchema },
46
- "additionalProperties": tickerSchema
47
- };
30
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
48
31
  }
49
32
 
50
- // --- THIS IS THE FIX ---
51
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
52
-
53
- const skilledFlow = fetchedDependencies['skilled-cohort-flow'];
54
- const unskilledFlow = fetchedDependencies['unskilled-cohort-flow'];
55
-
56
- if (!skilledFlow || !unskilledFlow) {
57
- // This is expected until the worker bug is fixed
58
- this.result = {};
59
- return;
60
- }
61
-
62
- const allTickers = new Set([
63
- ...Object.keys(skilledFlow),
64
- ...Object.keys(unskilledFlow)
65
- ]);
33
+ process(context) {
34
+ const { computed, math } = context;
35
+ const { signals } = math;
66
36
 
37
+ const tickers = signals.getUnionKeys(computed, ['skilled-cohort-flow', 'unskilled-cohort-flow']);
67
38
  const result = {};
68
39
 
69
- for (const ticker of allTickers) {
70
- const skilledData = skilledFlow[ticker];
71
- const unskilledData = unskilledFlow[ticker];
72
-
73
- // Get conviction score (avg_position_change_pct)
74
- const skilled_conviction = skilledData?.avg_position_change_pct || 0;
75
- const unskilled_conviction = unskilledData?.avg_position_change_pct || 0;
76
-
77
- if (skilled_conviction === 0 && unskilled_conviction === 0) {
78
- continue;
79
- }
40
+ for (const ticker of tickers) {
41
+ const skilled = signals.getMetric(computed, 'skilled-cohort-flow', ticker, 'avg_position_change_pct');
42
+ const unskilled = signals.getMetric(computed, 'unskilled-cohort-flow', ticker, 'avg_position_change_pct');
43
+
44
+ if (skilled === 0 && unskilled === 0) continue;
80
45
 
81
46
  result[ticker] = {
82
- skilled_conviction_change_pct: skilled_conviction,
83
- unskilled_conviction_change_pct: unskilled_conviction,
84
- conviction_divergence_score: skilled_conviction - unskilled_conviction
47
+ skilled_conviction_change_pct: skilled,
48
+ unskilled_conviction_change_pct: unskilled,
49
+ conviction_divergence_score: signals.divergence(skilled, unskilled)
85
50
  };
86
51
  }
87
-
88
52
  this.result = result;
89
53
  }
90
- // --- END FIX ---
91
-
92
- async getResult(fetchedDependencies) {
93
- return this.result;
94
- }
95
54
 
96
- reset() {
97
- this.result = {};
98
- }
55
+ async getResult() { return this.result; }
56
+ reset() { this.result = {}; }
99
57
  }
100
58
  module.exports = PlatformConvictionDivergence;
@@ -1,18 +1,10 @@
1
1
  /**
2
2
  * @fileoverview GEM Product Line (Pass 4)
3
- *
4
- * This is the final, stateless signal generator for the GEM line.
5
- * It answers: "Are skilled users buying into *positive* momentum
6
- * while unskilled users are buying into *negative* momentum?"
3
+ * REFACTORED: Uses context.math.signals and process(context).
7
4
  */
8
5
  class QuantSkillAlphaSignal {
6
+ constructor() { this.result = {}; }
9
7
 
10
- // --- STANDARD 2: ADDED ---
11
- constructor() {
12
- this.result = {};
13
- }
14
-
15
- /** Statically defines metadata */
16
8
  static getMetadata() {
17
9
  return {
18
10
  type: 'meta',
@@ -23,69 +15,36 @@ class QuantSkillAlphaSignal {
23
15
  };
24
16
  }
25
17
 
26
- /** Statically declare dependencies */
27
18
  static getDependencies() {
28
- return [
29
- 'skilled-unskilled-divergence', // from gem (Pass 3)
30
- 'cohort-momentum-state', // from gem (Pass 2)
31
- 'instrument-price-momentum-20d'// from core (Pass 1)
32
- ];
19
+ return ['skilled-unskilled-divergence', 'cohort-momentum-state', 'instrument-price-momentum-20d'];
33
20
  }
34
21
 
35
- /**
36
- * Defines the output schema for this calculation.
37
- */
38
22
  static getSchema() {
39
23
  const tickerSchema = {
40
24
  "type": "object",
41
25
  "properties": {
42
- "signal": { "type": "string", "enum": ["Strong Buy", "Buy", "Neutral", "Sell", "Strong Sell"] },
26
+ "signal": { "type": "string" },
43
27
  "gem_score": { "type": "number" },
44
- "components": {
45
- "type": "object",
46
- "properties": {
47
- "flow_divergence": { "type": "number" },
48
- "momentum_divergence": { "type": "number" },
49
- "asset_momentum": { "type": "number" }
50
- }
51
- }
28
+ "components": { "type": "object" }
52
29
  },
53
- "required": ["signal", "gem_score", "components"]
54
- };
55
-
56
- return {
57
- "type": "object",
58
- "description": "Generates a final skill-based alpha signal.",
59
- "patternProperties": { "^.*$": tickerSchema },
60
- "additionalProperties": tickerSchema
30
+ "required": ["signal", "gem_score"]
61
31
  };
62
- }
63
-
64
- _normalize(score) {
65
- return Math.tanh(score / 5.0) * 10;
32
+ return { "type": "object", "patternProperties": { "^.*$": tickerSchema } };
66
33
  }
67
34
 
68
- /**
69
- * This is a 'meta' calculation. It runs once.
70
- */
71
- // --- STANDARD 1: UPDATED SIGNATURE (added rootData) ---
72
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
73
- const { logger, calculationUtils } = dependencies;
74
-
75
- const flowDivergence = fetchedDependencies['skilled-unskilled-divergence'];
76
- const momentumExposure = fetchedDependencies['cohort-momentum-state'];
77
- const assetMomentum = fetchedDependencies['instrument-price-momentum-20d'];
35
+ process(context) {
36
+ const { computed, math } = context;
37
+ const { signals } = math;
78
38
 
79
- if (!flowDivergence || !momentumExposure || !assetMomentum) {
80
- logger.log('WARN', `[gem/quant-skill-alpha-signal] Missing dependencies for ${dateStr}.`);
81
- // --- STANDARD 2: DO NOT RETURN ---
82
- this.result = {};
83
- return;
84
- }
39
+ const flowDivergence = computed['skilled-unskilled-divergence'];
40
+ const momentumExposure = computed['cohort-momentum-state'];
41
+ const assetMomentum = computed['instrument-price-momentum-20d'];
42
+
43
+ if (!flowDivergence || !momentumExposure || !assetMomentum) return;
85
44
 
86
- const skilledMomentum = momentumExposure.skilled?.average_momentum_exposure_pct || 0;
87
- const unskilledMomentum = momentumExposure.unskilled?.average_momentum_exposure_pct || 0;
88
- const momentumDivergence = skilledMomentum - unskilledMomentum;
45
+ const skilledMom = momentumExposure.skilled?.average_momentum_exposure_pct || 0;
46
+ const unskilledMom = momentumExposure.unskilled?.average_momentum_exposure_pct || 0;
47
+ const momentumDivergence = skilledMom - unskilledMom;
89
48
 
90
49
  const result = {};
91
50
  const allTickers = Object.keys(flowDivergence);
@@ -93,32 +52,13 @@ class QuantSkillAlphaSignal {
93
52
  for (const ticker of allTickers) {
94
53
  const flowData = flowDivergence[ticker];
95
54
  const assetMom = assetMomentum[ticker]?.momentum_20d_pct || 0;
96
-
97
- // 1. Flow Divergence (Skilled buying, Unskilled selling)
98
- const flowScore = flowData.flow_divergence_score; // e.g., +1.5
99
-
100
- // 2. Momentum Divergence (Skilled buying high-mom, Unskilled buying low-mom)
101
- const momScore = momentumDivergence; // e.g., +20.0
102
-
103
- // 3. Asset Momentum (Is the asset *actually* in an uptrend?)
104
- const assetScore = assetMom; // e.g., +15.0
105
-
106
- // --- Signal Logic ---
107
- // We want all three to be positive.
108
- // 1. Skilled are buying, Unskilled are selling (flowScore > 0)
109
- // 2. Skilled are buying *more* momentum than Unskilled (momScore > 0)
110
- // 3. The asset itself has positive momentum (assetScore > 0)
55
+ const flowScore = flowData.flow_divergence_score;
111
56
 
112
- // If skilled are buying *and* they are buying into strength, that's a good sign.
113
- // We combine flow and momentum divergence.
114
- const divergenceFactor = (flowScore > 0 && momScore > 0) ? (flowScore * momScore) : 0;
57
+ const scaledFlow = flowScore * 10;
58
+ const finalScore = (scaledFlow + momentumDivergence + assetMom) / 3.0;
115
59
 
116
- // The final score is the divergence, *validated* by the asset's own momentum.
117
- // We scale flowScore (small) and momScore (large) to be comparable
118
- const scaledFlow = flowScore * 10; // (e.g., 1.5 -> 15)
119
- const finalScore = (scaledFlow + momScore + assetScore) / 3.0;
60
+ const gem_score = signals.normalizeTanh(finalScore, 10, 5.0);
120
61
 
121
- const gem_score = this._normalize(finalScore);
122
62
  let signal = "Neutral";
123
63
  if (gem_score > 7.0) signal = "Strong Buy";
124
64
  else if (gem_score > 2.5) signal = "Buy";
@@ -128,26 +68,13 @@ class QuantSkillAlphaSignal {
128
68
  result[ticker] = {
129
69
  signal: signal,
130
70
  gem_score: gem_score,
131
- components: {
132
- flow_divergence: flowScore,
133
- momentum_divergence: momScore,
134
- asset_momentum: assetScore
135
- }
71
+ components: { flow_divergence: flowScore, momentum_divergence: momentumDivergence, asset_momentum: assetMom }
136
72
  };
137
73
  }
138
-
139
- // --- STANDARD 2: SET STATE, DO NOT RETURN ---
140
74
  this.result = result;
141
75
  }
142
76
 
143
- // --- STANDARD 2: ADDED ---
144
- async getResult(fetchedDependencies) {
145
- return this.result;
146
- }
147
-
148
- // --- STANDARD 2: ADDED ---
149
- reset() {
150
- this.result = {};
151
- }
77
+ async getResult() { return this.result; }
78
+ reset() { this.result = {}; }
152
79
  }
153
80
  module.exports = QuantSkillAlphaSignal;