aiden-shared-calculations-unified 1.0.34 → 1.0.36

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 (58) hide show
  1. package/README.MD +77 -77
  2. package/calculations/activity/historical/activity_by_pnl_status.js +85 -85
  3. package/calculations/activity/historical/daily_asset_activity.js +85 -85
  4. package/calculations/activity/historical/daily_user_activity_tracker.js +144 -144
  5. package/calculations/activity/historical/speculator_adjustment_activity.js +76 -76
  6. package/calculations/asset_metrics/asset_position_size.js +57 -57
  7. package/calculations/backtests/strategy-performance.js +229 -245
  8. package/calculations/behavioural/historical/asset_crowd_flow.js +165 -170
  9. package/calculations/behavioural/historical/drawdown_response.js +58 -58
  10. package/calculations/behavioural/historical/dumb-cohort-flow.js +249 -249
  11. package/calculations/behavioural/historical/gain_response.js +57 -57
  12. package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +98 -98
  13. package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +99 -99
  14. package/calculations/behavioural/historical/paper_vs_diamond_hands.js +39 -39
  15. package/calculations/behavioural/historical/position_count_pnl.js +67 -67
  16. package/calculations/behavioural/historical/smart-cohort-flow.js +250 -250
  17. package/calculations/behavioural/historical/smart_money_flow.js +165 -165
  18. package/calculations/behavioural/historical/user-investment-profile.js +412 -412
  19. package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +121 -121
  20. package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +117 -117
  21. package/calculations/capital_flow/historical/new_allocation_percentage.js +49 -49
  22. package/calculations/insights/daily_bought_vs_sold_count.js +55 -55
  23. package/calculations/insights/daily_buy_sell_sentiment_count.js +49 -49
  24. package/calculations/insights/daily_ownership_delta.js +55 -55
  25. package/calculations/insights/daily_total_positions_held.js +39 -39
  26. package/calculations/meta/capital_deployment_strategy.js +129 -137
  27. package/calculations/meta/capital_liquidation_performance.js +121 -163
  28. package/calculations/meta/capital_vintage_performance.js +121 -158
  29. package/calculations/meta/cash-flow-deployment.js +110 -124
  30. package/calculations/meta/cash-flow-liquidation.js +126 -142
  31. package/calculations/meta/crowd_sharpe_ratio_proxy.js +83 -91
  32. package/calculations/meta/profit_cohort_divergence.js +77 -91
  33. package/calculations/meta/smart-dumb-divergence-index.js +116 -138
  34. package/calculations/meta/social_flow_correlation.js +99 -125
  35. package/calculations/pnl/asset_pnl_status.js +46 -46
  36. package/calculations/pnl/historical/profitability_migration.js +57 -57
  37. package/calculations/pnl/historical/user_profitability_tracker.js +117 -117
  38. package/calculations/pnl/profitable_and_unprofitable_status.js +64 -64
  39. package/calculations/sectors/historical/diversification_pnl.js +76 -76
  40. package/calculations/sectors/historical/sector_rotation.js +67 -67
  41. package/calculations/sentiment/historical/crowd_conviction_score.js +80 -80
  42. package/calculations/socialPosts/social-asset-posts-trend.js +52 -52
  43. package/calculations/socialPosts/social-top-mentioned-words.js +102 -102
  44. package/calculations/socialPosts/social-topic-interest-evolution.js +53 -53
  45. package/calculations/socialPosts/social-word-mentions-trend.js +62 -62
  46. package/calculations/socialPosts/social_activity_aggregation.js +103 -103
  47. package/calculations/socialPosts/social_event_correlation.js +121 -121
  48. package/calculations/socialPosts/social_sentiment_aggregation.js +114 -114
  49. package/calculations/speculators/historical/risk_appetite_change.js +54 -54
  50. package/calculations/speculators/historical/tsl_effectiveness.js +74 -74
  51. package/index.js +33 -33
  52. package/package.json +32 -32
  53. package/utils/firestore_utils.js +76 -76
  54. package/utils/price_data_provider.js +142 -142
  55. package/utils/sector_mapping_provider.js +74 -74
  56. package/calculations/capital_flow/historical/reallocation_increase_percentage.js +0 -63
  57. package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +0 -91
  58. package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +0 -73
@@ -1,139 +1,117 @@
1
- /**
2
- * @fileoverview Meta-calculation (Pass 3) that correlates the asset/sector flow
3
- * of the "Smart Cohort" vs. the "Dumb Cohort" to find divergence signals.
4
- *
5
- * This identifies:
6
- * 1. "Capitulation": Smart cohort is buying what the dumb cohort is panic-selling.
7
- * 2. "Euphoria": Smart cohort is selling what the dumb cohort is FOMO-buying.
8
- */
9
-
10
- class SmartDumbDivergenceIndex {
11
- constructor() {
12
- // Minimum net flow (as a percentage) to be considered a signal
13
- this.FLOW_THRESHOLD = 0.005; // Formerly 0.5
14
- }
15
-
16
- /**
17
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
18
- * @param {object} dependencies The shared dependencies (db, logger).
19
- * @param {object} config The computation system configuration.
20
- * @returns {Promise<object|null>} The analysis result or null.
21
- */
22
- async process(dateStr, dependencies, config) {
23
- const { db, logger } = dependencies;
24
- const collection = config.resultsCollection;
25
- const resultsSub = config.resultsSubcollection || 'results';
26
- const compsSub = config.computationsSubcollection || 'computations';
27
-
28
- // 1. Define dependencies
29
- const refsToGet = [
30
- {
31
- key: 'smart_flow',
32
- ref: db.collection(collection).doc(dateStr).collection(resultsSub).doc('behavioural').collection(compsSub).doc('smart-cohort-flow')
33
- },
34
- {
35
- key: 'dumb_flow',
36
- ref: db.collection(collection).doc(dateStr).collection(resultsSub).doc('behavioural').collection(compsSub).doc('dumb-cohort-flow')
37
- }
38
- ];
39
-
40
- // 2. Fetch
41
- const snapshots = await db.getAll(...refsToGet.map(r => r.ref));
42
- const smartData = snapshots[0].exists ? snapshots[0].data() : null;
43
- const dumbData = snapshots[1].exists ? snapshots[1].data() : null;
44
-
45
- // 3. Handle "day-delay"
46
- // --- START MODIFICATION ---
47
- // This check now catches if the dependency calcs returned null (and thus docs don't exist)
48
- if (!smartData || !dumbData) {
49
- logger.log('WARN', `[SmartDumbDivergence] Missing cohort flow data for ${dateStr}. Allowing backfill.`);
50
- return null; // Let backfill handle it
51
- }
52
-
53
- const results = {
54
- assets: {},
55
- sectors: {}
56
- };
57
-
58
- // Check for the asset_flow key specifically.
59
- const smartAssetFlow = smartData.asset_flow;
60
- const dumbAssetFlow = dumbData.asset_flow;
61
- const smartSectorFlow = smartData.sector_rotation;
62
- const dumbSectorFlow = dumbData.sector_rotation;
63
-
64
- // If the docs exist but the data *inside* is missing (e.g., from an old, bad run), return null.
65
- if (!smartAssetFlow || !dumbAssetFlow || !smartSectorFlow || !dumbSectorFlow) {
66
- logger.log('WARN', `[SmartDumbDivergence] Dependency data for ${dateStr} is incomplete (missing asset_flow or sector_rotation). Allowing backfill.`);
67
- return null;
68
- }
69
- // --- END MODIFICATION ---
70
-
71
-
72
- // 4. Correlate Assets
73
- const allTickers = new Set([...Object.keys(smartAssetFlow), ...Object.keys(dumbAssetFlow)]);
74
- for (const ticker of allTickers) {
75
- const sFlow = smartAssetFlow[ticker]?.net_crowd_flow_pct || 0;
76
- const dFlow = dumbAssetFlow[ticker]?.net_crowd_flow_pct || 0;
77
-
78
- const smartBuys = sFlow >= this.FLOW_THRESHOLD;
79
- const smartSells = sFlow <= -this.FLOW_THRESHOLD;
80
- const dumbBuys = dFlow >= this.FLOW_THRESHOLD;
81
- const dumbSells = dFlow <= -this.FLOW_THRESHOLD;
82
-
83
- let status = 'No_Divergence';
84
- let detail = 'Cohorts are aligned or flow is insignificant.';
85
-
86
- if (smartBuys && dumbSells) {
87
- status = 'Capitulation';
88
- detail = 'Smart cohort is buying the dip from the panic-selling dumb cohort.';
89
- } else if (smartSells && dumbBuys) {
90
- status = 'Euphoria';
91
- detail = 'Smart cohort is selling into the FOMO-buying dumb cohort.';
92
- } else if (smartBuys && dumbBuys) {
93
- status = 'Aligned_Buy';
94
- } else if (smartSells && dumbSells) {
95
- status = 'Aligned_Sell';
96
- }
97
-
98
- if (status !== 'No_Divergence') {
99
- results.assets[ticker] = {
100
- status: status,
101
- detail: detail,
102
- smart_cohort_flow_pct: sFlow,
103
- dumb_cohort_flow_pct: dFlow
104
- };
105
- }
106
- }
107
-
108
- // 5. Correlate Sectors (Note: flow is total $, not %)
109
- // We can just check for opposing signs
110
- const allSectors = new Set([...Object.keys(smartSectorFlow), ...Object.keys(dumbSectorFlow)]);
111
- for (const sector of allSectors) {
112
- const sFlow = smartSectorFlow[sector] || 0;
113
- const dFlow = dumbSectorFlow[sector] || 0;
114
-
115
- let status = 'No_Divergence';
116
-
117
- if (sFlow > 0 && dFlow < 0) {
118
- status = 'Capitulation';
119
- } else if (sFlow < 0 && dFlow > 0) {
120
- status = 'Euphoria';
121
- }
122
-
123
- if (status !== 'No_Divergence') {
124
- results.sectors[sector] = {
125
- status: status,
126
- smart_cohort_flow_usd: sFlow,
127
- dumb_cohort_flow_usd: dFlow
128
- };
129
- }
130
- }
131
-
132
- return results;
133
- }
134
-
135
- async getResult() { return null; }
136
- reset() {}
137
- }
138
-
1
+ /**
2
+ * @fileoverview Meta-calculation (Pass 3) that correlates the asset/sector flow
3
+ * of the "Smart Cohort" vs. the "Dumb Cohort" to find divergence signals.
4
+ *
5
+ * This identifies:
6
+ * 1. "Capitulation": Smart cohort is buying what the dumb cohort is panic-selling.
7
+ * 2. "Euphoria": Smart cohort is selling what the dumb cohort is FOMO-buying.
8
+ */
9
+
10
+ class SmartDumbDivergenceIndex {
11
+ constructor() {
12
+ // Minimum net flow (as a percentage) to be considered a signal
13
+ this.FLOW_THRESHOLD = 0.005; // Formerly 0.5
14
+ }
15
+
16
+ /**
17
+ * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
18
+ * @param {object} dependencies The shared dependencies (db, logger).
19
+ * @param {object} config The computation system configuration.
20
+ * @param {object} computedDependencies In-memory results from previous passes.
21
+ * @returns {Promise<object|null>} The analysis result or null.
22
+ */
23
+ async process(dateStr, dependencies, config, computedDependencies) {
24
+ const { logger } = dependencies;
25
+
26
+ // 1. Get dependencies from in-memory cache
27
+ const smartData = computedDependencies['smart-cohort-flow'];
28
+ const dumbData = computedDependencies['dumb-cohort-flow'];
29
+
30
+ // 2. Handle missing dependencies
31
+ if (!smartData || !dumbData) {
32
+ logger.log('WARN', `[SmartDumbDivergence] Missing cohort flow data dependency for ${dateStr}. Skipping.`);
33
+ return null;
34
+ }
35
+
36
+ const results = {
37
+ assets: {},
38
+ sectors: {}
39
+ };
40
+
41
+ const smartAssetFlow = smartData.asset_flow;
42
+ const dumbAssetFlow = dumbData.asset_flow;
43
+ const smartSectorFlow = smartData.sector_rotation;
44
+ const dumbSectorFlow = dumbData.sector_rotation;
45
+
46
+ if (!smartAssetFlow || !dumbAssetFlow || !smartSectorFlow || !dumbSectorFlow) {
47
+ logger.log('WARN', `[SmartDumbDivergence] Dependency data for ${dateStr} is incomplete (missing asset_flow or sector_rotation). Skipping.`);
48
+ return null;
49
+ }
50
+
51
+ // 3. Correlate Assets
52
+ const allTickers = new Set([...Object.keys(smartAssetFlow), ...Object.keys(dumbAssetFlow)]);
53
+ for (const ticker of allTickers) {
54
+ const sFlow = smartAssetFlow[ticker]?.net_crowd_flow_pct || 0;
55
+ const dFlow = dumbAssetFlow[ticker]?.net_crowd_flow_pct || 0;
56
+
57
+ const smartBuys = sFlow >= this.FLOW_THRESHOLD;
58
+ const smartSells = sFlow <= -this.FLOW_THRESHOLD;
59
+ const dumbBuys = dFlow >= this.FLOW_THRESHOLD;
60
+ const dumbSells = dFlow <= -this.FLOW_THRESHOLD;
61
+
62
+ let status = 'No_Divergence';
63
+ let detail = 'Cohorts are aligned or flow is insignificant.';
64
+
65
+ if (smartBuys && dumbSells) {
66
+ status = 'Capitulation';
67
+ detail = 'Smart cohort is buying the dip from the panic-selling dumb cohort.';
68
+ } else if (smartSells && dumbBuys) {
69
+ status = 'Euphoria';
70
+ detail = 'Smart cohort is selling into the FOMO-buying dumb cohort.';
71
+ } else if (smartBuys && dumbBuys) {
72
+ status = 'Aligned_Buy';
73
+ } else if (smartSells && dumbSells) {
74
+ status = 'Aligned_Sell';
75
+ }
76
+
77
+ if (status !== 'No_Divergence') {
78
+ results.assets[ticker] = {
79
+ status: status,
80
+ detail: detail,
81
+ smart_cohort_flow_pct: sFlow,
82
+ dumb_cohort_flow_pct: dFlow
83
+ };
84
+ }
85
+ }
86
+
87
+ // 4. Correlate Sectors
88
+ const allSectors = new Set([...Object.keys(smartSectorFlow), ...Object.keys(dumbSectorFlow)]);
89
+ for (const sector of allSectors) {
90
+ const sFlow = smartSectorFlow[sector] || 0;
91
+ const dFlow = dumbSectorFlow[sector] || 0;
92
+
93
+ let status = 'No_Divergence';
94
+
95
+ if (sFlow > 0 && dFlow < 0) {
96
+ status = 'Capitulation';
97
+ } else if (sFlow < 0 && dFlow > 0) {
98
+ status = 'Euphoria';
99
+ }
100
+
101
+ if (status !== 'No_Divergence') {
102
+ results.sectors[sector] = {
103
+ status: status,
104
+ smart_cohort_flow_usd: sFlow,
105
+ dumb_cohort_flow_usd: dFlow
106
+ };
107
+ }
108
+ }
109
+
110
+ return results;
111
+ }
112
+
113
+ async getResult() { return null; }
114
+ reset() {}
115
+ }
116
+
139
117
  module.exports = SmartDumbDivergenceIndex;
@@ -1,126 +1,100 @@
1
- /**
2
- * @fileoverview Meta-calculation (Pass 3) to correlate daily social sentiment with
3
- * the actual crowd asset flow. It identifies divergences between what the crowd
4
- * says and what they do.
5
- */
6
-
7
- class SocialFlowCorrelation {
8
- // REPLACE this constructor in social_flow_correlation.js
9
- constructor() {
10
- // Define sensitivity thresholds
11
- this.bullishSentimentThreshold = 70.0; // % ratio (This is CORRECT)
12
- this.bearishSentimentThreshold = 30.0; // % ratio (This is CORRECT)
13
-
14
- // FIX: The flow thresholds must be small decimal percentages.
15
- this.positiveFlowThreshold = 0.005; // net_crowd_flow_pct (formerly 0.5)
16
- this.negativeFlowThreshold = -0.005; // net_crowd_flow_pct (formerly -0.5)
17
- }
18
-
19
- /**
20
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
21
- * @param {object} dependencies The shared dependencies (db, logger).
22
- * @param {object} config The computation system configuration.
23
- * @returns {Promise<object|null>} The analysis result or null.
24
- */
25
- async process(dateStr, dependencies, config) {
26
- const { db, logger } = dependencies;
27
- const collection = config.resultsCollection;
28
-
29
- // 1. Define dependencies for the *same day*
30
- const dependenciesToFetch = [
31
- { category: 'socialPosts', computation: 'social_sentiment_aggregation' },
32
- { category: 'behavioural', computation: 'asset_crowd_flow' }
33
- ];
34
-
35
- // 2. Build refs and fetch
36
- const refs = dependenciesToFetch.map(d =>
37
- db.collection(collection).doc(dateStr)
38
- .collection('results').doc(d.category)
39
- .collection('computations').doc(d.computation)
40
- );
41
-
42
- const snapshots = await db.getAll(...refs);
43
-
44
- // 3. Check for data and handle the "day-delay"
45
- const socialData = snapshots[0].exists ? snapshots[0].data() : null;
46
- const flowData = snapshots[1].exists ? snapshots[1].data() : null;
47
-
48
- if (!socialData || !flowData) {
49
- logger.log('WARN', `[SocialFlowCorrelation] Missing dependency data for ${dateStr}. Allowing backfill.`);
50
- // Return null. This stops execution and does not save an empty doc.
51
- // The Pass 3 backfill will pick this up tomorrow.
52
- return null;
53
- }
54
-
55
- // 4. If data exists, perform the correlation
56
- const sentimentMap = socialData.tickerSentiment || {};
57
- const correlationResults = {};
58
-
59
- // Use all tickers from the flow data as the primary loop
60
- for (const ticker in flowData) {
61
- if (!flowData[ticker] || typeof flowData[ticker].net_crowd_flow_pct === 'undefined') {
62
- continue;
63
- }
64
-
65
- const flow = flowData[ticker].net_crowd_flow_pct;
66
- const sentiment = sentimentMap[ticker]?.sentimentRatio; // e.g., 85.0 or 22.5
67
-
68
- if (typeof sentiment === 'undefined') {
69
- // No sentiment found, just record flow
70
- correlationResults[ticker] = {
71
- status: 'no_social_sentiment',
72
- net_crowd_flow_pct: flow
73
- };
74
- continue;
75
- }
76
-
77
- // --- The "Jaw-Drop" Logic ---
78
- if (sentiment >= this.bullishSentimentThreshold && flow <= this.negativeFlowThreshold) {
79
- // Crowd is very bullish but is actively selling
80
- correlationResults[ticker] = {
81
- status: 'Bullish Divergence',
82
- detail: 'Crowd is publicly bullish but is net-selling the asset.',
83
- sentiment_ratio: sentiment,
84
- net_crowd_flow_pct: flow
85
- };
86
- } else if (sentiment <= this.bearishSentimentThreshold && flow >= this.positiveFlowThreshold) {
87
- // Crowd is very bearish but is actively buying
88
- correlationResults[ticker] = {
89
- status: 'Bearish Divergence',
90
- detail: 'Crowd is publicly bearish but is net-buying the asset.',
91
- sentiment_ratio: sentiment,
92
- net_crowd_flow_pct: flow
93
- };
94
- } else if (sentiment >= this.bullishSentimentThreshold && flow >= this.positiveFlowThreshold) {
95
- // Crowd is bullish and is buying
96
- correlationResults[ticker] = {
97
- status: 'High Conviction Buy',
98
- sentiment_ratio: sentiment,
99
- net_crowd_flow_pct: flow
100
- };
101
- } else if (sentiment <= this.bearishSentimentThreshold && flow <= this.negativeFlowThreshold) {
102
- // Crowd is bearish and is selling
103
- correlationResults[ticker] = {
104
- status: 'High Conviction Sell',
105
- sentiment_ratio: sentiment,
106
- net_crowd_flow_pct: flow
107
- };
108
- } else {
109
- // No strong signal or divergence
110
- correlationResults[ticker] = {
111
- status: 'No Clear Signal',
112
- sentiment_ratio: sentiment,
113
- net_crowd_flow_pct: flow
114
- };
115
- }
116
- }
117
-
118
- return correlationResults;
119
- }
120
-
121
- // Must exist for the meta-computation runner
122
- async getResult() { return null; }
123
- reset() {}
124
- }
125
-
1
+ /**
2
+ * @fileoverview Meta-calculation (Pass 3) to correlate daily social sentiment with
3
+ * the actual crowd asset flow. It identifies divergences between what the crowd
4
+ * says and what they do.
5
+ */
6
+
7
+ class SocialFlowCorrelation {
8
+ constructor() {
9
+ this.bullishSentimentThreshold = 70.0;
10
+ this.bearishSentimentThreshold = 30.0;
11
+ this.positiveFlowThreshold = 0.005;
12
+ this.negativeFlowThreshold = -0.005;
13
+ }
14
+
15
+ /**
16
+ * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
17
+ * @param {object} dependencies The shared dependencies (db, logger).
18
+ * @param {object} config The computation system configuration.
19
+ * @param {object} computedDependencies In-memory results from previous passes.
20
+ * @returns {Promise<object|null>} The analysis result or null.
21
+ */
22
+ async process(dateStr, dependencies, config, computedDependencies) {
23
+ const { logger } = dependencies;
24
+
25
+ // 1. Get dependencies from in-memory cache
26
+ const socialData = computedDependencies['social-sentiment-aggregation'];
27
+ const flowData = computedDependencies['asset-crowd-flow'];
28
+
29
+ // 2. Handle missing dependencies
30
+ if (!socialData || !flowData) {
31
+ logger.log('WARN', `[SocialFlowCorrelation] Missing computed dependency data for ${dateStr}. Skipping.`);
32
+ return null;
33
+ }
34
+
35
+ // 3. If data exists, perform the correlation
36
+ const sentimentMap = socialData.tickerSentiment || {};
37
+ const correlationResults = {};
38
+
39
+ // Use all tickers from the flow data as the primary loop
40
+ for (const ticker in flowData) {
41
+ if (!flowData[ticker] || typeof flowData[ticker].net_crowd_flow_pct === 'undefined') {
42
+ continue;
43
+ }
44
+
45
+ const flow = flowData[ticker].net_crowd_flow_pct;
46
+ const sentiment = sentimentMap[ticker]?.sentimentRatio;
47
+
48
+ if (typeof sentiment === 'undefined') {
49
+ correlationResults[ticker] = {
50
+ status: 'no_social_sentiment',
51
+ net_crowd_flow_pct: flow
52
+ };
53
+ continue;
54
+ }
55
+
56
+ // --- The "Jaw-Drop" Logic ---
57
+ if (sentiment >= this.bullishSentimentThreshold && flow <= this.negativeFlowThreshold) {
58
+ correlationResults[ticker] = {
59
+ status: 'Bullish Divergence',
60
+ detail: 'Crowd is publicly bullish but is net-selling the asset.',
61
+ sentiment_ratio: sentiment,
62
+ net_crowd_flow_pct: flow
63
+ };
64
+ } else if (sentiment <= this.bearishSentimentThreshold && flow >= this.positiveFlowThreshold) {
65
+ correlationResults[ticker] = {
66
+ status: 'Bearish Divergence',
67
+ detail: 'Crowd is publicly bearish but is net-buying the asset.',
68
+ sentiment_ratio: sentiment,
69
+ net_crowd_flow_pct: flow
70
+ };
71
+ } else if (sentiment >= this.bullishSentimentThreshold && flow >= this.positiveFlowThreshold) {
72
+ correlationResults[ticker] = {
73
+ status: 'High Conviction Buy',
74
+ sentiment_ratio: sentiment,
75
+ net_crowd_flow_pct: flow
76
+ };
77
+ } else if (sentiment <= this.bearishSentimentThreshold && flow <= this.negativeFlowThreshold) {
78
+ correlationResults[ticker] = {
79
+ status: 'High Conviction Sell',
80
+ sentiment_ratio: sentiment,
81
+ net_crowd_flow_pct: flow
82
+ };
83
+ } else {
84
+ correlationResults[ticker] = {
85
+ status: 'No Clear Signal',
86
+ sentiment_ratio: sentiment,
87
+ net_crowd_flow_pct: flow
88
+ };
89
+ }
90
+ }
91
+
92
+ return correlationResults;
93
+ }
94
+
95
+ // Must exist for the meta-computation runner
96
+ async getResult() { return null; }
97
+ reset() {}
98
+ }
99
+
126
100
  module.exports = SocialFlowCorrelation;
@@ -1,47 +1,47 @@
1
- /**
2
- * Counts the number of users in profit vs. loss for each asset (from our sample).
3
- * This data is used for extrapolation with the ground truth owner count.
4
- */
5
- class AssetPnlStatus {
6
- constructor() {
7
- this.assets = {};
8
- }
9
-
10
- _initAsset(ticker) {
11
- if (!this.assets[ticker]) {
12
- this.assets[ticker] = { profit_count: 0, loss_count: 0 };
13
- }
14
- }
15
-
16
- process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
17
- const { instrumentMappings } = context;
18
- const processedTickers = new Set(); // Ensure one user is only counted once per ticker
19
-
20
- // FIX: Use the correct portfolio position properties
21
- const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
22
- if (!positions || !Array.isArray(positions)) return;
23
-
24
- for (const position of positions) {
25
- // FIX: Use the correct PascalCase InstrumentID
26
- const ticker = instrumentMappings[position.InstrumentID];
27
- if (!ticker || processedTickers.has(ticker)) continue;
28
-
29
- this._initAsset(ticker);
30
-
31
- // FIX: Use the correct PascalCase NetProfit
32
- if (position.NetProfit > 0) {
33
- this.assets[ticker].profit_count++;
34
- } else {
35
- this.assets[ticker].loss_count++;
36
- }
37
- processedTickers.add(ticker);
38
- }
39
- }
40
-
41
- getResult() {
42
- // Returns raw counts for frontend extrapolation
43
- return this.assets;
44
- }
45
- }
46
-
1
+ /**
2
+ * Counts the number of users in profit vs. loss for each asset (from our sample).
3
+ * This data is used for extrapolation with the ground truth owner count.
4
+ */
5
+ class AssetPnlStatus {
6
+ constructor() {
7
+ this.assets = {};
8
+ }
9
+
10
+ _initAsset(ticker) {
11
+ if (!this.assets[ticker]) {
12
+ this.assets[ticker] = { profit_count: 0, loss_count: 0 };
13
+ }
14
+ }
15
+
16
+ process(portfolioData, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights) {
17
+ const { instrumentMappings } = context;
18
+ const processedTickers = new Set(); // Ensure one user is only counted once per ticker
19
+
20
+ // FIX: Use the correct portfolio position properties
21
+ const positions = portfolioData.AggregatedPositions || portfolioData.PublicPositions;
22
+ if (!positions || !Array.isArray(positions)) return;
23
+
24
+ for (const position of positions) {
25
+ // FIX: Use the correct PascalCase InstrumentID
26
+ const ticker = instrumentMappings[position.InstrumentID];
27
+ if (!ticker || processedTickers.has(ticker)) continue;
28
+
29
+ this._initAsset(ticker);
30
+
31
+ // FIX: Use the correct PascalCase NetProfit
32
+ if (position.NetProfit > 0) {
33
+ this.assets[ticker].profit_count++;
34
+ } else {
35
+ this.assets[ticker].loss_count++;
36
+ }
37
+ processedTickers.add(ticker);
38
+ }
39
+ }
40
+
41
+ getResult() {
42
+ // Returns raw counts for frontend extrapolation
43
+ return this.assets;
44
+ }
45
+ }
46
+
47
47
  module.exports = AssetPnlStatus;