aiden-shared-calculations-unified 1.0.25 → 1.0.27

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.
@@ -95,21 +95,22 @@ class DailyUserActivityTracker {
95
95
 
96
96
  // 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
97
97
  // This checks for changes in the 'Invested' percentage
98
+ // 3. Check for reallocation (only possible if we have AggregatedPositions for both days)
98
99
  if (yHasAgg && tHasAgg) {
99
100
  for (const tId of tIds) {
100
- // We know tId is also in yIds from the checks above
101
101
  const tInvested = tPosMap.get(tId).invested;
102
- const yInvested = yPosMap.get(yId).invested;
102
+ const yInvested = yPosMap.get(tId)?.invested ?? 0;
103
103
 
104
104
  // Check for a meaningful change (e.g., > 0.01% to avoid float noise)
105
105
  if (Math.abs(tInvested - yInvested) > 0.0001) {
106
106
  isActive = true;
107
107
  this.activityEvents.reallocation++;
108
- break; // Found activity
108
+ break;
109
109
  }
110
110
  }
111
111
  }
112
112
 
113
+
113
114
  if (isActive) {
114
115
  this.activeUserIds.add(userId);
115
116
  }
@@ -0,0 +1,164 @@
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
+ * This answers: "After the crowd sold an asset to withdraw cash,
7
+ * did that asset recover (implying a panic-sell) or
8
+ * continue to fall (implying a smart exit)?"
9
+ */
10
+
11
+ class CapitalLiquidationPerformance {
12
+ constructor() {
13
+ // How many days to look back/forward to measure performance
14
+ this.PERFORMANCE_WINDOW_DAYS = 7;
15
+ this.dependenciesLoaded = false;
16
+ this.priceMap = null;
17
+ this.tickerToIdMap = null;
18
+ }
19
+
20
+ /**
21
+ * Helper to load all dependencies in parallel
22
+ */
23
+ async _loadDependencies(calculationUtils) {
24
+ if (this.dependenciesLoaded) return;
25
+
26
+ const { loadAllPriceData, loadInstrumentMappings } = calculationUtils;
27
+
28
+ const [priceData, mappings] = await Promise.all([
29
+ loadAllPriceData(),
30
+ loadInstrumentMappings()
31
+ ]);
32
+
33
+ this.priceMap = priceData;
34
+
35
+ // Create a reverse map for easy lookup
36
+ this.tickerToIdMap = {};
37
+ if (mappings && mappings.instrumentToTicker) {
38
+ for (const [id, ticker] of Object.entries(mappings.instrumentToTicker)) {
39
+ this.tickerToIdMap[ticker] = id;
40
+ }
41
+ }
42
+
43
+ this.dependenciesLoaded = true;
44
+ }
45
+
46
+ /**
47
+ * Helper to get a date string X days from a base date
48
+ */
49
+ _getDateStr(baseDateStr, daysOffset) {
50
+ const date = new Date(baseDateStr + 'T00:00:00Z');
51
+ date.setUTCDate(date.getUTCDate() + daysOffset);
52
+ return date.toISOString().slice(0, 10);
53
+ }
54
+
55
+ /**
56
+ * Helper to calculate the average return of a basket of assets
57
+ * over a specified period.
58
+ */
59
+ _calculateBasketPerformance(assets, startDateStr, endDateStr) {
60
+ if (!assets || assets.length === 0) {
61
+ return 0;
62
+ }
63
+
64
+ let totalReturn = 0;
65
+ let validAssets = 0;
66
+
67
+ for (const asset of assets) {
68
+ const ticker = asset.ticker;
69
+ const instrumentId = this.tickerToIdMap[ticker];
70
+
71
+ if (!instrumentId || !this.priceMap[instrumentId]) {
72
+ continue; // No price data for this ticker
73
+ }
74
+
75
+ const startPrice = this.priceMap[instrumentId][startDateStr];
76
+ const endPrice = this.priceMap[instrumentId][endDateStr];
77
+
78
+ if (startPrice && endPrice && startPrice > 0) {
79
+ const assetReturn = (endPrice - startPrice) / startPrice;
80
+ totalReturn += assetReturn;
81
+ validAssets++;
82
+ }
83
+ }
84
+
85
+ if (validAssets === 0) return 0;
86
+ return (totalReturn / validAssets) * 100; // Return as percentage
87
+ }
88
+
89
+ /**
90
+ * @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
91
+ * @param {object} dependencies The shared dependencies (db, logger, calculationUtils).
92
+ * @param {object} config The computation system configuration.
93
+ * @returns {Promise<object|null>} The analysis result or null.
94
+ */
95
+ async process(dateStr, dependencies, config) {
96
+ const { db, logger, calculationUtils } = dependencies;
97
+
98
+ // 1. Load all price/mapping data
99
+ await this._loadDependencies(calculationUtils);
100
+
101
+ // 2. Define and fetch dependency: cash-flow-liquidation
102
+ const depRef = db.collection(config.resultsCollection).doc(dateStr)
103
+ .collection('results').doc('meta')
104
+ .collection('computations').doc('cash-flow-liquidation'); // <-- This is the dependency
105
+
106
+ const snapshot = await depRef.get();
107
+
108
+ if (!snapshot.exists || snapshot.data().status !== 'analysis_complete') {
109
+ logger.log('WARN', `[CapitalLiquidation] Skipping ${dateStr}, no valid 'cash-flow-liquidation' data found.`);
110
+ return null;
111
+ }
112
+
113
+ const data = snapshot.data();
114
+ // This is the key change: we get the list of *sold* assets
115
+ const topAssets = data.top_liquidation_assets; // [{ ticker: 'MSFT', ... }]
116
+ const signalDate = data.signal_date; // The day the withdrawal signal occurred
117
+ const liquidationDate = data.analysis_date; // The day the capital was sold (dateStr)
118
+
119
+ if (!topAssets || topAssets.length === 0) {
120
+ logger.log('INFO', `[CapitalLiquidation] No top assets liquidated on ${dateStr}.`);
121
+ return { status: 'no_liquidation' };
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
+
164
+ module.exports = CapitalLiquidationPerformance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [