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.
- 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,29 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for sentiment per stock.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "For each stock, what is the count
|
|
5
|
+
* of long versus short positions?"
|
|
3
6
|
*/
|
|
4
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class SentimentPerStock {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// { [instrumentId]: { long: 0, short: 0 } }
|
|
12
|
+
this.assets = new Map();
|
|
9
13
|
this.mappings = null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
const tickerSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Long/short position counts for a specific asset.",
|
|
24
|
+
"properties": {
|
|
25
|
+
"long_count": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Count of long ('buy') positions."
|
|
28
|
+
},
|
|
29
|
+
"short_count": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Count of short ('sell') positions."
|
|
32
|
+
},
|
|
33
|
+
"sentiment_ratio": {
|
|
34
|
+
"type": ["number", "null"],
|
|
35
|
+
"description": "Ratio of long to short (Long / Short). Null if no short positions."
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": ["long_count", "short_count", "sentiment_ratio"]
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Calculates the count of long vs. short positions for each asset.",
|
|
44
|
+
"patternProperties": {
|
|
45
|
+
"^.*$": tickerSchema // Ticker
|
|
46
|
+
},
|
|
47
|
+
"additionalProperties": tickerSchema
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
_initAsset(instrumentId) {
|
|
52
|
+
if (!this.assets.has(instrumentId)) {
|
|
53
|
+
this.assets.set(instrumentId, {
|
|
54
|
+
long: 0,
|
|
55
|
+
short: 0
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
process(portfolioData) {
|
|
61
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
62
|
+
if (!positions || !Array.isArray(positions)) {
|
|
14
63
|
return;
|
|
15
64
|
}
|
|
16
65
|
|
|
17
|
-
for (const
|
|
18
|
-
const instrumentId =
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
|
|
66
|
+
for (const pos of positions) {
|
|
67
|
+
const instrumentId = pos.InstrumentID;
|
|
68
|
+
if (!instrumentId) continue;
|
|
69
|
+
|
|
70
|
+
this._initAsset(instrumentId);
|
|
71
|
+
const assetData = this.assets.get(instrumentId);
|
|
22
72
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
} else
|
|
26
|
-
|
|
73
|
+
if (pos.IsBuy) {
|
|
74
|
+
assetData.long++;
|
|
75
|
+
} else {
|
|
76
|
+
assetData.short++;
|
|
27
77
|
}
|
|
28
78
|
}
|
|
29
79
|
}
|
|
@@ -32,17 +82,24 @@ class SentimentPerStock {
|
|
|
32
82
|
if (!this.mappings) {
|
|
33
83
|
this.mappings = await loadInstrumentMappings();
|
|
34
84
|
}
|
|
85
|
+
|
|
35
86
|
const result = {};
|
|
36
|
-
for (const instrumentId
|
|
37
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
38
|
-
|
|
87
|
+
for (const [instrumentId, data] of this.assets.entries()) {
|
|
88
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
89
|
+
|
|
90
|
+
if (data.long > 0 || data.short > 0) {
|
|
91
|
+
result[ticker] = {
|
|
92
|
+
long_count: data.long,
|
|
93
|
+
short_count: data.short,
|
|
94
|
+
sentiment_ratio: (data.short > 0) ? (data.long / data.short) : null
|
|
95
|
+
};
|
|
96
|
+
}
|
|
39
97
|
}
|
|
40
|
-
|
|
41
|
-
return { sentiment_by_asset: result };
|
|
98
|
+
return result;
|
|
42
99
|
}
|
|
43
100
|
|
|
44
101
|
reset() {
|
|
45
|
-
this.
|
|
102
|
+
this.assets.clear();
|
|
46
103
|
this.mappings = null;
|
|
47
104
|
}
|
|
48
105
|
}
|
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for short positions per stock.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "How many short ('sell') positions
|
|
5
|
+
* are there for each stock?"
|
|
3
6
|
*/
|
|
4
7
|
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
5
8
|
|
|
6
9
|
class ShortPositionPerStock {
|
|
7
10
|
constructor() {
|
|
8
|
-
|
|
11
|
+
// We will store { [instrumentId]: count }
|
|
12
|
+
this.assets = new Map();
|
|
9
13
|
this.mappings = null;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
|
+
*/
|
|
20
|
+
static getSchema() {
|
|
21
|
+
return {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Calculates the total count of short ('sell') positions for each asset.",
|
|
24
|
+
"patternProperties": {
|
|
25
|
+
// Ticker
|
|
26
|
+
"^.*$": {
|
|
27
|
+
"type": "number",
|
|
28
|
+
"description": "The total count of short positions for this asset."
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "number"
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_initAsset(instrumentId) {
|
|
38
|
+
if (!this.assets.has(instrumentId)) {
|
|
39
|
+
this.assets.set(instrumentId, 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process(portfolioData) {
|
|
44
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
45
|
+
if (!positions || !Array.isArray(positions)) {
|
|
14
46
|
return;
|
|
15
47
|
}
|
|
16
48
|
|
|
17
|
-
for (const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
for (const pos of positions) {
|
|
50
|
+
// Only count 'sell' (short) positions
|
|
51
|
+
if (!pos.IsBuy) {
|
|
52
|
+
const instrumentId = pos.InstrumentID;
|
|
53
|
+
if (!instrumentId) continue;
|
|
54
|
+
|
|
55
|
+
this._initAsset(instrumentId);
|
|
56
|
+
this.assets.set(instrumentId, this.assets.get(instrumentId) + 1);
|
|
21
57
|
}
|
|
22
58
|
}
|
|
23
59
|
}
|
|
@@ -26,16 +62,17 @@ class ShortPositionPerStock {
|
|
|
26
62
|
if (!this.mappings) {
|
|
27
63
|
this.mappings = await loadInstrumentMappings();
|
|
28
64
|
}
|
|
65
|
+
|
|
29
66
|
const result = {};
|
|
30
|
-
for (const instrumentId
|
|
31
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId
|
|
32
|
-
result[ticker] =
|
|
67
|
+
for (const [instrumentId, count] of this.assets.entries()) {
|
|
68
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
69
|
+
result[ticker] = count;
|
|
33
70
|
}
|
|
34
|
-
return
|
|
71
|
+
return result;
|
|
35
72
|
}
|
|
36
73
|
|
|
37
74
|
reset() {
|
|
38
|
-
this.
|
|
75
|
+
this.assets.clear();
|
|
39
76
|
this.mappings = null;
|
|
40
77
|
}
|
|
41
78
|
}
|
|
@@ -1,33 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total long figures.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the total number of long
|
|
5
|
+
* ('buy') positions across all users and instruments?"
|
|
3
6
|
*/
|
|
4
|
-
|
|
5
7
|
class TotalLongFigures {
|
|
6
8
|
constructor() {
|
|
7
|
-
this.
|
|
9
|
+
this.longCount = 0;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Defines the output schema for this calculation.
|
|
14
|
+
* @returns {object} JSON Schema object
|
|
15
|
+
*/
|
|
16
|
+
static getSchema() {
|
|
17
|
+
return {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Calculates the total count of all long ('buy') positions.",
|
|
20
|
+
"properties": {
|
|
21
|
+
"total_long_positions": {
|
|
22
|
+
"type": "number",
|
|
23
|
+
"description": "The aggregated count of all long positions."
|
|
15
24
|
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["total_long_positions"]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process(portfolioData) {
|
|
31
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
32
|
+
if (!positions || !Array.isArray(positions)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const pos of positions) {
|
|
37
|
+
if (pos.IsBuy) {
|
|
38
|
+
this.longCount++;
|
|
16
39
|
}
|
|
17
40
|
}
|
|
18
41
|
}
|
|
19
42
|
|
|
20
|
-
// Change getResult() (lines 20-22) to:
|
|
21
43
|
getResult() {
|
|
22
|
-
if (this.totalLongPositions === 0) return {};
|
|
23
44
|
return {
|
|
24
|
-
|
|
45
|
+
total_long_positions: this.longCount
|
|
25
46
|
};
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
reset() {
|
|
29
|
-
this.
|
|
50
|
+
this.longCount = 0;
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
53
|
|
|
33
|
-
module.exports = TotalLongFigures;
|
|
54
|
+
module.exports = TotalLongFigures;
|
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for total short figures.
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "What is the total number of short
|
|
5
|
+
* ('sell') positions across all users and instruments?"
|
|
3
6
|
*/
|
|
4
|
-
|
|
5
7
|
class TotalShortFigures {
|
|
6
8
|
constructor() {
|
|
7
|
-
this.
|
|
9
|
+
this.shortCount = 0;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Defines the output schema for this calculation.
|
|
14
|
+
* @returns {object} JSON Schema object
|
|
15
|
+
*/
|
|
16
|
+
static getSchema() {
|
|
17
|
+
return {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"description": "Calculates the total count of all short ('sell') positions.",
|
|
20
|
+
"properties": {
|
|
21
|
+
"total_short_positions": {
|
|
22
|
+
"type": "number",
|
|
23
|
+
"description": "The aggregated count of all short positions."
|
|
16
24
|
}
|
|
25
|
+
},
|
|
26
|
+
"required": ["total_short_positions"]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
process(portfolioData) {
|
|
31
|
+
const positions = portfolioData.PublicPositions || portfolioData.AggregatedPositions;
|
|
32
|
+
if (!positions || !Array.isArray(positions)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const pos of positions) {
|
|
37
|
+
if (!pos.IsBuy) {
|
|
38
|
+
this.shortCount++;
|
|
17
39
|
}
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
42
|
|
|
21
|
-
// Change getResult() (lines 20-22) to:
|
|
22
43
|
getResult() {
|
|
23
|
-
if (this.totalShortPositions === 0) return {};
|
|
24
44
|
return {
|
|
25
|
-
|
|
45
|
+
total_short_positions: this.shortCount
|
|
26
46
|
};
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
reset() {
|
|
30
|
-
this.
|
|
50
|
+
this.shortCount = 0;
|
|
31
51
|
}
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
module.exports = TotalShortFigures;
|
|
54
|
+
module.exports = TotalShortFigures;
|
|
@@ -1,52 +1,119 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* @fileoverview Social Calculation (Pass 1)
|
|
3
|
+
*
|
|
4
|
+
* This metric answers: "How many social media posts
|
|
5
|
+
* were made today for each asset (ticker)?"
|
|
6
|
+
*
|
|
7
|
+
* This is a stateful calculation that needs to read its
|
|
8
|
+
* own 30-day history from Firestore to calculate trend.
|
|
6
9
|
*/
|
|
7
|
-
|
|
8
10
|
class SocialAssetPostsTrend {
|
|
9
11
|
constructor() {
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
+
// { [ticker]: { count: 0, history: [] } }
|
|
13
|
+
this.assetData = new Map();
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* @param {object} todaySocialPostInsights - Map of { [postId]: postData } for today.
|
|
18
|
-
* @param {object} yesterdaySocialPostInsights - Not used.
|
|
17
|
+
* Defines the output schema for this calculation.
|
|
18
|
+
* @returns {object} JSON Schema object
|
|
19
19
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
static getSchema() {
|
|
21
|
+
const tickerSchema = {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "Daily and trended post count for a specific asset.",
|
|
24
|
+
"properties": {
|
|
25
|
+
"count": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Today's raw post count."
|
|
28
|
+
},
|
|
29
|
+
"trend_30d_pct": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Percentage change from the 30-day average post count."
|
|
32
|
+
},
|
|
33
|
+
"history_30d": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"description": "30-day history of post counts (today first).",
|
|
36
|
+
"items": { "type": "number" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"required": ["count", "trend_30d_pct", "history_30d"]
|
|
40
|
+
};
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
return {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"description": "Tracks the daily post count and 30-day trend for each asset ticker.",
|
|
45
|
+
"patternProperties": {
|
|
46
|
+
"^.*$": tickerSchema // Ticker
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": tickerSchema
|
|
49
|
+
};
|
|
50
|
+
}
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} dateStr - Today's date.
|
|
54
|
+
* @param {object} dependencies - db, logger.
|
|
55
|
+
* @param {object} config - Computation config.
|
|
56
|
+
* @param {object} fetchedDependencies - (UNUSED) In-memory results.
|
|
57
|
+
*/
|
|
58
|
+
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
59
|
+
const { db, logger, calculationUtils } = dependencies;
|
|
60
|
+
|
|
61
|
+
// 1. Load this metric's history from yesterday
|
|
62
|
+
const yHistoryData = await calculationUtils.loadYesterdayData('social-asset-posts-trend');
|
|
31
63
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
// 2. Initialize state with history
|
|
65
|
+
if (yHistoryData) {
|
|
66
|
+
for (const [ticker, data] of Object.entries(yHistoryData)) {
|
|
67
|
+
this.assetData.set(ticker, {
|
|
68
|
+
count: 0, // Today's count starts at 0
|
|
69
|
+
history: data.history_30d || []
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 3. Process today's raw social data
|
|
75
|
+
// This relies on the social data loader (not shown here)
|
|
76
|
+
const todaySocialPosts = await calculationUtils.loadSocialData(dateStr);
|
|
77
|
+
|
|
78
|
+
for (const post of todaySocialPosts) {
|
|
79
|
+
const tickers = post.tickers || [];
|
|
80
|
+
for (const ticker of tickers) {
|
|
81
|
+
if (!this.assetData.has(ticker)) {
|
|
82
|
+
this.assetData.set(ticker, { count: 0, history: [] });
|
|
37
83
|
}
|
|
84
|
+
const data = this.assetData.get(ticker);
|
|
85
|
+
data.count++;
|
|
38
86
|
}
|
|
39
87
|
}
|
|
40
88
|
}
|
|
41
89
|
|
|
42
90
|
async getResult() {
|
|
43
|
-
|
|
44
|
-
|
|
91
|
+
const result = {};
|
|
92
|
+
for (const [ticker, data] of this.assetData.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[ticker] = {
|
|
107
|
+
count: todayCount,
|
|
108
|
+
trend_30d_pct: trend_30d_pct,
|
|
109
|
+
history_30d: newHistory
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
45
113
|
}
|
|
46
114
|
|
|
47
115
|
reset() {
|
|
48
|
-
this.
|
|
49
|
-
this.processed = false;
|
|
116
|
+
this.assetData.clear();
|
|
50
117
|
}
|
|
51
118
|
}
|
|
52
119
|
|