aiden-shared-calculations-unified 1.0.7 → 1.0.9

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 (35) hide show
  1. package/calculations/pnl/average_daily_pnl_all_users.js +1 -1
  2. package/calculations/pnl/average_daily_pnl_per_sector.js +1 -1
  3. package/calculations/pnl/average_daily_pnl_per_stock.js +1 -1
  4. package/calculations/pnl/average_daily_position_pnl.js +1 -1
  5. package/calculations/pnl/pnl_distribution_per_stock.js +52 -52
  6. package/calculations/pnl/profitability_ratio_per_stock.js +50 -50
  7. package/calculations/pnl/profitability_skew_per_stock.js +58 -58
  8. package/calculations/sanity/users_processed.js +26 -26
  9. package/calculations/sectors/total_long_per_sector.js +1 -1
  10. package/calculations/sectors/total_short_per_sector.js +1 -1
  11. package/calculations/short_and_long_stats/long_position_per_stock.js +1 -1
  12. package/calculations/short_and_long_stats/sentiment_per_stock.js +49 -49
  13. package/calculations/short_and_long_stats/short_position_per_stock.js +1 -1
  14. package/calculations/short_and_long_stats/total_long_figures.js +1 -1
  15. package/calculations/short_and_long_stats/total_short_figures.js +1 -1
  16. package/calculations/socialPosts/social-asset-posts-trend.js +53 -0
  17. package/calculations/socialPosts/social-top-mentioned-words.js +103 -0
  18. package/calculations/socialPosts/social-topic-interest-evolution.js +54 -0
  19. package/calculations/socialPosts/social-word-mentions-trend.js +63 -0
  20. package/calculations/speculators/distance_to_stop_loss_per_leverage.js +78 -78
  21. package/calculations/speculators/distance_to_tp_per_leverage.js +76 -76
  22. package/calculations/speculators/entry_distance_to_sl_per_leverage.js +78 -78
  23. package/calculations/speculators/entry_distance_to_tp_per_leverage.js +77 -77
  24. package/calculations/speculators/holding_duration_per_asset.js +55 -55
  25. package/calculations/speculators/leverage_per_asset.js +46 -46
  26. package/calculations/speculators/leverage_per_sector.js +44 -44
  27. package/calculations/speculators/risk_reward_ratio_per_asset.js +60 -60
  28. package/calculations/speculators/speculator_asset_sentiment.js +81 -81
  29. package/calculations/speculators/speculator_danger_zone.js +57 -57
  30. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +91 -91
  31. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +73 -73
  32. package/calculations/speculators/stop_loss_per_asset.js +55 -55
  33. package/calculations/speculators/take_profit_per_asset.js +55 -55
  34. package/calculations/speculators/tsl_per_asset.js +51 -51
  35. package/package.json +1 -1
@@ -9,7 +9,7 @@ class AverageDailyPnlAllUsers {
9
9
  this.userCount = 0;
10
10
  }
11
11
 
12
- process(portfolioData, userId, context) {
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
13
  if (!portfolioData || !portfolioData.AggregatedPositions || portfolioData.AggregatedPositions.length === 0) {
14
14
  return;
15
15
  }
@@ -9,7 +9,7 @@ class AverageDailyPnlPerSector {
9
9
  this.positions = [];
10
10
  }
11
11
 
12
- process(portfolioData, userId, context) {
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
13
  if (portfolioData && portfolioData.AggregatedPositions) {
14
14
  this.positions.push(...portfolioData.AggregatedPositions);
15
15
  }
@@ -10,7 +10,7 @@ class AverageDailyPnlPerStock {
10
10
  this.mappings = null;
11
11
  }
12
12
 
13
- process(portfolioData, userId, context) {
13
+ process(portfolioData, yesterdayPortfolio, userId, context) {
14
14
  const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
15
  if (!positions) {
16
16
  return;
@@ -9,7 +9,7 @@ class AverageDailyPositionPnl {
9
9
  this.totalPositions = 0;
10
10
  }
11
11
 
12
- process(portfolioData, userId, context) {
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
13
  if (portfolioData && portfolioData.AggregatedPositions) {
14
14
  for (const position of portfolioData.AggregatedPositions) {
15
15
  this.totalNetProfit += position.NetProfit;
@@ -1,53 +1,53 @@
1
- /**
2
- * @fileoverview Calculates the necessary components to determine the statistical variance of PnL for each stock,
3
- * serving as a proxy for crowd volatility.
4
- */
5
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
-
7
- class PnlDistributionPerStock {
8
- constructor() {
9
- this.distributionData = {};
10
- this.mappings = null;
11
- }
12
-
13
- process(portfolioData, userId, context) {
14
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
- if (!positions) return;
16
-
17
- for (const position of positions) {
18
- const instrumentId = position.InstrumentID;
19
- const netProfit = position.NetProfit;
20
-
21
- if (!this.distributionData[instrumentId]) {
22
- this.distributionData[instrumentId] = {
23
- pnl_sum: 0,
24
- pnl_sum_sq: 0,
25
- position_count: 0
26
- };
27
- }
28
-
29
- this.distributionData[instrumentId].pnl_sum += netProfit;
30
- this.distributionData[instrumentId].pnl_sum_sq += netProfit * netProfit;
31
- this.distributionData[instrumentId].position_count++;
32
- }
33
- }
34
-
35
- async getResult() {
36
- if (!this.mappings) {
37
- this.mappings = await loadInstrumentMappings();
38
- }
39
- const result = {};
40
- for (const instrumentId in this.distributionData) {
41
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
42
- result[ticker] = this.distributionData[instrumentId];
43
- }
44
- return { pnl_distribution_by_asset: result };
45
- }
46
-
47
- reset() {
48
- this.distributionData = {};
49
- this.mappings = null;
50
- }
51
- }
52
-
1
+ /**
2
+ * @fileoverview Calculates the necessary components to determine the statistical variance of PnL for each stock,
3
+ * serving as a proxy for crowd volatility.
4
+ */
5
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
+
7
+ class PnlDistributionPerStock {
8
+ constructor() {
9
+ this.distributionData = {};
10
+ this.mappings = null;
11
+ }
12
+
13
+ process(portfolioData, yesterdayPortfolio, userId, context) {
14
+ const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
+ if (!positions) return;
16
+
17
+ for (const position of positions) {
18
+ const instrumentId = position.InstrumentID;
19
+ const netProfit = position.NetProfit;
20
+
21
+ if (!this.distributionData[instrumentId]) {
22
+ this.distributionData[instrumentId] = {
23
+ pnl_sum: 0,
24
+ pnl_sum_sq: 0,
25
+ position_count: 0
26
+ };
27
+ }
28
+
29
+ this.distributionData[instrumentId].pnl_sum += netProfit;
30
+ this.distributionData[instrumentId].pnl_sum_sq += netProfit * netProfit;
31
+ this.distributionData[instrumentId].position_count++;
32
+ }
33
+ }
34
+
35
+ async getResult() {
36
+ if (!this.mappings) {
37
+ this.mappings = await loadInstrumentMappings();
38
+ }
39
+ const result = {};
40
+ for (const instrumentId in this.distributionData) {
41
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
42
+ result[ticker] = this.distributionData[instrumentId];
43
+ }
44
+ return { pnl_distribution_by_asset: result };
45
+ }
46
+
47
+ reset() {
48
+ this.distributionData = {};
49
+ this.mappings = null;
50
+ }
51
+ }
52
+
53
53
  module.exports = PnlDistributionPerStock;
@@ -1,51 +1,51 @@
1
- /**
2
- * @fileoverview Calculates the ratio of profitable vs. unprofitable positions for each stock.
3
- */
4
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
5
-
6
- class ProfitabilityRatioPerStock {
7
- constructor() {
8
- this.profitabilityData = {};
9
- this.mappings = null;
10
- }
11
-
12
- process(portfolioData, userId, context) {
13
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
14
- if (!positions) return;
15
-
16
- for (const position of positions) {
17
- const instrumentId = position.InstrumentID;
18
- const netProfit = position.NetProfit;
19
-
20
- if (!this.profitabilityData[instrumentId]) {
21
- this.profitabilityData[instrumentId] = { profitable: 0, unprofitable: 0 };
22
- }
23
-
24
- if (netProfit > 0) {
25
- this.profitabilityData[instrumentId].profitable++;
26
- } else if (netProfit < 0) {
27
- this.profitabilityData[instrumentId].unprofitable++;
28
- }
29
- }
30
- }
31
-
32
- async getResult() {
33
- if (!this.mappings) {
34
- this.mappings = await loadInstrumentMappings();
35
- }
36
- const result = {};
37
- for (const instrumentId in this.profitabilityData) {
38
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
39
- result[ticker] = this.profitabilityData[instrumentId];
40
- }
41
- if (Object.keys(result).length === 0) return {};
42
- return { profitability_by_asset: result };
43
- }
44
-
45
- reset() {
46
- this.profitabilityData = {};
47
- this.mappings = null;
48
- }
49
- }
50
-
1
+ /**
2
+ * @fileoverview Calculates the ratio of profitable vs. unprofitable positions for each stock.
3
+ */
4
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
5
+
6
+ class ProfitabilityRatioPerStock {
7
+ constructor() {
8
+ this.profitabilityData = {};
9
+ this.mappings = null;
10
+ }
11
+
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
+ const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
14
+ if (!positions) return;
15
+
16
+ for (const position of positions) {
17
+ const instrumentId = position.InstrumentID;
18
+ const netProfit = position.NetProfit;
19
+
20
+ if (!this.profitabilityData[instrumentId]) {
21
+ this.profitabilityData[instrumentId] = { profitable: 0, unprofitable: 0 };
22
+ }
23
+
24
+ if (netProfit > 0) {
25
+ this.profitabilityData[instrumentId].profitable++;
26
+ } else if (netProfit < 0) {
27
+ this.profitabilityData[instrumentId].unprofitable++;
28
+ }
29
+ }
30
+ }
31
+
32
+ async getResult() {
33
+ if (!this.mappings) {
34
+ this.mappings = await loadInstrumentMappings();
35
+ }
36
+ const result = {};
37
+ for (const instrumentId in this.profitabilityData) {
38
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
39
+ result[ticker] = this.profitabilityData[instrumentId];
40
+ }
41
+ if (Object.keys(result).length === 0) return {};
42
+ return { profitability_by_asset: result };
43
+ }
44
+
45
+ reset() {
46
+ this.profitabilityData = {};
47
+ this.mappings = null;
48
+ }
49
+ }
50
+
51
51
  module.exports = ProfitabilityRatioPerStock;
@@ -1,59 +1,59 @@
1
- /**
2
- * @fileoverview Calculates the magnitude of profitable vs. unprofitable positions for each stock,
3
- * providing insight into the skew of returns.
4
- */
5
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
-
7
- class ProfitabilitySkewPerStock {
8
- constructor() {
9
- this.skewData = {};
10
- this.mappings = null;
11
- }
12
-
13
- process(portfolioData, userId, context) {
14
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
- if (!positions) return;
16
-
17
- for (const position of positions) {
18
- const instrumentId = position.InstrumentID;
19
- const netProfit = position.NetProfit;
20
-
21
- if (!this.skewData[instrumentId]) {
22
- this.skewData[instrumentId] = {
23
- profit_sum: 0,
24
- loss_sum: 0,
25
- profitable_count: 0,
26
- unprofitable_count: 0
27
- };
28
- }
29
-
30
- if (netProfit > 0) {
31
- this.skewData[instrumentId].profit_sum += netProfit;
32
- this.skewData[instrumentId].profitable_count++;
33
- } else if (netProfit < 0) {
34
- this.skewData[instrumentId].loss_sum += netProfit; // Keep it negative to sum up losses
35
- this.skewData[instrumentId].unprofitable_count++;
36
- }
37
- }
38
- }
39
-
40
- async getResult() {
41
- if (!this.mappings) {
42
- this.mappings = await loadInstrumentMappings();
43
- }
44
- const result = {};
45
- for (const instrumentId in this.skewData) {
46
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
47
- result[ticker] = this.skewData[instrumentId];
48
- }
49
- if (Object.keys(result).length === 0) return {};
50
- return { profitability_skew_by_asset: result };
51
- }
52
-
53
- reset() {
54
- this.skewData = {};
55
- this.mappings = null;
56
- }
57
- }
58
-
1
+ /**
2
+ * @fileoverview Calculates the magnitude of profitable vs. unprofitable positions for each stock,
3
+ * providing insight into the skew of returns.
4
+ */
5
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
6
+
7
+ class ProfitabilitySkewPerStock {
8
+ constructor() {
9
+ this.skewData = {};
10
+ this.mappings = null;
11
+ }
12
+
13
+ process(portfolioData, yesterdayPortfolio, userId, context) {
14
+ const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
15
+ if (!positions) return;
16
+
17
+ for (const position of positions) {
18
+ const instrumentId = position.InstrumentID;
19
+ const netProfit = position.NetProfit;
20
+
21
+ if (!this.skewData[instrumentId]) {
22
+ this.skewData[instrumentId] = {
23
+ profit_sum: 0,
24
+ loss_sum: 0,
25
+ profitable_count: 0,
26
+ unprofitable_count: 0
27
+ };
28
+ }
29
+
30
+ if (netProfit > 0) {
31
+ this.skewData[instrumentId].profit_sum += netProfit;
32
+ this.skewData[instrumentId].profitable_count++;
33
+ } else if (netProfit < 0) {
34
+ this.skewData[instrumentId].loss_sum += netProfit; // Keep it negative to sum up losses
35
+ this.skewData[instrumentId].unprofitable_count++;
36
+ }
37
+ }
38
+ }
39
+
40
+ async getResult() {
41
+ if (!this.mappings) {
42
+ this.mappings = await loadInstrumentMappings();
43
+ }
44
+ const result = {};
45
+ for (const instrumentId in this.skewData) {
46
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
47
+ result[ticker] = this.skewData[instrumentId];
48
+ }
49
+ if (Object.keys(result).length === 0) return {};
50
+ return { profitability_skew_by_asset: result };
51
+ }
52
+
53
+ reset() {
54
+ this.skewData = {};
55
+ this.mappings = null;
56
+ }
57
+ }
58
+
59
59
  module.exports = ProfitabilitySkewPerStock;
@@ -1,27 +1,27 @@
1
- /**
2
- * @fileoverview A simple counter that increments for each user processed.
3
- */
4
-
5
- class UsersProcessed {
6
- constructor() {
7
- this.userCount = 0;
8
- }
9
-
10
- process(portfolioData, userId, context) {
11
- this.userCount++;
12
- }
13
-
14
- getResult() {
15
- if (this.userCount === 0) return {};
16
- // FIX: Rename the key to use the 'raw' prefix for correct aggregation.
17
- return {
18
- rawTotalUsersProcessed: this.userCount
19
- };
20
- }
21
-
22
- reset() {
23
- this.userCount = 0;
24
- }
25
- }
26
-
1
+ /**
2
+ * @fileoverview A simple counter that increments for each user processed.
3
+ */
4
+
5
+ class UsersProcessed {
6
+ constructor() {
7
+ this.userCount = 0;
8
+ }
9
+
10
+ process(portfolioData, yesterdayPortfolio, userId, context) {
11
+ this.userCount++;
12
+ }
13
+
14
+ getResult() {
15
+ if (this.userCount === 0) return {};
16
+ // FIX: Rename the key to use the 'raw' prefix for correct aggregation.
17
+ return {
18
+ rawTotalUsersProcessed: this.userCount
19
+ };
20
+ }
21
+
22
+ reset() {
23
+ this.userCount = 0;
24
+ }
25
+ }
26
+
27
27
  module.exports = UsersProcessed;
@@ -8,7 +8,7 @@ class TotalLongPerSector {
8
8
  this.longPositions = [];
9
9
  }
10
10
 
11
- process(portfolioData, userId, context) {
11
+ process(portfolioData, yesterdayPortfolio, userId, context) {
12
12
  if (portfolioData && portfolioData.AggregatedPositions) {
13
13
  for (const position of portfolioData.AggregatedPositions) {
14
14
  if (position.Direction === 'Buy') {
@@ -8,7 +8,7 @@ class TotalShortPerSector {
8
8
  this.shortPositions = [];
9
9
  }
10
10
 
11
- process(portfolioData, userId, context) {
11
+ process(portfolioData, yesterdayPortfolio, userId, context) {
12
12
  if (portfolioData && portfolioData.AggregatedPositions) {
13
13
  for (const position of portfolioData.AggregatedPositions) {
14
14
  if (position.Direction === 'Sell') {
@@ -10,7 +10,7 @@ class LongPositionPerStock {
10
10
  this.mappings = null;
11
11
  }
12
12
 
13
- process(portfolioData, userId, context) {
13
+ process(portfolioData, yesterdayPortfolio, userId, context) {
14
14
  if (!portfolioData || !portfolioData.AggregatedPositions) {
15
15
  return;
16
16
  }
@@ -1,50 +1,50 @@
1
- /**
2
- * @fileoverview Calculates a crowd sentiment score for each instrument by counting long and short positions.
3
- */
4
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
5
-
6
- class SentimentPerStock {
7
- constructor() {
8
- this.sentimentData = {};
9
- this.mappings = null;
10
- }
11
-
12
- process(portfolioData, userId, context) {
13
- if (!portfolioData || !portfolioData.AggregatedPositions) {
14
- return;
15
- }
16
-
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
- }
22
-
23
- if (position.Direction === 'Buy') {
24
- this.sentimentData[instrumentId].long_positions++;
25
- } else if (position.Direction === 'Sell') {
26
- this.sentimentData[instrumentId].short_positions++;
27
- }
28
- }
29
- }
30
-
31
- async getResult() {
32
- if (!this.mappings) {
33
- this.mappings = await loadInstrumentMappings();
34
- }
35
- const result = {};
36
- for (const instrumentId in this.sentimentData) {
37
- const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
38
- result[ticker] = this.sentimentData[instrumentId];
39
- }
40
- if (Object.keys(result).length === 0) return {};
41
- return { sentiment_by_asset: result };
42
- }
43
-
44
- reset() {
45
- this.sentimentData = {};
46
- this.mappings = null;
47
- }
48
- }
49
-
1
+ /**
2
+ * @fileoverview Calculates a crowd sentiment score for each instrument by counting long and short positions.
3
+ */
4
+ const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
5
+
6
+ class SentimentPerStock {
7
+ constructor() {
8
+ this.sentimentData = {};
9
+ this.mappings = null;
10
+ }
11
+
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
+ if (!portfolioData || !portfolioData.AggregatedPositions) {
14
+ return;
15
+ }
16
+
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
+ }
22
+
23
+ if (position.Direction === 'Buy') {
24
+ this.sentimentData[instrumentId].long_positions++;
25
+ } else if (position.Direction === 'Sell') {
26
+ this.sentimentData[instrumentId].short_positions++;
27
+ }
28
+ }
29
+ }
30
+
31
+ async getResult() {
32
+ if (!this.mappings) {
33
+ this.mappings = await loadInstrumentMappings();
34
+ }
35
+ const result = {};
36
+ for (const instrumentId in this.sentimentData) {
37
+ const ticker = this.mappings.instrumentToTicker[instrumentId] || instrumentId.toString();
38
+ result[ticker] = this.sentimentData[instrumentId];
39
+ }
40
+ if (Object.keys(result).length === 0) return {};
41
+ return { sentiment_by_asset: result };
42
+ }
43
+
44
+ reset() {
45
+ this.sentimentData = {};
46
+ this.mappings = null;
47
+ }
48
+ }
49
+
50
50
  module.exports = SentimentPerStock;
@@ -9,7 +9,7 @@ class ShortPositionPerStock {
9
9
  this.mappings = null;
10
10
  }
11
11
 
12
- process(portfolioData, userId, context) {
12
+ process(portfolioData, yesterdayPortfolio, userId, context) {
13
13
  if (!portfolioData || !portfolioData.AggregatedPositions) {
14
14
  return;
15
15
  }
@@ -7,7 +7,7 @@ class TotalLongFigures {
7
7
  this.totalLongPositions = 0;
8
8
  }
9
9
 
10
- process(portfolioData, userId, context) {
10
+ process(portfolioData, yesterdayPortfolio, userId, context) {
11
11
  if (portfolioData && portfolioData.AggregatedPositions) {
12
12
  for (const position of portfolioData.AggregatedPositions) {
13
13
  if (position.Direction === 'Buy') {
@@ -7,7 +7,7 @@ class TotalShortFigures {
7
7
  this.totalShortPositions = 0;
8
8
  }
9
9
 
10
- process(portfolioData, userId, context) {
10
+ process(portfolioData, yesterdayPortfolio, userId, context) {
11
11
  if (portfolioData && portfolioData.AggregatedPositions) {
12
12
  for (const position of portfolioData.AggregatedPositions) {
13
13
  // Note: Short positions are indicated by Direction: 'Sell' in this data model
@@ -0,0 +1,53 @@
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.
6
+ */
7
+
8
+ class SocialAssetPostsTrend {
9
+ constructor() {
10
+ this.postsByTicker = {};
11
+ this.processed = false; // Flag to ensure this runs only once
12
+ }
13
+
14
+ /**
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.
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;
26
+
27
+ const posts = Object.values(todaySocialPostInsights);
28
+
29
+ for (const post of posts) {
30
+ if (!post.tickers || !Array.isArray(post.tickers)) continue;
31
+
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;
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ async getResult() {
43
+ // Returns an object like: { "AAPL": 15, "TSLA": 22 }
44
+ return this.postsByTicker;
45
+ }
46
+
47
+ reset() {
48
+ this.postsByTicker = {};
49
+ this.processed = false;
50
+ }
51
+ }
52
+
53
+ module.exports = SocialAssetPostsTrend;