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,29 +1,79 @@
1
1
  /**
2
- * @fileoverview Calculates a crowd sentiment score for each instrument by counting long and short positions.
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
- this.sentimentData = {};
11
+ // { [instrumentId]: { long: 0, short: 0 } }
12
+ this.assets = new Map();
9
13
  this.mappings = null;
10
14
  }
11
15
 
12
- process(portfolioData, yesterdayPortfolio, userId, context) {
13
- if (!portfolioData || !portfolioData.AggregatedPositions) {
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 position of portfolioData.AggregatedPositions) {
18
- const instrumentId = position.InstrumentID;
19
- if (!this.sentimentData[instrumentId]) {
20
- this.sentimentData[instrumentId] = { long_positions: 0, short_positions: 0 };
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 (position.Direction === 'Buy') {
24
- this.sentimentData[instrumentId].long_positions++;
25
- } else if (position.Direction === 'Sell') {
26
- this.sentimentData[instrumentId].short_positions++;
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 in this.sentimentData) {
37
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
38
- result[ticker] = this.sentimentData[instrumentId];
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
- if (Object.keys(result).length === 0) return {};
41
- return { sentiment_by_asset: result };
98
+ return result;
42
99
  }
43
100
 
44
101
  reset() {
45
- this.sentimentData = {};
102
+ this.assets.clear();
46
103
  this.mappings = null;
47
104
  }
48
105
  }
@@ -1,23 +1,59 @@
1
1
  /**
2
- * @fileoverview Counts the total number of 'short' (Sell) positions for each instrument.
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
- this.shortCountByInstrument = {};
11
+ // We will store { [instrumentId]: count }
12
+ this.assets = new Map();
9
13
  this.mappings = null;
10
14
  }
11
15
 
12
- process(portfolioData, yesterdayPortfolio, userId, context) {
13
- if (!portfolioData || !portfolioData.AggregatedPositions) {
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 position of portfolioData.AggregatedPositions) {
18
- if (position.Direction === 'Sell') {
19
- const instrumentId = position.InstrumentID;
20
- this.shortCountByInstrument[instrumentId] = (this.shortCountByInstrument[instrumentId] || 0) + 1;
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 in this.shortCountByInstrument) {
31
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
32
- result[ticker] = this.shortCountByInstrument[instrumentId];
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 { short_positions_by_stock: result };
71
+ return result;
35
72
  }
36
73
 
37
74
  reset() {
38
- this.shortCountByInstrument = {};
75
+ this.assets.clear();
39
76
  this.mappings = null;
40
77
  }
41
78
  }
@@ -1,33 +1,54 @@
1
1
  /**
2
- * @fileoverview Calculates the total number of 'long' (Buy) positions across all users and instruments.
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.totalLongPositions = 0;
9
+ this.longCount = 0;
8
10
  }
9
11
 
10
- process(portfolioData, yesterdayPortfolio, userId, context) {
11
- if (portfolioData && portfolioData.AggregatedPositions) {
12
- for (const position of portfolioData.AggregatedPositions) {
13
- if (position.Direction === 'Buy') {
14
- this.totalLongPositions++;
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
- rawTotalLongPositions: this.totalLongPositions
45
+ total_long_positions: this.longCount
25
46
  };
26
47
  }
27
48
 
28
49
  reset() {
29
- this.totalLongPositions = 0;
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 Calculates the total number of 'short' (Sell) positions across all users and instruments.
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.totalShortPositions = 0;
9
+ this.shortCount = 0;
8
10
  }
9
11
 
10
- process(portfolioData, yesterdayPortfolio, userId, context) {
11
- if (portfolioData && portfolioData.AggregatedPositions) {
12
- for (const position of portfolioData.AggregatedPositions) {
13
- // Note: Short positions are indicated by Direction: 'Sell' in this data model
14
- if (position.Direction === 'Sell') {
15
- this.totalShortPositions++;
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
- rawTotalShortPositions: this.totalShortPositions
45
+ total_short_positions: this.shortCount
26
46
  };
27
47
  }
28
48
 
29
49
  reset() {
30
- this.totalShortPositions = 0;
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 Aggregates the total number of posts per asset (ticker).
3
- * This data can be used for a time-series trend (AssetPostsTrend) or
4
- * for a "Top Tickers" card.
5
- * This calculation ONLY uses the social post insights context.
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
- this.postsByTicker = {};
11
- this.processed = false; // Flag to ensure this runs only once
12
+ // { [ticker]: { count: 0, history: [] } }
13
+ this.assetData = new Map();
12
14
  }
13
15
 
14
16
  /**
15
- * @param {null} todayPortfolio - Not used.
16
- * ... (other params not used) ...
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
- async process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, todaySocialPostInsights, yesterdaySocialPostInsights) {
21
-
22
- if (this.processed || !todaySocialPostInsights) {
23
- return;
24
- }
25
- this.processed = true;
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
- const posts = Object.values(todaySocialPostInsights);
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
- for (const post of posts) {
30
- if (!post.tickers || !Array.isArray(post.tickers)) continue;
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
- for (const ticker of post.tickers) {
33
- // Ensure ticker is a string and valid
34
- const validTicker = String(ticker).trim();
35
- if (validTicker) {
36
- this.postsByTicker[validTicker] = (this.postsByTicker[validTicker] || 0) + 1;
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
- // Returns an object like: { "AAPL": 15, "TSLA": 22 }
44
- return this.postsByTicker;
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.postsByTicker = {};
49
- this.processed = false;
116
+ this.assetData.clear();
50
117
  }
51
118
  }
52
119