aiden-shared-calculations-unified 1.0.64 → 1.0.65
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 +1 -1
- package/calculations/activity/historical/activity_by_pnl_status.js +33 -0
- package/calculations/activity/historical/daily_asset_activity.js +42 -0
- package/calculations/activity/historical/daily_user_activity_tracker.js +37 -0
- package/calculations/activity/historical/speculator_adjustment_activity.js +26 -0
- package/calculations/asset_metrics/asset_position_size.js +36 -0
- package/calculations/backtests/strategy-performance.js +41 -0
- package/calculations/behavioural/historical/asset_crowd_flow.js +124 -127
- package/calculations/behavioural/historical/drawdown_response.js +113 -35
- package/calculations/behavioural/historical/dumb-cohort-flow.js +191 -171
- package/calculations/behavioural/historical/gain_response.js +113 -34
- package/calculations/behavioural/historical/historical_performance_aggregator.js +63 -48
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +159 -63
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +159 -64
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +86 -19
- package/calculations/behavioural/historical/position_count_pnl.js +91 -39
- package/calculations/behavioural/historical/smart-cohort-flow.js +192 -172
- package/calculations/behavioural/historical/smart_money_flow.js +160 -151
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +95 -89
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +88 -81
- package/calculations/capital_flow/historical/new_allocation_percentage.js +75 -26
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +73 -32
- package/calculations/insights/daily_buy_sell_sentiment_count.js +47 -32
- package/calculations/insights/daily_total_positions_held.js +28 -24
- package/calculations/insights/historical/daily_bought_vs_sold_count.js +101 -36
- package/calculations/insights/historical/daily_ownership_delta.js +95 -32
- package/calculations/meta/capital_deployment_strategy.js +78 -110
- package/calculations/meta/capital_liquidation_performance.js +114 -111
- package/calculations/meta/cash-flow-deployment.js +114 -107
- package/calculations/meta/cash-flow-liquidation.js +114 -107
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +94 -54
- package/calculations/meta/negative_expectancy_cohort_flow.js +185 -177
- package/calculations/meta/positive_expectancy_cohort_flow.js +186 -181
- package/calculations/meta/profit_cohort_divergence.js +83 -59
- package/calculations/meta/shark_attack_signal.js +91 -39
- package/calculations/meta/smart-dumb-divergence-index.js +114 -98
- package/calculations/meta/smart_dumb_divergence_index_v2.js +109 -98
- package/calculations/meta/social-predictive-regime-state.js +76 -155
- package/calculations/meta/social-topic-driver-index.js +74 -127
- package/calculations/meta/user_expectancy_score.js +83 -31
- package/calculations/pnl/asset_pnl_status.js +120 -31
- package/calculations/pnl/average_daily_pnl_all_users.js +42 -27
- package/calculations/pnl/average_daily_pnl_per_sector.js +84 -26
- package/calculations/pnl/average_daily_pnl_per_stock.js +71 -29
- package/calculations/pnl/average_daily_position_pnl.js +49 -21
- package/calculations/pnl/historical/profitability_migration.js +81 -35
- package/calculations/pnl/historical/user_profitability_tracker.js +107 -104
- package/calculations/pnl/pnl_distribution_per_stock.js +65 -45
- package/calculations/pnl/profitability_ratio_per_stock.js +78 -21
- package/calculations/pnl/profitability_skew_per_stock.js +86 -31
- package/calculations/pnl/profitable_and_unprofitable_status.js +45 -45
- package/calculations/sanity/users_processed.js +24 -1
- package/calculations/sectors/historical/diversification_pnl.js +104 -42
- package/calculations/sectors/historical/sector_rotation.js +94 -45
- package/calculations/sectors/total_long_per_sector.js +55 -20
- package/calculations/sectors/total_short_per_sector.js +55 -20
- package/calculations/sentiment/historical/crowd_conviction_score.js +233 -53
- package/calculations/short_and_long_stats/long_position_per_stock.js +50 -14
- package/calculations/short_and_long_stats/sentiment_per_stock.js +76 -19
- package/calculations/short_and_long_stats/short_position_per_stock.js +50 -13
- package/calculations/short_and_long_stats/total_long_figures.js +34 -13
- package/calculations/short_and_long_stats/total_short_figures.js +34 -14
- package/calculations/socialPosts/social-asset-posts-trend.js +96 -29
- package/calculations/socialPosts/social-top-mentioned-words.js +95 -74
- package/calculations/socialPosts/social-topic-interest-evolution.js +92 -29
- package/calculations/socialPosts/social-topic-sentiment-matrix.js +70 -78
- package/calculations/socialPosts/social-word-mentions-trend.js +96 -38
- package/calculations/socialPosts/social_activity_aggregation.js +106 -77
- package/calculations/socialPosts/social_sentiment_aggregation.js +115 -86
- package/calculations/speculators/distance_to_stop_loss_per_leverage.js +82 -43
- package/calculations/speculators/distance_to_tp_per_leverage.js +81 -42
- package/calculations/speculators/entry_distance_to_sl_per_leverage.js +80 -44
- package/calculations/speculators/entry_distance_to_tp_per_leverage.js +81 -44
- package/calculations/speculators/historical/risk_appetite_change.js +89 -32
- package/calculations/speculators/historical/tsl_effectiveness.js +57 -47
- package/calculations/speculators/holding_duration_per_asset.js +83 -23
- package/calculations/speculators/leverage_per_asset.js +68 -19
- package/calculations/speculators/leverage_per_sector.js +86 -25
- package/calculations/speculators/risk_reward_ratio_per_asset.js +82 -28
- package/calculations/speculators/speculator_asset_sentiment.js +100 -48
- package/calculations/speculators/speculator_danger_zone.js +101 -33
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +93 -66
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +94 -47
- package/calculations/speculators/stop_loss_per_asset.js +94 -26
- package/calculations/speculators/take_profit_per_asset.js +95 -27
- package/calculations/speculators/tsl_per_asset.js +77 -23
- package/package.json +1 -1
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
|
@@ -1,188 +1,197 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
2
|
+
* @fileoverview Calculation (Pass 3) for smart money flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* This metric calculates the "Net Crowd Flow Percentage" for
|
|
5
|
+
* "Smart Money" users, defined as users who were profitable
|
|
6
|
+
* in 5 of the last 7 days.
|
|
7
|
+
*
|
|
8
|
+
* This calculation *depends* on 'user_profitability_tracker'
|
|
9
|
+
* to identify the cohort.
|
|
8
10
|
*/
|
|
11
|
+
const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
class SmartMoneyFlow {
|
|
14
|
+
constructor() {
|
|
15
|
+
// This calculation only aggregates by sector
|
|
16
|
+
this.sectorData = new Map();
|
|
17
|
+
this.mappings = null;
|
|
18
|
+
this.smartMoneyUserIds = null;
|
|
19
|
+
}
|
|
15
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Defines the output schema for this calculation.
|
|
23
|
+
* @returns {object} JSON Schema object
|
|
24
|
+
*/
|
|
25
|
+
static getSchema() {
|
|
26
|
+
return {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"description": "Calculates net capital flow % (price-adjusted) for the 'Smart Money' cohort (profitable 5 of last 7 days), aggregated by sector.",
|
|
29
|
+
"properties": {
|
|
30
|
+
"cohort_size": {
|
|
31
|
+
"type": "number",
|
|
32
|
+
"description": "The number of users identified as being in the Smart Money Cohort."
|
|
33
|
+
},
|
|
34
|
+
"sectors": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"description": "Price-adjusted net flow per sector.",
|
|
37
|
+
"patternProperties": {
|
|
38
|
+
// Sector
|
|
39
|
+
"^.*$": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"properties": {
|
|
42
|
+
"net_flow_percentage": { "type": "number" },
|
|
43
|
+
"total_invested_today": { "type": "number" },
|
|
44
|
+
"total_invested_yesterday": { "type": "number" }
|
|
45
|
+
},
|
|
46
|
+
"required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"additionalProperties": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"net_flow_percentage": { "type": "number" },
|
|
53
|
+
"total_invested_today": { "type": "number" },
|
|
54
|
+
"total_invested_yesterday": { "type": "number" }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"required": ["cohort_size", "sectors"]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
16
62
|
|
|
17
|
-
class SmartMoneyFlow {
|
|
18
|
-
|
|
19
63
|
/**
|
|
20
|
-
*
|
|
21
|
-
* This calc reads shards from Pass 1, but doesn't take a computed
|
|
22
|
-
* result as an argument. The manifest ensures it runs in Pass 2.
|
|
64
|
+
* Statically declare dependencies.
|
|
23
65
|
*/
|
|
24
66
|
static getDependencies() {
|
|
25
|
-
return ['
|
|
67
|
+
return ['user_profitability_tracker'];
|
|
26
68
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
|
|
70
|
+
_getPortfolioPositions(portfolio) {
|
|
71
|
+
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_initSector(sector) {
|
|
75
|
+
if (!this.sectorData.has(sector)) {
|
|
76
|
+
this.sectorData.set(sector, {
|
|
77
|
+
total_invested_yesterday: 0,
|
|
78
|
+
total_invested_today: 0,
|
|
79
|
+
price_change_yesterday: 0,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
36
82
|
}
|
|
37
83
|
|
|
38
84
|
/**
|
|
39
|
-
*
|
|
40
|
-
* This is now the *first* method called by the runner.
|
|
41
|
-
* It caches the portfolios.
|
|
85
|
+
* Helper to get the cohort IDs from the dependency.
|
|
42
86
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
logger.log('INFO', '[SmartMoneyFlow] Starting meta-process...');
|
|
47
|
-
|
|
48
|
-
// 1. Load external dependencies (sectors)
|
|
49
|
-
if (!this.sectorMap) {
|
|
50
|
-
try {
|
|
51
|
-
this.sectorMap = await getInstrumentSectorMap();
|
|
52
|
-
} catch (error) {
|
|
53
|
-
logger.log('ERROR', '[SmartMoneyFlow] Failed to load sector map.', { err: error.message });
|
|
54
|
-
return null; // Abort
|
|
55
|
-
}
|
|
87
|
+
_getSmartMoneyCohort(fetchedDependencies) {
|
|
88
|
+
if (this.smartMoneyUserIds) {
|
|
89
|
+
return this.smartMoneyUserIds;
|
|
56
90
|
}
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
|
|
62
|
-
const yesterdayRefs = await getPortfolioPartRefs(config, dependencies, yesterdayStr);
|
|
63
|
-
const yesterdayPortfolios = await loadFullDayMap(config, dependencies, yesterdayRefs);
|
|
64
|
-
logger.log('INFO', `[SmartMoneyFlow] Loaded ${yesterdayRefs.length} part refs for yesterday.`);
|
|
65
|
-
|
|
66
|
-
// 3. Identify smart money users by reading Pass 1 shards
|
|
67
|
-
// This is the dependency.
|
|
68
|
-
await this.identifySmartMoney(dependencies);
|
|
69
|
-
if (this.smartMoneyUsers.size === 0) {
|
|
70
|
-
logger.log('WARN', '[SmartMoneyFlow] No smart money users identified. Aborting.');
|
|
71
|
-
return { smart_money_flow: {} };
|
|
92
|
+
const profitabilityData = fetchedDependencies['user_profitability_tracker'];
|
|
93
|
+
if (!profitabilityData || !profitabilityData.user_details) {
|
|
94
|
+
return new Set();
|
|
72
95
|
}
|
|
73
96
|
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
97
|
+
this.smartMoneyUserIds = new Set();
|
|
98
|
+
for (const [userId, data] of Object.entries(profitabilityData.user_details)) {
|
|
99
|
+
// Definition: Profitable in 5 of the last 7 days.
|
|
100
|
+
if (data.profitable_days_7d >= 5) {
|
|
101
|
+
this.smartMoneyUserIds.add(userId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return this.smartMoneyUserIds;
|
|
105
|
+
}
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
108
|
+
const smartMoneyCohort = this._getSmartMoneyCohort(fetchedDependencies);
|
|
109
|
+
|
|
110
|
+
// This user is not in the "smart money cohort", skip.
|
|
111
|
+
if (!smartMoneyCohort.has(userId)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
81
114
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const pToday = todayPortfoliosChunk[uid];
|
|
89
|
-
const pYesterday = yesterdayPortfolios[uid];
|
|
90
|
-
|
|
91
|
-
if (!pToday || !pYesterday) continue;
|
|
115
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
92
118
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const yesterdaySectorInvestment = this.calculateSectorInvestment(pYesterday);
|
|
119
|
+
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
120
|
+
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
96
121
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const todayAmount = todaySectorInvestment[sector] || 0;
|
|
100
|
-
const yesterdayAmount = yesterdaySectorInvestment[sector] || 0;
|
|
101
|
-
const change = todayAmount - yesterdayAmount;
|
|
122
|
+
const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
|
|
123
|
+
const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
|
|
102
124
|
|
|
103
|
-
|
|
104
|
-
if (!sectorFlow[sector]) {
|
|
105
|
-
sectorFlow[sector] = 0;
|
|
106
|
-
}
|
|
107
|
-
sectorFlow[sector] += change;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
125
|
+
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
127
|
+
if (!this.mappings) {
|
|
128
|
+
// Context contains the mappings loaded in Pass 1
|
|
129
|
+
this.mappings = context.mappings;
|
|
130
|
+
}
|
|
118
131
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
for (const instrumentId of allInstrumentIds) {
|
|
133
|
+
if (!instrumentId) continue;
|
|
134
|
+
|
|
135
|
+
const yP = yPosMap.get(instrumentId);
|
|
136
|
+
const tP = tPosMap.get(instrumentId);
|
|
137
|
+
|
|
138
|
+
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
139
|
+
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
140
|
+
|
|
141
|
+
// Get sector and initialize it
|
|
142
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
143
|
+
this._initSector(sector);
|
|
144
|
+
const sectorAsset = this.sectorData.get(sector);
|
|
145
|
+
|
|
146
|
+
if (yInvested > 0) {
|
|
147
|
+
const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
|
|
148
|
+
sectorAsset.total_invested_yesterday += yInvested;
|
|
149
|
+
sectorAsset.price_change_yesterday += yPriceChange * yInvested;
|
|
150
|
+
}
|
|
151
|
+
if (tInvested > 0) {
|
|
152
|
+
sectorAsset.total_invested_today += tInvested;
|
|
128
153
|
}
|
|
129
|
-
const shardSnapshots = await Promise.all(shardPromises);
|
|
130
|
-
|
|
131
|
-
let profitableUsersFound = 0;
|
|
132
|
-
shardSnapshots.forEach((snap, index) => {
|
|
133
|
-
if (snap.exists) {
|
|
134
|
-
const shardData = snap.data() || {};
|
|
135
|
-
for (const userId in shardData) {
|
|
136
|
-
const history = shardData[userId];
|
|
137
|
-
if (Array.isArray(history) && history.length >= 5) {
|
|
138
|
-
const profitableDays = history.slice(-7).filter(d => d && typeof d.pnl === 'number' && d.pnl > 0).length;
|
|
139
|
-
if (profitableDays >= 5) {
|
|
140
|
-
this.smartMoneyUsers.add(userId);
|
|
141
|
-
profitableUsersFound++;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
} else {
|
|
146
|
-
logger.log('WARN', `[SmartMoneyFlow] Profitability shard user_profitability_shard_${index} does not exist.`);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
logger.log('INFO', `[SmartMoneyFlow] Found ${profitableUsersFound} smart money users across all shards.`);
|
|
150
|
-
|
|
151
|
-
} catch (error) {
|
|
152
|
-
logger.log('ERROR', '[SmartMoneyFlow] Error identifying smart money users', { err: error.message });
|
|
153
|
-
this.smartMoneyUsers.clear();
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
157
|
+
async getResult(fetchedDependencies) {
|
|
158
|
+
// Ensure mappings are loaded (can be from context or loaded now)
|
|
159
|
+
if (!this.mappings) {
|
|
160
|
+
this.mappings = await loadInstrumentMappings();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Ensure cohort is calculated at least once
|
|
164
|
+
const smartMoneyCohort = this._getSmartMoneyCohort(fetchedDependencies);
|
|
165
|
+
|
|
166
|
+
// Calculate Sector Flow
|
|
167
|
+
const sectorResult = {};
|
|
168
|
+
for (const [sector, data] of this.sectorData.entries()) {
|
|
169
|
+
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
170
|
+
|
|
171
|
+
if (total_invested_yesterday > 0) {
|
|
172
|
+
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
173
|
+
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
174
|
+
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
175
|
+
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
176
|
+
|
|
177
|
+
sectorResult[sector] = {
|
|
178
|
+
net_flow_percentage: net_flow_percentage,
|
|
179
|
+
total_invested_today: total_invested_today,
|
|
180
|
+
total_invested_yesterday: total_invested_yesterday
|
|
181
|
+
};
|
|
174
182
|
}
|
|
175
183
|
}
|
|
176
|
-
return sectorInvestment;
|
|
177
|
-
}
|
|
178
184
|
|
|
185
|
+
return {
|
|
186
|
+
cohort_size: smartMoneyCohort.size,
|
|
187
|
+
sectors: sectorResult
|
|
188
|
+
};
|
|
189
|
+
}
|
|
179
190
|
|
|
180
|
-
async getResult() { return null; }
|
|
181
191
|
reset() {
|
|
182
|
-
this.
|
|
183
|
-
this.
|
|
184
|
-
this.
|
|
185
|
-
this.yesterdayPortfolios = {};
|
|
192
|
+
this.sectorData.clear();
|
|
193
|
+
this.mappings = null;
|
|
194
|
+
this.smartMoneyUserIds = null;
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
|
|
@@ -1,121 +1,127 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* by aggregating portfolio percentage changes across all users.
|
|
2
|
+
* @fileoverview Calculation (Pass 2) for crowd cash flow proxy.
|
|
4
3
|
*
|
|
5
|
-
* This
|
|
6
|
-
*
|
|
4
|
+
* This metric estimates the net cash flow (deposits vs. withdrawals)
|
|
5
|
+
* of the crowd by analyzing changes in 'Invested' vs. 'Cash' balances
|
|
6
|
+
* in user portfolios.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
8
|
+
* It's a proxy because it doesn't see real bank transactions, but
|
|
9
|
+
* infers from the total portfolio value vs. the invested amount.
|
|
20
10
|
*/
|
|
21
|
-
|
|
22
11
|
class CrowdCashFlowProxy {
|
|
23
12
|
constructor() {
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
13
|
+
this.total_portfolio_value_yesterday = 0;
|
|
14
|
+
this.total_invested_yesterday = 0;
|
|
15
|
+
|
|
16
|
+
this.total_portfolio_value_today = 0;
|
|
17
|
+
this.total_invested_today = 0;
|
|
29
18
|
}
|
|
30
19
|
|
|
31
20
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @
|
|
34
|
-
* @param {string} field - The field to sum ('Invested' or 'Value').
|
|
35
|
-
* @returns {number} The total sum of that field.
|
|
21
|
+
* Defines the output schema for this calculation.
|
|
22
|
+
* @returns {object} JSON Schema object
|
|
36
23
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
24
|
+
static getSchema() {
|
|
25
|
+
return {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"description": "Estimates net cash flow (deposits/withdrawals) by comparing changes in total portfolio value vs. invested capital.",
|
|
28
|
+
"properties": {
|
|
29
|
+
"total_portfolio_value_yesterday": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Sum of all users' total portfolio values from yesterday."
|
|
32
|
+
},
|
|
33
|
+
"total_invested_yesterday": {
|
|
34
|
+
"type": "number",
|
|
35
|
+
"description": "Sum of all users' invested capital from yesterday."
|
|
36
|
+
},
|
|
37
|
+
"total_portfolio_value_today": {
|
|
38
|
+
"type": "number",
|
|
39
|
+
"description": "Sum of all users' total portfolio values from today."
|
|
40
|
+
},
|
|
41
|
+
"total_invested_today": {
|
|
42
|
+
"type": "number",
|
|
43
|
+
"description": "Sum of all users' invested capital from today."
|
|
44
|
+
},
|
|
45
|
+
"total_pnl_contribution": {
|
|
46
|
+
"type": "number",
|
|
47
|
+
"description": "The estimated change in portfolio value attributable to P&L."
|
|
48
|
+
},
|
|
49
|
+
"net_cash_flow_proxy": {
|
|
50
|
+
"type": "number",
|
|
51
|
+
"description": "The estimated net cash flow (Deposits - Withdrawals). Positive indicates net deposits."
|
|
52
|
+
},
|
|
53
|
+
"net_cash_flow_proxy_pct": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"description": "The net cash flow proxy as a percentage of yesterday's total portfolio value."
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"required": [
|
|
59
|
+
"total_portfolio_value_yesterday", "total_invested_yesterday",
|
|
60
|
+
"total_portfolio_value_today", "total_invested_today",
|
|
61
|
+
"total_pnl_contribution", "net_cash_flow_proxy", "net_cash_flow_proxy_pct"
|
|
62
|
+
]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_getPortfolioValues(portfolio) {
|
|
67
|
+
if (!portfolio || !portfolio.Summary) {
|
|
68
|
+
return { portfolioValue: 0, invested: 0 };
|
|
40
69
|
}
|
|
41
|
-
|
|
70
|
+
|
|
71
|
+
// Total value (Invested + Cash)
|
|
72
|
+
const portfolioValue = portfolio.Summary.PortfolioValue || 0;
|
|
73
|
+
// Total invested in positions
|
|
74
|
+
const invested = portfolio.Summary.Invested || 0;
|
|
75
|
+
|
|
76
|
+
return { portfolioValue, invested };
|
|
42
77
|
}
|
|
43
78
|
|
|
44
|
-
process(todayPortfolio, yesterdayPortfolio
|
|
45
|
-
|
|
46
|
-
if (!todayPortfolio || !yesterdayPortfolio || !todayPortfolio.AggregatedPositions || !yesterdayPortfolio.AggregatedPositions) {
|
|
79
|
+
process(todayPortfolio, yesterdayPortfolio) {
|
|
80
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
47
81
|
return;
|
|
48
82
|
}
|
|
49
83
|
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const invested_day2 = this._sumPositions(todayPortfolio.AggregatedPositions, 'Invested');
|
|
53
|
-
const value_day2 = this._sumPositions(todayPortfolio.AggregatedPositions, 'Value');
|
|
84
|
+
const yValues = this._getPortfolioValues(yesterdayPortfolio);
|
|
85
|
+
const tValues = this._getPortfolioValues(todayPortfolio);
|
|
54
86
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
87
|
+
this.total_portfolio_value_yesterday += yValues.portfolioValue;
|
|
88
|
+
this.total_invested_yesterday += yValues.invested;
|
|
59
89
|
|
|
60
|
-
this.
|
|
61
|
-
this.
|
|
62
|
-
this.total_invested_day2 += invested_day2;
|
|
63
|
-
this.total_value_day2 += value_day2;
|
|
64
|
-
this.user_count++;
|
|
90
|
+
this.total_portfolio_value_today += tValues.portfolioValue;
|
|
91
|
+
this.total_invested_today += tValues.invested;
|
|
65
92
|
}
|
|
66
93
|
|
|
67
94
|
getResult() {
|
|
68
|
-
|
|
69
|
-
|
|
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;
|
|
95
|
+
// P&L is the change in *invested* value, not total value
|
|
96
|
+
const totalPnl = this.total_invested_today - this.total_invested_yesterday;
|
|
77
97
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
// The change in total value is (P&L + Net Cash Flow)
|
|
99
|
+
// So, Net Cash Flow = (Total Value Change) - P&L
|
|
100
|
+
const totalValueChange = this.total_portfolio_value_today - this.total_portfolio_value_yesterday;
|
|
101
|
+
|
|
102
|
+
const netCashFlowProxy = totalValueChange - totalPnl;
|
|
103
|
+
|
|
104
|
+
const netCashFlowProxyPct = (this.total_portfolio_value_yesterday > 0)
|
|
105
|
+
? (netCashFlowProxy / this.total_portfolio_value_yesterday) * 100
|
|
106
|
+
: 0;
|
|
86
107
|
|
|
87
108
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Interpretation for the frontend
|
|
94
|
-
interpretation: "A negative value signals a net crowd deposit. A positive value signals a net crowd withdrawal.",
|
|
109
|
+
total_portfolio_value_yesterday: this.total_portfolio_value_yesterday,
|
|
110
|
+
total_invested_yesterday: this.total_invested_yesterday,
|
|
111
|
+
total_portfolio_value_today: this.total_portfolio_value_today,
|
|
112
|
+
total_invested_today: this.total_invested_today,
|
|
95
113
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
114
|
+
total_pnl_contribution: totalPnl,
|
|
115
|
+
net_cash_flow_proxy: netCashFlowProxy,
|
|
116
|
+
net_cash_flow_proxy_pct: netCashFlowProxyPct
|
|
110
117
|
};
|
|
111
118
|
}
|
|
112
119
|
|
|
113
120
|
reset() {
|
|
114
|
-
this.
|
|
115
|
-
this.
|
|
116
|
-
this.
|
|
117
|
-
this.
|
|
118
|
-
this.user_count = 0;
|
|
121
|
+
this.total_portfolio_value_yesterday = 0;
|
|
122
|
+
this.total_invested_yesterday = 0;
|
|
123
|
+
this.total_portfolio_value_today = 0;
|
|
124
|
+
this.total_invested_today = 0;
|
|
119
125
|
}
|
|
120
126
|
}
|
|
121
127
|
|