aiden-shared-calculations-unified 1.0.86 → 1.0.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/calculations/capitulation/asset-volatility-estimator.js +96 -0
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +173 -0
- package/calculations/core/asset-cost-basis-profile.js +127 -0
- package/calculations/core/asset-pnl-status.js +36 -106
- package/calculations/core/asset-position-size.js +40 -91
- package/calculations/core/average-daily-pnl-all-users.js +18 -57
- package/calculations/core/average-daily-pnl-per-sector.js +41 -88
- package/calculations/core/average-daily-pnl-per-stock.js +38 -91
- package/calculations/core/average-daily-position-pnl.js +19 -49
- package/calculations/core/holding-duration-per-asset.js +25 -127
- package/calculations/core/instrument-price-change-1d.js +30 -49
- package/calculations/core/instrument-price-momentum-20d.js +50 -60
- package/calculations/core/long-position-per-stock.js +39 -68
- package/calculations/core/overall-holding-duration.js +16 -87
- package/calculations/core/overall-profitability-ratio.js +11 -40
- package/calculations/core/platform-buy-sell-sentiment.js +41 -124
- package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
- package/calculations/core/platform-daily-ownership-delta.js +68 -126
- package/calculations/core/platform-ownership-per-sector.js +45 -96
- package/calculations/core/platform-total-positions-held.js +20 -80
- package/calculations/core/pnl-distribution-per-stock.js +29 -135
- package/calculations/core/price-metrics.js +95 -206
- package/calculations/core/profitability-ratio-per-sector.js +34 -79
- package/calculations/core/profitability-ratio-per-stock.js +32 -88
- package/calculations/core/profitability-skew-per-stock.js +41 -94
- package/calculations/core/profitable-and-unprofitable-status.js +44 -76
- package/calculations/core/sentiment-per-stock.js +24 -77
- package/calculations/core/short-position-per-stock.js +35 -43
- package/calculations/core/social-activity-aggregation.js +26 -49
- package/calculations/core/social-asset-posts-trend.js +38 -94
- package/calculations/core/social-event-correlation.js +26 -93
- package/calculations/core/social-sentiment-aggregation.js +20 -44
- package/calculations/core/social-top-mentioned-words.js +35 -87
- package/calculations/core/social-topic-interest-evolution.js +22 -111
- package/calculations/core/social-topic-sentiment-matrix.js +38 -104
- package/calculations/core/social-word-mentions-trend.js +27 -104
- package/calculations/core/speculator-asset-sentiment.js +31 -72
- package/calculations/core/speculator-danger-zone.js +48 -84
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
- package/calculations/core/speculator-leverage-per-asset.js +25 -64
- package/calculations/core/speculator-leverage-per-sector.js +27 -63
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
- package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
- package/calculations/core/speculator-take-profit-per-asset.js +20 -57
- package/calculations/core/speculator-tsl-per-asset.js +17 -56
- package/calculations/core/test..js +0 -0
- package/calculations/core/total-long-figures.js +16 -31
- package/calculations/core/total-long-per-sector.js +39 -61
- package/calculations/core/total-short-figures.js +13 -32
- package/calculations/core/total-short-per-sector.js +39 -61
- package/calculations/core/users-processed.js +11 -46
- package/calculations/gauss/cohort-capital-flow.js +54 -173
- package/calculations/gauss/cohort-definer.js +77 -163
- package/calculations/gauss/daily-dna-filter.js +29 -83
- package/calculations/gauss/gauss-divergence-signal.js +22 -109
- package/calculations/gem/cohort-momentum-state.js +27 -72
- package/calculations/gem/cohort-skill-definition.js +36 -52
- package/calculations/gem/platform-conviction-divergence.js +18 -60
- package/calculations/gem/quant-skill-alpha-signal.js +25 -98
- package/calculations/gem/skilled-cohort-flow.js +67 -175
- package/calculations/gem/skilled-unskilled-divergence.js +18 -73
- package/calculations/gem/unskilled-cohort-flow.js +64 -172
- package/calculations/ghost-book/cost-basis-density.js +79 -0
- package/calculations/ghost-book/liquidity-vacuum.js +52 -0
- package/calculations/ghost-book/retail-gamma-exposure.js +86 -0
- package/calculations/helix/helix-contrarian-signal.js +20 -114
- package/calculations/helix/herd-consensus-score.js +42 -124
- package/calculations/helix/winner-loser-flow.js +36 -118
- package/calculations/predicative-alpha/cognitive-dissonance.js +113 -0
- package/calculations/predicative-alpha/diamond-hand-fracture.js +90 -0
- package/calculations/predicative-alpha/mimetic-latency.js +124 -0
- package/calculations/pyro/risk-appetite-index.js +33 -74
- package/calculations/pyro/squeeze-potential.js +30 -87
- package/calculations/pyro/volatility-signal.js +33 -78
- package/package.json +1 -1
|
@@ -1,115 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2 - Meta) for
|
|
3
|
-
*
|
|
4
|
-
* 1. Changed logic to read from 'socialDoc' directly, not 'socialDoc.posts'.
|
|
5
|
-
* 2. Changed tokenizer to read from 'post.textSnippet' to match schema.md.
|
|
2
|
+
* @fileoverview Calculation (Pass 2 - Meta) for top words.
|
|
3
|
+
* REFACTORED: Counts word frequency in social posts.
|
|
6
4
|
*/
|
|
7
|
-
const STOPWORDS = new Set([
|
|
8
|
-
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'being', 'been',
|
|
9
|
-
'it', 'its', 'it\'s', 'i', 'me', 'my', 'mine', 'you', 'your', 'yours',
|
|
10
|
-
'he', 'him', 'his', 'she', 'her', 'hers', 'they', 'them', 'their', 'theirs',
|
|
11
|
-
'we', 'us', 'our', 'ours', 'what', 'which', 'who', 'whom', 'this', 'that',
|
|
12
|
-
'these', 'those', 'am', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
|
|
13
|
-
'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between',
|
|
14
|
-
'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to',
|
|
15
|
-
'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again',
|
|
16
|
-
'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how',
|
|
17
|
-
'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some',
|
|
18
|
-
'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too',
|
|
19
|
-
'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now',
|
|
20
|
-
'stock', 'stocks', 'market', 'price', 'trade', 'trading', 'buy', 'sell',
|
|
21
|
-
'long', 'short', 'chart', 'week', 'day', 'today', 'going', 'think'
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
5
|
class SocialTopMentionedWords {
|
|
25
|
-
|
|
26
|
-
constructor() {
|
|
27
|
-
this.result = {};
|
|
28
|
-
}
|
|
6
|
+
constructor() { this.result = {}; }
|
|
29
7
|
|
|
30
8
|
static getMetadata() {
|
|
31
9
|
return {
|
|
32
10
|
type: 'meta',
|
|
33
11
|
rootDataDependencies: ['social'],
|
|
34
12
|
isHistorical: false,
|
|
35
|
-
userType: 'n
|
|
13
|
+
userType: 'n/a',
|
|
36
14
|
category: 'core_social'
|
|
37
15
|
};
|
|
38
16
|
}
|
|
39
17
|
|
|
40
|
-
static getDependencies() {
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
18
|
+
static getDependencies() { return []; }
|
|
43
19
|
|
|
44
20
|
static getSchema() {
|
|
45
|
-
const wordSchema = {
|
|
46
|
-
"type": "object",
|
|
47
|
-
"properties": { "count": { "type": "number" } }
|
|
48
|
-
};
|
|
49
|
-
|
|
50
21
|
return {
|
|
51
22
|
"type": "object",
|
|
52
|
-
"description": "
|
|
53
|
-
"patternProperties": { "^.*$":
|
|
54
|
-
"additionalProperties": wordSchema
|
|
23
|
+
"description": "Top 50 mentioned words/tickers.",
|
|
24
|
+
"patternProperties": { "^.*$": { "type": "number" } }
|
|
55
25
|
};
|
|
56
26
|
}
|
|
57
27
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return text.toLowerCase()
|
|
61
|
-
.replace(/[^\w\s$]/g, '')
|
|
62
|
-
.split(/\s+/)
|
|
63
|
-
.filter(word =>
|
|
64
|
-
word.length > 2 &&
|
|
65
|
-
!STOPWORDS.has(word) &&
|
|
66
|
-
!/^\d+$/.test(word)
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
28
|
+
process(context) {
|
|
29
|
+
const socialPosts = context.social?.today || {};
|
|
71
30
|
const wordCounts = new Map();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
31
|
+
const STOP_WORDS = new Set(['the', 'and', 'this', 'that', 'for', 'is', 'are', 'was', 'with', 'on', 'in', 'to', 'of', 'it', 'my', 'me', 'at']);
|
|
32
|
+
|
|
33
|
+
for (const postId in socialPosts) {
|
|
34
|
+
const post = socialPosts[postId];
|
|
35
|
+
const text = (post.textSnippet || "").toLowerCase();
|
|
36
|
+
|
|
37
|
+
// Basic Tokenization
|
|
38
|
+
const tokens = text.split(/[\s.,!?;:()]+/);
|
|
39
|
+
|
|
40
|
+
for (const token of tokens) {
|
|
41
|
+
if (token.length > 2 && !STOP_WORDS.has(token) && !/^\d+$/.test(token)) {
|
|
42
|
+
wordCounts.set(token, (wordCounts.get(token) || 0) + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Also count tagged tickers explicitly if available
|
|
47
|
+
if (post.tickers && Array.isArray(post.tickers)) {
|
|
48
|
+
for (const ticker of post.tickers) {
|
|
49
|
+
const t = `$${ticker.toLowerCase()}`;
|
|
50
|
+
wordCounts.set(t, (wordCounts.get(t) || 0) + 1);
|
|
51
|
+
}
|
|
91
52
|
}
|
|
92
53
|
}
|
|
93
54
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const result = {};
|
|
99
|
-
for (const [word, count] of sortedWords) {
|
|
100
|
-
result[word] = { count: count };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.result = result;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async getResult(fetchedDependencies) {
|
|
107
|
-
return this.result;
|
|
55
|
+
// Sort and take top 50
|
|
56
|
+
const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 50);
|
|
57
|
+
this.result = Object.fromEntries(sorted);
|
|
108
58
|
}
|
|
109
59
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
60
|
+
async getResult() { return this.result; }
|
|
61
|
+
reset() { this.result = {}; }
|
|
113
62
|
}
|
|
114
|
-
|
|
115
63
|
module.exports = SocialTopMentionedWords;
|
|
@@ -1,141 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2 - Meta) for
|
|
3
|
-
*
|
|
4
|
-
* 1. Changed 'process' signature to 5-arg 'meta' standard.
|
|
5
|
-
* 2. Changed data access to read 'dateStr.social' (Arg 1)
|
|
6
|
-
* 3. Changed 'socialDoc.posts' (Object) to 'socialDoc' (Object).
|
|
7
|
-
* 4. Changed 'post.timestamp' to 'post.createdAt'.
|
|
8
|
-
* 5. Changed 'post.topics' to 'post.sentiment.topics'.
|
|
9
|
-
* 6. Changed helper to read 'post.sentiment.overallSentiment'.
|
|
2
|
+
* @fileoverview Calculation (Pass 2 - Meta) for topic frequency.
|
|
3
|
+
* REFACTORED: Counts topics (same as events, but conceptual separation).
|
|
10
4
|
*/
|
|
11
5
|
class SocialTopicInterestEvolution {
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
this.result = {};
|
|
15
|
-
}
|
|
6
|
+
constructor() { this.result = {}; }
|
|
16
7
|
|
|
17
8
|
static getMetadata() {
|
|
18
9
|
return {
|
|
19
10
|
type: 'meta',
|
|
20
11
|
rootDataDependencies: ['social'],
|
|
21
|
-
isHistorical:
|
|
22
|
-
userType: 'n
|
|
12
|
+
isHistorical: false, // Only today's snapshot
|
|
13
|
+
userType: 'n/a',
|
|
23
14
|
category: 'core_social'
|
|
24
15
|
};
|
|
25
16
|
}
|
|
26
17
|
|
|
27
|
-
static getDependencies() {
|
|
28
|
-
return [];
|
|
29
|
-
}
|
|
18
|
+
static getDependencies() { return []; }
|
|
30
19
|
|
|
31
20
|
static getSchema() {
|
|
32
21
|
const topicSchema = {
|
|
33
22
|
"type": "object",
|
|
34
|
-
"properties": {
|
|
35
|
-
"posts_1d_pct": { "type": "number" },
|
|
36
|
-
"posts_7d_pct": { "type": "number" },
|
|
37
|
-
"sentiment_1d": { "type": "number" },
|
|
38
|
-
"sentiment_7d": { "type": "number" }
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
"type": "object",
|
|
44
|
-
"description": "Calculates the evolution of post volume and sentiment for all social topics.",
|
|
45
|
-
"patternProperties": { "^.*$": topicSchema },
|
|
46
|
-
"additionalProperties": topicSchema
|
|
23
|
+
"properties": { "mention_count": { "type": "number" } }
|
|
47
24
|
};
|
|
25
|
+
return { "type": "object", "patternProperties": { "^.*$": topicSchema } };
|
|
48
26
|
}
|
|
49
27
|
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
if (sentiment === 'bearish') return -1;
|
|
54
|
-
return 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// --- THIS IS THE FIX ---
|
|
58
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
59
|
-
|
|
60
|
-
// Get social data from Arg 1 (metaPayload)
|
|
61
|
-
const socialDoc = dateStr.social; // This IS the map of posts
|
|
62
|
-
// Get the date from Arg 1
|
|
63
|
-
const todayDateStr = dateStr.date;
|
|
64
|
-
|
|
65
|
-
if (!socialDoc || typeof socialDoc !== 'object' || !todayDateStr) {
|
|
66
|
-
this.result = {};
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const today = this._parseDate(todayDateStr);
|
|
71
|
-
const sevenDaysAgo = this._parseDate(todayDateStr);
|
|
72
|
-
sevenDaysAgo.setUTCDate(today.getUTCDate() - 7);
|
|
73
|
-
|
|
74
|
-
const topicStats = new Map();
|
|
75
|
-
let total1d = 0;
|
|
76
|
-
let total7d = 0;
|
|
77
|
-
|
|
78
|
-
const _init = (topic) => {
|
|
79
|
-
if (!topicStats.has(topic)) {
|
|
80
|
-
topicStats.set(topic, { c1: 0, c7: 0, s1: 0, s7: 0 });
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
for (const post of Object.values(socialDoc)) {
|
|
85
|
-
const postDate = this._parseDate(post.createdAt);
|
|
86
|
-
const is1d = postDate.toISOString().slice(0, 10) === todayDateStr;
|
|
87
|
-
const is7d = postDate >= sevenDaysAgo;
|
|
88
|
-
|
|
89
|
-
if (!is7d) continue;
|
|
28
|
+
process(context) {
|
|
29
|
+
const socialPosts = context.social?.today || {};
|
|
30
|
+
const topicCounts = new Map();
|
|
90
31
|
|
|
32
|
+
for (const postId in socialPosts) {
|
|
33
|
+
const post = socialPosts[postId];
|
|
91
34
|
const topics = post.sentiment?.topics || [];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const sentiment = this._getSentimentScore(post.sentiment?.overallSentiment);
|
|
95
|
-
|
|
35
|
+
|
|
96
36
|
for (const topic of topics) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (is7d) {
|
|
101
|
-
stats.c7++;
|
|
102
|
-
stats.s7 += sentiment;
|
|
103
|
-
total7d++;
|
|
104
|
-
}
|
|
105
|
-
if (is1d) {
|
|
106
|
-
stats.c1++;
|
|
107
|
-
stats.s1 += sentiment;
|
|
108
|
-
total1d++;
|
|
109
|
-
}
|
|
37
|
+
const t = topic.toUpperCase();
|
|
38
|
+
topicCounts.set(t, (topicCounts.get(t) || 0) + 1);
|
|
110
39
|
}
|
|
111
40
|
}
|
|
112
41
|
|
|
113
|
-
const
|
|
114
|
-
for (const [topic,
|
|
115
|
-
|
|
116
|
-
posts_1d_pct: (total1d > 0) ? (stats.c1 / total1d) * 100 : 0,
|
|
117
|
-
posts_7d_pct: (total7d > 0) ? (stats.c7 / total7d) * 100 : 0,
|
|
118
|
-
sentiment_1d: (stats.c1 > 0) ? (stats.s1 / stats.c1) : 0,
|
|
119
|
-
sentiment_7d: (stats.c7 > 0) ? (stats.s7 / stats.c7) : 0,
|
|
120
|
-
};
|
|
42
|
+
const output = {};
|
|
43
|
+
for (const [topic, count] of topicCounts.entries()) {
|
|
44
|
+
output[topic] = { mention_count: count };
|
|
121
45
|
}
|
|
122
|
-
|
|
123
|
-
this.result = result;
|
|
124
|
-
}
|
|
125
|
-
// --- END FIX ---
|
|
126
|
-
|
|
127
|
-
_parseDate(dateStr) {
|
|
128
|
-
// Helper to ensure UTC parsing
|
|
129
|
-
return new Date(String(dateStr).slice(0, 10) + 'T00:00:00Z');
|
|
46
|
+
this.result = output;
|
|
130
47
|
}
|
|
131
48
|
|
|
132
|
-
async getResult(
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
reset() {
|
|
137
|
-
this.result = {};
|
|
138
|
-
}
|
|
49
|
+
async getResult() { return this.result; }
|
|
50
|
+
reset() { this.result = {}; }
|
|
139
51
|
}
|
|
140
|
-
|
|
141
52
|
module.exports = SocialTopicInterestEvolution;
|
|
@@ -1,137 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2 - Meta) for
|
|
3
|
-
*
|
|
4
|
-
* 1. Changed 'process' signature to 5-arg 'meta' standard.
|
|
5
|
-
* 2. Removed 'calculationUtils.loadInstrumentMappings()' as it's
|
|
6
|
-
* not available in the test harness 'meta' signature.
|
|
7
|
-
* 3. Logic now uses 'config.instrumentToTicker' (Arg 4)
|
|
8
|
-
* which IS provided by the worker.
|
|
9
|
-
* 4. Changed 'socialDoc.posts' to 'socialDoc' (Arg 1).
|
|
10
|
-
* 5. Changed 'post.sentiment.label' to 'post.sentiment.overallSentiment'.
|
|
11
|
-
* 6. Changed 'post.topics' to 'post.sentiment.topics'.
|
|
12
|
-
* 7. Changed 'post.instrumentIds' to 'post.tickers'.
|
|
2
|
+
* @fileoverview Calculation (Pass 2 - Meta) for Topic vs Sentiment.
|
|
3
|
+
* REFACTORED: Groups sentiment scores by topic.
|
|
13
4
|
*/
|
|
14
|
-
|
|
15
5
|
class SocialTopicSentimentMatrix {
|
|
16
|
-
|
|
17
|
-
constructor() {
|
|
18
|
-
this.result = {};
|
|
19
|
-
}
|
|
6
|
+
constructor() { this.result = {}; }
|
|
20
7
|
|
|
21
8
|
static getMetadata() {
|
|
22
9
|
return {
|
|
23
10
|
type: 'meta',
|
|
24
11
|
rootDataDependencies: ['social'],
|
|
25
12
|
isHistorical: false,
|
|
26
|
-
userType: 'n
|
|
13
|
+
userType: 'n/a',
|
|
27
14
|
category: 'core_social'
|
|
28
15
|
};
|
|
29
16
|
}
|
|
30
17
|
|
|
31
|
-
static getDependencies() {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
18
|
+
static getDependencies() { return []; }
|
|
34
19
|
|
|
35
20
|
static getSchema() {
|
|
36
|
-
const
|
|
21
|
+
const topicSchema = {
|
|
37
22
|
"type": "object",
|
|
38
23
|
"properties": {
|
|
39
|
-
"
|
|
40
|
-
"
|
|
24
|
+
"bullish_count": { "type": "number" },
|
|
25
|
+
"bearish_count": { "type": "number" },
|
|
26
|
+
"neutral_count": { "type": "number" },
|
|
27
|
+
"net_sentiment": { "type": "number" }
|
|
41
28
|
}
|
|
42
29
|
};
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"description": "Calculates the average sentiment and post count for all topics and all instruments.",
|
|
47
|
-
"properties": {
|
|
48
|
-
"by_topic": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema },
|
|
49
|
-
"by_instrument": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema }
|
|
50
|
-
},
|
|
51
|
-
"required": ["by_topic", "by_instrument"]
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
_getSentimentScore(sentimentString) {
|
|
56
|
-
const sentiment = sentimentString?.toLowerCase();
|
|
57
|
-
if (sentiment === 'bullish') return 1;
|
|
58
|
-
if (sentiment === 'bearish') return -1;
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
_init(map, key) {
|
|
63
|
-
if (!map.has(key)) {
|
|
64
|
-
map.set(key, { score_sum: 0, count: 0 });
|
|
65
|
-
}
|
|
30
|
+
return { "type": "object", "patternProperties": { "^.*$": topicSchema } };
|
|
66
31
|
}
|
|
67
32
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const { instrumentToTicker } = config;
|
|
72
|
-
|
|
73
|
-
// Get social data from Arg 1 (dateStr is metaPayload)
|
|
74
|
-
const socialDoc = dateStr.social;
|
|
75
|
-
|
|
76
|
-
if (!socialDoc || typeof socialDoc !== 'object' || !instrumentToTicker) {
|
|
77
|
-
this.result = { by_topic: {}, by_instrument: {} };
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const topicMap = new Map();
|
|
82
|
-
const instrumentMap = new Map();
|
|
33
|
+
process(context) {
|
|
34
|
+
const socialPosts = context.social?.today || {};
|
|
35
|
+
const topicStats = new Map();
|
|
83
36
|
|
|
84
|
-
for (const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
// 1. Process topics
|
|
37
|
+
for (const postId in socialPosts) {
|
|
38
|
+
const post = socialPosts[postId];
|
|
88
39
|
const topics = post.sentiment?.topics || [];
|
|
89
|
-
|
|
90
|
-
this._init(topicMap, topic);
|
|
91
|
-
const data = topicMap.get(topic);
|
|
92
|
-
data.score_sum += sentiment;
|
|
93
|
-
data.count++;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 2. Process instruments (tickers)
|
|
97
|
-
const tickers = post.tickers || [];
|
|
98
|
-
for (const ticker of tickers) {
|
|
99
|
-
if (!ticker) continue;
|
|
40
|
+
const sentiment = (post.sentiment?.overallSentiment || 'neutral').toLowerCase();
|
|
100
41
|
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
42
|
+
for (const topic of topics) {
|
|
43
|
+
const t = topic.toUpperCase();
|
|
44
|
+
if (!topicStats.has(t)) {
|
|
45
|
+
topicStats.set(t, { bullish: 0, bearish: 0, neutral: 0 });
|
|
46
|
+
}
|
|
47
|
+
const stats = topicStats.get(t);
|
|
48
|
+
|
|
49
|
+
if (sentiment === 'bullish') stats.bullish++;
|
|
50
|
+
else if (sentiment === 'bearish') stats.bearish++;
|
|
51
|
+
else stats.neutral++;
|
|
105
52
|
}
|
|
106
53
|
}
|
|
107
54
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
for (const [ticker, data] of instrumentMap.entries()) {
|
|
118
|
-
result.by_instrument[ticker] = {
|
|
119
|
-
avg_sentiment: (data.count > 0) ? (data.score_sum / data.count) : 0,
|
|
120
|
-
post_count: data.count
|
|
55
|
+
const output = {};
|
|
56
|
+
for (const [topic, stats] of topicStats.entries()) {
|
|
57
|
+
const total = stats.bullish + stats.bearish + stats.neutral;
|
|
58
|
+
output[topic] = {
|
|
59
|
+
bullish_count: stats.bullish,
|
|
60
|
+
bearish_count: stats.bearish,
|
|
61
|
+
neutral_count: stats.neutral,
|
|
62
|
+
net_sentiment: total > 0 ? (stats.bullish - stats.bearish) / total : 0
|
|
121
63
|
};
|
|
122
64
|
}
|
|
123
|
-
|
|
124
|
-
this.result = result;
|
|
65
|
+
this.result = output;
|
|
125
66
|
}
|
|
126
|
-
// --- END FIX ---
|
|
127
67
|
|
|
128
|
-
async getResult(
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
reset() {
|
|
133
|
-
this.result = {};
|
|
134
|
-
}
|
|
68
|
+
async getResult() { return this.result; }
|
|
69
|
+
reset() { this.result = {}; }
|
|
135
70
|
}
|
|
136
|
-
|
|
137
71
|
module.exports = SocialTopicSentimentMatrix;
|
|
@@ -1,132 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2 - Meta) for
|
|
3
|
-
*
|
|
4
|
-
* 1. Changed 'process' signature to 5-arg 'meta' standard.
|
|
5
|
-
* 2. Changed data access to read 'dateStr.social' (Arg 1)
|
|
6
|
-
* 3. Changed 'socialDoc.posts' (Array) to 'socialDoc' (Object).
|
|
7
|
-
* 4. Changed 'post.timestamp' to 'post.createdAt'.
|
|
8
|
-
* 5. Changed 'post.content' to 'post.textSnippet'.
|
|
2
|
+
* @fileoverview Calculation (Pass 2 - Meta) for word frequency trend.
|
|
3
|
+
* REFACTORED: Calculates word counts (identical logic to top words, but for trending analysis).
|
|
9
4
|
*/
|
|
10
|
-
const STOPWORDS = new Set([
|
|
11
|
-
'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'being', 'been',
|
|
12
|
-
'it', 'its', 'it\'s', 'i', 'me', 'my', 'mine', 'you', 'your', 'yours',
|
|
13
|
-
'he', 'him', 'his', 'she', 'her', 'hers', 'they', 'them', 'their', 'theirs',
|
|
14
|
-
'we', 'us', 'our', 'ours', 'what', 'which', 'who', 'whom', 'this', 'that',
|
|
15
|
-
'these', 'those', 'am', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
|
|
16
|
-
'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between',
|
|
17
|
-
'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to',
|
|
18
|
-
'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again',
|
|
19
|
-
'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how',
|
|
20
|
-
'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some',
|
|
21
|
-
'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too',
|
|
22
|
-
'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now',
|
|
23
|
-
'stock', 'stocks', 'market', 'price', 'trade', 'trading', 'buy', 'sell',
|
|
24
|
-
'long', 'short', 'chart', 'week', 'day', 'today', 'going', 'think'
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
5
|
class SocialWordMentionsTrend {
|
|
28
|
-
|
|
29
|
-
constructor() {
|
|
30
|
-
this.result = {};
|
|
31
|
-
}
|
|
6
|
+
constructor() { this.result = {}; }
|
|
32
7
|
|
|
33
8
|
static getMetadata() {
|
|
34
9
|
return {
|
|
35
10
|
type: 'meta',
|
|
36
11
|
rootDataDependencies: ['social'],
|
|
37
|
-
isHistorical:
|
|
38
|
-
userType: 'n
|
|
12
|
+
isHistorical: false,
|
|
13
|
+
userType: 'n/a',
|
|
39
14
|
category: 'core_social'
|
|
40
15
|
};
|
|
41
16
|
}
|
|
42
17
|
|
|
43
|
-
static getDependencies() {
|
|
44
|
-
return ['social-top-mentioned-words'];
|
|
45
|
-
}
|
|
18
|
+
static getDependencies() { return []; }
|
|
46
19
|
|
|
47
20
|
static getSchema() {
|
|
48
|
-
const wordSchema = {
|
|
49
|
-
"type": "object",
|
|
50
|
-
"properties": {
|
|
51
|
-
"count_1d": { "type": "number" },
|
|
52
|
-
"count_7d": { "type": "number" }
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
21
|
return {
|
|
57
22
|
"type": "object",
|
|
58
|
-
"
|
|
59
|
-
"patternProperties": { "^.*$": wordSchema },
|
|
60
|
-
"additionalProperties": wordSchema
|
|
23
|
+
"patternProperties": { "^.*$": { "type": "number" } }
|
|
61
24
|
};
|
|
62
25
|
}
|
|
63
26
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.split(/\s+/)
|
|
69
|
-
.filter(word =>
|
|
70
|
-
word.length > 2 &&
|
|
71
|
-
!STOPWORDS.has(word) &&
|
|
72
|
-
!/^\d+$/.test(word)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// --- THIS IS THE FIX ---
|
|
77
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
78
|
-
// dateStr is Arg 1 (metaPayload), rootData is Arg 2, dependencies is Arg 3
|
|
79
|
-
|
|
80
|
-
// Get social data from Arg 1 (worker hack)
|
|
81
|
-
const socialDoc = dateStr.social; // This IS the map of posts
|
|
82
|
-
// Get dependency from Arg 5
|
|
83
|
-
const topWordsData = fetchedDependencies['social-top-mentioned-words'];
|
|
84
|
-
|
|
85
|
-
if (!socialDoc || typeof socialDoc !== 'object' || !topWordsData) {
|
|
86
|
-
this.result = {};
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const topWords = new Set(Object.keys(topWordsData));
|
|
91
|
-
// dateStr is the metaPayload, but it has a 'date' field
|
|
92
|
-
const today = new Date(dateStr.date + 'T00:00:00Z');
|
|
93
|
-
const sevenDaysAgo = new Date(today);
|
|
94
|
-
sevenDaysAgo.setUTCDate(today.getUTCDate() - 7);
|
|
95
|
-
|
|
96
|
-
const counts = new Map();
|
|
97
|
-
for (const word of topWords) {
|
|
98
|
-
counts.set(word, { count_1d: 0, count_7d: 0 });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
for (const post of Object.values(socialDoc)) {
|
|
102
|
-
const postDate = new Date(post.createdAt); // Use 'createdAt'
|
|
103
|
-
const is1d = postDate.toISOString().slice(0, 10) === dateStr.date;
|
|
104
|
-
const is7d = postDate >= sevenDaysAgo;
|
|
105
|
-
|
|
106
|
-
if (!is7d) continue;
|
|
27
|
+
process(context) {
|
|
28
|
+
const socialPosts = context.social?.today || {};
|
|
29
|
+
const wordCounts = new Map();
|
|
30
|
+
const STOP_WORDS = new Set(['the', 'and', 'for', 'that', 'this', 'with', 'you', 'are', 'not']);
|
|
107
31
|
|
|
108
|
-
|
|
32
|
+
for (const postId in socialPosts) {
|
|
33
|
+
const post = socialPosts[postId];
|
|
34
|
+
const text = (post.textSnippet || "").toLowerCase();
|
|
35
|
+
const tokens = text.split(/[\s.,!?;:()]+/);
|
|
109
36
|
|
|
110
|
-
for (const
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
if (is7d) stats.count_7d++;
|
|
114
|
-
if (is1d) stats.count_1d++;
|
|
37
|
+
for (const token of tokens) {
|
|
38
|
+
if (token.length > 3 && !STOP_WORDS.has(token) && !/^\d+$/.test(token)) {
|
|
39
|
+
wordCounts.set(token, (wordCounts.get(token) || 0) + 1);
|
|
115
40
|
}
|
|
116
41
|
}
|
|
117
42
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
43
|
+
|
|
44
|
+
// Return counts for all words found > 2 times
|
|
45
|
+
const filtered = {};
|
|
46
|
+
for (const [word, count] of wordCounts.entries()) {
|
|
47
|
+
if (count > 2) filtered[word] = count;
|
|
48
|
+
}
|
|
49
|
+
this.result = filtered;
|
|
125
50
|
}
|
|
126
51
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
52
|
+
async getResult() { return this.result; }
|
|
53
|
+
reset() { this.result = {}; }
|
|
130
54
|
}
|
|
131
|
-
|
|
132
55
|
module.exports = SocialWordMentionsTrend;
|