aiden-shared-calculations-unified 1.0.64 → 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,62 +1,120 @@
1
1
  /**
2
- * @fileoverview Tracks daily mentions of a pre-defined list of keywords.
3
- * This calculation ONLY uses the social post insights context.
2
+ * @fileoverview Social Calculation (Pass 1)
3
+ *
4
+ * This metric answers: "How many times was each
5
+ * predefined keyword mentioned in posts today?"
6
+ *
7
+ * This is a stateful calculation.
4
8
  */
5
-
6
9
  class SocialWordMentionsTrend {
7
10
  constructor() {
8
- // A pre-defined list of keywords to track
9
- this.KEYWORDS = [
10
- 'inflation', 'fed', 'fomc', 'cpi', 'earnings',
11
- 'recession', 'bullish', 'bearish', 'buy', 'sell',
12
- 'war', 'acquisition', 'ai'
13
- ];
14
- this.mentions = {};
15
- this.processed = false;
11
+ // { [keyword]: { count: 0, history: [] } }
12
+ this.keywordData = new Map();
13
+ // Keywords are loaded from config
14
+ this.keywords = [];
16
15
  }
17
16
 
18
17
  /**
19
- * @param {null} todayPortfolio - Not used.
20
- * ... (other params not used) ...
21
- * @param {object} todaySocialPostInsights - Map of { [postId]: postData } for today.
22
- * @param {object} yesterdaySocialPostInsights - Not used.
18
+ * Defines the output schema for this calculation.
19
+ * @returns {object} JSON Schema object
23
20
  */
24
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights) {
25
-
26
- if (this.processed || !todaySocialPostInsights) {
27
- return;
28
- }
29
- this.processed = true;
21
+ static getSchema() {
22
+ const keywordSchema = {
23
+ "type": "object",
24
+ "description": "Daily and trended post count for a specific keyword.",
25
+ "properties": {
26
+ "count": {
27
+ "type": "number",
28
+ "description": "Today's raw mention count."
29
+ },
30
+ "trend_30d_pct": {
31
+ "type": "number",
32
+ "description": "Percentage change from the 30-day average count."
33
+ },
34
+ "history_30d": {
35
+ "type": "array",
36
+ "description": "30-day history of mention counts (today first).",
37
+ "items": { "type": "number" }
38
+ }
39
+ },
40
+ "required": ["count", "trend_30d_pct", "history_30d"]
41
+ };
30
42
 
31
- // Initialize all keywords with 0 counts
32
- this.mentions = this.KEYWORDS.reduce((acc, key) => {
33
- acc[key] = 0;
34
- return acc;
35
- }, {});
43
+ return {
44
+ "type": "object",
45
+ "description": "Tracks the daily mention count and 30-day trend for predefined keywords.",
46
+ "patternProperties": {
47
+ "^.*$": keywordSchema // Keyword
48
+ },
49
+ "additionalProperties": keywordSchema
50
+ };
51
+ }
52
+
53
+ /**
54
+ * @param {string} dateStr - Today's date.
55
+ * @param {object} dependencies - db, logger.
56
+ * @param {object} config - Computation config.
57
+ * @param {object} fetchedDependencies - (UNUSED) In-memory results.
58
+ */
59
+ async process(dateStr, dependencies, config, fetchedDependencies) {
60
+ const { db, logger, calculationUtils } = dependencies;
61
+
62
+ // 1. Load keywords from config (or another source)
63
+ this.keywords = config.trackedKeywords || ['inflation', 'fed', 'AI', 'crypto', 'recession'];
36
64
 
37
- const posts = Object.values(todaySocialPostInsights);
65
+ // 2. Load this metric's history from yesterday
66
+ const yHistoryData = await calculationUtils.loadYesterdayData('social-word-mentions-trend');
38
67
 
39
- for (const post of posts) {
40
- const text = post.fullText?.toLowerCase() || '';
41
- if (!text) continue;
68
+ // 3. Initialize state for all keywords
69
+ for (const keyword of this.keywords) {
70
+ this.keywordData.set(keyword, {
71
+ count: 0,
72
+ history: yHistoryData?.[keyword]?.history_30d || []
73
+ });
74
+ }
75
+
76
+ // 4. Process today's raw social data
77
+ const todaySocialPosts = await calculationUtils.loadSocialData(dateStr);
42
78
 
43
- for (const keyword of this.KEYWORDS) {
44
- // Count once per post, even if mentioned multiple times
45
- if (text.includes(keyword)) {
46
- this.mentions[keyword]++;
79
+ for (const post of todaySocialPosts) {
80
+ const text = (post.content || '').toLowerCase();
81
+
82
+ for (const keyword of this.keywords) {
83
+ if (text.includes(keyword.toLowerCase())) {
84
+ this.keywordData.get(keyword).count++;
47
85
  }
48
86
  }
49
87
  }
50
88
  }
51
89
 
52
90
  async getResult() {
53
- // Returns an object like: { "inflation": 5, "fed": 12, "ai": 30 }
54
- return this.mentions;
91
+ const result = {};
92
+ for (const [keyword, data] of this.keywordData.entries()) {
93
+ const history = data.history;
94
+ const todayCount = data.count;
95
+
96
+ const newHistory = [todayCount, ...history].slice(0, 30);
97
+
98
+ let trend_30d_pct = 0;
99
+ if (history.length > 0) {
100
+ const avg_30d = history.reduce((a, b) => a + b, 0) / history.length;
101
+ if (avg_30d > 0) {
102
+ trend_30d_pct = ((todayCount - avg_30d) / avg_30d) * 100;
103
+ }
104
+ }
105
+
106
+ result[keyword] = {
107
+ count: todayCount,
108
+ trend_30d_pct: trend_30d_pct,
109
+ history_30d: newHistory
110
+ };
111
+ }
112
+ return result;
55
113
  }
56
114
 
57
115
  reset() {
58
- this.mentions = {};
59
- this.processed = false;
116
+ this.keywordData.clear();
117
+ this.keywords = [];
60
118
  }
61
119
  }
62
120
 
@@ -1,103 +1,132 @@
1
1
  /**
2
- * @fileoverview Aggregates activity data (likes, comments) from social posts.
3
- * This calculation ONLY uses the social post insights context, not portfolio data.
2
+ * @fileoverview Social Calculation (Pass 1)
3
+ *
4
+ * This metric answers: "What is the total and average number
5
+ * of likes and comments on social media posts, both globally
6
+ * and per ticker?"
4
7
  */
5
-
6
8
  class SocialActivityAggregation {
7
9
  constructor() {
8
- this.tickerActivity = {};
9
- this.totalLikes = 0;
10
- this.totalComments = 0;
11
- this.totalPosts = 0; // <-- ADD THIS LINE
12
- this.processed = false; // Flag to ensure this runs only once
10
+ this.global = {
11
+ total_posts: 0,
12
+ total_likes: 0,
13
+ total_comments: 0
14
+ };
15
+ // { [ticker]: { total_posts: 0, total_likes: 0, total_comments: 0 } }
16
+ this.perTicker = new Map();
13
17
  }
14
18
 
15
19
  /**
16
- * @param {null} todayPortfolio - Not used.
17
- * @param {null} yesterdayPortfolio - Not used.
18
- * @param {null} userId - Not used.
19
- * @param {object} context - Shared context (e.g., mappings).
20
- * @param {object} todayInsights - Daily instrument insights.
21
- * @param {object} yesterdayInsights - Yesterday's instrument insights.
22
- * @param {object} todaySocialPostInsights - Map of { [postId]: postData } for today.
23
- * @param {object} yesterdaySocialPostInsights - Map of { [postId]: postData } for yesterday.
20
+ * Defines the output schema for this calculation.
21
+ * @returns {object} JSON Schema object
24
22
  */
25
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights) {
26
- // This process should only run once per day
27
- if (this.processed || !todaySocialPostInsights) {
28
- return;
29
- }
30
- this.processed = true;
31
- this.totalPosts = Object.keys(todaySocialPostInsights).length;
23
+ static getSchema() {
24
+ const statsSchema = {
25
+ "type": "object",
26
+ "properties": {
27
+ "total_posts": { "type": "number" },
28
+ "total_likes": { "type": "number" },
29
+ "total_comments": { "type": "number" },
30
+ "avg_likes_per_post": { "type": "number" },
31
+ "avg_comments_per_post": { "type": "number" }
32
+ },
33
+ "required": ["total_posts", "total_likes", "total_comments", "avg_likes_per_post", "avg_comments_per_post"]
34
+ };
32
35
 
33
- const posts = Object.values(todaySocialPostInsights);
36
+ return {
37
+ "type": "object",
38
+ "description": "Aggregates social activity (posts, likes, comments) globally and per ticker.",
39
+ "properties": {
40
+ "global_activity": {
41
+ ...statsSchema,
42
+ "description": "Aggregated activity across all posts."
43
+ },
44
+ "per_ticker": {
45
+ "type": "object",
46
+ "description": "Aggregated activity broken down by asset ticker.",
47
+ "patternProperties": {
48
+ "^.*$": {
49
+ ...statsSchema,
50
+ "description": "Aggregated activity for a specific ticker."
51
+ } // Ticker
52
+ },
53
+ "additionalProperties": statsSchema
54
+ }
55
+ },
56
+ "required": ["global_activity", "per_ticker"]
57
+ };
58
+ }
34
59
 
35
- for (const post of posts) {
36
- const likeCount = post.likeCount || 0;
37
- const commentCount = post.commentCount || 0;
60
+ _initTicker(ticker) {
61
+ if (!this.perTicker.has(ticker)) {
62
+ this.perTicker.set(ticker, {
63
+ total_posts: 0,
64
+ total_likes: 0,
65
+ total_comments: 0
66
+ });
67
+ }
68
+ }
38
69
 
39
- // 1. Aggregate global counts
40
- this.totalLikes += likeCount;
41
- this.totalComments += commentCount;
70
+ /**
71
+ * @param {string} dateStr - Today's date.
72
+ * @param {object} dependencies - db, logger.
73
+ * @param {object} config - Computation config.
74
+ * @param {object} fetchedDependencies - (UNUSED) In-memory results.
75
+ */
76
+ async process(dateStr, dependencies, config, fetchedDependencies) {
77
+ const { calculationUtils } = dependencies;
78
+ const todaySocialPosts = await calculationUtils.loadSocialData(dateStr);
42
79
 
43
- // 2. Aggregate counts per ticker
44
- if (post.tickers && Array.isArray(post.tickers)) {
45
- for (const ticker of post.tickers) {
46
- if (!this.tickerActivity[ticker]) {
47
- this.tickerActivity[ticker] = {
48
- totalLikes: 0,
49
- totalComments: 0,
50
- totalPosts: 0
51
- };
52
- }
53
- this.tickerActivity[ticker].totalLikes += likeCount;
54
- this.tickerActivity[ticker].totalComments += commentCount;
55
- this.tickerActivity[ticker].totalPosts++;
56
- }
80
+ for (const post of todaySocialPosts) {
81
+ const likes = post.likes || 0;
82
+ const comments = post.comments || 0;
83
+
84
+ // 1. Aggregate global stats
85
+ this.global.total_posts++;
86
+ this.global.total_likes += likes;
87
+ this.global.total_comments += comments;
88
+
89
+ // 2. Aggregate per-ticker stats
90
+ const tickers = post.tickers || [];
91
+ for (const ticker of tickers) {
92
+ this._initTicker(ticker);
93
+ const data = this.perTicker.get(ticker);
94
+ data.total_posts++;
95
+ data.total_likes += likes;
96
+ data.total_comments += comments;
57
97
  }
58
98
  }
59
99
  }
100
+
101
+ _calculateAverages(data) {
102
+ const posts = data.total_posts;
103
+ return {
104
+ ...data,
105
+ avg_likes_per_post: (posts > 0) ? (data.total_likes / posts) : 0,
106
+ avg_comments_per_post: (posts > 0) ? (data.total_comments / posts) : 0
107
+ };
108
+ }
60
109
 
61
110
  async getResult() {
62
- if (!this.processed) return {};
63
-
64
- // Calculate average activity per ticker
65
- const tickerAverageActivity = {};
66
- for (const ticker in this.tickerActivity) {
67
- const data = this.tickerActivity[ticker];
68
- if (data.totalPosts > 0) {
69
- tickerAverageActivity[ticker] = {
70
- avgLikesPerPost: data.totalLikes / data.totalPosts,
71
- avgCommentsPerPost: data.totalComments / data.totalPosts,
72
- ...data // Include total counts
73
- };
74
- } else {
75
- tickerAverageActivity[ticker] = {
76
- avgLikesPerPost: 0,
77
- avgCommentsPerPost: 0,
78
- ...data
79
- };
80
- }
111
+ // Calculate averages for global
112
+ const global_activity = this._calculateAverages(this.global);
113
+
114
+ // Calculate averages for per-ticker
115
+ const per_ticker = {};
116
+ for (const [ticker, data] of this.perTicker.entries()) {
117
+ per_ticker[ticker] = this._calculateAverages(data);
81
118
  }
82
-
119
+
83
120
  return {
84
- globalActivity: {
85
- totalLikes: this.totalLikes,
86
- totalComments: this.totalComments,
87
- totalPosts: this.totalPosts
88
- // --- END FIX ---
89
- },
90
- tickerActivity: tickerAverageActivity
121
+ global_activity,
122
+ per_ticker
91
123
  };
92
124
  }
93
125
 
94
126
  reset() {
95
- this.tickerActivity = {};
96
- this.totalLikes = 0;
97
- this.totalComments = 0;
98
- this.totalPosts = 0;
99
- this.processed = false;
127
+ this.global = { total_posts: 0, total_likes: 0, total_comments: 0 };
128
+ this.perTicker.clear();
100
129
  }
101
130
  }
102
131
 
103
- module.exports = SocialActivityAggregation;
132
+ module.exports = SocialActivityAggregation;
@@ -1,114 +1,143 @@
1
1
  /**
2
- * @fileoverview Aggregates sentiment data from social posts.
3
- * This calculation ONLY uses the social post insights context, not portfolio data.
4
- * UPDATED: To read sentiment from the `sentiment.overallSentiment` property.
2
+ * @fileoverview Social Calculation (Pass 1)
3
+ *
4
+ * This metric answers: "What is the aggregated social media
5
+ * sentiment (bullish, bearish, neutral) globally and
6
+ * for each ticker?"
5
7
  */
6
-
7
8
  class SocialSentimentAggregation {
8
9
  constructor() {
9
- this.tickerSentiments = {};
10
- this.totalPosts = 0;
11
- this.sentimentCounts = {
12
- Bullish: 0,
13
- Bearish: 0,
14
- Neutral: 0
10
+ this.global = {
11
+ bullish: 0,
12
+ bearish: 0,
13
+ neutral: 0
15
14
  };
16
- this.processed = false; // Flag to ensure this runs only once
15
+ // { [ticker]: { bullish: 0, bearish: 0, neutral: 0 } }
16
+ this.perTicker = new Map();
17
17
  }
18
18
 
19
19
  /**
20
- * @param {null} todayPortfolio - Not used.
21
- * @param {null} yesterdayPortfolio - Not used.
22
- * @param {null} userId - Not used.
23
- * @param {object} context - Shared context (e.g., mappings).
24
- * @param {object} todayInsights - Daily instrument insights.
25
- * @param {object} yesterdayInsights - Yesterday's instrument insights.
26
- * @param {object} todaySocialPostInsights - Map of { [postId]: postData } for today.
27
- * @param {object} yesterdaySocialPostInsights - Map of { [postId]: postData } for yesterday.
20
+ * Defines the output schema for this calculation.
21
+ * @returns {object} JSON Schema object
28
22
  */
29
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights) {
30
- // This process should only run once per day, triggered by the first "user" (which is just a trigger).
31
- if (this.processed || !todaySocialPostInsights) {
32
- return;
33
- }
34
- this.processed = true;
23
+ static getSchema() {
24
+ const sentimentSchema = {
25
+ "type": "object",
26
+ "properties": {
27
+ "bullish": { "type": "number" },
28
+ "bearish": { "type": "number" },
29
+ "neutral": { "type": "number" },
30
+ "total_posts": { "type": "number" },
31
+ "net_sentiment": { "type": "number" },
32
+ "net_sentiment_pct": { "type": "number" }
33
+ },
34
+ "required": ["bullish", "bearish", "neutral", "total_posts", "net_sentiment", "net_sentiment_pct"]
35
+ };
35
36
 
36
- const posts = Object.values(todaySocialPostInsights);
37
- this.totalPosts = posts.length;
37
+ return {
38
+ "type": "object",
39
+ "description": "Aggregates social sentiment (bullish, bearish) globally and per ticker.",
40
+ "properties": {
41
+ "global_sentiment": {
42
+ ...sentimentSchema,
43
+ "description": "Aggregated sentiment across all posts."
44
+ },
45
+ "per_ticker": {
46
+ "type": "object",
47
+ "description": "Aggregated sentiment broken down by asset ticker.",
48
+ "patternProperties": {
49
+ "^.*$": {
50
+ ...sentimentSchema,
51
+ "description": "Aggregated sentiment for a specific ticker."
52
+ } // Ticker
53
+ },
54
+ "additionalProperties": sentimentSchema
55
+ }
56
+ },
57
+ "required": ["global_sentiment", "per_ticker"]
58
+ };
59
+ }
38
60
 
39
- for (const post of posts) {
40
- // --- FIX: Read from the nested overallSentiment property ---
41
- const sentiment = post.sentiment?.overallSentiment || 'Neutral';
42
- // --- END FIX ---
61
+ _initTicker(ticker) {
62
+ if (!this.perTicker.has(ticker)) {
63
+ this.perTicker.set(ticker, {
64
+ bullish: 0,
65
+ bearish: 0,
66
+ neutral: 0
67
+ });
68
+ }
69
+ }
43
70
 
44
- // 1. Aggregate global sentiment counts
45
- if (this.sentimentCounts.hasOwnProperty(sentiment)) {
46
- this.sentimentCounts[sentiment]++;
47
- }
71
+ /**
72
+ * @param {string} dateStr - Today's date.
73
+ * @param {object} dependencies - db, logger.
74
+ * @param {object} config - Computation config.
75
+ * @param {object} fetchedDependencies - (UNUSED) In-memory results.
76
+ */
77
+ async process(dateStr, dependencies, config, fetchedDependencies) {
78
+ const { calculationUtils } = dependencies;
79
+ const todaySocialPosts = await calculationUtils.loadSocialData(dateStr);
48
80
 
49
- // 2. Aggregate sentiment per ticker
50
- if (post.tickers && Array.isArray(post.tickers)) {
51
- for (const ticker of post.tickers) {
52
- if (!this.tickerSentiments[ticker]) {
53
- this.tickerSentiments[ticker] = {
54
- Bullish: 0,
55
- Bearish: 0,
56
- Neutral: 0,
57
- totalPosts: 0
58
- };
59
- }
60
- if (this.tickerSentiments[ticker].hasOwnProperty(sentiment)) {
61
- this.tickerSentiments[ticker][sentiment]++;
62
- }
63
- this.tickerSentiments[ticker].totalPosts++;
81
+ for (const post of todaySocialPosts) {
82
+ const sentiment = post.sentiment || 'neutral'; // 'bullish', 'bearish', 'neutral'
83
+
84
+ // 1. Aggregate global sentiment
85
+ if (sentiment === 'bullish') {
86
+ this.global.bullish++;
87
+ } else if (sentiment === 'bearish') {
88
+ this.global.bearish++;
89
+ } else {
90
+ this.global.neutral++;
91
+ }
92
+
93
+ // 2. Aggregate per-ticker sentiment
94
+ const tickers = post.tickers || [];
95
+ for (const ticker of tickers) {
96
+ this._initTicker(ticker);
97
+ const data = this.perTicker.get(ticker);
98
+
99
+ if (sentiment === 'bullish') {
100
+ data.bullish++;
101
+ } else if (sentiment === 'bearish') {
102
+ data.bearish++;
103
+ } else {
104
+ data.neutral++;
64
105
  }
65
106
  }
66
107
  }
67
108
  }
109
+
110
+ _calculateSentiment(data) {
111
+ const total = data.bullish + data.bearish + data.neutral;
112
+ const net = data.bullish - data.bearish;
113
+ return {
114
+ ...data,
115
+ total_posts: total,
116
+ net_sentiment: net,
117
+ // Net sentiment as a percentage of *all* posts (bullish, bearish, neutral)
118
+ net_sentiment_pct: (total > 0) ? (net / total) * 100 : 0
119
+ };
120
+ }
68
121
 
69
122
  async getResult() {
70
- if (!this.processed) return {};
71
-
72
- // Calculate global sentiment ratio
73
- let globalSentimentRatio = 0;
74
- const totalSentiments = this.sentimentCounts.Bullish + this.sentimentCounts.Bearish;
75
- if (totalSentiments > 0) {
76
- globalSentimentRatio = (this.sentimentCounts.Bullish / totalSentiments) * 100;
123
+ // Calculate stats for global
124
+ const global_sentiment = this._calculateSentiment(this.global);
125
+
126
+ // Calculate stats for per-ticker
127
+ const per_ticker = {};
128
+ for (const [ticker, data] of this.perTicker.entries()) {
129
+ per_ticker[ticker] = this._calculateSentiment(data);
77
130
  }
78
-
79
- // Calculate per-ticker sentiment ratios
80
- const tickerSentimentRatios = {};
81
- for (const ticker in this.tickerSentiments) {
82
- const data = this.tickerSentiments[ticker];
83
- const totalTickerSentiments = data.Bullish + data.Bearish;
84
- if (totalTickerSentiments > 0) {
85
- tickerSentimentRatios[ticker] = {
86
- sentimentRatio: (data.Bullish / totalTickerSentiments) * 100,
87
- ...data // Include counts
88
- };
89
- } else {
90
- tickerSentimentRatios[ticker] = {
91
- sentimentRatio: 50, // Default to neutral if no clear sentiment
92
- ...data // Include counts
93
- };
94
- }
95
- }
96
-
131
+
97
132
  return {
98
- globalSentiment: {
99
- ...this.sentimentCounts,
100
- totalPosts: this.totalPosts,
101
- sentimentRatio: globalSentimentRatio
102
- },
103
- tickerSentiment: tickerSentimentRatios
133
+ global_sentiment,
134
+ per_ticker
104
135
  };
105
136
  }
106
137
 
107
138
  reset() {
108
- this.tickerSentiments = {};
109
- this.totalPosts = 0;
110
- this.sentimentCounts = { Bullish: 0, Bearish: 0, Neutral: 0 };
111
- this.processed = false;
139
+ this.global = { bullish: 0, bearish: 0, neutral: 0 };
140
+ this.perTicker.clear();
112
141
  }
113
142
  }
114
143