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.
- 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 -170
- package/calculations/behavioural/historical/drawdown_response.js +58 -58
- package/calculations/behavioural/historical/dumb-cohort-flow.js +249 -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 +250 -250
- package/calculations/behavioural/historical/smart_money_flow.js +165 -165
- package/calculations/behavioural/historical/user-investment-profile.js +412 -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,138 +1,130 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 3) that analyzes "what" the crowd does
|
|
3
|
-
* following a net deposit signal. It determines if the new capital is used to
|
|
4
|
-
* buy *new* assets or *add* to existing ones.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Must exist for the meta-computation runner
|
|
134
|
-
async getResult() { return null; }
|
|
135
|
-
reset() {}
|
|
136
|
-
}
|
|
137
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 3) that analyzes "what" the crowd does
|
|
3
|
+
* following a net deposit signal. It determines if the new capital is used to
|
|
4
|
+
* buy *new* assets or *add* to existing ones.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Note: This calculation still needs to read *historical* data for the signal.
|
|
8
|
+
// Only same-day dependencies can be passed in-memory.
|
|
9
|
+
// A full refactor would involve the orchestrator passing historical results
|
|
10
|
+
// into the cache, but for now we leave the historical lookback.
|
|
11
|
+
|
|
12
|
+
const { FieldValue } = require('@google-cloud/firestore');
|
|
13
|
+
|
|
14
|
+
class CapitalDeploymentStrategy {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.lookbackDays = 7;
|
|
17
|
+
this.correlationWindow = 3; // How many days after a signal to link behavior
|
|
18
|
+
this.depositSignalThreshold = -0.005; // Formerly -1.0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_getDateStr(baseDate, daysAgo) {
|
|
22
|
+
const date = new Date(baseDate + 'T00:00:00Z');
|
|
23
|
+
date.setUTCDate(date.getUTCDate() - daysAgo);
|
|
24
|
+
return date.toISOString().slice(0, 10);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
29
|
+
* @param {object} dependencies The shared dependencies (db, logger).
|
|
30
|
+
* @param {object} config The computation system configuration.
|
|
31
|
+
* @param {object} computedDependencies In-memory results from previous passes.
|
|
32
|
+
* @returns {Promise<object|null>} The analysis result or null.
|
|
33
|
+
*/
|
|
34
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
35
|
+
const { db, logger } = dependencies;
|
|
36
|
+
const collection = config.resultsCollection;
|
|
37
|
+
const resultsSub = config.resultsSubcollection || 'results';
|
|
38
|
+
const compsSub = config.computationsSubcollection || 'computations';
|
|
39
|
+
|
|
40
|
+
// 1. Find the most recent deposit signal (Still requires historical lookback)
|
|
41
|
+
let depositSignal = null;
|
|
42
|
+
let depositSignalDay = null;
|
|
43
|
+
let refsToGet = [];
|
|
44
|
+
|
|
45
|
+
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
46
|
+
const checkDate = this._getDateStr(dateStr, i);
|
|
47
|
+
refsToGet.push({
|
|
48
|
+
date: checkDate,
|
|
49
|
+
key: `signal_${checkDate}`,
|
|
50
|
+
ref: db.collection(collection).doc(checkDate).collection(resultsSub).doc('capital_flow').collection(compsSub).doc('crowd-cash-flow-proxy')
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const signalSnapshots = await db.getAll(...refsToGet.map(r => r.ref));
|
|
55
|
+
const dataMap = new Map();
|
|
56
|
+
signalSnapshots.forEach((snap, idx) => {
|
|
57
|
+
if (snap.exists) dataMap.set(refsToGet[idx].key, snap.data());
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
for (let i = 1; i <= this.lookbackDays; i++) {
|
|
61
|
+
const checkDate = this._getDateStr(dateStr, i);
|
|
62
|
+
const flowData = dataMap.get(`signal_${checkDate}`);
|
|
63
|
+
if (flowData && flowData.cash_flow_effect_proxy < this.depositSignalThreshold) {
|
|
64
|
+
depositSignal = flowData;
|
|
65
|
+
depositSignalDay = checkDate;
|
|
66
|
+
break; // Found the most recent signal
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!depositSignal) {
|
|
71
|
+
return {
|
|
72
|
+
status: 'no_deposit_signal_found',
|
|
73
|
+
lookback_days: this.lookbackDays
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. Check if today is within the correlation window
|
|
78
|
+
const daysSinceSignal = (new Date(dateStr) - new Date(depositSignalDay)) / (1000 * 60 * 60 * 24);
|
|
79
|
+
|
|
80
|
+
if (daysSinceSignal <= 0 || daysSinceSignal > this.correlationWindow) {
|
|
81
|
+
return {
|
|
82
|
+
status: 'outside_correlation_window',
|
|
83
|
+
signal_day: depositSignalDay,
|
|
84
|
+
days_since_signal: daysSinceSignal
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Fetch deployment data for *today* FROM IN-MEMORY CACHE
|
|
89
|
+
const newAllocData = computedDependencies['new-allocation-percentage'];
|
|
90
|
+
const reAllocData = computedDependencies['reallocation-increase-percentage'];
|
|
91
|
+
|
|
92
|
+
// 4. Handle missing dependencies
|
|
93
|
+
if (!newAllocData || !reAllocData) {
|
|
94
|
+
logger.log('WARN', `[CapitalDeploymentStrategy] Missing in-memory deployment data for ${dateStr}. Skipping.`);
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 5. Calculate deployment bias
|
|
99
|
+
const newAlloc = newAllocData.average_new_allocation_percentage || 0;
|
|
100
|
+
const reAlloc = reAllocData.average_reallocation_increase_percentage || 0;
|
|
101
|
+
|
|
102
|
+
const totalDeployment = newAlloc + reAlloc;
|
|
103
|
+
let newAssetBias = 0;
|
|
104
|
+
let existingAssetBias = 0;
|
|
105
|
+
|
|
106
|
+
if (totalDeployment > 0) {
|
|
107
|
+
newAssetBias = (newAlloc / totalDeployment) * 100;
|
|
108
|
+
existingAssetBias = (reAlloc / totalDeployment) * 100;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
status: 'analysis_complete',
|
|
113
|
+
analysis_date: dateStr,
|
|
114
|
+
signal_date: depositSignalDay,
|
|
115
|
+
days_since_signal: daysSinceSignal,
|
|
116
|
+
signal_deposit_proxy_pct: Math.abs(depositSignal.cash_flow_effect_proxy),
|
|
117
|
+
deployment_new_alloc_pct: newAlloc,
|
|
118
|
+
deployment_existing_alloc_pct: reAlloc,
|
|
119
|
+
total_deployment_pct: totalDeployment,
|
|
120
|
+
new_asset_bias: newAssetBias,
|
|
121
|
+
existing_asset_bias: existingAssetBias
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Must exist for the meta-computation runner
|
|
126
|
+
async getResult() { return null; }
|
|
127
|
+
reset() {}
|
|
128
|
+
}
|
|
129
|
+
|
|
138
130
|
module.exports = CapitalDeploymentStrategy;
|
|
@@ -1,164 +1,122 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 3) that tracks the performance
|
|
3
|
-
* of assets that were heavily liquidated (sold) by the crowd to fund
|
|
4
|
-
* a withdrawal event.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
// 3. Define performance windows
|
|
125
|
-
// "Before" window: 7 days leading up to the signal
|
|
126
|
-
const preSignalStart = this._getDateStr(signalDate, -this.PERFORMANCE_WINDOW_DAYS);
|
|
127
|
-
const preSignalEnd = signalDate;
|
|
128
|
-
|
|
129
|
-
// "After" window: 7 days starting from the liquidation
|
|
130
|
-
const postLiquidationStart = liquidationDate;
|
|
131
|
-
const postLiquidationEnd = this._getDateStr(liquidationDate, this.PERFORMANCE_WINDOW_DAYS);
|
|
132
|
-
|
|
133
|
-
// 4. Calculate performance
|
|
134
|
-
const preSignalReturnPct = this._calculateBasketPerformance(
|
|
135
|
-
topAssets, preSignalStart, preSignalEnd
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
const postLiquidationReturnPct = this._calculateBasketPerformance(
|
|
139
|
-
topAssets, postLiquidationStart, postLiquidationEnd
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
// This answers the question:
|
|
143
|
-
// A positive value means the assets RECOVERED (crowd panic-sold at the bottom).
|
|
144
|
-
// A negative value means the assets CONTINUED TO FALL (crowd made a good exit).
|
|
145
|
-
const crowdTimingError = postLiquidationReturnPct;
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
status: 'analysis_complete',
|
|
149
|
-
signal_date: signalDate,
|
|
150
|
-
liquidation_date: liquidationDate,
|
|
151
|
-
performance_window_days: this.PERFORMANCE_WINDOW_DAYS,
|
|
152
|
-
liquidated_assets: topAssets.map(a => a.ticker),
|
|
153
|
-
pre_signal_return_pct: preSignalReturnPct,
|
|
154
|
-
post_liquidation_return_pct: postLiquidationReturnPct,
|
|
155
|
-
crowd_timing_error: crowdTimingError,
|
|
156
|
-
interpretation: "Measures the 7-day return of liquidated assets *after* being sold. Positive = asset recovered (bad timing). Negative = asset kept falling (good timing)."
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async getResult() { return null; }
|
|
161
|
-
reset() {}
|
|
162
|
-
}
|
|
163
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Meta-calculation (Pass 3) that tracks the performance
|
|
3
|
+
* of assets that were heavily liquidated (sold) by the crowd to fund
|
|
4
|
+
* a withdrawal event.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class CapitalLiquidationPerformance {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.PERFORMANCE_WINDOW_DAYS = 7;
|
|
10
|
+
this.dependenciesLoaded = false;
|
|
11
|
+
this.priceMap = null;
|
|
12
|
+
this.tickerToIdMap = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async _loadDependencies(calculationUtils) {
|
|
16
|
+
if (this.dependenciesLoaded) return;
|
|
17
|
+
const { loadAllPriceData, loadInstrumentMappings } = calculationUtils;
|
|
18
|
+
const [priceData, mappings] = await Promise.all([
|
|
19
|
+
loadAllPriceData(),
|
|
20
|
+
loadInstrumentMappings()
|
|
21
|
+
]);
|
|
22
|
+
this.priceMap = priceData;
|
|
23
|
+
this.tickerToIdMap = {};
|
|
24
|
+
if (mappings && mappings.instrumentToTicker) {
|
|
25
|
+
for (const [id, ticker] of Object.entries(mappings.instrumentToTicker)) {
|
|
26
|
+
this.tickerToIdMap[ticker] = id;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
this.dependenciesLoaded = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_getDateStr(baseDateStr, daysOffset) {
|
|
33
|
+
const date = new Date(baseDateStr + 'T00:00:00Z');
|
|
34
|
+
date.setUTCDate(date.getUTCDate() + daysOffset);
|
|
35
|
+
return date.toISOString().slice(0, 10);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_calculateBasketPerformance(assets, startDateStr, endDateStr) {
|
|
39
|
+
if (!assets || assets.length === 0) return 0;
|
|
40
|
+
let totalReturn = 0;
|
|
41
|
+
let validAssets = 0;
|
|
42
|
+
for (const asset of assets) {
|
|
43
|
+
const ticker = asset.ticker;
|
|
44
|
+
const instrumentId = this.tickerToIdMap[ticker];
|
|
45
|
+
if (!instrumentId || !this.priceMap[instrumentId]) continue;
|
|
46
|
+
const startPrice = this.priceMap[instrumentId][startDateStr];
|
|
47
|
+
const endPrice = this.priceMap[instrumentId][endDateStr];
|
|
48
|
+
if (startPrice && endPrice && startPrice > 0) {
|
|
49
|
+
const assetReturn = (endPrice - startPrice) / startPrice;
|
|
50
|
+
totalReturn += assetReturn;
|
|
51
|
+
validAssets++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (validAssets === 0) return 0;
|
|
55
|
+
return (totalReturn / validAssets) * 100;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
60
|
+
* @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
|
|
61
|
+
* @param {object} config The computation system configuration.
|
|
62
|
+
* @param {object} computedDependencies In-memory results from previous passes.
|
|
63
|
+
* @returns {Promise<object|null>} The analysis result or null.
|
|
64
|
+
*/
|
|
65
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
66
|
+
const { db, logger, calculationUtils } = dependencies;
|
|
67
|
+
|
|
68
|
+
// 1. Load all price/mapping data
|
|
69
|
+
await this._loadDependencies(calculationUtils);
|
|
70
|
+
|
|
71
|
+
// 2. Get dependency from in-memory cache
|
|
72
|
+
const data = computedDependencies['cash-flow-liquidation'];
|
|
73
|
+
|
|
74
|
+
if (!data || data.status !== 'analysis_complete') {
|
|
75
|
+
logger.log('WARN', `[CapitalLiquidation] Skipping ${dateStr}, no valid 'cash-flow-liquidation' data found.`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const topAssets = data.top_liquidation_assets; // [{ ticker: 'MSFT', ... }]
|
|
80
|
+
const signalDate = data.signal_date;
|
|
81
|
+
const liquidationDate = data.analysis_date;
|
|
82
|
+
|
|
83
|
+
if (!topAssets || topAssets.length === 0) {
|
|
84
|
+
logger.log('INFO', `[CapitalLiquidation] No top assets liquidated on ${dateStr}.`);
|
|
85
|
+
return { status: 'no_liquidation' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Define performance windows
|
|
89
|
+
const preSignalStart = this._getDateStr(signalDate, -this.PERFORMANCE_WINDOW_DAYS);
|
|
90
|
+
const preSignalEnd = signalDate;
|
|
91
|
+
const postLiquidationStart = liquidationDate;
|
|
92
|
+
const postLiquidationEnd = this._getDateStr(liquidationDate, this.PERFORMANCE_WINDOW_DAYS);
|
|
93
|
+
|
|
94
|
+
// 4. Calculate performance
|
|
95
|
+
const preSignalReturnPct = this._calculateBasketPerformance(
|
|
96
|
+
topAssets, preSignalStart, preSignalEnd
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const postLiquidationReturnPct = this._calculateBasketPerformance(
|
|
100
|
+
topAssets, postLiquidationStart, postLiquidationEnd
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const crowdTimingError = postLiquidationReturnPct;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
status: 'analysis_complete',
|
|
107
|
+
signal_date: signalDate,
|
|
108
|
+
liquidation_date: liquidationDate,
|
|
109
|
+
performance_window_days: this.PERFORMANCE_WINDOW_DAYS,
|
|
110
|
+
liquidated_assets: topAssets.map(a => a.ticker),
|
|
111
|
+
pre_signal_return_pct: preSignalReturnPct,
|
|
112
|
+
post_liquidation_return_pct: postLiquidationReturnPct,
|
|
113
|
+
crowd_timing_error: crowdTimingError,
|
|
114
|
+
interpretation: "Measures the 7-day return of liquidated assets *after* being sold. Positive = asset recovered (bad timing). Negative = asset kept falling (good timing)."
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async getResult() { return null; }
|
|
119
|
+
reset() {}
|
|
120
|
+
}
|
|
121
|
+
|
|
164
122
|
module.exports = CapitalLiquidationPerformance;
|