aiden-shared-calculations-unified 1.0.64 → 1.0.66
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.
- package/README.MD +1 -1
- package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
- package/calculations/activity/historical/daily_asset_activity.js +42 -0
- package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
- package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
- package/calculations/asset_metrics/asset_position_size.js +36 -0
- package/calculations/backtests/strategy-performance.js +41 -0
- package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
- package/calculations/behavioural/historical/drawdown_response.js +113 -35
- package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
- package/calculations/behavioural/historical/gain_response.js +113 -34
- package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
- package/calculations/behavioural/historical/position_count_pnl.js +91 -39
- package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
- package/calculations/behavioural/historical/smart_money_flow.js +160 -151
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
- package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
- package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
- package/calculations/insights/daily_total_positions_held.js +28 -24
- package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
- package/calculations/insights/historical/daily_ownership_delta.js +95 -32
- package/calculations/meta/capital_deployment_strategy.js +78 -110
- package/calculations/meta/capital_liquidation_performance.js +114 -111
- package/calculations/meta/cash-flow-deployment.js +114 -107
- package/calculations/meta/cash-flow-liquidation.js +114 -107
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
- package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
- package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
- package/calculations/meta/profit_cohort_divergence.js +83 -59
- package/calculations/meta/shark_attack_signal.js +91 -39
- package/calculations/meta/smart-dumb-divergence-index.js +114 -98
- package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
- package/calculations/meta/social-predictive-regime-state.js +76 -155
- package/calculations/meta/social-topic-driver-index.js +74 -127
- package/calculations/meta/user_expectancy_score.js +83 -31
- package/calculations/pnl/asset_pnl_status.js +120 -31
- package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
- package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
- package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
- package/calculations/pnl/average_daily_position_pnl.js +49 -21
- package/calculations/pnl/historical/profitability_migration.js +81 -35
- package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
- package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
- package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
- package/calculations/pnl/profitability_skew_per_stock.js +86 -31
- package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
- package/calculations/sanity/users_processed.js +24 -1
- package/calculations/sectors/historical/diversification_pnl.js +104 -42
- package/calculations/sectors/historical/sector_rotation.js +94 -45
- package/calculations/sectors/total_long_per_sector.js +55 -20
- package/calculations/sectors/total_short_per_sector.js +55 -20
- package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
- package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
- package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
- package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
- package/calculations/short_and_long_stats/total_long_figures.js +34 -13
- package/calculations/short_and_long_stats/total_short_figures.js +34 -14
- package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
- package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
- package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
- package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
- package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
- package/calculations/socialPosts/social_activity_aggregation.js +106 -77
- package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
- package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
- package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
- package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
- package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
- package/calculations/speculators/historical/risk_appetite_change.js +89 -32
- package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
- package/calculations/speculators/holding_duration_per_asset.js +83 -23
- package/calculations/speculators/leverage_per_asset.js +68 -19
- package/calculations/speculators/leverage_per_sector.js +86 -25
- package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
- package/calculations/speculators/speculator_asset_sentiment.js +100 -48
- package/calculations/speculators/speculator_danger_zone.js +101 -33
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
- package/calculations/speculators/stop_loss_per_asset.js +94 -26
- package/calculations/speculators/take_profit_per_asset.js +95 -27
- package/calculations/speculators/tsl_per_asset.js +77 -23
- package/package.json +1 -1
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
|
@@ -1,62 +1,120 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
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
|
-
//
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
*
|
|
20
|
-
*
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
65
|
+
// 2. Load this metric's history from yesterday
|
|
66
|
+
const yHistoryData = await calculationUtils.loadYesterdayData('social-word-mentions-trend');
|
|
38
67
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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.
|
|
59
|
-
this.
|
|
116
|
+
this.keywordData.clear();
|
|
117
|
+
this.keywords = [];
|
|
60
118
|
}
|
|
61
119
|
}
|
|
62
120
|
|
|
@@ -1,103 +1,132 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
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.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
*
|
|
17
|
-
* @
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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.
|
|
96
|
-
this.
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Bearish: 0,
|
|
14
|
-
Neutral: 0
|
|
10
|
+
this.global = {
|
|
11
|
+
bullish: 0,
|
|
12
|
+
bearish: 0,
|
|
13
|
+
neutral: 0
|
|
15
14
|
};
|
|
16
|
-
|
|
15
|
+
// { [ticker]: { bullish: 0, bearish: 0, neutral: 0 } }
|
|
16
|
+
this.perTicker = new Map();
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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.
|
|
109
|
-
this.
|
|
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
|
|