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,224 +1,232 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* *only* for the "Dumb Cohort" (Bottom 20% of Investor Scores).
|
|
2
|
+
* @fileoverview Calculation (Pass 4) for negative expectancy cohort flow.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This metric calculates the "Net Crowd Flow Percentage" for the
|
|
5
|
+
* "Negative Expectancy Cohort" (users with a low expectancy score).
|
|
6
|
+
*
|
|
7
|
+
* This calculation *depends* on 'user_expectancy_score'
|
|
8
|
+
* to identify the cohort.
|
|
9
9
|
*/
|
|
10
|
+
const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const COHORT_PERCENTILE = 0.2; // Bottom 20%
|
|
19
|
-
const PROFILE_CALC_ID = 'user-investment-profile'; // The calc to read IS scores from
|
|
20
|
-
|
|
21
|
-
class DumbCohortFlow {
|
|
12
|
+
class NegativeExpectancyCohortFlow {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.assetData = new Map();
|
|
15
|
+
this.sectorData = new Map();
|
|
16
|
+
this.mappings = null;
|
|
17
|
+
this.negExpCohortUserIds = null;
|
|
18
|
+
}
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
21
|
+
* Defines the output schema for this calculation.
|
|
22
|
+
* @returns {object} JSON Schema object
|
|
25
23
|
*/
|
|
26
|
-
static
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
static getSchema() {
|
|
25
|
+
const flowSchema = {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"net_flow_percentage": { "type": "number" },
|
|
29
|
+
"total_invested_today": { "type": "number" },
|
|
30
|
+
"total_invested_yesterday": { "type": "number" }
|
|
31
|
+
},
|
|
32
|
+
"required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday"]
|
|
33
|
+
};
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
return {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Calculates net capital flow % (price-adjusted) for the 'Negative Expectancy' cohort (score < 0.2), aggregated by asset and sector.",
|
|
38
|
+
"properties": {
|
|
39
|
+
"cohort_size": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"description": "The number of users identified as being in the Negative Expectancy Cohort."
|
|
42
|
+
},
|
|
43
|
+
"assets": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"description": "Price-adjusted net flow per asset.",
|
|
46
|
+
"patternProperties": { "^.*$": flowSchema }, // Ticker
|
|
47
|
+
"additionalProperties": flowSchema
|
|
48
|
+
},
|
|
49
|
+
"sectors": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"description": "Price-adjusted net flow per sector.",
|
|
52
|
+
"patternProperties": { "^.*$": flowSchema }, // Sector
|
|
53
|
+
"additionalProperties": flowSchema
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": ["cohort_size", "assets", "sectors"]
|
|
57
|
+
};
|
|
32
58
|
}
|
|
33
59
|
|
|
34
60
|
/**
|
|
35
|
-
*
|
|
61
|
+
* Statically declare dependencies.
|
|
36
62
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const profileData = fetchedDependencies['user-expectancy-score'];
|
|
41
|
-
|
|
42
|
-
if (!profileData || Object.keys(profileData).length === 0) {
|
|
43
|
-
logger.log('WARN', `[NegativeExpectancyCohortFlow] Cannot find dependency 'user-expectancy-score'. Cohort will not be built.`);
|
|
44
|
-
return null;
|
|
63
|
+
static getDependencies() {
|
|
64
|
+
return ['user_expectancy_score']; // Pass 3
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
score: data.expectancy_score
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
allScores.sort((a, b) => a.score - b.score);
|
|
53
|
-
|
|
54
|
-
// Find the 20th percentile (Bottom 20%)
|
|
55
|
-
const thresholdIndex = Math.floor(allScores.length * 0.20);
|
|
56
|
-
const thresholdScore = allScores[thresholdIndex]?.score || 0;
|
|
57
|
-
|
|
58
|
-
// Filter for users with a *negative expectancy score* AND are in the bottom 20%
|
|
59
|
-
const cohortIds = new Set(
|
|
60
|
-
allScores.filter(s => s.score <= thresholdScore && s.score < 0)
|
|
61
|
-
.map(s => s.userId)
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
logger.log('INFO', `[NegativeExpectancyCohortFlow] Cohort built. ${cohortIds.size} users at or below ${thresholdScore.toFixed(2)} (20th percentile) and < 0.`);
|
|
65
|
-
return cohortIds;
|
|
67
|
+
_getPortfolioPositions(portfolio) {
|
|
68
|
+
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
_initAsset(instrumentId) {
|
|
72
|
+
if (!this.assetData.has(instrumentId)) {
|
|
73
|
+
this.assetData.set(instrumentId, {
|
|
74
|
+
total_invested_yesterday: 0,
|
|
75
|
+
total_invested_today: 0,
|
|
76
|
+
price_change_yesterday: 0,
|
|
77
|
+
});
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
_initSector(sector) {
|
|
82
|
+
if (!this.sectorData.has(sector)) {
|
|
83
|
+
this.sectorData.set(sector, {
|
|
84
|
+
total_invested_yesterday: 0,
|
|
85
|
+
total_invested_today: 0,
|
|
86
|
+
price_change_yesterday: 0,
|
|
87
|
+
});
|
|
81
88
|
}
|
|
82
|
-
return valueMap;
|
|
83
89
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
|
|
91
|
+
_getNegExpCohort(fetchedDependencies) {
|
|
92
|
+
if (this.negExpCohortUserIds) {
|
|
93
|
+
return this.negExpCohortUserIds;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const expectancyData = fetchedDependencies['user_expectancy_score'];
|
|
97
|
+
if (!expectancyData) {
|
|
98
|
+
return new Set();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.negExpCohortUserIds = new Set();
|
|
102
|
+
for (const [userId, data] of Object.entries(expectancyData)) {
|
|
103
|
+
// Definition: Expectancy score < 0.2
|
|
104
|
+
if (data.expectancy_score < 0.2) {
|
|
105
|
+
this.negExpCohortUserIds.add(userId);
|
|
89
106
|
}
|
|
90
107
|
}
|
|
108
|
+
return this.negExpCohortUserIds;
|
|
91
109
|
}
|
|
92
110
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
96
|
-
* @param {object} dependencies The shared dependencies (db, logger, rootData, etc.).
|
|
97
|
-
* @param {object} config The computation system configuration.
|
|
98
|
-
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
99
|
-
* @returns {Promise<object|null>} The analysis result or null.
|
|
100
|
-
*/
|
|
101
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
102
|
-
const { logger, db, rootData, calculationUtils } = dependencies;
|
|
103
|
-
const { portfolioRefs } = rootData;
|
|
104
|
-
logger.log('INFO', '[DumbCohortFlow] Starting meta-process...');
|
|
111
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
112
|
+
const cohort = this._getNegExpCohort(fetchedDependencies);
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (!dumbCohortIds) {
|
|
109
|
-
return null; // Dependency failed
|
|
114
|
+
if (!cohort.has(userId)) {
|
|
115
|
+
return;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
loadAllPriceData(),
|
|
115
|
-
loadInstrumentMappings(),
|
|
116
|
-
getInstrumentSectorMap()
|
|
117
|
-
]);
|
|
118
|
-
if (!priceMap || !mappings || !sectorMap || Object.keys(priceMap).length === 0) {
|
|
119
|
-
logger.log('ERROR', '[DumbCohortFlow] Failed to load critical price/mapping/sector data. Aborting.');
|
|
120
|
-
return null;
|
|
118
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
119
|
+
return;
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
yesterdayDate.setUTCDate(yesterdayDate.getUTCDate() - 1);
|
|
126
|
-
const yesterdayStr = yesterdayDate.toISOString().slice(0, 10);
|
|
127
|
-
const yesterdayRefs = await getPortfolioPartRefs(config, dependencies, yesterdayStr);
|
|
128
|
-
const yesterdayPortfolios = await loadFullDayMap(config, dependencies, yesterdayRefs);
|
|
129
|
-
logger.log('INFO', `[DumbCohortFlow] Loaded ${yesterdayRefs.length} part refs for yesterday.`);
|
|
130
|
-
|
|
131
|
-
// 4. Stream "today's" portfolio data and process
|
|
132
|
-
const asset_values = {};
|
|
133
|
-
const todaySectorInvestment = {};
|
|
134
|
-
const yesterdaySectorInvestment = {};
|
|
135
|
-
let user_count = 0;
|
|
136
|
-
|
|
137
|
-
const batchSize = config.partRefBatchSize || 10;
|
|
138
|
-
for (let i = 0; i < portfolioRefs.length; i += batchSize) {
|
|
139
|
-
const batchRefs = portfolioRefs.slice(i, i + batchSize);
|
|
140
|
-
const todayPortfoliosChunk = await loadDataByRefs(config, dependencies, batchRefs);
|
|
141
|
-
|
|
142
|
-
for (const uid in todayPortfoliosChunk) {
|
|
143
|
-
|
|
144
|
-
if (!dumbCohortIds.has(uid)) continue; // --- Filter user ---
|
|
145
|
-
|
|
146
|
-
const pToday = todayPortfoliosChunk[uid];
|
|
147
|
-
const pYesterday = yesterdayPortfolios[uid];
|
|
148
|
-
|
|
149
|
-
if (!pToday || !pYesterday || !pToday.AggregatedPositions || !pYesterday.AggregatedPositions) {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 4a. RUN ASSET FLOW LOGIC
|
|
154
|
-
const yesterdayValues = this._sumAssetValue(pYesterday.AggregatedPositions);
|
|
155
|
-
const todayValues = this._sumAssetValue(pToday.AggregatedPositions);
|
|
156
|
-
const allInstrumentIds = new Set([...Object.keys(yesterdayValues), ...Object.keys(todayValues)]);
|
|
157
|
-
|
|
158
|
-
for (const instrumentId of allInstrumentIds) {
|
|
159
|
-
this._initAsset(asset_values, instrumentId);
|
|
160
|
-
asset_values[instrumentId].day1_value_sum += (yesterdayValues[instrumentId] || 0);
|
|
161
|
-
asset_values[instrumentId].day2_value_sum += (todayValues[instrumentId] || 0);
|
|
162
|
-
}
|
|
122
|
+
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
123
|
+
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
163
124
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
logger.log('INFO', `[DumbCohortFlow] Processed ${user_count} users in cohort.`);
|
|
125
|
+
const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
|
|
126
|
+
const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
|
|
127
|
+
|
|
128
|
+
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
172
129
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
logger.warn('[DumbCohortFlow] No users processed for dumb cohort. Returning null.');
|
|
176
|
-
return null;
|
|
130
|
+
if (!this.mappings) {
|
|
131
|
+
this.mappings = context.mappings;
|
|
177
132
|
}
|
|
178
133
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const avg_day2_value = asset_values[instrumentId].day2_value_sum / user_count;
|
|
185
|
-
const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, dateStr, priceMap);
|
|
134
|
+
for (const instrumentId of allInstrumentIds) {
|
|
135
|
+
if (!instrumentId) continue;
|
|
136
|
+
|
|
137
|
+
this._initAsset(instrumentId);
|
|
138
|
+
const asset = this.assetData.get(instrumentId);
|
|
186
139
|
|
|
187
|
-
|
|
140
|
+
const yP = yPosMap.get(instrumentId);
|
|
141
|
+
const tP = tPosMap.get(instrumentId);
|
|
188
142
|
|
|
189
|
-
const
|
|
190
|
-
const
|
|
143
|
+
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
144
|
+
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
145
|
+
|
|
146
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
147
|
+
this._initSector(sector);
|
|
148
|
+
const sectorAsset = this.sectorData.get(sector);
|
|
191
149
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
150
|
+
if (yInvested > 0) {
|
|
151
|
+
const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
|
|
152
|
+
|
|
153
|
+
asset.total_invested_yesterday += yInvested;
|
|
154
|
+
asset.price_change_yesterday += yPriceChange * yInvested;
|
|
155
|
+
|
|
156
|
+
sectorAsset.total_invested_yesterday += yInvested;
|
|
157
|
+
sectorAsset.price_change_yesterday += yPriceChange * yInvested;
|
|
158
|
+
}
|
|
159
|
+
if (tInvested > 0) {
|
|
160
|
+
asset.total_invested_today += tInvested;
|
|
161
|
+
sectorAsset.total_invested_today += tInvested;
|
|
162
|
+
}
|
|
197
163
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
for (const
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_calculateFlow(dataMap) {
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [key, data] of dataMap.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
|
+
result[key] = {
|
|
178
|
+
net_flow_percentage: net_flow_percentage,
|
|
179
|
+
total_invested_today: total_invested_today,
|
|
180
|
+
total_invested_yesterday: total_invested_yesterday
|
|
181
|
+
};
|
|
182
|
+
}
|
|
206
183
|
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
187
|
+
async getResult(fetchedDependencies) {
|
|
188
|
+
if (!this.mappings) {
|
|
189
|
+
this.mappings = await loadInstrumentMappings();
|
|
211
190
|
}
|
|
191
|
+
|
|
192
|
+
const cohort = this._getNegExpCohort(fetchedDependencies);
|
|
193
|
+
|
|
194
|
+
// 1. Calculate Asset Flow
|
|
195
|
+
const assetResult = {};
|
|
196
|
+
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
197
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
198
|
+
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
199
|
+
|
|
200
|
+
if (total_invested_yesterday > 0) {
|
|
201
|
+
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
202
|
+
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
203
|
+
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
204
|
+
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
205
|
+
|
|
206
|
+
assetResult[ticker] = {
|
|
207
|
+
net_flow_percentage: net_flow_percentage,
|
|
208
|
+
total_invested_today: total_invested_today,
|
|
209
|
+
total_invested_yesterday: total_invested_yesterday
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 2. Calculate Sector Flow
|
|
215
|
+
const sectorResult = this._calculateFlow(this.sectorData);
|
|
212
216
|
|
|
213
217
|
return {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
cohort_size: cohort.size,
|
|
219
|
+
assets: assetResult,
|
|
220
|
+
sectors: sectorResult
|
|
217
221
|
};
|
|
218
222
|
}
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
reset() {
|
|
225
|
+
this.assetData.clear();
|
|
226
|
+
this.sectorData.clear();
|
|
227
|
+
this.mappings = null;
|
|
228
|
+
this.negExpCohortUserIds = null;
|
|
229
|
+
}
|
|
222
230
|
}
|
|
223
231
|
|
|
224
|
-
module.exports =
|
|
232
|
+
module.exports = NegativeExpectancyCohortFlow;
|