aiden-shared-calculations-unified 1.0.84 → 1.0.87
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/calculations/core/asset-pnl-status.js +36 -106
- package/calculations/core/asset-position-size.js +40 -91
- package/calculations/core/average-daily-pnl-all-users.js +18 -57
- package/calculations/core/average-daily-pnl-per-sector.js +41 -88
- package/calculations/core/average-daily-pnl-per-stock.js +38 -91
- package/calculations/core/average-daily-position-pnl.js +19 -49
- package/calculations/core/holding-duration-per-asset.js +25 -127
- package/calculations/core/instrument-price-change-1d.js +30 -49
- package/calculations/core/instrument-price-momentum-20d.js +50 -60
- package/calculations/core/long-position-per-stock.js +39 -68
- package/calculations/core/overall-holding-duration.js +16 -87
- package/calculations/core/overall-profitability-ratio.js +11 -40
- package/calculations/core/platform-buy-sell-sentiment.js +41 -124
- package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
- package/calculations/core/platform-daily-ownership-delta.js +68 -126
- package/calculations/core/platform-ownership-per-sector.js +45 -96
- package/calculations/core/platform-total-positions-held.js +20 -80
- package/calculations/core/pnl-distribution-per-stock.js +29 -135
- package/calculations/core/price-metrics.js +95 -206
- package/calculations/core/profitability-ratio-per-sector.js +34 -79
- package/calculations/core/profitability-ratio-per-stock.js +32 -88
- package/calculations/core/profitability-skew-per-stock.js +41 -94
- package/calculations/core/profitable-and-unprofitable-status.js +44 -76
- package/calculations/core/sentiment-per-stock.js +24 -77
- package/calculations/core/short-position-per-stock.js +35 -43
- package/calculations/core/social-activity-aggregation.js +26 -49
- package/calculations/core/social-asset-posts-trend.js +38 -94
- package/calculations/core/social-event-correlation.js +26 -93
- package/calculations/core/social-sentiment-aggregation.js +20 -44
- package/calculations/core/social-top-mentioned-words.js +35 -87
- package/calculations/core/social-topic-interest-evolution.js +22 -111
- package/calculations/core/social-topic-sentiment-matrix.js +38 -104
- package/calculations/core/social-word-mentions-trend.js +27 -104
- package/calculations/core/speculator-asset-sentiment.js +31 -72
- package/calculations/core/speculator-danger-zone.js +48 -84
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
- package/calculations/core/speculator-leverage-per-asset.js +25 -64
- package/calculations/core/speculator-leverage-per-sector.js +27 -63
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
- package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
- package/calculations/core/speculator-take-profit-per-asset.js +20 -57
- package/calculations/core/speculator-tsl-per-asset.js +17 -56
- package/calculations/core/total-long-figures.js +16 -31
- package/calculations/core/total-long-per-sector.js +39 -61
- package/calculations/core/total-short-figures.js +13 -32
- package/calculations/core/total-short-per-sector.js +39 -61
- package/calculations/core/users-processed.js +11 -46
- package/calculations/gauss/cohort-capital-flow.js +54 -173
- package/calculations/gauss/cohort-definer.js +77 -163
- package/calculations/gauss/daily-dna-filter.js +29 -83
- package/calculations/gauss/gauss-divergence-signal.js +22 -109
- package/calculations/gem/cohort-momentum-state.js +27 -72
- package/calculations/gem/cohort-skill-definition.js +36 -52
- package/calculations/gem/platform-conviction-divergence.js +18 -60
- package/calculations/gem/quant-skill-alpha-signal.js +25 -98
- package/calculations/gem/skilled-cohort-flow.js +67 -175
- package/calculations/gem/skilled-unskilled-divergence.js +18 -73
- package/calculations/gem/unskilled-cohort-flow.js +64 -172
- package/calculations/helix/helix-contrarian-signal.js +20 -114
- package/calculations/helix/herd-consensus-score.js +42 -124
- package/calculations/helix/winner-loser-flow.js +36 -118
- package/calculations/pyro/risk-appetite-index.js +33 -74
- package/calculations/pyro/squeeze-potential.js +30 -87
- package/calculations/pyro/volatility-signal.js +33 -78
- package/package.json +1 -1
|
@@ -1,42 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1) for total users processed.
|
|
3
|
-
*
|
|
4
|
-
* This metric is a simple counter that tracks the total number
|
|
5
|
-
* of users processed and the breakdown by user type (Normal/Speculator).
|
|
6
|
-
*
|
|
7
|
-
* It is a fundamental metric for sanity-checking the data stream.
|
|
3
|
+
* REFACTORED: Uses context.user.type.
|
|
8
4
|
*/
|
|
9
5
|
class UsersProcessed {
|
|
10
6
|
constructor() {
|
|
11
7
|
this.totalUsers = 0;
|
|
12
8
|
this.normalUsers = 0;
|
|
13
9
|
this.speculatorUsers = 0;
|
|
14
|
-
this.totalValue = 0;
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
/**
|
|
18
|
-
* Statically defines all metadata for the manifest builder.
|
|
19
|
-
*/
|
|
20
12
|
static getMetadata() {
|
|
21
13
|
return {
|
|
22
14
|
type: 'standard',
|
|
23
|
-
rootDataDependencies: ['portfolio'],
|
|
15
|
+
rootDataDependencies: ['portfolio'],
|
|
24
16
|
isHistorical: false,
|
|
25
17
|
userType: 'all',
|
|
26
|
-
category: 'core_metrics'
|
|
18
|
+
category: 'core_metrics'
|
|
27
19
|
};
|
|
28
20
|
}
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
* Statically declare dependencies.
|
|
32
|
-
*/
|
|
33
|
-
static getDependencies() {
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
22
|
+
static getDependencies() { return []; }
|
|
36
23
|
|
|
37
|
-
/**
|
|
38
|
-
* Defines the output schema for this calculation.
|
|
39
|
-
*/
|
|
40
24
|
static getSchema() {
|
|
41
25
|
return {
|
|
42
26
|
"type": "object",
|
|
@@ -44,47 +28,29 @@ class UsersProcessed {
|
|
|
44
28
|
"properties": {
|
|
45
29
|
"total_users": { "type": "number" },
|
|
46
30
|
"normal_users": { "type": "number" },
|
|
47
|
-
"speculator_users": { "type": "number" }
|
|
48
|
-
"total_portfolio_value": { "type": "number" }
|
|
31
|
+
"speculator_users": { "type": "number" }
|
|
49
32
|
},
|
|
50
|
-
"required": ["total_users", "normal_users", "speculator_users"
|
|
33
|
+
"required": ["total_users", "normal_users", "speculator_users"]
|
|
51
34
|
};
|
|
52
35
|
}
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
yesterdayPortfolio,
|
|
58
|
-
userId,
|
|
59
|
-
context,
|
|
60
|
-
todayInsights,
|
|
61
|
-
yesterdayInsights,
|
|
62
|
-
todaySocialPostInsights,
|
|
63
|
-
yesterdaySocialPostInsights,
|
|
64
|
-
todayHistory
|
|
65
|
-
) {
|
|
37
|
+
process(context) {
|
|
38
|
+
const { user } = context;
|
|
39
|
+
|
|
66
40
|
this.totalUsers++;
|
|
67
41
|
|
|
68
|
-
|
|
69
|
-
if (context.userType === 'speculator') {
|
|
42
|
+
if (user.type === 'speculator') {
|
|
70
43
|
this.speculatorUsers++;
|
|
71
44
|
} else {
|
|
72
45
|
this.normalUsers++;
|
|
73
46
|
}
|
|
74
|
-
|
|
75
|
-
// --- UPDATED ---
|
|
76
|
-
const portfolioValue = todayPortfolio?.Summary?.PortfolioValue;
|
|
77
|
-
if (typeof portfolioValue === 'number') {
|
|
78
|
-
this.totalValue += portfolioValue;
|
|
79
|
-
}
|
|
80
47
|
}
|
|
81
48
|
|
|
82
49
|
getResult() {
|
|
83
50
|
return {
|
|
84
51
|
total_users: this.totalUsers,
|
|
85
52
|
normal_users: this.normalUsers,
|
|
86
|
-
speculator_users: this.speculatorUsers
|
|
87
|
-
total_portfolio_value: this.totalValue
|
|
53
|
+
speculator_users: this.speculatorUsers
|
|
88
54
|
};
|
|
89
55
|
}
|
|
90
56
|
|
|
@@ -92,7 +58,6 @@ class UsersProcessed {
|
|
|
92
58
|
this.totalUsers = 0;
|
|
93
59
|
this.normalUsers = 0;
|
|
94
60
|
this.speculatorUsers = 0;
|
|
95
|
-
this.totalValue = 0;
|
|
96
61
|
}
|
|
97
62
|
}
|
|
98
63
|
|
|
@@ -1,42 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview GAUSS Product Line (Pass 3)
|
|
3
|
-
*
|
|
4
|
-
* This 'standard' calculation streams all user portfolios, tags
|
|
5
|
-
* each user with their "Gauss" cohort (from Pass 2), and calculates
|
|
6
|
-
* the price-adjusted capital flow for each cohort, per asset.
|
|
7
|
-
*
|
|
8
|
-
* This is the primary input for the final Pass 4 signal.
|
|
9
|
-
*
|
|
10
|
-
* --- REVISED 11/12/2025 ---
|
|
11
|
-
* - Removed dependency on 'insights' data.
|
|
12
|
-
* - Added dependency on 'instrument-price-change-1d' (Pass 1 meta calc).
|
|
13
|
-
* - Price adjustment logic now uses the reliable 1-day price change
|
|
14
|
-
* from the new dependency.
|
|
15
|
-
* - **FIXED:** getResult() was wrapping the ticker map in an "assets" object,
|
|
16
|
-
* causing a schema validation failure in the test harness.
|
|
17
|
-
* --------------------------
|
|
3
|
+
* REFACTORED: Implemented getResult logic and mapping caching.
|
|
18
4
|
*/
|
|
19
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
20
|
-
|
|
21
|
-
|
|
22
5
|
class CohortCapitalFlow {
|
|
23
6
|
constructor() {
|
|
24
|
-
// { [cohortName]: Map<instrumentId, { flow_data... }> }
|
|
25
7
|
this.cohortFlows = new Map();
|
|
26
|
-
// { [userId]: "cohortName" }
|
|
27
8
|
this.cohortMap = new Map();
|
|
28
|
-
|
|
29
|
-
this.tickerMap = null;
|
|
9
|
+
this.tickerMap = null; // Cache mappings
|
|
30
10
|
this.dependenciesLoaded = false;
|
|
31
|
-
|
|
32
|
-
// --- NEW ---
|
|
33
|
-
// This will store the { [ticker]: { price_change_1d_pct: 5.5 } } map
|
|
34
|
-
this.priceChangeMap = null;
|
|
35
11
|
}
|
|
36
12
|
|
|
37
|
-
/**
|
|
38
|
-
* Defines the output schema for this calculation.
|
|
39
|
-
*/
|
|
40
13
|
static getSchema() {
|
|
41
14
|
const flowSchema = {
|
|
42
15
|
"type": "object",
|
|
@@ -46,156 +19,84 @@ class CohortCapitalFlow {
|
|
|
46
19
|
},
|
|
47
20
|
"required": ["net_flow_percentage", "net_flow_contribution"]
|
|
48
21
|
};
|
|
49
|
-
|
|
50
|
-
const cohortSchema = {
|
|
51
|
-
"type": "object",
|
|
52
|
-
"description": "Price-adjusted capital flow for all assets for a specific cohort.",
|
|
53
|
-
"patternProperties": { "^.*$": flowSchema }, // Ticker
|
|
54
|
-
"additionalProperties": flowSchema
|
|
55
|
-
};
|
|
56
|
-
|
|
57
22
|
return {
|
|
58
23
|
"type": "object",
|
|
59
|
-
"
|
|
60
|
-
"patternProperties": {
|
|
61
|
-
"^.*$": cohortSchema // Cohort Name
|
|
62
|
-
},
|
|
63
|
-
"additionalProperties": cohortSchema
|
|
24
|
+
"patternProperties": { "^.*$": { "type": "object", "patternProperties": { "^.*$": flowSchema } } }
|
|
64
25
|
};
|
|
65
26
|
}
|
|
66
27
|
|
|
67
|
-
/**
|
|
68
|
-
* Statically defines all metadata for the manifest builder.
|
|
69
|
-
*/
|
|
70
28
|
static getMetadata() {
|
|
71
29
|
return {
|
|
72
30
|
type: 'standard',
|
|
73
31
|
rootDataDependencies: ['portfolio', 'history'],
|
|
74
|
-
isHistorical: true,
|
|
32
|
+
isHistorical: true,
|
|
75
33
|
userType: 'all',
|
|
76
34
|
category: 'gauss'
|
|
77
35
|
};
|
|
78
36
|
}
|
|
79
37
|
|
|
80
|
-
/**
|
|
81
|
-
* Statically declare dependencies.
|
|
82
|
-
*/
|
|
83
38
|
static getDependencies() {
|
|
84
|
-
return [
|
|
85
|
-
'cohort-definer', // from gauss (Pass 2)
|
|
86
|
-
'instrument-price-change-1d' // from core (Pass 1)
|
|
87
|
-
];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
_getPortfolioPositions(portfolio) {
|
|
91
|
-
// FIX: Check for Normal User (AggregatedPositions) OR Speculator (PublicPositions)
|
|
92
|
-
if (portfolio?.AggregatedPositions) {
|
|
93
|
-
return portfolio.AggregatedPositions; // Normal User
|
|
94
|
-
}
|
|
95
|
-
if (portfolio?.PublicPositions) {
|
|
96
|
-
return portfolio.PublicPositions; // Speculator User
|
|
97
|
-
}
|
|
98
|
-
return [];
|
|
39
|
+
return ['cohort-definer', 'instrument-price-change-1d'];
|
|
99
40
|
}
|
|
100
41
|
|
|
101
42
|
_initFlowData(cohortName, instrumentId) {
|
|
102
|
-
if (!this.cohortFlows.has(cohortName))
|
|
103
|
-
this.cohortFlows.set(cohortName, new Map());
|
|
104
|
-
}
|
|
43
|
+
if (!this.cohortFlows.has(cohortName)) this.cohortFlows.set(cohortName, new Map());
|
|
105
44
|
if (!this.cohortFlows.get(cohortName).has(instrumentId)) {
|
|
106
45
|
this.cohortFlows.get(cohortName).set(instrumentId, {
|
|
107
|
-
total_invested_yesterday: 0,
|
|
108
|
-
total_invested_today: 0,
|
|
109
|
-
price_change_yesterday: 0, // Weighted sum
|
|
46
|
+
total_invested_yesterday: 0, total_invested_today: 0, price_change_yesterday: 0
|
|
110
47
|
});
|
|
111
48
|
}
|
|
112
49
|
}
|
|
113
50
|
|
|
114
|
-
|
|
115
|
-
* Loads dependencies into memory on the first process() call.
|
|
116
|
-
*/
|
|
117
|
-
_loadDependencies(fetchedDependencies) {
|
|
51
|
+
_loadDependencies(computed) {
|
|
118
52
|
if (this.dependenciesLoaded) return;
|
|
119
|
-
|
|
120
|
-
// 1. Load Cohort Definitions
|
|
121
|
-
const cohortData = fetchedDependencies['cohort-definer'];
|
|
53
|
+
const cohortData = computed['cohort-definer'];
|
|
122
54
|
if (cohortData) {
|
|
123
55
|
for (const [cohortName, userIds] of Object.entries(cohortData)) {
|
|
124
|
-
if (Array.isArray(userIds))
|
|
125
|
-
for (const userId of userIds) {
|
|
126
|
-
this.cohortMap.set(userId, cohortName);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
56
|
+
if (Array.isArray(userIds)) userIds.forEach(uid => this.cohortMap.set(uid, cohortName));
|
|
129
57
|
}
|
|
130
58
|
}
|
|
131
|
-
|
|
132
|
-
// 2. Load Price Change Data
|
|
133
|
-
this.priceChangeMap = fetchedDependencies['instrument-price-change-1d'] || {};
|
|
134
|
-
|
|
135
59
|
this.dependenciesLoaded = true;
|
|
136
60
|
}
|
|
137
61
|
|
|
138
|
-
process(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// --- STANDARD 0: FIXED ---
|
|
142
|
-
if (!this.tickerMap) {
|
|
143
|
-
this.tickerMap = context.instrumentToTicker;
|
|
144
|
-
}
|
|
62
|
+
process(context) {
|
|
63
|
+
const { user, computed, mappings, math } = context;
|
|
64
|
+
const { extract } = math;
|
|
145
65
|
|
|
146
|
-
|
|
147
|
-
if (!
|
|
148
|
-
return; // Not in a defined cohort, skip.
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
66
|
+
// Cache mapping for getResult phase
|
|
67
|
+
if (!this.tickerMap) this.tickerMap = mappings.instrumentToTicker;
|
|
154
68
|
|
|
155
|
-
|
|
156
|
-
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
157
|
-
|
|
158
|
-
if (!yPos || !tPos) {
|
|
159
|
-
return; // Must have AggregatedPositions for both days
|
|
160
|
-
}
|
|
69
|
+
this._loadDependencies(computed);
|
|
161
70
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
71
|
+
const cohortName = this.cohortMap.get(user.id);
|
|
72
|
+
if (!cohortName) return;
|
|
165
73
|
|
|
166
|
-
|
|
167
|
-
if (!
|
|
168
|
-
return; // Cannot map instruments to tickers
|
|
169
|
-
}
|
|
74
|
+
const priceChangeMap = computed['instrument-price-change-1d'];
|
|
75
|
+
if (!priceChangeMap) return;
|
|
170
76
|
|
|
171
|
-
const
|
|
172
|
-
const
|
|
77
|
+
const yPos = extract.getPositions(user.portfolio.yesterday, user.type);
|
|
78
|
+
const tPos = extract.getPositions(user.portfolio.today, user.type);
|
|
79
|
+
|
|
80
|
+
const yPosMap = new Map(yPos.map(p => [extract.getInstrumentId(p), p]));
|
|
81
|
+
const tPosMap = new Map(tPos.map(p => [extract.getInstrumentId(p), p]));
|
|
173
82
|
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
174
83
|
|
|
175
|
-
for (const
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
this._initFlowData(cohortName, instrumentId);
|
|
179
|
-
const asset = this.cohortFlows.get(cohortName).get(instrumentId);
|
|
84
|
+
for (const instId of allInstrumentIds) {
|
|
85
|
+
if (!instId) continue;
|
|
180
86
|
|
|
181
|
-
|
|
182
|
-
const
|
|
87
|
+
this._initFlowData(cohortName, instId);
|
|
88
|
+
const asset = this.cohortFlows.get(cohortName).get(instId);
|
|
183
89
|
|
|
184
|
-
const yInvested =
|
|
185
|
-
const tInvested =
|
|
90
|
+
const yInvested = extract.getPositionWeight(yPosMap.get(instId), user.type);
|
|
91
|
+
const tInvested = extract.getPositionWeight(tPosMap.get(instId), user.type);
|
|
186
92
|
|
|
187
93
|
if (yInvested > 0) {
|
|
188
94
|
asset.total_invested_yesterday += yInvested;
|
|
189
95
|
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
const yPriceChange_pct = (ticker && this.priceChangeMap[ticker])
|
|
193
|
-
? this.priceChangeMap[ticker].price_change_1d_pct
|
|
194
|
-
: 0;
|
|
96
|
+
const ticker = this.tickerMap[instId];
|
|
97
|
+
const yPriceChange_pct = (ticker && priceChangeMap[ticker]) ? priceChangeMap[ticker].change_1d_pct : 0;
|
|
195
98
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
asset.price_change_yesterday += yPriceChange_decimal * yInvested; // Weighted sum
|
|
99
|
+
asset.price_change_yesterday += (yPriceChange_pct / 100.0) * yInvested;
|
|
199
100
|
}
|
|
200
101
|
if (tInvested > 0) {
|
|
201
102
|
asset.total_invested_today += tInvested;
|
|
@@ -203,62 +104,42 @@ class CohortCapitalFlow {
|
|
|
203
104
|
}
|
|
204
105
|
}
|
|
205
106
|
|
|
206
|
-
// --- THIS IS THE FIX ---
|
|
207
107
|
async getResult() {
|
|
208
|
-
// --- STANDARD 0: REMOVED forbidden data load ---
|
|
209
|
-
|
|
210
|
-
// Failsafe check
|
|
211
|
-
if (!this.tickerMap) {
|
|
212
|
-
return {}; // process() must run first to set mappings
|
|
213
|
-
}
|
|
214
|
-
|
|
215
108
|
const finalResult = {};
|
|
109
|
+
|
|
110
|
+
if (!this.tickerMap) return {}; // Should be populated by process()
|
|
216
111
|
|
|
217
|
-
for (const [cohortName,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const ticker = this.tickerMap[
|
|
222
|
-
if (!ticker) continue;
|
|
112
|
+
for (const [cohortName, assetsMap] of this.cohortFlows.entries()) {
|
|
113
|
+
finalResult[cohortName] = {};
|
|
114
|
+
|
|
115
|
+
for (const [instId, data] of assetsMap.entries()) {
|
|
116
|
+
const ticker = this.tickerMap[instId] || `id_${instId}`;
|
|
223
117
|
|
|
224
|
-
|
|
118
|
+
// Logic: Flow = Today - (Yesterday * (1 + PriceChange))
|
|
119
|
+
// This isolates the capital movement from the price action.
|
|
120
|
+
const expectedToday = data.total_invested_yesterday + data.price_change_yesterday;
|
|
121
|
+
const netFlow = data.total_invested_today - expectedToday;
|
|
225
122
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
net_flow_percentage: net_flow_percentage,
|
|
235
|
-
net_flow_contribution: flow_contribution
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
} else if (total_invested_today > 0) {
|
|
239
|
-
cohortAssets[ticker] = {
|
|
240
|
-
net_flow_percentage: Infinity, // Represents pure inflow
|
|
241
|
-
net_flow_contribution: total_invested_today
|
|
242
|
-
};
|
|
243
|
-
}
|
|
123
|
+
// Avoid divide by zero or noise for very small numbers
|
|
124
|
+
const denominator = Math.max(1, (data.total_invested_yesterday + data.total_invested_today) / 2);
|
|
125
|
+
const netFlowPct = (netFlow / denominator) * 100;
|
|
126
|
+
|
|
127
|
+
finalResult[cohortName][ticker] = {
|
|
128
|
+
net_flow_percentage: netFlowPct,
|
|
129
|
+
net_flow_contribution: netFlow
|
|
130
|
+
};
|
|
244
131
|
}
|
|
245
|
-
// Was: finalResult[cohortName] = { assets: cohortAssets };
|
|
246
|
-
// Is:
|
|
247
|
-
finalResult[cohortName] = cohortAssets;
|
|
248
132
|
}
|
|
249
133
|
|
|
250
134
|
return finalResult;
|
|
251
135
|
}
|
|
252
|
-
// --- END FIX ---
|
|
253
136
|
|
|
254
137
|
reset() {
|
|
255
138
|
this.cohortFlows.clear();
|
|
256
139
|
this.cohortMap.clear();
|
|
257
|
-
// --- STANDARD 0: RENAMED ---
|
|
258
140
|
this.tickerMap = null;
|
|
259
141
|
this.dependenciesLoaded = false;
|
|
260
|
-
this.priceChangeMap = null;
|
|
261
142
|
}
|
|
262
143
|
}
|
|
263
144
|
|
|
264
|
-
module.exports = CohortCapitalFlow;
|
|
145
|
+
module.exports = CohortCapitalFlow;
|