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
|
-
|
|
84
|
+
console.warn('[AssetCrowdFlow] No users processed or dates missing.');
|
|
85
|
+
return {};
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
// Load
|
|
88
|
+
// Load priceMap and mappings if not loaded
|
|
88
89
|
if (!this.priceMap || !this.mappings) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const avg_day1_value = this.asset_values[
|
|
106
|
-
const avg_day2_value = this.asset_values[
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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;
|