aiden-shared-calculations-unified 1.0.95 → 1.0.96
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/capitulation/asset-volatility-estimator.js +1 -2
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +1 -2
- package/calculations/ghost-book/cost-basis-density.js +1 -2
- package/calculations/ghost-book/liquidity-vacuum.js +1 -2
- package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
- package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
- package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
- package/calculations/predicative-alpha/mimetic-latency.js +1 -2
- package/package.json +1 -1
- package/calculations/legacy/activity_by_pnl_status.js +0 -119
- package/calculations/legacy/asset_crowd_flow.js +0 -163
- package/calculations/legacy/capital_deployment_strategy.js +0 -108
- package/calculations/legacy/capital_liquidation_performance.js +0 -139
- package/calculations/legacy/capital_vintage_performance.js +0 -136
- package/calculations/legacy/cash-flow-deployment.js +0 -144
- package/calculations/legacy/cash-flow-liquidation.js +0 -146
- package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
- package/calculations/legacy/crowd_conviction_score.js +0 -261
- package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
- package/calculations/legacy/daily_asset_activity.js +0 -128
- package/calculations/legacy/daily_user_activity_tracker.js +0 -182
- package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
- package/calculations/legacy/diversification_pnl.js +0 -115
- package/calculations/legacy/drawdown_response.js +0 -137
- package/calculations/legacy/dumb-cohort-flow.js +0 -238
- package/calculations/legacy/gain_response.js +0 -137
- package/calculations/legacy/historical_performance_aggregator.js +0 -85
- package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
- package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
- package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/new_allocation_percentage.js +0 -98
- package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
- package/calculations/legacy/position_count_pnl.js +0 -120
- package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/profit_cohort_divergence.js +0 -115
- package/calculations/legacy/profitability_migration.js +0 -104
- package/calculations/legacy/reallocation_increase_percentage.js +0 -104
- package/calculations/legacy/risk_appetite_change.js +0 -97
- package/calculations/legacy/sector_rotation.js +0 -117
- package/calculations/legacy/shark_attack_signal.js +0 -112
- package/calculations/legacy/smart-cohort-flow.js +0 -238
- package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
- package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
- package/calculations/legacy/smart_money_flow.js +0 -198
- package/calculations/legacy/social-predictive-regime-state.js +0 -102
- package/calculations/legacy/social-topic-driver-index.js +0 -147
- package/calculations/legacy/social-topic-predictive-potential.js +0 -461
- package/calculations/legacy/social_flow_correlation.js +0 -112
- package/calculations/legacy/speculator_adjustment_activity.js +0 -103
- package/calculations/legacy/strategy-performance.js +0 -265
- package/calculations/legacy/tsl_effectiveness.js +0 -85
- package/calculations/legacy/user-investment-profile.js +0 -313
- package/calculations/legacy/user_expectancy_score.js +0 -106
- package/calculations/legacy/user_profitability_tracker.js +0 -131
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 3) for "In Profit" cohort asset flow.
|
|
3
|
-
*
|
|
4
|
-
* --- MODIFIED ---
|
|
5
|
-
* Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
|
|
6
|
-
* This calculation now determines "in profit" status internally
|
|
7
|
-
* by reading the *yesterday's* portfolio P&L directly.
|
|
8
|
-
*/
|
|
9
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
10
|
-
|
|
11
|
-
class InProfitAssetCrowdFlow {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.assetData = new Map();
|
|
14
|
-
this.mappings = null;
|
|
15
|
-
// No longer need this:
|
|
16
|
-
// this.inProfitCohorts = null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Defines the output schema for this calculation.
|
|
21
|
-
* @returns {object} JSON Schema object
|
|
22
|
-
*/
|
|
23
|
-
static getSchema() {
|
|
24
|
-
return {
|
|
25
|
-
"type": "object",
|
|
26
|
-
"description": "Calculates net capital flow % (price-adjusted) per asset, but only for the cohort of users currently in profit on that asset.",
|
|
27
|
-
"patternProperties": {
|
|
28
|
-
// Ticker
|
|
29
|
-
"^.*$": {
|
|
30
|
-
"type": "object",
|
|
31
|
-
"description": "Net flow metrics for a specific asset ticker from its 'in profit' cohort.",
|
|
32
|
-
"properties": {
|
|
33
|
-
"net_flow_percentage": {
|
|
34
|
-
"type": "number",
|
|
35
|
-
"description": "Net capital flow % from the 'in profit' cohort, adjusted for price changes."
|
|
36
|
-
},
|
|
37
|
-
"total_invested_today": { "type": "number" },
|
|
38
|
-
"total_invested_yesterday": { "type": "number" },
|
|
39
|
-
"cohort_size": { "type": "number" }
|
|
40
|
-
},
|
|
41
|
-
"required": ["net_flow_percentage", "total_invested_today", "total_invested_yesterday", "cohort_size"]
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"additionalProperties": {
|
|
45
|
-
"type": "object",
|
|
46
|
-
"properties": {
|
|
47
|
-
"net_flow_percentage": { "type": "number" },
|
|
48
|
-
"total_invested_today": { "type": "number" },
|
|
49
|
-
"total_invested_yesterday": { "type": "number" },
|
|
50
|
-
"cohort_size": { "type": "number" }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Statically declare dependencies.
|
|
58
|
-
* --- MODIFIED ---
|
|
59
|
-
* Removed 'asset_pnl-status'
|
|
60
|
-
*/
|
|
61
|
-
static getDependencies() {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
_getPortfolioPositions(portfolio) {
|
|
66
|
-
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
_initAsset(instrumentId) {
|
|
70
|
-
if (!this.assetData.has(instrumentId)) {
|
|
71
|
-
this.assetData.set(instrumentId, {
|
|
72
|
-
total_invested_yesterday: 0,
|
|
73
|
-
total_invested_today: 0,
|
|
74
|
-
price_change_yesterday: 0,
|
|
75
|
-
cohort: new Set()
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// --- MODIFIED ---
|
|
81
|
-
// Removed _getInProfitCohorts helper
|
|
82
|
-
|
|
83
|
-
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
84
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (!this.mappings) {
|
|
89
|
-
this.mappings = context.mappings;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
93
|
-
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
94
|
-
|
|
95
|
-
const yPosMap = new Map(yPos?.map(p => [p.InstrumentID, p]) || []);
|
|
96
|
-
const tPosMap = new Map(tPos?.map(p => [p.InstrumentID, p]) || []);
|
|
97
|
-
|
|
98
|
-
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
99
|
-
|
|
100
|
-
for (const instrumentId of allInstrumentIds) {
|
|
101
|
-
if (!instrumentId) continue;
|
|
102
|
-
|
|
103
|
-
const yP = yPosMap.get(instrumentId);
|
|
104
|
-
const tP = tPosMap.get(instrumentId);
|
|
105
|
-
|
|
106
|
-
// --- MODIFIED ---
|
|
107
|
-
// Check P&L from YESTERDAY's position to define cohort
|
|
108
|
-
const yPnl = yP?.NetProfit || 0;
|
|
109
|
-
if (yPnl <= 0) {
|
|
110
|
-
continue; // User was not in profit yesterday, skip.
|
|
111
|
-
}
|
|
112
|
-
// --- END MODIFIED ---
|
|
113
|
-
|
|
114
|
-
// User *is* in the cohort, process their data
|
|
115
|
-
this._initAsset(instrumentId);
|
|
116
|
-
const asset = this.assetData.get(instrumentId);
|
|
117
|
-
asset.cohort.add(userId); // Track cohort size
|
|
118
|
-
|
|
119
|
-
const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
|
|
120
|
-
const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
|
|
121
|
-
|
|
122
|
-
if (yInvested > 0) {
|
|
123
|
-
asset.total_invested_yesterday += yInvested;
|
|
124
|
-
const yPriceChange = (yP?.PipsRate || 0) / (yP?.OpenRate || 1);
|
|
125
|
-
asset.price_change_yesterday += yPriceChange * yInvested;
|
|
126
|
-
}
|
|
127
|
-
if (tInvested > 0) {
|
|
128
|
-
asset.total_invested_today += tInvested;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async getResult() {
|
|
134
|
-
if (!this.mappings) {
|
|
135
|
-
this.mappings = await loadInstrumentMappings();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const result = {};
|
|
139
|
-
|
|
140
|
-
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
141
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
142
|
-
|
|
143
|
-
const { total_invested_yesterday, total_invested_today, price_change_yesterday, cohort } = data;
|
|
144
|
-
|
|
145
|
-
if (total_invested_yesterday > 0) {
|
|
146
|
-
const avg_price_change_pct = price_change_yesterday / total_invested_yesterday;
|
|
147
|
-
const price_contribution = total_invested_yesterday * avg_price_change_pct;
|
|
148
|
-
const flow_contribution = total_invested_today - (total_invested_yesterday + price_contribution);
|
|
149
|
-
const net_flow_percentage = (flow_contribution / total_invested_yesterday) * 100;
|
|
150
|
-
|
|
151
|
-
result[ticker] = {
|
|
152
|
-
net_flow_percentage: net_flow_percentage,
|
|
153
|
-
total_invested_today: total_invested_today,
|
|
154
|
-
total_invested_yesterday: total_invested_yesterday,
|
|
155
|
-
cohort_size: cohort.size
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return result;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
reset() {
|
|
163
|
-
this.assetData.clear();
|
|
164
|
-
this.mappings = null;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
module.exports = InProfitAssetCrowdFlow;
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 4) for negative expectancy cohort flow.
|
|
3
|
-
*
|
|
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
|
-
*/
|
|
10
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
11
|
-
|
|
12
|
-
class NegativeExpectancyCohortFlow {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.assetData = new Map();
|
|
15
|
-
this.sectorData = new Map();
|
|
16
|
-
this.mappings = null;
|
|
17
|
-
this.negExpCohortUserIds = null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Defines the output schema for this calculation.
|
|
22
|
-
* @returns {object} JSON Schema object
|
|
23
|
-
*/
|
|
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 '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
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Statically declare dependencies.
|
|
62
|
-
*/
|
|
63
|
-
static getDependencies() {
|
|
64
|
-
return ['user_expectancy_score']; // Pass 3
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
_getPortfolioPositions(portfolio) {
|
|
68
|
-
return portfolio?.PublicPositions || portfolio?.AggregatedPositions;
|
|
69
|
-
}
|
|
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
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
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
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
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);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return this.negExpCohortUserIds;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
112
|
-
const cohort = this._getNegExpCohort(fetchedDependencies);
|
|
113
|
-
|
|
114
|
-
if (!cohort.has(userId)) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const yPos = this._getPortfolioPositions(yesterdayPortfolio);
|
|
123
|
-
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
124
|
-
|
|
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()]);
|
|
129
|
-
|
|
130
|
-
if (!this.mappings) {
|
|
131
|
-
this.mappings = context.mappings;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
for (const instrumentId of allInstrumentIds) {
|
|
135
|
-
if (!instrumentId) continue;
|
|
136
|
-
|
|
137
|
-
this._initAsset(instrumentId);
|
|
138
|
-
const asset = this.assetData.get(instrumentId);
|
|
139
|
-
|
|
140
|
-
const yP = yPosMap.get(instrumentId);
|
|
141
|
-
const tP = tPosMap.get(instrumentId);
|
|
142
|
-
|
|
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);
|
|
149
|
-
|
|
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
|
-
}
|
|
163
|
-
}
|
|
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
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return result;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async getResult(fetchedDependencies) {
|
|
188
|
-
if (!this.mappings) {
|
|
189
|
-
this.mappings = await loadInstrumentMappings();
|
|
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);
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
cohort_size: cohort.size,
|
|
219
|
-
assets: assetResult,
|
|
220
|
-
sectors: sectorResult
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
reset() {
|
|
225
|
-
this.assetData.clear();
|
|
226
|
-
this.sectorData.clear();
|
|
227
|
-
this.mappings = null;
|
|
228
|
-
this.negExpCohortUserIds = null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
module.exports = NegativeExpectancyCohortFlow;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for new allocation percentage.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "On average, what percentage of a user's
|
|
5
|
-
* portfolio was allocated to *new* positions today?"
|
|
6
|
-
*
|
|
7
|
-
* This measures the flow of capital into new ideas vs. existing ones.
|
|
8
|
-
*/
|
|
9
|
-
class NewAllocationPercentage {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.total_new_allocation_pct = 0;
|
|
12
|
-
this.users_with_new_positions = 0;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Defines the output schema for this calculation.
|
|
17
|
-
* @returns {object} JSON Schema object
|
|
18
|
-
*/
|
|
19
|
-
static getSchema() {
|
|
20
|
-
return {
|
|
21
|
-
"type": "object",
|
|
22
|
-
"description": "Calculates the average portfolio percentage allocated to *new* positions today.",
|
|
23
|
-
"properties": {
|
|
24
|
-
"average_new_allocation_pct": {
|
|
25
|
-
"type": "number",
|
|
26
|
-
"description": "The average percentage of a portfolio allocated to new positions, for users who opened new positions."
|
|
27
|
-
},
|
|
28
|
-
"total_new_allocation_pct_sum": {
|
|
29
|
-
"type": "number",
|
|
30
|
-
"description": "The sum of all new allocation percentages."
|
|
31
|
-
},
|
|
32
|
-
"user_count_with_new_positions": {
|
|
33
|
-
"type": "number",
|
|
34
|
-
"description": "The count of users who opened at least one new position."
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
"required": ["average_new_allocation_pct", "total_new_allocation_pct_sum", "user_count_with_new_positions"]
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
_getPortfolioPositionMap(portfolio) {
|
|
42
|
-
// We MUST use AggregatedPositions for the 'Invested' %
|
|
43
|
-
const positions = portfolio?.AggregatedPositions;
|
|
44
|
-
if (!positions || !Array.isArray(positions)) {
|
|
45
|
-
return new Map();
|
|
46
|
-
}
|
|
47
|
-
// Map<InstrumentID, { invested: number }>
|
|
48
|
-
return new Map(positions.map(p => [p.InstrumentID, {
|
|
49
|
-
invested: p.InvestedAmount || p.Invested || 0
|
|
50
|
-
}]));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
process(todayPortfolio, yesterdayPortfolio) {
|
|
54
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const yPosMap = this._getPortfolioPositionMap(yesterdayPortfolio);
|
|
59
|
-
const tPosMap = this._getPortfolioPositionMap(todayPortfolio);
|
|
60
|
-
|
|
61
|
-
if (tPosMap.size === 0) {
|
|
62
|
-
return; // No positions today
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let userNewAllocationPct = 0;
|
|
66
|
-
|
|
67
|
-
for (const [tId, tPosData] of tPosMap.entries()) {
|
|
68
|
-
// Check if this position is new (in today but not yesterday)
|
|
69
|
-
if (!yPosMap.has(tId)) {
|
|
70
|
-
userNewAllocationPct += tPosData.invested;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (userNewAllocationPct > 0) {
|
|
75
|
-
this.total_new_allocation_pct += userNewAllocationPct;
|
|
76
|
-
this.users_with_new_positions++;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getResult() {
|
|
81
|
-
const avg_pct = (this.users_with_new_positions > 0)
|
|
82
|
-
? (this.total_new_allocation_pct / this.users_with_new_positions)
|
|
83
|
-
: 0;
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
average_new_allocation_pct: avg_pct,
|
|
87
|
-
total_new_allocation_pct_sum: this.total_new_allocation_pct,
|
|
88
|
-
user_count_with_new_positions: this.users_with_new_positions
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
reset() {
|
|
93
|
-
this.total_new_allocation_pct = 0;
|
|
94
|
-
this.users_with_new_positions = 0;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
module.exports = NewAllocationPercentage;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for Paper vs Diamond Hands.
|
|
3
|
-
*
|
|
4
|
-
* This metric provides a simple ratio of positions that were
|
|
5
|
-
* closed today ("paper hands") vs. positions that were held
|
|
6
|
-
* ("diamond hands").
|
|
7
|
-
*
|
|
8
|
-
* It gives a general sense of market turnover.
|
|
9
|
-
*/
|
|
10
|
-
class PaperVsDiamondHands {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.paper_hands = 0; // Positions closed
|
|
13
|
-
this.diamond_hands = 0; // Positions held
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Defines the output schema for this calculation.
|
|
18
|
-
* @returns {object} JSON Schema object
|
|
19
|
-
*/
|
|
20
|
-
static getSchema() {
|
|
21
|
-
return {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"description": "Calculates the ratio of positions closed ('paper hands') vs. held ('diamond hands') from yesterday to today.",
|
|
24
|
-
"properties": {
|
|
25
|
-
"paper_hands_count": {
|
|
26
|
-
"type": "number",
|
|
27
|
-
"description": "Total count of positions that existed yesterday but were closed today."
|
|
28
|
-
},
|
|
29
|
-
"diamond_hands_count": {
|
|
30
|
-
"type": "number",
|
|
31
|
-
"description": "Total count of positions that existed yesterday and are still held today."
|
|
32
|
-
},
|
|
33
|
-
"total_positions_yesterday": {
|
|
34
|
-
"type": "number",
|
|
35
|
-
"description": "The sum of paper and diamond hands counts."
|
|
36
|
-
},
|
|
37
|
-
"paper_hands_ratio": {
|
|
38
|
-
"type": "number",
|
|
39
|
-
"description": "Ratio of paper hands to diamond hands (Paper / Diamond). Null if no diamond hands."
|
|
40
|
-
},
|
|
41
|
-
"paper_hands_pct": {
|
|
42
|
-
"type": "number",
|
|
43
|
-
"description": "Percentage of positions that were 'paper handed' (closed)."
|
|
44
|
-
},
|
|
45
|
-
"diamond_hands_pct": {
|
|
46
|
-
"type": "number",
|
|
47
|
-
"description": "Percentage of positions that were 'diamond handed' (held)."
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"required": ["paper_hands_count", "diamond_hands_count", "total_positions_yesterday", "paper_hands_pct", "diamond_hands_pct"]
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
_getPortfolioPositionIds(portfolio) {
|
|
55
|
-
// We MUST use PositionID to track specific trades
|
|
56
|
-
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
57
|
-
if (!positions || !Array.isArray(positions)) {
|
|
58
|
-
return new Set();
|
|
59
|
-
}
|
|
60
|
-
return new Set(positions.map(p => p.PositionID).filter(Boolean));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
process(todayPortfolio, yesterdayPortfolio) {
|
|
64
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const yPosIds = this._getPortfolioPositionIds(yesterdayPortfolio);
|
|
69
|
-
const tPosIds = this._getPortfolioPositionIds(todayPortfolio);
|
|
70
|
-
|
|
71
|
-
if (yPosIds.size === 0) {
|
|
72
|
-
return; // No positions yesterday to analyze
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
for (const yPosId of yPosIds) {
|
|
76
|
-
if (tPosIds.has(yPosId)) {
|
|
77
|
-
// Position was held
|
|
78
|
-
this.diamond_hands++;
|
|
79
|
-
} else {
|
|
80
|
-
// Position was closed
|
|
81
|
-
this.paper_hands++;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
getResult() {
|
|
87
|
-
const total = this.paper_hands + this.diamond_hands;
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
paper_hands_count: this.paper_hands,
|
|
91
|
-
diamond_hands_count: this.diamond_hands,
|
|
92
|
-
total_positions_yesterday: total,
|
|
93
|
-
// Ratio of paper-to-diamond. Can be null if diamond_hands is 0.
|
|
94
|
-
paper_hands_ratio: (this.diamond_hands > 0) ? (this.paper_hands / this.diamond_hands) : null,
|
|
95
|
-
// Percentage of total positions
|
|
96
|
-
paper_hands_pct: (total > 0) ? (this.paper_hands / total) * 100 : 0,
|
|
97
|
-
diamond_hands_pct: (total > 0) ? (this.diamond_hands / total) * 100 : 0,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
reset() {
|
|
102
|
-
this.paper_hands = 0;
|
|
103
|
-
this.diamond_hands = 0;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = PaperVsDiamondHands;
|