aiden-shared-calculations-unified 1.0.63 → 1.0.65

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 (89) hide show
  1. package/README.MD +1 -1
  2. package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
  3. package/calculations/activity/historical/daily_asset_activity.js +42 -0
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
  6. package/calculations/asset_metrics/asset_position_size.js +36 -0
  7. package/calculations/backtests/strategy-performance.js +41 -0
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
  9. package/calculations/behavioural/historical/drawdown_response.js +113 -35
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
  11. package/calculations/behavioural/historical/gain_response.js +113 -34
  12. package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
  13. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
  14. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
  15. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
  16. package/calculations/behavioural/historical/position_count_pnl.js +91 -39
  17. package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
  18. package/calculations/behavioural/historical/smart_money_flow.js +160 -151
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
  22. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
  24. package/calculations/insights/daily_total_positions_held.js +28 -24
  25. package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
  26. package/calculations/insights/historical/daily_ownership_delta.js +95 -32
  27. package/calculations/meta/capital_deployment_strategy.js +78 -110
  28. package/calculations/meta/capital_liquidation_performance.js +114 -111
  29. package/calculations/meta/cash-flow-deployment.js +114 -107
  30. package/calculations/meta/cash-flow-liquidation.js +114 -107
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
  32. package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
  33. package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
  34. package/calculations/meta/profit_cohort_divergence.js +83 -59
  35. package/calculations/meta/shark_attack_signal.js +91 -39
  36. package/calculations/meta/smart-dumb-divergence-index.js +114 -98
  37. package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
  38. package/calculations/meta/social-predictive-regime-state.js +76 -155
  39. package/calculations/meta/social-topic-driver-index.js +74 -127
  40. package/calculations/meta/user_expectancy_score.js +83 -31
  41. package/calculations/pnl/asset_pnl_status.js +120 -31
  42. package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
  43. package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
  44. package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
  45. package/calculations/pnl/average_daily_position_pnl.js +49 -21
  46. package/calculations/pnl/historical/profitability_migration.js +81 -35
  47. package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
  48. package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
  49. package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
  50. package/calculations/pnl/profitability_skew_per_stock.js +86 -31
  51. package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
  52. package/calculations/sanity/users_processed.js +24 -1
  53. package/calculations/sectors/historical/diversification_pnl.js +104 -42
  54. package/calculations/sectors/historical/sector_rotation.js +94 -45
  55. package/calculations/sectors/total_long_per_sector.js +55 -20
  56. package/calculations/sectors/total_short_per_sector.js +55 -20
  57. package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
  58. package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
  59. package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
  60. package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
  61. package/calculations/short_and_long_stats/total_long_figures.js +34 -13
  62. package/calculations/short_and_long_stats/total_short_figures.js +34 -14
  63. package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
  64. package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
  65. package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
  66. package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
  67. package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
  68. package/calculations/socialPosts/social_activity_aggregation.js +106 -77
  69. package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
  70. package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
  71. package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
  72. package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
  73. package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
  74. package/calculations/speculators/historical/risk_appetite_change.js +89 -32
  75. package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
  76. package/calculations/speculators/holding_duration_per_asset.js +83 -23
  77. package/calculations/speculators/leverage_per_asset.js +68 -19
  78. package/calculations/speculators/leverage_per_sector.js +86 -25
  79. package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
  80. package/calculations/speculators/speculator_asset_sentiment.js +100 -48
  81. package/calculations/speculators/speculator_danger_zone.js +101 -33
  82. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
  83. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
  84. package/calculations/speculators/stop_loss_per_asset.js +94 -26
  85. package/calculations/speculators/take_profit_per_asset.js +95 -27
  86. package/calculations/speculators/tsl_per_asset.js +77 -23
  87. package/package.json +1 -1
  88. package/utils/price_data_provider.js +142 -142
  89. package/utils/sector_mapping_provider.js +74 -74
@@ -1,60 +1,112 @@
1
1
  /**
2
- * @fileoverview Pass 4: Identifies "Shark Attack" signals where
3
- * the positive expectancy cohort is buying what the in-loss cohort
4
- * is panic-selling.
2
+ * @fileoverview Calculation (Pass 4) for "Shark Attack" signal.
3
+ *
4
+ * This signal identifies when the "Smart Cohort" is behaving
5
+ * differently from both the "Dumb Cohort" and the "In-Loss Cohort"
6
+ * simultaneously.
7
+ *
8
+ * e.g., Smart buying, while Dumb AND Loss cohorts are selling.
9
+ *
10
+ * It *depends* on 'smart-cohort-flow', 'dumb-cohort-flow',
11
+ * and 'in_loss_asset_crowd_flow'.
5
12
  */
6
13
  class SharkAttackSignal {
14
+ constructor() {
15
+ // No per-user processing
16
+ }
7
17
 
8
- static getDependencies() {
9
- // Depends on Pass 3 "smart" flow and Pass 1 "in-loss" flow
10
- return ['positive-expectancy-cohort-flow', 'in-loss-asset-crowd-flow'];
18
+ /**
19
+ * Defines the output schema for this calculation.
20
+ * @returns {object} JSON Schema object
21
+ */
22
+ static getSchema() {
23
+ const signalSchema = {
24
+ "type": "object",
25
+ "properties": {
26
+ "status": {
27
+ "type": "string",
28
+ "enum": ["Shark Attack (Buy)", "Shark Bait (Sell)", "Neutral"]
29
+ },
30
+ "smart_flow_pct": { "type": "number" },
31
+ "dumb_flow_pct": { "type": "number" },
32
+ "loss_cohort_flow_pct": { "type": "number" }
33
+ },
34
+ "required": ["status", "smart_flow_pct", "dumb_flow_pct", "loss_cohort_flow_pct"]
35
+ };
36
+
37
+ return {
38
+ "type": "object",
39
+ "description": "Generates 'Shark Attack' signals where Smart Cohort flow diverges from both Dumb and In-Loss cohorts.",
40
+ "patternProperties": {
41
+ "^.*$": signalSchema // Ticker
42
+ },
43
+ "additionalProperties": signalSchema
44
+ };
11
45
  }
12
-
13
- constructor() {
14
- this.buyThreshold = 0.005; // Min "smart" buy-in
15
- this.sellThreshold = -0.005; // Min "in-loss" capitulation
46
+
47
+ /**
48
+ * Statically declare dependencies.
49
+ */
50
+ static getDependencies() {
51
+ return [
52
+ 'smart-cohort-flow', // Pass 3
53
+ 'dumb-cohort-flow', // Pass 3
54
+ 'in_loss_asset_crowd_flow'// Pass 3
55
+ ];
16
56
  }
17
57
 
18
- async process(dateStr, dependencies, config, fetchedDependencies) {
19
- const { logger } = dependencies;
58
+ process() {
59
+ // No-op
60
+ }
20
61
 
21
- const smartData = fetchedDependencies['positive-expectancy-cohort-flow'];
22
- const lossData = fetchedDependencies['in-loss-asset-crowd-flow'];
62
+ getResult(fetchedDependencies) {
63
+ const smartFlowData = fetchedDependencies['smart-cohort-flow']?.assets;
64
+ const dumbFlowData = fetchedDependencies['dumb-cohort-flow']?.assets;
65
+ const lossFlowData = fetchedDependencies['in_loss_asset_crowd_flow'];
23
66
 
24
- if (!smartData || !smartData.asset_flow || !lossData) {
25
- logger.log('WARN', `[SharkAttack] Missing computed dependency data for ${dateStr}. Skipping.`);
26
- return null;
67
+ if (!smartFlowData || !dumbFlowData || !lossFlowData) {
68
+ return {};
27
69
  }
28
70
 
29
- const smartFlow = smartData.asset_flow;
30
- const lossFlow = lossData; // This is the root object
31
- const signals = {};
71
+ const allTickers = new Set([
72
+ ...Object.keys(smartFlowData),
73
+ ...Object.keys(dumbFlowData),
74
+ ...Object.keys(lossFlowData)
75
+ ]);
32
76
 
33
- const allTickers = new Set([...Object.keys(smartFlow), ...Object.keys(lossFlow)]);
77
+ const result = {};
78
+ const THRESHOLD = 1.5; // Higher threshold for a stronger signal
34
79
 
35
80
  for (const ticker of allTickers) {
36
- const sFlow = smartFlow[ticker]?.net_crowd_flow_pct || 0;
37
- const lFlow = lossFlow[ticker]?.net_crowd_flow_pct || 0;
38
-
39
- // Check for the divergence: Smart is buying, In-Loss is selling
40
- if (sFlow >= this.buyThreshold && lFlow <= this.sellThreshold) {
41
-
42
- signals[ticker] = {
43
- status: "SHARK_ATTACK",
44
- detail: "Positive expectancy cohort is buying from the capitulating in-loss cohort.",
45
- smart_flow: sFlow,
46
- loss_flow: lFlow,
47
- // Calculate a simple conviction score
48
- conviction: sFlow + Math.abs(lFlow)
49
- };
81
+ const sFlow = smartFlowData[ticker]?.net_flow_percentage || 0;
82
+ const dFlow = dumbFlowData[ticker]?.net_flow_percentage || 0;
83
+ const lFlow = lossFlowData[ticker]?.net_flow_percentage || 0;
84
+
85
+ let status = 'Neutral';
86
+
87
+ // "Shark Attack": Smart buying, Dumb selling, Loss selling
88
+ if (sFlow > THRESHOLD && dFlow < -THRESHOLD && lFlow < -THRESHOLD) {
89
+ status = 'Shark Attack (Buy)';
90
+ }
91
+ // "Shark Bait": Smart selling, Dumb buying, Loss buying
92
+ else if (sFlow < -THRESHOLD && dFlow > THRESHOLD && lFlow > THRESHOLD) {
93
+ status = 'Shark Bait (Sell)';
50
94
  }
51
- }
52
95
 
53
- return signals;
96
+ result[ticker] = {
97
+ status: status,
98
+ smart_flow_pct: sFlow,
99
+ dumb_flow_pct: dFlow,
100
+ loss_cohort_flow_pct: lFlow
101
+ };
102
+ }
103
+
104
+ return result;
54
105
  }
55
106
 
56
- async getResult() { return null; }
57
- reset() {}
107
+ reset() {
108
+ // No state
109
+ }
58
110
  }
59
111
 
60
112
  module.exports = SharkAttackSignal;
@@ -1,127 +1,143 @@
1
1
  /**
2
- * @fileoverview Meta-calculation (Pass 4) that correlates the asset/sector flow
3
- * of the "Smart Cohort" vs. the "Dumb Cohort" to find divergence signals.
2
+ * @fileoverview Calculation (Pass 4) for smart-dumb divergence index.
4
3
  *
5
- * --- META REFACTOR (v2) ---
6
- * This calculation is now stateless. It declares its dependencies and
7
- * expects them to be passed to its `process` method.
4
+ * This metric answers: "What divergence signals (e.g., capitulation,
5
+ * euphoria) can be found by comparing the net asset and sector flow
6
+ * of the 'smart cohort' vs. the 'dumb cohort'?"
7
+ *
8
+ * It *depends* on 'smart-cohort-flow' and 'dumb-cohort-flow'.
8
9
  */
9
-
10
10
  class SmartDumbDivergenceIndex {
11
+ constructor() {
12
+ // No per-user processing
13
+ }
11
14
 
12
15
  /**
13
- * (NEW) Statically declare dependencies.
16
+ * Defines the output schema for this calculation.
17
+ * @returns {object} JSON Schema object
14
18
  */
15
- static getDependencies() {
16
- return ['smart-cohort-flow', 'dumb-cohort-flow'];
17
- }
19
+ static getSchema() {
20
+ const signalSchema = {
21
+ "type": "object",
22
+ "properties": {
23
+ "status": {
24
+ "type": "string",
25
+ "enum": ["Capitulation", "Euphoria", "Confirmation (Buy)", "Confirmation (Sell)", "Divergence (Smart Buy)", "Divergence (Smart Sell)", "Neutral"]
26
+ },
27
+ "smart_flow_pct": { "type": "number" },
28
+ "dumb_flow_pct": { "type": "number" }
29
+ },
30
+ "required": ["status", "smart_flow_pct", "dumb_flow_pct"]
31
+ };
18
32
 
19
- constructor() {
20
- // Minimum net flow (as a percentage) to be considered a signal
21
- this.FLOW_THRESHOLD = 0.005; // Formerly 0.5
33
+ return {
34
+ "type": "object",
35
+ "description": "Generates divergence signals by comparing net flow of 'Smart' vs. 'Dumb' cohorts, by asset and sector.",
36
+ "properties": {
37
+ "assets": {
38
+ "type": "object",
39
+ "description": "Divergence signals per asset.",
40
+ "patternProperties": { "^.*$": signalSchema }, // Ticker
41
+ "additionalProperties": signalSchema
42
+ },
43
+ "sectors": {
44
+ "type": "object",
45
+ "description": "Divergence signals per sector.",
46
+ "patternProperties": { "^.*$": signalSchema }, // Sector
47
+ "additionalProperties": signalSchema
48
+ }
49
+ },
50
+ "required": ["assets", "sectors"]
51
+ };
22
52
  }
23
53
 
24
54
  /**
25
- * REFACTORED PROCESS METHOD
26
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
27
- * @param {object} dependencies The shared dependencies (db, logger).
28
- * @param {object} config The computation system configuration.
29
- * @param {object} fetchedDependencies In-memory results from previous passes.
30
- * e.g., { 'smart-cohort-flow': ..., 'dumb-cohort-flow': ... }
31
- * @returns {Promise<object|null>} The analysis result or null.
55
+ * Statically declare dependencies.
32
56
  */
33
- async process(dateStr, dependencies, config, fetchedDependencies) {
34
- const { logger } = dependencies;
57
+ static getDependencies() {
58
+ return [
59
+ 'smart-cohort-flow', // Pass 3
60
+ 'dumb-cohort-flow' // Pass 3
61
+ ];
62
+ }
35
63
 
36
- // 1. Get dependencies from the new argument
37
- const smartData = fetchedDependencies['smart-cohort-flow'];
38
- const dumbData = fetchedDependencies['dumb-cohort-flow'];
39
-
40
- // 2. Handle missing dependencies
41
- if (!smartData || !dumbData) {
42
- logger.log('WARN', `[SmartDumbDivergence] Missing cohort flow data dependency for ${dateStr}. Skipping.`);
43
- return null;
64
+ process() {
65
+ // No-op
66
+ }
67
+
68
+ _calculateDivergence(smartFlow, dumbFlow) {
69
+ const result = {};
70
+ if (!smartFlow || !dumbFlow) {
71
+ return result;
44
72
  }
45
73
 
46
- const results = {
47
- assets: {},
48
- sectors: {}
49
- };
50
-
51
- const smartAssetFlow = smartData.asset_flow;
52
- const dumbAssetFlow = dumbData.asset_flow;
53
- const smartSectorFlow = smartData.sector_rotation;
54
- const dumbSectorFlow = dumbData.sector_rotation;
74
+ const allKeys = new Set([...Object.keys(smartFlow), ...Object.keys(dumbFlow)]);
75
+ const THRESHOLD = 1; // Min flow %
55
76
 
56
- if (!smartAssetFlow || !dumbAssetFlow || !smartSectorFlow || !dumbSectorFlow) {
57
- logger.log('WARN', `[SmartDumbDivergence] Dependency data for ${dateStr} is incomplete (missing asset_flow or sector_rotation). Skipping.`);
58
- return null;
59
- }
60
-
61
- // 3. Correlate Assets
62
- const allTickers = new Set([...Object.keys(smartAssetFlow), ...Object.keys(dumbAssetFlow)]);
63
- for (const ticker of allTickers) {
64
- const sFlow = smartAssetFlow[ticker]?.net_crowd_flow_pct || 0;
65
- const dFlow = dumbAssetFlow[ticker]?.net_crowd_flow_pct || 0;
77
+ for (const key of allKeys) {
78
+ const sFlow = smartFlow[key]?.net_flow_percentage || 0;
79
+ const dFlow = dumbFlow[key]?.net_flow_percentage || 0;
66
80
 
67
- const smartBuys = sFlow >= this.FLOW_THRESHOLD;
68
- const smartSells = sFlow <= -this.FLOW_THRESHOLD;
69
- const dumbBuys = dFlow >= this.FLOW_THRESHOLD;
70
- const dumbSells = dFlow <= -this.FLOW_THRESHOLD;
81
+ let status = 'Neutral';
71
82
 
72
- let status = 'No_Divergence';
73
- let detail = 'Cohorts are aligned or flow is insignificant.';
74
-
75
- if (smartBuys && dumbSells) {
76
- status = 'Capitulation';
77
- detail = 'Smart cohort is buying the dip from the panic-selling dumb cohort.';
78
- } else if (smartSells && dumbBuys) {
79
- status = 'Euphoria';
80
- detail = 'Smart cohort is selling into the FOMO-buying dumb cohort.';
81
- } else if (smartBuys && dumbBuys) {
82
- status = 'Aligned_Buy';
83
- } else if (smartSells && dumbSells) {
84
- status = 'Aligned_Sell';
83
+ // Both buying
84
+ if (sFlow > THRESHOLD && dFlow > THRESHOLD) {
85
+ status = 'Confirmation (Buy)';
85
86
  }
86
-
87
- if (status !== 'No_Divergence') {
88
- results.assets[ticker] = {
89
- status: status,
90
- detail: detail,
91
- smart_cohort_flow_pct: sFlow,
92
- dumb_cohort_flow_pct: dFlow
93
- };
87
+ // Both selling
88
+ else if (sFlow < -THRESHOLD && dFlow < -THRESHOLD) {
89
+ status = 'Confirmation (Sell)';
90
+ }
91
+ // Smart buying, Dumb selling
92
+ else if (sFlow > THRESHOLD && dFlow < -THRESHOLD) {
93
+ status = 'Capitulation'; // Smart buying the dip from dumb money
94
+ }
95
+ // Smart selling, Dumb buying
96
+ else if (sFlow < -THRESHOLD && dFlow > THRESHOLD) {
97
+ status = 'Euphoria'; // Smart selling into dumb money fomo
98
+ }
99
+ // Smart buying, Dumb neutral
100
+ else if (sFlow > THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
101
+ status = 'Divergence (Smart Buy)';
102
+ }
103
+ // Smart selling, Dumb neutral
104
+ else if (sFlow < -THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
105
+ status = 'Divergence (Smart Sell)';
94
106
  }
107
+ // Dumb buying, Smart neutral
108
+ else if (dFlow > THRESHOLD && Math.abs(sFlow) < THRESHOLD) {
109
+ status = 'Divergence (Smart Sell)'; // Implied smart sell
110
+ }
111
+ // Dumb selling, Smart neutral
112
+ else if (dFlow < -THRESHOLD && Math.abs(sFlow) < THRESHOLD) {
113
+ status = 'Divergence (Smart Buy)'; // Implied smart buy
114
+ }
115
+
116
+ result[key] = {
117
+ status: status,
118
+ smart_flow_pct: sFlow,
119
+ dumb_flow_pct: dFlow
120
+ };
95
121
  }
96
-
97
- // 4. Correlate Sectors
98
- const allSectors = new Set([...Object.keys(smartSectorFlow), ...Object.keys(dumbSectorFlow)]);
99
- for (const sector of allSectors) {
100
- const sFlow = smartSectorFlow[sector] || 0;
101
- const dFlow = dumbSectorFlow[sector] || 0;
122
+ return result;
123
+ }
102
124
 
103
- let status = 'No_Divergence';
104
-
105
- if (sFlow > 0 && dFlow < 0) {
106
- status = 'Capitulation';
107
- } else if (sFlow < 0 && dFlow > 0) {
108
- status = 'Euphoria';
109
- }
125
+ getResult(fetchedDependencies) {
126
+ const smartFlowData = fetchedDependencies['smart-cohort-flow'];
127
+ const dumbFlowData = fetchedDependencies['dumb-cohort-flow'];
110
128
 
111
- if (status !== 'No_Divergence') {
112
- results.sectors[sector] = {
113
- status: status,
114
- smart_cohort_flow_usd: sFlow,
115
- dumb_cohort_flow_usd: dFlow
116
- };
117
- }
118
- }
129
+ const assetResult = this._calculateDivergence(smartFlowData?.assets, dumbFlowData?.assets);
130
+ const sectorResult = this._calculateDivergence(smartFlowData?.sectors, dumbFlowData?.sectors);
119
131
 
120
- return results;
132
+ return {
133
+ assets: assetResult,
134
+ sectors: sectorResult
135
+ };
121
136
  }
122
137
 
123
- async getResult() { return null; }
124
- reset() {}
138
+ reset() {
139
+ // No state
140
+ }
125
141
  }
126
142
 
127
143
  module.exports = SmartDumbDivergenceIndex;
@@ -1,127 +1,138 @@
1
1
  /**
2
- * @fileoverview Meta-calculation (Pass 4) that correlates the asset/sector flow
3
- * of the "Smart Cohort" vs. the "Dumb Cohort" to find divergence signals.
2
+ * @fileoverview Calculation (Pass 5) for smart-dumb divergence v2.
4
3
  *
5
- * --- META REFACTOR (v2) ---
6
- * This calculation is now stateless. It declares its dependencies and
7
- * expects them to be passed to its `process` method.
4
+ * This is a more advanced version that likely uses a different
5
+ * cohort definition (e.g., 'expectancy' based) and combines more
6
+ * signals.
7
+ *
8
+ * It *depends* on 'positive_expectancy_cohort_flow' and
9
+ * 'negative_expectancy_cohort_flow'.
8
10
  */
9
-
10
- class SmartDumbDivergenceIndex {
11
+ class SmartDumbDivergenceIndexV2 {
12
+ constructor() {
13
+ // No per-user processing
14
+ }
11
15
 
12
16
  /**
13
- * (NEW) Statically declare dependencies.
17
+ * Defines the output schema for this calculation.
18
+ * @returns {object} JSON Schema object
14
19
  */
15
- static getDependencies() {
16
- return ['positive-expectancy-cohort-flow', 'negative-expectancy-cohort-flow'];
17
- }
20
+ static getSchema() {
21
+ const signalSchema = {
22
+ "type": "object",
23
+ "properties": {
24
+ "status": {
25
+ "type": "string",
26
+ "enum": ["Capitulation", "Euphoria", "Confirmation (Buy)", "Confirmation (Sell)", "Divergence (Smart Buy)", "Divergence (Smart Sell)", "Neutral"]
27
+ },
28
+ "smart_flow_pct": { "type": "number" },
29
+ "dumb_flow_pct": { "type": "number" }
30
+ },
31
+ "required": ["status", "smart_flow_pct", "dumb_flow_pct"]
32
+ };
18
33
 
19
- constructor() {
20
- // Minimum net flow (as a percentage) to be considered a signal
21
- this.FLOW_THRESHOLD = 0.005; // Formerly 0.5
34
+ return {
35
+ "type": "object",
36
+ "description": "Generates divergence signals (V2) by comparing net flow of 'Positive Expectancy' vs. 'Negative Expectancy' cohorts.",
37
+ "properties": {
38
+ "assets": {
39
+ "type": "object",
40
+ "description": "Divergence signals per asset.",
41
+ "patternProperties": { "^.*$": signalSchema }, // Ticker
42
+ "additionalProperties": signalSchema
43
+ },
44
+ "sectors": {
45
+ "type": "object",
46
+ "description": "Divergence signals per sector.",
47
+ "patternProperties": { "^.*$": signalSchema }, // Sector
48
+ "additionalProperties": signalSchema
49
+ }
50
+ },
51
+ "required": ["assets", "sectors"]
52
+ };
22
53
  }
23
54
 
24
55
  /**
25
- * REFACTORED PROCESS METHOD
26
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
27
- * @param {object} dependencies The shared dependencies (db, logger).
28
- * @param {object} config The computation system configuration.
29
- * @param {object} fetchedDependencies In-memory results from previous passes.
30
- * e.g., { 'smart-cohort-flow': ..., 'dumb-cohort-flow': ... }
31
- * @returns {Promise<object|null>} The analysis result or null.
56
+ * Statically declare dependencies.
32
57
  */
33
- async process(dateStr, dependencies, config, fetchedDependencies) {
34
- const { logger } = dependencies;
58
+ static getDependencies() {
59
+ return [
60
+ 'positive_expectancy_cohort_flow', // Pass 4
61
+ 'negative_expectancy_cohort_flow' // Pass 4
62
+ ];
63
+ }
35
64
 
36
- // 1. Get dependencies from the new argument
37
- const smartData = fetchedDependencies['positive-expectancy-cohort-flow'];
38
- const dumbData = fetchedDependencies['negative-expectancy-cohort-flow'];
39
-
40
- // 2. Handle missing dependencies
41
- if (!smartData || !dumbData) {
42
- logger.log('WARN', `[SmartDumbDivergence] Missing cohort flow data dependency for ${dateStr}. Skipping.`);
43
- return null;
65
+ process() {
66
+ // No-op
67
+ }
68
+
69
+ _calculateDivergence(smartFlow, dumbFlow) {
70
+ const result = {};
71
+ if (!smartFlow || !dumbFlow) {
72
+ return result;
44
73
  }
45
74
 
46
- const results = {
47
- assets: {},
48
- sectors: {}
49
- };
50
-
51
- const smartAssetFlow = smartData.asset_flow;
52
- const dumbAssetFlow = dumbData.asset_flow;
53
- const smartSectorFlow = smartData.sector_rotation;
54
- const dumbSectorFlow = dumbData.sector_rotation;
75
+ const allKeys = new Set([...Object.keys(smartFlow), ...Object.keys(dumbFlow)]);
76
+ const THRESHOLD = 1; // Min flow %
55
77
 
56
- if (!smartAssetFlow || !dumbAssetFlow || !smartSectorFlow || !dumbSectorFlow) {
57
- logger.log('WARN', `[SmartDumbDivergence] Dependency data for ${dateStr} is incomplete (missing asset_flow or sector_rotation). Skipping.`);
58
- return null;
59
- }
60
-
61
- // 3. Correlate Assets
62
- const allTickers = new Set([...Object.keys(smartAssetFlow), ...Object.keys(dumbAssetFlow)]);
63
- for (const ticker of allTickers) {
64
- const sFlow = smartAssetFlow[ticker]?.net_crowd_flow_pct || 0;
65
- const dFlow = dumbAssetFlow[ticker]?.net_crowd_flow_pct || 0;
78
+ for (const key of allKeys) {
79
+ // "Smart" = Positive Expectancy
80
+ const sFlow = smartFlow[key]?.net_flow_percentage || 0;
81
+ // "Dumb" = Negative Expectancy
82
+ const dFlow = dumbFlow[key]?.net_flow_percentage || 0;
66
83
 
67
- const smartBuys = sFlow >= this.FLOW_THRESHOLD;
68
- const smartSells = sFlow <= -this.FLOW_THRESHOLD;
69
- const dumbBuys = dFlow >= this.FLOW_THRESHOLD;
70
- const dumbSells = dFlow <= -this.FLOW_THRESHOLD;
84
+ let status = 'Neutral';
71
85
 
72
- let status = 'No_Divergence';
73
- let detail = 'Cohorts are aligned or flow is insignificant.';
74
-
75
- if (smartBuys && dumbSells) {
86
+ if (sFlow > THRESHOLD && dFlow > THRESHOLD) {
87
+ status = 'Confirmation (Buy)';
88
+ }
89
+ else if (sFlow < -THRESHOLD && dFlow < -THRESHOLD) {
90
+ status = 'Confirmation (Sell)';
91
+ }
92
+ else if (sFlow > THRESHOLD && dFlow < -THRESHOLD) {
76
93
  status = 'Capitulation';
77
- detail = 'Smart cohort is buying the dip from the panic-selling dumb cohort.';
78
- } else if (smartSells && dumbBuys) {
94
+ }
95
+ else if (sFlow < -THRESHOLD && dFlow > THRESHOLD) {
79
96
  status = 'Euphoria';
80
- detail = 'Smart cohort is selling into the FOMO-buying dumb cohort.';
81
- } else if (smartBuys && dumbBuys) {
82
- status = 'Aligned_Buy';
83
- } else if (smartSells && dumbSells) {
84
- status = 'Aligned_Sell';
85
97
  }
86
-
87
- if (status !== 'No_Divergence') {
88
- results.assets[ticker] = {
89
- status: status,
90
- detail: detail,
91
- smart_cohort_flow_pct: sFlow,
92
- dumb_cohort_flow_pct: dFlow
93
- };
98
+ else if (sFlow > THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
99
+ status = 'Divergence (Smart Buy)';
100
+ }
101
+ else if (sFlow < -THRESHOLD && Math.abs(dFlow) < THRESHOLD) {
102
+ status = 'Divergence (Smart Sell)';
94
103
  }
104
+ else if (dFlow > THRESHOLD && Math.abs(sFlow) < THRESHOLD) {
105
+ status = 'Divergence (Smart Sell)'; // Implied
106
+ }
107
+ else if (dFlow < -THRESHOLD && Math.abs(sFlow) < THRESHOLD) {
108
+ status = 'Divergence (Smart Buy)'; // Implied
109
+ }
110
+
111
+ result[key] = {
112
+ status: status,
113
+ smart_flow_pct: sFlow,
114
+ dumb_flow_pct: dFlow
115
+ };
95
116
  }
96
-
97
- // 4. Correlate Sectors
98
- const allSectors = new Set([...Object.keys(smartSectorFlow), ...Object.keys(dumbSectorFlow)]);
99
- for (const sector of allSectors) {
100
- const sFlow = smartSectorFlow[sector] || 0;
101
- const dFlow = dumbSectorFlow[sector] || 0;
117
+ return result;
118
+ }
102
119
 
103
- let status = 'No_Divergence';
104
-
105
- if (sFlow > 0 && dFlow < 0) {
106
- status = 'Capitulation';
107
- } else if (sFlow < 0 && dFlow > 0) {
108
- status = 'Euphoria';
109
- }
120
+ getResult(fetchedDependencies) {
121
+ const smartFlowData = fetchedDependencies['positive_expectancy_cohort_flow'];
122
+ const dumbFlowData = fetchedDependencies['negative_expectancy_cohort_flow'];
110
123
 
111
- if (status !== 'No_Divergence') {
112
- results.sectors[sector] = {
113
- status: status,
114
- smart_cohort_flow_usd: sFlow,
115
- dumb_cohort_flow_usd: dFlow
116
- };
117
- }
118
- }
124
+ const assetResult = this._calculateDivergence(smartFlowData?.assets, dumbFlowData?.assets);
125
+ const sectorResult = this._calculateDivergence(smartFlowData?.sectors, dumbFlowData?.sectors);
119
126
 
120
- return results;
127
+ return {
128
+ assets: assetResult,
129
+ sectors: sectorResult
130
+ };
121
131
  }
122
132
 
123
- async getResult() { return null; }
124
- reset() {}
133
+ reset() {
134
+ // No state
135
+ }
125
136
  }
126
137
 
127
- module.exports = SmartDumbDivergenceIndex;
138
+ module.exports = SmartDumbDivergenceIndexV2;