aiden-shared-calculations-unified 1.0.35 → 1.0.37
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 +77 -77
- package/calculations/activity/historical/activity_by_pnl_status.js +85 -85
- package/calculations/activity/historical/daily_asset_activity.js +85 -85
- package/calculations/activity/historical/daily_user_activity_tracker.js +144 -144
- package/calculations/activity/historical/speculator_adjustment_activity.js +76 -76
- package/calculations/asset_metrics/asset_position_size.js +57 -57
- package/calculations/backtests/strategy-performance.js +229 -245
- package/calculations/behavioural/historical/asset_crowd_flow.js +165 -165
- package/calculations/behavioural/historical/drawdown_response.js +58 -58
- package/calculations/behavioural/historical/dumb-cohort-flow.js +217 -249
- package/calculations/behavioural/historical/gain_response.js +57 -57
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +98 -98
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +99 -99
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +39 -39
- package/calculations/behavioural/historical/position_count_pnl.js +67 -67
- package/calculations/behavioural/historical/smart-cohort-flow.js +217 -250
- package/calculations/behavioural/historical/smart_money_flow.js +165 -165
- package/calculations/behavioural/historical/user-investment-profile.js +358 -412
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +121 -121
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +117 -117
- package/calculations/capital_flow/historical/new_allocation_percentage.js +49 -49
- package/calculations/insights/daily_bought_vs_sold_count.js +55 -55
- package/calculations/insights/daily_buy_sell_sentiment_count.js +49 -49
- package/calculations/insights/daily_ownership_delta.js +55 -55
- package/calculations/insights/daily_total_positions_held.js +39 -39
- package/calculations/meta/capital_deployment_strategy.js +129 -137
- package/calculations/meta/capital_liquidation_performance.js +121 -163
- package/calculations/meta/capital_vintage_performance.js +121 -158
- package/calculations/meta/cash-flow-deployment.js +110 -124
- package/calculations/meta/cash-flow-liquidation.js +126 -142
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +83 -91
- package/calculations/meta/profit_cohort_divergence.js +77 -91
- package/calculations/meta/smart-dumb-divergence-index.js +116 -138
- package/calculations/meta/social_flow_correlation.js +99 -125
- package/calculations/pnl/asset_pnl_status.js +46 -46
- package/calculations/pnl/historical/profitability_migration.js +57 -57
- package/calculations/pnl/historical/user_profitability_tracker.js +117 -117
- package/calculations/pnl/profitable_and_unprofitable_status.js +64 -64
- package/calculations/sectors/historical/diversification_pnl.js +76 -76
- package/calculations/sectors/historical/sector_rotation.js +67 -67
- package/calculations/sentiment/historical/crowd_conviction_score.js +80 -80
- package/calculations/socialPosts/social-asset-posts-trend.js +52 -52
- package/calculations/socialPosts/social-top-mentioned-words.js +102 -102
- package/calculations/socialPosts/social-topic-interest-evolution.js +53 -53
- package/calculations/socialPosts/social-word-mentions-trend.js +62 -62
- package/calculations/socialPosts/social_activity_aggregation.js +103 -103
- package/calculations/socialPosts/social_event_correlation.js +121 -121
- package/calculations/socialPosts/social_sentiment_aggregation.js +114 -114
- package/calculations/speculators/historical/risk_appetite_change.js +54 -54
- package/calculations/speculators/historical/tsl_effectiveness.js +74 -74
- package/index.js +33 -33
- package/package.json +32 -32
- package/utils/firestore_utils.js +76 -76
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +0 -63
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +0 -91
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +0 -73
|
@@ -1,143 +1,127 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Correlates a crowd-wide withdrawal signal with the specific assets
|
|
3
|
-
* that are being sold (liquidated) to fund those withdrawals.
|
|
4
|
-
* This is a meta-calculation that runs in Pass 3.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { FieldValue } = require('@google-cloud/firestore');
|
|
8
|
-
|
|
9
|
-
class CashFlowLiquidation {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.lookbackDays = 7;
|
|
12
|
-
this.correlationWindow = 3;
|
|
13
|
-
// A positive value signals a net crowd withdrawal
|
|
14
|
-
this.withdrawalSignalThreshold = 0.005; // Formerly 1.0
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
_getDateStr(baseDate, daysAgo) {
|
|
18
|
-
const date = new Date(baseDate + 'T00:00:00Z');
|
|
19
|
-
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
20
|
-
return date.toISOString().slice(0, 10);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
25
|
-
* @param {object} dependencies The shared dependencies (db, logger).
|
|
26
|
-
* @param {object} config The computation system configuration.
|
|
27
|
-
* @
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
dates
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
status: 'analysis_complete',
|
|
128
|
-
analysis_date: dateStr,
|
|
129
|
-
signal_date: withdrawalSignalDay,
|
|
130
|
-
days_since_signal: daysSinceSignal,
|
|
131
|
-
signal_withdrawal_proxy_pct: netWithdrawalPct,
|
|
132
|
-
day_net_sell_pct: netSellPct, // This value should be negative
|
|
133
|
-
pct_of_withdrawal_funded_today: (Math.abs(netSellPct) / netWithdrawalPct) * 100,
|
|
134
|
-
top_liquidation_assets: topLiquidations
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Must exist for the meta-computation runner
|
|
139
|
-
async getResult() { return null; }
|
|
140
|
-
reset() {}
|
|
141
|
-
}
|
|
142
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Correlates a crowd-wide withdrawal signal with the specific assets
|
|
3
|
+
* that are being sold (liquidated) to fund those withdrawals.
|
|
4
|
+
* This is a meta-calculation that runs in Pass 3.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
8
|
+
|
|
9
|
+
class CashFlowLiquidation {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.lookbackDays = 7;
|
|
12
|
+
this.correlationWindow = 3;
|
|
13
|
+
// A positive value signals a net crowd withdrawal
|
|
14
|
+
this.withdrawalSignalThreshold = 0.005; // Formerly 1.0
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_getDateStr(baseDate, daysAgo) {
|
|
18
|
+
const date = new Date(baseDate + 'T00:00:00Z');
|
|
19
|
+
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
20
|
+
return date.toISOString().slice(0, 10);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
25
|
+
* @param {object} dependencies The shared dependencies (db, logger).
|
|
26
|
+
* @param {object} config The computation system configuration.
|
|
27
|
+
* @param {object} computedDependencies In-memory results from previous passes.
|
|
28
|
+
* @returns {Promise<object|null>} The analysis result or null.
|
|
29
|
+
*/
|
|
30
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
31
|
+
const { db, logger } = dependencies;
|
|
32
|
+
const collection = config.resultsCollection;
|
|
33
|
+
|
|
34
|
+
// --- MODIFICATION: Get same-day dependencies from cache ---
|
|
35
|
+
const cashFlowData = computedDependencies['crowd-cash-flow-proxy'];
|
|
36
|
+
const assetFlowData = computedDependencies['asset-crowd-flow'];
|
|
37
|
+
|
|
38
|
+
if (!cashFlowData || !assetFlowData) {
|
|
39
|
+
logger.log('WARN', `[CashFlowLiquidation] Missing critical in-memory dependency data for ${dateStr}. Skipping.`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
// --- END MODIFICATION ---
|
|
43
|
+
|
|
44
|
+
// --- Historical lookback for signal still uses Firestore ---
|
|
45
|
+
const dates = [];
|
|
46
|
+
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
47
|
+
const checkDate = this._getDateStr(dateStr, i);
|
|
48
|
+
dates.push({ date: checkDate, category: 'capital_flow', computation: 'crowd-cash-flow-proxy' });
|
|
49
|
+
}
|
|
50
|
+
const refs = dates.map(d =>
|
|
51
|
+
db.collection(collection).doc(d.date)
|
|
52
|
+
.collection('results').doc(d.category)
|
|
53
|
+
.collection('computations').doc(d.computation)
|
|
54
|
+
);
|
|
55
|
+
const snapshots = await db.getAll(...refs);
|
|
56
|
+
const dataMap = new Map();
|
|
57
|
+
snapshots.forEach((snap, idx) => {
|
|
58
|
+
if (snap.exists) dataMap.set(idx, snap.data());
|
|
59
|
+
});
|
|
60
|
+
// --- End historical lookback ---
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// 2. Find the withdrawal signal
|
|
64
|
+
let withdrawalSignal = null;
|
|
65
|
+
let withdrawalSignalDay = null;
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < this.lookbackDays; i++) {
|
|
68
|
+
const flowData = dataMap.get(i);
|
|
69
|
+
const dateUsed = dates[i].date;
|
|
70
|
+
// INVERTED LOGIC: Look for a POSITIVE value
|
|
71
|
+
if (flowData && flowData.cash_flow_effect_proxy > this.withdrawalSignalThreshold) {
|
|
72
|
+
withdrawalSignal = flowData;
|
|
73
|
+
withdrawalSignalDay = dateUsed;
|
|
74
|
+
break; // Found the most recent signal
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!withdrawalSignal) {
|
|
79
|
+
return {
|
|
80
|
+
status: 'no_withdrawal_signal_found',
|
|
81
|
+
lookback_days: this.lookbackDays,
|
|
82
|
+
signal_threshold: this.withdrawalSignalThreshold
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const daysSinceSignal = (new Date(dateStr) - new Date(withdrawalSignalDay)) / (1000 * 60 * 60 * 24);
|
|
87
|
+
|
|
88
|
+
if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'outside_correlation_window',
|
|
91
|
+
signal_day: withdrawalSignalDay,
|
|
92
|
+
days_since_signal: daysSinceSignal
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 'trading_effect' will be negative if the crowd is net-selling
|
|
97
|
+
const netSellPct = cashFlowData.components?.trading_effect || 0;
|
|
98
|
+
const netWithdrawalPct = Math.abs(withdrawalSignal.cash_flow_effect_proxy);
|
|
99
|
+
|
|
100
|
+
// INVERTED LOGIC: Find top *sells*
|
|
101
|
+
const topLiquidations = Object.entries(assetFlowData)
|
|
102
|
+
.filter(([ticker, data]) => data.net_crowd_flow_pct < 0) // Find assets with negative flow
|
|
103
|
+
.sort(([, a], [, b]) => a.net_crowd_flow_pct - b.net_crowd_flow_pct) // Sort ascending (most negative first)
|
|
104
|
+
.slice(0, 10)
|
|
105
|
+
.map(([ticker, data]) => ({
|
|
106
|
+
ticker,
|
|
107
|
+
net_flow_pct: data.net_crowd_flow_pct
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
status: 'analysis_complete',
|
|
112
|
+
analysis_date: dateStr,
|
|
113
|
+
signal_date: withdrawalSignalDay,
|
|
114
|
+
days_since_signal: daysSinceSignal,
|
|
115
|
+
signal_withdrawal_proxy_pct: netWithdrawalPct,
|
|
116
|
+
day_net_sell_pct: netSellPct, // This value should be negative
|
|
117
|
+
pct_of_withdrawal_funded_today: (Math.abs(netSellPct) / netWithdrawalPct) * 100,
|
|
118
|
+
top_liquidation_assets: topLiquidations
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Must exist for the meta-computation runner
|
|
123
|
+
async getResult() { return null; }
|
|
124
|
+
reset() {}
|
|
125
|
+
}
|
|
126
|
+
|
|
143
127
|
module.exports = CashFlowLiquidation;
|
|
@@ -1,92 +1,84 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 3) to calculate a proxy for the
|
|
3
|
-
* crowd's "Sharpe Ratio" (risk-adjusted return) on a per-asset basis.
|
|
4
|
-
* It uses the components from 'pnl_distribution_per_stock' to calculate
|
|
5
|
-
* the standard deviation of P/L, which serves as the "risk".
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
class CrowdSharpeRatioProxy {
|
|
9
|
-
constructor() {}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
13
|
-
* @param {object} dependencies The shared dependencies (db, logger).
|
|
14
|
-
* @param {object} config The computation system configuration.
|
|
15
|
-
* @
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
// 1.
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
// 2.
|
|
25
|
-
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return results;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async getResult() { return null; }
|
|
89
|
-
reset() {}
|
|
90
|
-
}
|
|
91
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 3) to calculate a proxy for the
|
|
3
|
+
* crowd's "Sharpe Ratio" (risk-adjusted return) on a per-asset basis.
|
|
4
|
+
* It uses the components from 'pnl_distribution_per_stock' to calculate
|
|
5
|
+
* the standard deviation of P/L, which serves as the "risk".
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class CrowdSharpeRatioProxy {
|
|
9
|
+
constructor() {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
13
|
+
* @param {object} dependencies The shared dependencies (db, logger).
|
|
14
|
+
* @param {object} config The computation system configuration.
|
|
15
|
+
* @param {object} computedDependencies In-memory results from previous passes.
|
|
16
|
+
* @returns {Promise<object|null>} The analysis result or null.
|
|
17
|
+
*/
|
|
18
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
19
|
+
const { logger } = dependencies;
|
|
20
|
+
|
|
21
|
+
// 1. Get dependency from in-memory cache
|
|
22
|
+
const data = computedDependencies['pnl-distribution-per-stock'];
|
|
23
|
+
|
|
24
|
+
// 2. Handle missing dependency
|
|
25
|
+
if (!data) {
|
|
26
|
+
logger.log('WARN', `[CrowdSharpeRatioProxy] Missing dependency 'pnl-distribution-per-stock' for ${dateStr}. Skipping.`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pnlDistribution = data.pnl_distribution_by_asset;
|
|
31
|
+
|
|
32
|
+
if (!pnlDistribution) {
|
|
33
|
+
logger.log('WARN', `[CrowdSharpeRatioProxy] Dependency data for ${dateStr} is empty. Skipping.`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const results = {};
|
|
38
|
+
|
|
39
|
+
// 3. Calculate Sharpe Proxy for each asset
|
|
40
|
+
for (const ticker in pnlDistribution) {
|
|
41
|
+
const stats = pnlDistribution[ticker];
|
|
42
|
+
const N = stats.position_count;
|
|
43
|
+
|
|
44
|
+
// Need at least 2 data points to calculate variance
|
|
45
|
+
if (N < 2) continue;
|
|
46
|
+
|
|
47
|
+
const mean = stats.pnl_sum / N; // E(x)
|
|
48
|
+
const mean_sq = stats.pnl_sum_sq / N; // E(x^2)
|
|
49
|
+
|
|
50
|
+
const variance = mean_sq - (mean * mean);
|
|
51
|
+
|
|
52
|
+
// If variance is negative (floating point error) or zero, we can't get std_dev
|
|
53
|
+
if (variance <= 0) {
|
|
54
|
+
results[ticker] = {
|
|
55
|
+
average_pnl: mean,
|
|
56
|
+
std_dev_pnl: 0,
|
|
57
|
+
sharpe_ratio_proxy: 0,
|
|
58
|
+
position_count: N
|
|
59
|
+
};
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const std_dev = Math.sqrt(variance); // "Risk"
|
|
64
|
+
|
|
65
|
+
// Calculate Sharpe Ratio (Return / Risk)
|
|
66
|
+
// (Assuming 0 risk-free rate)
|
|
67
|
+
const sharpe_proxy = mean / std_dev;
|
|
68
|
+
|
|
69
|
+
results[ticker] = {
|
|
70
|
+
average_pnl: mean,
|
|
71
|
+
std_dev_pnl: std_dev,
|
|
72
|
+
sharpe_ratio_proxy: sharpe_proxy,
|
|
73
|
+
position_count: N
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getResult() { return null; }
|
|
81
|
+
reset() {}
|
|
82
|
+
}
|
|
83
|
+
|
|
92
84
|
module.exports = CrowdSharpeRatioProxy;
|
|
@@ -1,92 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 3) that correlates the asset flow
|
|
3
|
-
* of the "In Profit" cohort vs. the "In Loss" cohort to find
|
|
4
|
-
* powerful divergence signals (e.g., profit-taking, capitulation).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
class ProfitCohortDivergence {
|
|
8
|
-
constructor() {
|
|
9
|
-
this.flowThreshold = 0.005; // Min abs flow % to be considered a signal (formerly 0.5)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
14
|
-
* @param {object} dependencies The shared dependencies (db, logger).
|
|
15
|
-
* @param {object} config The computation system configuration.
|
|
16
|
-
* @
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
status
|
|
65
|
-
detail
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
status: status,
|
|
79
|
-
detail: detail,
|
|
80
|
-
profit_cohort_flow: profitFlow,
|
|
81
|
-
loss_cohort_flow: lossFlow
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return results;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async getResult() { return null; }
|
|
89
|
-
reset() {}
|
|
90
|
-
}
|
|
91
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 3) that correlates the asset flow
|
|
3
|
+
* of the "In Profit" cohort vs. the "In Loss" cohort to find
|
|
4
|
+
* powerful divergence signals (e.g., profit-taking, capitulation).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class ProfitCohortDivergence {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.flowThreshold = 0.005; // Min abs flow % to be considered a signal (formerly 0.5)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
14
|
+
* @param {object} dependencies The shared dependencies (db, logger).
|
|
15
|
+
* @param {object} config The computation system configuration.
|
|
16
|
+
* @param {object} computedDependencies In-memory results from previous passes.
|
|
17
|
+
* @returns {Promise<object|null>} The analysis result or null.
|
|
18
|
+
*/
|
|
19
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
20
|
+
const { logger } = dependencies;
|
|
21
|
+
|
|
22
|
+
// 1. Get dependencies directly from the new argument
|
|
23
|
+
// Names are normalized to kebab-case by the orchestrator
|
|
24
|
+
const profitFlowData = computedDependencies['in-profit-asset-crowd-flow'];
|
|
25
|
+
const lossFlowData = computedDependencies['in-loss-asset-crowd-flow'];
|
|
26
|
+
|
|
27
|
+
// 2. Handle missing dependencies
|
|
28
|
+
if (!profitFlowData || !lossFlowData) {
|
|
29
|
+
logger.log('WARN', `[ProfitCohortDivergence] Missing computed dependency for ${dateStr}. Skipping.`);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const results = {};
|
|
34
|
+
const allTickers = new Set([...Object.keys(profitFlowData), ...Object.keys(lossFlowData)]);
|
|
35
|
+
|
|
36
|
+
// 4. Correlate
|
|
37
|
+
for (const ticker of allTickers) {
|
|
38
|
+
const profitFlow = profitFlowData[ticker]?.net_crowd_flow_pct || 0;
|
|
39
|
+
const lossFlow = lossFlowData[ticker]?.net_crowd_flow_pct || 0;
|
|
40
|
+
|
|
41
|
+
const profitSells = profitFlow <= -this.flowThreshold;
|
|
42
|
+
const profitBuys = profitFlow >= this.flowThreshold;
|
|
43
|
+
const lossSells = lossFlow <= -this.flowThreshold;
|
|
44
|
+
const lossBuys = lossFlow >= this.flowThreshold;
|
|
45
|
+
|
|
46
|
+
let status = 'No Divergence';
|
|
47
|
+
let detail = 'Both cohorts are acting similarly or flow is insignificant.';
|
|
48
|
+
|
|
49
|
+
if (profitSells && lossBuys) {
|
|
50
|
+
status = 'Profit Taking';
|
|
51
|
+
detail = 'The "in-profit" cohort is selling to the "in-loss" cohort, who are averaging down.';
|
|
52
|
+
} else if (profitBuys && lossSells) {
|
|
53
|
+
status = 'Capitulation';
|
|
54
|
+
detail = 'The "in-loss" cohort is panic-selling, and the "in-profit" cohort is buying the dip.';
|
|
55
|
+
} else if (profitBuys && lossBuys) {
|
|
56
|
+
status = 'High Conviction Buy';
|
|
57
|
+
detail = 'All cohorts are net-buying.';
|
|
58
|
+
} else if (profitSells && lossSells) {
|
|
59
|
+
status = 'High Conviction Sell';
|
|
60
|
+
detail = 'All cohorts are net-selling.';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
results[ticker] = {
|
|
64
|
+
status: status,
|
|
65
|
+
detail: detail,
|
|
66
|
+
profit_cohort_flow: profitFlow,
|
|
67
|
+
loss_cohort_flow: lossFlow
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getResult() { return null; }
|
|
75
|
+
reset() {}
|
|
76
|
+
}
|
|
77
|
+
|
|
92
78
|
module.exports = ProfitCohortDivergence;
|