aiden-shared-calculations-unified 1.0.27 → 1.0.29

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.
@@ -81,64 +81,85 @@ class AssetCrowdFlow {
81
81
 
82
82
  async getResult() {
83
83
  if (this.user_count === 0 || !this.dates.today) {
84
- return {}; // No users processed or dates not found
84
+ console.warn('[AssetCrowdFlow] No users processed or dates missing.');
85
+ return {};
85
86
  }
86
87
 
87
- // Load dependencies (prices and mappings) in parallel
88
+ // Load priceMap and mappings if not loaded
88
89
  if (!this.priceMap || !this.mappings) {
89
- const [priceData, mappingData] = await Promise.all([
90
- loadAllPriceData(),
91
- loadInstrumentMappings()
92
- ]);
93
- this.priceMap = priceData;
94
- this.mappings = mappingData;
90
+ try {
91
+ const [priceData, mappingData] = await Promise.all([
92
+ loadAllPriceData(),
93
+ loadInstrumentMappings()
94
+ ]);
95
+ this.priceMap = priceData;
96
+ this.mappings = mappingData;
97
+ } catch (err) {
98
+ console.error('[AssetCrowdFlow] Failed to load dependencies:', err);
99
+ return {};
100
+ }
95
101
  }
96
-
102
+
97
103
  const finalResults = {};
98
104
  const todayStr = this.dates.today;
99
105
  const yesterdayStr = this.dates.yesterday;
100
106
 
101
- for (const instrumentId in this.asset_values) {
102
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
103
-
104
- // 1. Calculate average % values
105
- const avg_day1_value = this.asset_values[instrumentId].day1_value_sum / this.user_count;
106
- const avg_day2_value = this.asset_values[instrumentId].day2_value_sum / this.user_count;
107
-
108
- // 2. Get the actual price change
109
- const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, todayStr, this.priceMap);
107
+ for (const rawInstrumentId in this.asset_values) {
108
+ const instrumentId = String(rawInstrumentId); // normalize
109
+ const ticker = this.mappings.instrumentToTicker?.[instrumentId] || `id_${instrumentId}`;
110
+
111
+ const avg_day1_value = this.asset_values[rawInstrumentId].day1_value_sum / this.user_count;
112
+ const avg_day2_value = this.asset_values[rawInstrumentId].day2_value_sum / this.user_count;
113
+
114
+ let priceChangePct = null;
115
+
116
+ // Check priceMap presence
117
+ if (!this.priceMap || !this.priceMap[instrumentId]) {
118
+ console.debug(`[AssetCrowdFlow] Missing priceMap entry for instrumentId ${instrumentId} (${ticker})`);
119
+ } else {
120
+ const priceDay1 = this.priceMap[instrumentId][yesterdayStr];
121
+ const priceDay2 = this.priceMap[instrumentId][todayStr];
122
+
123
+ if (priceDay1 == null) console.debug(`[AssetCrowdFlow] Missing price for ${instrumentId} (${ticker}) on ${yesterdayStr}`);
124
+ if (priceDay2 == null) console.debug(`[AssetCrowdFlow] Missing price for ${instrumentId} (${ticker}) on ${todayStr}`);
125
+
126
+ if (priceDay1 != null && priceDay2 != null && priceDay1 > 0) {
127
+ priceChangePct = (priceDay2 - priceDay1) / priceDay1;
128
+ }
129
+ }
110
130
 
111
131
  if (priceChangePct === null) {
112
- // Cannot calculate if price data is missing for either day
113
132
  finalResults[ticker] = {
114
133
  net_crowd_flow_pct: 0,
115
- error: "Missing price data for calculation."
134
+ avg_value_day1: avg_day1_value,
135
+ avg_value_day2: avg_day2_value,
136
+ price_change_pct: null,
137
+ user_sample_size: this.user_count,
138
+ warning: 'Missing price data for calculation'
116
139
  };
117
140
  continue;
118
141
  }
119
142
 
120
- // 3. Calculate the expected value (the "price-move" effect)
121
- // We use avg_day1_value as the base. The cash flow proxy calculation
122
- // uses (avg_value - avg_invested) because it's solving for a different unknown.
123
- // Here, we are solving for flow *relative to the asset itself*.
143
+ // Calculate expected day2 value from price movement
124
144
  const expected_day2_value = avg_day1_value * (1 + priceChangePct);
125
145
 
126
- // 4. Find the signal (the "crowd-flow" effect)
146
+ // Net crowd flow = actual minus expected
127
147
  const net_crowd_flow_pct = avg_day2_value - expected_day2_value;
128
148
 
129
149
  finalResults[ticker] = {
130
- net_crowd_flow_pct: net_crowd_flow_pct,
131
- avg_value_day1_pct: avg_day1_value,
132
- avg_value_day2_pct: avg_day2_value,
133
- expected_value_day2_pct: expected_day2_value,
150
+ net_crowd_flow_pct,
151
+ avg_value_day1,
152
+ avg_value_day2,
153
+ expected_day2_value,
134
154
  price_change_pct: priceChangePct,
135
155
  user_sample_size: this.user_count
136
156
  };
137
157
  }
138
-
158
+
139
159
  return finalResults;
140
160
  }
141
161
 
162
+
142
163
  reset() {
143
164
  this.asset_values = {};
144
165
  this.user_count = 0;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @fileoverview Estimates a proxy for net crowd cash flow (Deposits vs. Withdrawals)
3
+ * by aggregating portfolio percentage changes across all users.
4
+ *
5
+ * This calculation is based on the formula:
6
+ * Total_Change = P/L_Effect + Trading_Effect + Cash_Flow_Effect
7
+ *
8
+ * Where:
9
+ * - Total_Change = avg_value_day2 - avg_value_day1
10
+ * - P/L_Effect = avg_value_day1 - avg_invested_day1
11
+ * - Trading_Effect = avg_invested_day2 - avg_invested_day1
12
+ *
13
+ * We solve for Cash_Flow_Effect, which serves as our proxy.
14
+ * A negative value indicates a net DEPOSIT (denominator grew, shrinking all %s).
15
+ * A positive value indicates a net WITHDRAWAL (denominator shrank, inflating all %s).
16
+ *
17
+ * NOTE: This file is a logical duplicate of deposit_withdrawal_percentage.js
18
+ * to satisfy the hardcoded 'crowd-cash-flow-proxy' dependency in several
19
+ * meta-calculations.
20
+ */
21
+
22
+ class CrowdCashFlowProxy {
23
+ constructor() {
24
+ this.total_invested_day1 = 0;
25
+ this.total_value_day1 = 0;
26
+ this.total_invested_day2 = 0;
27
+ this.total_value_day2 = 0;
28
+ this.user_count = 0;
29
+ }
30
+
31
+ /**
32
+ * Helper to sum a specific field from an AggregatedPositions array.
33
+ * @param {Array<object>} positions - The AggregatedPositions array.
34
+ * @param {string} field - The field to sum ('Invested' or 'Value').
35
+ * @returns {number} The total sum of that field.
36
+ */
37
+ _sumPositions(positions, field) {
38
+ if (!positions || !Array.isArray(positions)) {
39
+ return 0;
40
+ }
41
+ return positions.reduce((sum, pos) => sum + (pos[field] || 0), 0);
42
+ }
43
+
44
+ process(todayPortfolio, yesterdayPortfolio, userId) {
45
+ // This calculation requires both days' data to compare
46
+ if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
47
+ return;
48
+ }
49
+
50
+ const invested_day1 = this._sumPositions(yesterdayPortfolio.AggregatedPositions, 'Invested');
51
+ const value_day1 = this._sumPositions(yesterdayPortfolio.AggregatedPositions, 'Value');
52
+ const invested_day2 = this._sumPositions(todayPortfolio.AggregatedPositions, 'Invested');
53
+ const value_day2 = this._sumPositions(todayPortfolio.AggregatedPositions, 'Value');
54
+
55
+ // Only include users who have some form of positions
56
+ if (invested_day1 === 0 && invested_day2 === 0 && value_day1 === 0 && value_day2 === 0) {
57
+ return;
58
+ }
59
+
60
+ this.total_invested_day1 += invested_day1;
61
+ this.total_value_day1 += value_day1;
62
+ this.total_invested_day2 += invested_day2;
63
+ this.total_value_day2 += value_day2;
64
+ this.user_count++;
65
+ }
66
+
67
+ getResult() {
68
+ if (this.user_count === 0) {
69
+ return {}; // No users processed, return empty.
70
+ }
71
+
72
+ // 1. Calculate the average portfolio for the crowd
73
+ const avg_invested_day1 = this.total_invested_day1 / this.user_count;
74
+ const avg_value_day1 = this.total_value_day1 / this.user_count;
75
+ const avg_invested_day2 = this.total_invested_day2 / this.user_count;
76
+ const avg_value_day2 = this.total_value_day2 / this.user_count;
77
+
78
+ // 2. Isolate the three effects
79
+ const total_value_change = avg_value_day2 - avg_value_day1;
80
+ const pl_effect = avg_value_day1 - avg_invested_day1;
81
+ const trading_effect = avg_invested_day2 - avg_invested_day1;
82
+
83
+ // 3. Solve for the Cash Flow Effect
84
+ // Total_Change = pl_effect + trading_effect + cash_flow_effect
85
+ const cash_flow_effect = total_value_change - pl_effect - trading_effect;
86
+
87
+ return {
88
+ // The final proxy value.
89
+ // A negative value signals a net DEPOSIT.
90
+ // A positive value signals a net WITHDRAWAL.
91
+ cash_flow_effect_proxy: cash_flow_effect,
92
+
93
+ // Interpretation for the frontend
94
+ interpretation: "A negative value signals a net crowd deposit. A positive value signals a net crowd withdrawal.",
95
+
96
+ // Debug components
97
+ components: {
98
+ total_value_change: total_value_change,
99
+ pl_effect: pl_effect,
100
+ trading_effect: trading_effect
101
+ },
102
+ // Debug averages
103
+ averages: {
104
+ avg_invested_day1: avg_invested_day1,
105
+ avg_value_day1: avg_value_day1,
106
+ avg_invested_day2: avg_invested_day2,
107
+ avg_value_day2: avg_value_day2
108
+ },
109
+ user_sample_size: this.user_count
110
+ };
111
+ }
112
+
113
+ reset() {
114
+ this.total_invested_day1 = 0;
115
+ this.total_value_day1 = 0;
116
+ this.total_invested_day2 = 0;
117
+ this.total_value_day2 = 0;
118
+ this.user_count = 0;
119
+ }
120
+ }
121
+
122
+ module.exports = CrowdCashFlowProxy;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [