aiden-shared-calculations-unified 1.0.64 → 1.0.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +1 -1
- package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
- package/calculations/activity/historical/daily_asset_activity.js +42 -0
- package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
- package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
- package/calculations/asset_metrics/asset_position_size.js +36 -0
- package/calculations/backtests/strategy-performance.js +41 -0
- package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
- package/calculations/behavioural/historical/drawdown_response.js +113 -35
- package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
- package/calculations/behavioural/historical/gain_response.js +113 -34
- package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
- package/calculations/behavioural/historical/position_count_pnl.js +91 -39
- package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
- package/calculations/behavioural/historical/smart_money_flow.js +160 -151
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
- package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
- package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
- package/calculations/insights/daily_total_positions_held.js +28 -24
- package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
- package/calculations/insights/historical/daily_ownership_delta.js +95 -32
- package/calculations/meta/capital_deployment_strategy.js +78 -110
- package/calculations/meta/capital_liquidation_performance.js +114 -111
- package/calculations/meta/cash-flow-deployment.js +114 -107
- package/calculations/meta/cash-flow-liquidation.js +114 -107
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
- package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
- package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
- package/calculations/meta/profit_cohort_divergence.js +83 -59
- package/calculations/meta/shark_attack_signal.js +91 -39
- package/calculations/meta/smart-dumb-divergence-index.js +114 -98
- package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
- package/calculations/meta/social-predictive-regime-state.js +76 -155
- package/calculations/meta/social-topic-driver-index.js +74 -127
- package/calculations/meta/user_expectancy_score.js +83 -31
- package/calculations/pnl/asset_pnl_status.js +120 -31
- package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
- package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
- package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
- package/calculations/pnl/average_daily_position_pnl.js +49 -21
- package/calculations/pnl/historical/profitability_migration.js +81 -35
- package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
- package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
- package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
- package/calculations/pnl/profitability_skew_per_stock.js +86 -31
- package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
- package/calculations/sanity/users_processed.js +24 -1
- package/calculations/sectors/historical/diversification_pnl.js +104 -42
- package/calculations/sectors/historical/sector_rotation.js +94 -45
- package/calculations/sectors/total_long_per_sector.js +55 -20
- package/calculations/sectors/total_short_per_sector.js +55 -20
- package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
- package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
- package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
- package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
- package/calculations/short_and_long_stats/total_long_figures.js +34 -13
- package/calculations/short_and_long_stats/total_short_figures.js +34 -14
- package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
- package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
- package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
- package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
- package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
- package/calculations/socialPosts/social_activity_aggregation.js +106 -77
- package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
- package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
- package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
- package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
- package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
- package/calculations/speculators/historical/risk_appetite_change.js +89 -32
- package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
- package/calculations/speculators/holding_duration_per_asset.js +83 -23
- package/calculations/speculators/leverage_per_asset.js +68 -19
- package/calculations/speculators/leverage_per_sector.js +86 -25
- package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
- package/calculations/speculators/speculator_asset_sentiment.js +100 -48
- package/calculations/speculators/speculator_danger_zone.js +101 -33
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
- package/calculations/speculators/stop_loss_per_asset.js +94 -26
- package/calculations/speculators/take_profit_per_asset.js +95 -27
- package/calculations/speculators/tsl_per_asset.js +77 -23
- package/package.json +1 -1
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
|
@@ -1,181 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* This is the "Quant Regime Engine" or "Master Switch."
|
|
2
|
+
* @fileoverview Calculation (Pass 5) for social predictive regime.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* This metric answers: "Are we in a 'social-driven' or 'flow-driven'
|
|
5
|
+
* market regime?"
|
|
7
6
|
*
|
|
8
|
-
* It
|
|
9
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Statically declare dependencies.
|
|
48
|
+
*/
|
|
55
49
|
static getDependencies() {
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
return [
|
|
51
|
+
'social_flow_correlation' // Pass 4
|
|
52
|
+
];
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
process() {
|
|
56
|
+
// No-op
|
|
57
|
+
}
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
const defaults = {
|
|
63
|
+
regime: "Unknown",
|
|
64
|
+
average_correlation: 0,
|
|
65
|
+
correlated_assets_count: 0,
|
|
66
|
+
decoupled_assets_count: 0
|
|
67
|
+
};
|
|
83
68
|
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
90
|
+
regime: regime,
|
|
91
|
+
average_correlation: avgCorrelation,
|
|
92
|
+
correlated_assets_count: correlatedCount,
|
|
93
|
+
decoupled_assets_count: decoupledCount
|
|
174
94
|
};
|
|
175
95
|
}
|
|
176
96
|
|
|
177
|
-
|
|
178
|
-
|
|
97
|
+
reset() {
|
|
98
|
+
// No state
|
|
99
|
+
}
|
|
179
100
|
}
|
|
180
101
|
|
|
181
102
|
module.exports = SocialPredictiveRegimeState;
|
|
@@ -1,153 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
25
|
-
this.tickerToIdMap = null; // For { "AAPL" -> 123 }
|
|
26
|
-
this.dependenciesLoaded = false;
|
|
11
|
+
// No per-user processing
|
|
27
12
|
}
|
|
28
13
|
|
|
29
14
|
/**
|
|
30
|
-
*
|
|
15
|
+
* Defines the output schema for this calculation.
|
|
16
|
+
* @returns {object} JSON Schema object
|
|
31
17
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
*
|
|
50
|
+
* Statically declare dependencies.
|
|
55
51
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
static getDependencies() {
|
|
53
|
+
return [
|
|
54
|
+
'social-topic-predictive-potential' // Pass 5
|
|
55
|
+
];
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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 (!
|
|
83
|
-
|
|
84
|
-
return null;
|
|
70
|
+
if (!potentialData) {
|
|
71
|
+
return defaults;
|
|
85
72
|
}
|
|
86
73
|
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
89
|
+
return {
|
|
90
|
+
top_driving_topics: allTopics.slice(0, 5),
|
|
91
|
+
all_topics: allTopics
|
|
92
|
+
};
|
|
147
93
|
}
|
|
148
94
|
|
|
149
|
-
|
|
150
|
-
|
|
95
|
+
reset() {
|
|
96
|
+
// No state
|
|
97
|
+
}
|
|
151
98
|
}
|
|
152
99
|
|
|
153
100
|
module.exports = SocialTopicDriverIndex;
|
|
@@ -1,54 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Pass
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
|
|
10
|
-
|
|
50
|
+
return [
|
|
51
|
+
'user_profitability_tracker' // Pass 2
|
|
52
|
+
];
|
|
11
53
|
}
|
|
12
54
|
|
|
13
|
-
|
|
55
|
+
process() {
|
|
56
|
+
// No-op
|
|
57
|
+
}
|
|
14
58
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const performanceData = fetchedDependencies['historical-performance-aggregator'];
|
|
59
|
+
getResult(fetchedDependencies) {
|
|
60
|
+
const profitabilityData = fetchedDependencies['user_profitability_tracker']?.user_details;
|
|
18
61
|
|
|
19
|
-
if (!
|
|
20
|
-
|
|
21
|
-
return null;
|
|
62
|
+
if (!profitabilityData) {
|
|
63
|
+
return {};
|
|
22
64
|
}
|
|
23
65
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
for (const userId in performanceData) {
|
|
27
|
-
const stats = performanceData[userId];
|
|
66
|
+
const result = {};
|
|
28
67
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
const lossRatio = 1.0 - winRatio;
|
|
76
|
+
if (totalTrades === 0) continue;
|
|
36
77
|
|
|
37
|
-
|
|
38
|
-
const
|
|
78
|
+
const winRate = (wins.length / totalTrades);
|
|
79
|
+
const lossRate = (losses.length / totalTrades);
|
|
39
80
|
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
97
|
+
|
|
98
|
+
return result;
|
|
48
99
|
}
|
|
49
100
|
|
|
50
|
-
|
|
51
|
-
|
|
101
|
+
reset() {
|
|
102
|
+
// No state
|
|
103
|
+
}
|
|
52
104
|
}
|
|
53
105
|
|
|
54
106
|
module.exports = UserExpectancyScore;
|