aiden-shared-calculations-unified 1.0.76 → 1.0.77

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 (114) hide show
  1. package/calculations/{pnl/asset_pnl_status.js → core/asset-pnl-status.js} +20 -0
  2. package/calculations/{asset_metrics/asset_position_size.js → core/asset-position-size.js} +25 -1
  3. package/calculations/{pnl/average_daily_pnl_all_users.js → core/average-daily-pnl-all-users.js} +22 -0
  4. package/calculations/{pnl/average_daily_pnl_per_sector.js → core/average-daily-pnl-per-sector.js} +22 -0
  5. package/calculations/{pnl/average_daily_pnl_per_stock.js → core/average-daily-pnl-per-stock.js} +22 -0
  6. package/calculations/{pnl/average_daily_position_pnl.js → core/average-daily-position-pnl.js} +22 -0
  7. package/calculations/{behavioural/historical/holding_duration_per_asset.js → core/holding-duration-per-asset.js} +44 -4
  8. package/calculations/{meta/gem_instrument-price-momentum.js → core/instrument-price-momentum-20d.js} +17 -4
  9. package/calculations/{short_and_long_stats/long_position_per_stock.js → core/long-position-per-stock.js} +24 -0
  10. package/calculations/{behavioural/overall_holding_duration.js → core/overall-holding-duration.js} +27 -3
  11. package/calculations/{pnl/overall_profitability_ratio.js → core/overall-profitability-ratio.js} +24 -0
  12. package/calculations/{insights/daily_buy_sell_sentiment_count.js → core/platform-buy-sell-sentiment.js} +22 -0
  13. package/calculations/{insights/historical/daily_bought_vs_sold_count.js → core/platform-daily-bought-vs-sold-count.js} +22 -0
  14. package/calculations/{insights/historical/daily_ownership_delta.js → core/platform-daily-ownership-delta.js} +22 -0
  15. package/calculations/{insights/daily_ownership_per_sector.js → core/platform-ownership-per-sector.js} +22 -0
  16. package/calculations/{insights/daily_total_positions_held.js → core/platform-total-positions-held.js} +22 -0
  17. package/calculations/{pnl/pnl_distribution_per_stock.js → core/pnl-distribution-per-stock.js} +24 -0
  18. package/calculations/{pnl/profitability_ratio_per_sector,js → core/profitability-ratio-per-sector.js} +35 -5
  19. package/calculations/{pnl/profitability_ratio_per_stock.js → core/profitability-ratio-per-stock.js} +24 -0
  20. package/calculations/{pnl/profitability_skew_per_stock.js → core/profitability-skew-per-stock.js} +24 -0
  21. package/calculations/{pnl/profitable_and_unprofitable_status.js → core/profitable-and-unprofitable-status.js} +24 -0
  22. package/calculations/{short_and_long_stats/sentiment_per_stock.js → core/sentiment-per-stock.js} +20 -0
  23. package/calculations/{short_and_long_stats/short_position_per_stock.js → core/short-position-per-stock.js} +24 -0
  24. package/calculations/{socialPosts/social_activity_aggregation.js → core/social-activity-aggregation.js} +32 -8
  25. package/calculations/{socialPosts → core}/social-asset-posts-trend.js +30 -8
  26. package/calculations/{socialPosts/social_event_correlation.js → core/social-event-correlation.js} +24 -2
  27. package/calculations/{socialPosts/social_sentiment_aggregation.js → core/social-sentiment-aggregation.js} +23 -0
  28. package/calculations/{socialPosts → core}/social-top-mentioned-words.js +31 -8
  29. package/calculations/{socialPosts → core}/social-topic-interest-evolution.js +35 -11
  30. package/calculations/{socialPosts → core}/social-topic-sentiment-matrix.js +34 -10
  31. package/calculations/{socialPosts → core}/social-word-mentions-trend.js +36 -11
  32. package/calculations/{speculators/speculator_asset_sentiment.js → core/speculator-asset-sentiment.js} +20 -0
  33. package/calculations/{speculators/speculator_danger_zone.js → core/speculator-danger-zone.js} +22 -0
  34. package/calculations/{speculators/distance_to_stop_loss_per_leverage.js → core/speculator-distance-to-stop-loss-per-leverage.js} +24 -0
  35. package/calculations/{speculators/distance_to_tp_per_leverage.js → core/speculator-distance-to-tp-per-leverage.js} +24 -0
  36. package/calculations/{speculators/entry_distance_to_sl_per_leverage.js → core/speculator-entry-distance-to-sl-per-leverage.js} +24 -0
  37. package/calculations/{speculators/entry_distance_to_tp_per_leverage.js → core/speculator-entry-distance-to-tp-per-leverage.js} +24 -0
  38. package/calculations/{speculators/leverage_per_asset.js → core/speculator-leverage-per-asset.js} +20 -0
  39. package/calculations/{speculators/leverage_per_sector.js → core/speculator-leverage-per-sector.js} +22 -0
  40. package/calculations/{speculators/risk_reward_ratio_per_asset.js → core/speculator-risk-reward-ratio-per-asset.js} +20 -0
  41. package/calculations/{speculators/stop_loss_distance_by_sector_short_long_breakdown.js → core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js} +22 -0
  42. package/calculations/{speculators/stop_loss_distance_by_ticker_short_long_breakdown.js → core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js} +24 -0
  43. package/calculations/{speculators/stop_loss_per_asset.js → core/speculator-stop-loss-per-asset.js} +24 -0
  44. package/calculations/{speculators/take_profit_per_asset.js → core/speculator-take-profit-per-asset.js} +24 -0
  45. package/calculations/{speculators/tsl_per_asset.js → core/speculator-tsl-per-asset.js} +24 -0
  46. package/calculations/{short_and_long_stats/total_long_figures.js → core/total-long-figures.js} +24 -0
  47. package/calculations/{sectors/total_long_per_sector.js → core/total-long-per-sector.js} +22 -0
  48. package/calculations/{short_and_long_stats/total_short_figures.js → core/total-short-figures.js} +24 -0
  49. package/calculations/{sectors/total_short_per_sector.js → core/total-short-per-sector.js} +22 -0
  50. package/calculations/{sanity/users_processed.js → core/users-processed.js} +22 -0
  51. package/calculations/gauss/cohort-capital-flow.js +216 -0
  52. package/calculations/gauss/cohort-definer.js +211 -0
  53. package/calculations/gauss/daily-dna-filter.js +130 -0
  54. package/calculations/gauss/gauss-divergence-signal.js +160 -0
  55. package/calculations/{meta/gem_cohort-momentum-state.js → gem/cohort-momentum-state.js} +22 -7
  56. package/calculations/{behavioural/historical/gem_cohort-skill-definition.js → gem/cohort-skill-definition.js} +18 -1
  57. package/calculations/{sentiment/gem_platform-conviction-divergence.js → gem/platform-conviction-divergence.js} +13 -0
  58. package/calculations/{meta/gem_quant-skill-alpha-signal.js → gem/quant-skill-alpha-signal.js} +25 -8
  59. package/calculations/{behavioural/historical/gem_skilled-cohort-flow.js → gem/skilled-cohort-flow.js} +16 -2
  60. package/calculations/{meta/gem_skilled-unskilled-divergence.js → gem/skilled-unskilled-divergence.js} +18 -4
  61. package/calculations/{behavioural/historical/gem_unskilled-cohort-flow.js → gem/unskilled-cohort-flow.js} +16 -2
  62. package/calculations/helix/helix-contrarian-signal.js +154 -0
  63. package/calculations/helix/herd-consensus-score.js +152 -0
  64. package/calculations/helix/winner-loser-flow.js +206 -0
  65. package/calculations/{behavioural/historical → legacy}/asset_crowd_flow.js +1 -1
  66. package/calculations/{sentiment/historical → legacy}/crowd_conviction_score.js +1 -1
  67. package/calculations/{activity/historical → legacy}/daily_asset_activity.js +1 -1
  68. package/calculations/{behavioural/historical → legacy}/dumb-cohort-flow.js +1 -1
  69. package/calculations/{behavioural/historical → legacy}/in_loss_asset_crowd_flow.js +1 -1
  70. package/calculations/{behavioural/historical → legacy}/in_profit_asset_crowd_flow.js +1 -1
  71. package/calculations/{speculators/historical → legacy}/risk_appetite_change.js +3 -1
  72. package/calculations/{sectors/historical → legacy}/sector_rotation.js +1 -1
  73. package/calculations/{behavioural/historical → legacy}/smart-cohort-flow.js +1 -1
  74. package/calculations/{behavioural/historical → legacy}/smart_money_flow.js +1 -1
  75. package/calculations/{behavioural/historical → legacy}/user-investment-profile.js +2 -2
  76. package/calculations/pyro/risk-appetite-index.js +153 -0
  77. package/calculations/pyro/squeeze-potential.js +158 -0
  78. package/calculations/pyro/volatility-signal.js +133 -0
  79. package/package.json +1 -1
  80. package/calculations/socialPosts/gem_social_sentiment_aggregation.js +0 -146
  81. /package/calculations/{activity/historical → legacy}/activity_by_pnl_status.js +0 -0
  82. /package/calculations/{meta → legacy}/capital_deployment_strategy.js +0 -0
  83. /package/calculations/{meta → legacy}/capital_liquidation_performance.js +0 -0
  84. /package/calculations/{meta → legacy}/capital_vintage_performance.js +0 -0
  85. /package/calculations/{meta → legacy}/cash-flow-deployment.js +0 -0
  86. /package/calculations/{meta → legacy}/cash-flow-liquidation.js +0 -0
  87. /package/calculations/{capital_flow/historical → legacy}/crowd-cash-flow-proxy.js +0 -0
  88. /package/calculations/{meta → legacy}/crowd_sharpe_ratio_proxy.js +0 -0
  89. /package/calculations/{activity/historical → legacy}/daily_user_activity_tracker.js +0 -0
  90. /package/calculations/{capital_flow/historical → legacy}/deposit_withdrawal_percentage.js +0 -0
  91. /package/calculations/{sectors/historical → legacy}/diversification_pnl.js +0 -0
  92. /package/calculations/{behavioural/historical → legacy}/drawdown_response.js +0 -0
  93. /package/calculations/{behavioural/historical → legacy}/gain_response.js +0 -0
  94. /package/calculations/{behavioural/historical → legacy}/historical_performance_aggregator.js +0 -0
  95. /package/calculations/{meta → legacy}/negative_expectancy_cohort_flow.js +0 -0
  96. /package/calculations/{capital_flow/historical → legacy}/new_allocation_percentage.js +0 -0
  97. /package/calculations/{behavioural/historical → legacy}/paper_vs_diamond_hands.js +0 -0
  98. /package/calculations/{behavioural/historical → legacy}/position_count_pnl.js +0 -0
  99. /package/calculations/{meta → legacy}/positive_expectancy_cohort_flow.js +0 -0
  100. /package/calculations/{meta → legacy}/profit_cohort_divergence.js +0 -0
  101. /package/calculations/{pnl/historical → legacy}/profitability_migration.js +0 -0
  102. /package/calculations/{capital_flow/historical → legacy}/reallocation_increase_percentage.js +0 -0
  103. /package/calculations/{meta → legacy}/shark_attack_signal.js +0 -0
  104. /package/calculations/{meta → legacy}/smart-dumb-divergence-index.js +0 -0
  105. /package/calculations/{meta → legacy}/smart_dumb_divergence_index_v2.js +0 -0
  106. /package/calculations/{meta → legacy}/social-predictive-regime-state.js +0 -0
  107. /package/calculations/{meta → legacy}/social-topic-driver-index.js +0 -0
  108. /package/calculations/{meta → legacy}/social-topic-predictive-potential.js +0 -0
  109. /package/calculations/{meta → legacy}/social_flow_correlation.js +0 -0
  110. /package/calculations/{activity/historical → legacy}/speculator_adjustment_activity.js +0 -0
  111. /package/calculations/{backtests → legacy}/strategy-performance.js +0 -0
  112. /package/calculations/{speculators/historical → legacy}/tsl_effectiveness.js +0 -0
  113. /package/calculations/{meta → legacy}/user_expectancy_score.js +0 -0
  114. /package/calculations/{pnl/historical → legacy}/user_profitability_tracker.js +0 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * @fileoverview GAUSS Product Line (Pass 4)
3
+ *
4
+ * This is the final, stateless signal generator for the Gauss line.
5
+ * It answers: "What is the net 'Smart vs. Dumb' flow divergence?"
6
+ *
7
+ * It consumes the daily flows from all defined cohorts (Pass 3)
8
+ * and aggregates them into a final, actionable signal.
9
+ */
10
+ class GaussDivergenceSignal {
11
+
12
+ /**
13
+ * Defines the output schema for this calculation.
14
+ * @returns {object} JSON Schema object
15
+ */
16
+ static getSchema() {
17
+ const tickerSchema = {
18
+ "type": "object",
19
+ "properties": {
20
+ "signal": {
21
+ "type": "string",
22
+ "enum": ["Strong Buy", "Buy", "Neutral", "Sell", "Strong Sell"]
23
+ },
24
+ "gauss_score": {
25
+ "type": "number",
26
+ "description": "Final divergence score (-10 to +10). Positive = Smart Buying, Negative = Smart Selling."
27
+ },
28
+ "smart_flow_total_pct": {
29
+ "type": "number",
30
+ "description": "The total %-point flow from all 'Smart' cohorts."
31
+ },
32
+ "dumb_flow_total_pct": {
33
+ "type": "number",
34
+ "description": "The total %-point flow from all 'Dumb' cohorts."
35
+ }
36
+ },
37
+ "required": ["signal", "gauss_score", "smart_flow_total_pct", "dumb_flow_total_pct"]
38
+ };
39
+
40
+ return {
41
+ "type": "object",
42
+ "description": "Generates a final divergence signal by aggregating flow from Smart vs. Dumb cohorts.",
43
+ "patternProperties": {
44
+ "^.*$": tickerSchema // Ticker
45
+ },
46
+ "additionalProperties": tickerSchema
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Statically defines all metadata for the manifest builder.
52
+ */
53
+ static getMetadata() {
54
+ return {
55
+ type: 'meta',
56
+ rootDataDependencies: [],
57
+ isHistorical: false,
58
+ userType: 'n/a',
59
+ category: 'gauss'
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Statically declare dependencies.
65
+ */
66
+ static getDependencies() {
67
+ return [
68
+ 'cohort-capital-flow' // from gauss (Pass 3)
69
+ ];
70
+ }
71
+
72
+ /**
73
+ * Simple tanh normalization. Scales any number to a -10 to +10 range,
74
+ * with diminishing returns for extreme values.
75
+ * 0 -> 0, 1 -> 7.6, 2 -> 9.6, 3 -> 9.95
76
+ * We scale the input to tune sensitivity.
77
+ */
78
+ _normalize(score) {
79
+ // A score of 2.0 (%-point flow) will be ~9.6
80
+ // A score of 1.0 (%-point flow) will be ~7.6
81
+ return Math.tanh(score / 2.0) * 10;
82
+ }
83
+
84
+ /**
85
+ * This is a 'meta' calculation. It runs once.
86
+ */
87
+ async process(dateStr, dependencies, config, fetchedDependencies) {
88
+ const { logger } = dependencies;
89
+
90
+ const cohortFlows = fetchedDependencies['cohort-capital-flow'];
91
+
92
+ if (!cohortFlows) {
93
+ logger.log('WARN', `[gauss/gauss-divergence-signal] Missing dependency 'cohort-capital-flow' for ${dateStr}.`);
94
+ return {};
95
+ }
96
+
97
+ // Define which cohorts are "Smart" and which are "Dumb"
98
+ // These names must match the keys from Pass 2
99
+ const SMART_COHORTS = ['smart_investors', 'smart_scalpers'];
100
+ const DUMB_COHORTS = ['fomo_chasers', 'patient_losers', 'fomo_bagholders'];
101
+
102
+ const blendedFlows = new Map(); // Map<ticker, { smart: 0, dumb: 0 }>
103
+
104
+ // 1. Blend all cohort flows into two buckets
105
+ for (const cohortName in cohortFlows) {
106
+ const isSmart = SMART_COHORTS.includes(cohortName);
107
+ const isDumb = DUMB_COHORTS.includes(cohortName);
108
+ if (!isSmart && !isDumb) continue;
109
+
110
+ const assets = cohortFlows[cohortName]?.assets;
111
+ if (!assets) continue;
112
+
113
+ for (const [ticker, data] of Object.entries(assets)) {
114
+ if (!blendedFlows.has(ticker)) {
115
+ blendedFlows.set(ticker, { smart: 0, dumb: 0 });
116
+ }
117
+
118
+ // Use net_flow_contribution, which is the %-point flow
119
+ const flow = data.net_flow_contribution || 0;
120
+
121
+ if (isSmart) {
122
+ blendedFlows.get(ticker).smart += flow;
123
+ } else if (isDumb) {
124
+ blendedFlows.get(ticker).dumb += flow;
125
+ }
126
+ }
127
+ }
128
+
129
+ // 2. Calculate final signal
130
+ const result = {};
131
+ for (const [ticker, data] of blendedFlows.entries()) {
132
+
133
+ // The core signal is the divergence: (Smart Flow - Dumb Flow)
134
+ // If Smart buys (+1) and Dumb sells (-1), score is +2.
135
+ // If Smart sells (-1) and Dumb buys (+1), score is -2.
136
+ const divergence = data.smart - data.dumb;
137
+
138
+ // Normalize the score to a -10 to +10 range
139
+ const gauss_score = this._normalize(divergence);
140
+
141
+ let signal = "Neutral";
142
+ if (gauss_score > 7.0) signal = "Strong Buy"; // e.g., > 1.5% net divergence
143
+ else if (gauss_score > 2.0) signal = "Buy";
144
+ else if (gauss_score < -7.0) signal = "Strong Sell";
145
+ else if (gauss_score < -2.0) signal = "Sell";
146
+
147
+ result[ticker] = {
148
+ signal: signal,
149
+ gauss_score: gauss_score,
150
+ smart_flow_total_pct: data.smart,
151
+ dumb_flow_total_pct: data.dumb
152
+ };
153
+ }
154
+
155
+ // Final output is compact and non-sharded.
156
+ return result;
157
+ }
158
+ }
159
+
160
+ module.exports = GaussDivergenceSignal;
@@ -48,14 +48,27 @@ class CohortMomentumState {
48
48
  };
49
49
  }
50
50
 
51
+ /**
52
+ * Statically defines all metadata for the manifest builder.
53
+ */
54
+ static getMetadata() {
55
+ return {
56
+ type: 'meta',
57
+ rootDataDependencies: [],
58
+ isHistorical: false,
59
+ userType: 'n/a',
60
+ category: 'gem'
61
+ };
62
+ }
63
+
51
64
  /**
52
65
  * Statically declare dependencies.
53
66
  */
54
67
  static getDependencies() {
55
68
  return [
56
- 'gem_skilled-cohort-flow', // Pass 2
57
- 'gem_unskilled-cohort-flow', // Pass 2
58
- 'gem_instrument-price-momentum' // Pass 1
69
+ 'skilled-cohort-flow', // Pass 2 (gem)
70
+ 'unskilled-cohort-flow', // Pass 2 (gem)
71
+ 'instrument-price-momentum-20d' // Pass 1 (core)
59
72
  ];
60
73
  }
61
74
 
@@ -68,9 +81,10 @@ class CohortMomentumState {
68
81
  * @param {object} fetchedDependencies - Results from previous passes.
69
82
  */
70
83
  getResult(fetchedDependencies) {
71
- const skilledFlowData = fetchedDependencies['gem_skilled-cohort-flow']?.assets;
72
- const unskilledFlowData = fetchedDependencies['gem_unskilled-cohort-flow']?.assets;
73
- const momentumData = fetchedDependencies['gem_instrument-price-momentum'];
84
+ // FIX: Use normalized dependency names
85
+ const skilledFlowData = fetchedDependencies['skilled-cohort-flow']?.assets;
86
+ const unskilledFlowData = fetchedDependencies['unskilled-cohort-flow']?.assets;
87
+ const momentumData = fetchedDependencies['instrument-price-momentum-20d'];
74
88
 
75
89
  if (!skilledFlowData || !unskilledFlowData || !momentumData) {
76
90
  return {};
@@ -79,7 +93,8 @@ class CohortMomentumState {
79
93
  const result = {};
80
94
  const allTickers = new Set([
81
95
  ...Object.keys(skilledFlowData),
82
- ...Object.keys(unskilledFlowData)
96
+ ...Object.keys(unskilledFlowData),
97
+ ...Object.keys(momentumData) // Include momentum tickers
83
98
  ]);
84
99
 
85
100
  for (const ticker of allTickers) {
@@ -39,6 +39,19 @@ class CohortSkillDefinition {
39
39
  };
40
40
  }
41
41
 
42
+ /**
43
+ * Statically defines all metadata for the manifest builder.
44
+ */
45
+ static getMetadata() {
46
+ return {
47
+ type: 'standard',
48
+ rootDataDependencies: ['history'], // This calculation uses history data
49
+ isHistorical: false, // It processes today's history doc
50
+ userType: 'all',
51
+ category: 'gem'
52
+ };
53
+ }
54
+
42
55
  /**
43
56
  * This is a Pass 1 calculation and has no dependencies.
44
57
  */
@@ -52,7 +65,11 @@ class CohortSkillDefinition {
52
65
  * @param {string} userId - The user ID.
53
66
  */
54
67
  process(rootData, userId) {
55
- const history = rootData?.history?.all;
68
+ // FIX: The process signature for a 'standard' calc is different.
69
+ // It receives (todayPortfolio, yesterdayPortfolio, userId, context, ...)
70
+ // The 'history' data is inside todayPortfolio.
71
+
72
+ const history = rootData?.history?.all; // Correctly accessing history data
56
73
  if (!history) {
57
74
  return; // Skip user if they have no history data
58
75
  }
@@ -51,6 +51,19 @@ class PlatformConvictionDivergence {
51
51
  };
52
52
  }
53
53
 
54
+ /**
55
+ * Statically defines all metadata for the manifest builder.
56
+ */
57
+ static getMetadata() {
58
+ return {
59
+ type: 'standard',
60
+ rootDataDependencies: ['portfolio', 'insights'],
61
+ isHistorical: false,
62
+ userType: 'all',
63
+ category: 'gem'
64
+ };
65
+ }
66
+
54
67
  /**
55
68
  * This is a Pass 1 calculation and has no dependencies.
56
69
  */
@@ -54,16 +54,32 @@ class QuantSkillAlphaSignal {
54
54
  "additionalProperties": tickerSchema
55
55
  };
56
56
  }
57
+
58
+ /**
59
+ * Statically defines all metadata for the manifest builder.
60
+ */
61
+ static getMetadata() {
62
+ return {
63
+ type: 'meta',
64
+ rootDataDependencies: [],
65
+ isHistorical: false,
66
+ userType: 'n/a',
67
+ category: 'gem'
68
+ };
69
+ }
57
70
 
58
71
  /**
59
72
  * Statically declare dependencies.
60
73
  */
61
74
  static getDependencies() {
62
75
  return [
63
- 'gem_skilled-unskilled-divergence', // Pass 3
64
- 'gem_cohort-momentum-state', // Pass 3
65
- 'gem_platform-conviction-divergence', // Pass 1
66
- 'gem_social_sentiment_aggregation' // Pass 1
76
+ // Internal 'gem' dependencies (normalized)
77
+ 'skilled-unskilled-divergence',
78
+ 'cohort-momentum-state',
79
+ 'platform-conviction-divergence',
80
+
81
+ // Dependencies on 'core' product line (normalized)
82
+ 'social-sentiment-aggregation'
67
83
  ];
68
84
  }
69
85
 
@@ -76,10 +92,11 @@ class QuantSkillAlphaSignal {
76
92
  * @param {object} fetchedDependencies - Results from previous passes.
77
93
  */
78
94
  getResult(fetchedDependencies) {
79
- const divergenceData = fetchedDependencies['gem_skilled-unskilled-divergence']?.assets;
80
- const momentumData = fetchedDependencies['gem_cohort-momentum-state'];
81
- const platformData = fetchedDependencies['gem_platform-conviction-divergence'];
82
- const socialData = fetchedDependencies['gem_social_sentiment_aggregation']?.per_ticker;
95
+ // FIX: Use normalized dependency names
96
+ const divergenceData = fetchedDependencies['skilled-unskilled-divergence']?.assets;
97
+ const momentumData = fetchedDependencies['cohort-momentum-state'];
98
+ const platformData = fetchedDependencies['platform-conviction-divergence'];
99
+ const socialData = fetchedDependencies['social-sentiment-aggregation']?.per_ticker;
83
100
 
84
101
  if (!divergenceData || !momentumData || !platformData || !socialData) {
85
102
  return {}; // Missing one or more key dependencies
@@ -57,11 +57,24 @@ class SkilledCohortFlow {
57
57
  };
58
58
  }
59
59
 
60
+ /**
61
+ * Statically defines all metadata for the manifest builder.
62
+ */
63
+ static getMetadata() {
64
+ return {
65
+ type: 'standard',
66
+ rootDataDependencies: ['portfolio'],
67
+ isHistorical: true, // Compares today vs yesterday portfolio
68
+ userType: 'all',
69
+ category: 'gem'
70
+ };
71
+ }
72
+
60
73
  /**
61
74
  * Statically declare dependencies.
62
75
  */
63
76
  static getDependencies() {
64
- return ['gem_cohort-skill-definition']; // Pass 1
77
+ return ['cohort-skill-definition']; // Pass 1
65
78
  }
66
79
 
67
80
  _getPortfolioPositions(portfolio) {
@@ -93,7 +106,8 @@ class SkilledCohortFlow {
93
106
  return this.skilledCohortUserIds;
94
107
  }
95
108
 
96
- const cohortData = fetchedDependencies['gem_cohort-skill-definition'];
109
+ // FIX: Use normalized dependency name
110
+ const cohortData = fetchedDependencies['cohort-skill-definition'];
97
111
  if (!cohortData || !cohortData.skilled_user_ids) {
98
112
  return new Set();
99
113
  }
@@ -51,13 +51,26 @@ class SkilledUnskilledDivergence {
51
51
  };
52
52
  }
53
53
 
54
+ /**
55
+ * Statically defines all metadata for the manifest builder.
56
+ */
57
+ static getMetadata() {
58
+ return {
59
+ type: 'meta',
60
+ rootDataDependencies: [],
61
+ isHistorical: false,
62
+ userType: 'n/a',
63
+ category: 'gem'
64
+ };
65
+ }
66
+
54
67
  /**
55
68
  * Statically declare dependencies.
56
69
  */
57
70
  static getDependencies() {
58
71
  return [
59
- 'gem_skilled-cohort-flow', // Pass 2
60
- 'gem_unskilled-cohort-flow' // Pass 2
72
+ 'skilled-cohort-flow', // Pass 2
73
+ 'unskilled-cohort-flow' // Pass 2
61
74
  ];
62
75
  }
63
76
 
@@ -119,8 +132,9 @@ class SkilledUnskilledDivergence {
119
132
  * @param {object} fetchedDependencies - Results from Pass 2.
120
133
  */
121
134
  getResult(fetchedDependencies) {
122
- const skilledFlowData = fetchedDependencies['gem_skilled-cohort-flow'];
123
- const unskilledFlowData = fetchedDependencies['gem_unskilled-cohort-flow'];
135
+ // FIX: Use normalized dependency names
136
+ const skilledFlowData = fetchedDependencies['skilled-cohort-flow'];
137
+ const unskilledFlowData = fetchedDependencies['unskilled-cohort-flow'];
124
138
 
125
139
  const assetResult = this._calculateDivergence(skilledFlowData?.assets, unskilledFlowData?.assets);
126
140
  const sectorResult = this._calculateDivergence(skilledFlowData?.sectors, unskilledFlowData?.sectors);
@@ -57,11 +57,24 @@ class UnskilledCohortFlow {
57
57
  };
58
58
  }
59
59
 
60
+ /**
61
+ * Statically defines all metadata for the manifest builder.
62
+ */
63
+ static getMetadata() {
64
+ return {
65
+ type: 'standard',
66
+ rootDataDependencies: ['portfolio'],
67
+ isHistorical: true, // Compares today vs yesterday portfolio
68
+ userType: 'all',
69
+ category: 'gem'
70
+ };
71
+ }
72
+
60
73
  /**
61
74
  * Statically declare dependencies.
62
75
  */
63
76
  static getDependencies() {
64
- return ['gem_cohort-skill-definition']; // Pass 1
77
+ return ['cohort-skill-definition']; // Pass 1
65
78
  }
66
79
 
67
80
  _getPortfolioPositions(portfolio) {
@@ -93,7 +106,8 @@ class UnskilledCohortFlow {
93
106
  return this.unskilledCohortUserIds;
94
107
  }
95
108
 
96
- const cohortData = fetchedDependencies['gem_cohort-skill-definition'];
109
+ // FIX: Use normalized dependency name
110
+ const cohortData = fetchedDependencies['cohort-skill-definition'];
97
111
  if (!cohortData || !cohortData.unskilled_user_ids) {
98
112
  return new Set();
99
113
  }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @fileoverview HELIX Product Line (Pass 4)
3
+ *
4
+ * This metric answers: "What is the final HELIX contrarian signal?"
5
+ *
6
+ * It combines the 'Herd Consensus' with the 'Winner Cohort Flow'
7
+ * to generate actionable signals.
8
+ *
9
+ * HYPOTHESIS:
10
+ * - If Herd is in Euphoria (> +8) AND Winners are Selling = "Strong Sell"
11
+ * - If Herd is in Capitulation (< -8) AND Winners are Buying = "Strong Buy"
12
+ */
13
+ class HelixContrarianSignal {
14
+
15
+ /**
16
+ * Defines the output schema for this calculation.
17
+ * @returns {object} JSON Schema object
18
+ */
19
+ static getSchema() {
20
+ const tickerSchema = {
21
+ "type": "object",
22
+ "properties": {
23
+ "signal": {
24
+ "type": "string",
25
+ "enum": ["Capitulation (Strong Buy)", "Euphoria (Strong Sell)", "Confirmation (Buy)", "Confirmation (Sell)", "FOMO (Losers Buying)", "Panic (Losers Selling)", "Neutral"]
26
+ },
27
+ "consensus_score": {
28
+ "type": "number",
29
+ "description": "The Herd Consensus Score (-10 to +10)."
30
+ },
31
+ "net_winner_flow": {
32
+ "type": "number",
33
+ "description": "Net unique winners who joined/left the asset."
34
+ },
35
+ "net_loser_flow": {
36
+ "type": "number",
37
+ "description": "Net unique losers who joined/left the asset."
38
+ }
39
+ },
40
+ "required": ["signal", "consensus_score", "net_winner_flow", "net_loser_flow"]
41
+ };
42
+
43
+ return {
44
+ "type": "object",
45
+ "description": "Generates final contrarian signals by comparing herd consensus to winner/loser cohort flows.",
46
+ "patternProperties": {
47
+ "^.*$": tickerSchema // Ticker
48
+ },
49
+ "additionalProperties": tickerSchema
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Statically defines all metadata for the manifest builder.
55
+ */
56
+ static getMetadata() {
57
+ return {
58
+ type: 'meta',
59
+ rootDataDependencies: [],
60
+ isHistorical: false,
61
+ userType: 'n/a',
62
+ category: 'helix'
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Statically declare dependencies.
68
+ */
69
+ static getDependencies() {
70
+ return [
71
+ 'winner-loser-flow', // from helix (Pass 2)
72
+ 'herd-consensus-score' // from helix (Pass 3)
73
+ ];
74
+ }
75
+
76
+ /**
77
+ * This is a 'meta' calculation. It runs once.
78
+ */
79
+ async process(dateStr, dependencies, config, fetchedDependencies) {
80
+ const { logger } = dependencies;
81
+
82
+ const flowData = fetchedDependencies['winner-loser-flow'];
83
+ const consensusData = fetchedDependencies['herd-consensus-score'];
84
+
85
+ if (!flowData || !consensusData) {
86
+ logger.log('WARN', `[helix/helix-contrarian-signal] Missing dependencies for ${dateStr}. Skipping.`);
87
+ return {};
88
+ }
89
+
90
+ const allTickers = new Set([
91
+ ...Object.keys(flowData),
92
+ ...Object.keys(consensusData)
93
+ ]);
94
+
95
+ const result = {};
96
+ const CONSENSUS_THRESHOLD = 8.0; // Score for "Extreme"
97
+ const FLOW_THRESHOLD = 5; // Min 5 users moving
98
+
99
+ for (const ticker of allTickers) {
100
+ const consensus = consensusData[ticker]?.consensus_score || 0;
101
+ const winner_flow = flowData[ticker]?.net_winner_flow || 0;
102
+ const loser_flow = flowData[ticker]?.net_loser_flow || 0;
103
+
104
+ let signal = "Neutral";
105
+
106
+ // --- Primary Contrarian Signals ---
107
+
108
+ // 1. Capitulation: Herd is at extreme fear, but winners are buying.
109
+ if (consensus < -CONSENSUS_THRESHOLD && winner_flow > FLOW_THRESHOLD) {
110
+ signal = "Capitulation (Strong Buy)";
111
+ }
112
+ // 2. Euphoria: Herd is at extreme greed, but winners are selling.
113
+ else if (consensus > CONSENSUS_THRESHOLD && winner_flow < -FLOW_THRESHOLD) {
114
+ signal = "Euphoria (Strong Sell)";
115
+ }
116
+
117
+ // --- Secondary Confirmation Signals ---
118
+
119
+ // 3. Confirmation (Buy): Herd is bullish, winners are also buying.
120
+ else if (consensus > 0 && winner_flow > FLOW_THRESHOLD) {
121
+ signal = "Confirmation (Buy)";
122
+ }
123
+ // 4. Confirmation (Sell): Herd is bearish, winners are also selling.
124
+ else if (consensus < 0 && winner_flow < -FLOW_THRESHOLD) {
125
+ signal = "Confirmation (Sell)";
126
+ }
127
+
128
+ // --- Tertiary "Dumb Money" Signals (Warning signs) ---
129
+
130
+ // 5. FOMO: Herd is euphoric, and losers are piling in.
131
+ else if (consensus > CONSENSUS_THRESHOLD && loser_flow > FLOW_THRESHOLD) {
132
+ signal = "FOMO (Losers Buying)";
133
+ }
134
+ // 6. Panic: Herd is fearful, and losers are capitulating.
135
+ else if (consensus < -CONSENSUS_THRESHOLD && loser_flow < -FLOW_THRESHOLD) {
136
+ signal = "Panic (Losers Selling)";
137
+ }
138
+
139
+ // Add to result if not neutral
140
+ if (signal !== "Neutral") {
141
+ result[ticker] = {
142
+ signal: signal,
143
+ consensus_score: consensus,
144
+ net_winner_flow: winner_flow,
145
+ net_loser_flow: loser_flow
146
+ };
147
+ }
148
+ }
149
+
150
+ return result;
151
+ }
152
+ }
153
+
154
+ module.exports = HelixContrarianSignal;