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,181 +1,102 @@
1
1
  /**
2
- * @fileoverview NEW CALCULATION (PASS 4 - META)
3
- * This is the "Quant Regime Engine" or "Master Switch."
2
+ * @fileoverview Calculation (Pass 5) for social predictive regime.
4
3
  *
5
- * It determines if the entire class of "social-to-price" signals
6
- * is currently effective ("ON") or not ("OFF").
4
+ * This metric answers: "Are we in a 'social-driven' or 'flow-driven'
5
+ * market regime?"
7
6
  *
8
- * It is a STATEFUL, ROLLING calculation.
9
- * 1. It consumes the 'daily_topic_signals' from Pass 3 for the current day.
10
- * 2. It reads its *own* rolling history of past "breadth" metrics
11
- * from a single document in Firestore ('social_prediction_regime_state/history').
12
- * 3. It calculates the "predictive breadth" for the current day:
13
- * (What % of all topics analyzed had a Predictive Potential > threshold?)
14
- * 4. It appends this new breadth metric to its history.
15
- * 5. It calculates a short-term and long-term EMA of this breadth metric.
16
- * 6. It uses the EMA crossover to determine the `regime_state`.
17
- * 7. It returns its updated state AND the daily regime signal.
7
+ * It *depends* on 'social_flow_correlation' (Pass 4) to make
8
+ * a determination.
18
9
  */
19
-
20
- const { FieldValue } = require('@google-cloud/firestore');
21
-
22
- // --- CONFIGURATION ---
23
- // The single doc where this calc stores its rolling history
24
- const STATE_DOC_PATH = 'social_prediction_regime_state/history';
25
- const ROLLING_HISTORY_DAYS = 90; // History for EMAs
26
- const EMA_SHORT_PERIOD = 3; // 3-day EMA
27
- const EMA_LONG_PERIOD = 14; // 14-day EMA
28
- // How far apart the EMAs must be to declare a firm regime
29
- const REGIME_THRESHOLD = 0.02; // e.g., 2% difference
30
- // The Predictive Potential (from Pass 3) threshold to be counted
31
- const PP_THRESHOLD = 0.5;
32
-
33
- // --- STATS HELPER ---
34
- /**
35
- * Calculates a simple Exponential Moving Average (EMA) from an array of values.
36
- * @param {number[]} data - Array of numbers (oldest to newest).
37
- * @param {number} period - The EMA period.
38
- * @returns {number|null} The final EMA value, or null if not enough data.
39
- */
40
- function _calculateEMA(data, period) {
41
- if (data.length < period) {
42
- return null;
43
- }
44
- const k = 2 / (period + 1); // Smoothing factor
45
- let ema = data[0]; // Start with the first value
46
- for (let i = 1; i < data.length; i++) {
47
- ema = (data[i] * k) + (ema * (1 - k));
10
+ class SocialPredictiveRegimeState {
11
+ constructor() {
12
+ // No per-user processing
48
13
  }
49
- return ema;
50
- }
51
14
 
15
+ /**
16
+ * Defines the output schema for this calculation.
17
+ * @returns {object} JSON Schema object
18
+ */
19
+ static getSchema() {
20
+ return {
21
+ "type": "object",
22
+ "description": "Determines if the market is in a 'social-driven' or 'flow-driven' regime based on correlation.",
23
+ "properties": {
24
+ "regime": {
25
+ "type": "string",
26
+ "enum": ["Social-Driven", "Flow-Driven", "Decoupled", "Unknown"],
27
+ "description": "The current market regime."
28
+ },
29
+ "average_correlation": {
30
+ "type": "number",
31
+ "description": "The average correlation value across all assets from 'social_flow_correlation'."
32
+ },
33
+ "correlated_assets_count": {
34
+ "type": "number",
35
+ "description": "Number of assets with a strong positive correlation."
36
+ },
37
+ "decoupled_assets_count": {
38
+ "type": "number",
39
+ "description": "Number of assets with a weak or negative correlation."
40
+ }
41
+ },
42
+ "required": ["regime", "average_correlation", "correlated_assets_count", "decoupled_assets_count"]
43
+ };
44
+ }
52
45
 
53
- class SocialPredictiveRegimeState {
54
-
46
+ /**
47
+ * Statically declare dependencies.
48
+ */
55
49
  static getDependencies() {
56
- // Depends on the *daily output* from Pass 3
57
- return ['social-topic-predictive-potential'];
50
+ return [
51
+ 'social_flow_correlation' // Pass 4
52
+ ];
58
53
  }
59
54
 
60
- constructor() {}
55
+ process() {
56
+ // No-op
57
+ }
61
58
 
62
- /**
63
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-11-06").
64
- * @param {object} dependencies The shared dependencies (db, logger).
65
- * @param {object} config The computation system configuration.
66
- * @param {object} fetchedDependencies In-memory results from Pass 3.
67
- * @returns {Promise<object|null>} The analysis result or null.
68
- */
69
- async process(dateStr, dependencies, config, fetchedDependencies) {
70
- const { db, logger } = dependencies;
71
-
72
- // 1. Get Pass 3 Dependency
73
- const pass3_output = fetchedDependencies['social-topic-predictive-potential'];
74
- if (!pass3_output || !pass3_output.daily_topic_signals) {
75
- logger.log('WARN', `[SocialRegime] Missing or empty dependency 'social-topic-predictive-potential' for ${dateStr}. Skipping.`);
76
- return null;
77
- }
78
- const daily_signals = pass3_output.daily_topic_signals;
59
+ getResult(fetchedDependencies) {
60
+ const correlationData = fetchedDependencies['social_flow_correlation'];
79
61
 
80
- // 2. Calculate Today's "Predictive Breadth"
81
- let totalTopicsAnalyzed = 0;
82
- let totalPredictiveTopics = 0;
62
+ const defaults = {
63
+ regime: "Unknown",
64
+ average_correlation: 0,
65
+ correlated_assets_count: 0,
66
+ decoupled_assets_count: 0
67
+ };
83
68
 
84
- for (const ticker in daily_signals) {
85
- const allTopics = [
86
- ...(daily_signals[ticker].topPredictiveBullishTopics || []),
87
- ...(daily_signals[ticker].topPredictiveBearishTopics || [])
88
- ];
89
-
90
- for (const topic of allTopics) {
91
- totalTopicsAnalyzed++;
92
- // Check if this topic is "predictive"
93
- if (topic.predictivePotential > PP_THRESHOLD) {
94
- // Check if it has at least one stable window
95
- const hasStableWindow = Object.values(topic.correlations).some(
96
- corr => corr.samples >= MIN_SAMPLE_COUNT && Math.abs(corr.value) > CORR_THRESHOLD
97
- );
98
- if (hasStableWindow) {
99
- totalPredictiveTopics++;
100
- }
101
- }
102
- }
69
+ if (!correlationData) {
70
+ return defaults;
103
71
  }
104
72
 
105
- const todayBreadth = (totalTopicsAnalyzed > 0)
106
- ? (totalPredictiveTopics / totalTopicsAnalyzed)
107
- : 0; // 0% breadth if no topics found
73
+ const correlations = Object.values(correlationData).map(d => d.correlation_30d).filter(c => c !== null);
74
+ if (correlations.length === 0) {
75
+ return defaults;
76
+ }
108
77
 
109
- // 3. Load State, Update, and Save (all in one transaction)
78
+ const avgCorrelation = correlations.reduce((a, b) => a + b, 0) / correlations.length;
79
+ const correlatedCount = correlations.filter(c => c > 0.5).length;
80
+ const decoupledCount = correlations.filter(c => c <= 0.5).length;
110
81
 
111
- let dailyOutput = {}; // The daily signal for the API
112
- let stateToSave = {}; // The new state to save back to Firestore
113
-
114
- try {
115
- await db.runTransaction(async (transaction) => {
116
- const docRef = db.doc(STATE_DOC_PATH);
117
- const doc = await transaction.get(docRef);
118
-
119
- // 4a. Load and update history
120
- const history = doc.exists ? (doc.data().history || []) : [];
121
- history.push({ date: dateStr, breadth: todayBreadth });
122
-
123
- // 4b. Prune history
124
- const prunedHistory = history.slice(-ROLLING_HISTORY_DAYS);
125
-
126
- // 4c. Calculate EMAs
127
- const breadthValues = prunedHistory.map(h => h.breadth);
128
- const emaShort = _calculateEMA(breadthValues, EMA_SHORT_PERIOD);
129
- const emaLong = _calculateEMA(breadthValues, EMA_LONG_PERIOD);
130
-
131
- // 4d. Determine Regime State
132
- let regimeState = "TRANSITIONING";
133
- if (emaShort !== null && emaLong !== null) {
134
- const diff = emaShort - emaLong;
135
- if (diff > REGIME_THRESHOLD) {
136
- regimeState = "ON";
137
- } else if (diff < -REGIME_THRESHOLD) {
138
- regimeState = "OFF";
139
- }
140
- }
141
-
142
- // 4e. Prepare data to save and return
143
- stateToSave = {
144
- history: prunedHistory,
145
- lastUpdated: FieldValue.serverTimestamp()
146
- };
147
-
148
- dailyOutput = {
149
- social_predictive_regime_strength: emaShort, // The "fast" signal
150
- regime_state: regimeState,
151
- components: {
152
- today_breadth_pct: todayBreadth,
153
- ema_short: emaShort,
154
- ema_long: emaLong,
155
- threshold: REGIME_THRESHOLD
156
- }
157
- };
158
-
159
- // 4f. Save state back to Firestore
160
- transaction.set(docRef, stateToSave);
161
- });
162
-
163
- } catch (error) {
164
- logger.log('ERROR', `[SocialRegime] Transaction failed for ${dateStr}`, { err: error.message });
165
- return null; // Don't return partial data
82
+ let regime = "Decoupled";
83
+ if (avgCorrelation > 0.5 && correlatedCount > decoupledCount) {
84
+ regime = "Social-Driven";
85
+ } else if (avgCorrelation < 0.2) {
86
+ regime = "Flow-Driven"; // Social is not predictive, flow is
166
87
  }
167
88
 
168
- // 5. Return both state (for sharding) and daily signal (for API)
169
89
  return {
170
- // This key name must be unique
171
- sharded_regime_state: { [STATE_DOC_PATH]: stateToSave },
172
- // This is the daily result for unified_insights
173
- daily_regime_signal: dailyOutput
90
+ regime: regime,
91
+ average_correlation: avgCorrelation,
92
+ correlated_assets_count: correlatedCount,
93
+ decoupled_assets_count: decoupledCount
174
94
  };
175
95
  }
176
96
 
177
- async getResult() { return null; } // Logic is in process()
178
- reset() {}
97
+ reset() {
98
+ // No state
99
+ }
179
100
  }
180
101
 
181
102
  module.exports = SocialPredictiveRegimeState;
@@ -1,153 +1,100 @@
1
1
  /**
2
- * @fileoverview NEW CALCULATION (PASS 2 - META)
3
- * This is the advanced signal calculation. It consumes the
4
- * 'social-topic-sentiment-matrix' from Pass 1, loads the
5
- * daily price change for each asset, and then determines
6
- * the primary bullish and bearish topics driving the conversation.
2
+ * @fileoverview Calculation (Pass 5) for social topic driver index.
3
+ *
4
+ * This metric answers: "Which social media topics are driving
5
+ * the most asset price movement or capital flow?"
6
+ *
7
+ * It *depends* on 'social-topic-predictive-potential' (Pass 5).
7
8
  */
8
-
9
- // We need calculation utils to load price and mapping data
10
- const { loadAllPriceData, getDailyPriceChange } = require('../../utils/price_data_provider');
11
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
12
-
13
9
  class SocialTopicDriverIndex {
14
-
15
- /**
16
- * Statically declare dependencies.
17
- * This will run in Pass 2, after the matrix is built.
18
- */
19
- static getDependencies() {
20
- return ['social-topic-sentiment-matrix'];
21
- }
22
-
23
10
  constructor() {
24
- this.priceMap = null;
25
- this.tickerToIdMap = null; // For { "AAPL" -> 123 }
26
- this.dependenciesLoaded = false;
11
+ // No per-user processing
27
12
  }
28
13
 
29
14
  /**
30
- * Helper to load price/mapping data on the first run.
15
+ * Defines the output schema for this calculation.
16
+ * @returns {object} JSON Schema object
31
17
  */
32
- async _loadDependencies(calculationUtils) {
33
- if (this.dependenciesLoaded) return;
34
-
35
- const [priceData, mappings] = await Promise.all([
36
- calculationUtils.loadAllPriceData(),
37
- calculationUtils.loadInstrumentMappings()
38
- ]);
39
-
40
- this.priceMap = priceData;
41
-
42
- // Create the Ticker -> InstrumentID map
43
- this.tickerToIdMap = {};
44
- if (mappings && mappings.instrumentToTicker) {
45
- for (const [id, ticker] of Object.entries(mappings.instrumentToTicker)) {
46
- this.tickerToIdMap[ticker] = id;
47
- }
48
- }
49
-
50
- this.dependenciesLoaded = true;
18
+ static getSchema() {
19
+ const topicSchema = {
20
+ "type": "object",
21
+ "properties": {
22
+ "topic": { "type": "string" },
23
+ "driver_score": { "type": "number" },
24
+ "correlation_flow_30d": { "type": "number" },
25
+ "correlation_price_30d": { "type": "number" }
26
+ },
27
+ "required": ["topic", "driver_score"]
28
+ };
29
+
30
+ return {
31
+ "type": "object",
32
+ "description": "Ranks social media topics by their 'driver_score' (predictive potential for flow and price).",
33
+ "properties": {
34
+ "top_driving_topics": {
35
+ "type": "array",
36
+ "description": "Top 5 topics driving market activity.",
37
+ "items": topicSchema
38
+ },
39
+ "all_topics": {
40
+ "type": "array",
41
+ "description": "Full list of all topics and their driver scores.",
42
+ "items": topicSchema
43
+ }
44
+ },
45
+ "required": ["top_driving_topics", "all_topics"]
46
+ };
51
47
  }
52
48
 
53
49
  /**
54
- * Helper to get the previous day's date string.
50
+ * Statically declare dependencies.
55
51
  */
56
- _getYesterday(dateStr) {
57
- const date = new Date(dateStr + 'T00:00:00Z');
58
- date.setUTCDate(date.getUTCDate() - 1);
59
- return date.toISOString().slice(0, 10);
52
+ static getDependencies() {
53
+ return [
54
+ 'social-topic-predictive-potential' // Pass 5
55
+ ];
60
56
  }
61
57
 
62
- /**
63
- * @param {string} dateStr The date to run the analysis for (e.g., "2025-11-05").
64
- * @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
65
- * @param {object} config The computation system configuration.
66
- * @param {object} fetchedDependencies In-memory results from Pass 1.
67
- * e.g., { 'social-topic-sentiment-matrix': { "AAPL": { ... } } }
68
- * @returns {Promise<object|null>} The analysis result or null.
69
- */
70
- async process(dateStr, dependencies, config, fetchedDependencies) {
71
- const { logger, calculationUtils } = dependencies;
72
-
73
- // 1. Load dependencies
74
- await this._loadDependencies(calculationUtils);
75
- const matrix = fetchedDependencies['social-topic-sentiment-matrix'];
58
+ process() {
59
+ // No-op
60
+ }
76
61
 
77
- if (!matrix || Object.keys(matrix).length === 0) {
78
- logger.log('WARN', `[SocialTopicDriverIndex] Missing or empty dependency 'social-topic-sentiment-matrix' for ${dateStr}. Skipping.`);
79
- return null;
80
- }
62
+ getResult(fetchedDependencies) {
63
+ const potentialData = fetchedDependencies['social-topic-predictive-potential'];
64
+
65
+ const defaults = {
66
+ top_driving_topics: [],
67
+ all_topics: []
68
+ };
81
69
 
82
- if (!this.priceMap || !this.tickerToIdMap) {
83
- logger.log('ERROR', `[SocialTopicDriverIndex] Price map or Ticker map failed to load. Aborting.`);
84
- return null;
70
+ if (!potentialData) {
71
+ return defaults;
85
72
  }
86
73
 
87
- const yesterdayStr = this._getYesterday(dateStr); // e.g., "2025-11-04"
88
- const finalResults = {};
89
-
90
- // 2. Correlate for each ticker
91
- for (const ticker in matrix) {
92
- const topicData = matrix[ticker];
93
- const instrumentId = this.tickerToIdMap[ticker];
94
-
95
- if (!instrumentId) {
96
- logger.log('TRACE', `[SocialTopicDriverIndex] Skipping ${ticker}, no instrumentId found.`);
97
- continue;
98
- }
99
-
100
- // 3. Get Price Change
101
- // This correctly uses the resilient _findPreviousAvailablePrice helper
102
- const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, dateStr, this.priceMap);
103
-
104
- if (priceChangePct === null) {
105
- logger.log('TRACE', `[SocialTopicDriverIndex] Skipping ${ticker}, no price data found for ${dateStr}.`);
106
- continue; // Skip if we can't get price data
107
- }
108
-
109
- // 4. Analyze topics for this ticker
110
- const topicDrivers = [];
111
- for (const topic in topicData) {
112
- const data = topicData[topic];
113
- const totalSentientPosts = data.bullishPosts + data.bearishPosts;
114
-
115
- let sentimentScore = 0;
116
- if (totalSentientPosts > 0) {
117
- sentimentScore = (data.bullishPosts - data.bearishPosts) / totalSentientPosts;
118
- }
119
-
120
- topicDrivers.push({
121
- topic: topic,
122
- sentimentScore: sentimentScore, // -1 (Bearish) to +1 (Bullish)
123
- convictionScore: data.convictionScore,
124
- totalPosts: data.totalPosts,
125
- bullishPosts: data.bullishPosts,
126
- bearishPosts: data.bearishPosts
127
- });
128
- }
129
-
130
- // 5. Find top drivers by conviction
131
- // Sort by most conviction (likes/comments)
132
- topicDrivers.sort((a, b) => b.convictionScore - a.convictionScore);
74
+ const allTopics = [];
75
+ for (const [topic, data] of Object.entries(potentialData)) {
76
+ // Create a "Driver Score" - prioritize flow correlation
77
+ const score = (data.correlation_flow_30d * 0.7) + (data.correlation_price_30d * 0.3);
133
78
 
134
- // Find the most-discussed bullish and bearish topics
135
- const topBullishDriver = topicDrivers.find(d => d.sentimentScore > 0.1) || null;
136
- const topBearishDriver = topicDrivers.find(d => d.sentimentScore < -0.1) || null;
137
-
138
- finalResults[ticker] = {
139
- priceChangePct: priceChangePct,
140
- topBullishDriver: topBullishDriver,
141
- topBearishDriver: topBearishDriver,
142
- allDrivers: topicDrivers // Store all for potential future analysis
143
- };
79
+ allTopics.push({
80
+ topic: topic,
81
+ driver_score: score,
82
+ correlation_flow_30d: data.correlation_flow_30d,
83
+ correlation_price_30d: data.correlation_price_30d
84
+ });
144
85
  }
86
+
87
+ allTopics.sort((a, b) => b.driver_score - a.driver_score);
145
88
 
146
- return finalResults;
89
+ return {
90
+ top_driving_topics: allTopics.slice(0, 5),
91
+ all_topics: allTopics
92
+ };
147
93
  }
148
94
 
149
- async getResult() { return null; } // This is a meta-calc, logic is in process()
150
- reset() {}
95
+ reset() {
96
+ // No state
97
+ }
151
98
  }
152
99
 
153
100
  module.exports = SocialTopicDriverIndex;
@@ -1,54 +1,106 @@
1
1
  /**
2
- * @fileoverview Pass 2: Calculates the "Expectancy Score" for each user
3
- * based on their historical trading performance.
4
- * Expectancy = (WinRatio * AvgProfitPct) + (LossRatio * AvgLossPct)
2
+ * @fileoverview Calculation (Pass 3) for user expectancy score.
3
+ *
4
+ * This metric calculates a "trader expectancy score" for each user.
5
+ * Expectancy = (Win % * Avg Win Size) - (Loss % * Avg Loss Size)
6
+ *
7
+ * It *depends* on 'user_profitability_tracker' to get the
8
+ * historical win/loss data for each user.
5
9
  */
6
10
  class UserExpectancyScore {
11
+ constructor() {
12
+ // No per-user processing
13
+ }
14
+
15
+ /**
16
+ * Defines the output schema for this calculation.
17
+ * @returns {object} JSON Schema object
18
+ */
19
+ static getSchema() {
20
+ const userSchema = {
21
+ "type": "object",
22
+ "properties": {
23
+ "expectancy_score": {
24
+ "type": "number",
25
+ "description": "Trader expectancy score: (Win % * Avg Win) - (Loss % * Avg Loss). Measures P&L per trade."
26
+ },
27
+ "win_rate_pct": { "type": "number" },
28
+ "avg_win_pct": { "type": "number" },
29
+ "loss_rate_pct": { "type": "number" },
30
+ "avg_loss_pct": { "type": "number" },
31
+ "total_days_processed": { "type": "number" }
32
+ },
33
+ "required": ["expectancy_score", "win_rate_pct", "avg_win_pct", "loss_rate_pct", "avg_loss_pct"]
34
+ };
35
+
36
+ return {
37
+ "type": "object",
38
+ "description": "Calculates a 'trader expectancy score' for each user based on their 7-day P&L history.",
39
+ "patternProperties": {
40
+ "^.*$": userSchema // UserID
41
+ },
42
+ "additionalProperties": userSchema
43
+ };
44
+ }
7
45
 
46
+ /**
47
+ * Statically declare dependencies.
48
+ */
8
49
  static getDependencies() {
9
- // Depends on our new Pass 1 calculation
10
- return ['historical-performance-aggregator'];
50
+ return [
51
+ 'user_profitability_tracker' // Pass 2
52
+ ];
11
53
  }
12
54
 
13
- constructor() {}
55
+ process() {
56
+ // No-op
57
+ }
14
58
 
15
- async process(dateStr, dependencies, config, fetchedDependencies) {
16
- const { logger } = dependencies;
17
- const performanceData = fetchedDependencies['historical-performance-aggregator'];
59
+ getResult(fetchedDependencies) {
60
+ const profitabilityData = fetchedDependencies['user_profitability_tracker']?.user_details;
18
61
 
19
- if (!performanceData) {
20
- logger.log('WARN', `[UserExpectancyScore] Missing dependency 'historical-performance-aggregator' for ${dateStr}. Skipping.`);
21
- return null;
62
+ if (!profitabilityData) {
63
+ return {};
22
64
  }
23
65
 
24
- const expectancyScores = {};
25
-
26
- for (const userId in performanceData) {
27
- const stats = performanceData[userId];
66
+ const result = {};
28
67
 
29
- // Ensure we have all the data needed
30
- if (typeof stats.winRatio !== 'number' || typeof stats.avgProfitPct !== 'number' || typeof stats.avgLossPct !== 'number') {
31
- continue;
32
- }
68
+ for (const [userId, data] of Object.entries(profitabilityData)) {
69
+ const history = data.pnl_history_7d || [];
70
+ if (history.length === 0) continue;
71
+
72
+ const wins = history.filter(pnl => pnl > 0);
73
+ const losses = history.filter(pnl => pnl < 0);
74
+ const totalTrades = history.length;
33
75
 
34
- const winRatio = stats.winRatio / 100.0;
35
- const lossRatio = 1.0 - winRatio;
76
+ if (totalTrades === 0) continue;
36
77
 
37
- // Expectancy formula
38
- const expectancy = (winRatio * stats.avgProfitPct) + (lossRatio * stats.avgLossPct);
78
+ const winRate = (wins.length / totalTrades);
79
+ const lossRate = (losses.length / totalTrades);
39
80
 
40
- expectancyScores[userId] = {
81
+ const avgWin = (wins.length > 0) ? (wins.reduce((a, b) => a + b, 0) / wins.length) : 0;
82
+ // AvgLoss should be a positive number
83
+ const avgLoss = (losses.length > 0) ? (Math.abs(losses.reduce((a, b) => a + b, 0)) / losses.length) : 0;
84
+
85
+ // Expectancy = (Win % * Avg Win) - (Loss % * Avg Loss)
86
+ const expectancy = (winRate * avgWin) - (lossRate * avgLoss);
87
+
88
+ result[userId] = {
41
89
  expectancy_score: expectancy,
42
- winRatio: stats.winRatio, // Pass through for debugging/filtering
43
- totalTrades: stats.totalTrades
90
+ win_rate_pct: winRate * 100,
91
+ avg_win_pct: avgWin,
92
+ loss_rate_pct: lossRate * 100,
93
+ avg_loss_pct: avgLoss,
94
+ total_days_processed: totalTrades
44
95
  };
45
96
  }
46
-
47
- return expectancyScores;
97
+
98
+ return result;
48
99
  }
49
100
 
50
- async getResult() { return null; }
51
- reset() {}
101
+ reset() {
102
+ // No state
103
+ }
52
104
  }
53
105
 
54
106
  module.exports = UserExpectancyScore;