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,218 +1,238 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview
|
|
3
|
-
* *only* for the "Smart Cohort" (Top 20% of Investor Scores).
|
|
2
|
+
* @fileoverview Calculation (Pass 3) for smart cohort flow.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This metric calculates the "Net Crowd Flow Percentage" for the
|
|
5
|
+
* "Smart Cohort" (top 20% of investors).
|
|
6
|
+
*
|
|
7
|
+
* This calculation *depends* on 'user_profitability_tracker'
|
|
8
|
+
* to identify the cohort.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
const { Firestore } = require('@google-cloud/firestore');
|
|
12
|
-
const firestore = new Firestore();
|
|
13
|
-
const { loadAllPriceData, getDailyPriceChange } = require('../../../utils/price_data_provider');
|
|
14
|
-
const { loadInstrumentMappings, getInstrumentSectorMap } = require('../../../utils/sector_mapping_provider');
|
|
15
|
-
// NOTE: Corrected relative path for data_loader
|
|
16
|
-
const { loadDataByRefs, getPortfolioPartRefs, loadFullDayMap } = require('../../../../bulltrackers-module/functions/computation-system/utils/data_loader');
|
|
17
|
-
|
|
18
|
-
const COHORT_PERCENTILE = 0.8; // Top 20%
|
|
19
|
-
const PROFILE_CALC_ID = 'user-investment-profile'; // The calc to read IS scores from
|
|
10
|
+
const { loadInstrumentMappings } = require('../../../utils/sector_mapping_provider');
|
|
20
11
|
|
|
21
12
|
class SmartCohortFlow {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.assetData = new Map();
|
|
15
|
+
this.sectorData = new Map();
|
|
16
|
+
this.mappings = null;
|
|
17
|
+
this.smartCohortUserIds = 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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"description": "Calculates net capital flow % (price-adjusted) for the 'Smart Cohort' (top 20% users), aggregated by asset and sector.",
|
|
38
|
+
"properties": {
|
|
39
|
+
"cohort_size": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"description": "The number of users identified as being in the Smart 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[PROFILE_CALC_ID];
|
|
41
|
-
|
|
42
|
-
if (!profileData || !profileData.daily_investor_scores || Object.keys(profileData.daily_investor_scores).length === 0) {
|
|
43
|
-
logger.log('WARN', `[SmartCohortFlow] Cannot find dependency in fetched data: ${PROFILE_CALC_ID}. Cohort will not be built.`);
|
|
44
|
-
return null; // Return null to signal failure
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const scores = profileData.daily_investor_scores;
|
|
48
|
-
const allScores = Object.entries(scores).map(([userId, score]) => ({ userId, score }));
|
|
49
|
-
allScores.sort((a, b) => a.score - b.score);
|
|
50
|
-
|
|
51
|
-
const thresholdIndex = Math.floor(allScores.length * COHORT_PERCENTILE);
|
|
52
|
-
const thresholdScore = allScores[thresholdIndex]?.score || 999;
|
|
53
|
-
|
|
54
|
-
const smartCohortIds = new Set(
|
|
55
|
-
allScores.filter(s => s.score >= thresholdScore).map(s => s.userId)
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
logger.log('INFO', `[SmartCohortFlow] Cohort built. ${smartCohortIds.size} users at or above ${thresholdScore.toFixed(2)} (80th percentile).`);
|
|
59
|
-
return smartCohortIds;
|
|
63
|
+
static getDependencies() {
|
|
64
|
+
return ['user_profitability_tracker'];
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!asset_values[instrumentId]) {
|
|
65
|
-
asset_values[instrumentId] = { day1_value_sum: 0, day2_value_sum: 0 };
|
|
66
|
-
}
|
|
67
|
+
_getPortfolioPositions(portfolio) {
|
|
68
|
+
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
|
|
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
|
+
});
|
|
75
78
|
}
|
|
76
|
-
return valueMap;
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
});
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
/**
|
|
88
|
-
*
|
|
89
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
90
|
-
* @param {object} dependencies The shared dependencies (db, logger, rootData, etc.).
|
|
91
|
-
* @param {object} config The computation system configuration.
|
|
92
|
-
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
93
|
-
* @returns {Promise<object|null>} The analysis result or null.
|
|
92
|
+
* Helper to get the cohort IDs from the dependency.
|
|
94
93
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
_getSmartCohort(fetchedDependencies) {
|
|
95
|
+
if (this.smartCohortUserIds) {
|
|
96
|
+
return this.smartCohortUserIds;
|
|
97
|
+
}
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return null; // Dependency failed
|
|
99
|
+
const profitabilityData = fetchedDependencies['user_profitability_tracker'];
|
|
100
|
+
if (!profitabilityData || !profitabilityData.ranked_users) {
|
|
101
|
+
return new Set();
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
logger.log('ERROR', '[SmartCohortFlow] Failed to load critical price/mapping/sector data. Aborting.');
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
104
|
+
const rankedUsers = profitabilityData.ranked_users;
|
|
105
|
+
const cohortSize = Math.floor(rankedUsers.length * 0.20);
|
|
106
|
+
|
|
107
|
+
// The 'ranked_users' are sorted ascending by P&L, so top 20% is the *last* 20%.
|
|
108
|
+
this.smartCohortUserIds = new Set(rankedUsers.slice(-cohortSize).map(u => u.userId));
|
|
109
|
+
return this.smartCohortUserIds;
|
|
110
|
+
}
|
|
116
111
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// 4. Stream "today's" portfolio data and process
|
|
126
|
-
const asset_values = {};
|
|
127
|
-
const todaySectorInvestment = {};
|
|
128
|
-
const yesterdaySectorInvestment = {};
|
|
129
|
-
let user_count = 0;
|
|
130
|
-
|
|
131
|
-
const batchSize = config.partRefBatchSize || 10;
|
|
132
|
-
for (let i = 0; i < portfolioRefs.length; i += batchSize) {
|
|
133
|
-
const batchRefs = portfolioRefs.slice(i, i + batchSize);
|
|
134
|
-
const todayPortfoliosChunk = await loadDataByRefs(config, dependencies, batchRefs);
|
|
135
|
-
|
|
136
|
-
for (const uid in todayPortfoliosChunk) {
|
|
137
|
-
|
|
138
|
-
if (!smartCohortIds.has(uid)) continue; // --- Filter user ---
|
|
139
|
-
|
|
140
|
-
const pToday = todayPortfoliosChunk[uid];
|
|
141
|
-
const pYesterday = yesterdayPortfolios[uid];
|
|
142
|
-
|
|
143
|
-
if (!pToday || !pYesterday || !pToday.AggregatedPositions || !pYesterday.AggregatedPositions) {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 4a. RUN ASSET FLOW LOGIC
|
|
148
|
-
const yesterdayValues = this._sumAssetValue(pYesterday.AggregatedPositions);
|
|
149
|
-
const todayValues = this._sumAssetValue(pToday.AggregatedPositions);
|
|
150
|
-
const allInstrumentIds = new Set([...Object.keys(yesterdayValues), ...Object.keys(todayValues)]);
|
|
151
|
-
|
|
152
|
-
for (const instrumentId of allInstrumentIds) {
|
|
153
|
-
this._initAsset(asset_values, instrumentId);
|
|
154
|
-
asset_values[instrumentId].day1_value_sum += (yesterdayValues[instrumentId] || 0);
|
|
155
|
-
asset_values[instrumentId].day2_value_sum += (todayValues[instrumentId] || 0);
|
|
156
|
-
}
|
|
112
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
113
|
+
const smartCohort = this._getSmartCohort(fetchedDependencies);
|
|
114
|
+
|
|
115
|
+
// This user is not in the "smart cohort", skip.
|
|
116
|
+
if (!smartCohort.has(userId)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
157
119
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
this._accumulateSectorInvestment(pYesterday, yesterdaySectorInvestment, sectorMap);
|
|
161
|
-
user_count++;
|
|
162
|
-
}
|
|
120
|
+
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
121
|
+
return;
|
|
163
122
|
}
|
|
123
|
+
|
|
124
|
+
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
125
|
+
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
126
|
+
|
|
127
|
+
const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
|
|
128
|
+
const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
|
|
129
|
+
|
|
130
|
+
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
164
131
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (user_count === 0) {
|
|
169
|
-
logger.warn('[SmartCohortFlow] No users processed for smart cohort. Returning null.');
|
|
170
|
-
return null;
|
|
132
|
+
if (!this.mappings) {
|
|
133
|
+
// Context contains the mappings loaded in Pass 1
|
|
134
|
+
this.mappings = context.mappings;
|
|
171
135
|
}
|
|
172
136
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
const avg_day2_value = asset_values[instrumentId].day2_value_sum / user_count;
|
|
179
|
-
const priceChangePct = getDailyPriceChange(instrumentId, yesterdayStr, dateStr, priceMap);
|
|
137
|
+
for (const instrumentId of allInstrumentIds) {
|
|
138
|
+
if (!instrumentId) continue;
|
|
139
|
+
|
|
140
|
+
this._initAsset(instrumentId);
|
|
141
|
+
const asset = this.assetData.get(instrumentId);
|
|
180
142
|
|
|
181
|
-
|
|
143
|
+
const yP = yPosMap.get(instrumentId);
|
|
144
|
+
const tP = tPosMap.get(instrumentId);
|
|
182
145
|
|
|
183
|
-
const
|
|
184
|
-
const
|
|
146
|
+
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
147
|
+
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
148
|
+
|
|
149
|
+
// Get sector and initialize it
|
|
150
|
+
const sector = this.mappings.instrumentToSector[instrumentId] || 'Other';
|
|
151
|
+
this._initSector(sector);
|
|
152
|
+
const sectorAsset = this.sectorData.get(sector);
|
|
185
153
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
154
|
+
if (yInvested > 0) {
|
|
155
|
+
const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
|
|
156
|
+
|
|
157
|
+
asset.total_invested_yesterday += yInvested;
|
|
158
|
+
asset.price_change_yesterday += yPriceChange * yInvested;
|
|
159
|
+
|
|
160
|
+
sectorAsset.total_invested_yesterday += yInvested;
|
|
161
|
+
sectorAsset.price_change_yesterday += yPriceChange * yInvested;
|
|
162
|
+
}
|
|
163
|
+
if (tInvested > 0) {
|
|
164
|
+
asset.total_invested_today += tInvested;
|
|
165
|
+
sectorAsset.total_invested_today += tInvested;
|
|
166
|
+
}
|
|
191
167
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
for (const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
_calculateFlow(dataMap) {
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const [key, data] of dataMap.entries()) {
|
|
173
|
+
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
174
|
+
|
|
175
|
+
if (total_invested_yesterday > 0) {
|
|
176
|
+
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
177
|
+
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
178
|
+
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
179
|
+
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
180
|
+
|
|
181
|
+
result[key] = {
|
|
182
|
+
net_flow_percentage: net_flow_percentage,
|
|
183
|
+
total_invested_today: total_invested_today,
|
|
184
|
+
total_invested_yesterday: total_invested_yesterday
|
|
185
|
+
};
|
|
186
|
+
}
|
|
200
187
|
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
201
190
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
191
|
+
async getResult(fetchedDependencies) {
|
|
192
|
+
// Ensure mappings are loaded (can be from context or loaded now)
|
|
193
|
+
if (!this.mappings) {
|
|
194
|
+
this.mappings = await loadInstrumentMappings();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Ensure cohort is calculated at least once
|
|
198
|
+
const smartCohort = this._getSmartCohort(fetchedDependencies);
|
|
199
|
+
|
|
200
|
+
// 1. Calculate Asset Flow
|
|
201
|
+
const assetResult = {};
|
|
202
|
+
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
203
|
+
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
204
|
+
const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
|
|
205
|
+
|
|
206
|
+
if (total_invested_yesterday > 0) {
|
|
207
|
+
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
208
|
+
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
209
|
+
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
210
|
+
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
211
|
+
|
|
212
|
+
assetResult[ticker] = {
|
|
213
|
+
net_flow_percentage: net_flow_percentage,
|
|
214
|
+
total_invested_today: total_invested_today,
|
|
215
|
+
total_invested_yesterday: total_invested_yesterday
|
|
216
|
+
};
|
|
217
|
+
}
|
|
205
218
|
}
|
|
219
|
+
|
|
220
|
+
// 2. Calculate Sector Flow
|
|
221
|
+
const sectorResult = this._calculateFlow(this.sectorData);
|
|
206
222
|
|
|
207
223
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
cohort_size: smartCohort.size,
|
|
225
|
+
assets: assetResult,
|
|
226
|
+
sectors: sectorResult
|
|
211
227
|
};
|
|
212
228
|
}
|
|
213
229
|
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
reset() {
|
|
231
|
+
this.assetData.clear();
|
|
232
|
+
this.sectorData.clear();
|
|
233
|
+
this.mappings = null;
|
|
234
|
+
this.smartCohortUserIds = null;
|
|
235
|
+
}
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
module.exports = SmartCohortFlow;
|