aiden-shared-calculations-unified 1.0.94 → 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/gauss/cohort-definer.js +24 -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,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for crowd conviction.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the 'Crowd Conviction Score'
|
|
5
|
-
* for each instrument?"
|
|
6
|
-
*
|
|
7
|
-
* It's based on factors like:
|
|
8
|
-
* 1. Holding Duration (longer = more conviction)
|
|
9
|
-
* 2. P&L % (positive = more conviction)
|
|
10
|
-
* 3. Risk/Reward Ratio (higher = more conviction)
|
|
11
|
-
* 4. Leverage (lower = more conviction)
|
|
12
|
-
*
|
|
13
|
-
* This is a *stateful* calculation that computes a 30-day
|
|
14
|
-
* rolling average of these metrics to build the score.
|
|
15
|
-
*/
|
|
16
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
17
|
-
|
|
18
|
-
class CrowdConvictionScore {
|
|
19
|
-
constructor() {
|
|
20
|
-
// { [instrumentId]: { metrics: [], history: [] } }
|
|
21
|
-
this.assetData = new Map();
|
|
22
|
-
this.mappings = null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Defines the output schema for this calculation.
|
|
27
|
-
* @returns {object} JSON Schema object
|
|
28
|
-
*/
|
|
29
|
-
static getSchema() {
|
|
30
|
-
const historyItemSchema = {
|
|
31
|
-
"type": "object",
|
|
32
|
-
"properties": {
|
|
33
|
-
"avg_holding_duration": { "type": "number" },
|
|
34
|
-
"avg_pnl": { "type": "number" },
|
|
35
|
-
"avg_rr": { "type": "number" },
|
|
36
|
-
"avg_leverage": { "type": "number" }
|
|
37
|
-
},
|
|
38
|
-
"required": ["avg_holding_duration", "avg_pnl", "avg_rr", "avg_leverage"]
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const tickerSchema = {
|
|
42
|
-
"type": "object",
|
|
43
|
-
"description": "Crowd conviction score and its components for a specific asset.",
|
|
44
|
-
"properties": {
|
|
45
|
-
"conviction_score": {
|
|
46
|
-
"type": "number",
|
|
47
|
-
"description": "Final conviction score (0-100), based on 30-day rolling averages."
|
|
48
|
-
},
|
|
49
|
-
"roll_avg_holding_duration": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
"description": "30-day rolling average of holding duration (hours)."
|
|
52
|
-
},
|
|
53
|
-
"roll_avg_pnl": {
|
|
54
|
-
"type": "number",
|
|
55
|
-
"description": "30-day rolling average of P&L percentage."
|
|
56
|
-
},
|
|
57
|
-
"roll_avg_rr": {
|
|
58
|
-
"type": "number",
|
|
59
|
-
"description": "30-day rolling average of risk/reward ratio."
|
|
60
|
-
},
|
|
61
|
-
"roll_avg_leverage": {
|
|
62
|
-
"type": "number",
|
|
63
|
-
"description": "30-day rolling average of leverage."
|
|
64
|
-
},
|
|
65
|
-
"avg_holding_duration": {
|
|
66
|
-
"type": "number",
|
|
67
|
-
"description": "Today's average holding duration."
|
|
68
|
-
},
|
|
69
|
-
"avg_pnl": {
|
|
70
|
-
"type": "number",
|
|
71
|
-
"description": "Today's average P&L percentage."
|
|
72
|
-
},
|
|
73
|
-
"avg_rr": {
|
|
74
|
-
"type": "number",
|
|
75
|
-
"description": "Today's average risk/reward ratio."
|
|
76
|
-
},
|
|
77
|
-
"avg_leverage": {
|
|
78
|
-
"type": "number",
|
|
79
|
-
"description": "Today's average leverage."
|
|
80
|
-
},
|
|
81
|
-
"history": {
|
|
82
|
-
"type": "array",
|
|
83
|
-
"description": "30-day history of daily average metrics.",
|
|
84
|
-
"items": historyItemSchema
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"required": [
|
|
88
|
-
"conviction_score", "roll_avg_holding_duration", "roll_avg_pnl",
|
|
89
|
-
"roll_avg_rr", "roll_avg_leverage", "avg_holding_duration",
|
|
90
|
-
"avg_pnl", "avg_rr", "avg_leverage", "history"
|
|
91
|
-
]
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
"type": "object",
|
|
96
|
-
"description": "Calculates a 30-day rolling 'Crowd Conviction Score' (0-100) for each asset.",
|
|
97
|
-
"patternProperties": {
|
|
98
|
-
"^.*$": tickerSchema // Ticker
|
|
99
|
-
},
|
|
100
|
-
"additionalProperties": tickerSchema
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_initAsset(instrumentId) {
|
|
105
|
-
if (!this.assetData.has(instrumentId)) {
|
|
106
|
-
// metrics: Stores *today's* raw values before averaging
|
|
107
|
-
// history: Stores *yesterday's* 30-day history
|
|
108
|
-
this.assetData.set(instrumentId, {
|
|
109
|
-
metrics: [],
|
|
110
|
-
history: []
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
_getHoldingDurationHours(openDateStr) {
|
|
116
|
-
if (!openDateStr) return 0;
|
|
117
|
-
try {
|
|
118
|
-
const openDate = new Date(openDateStr);
|
|
119
|
-
const diffMs = new Date().getTime() - openDate.getTime();
|
|
120
|
-
return diffMs / (1000 * 60 * 60);
|
|
121
|
-
} catch (e) {
|
|
122
|
-
return 0;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Simple min-max normalization helper
|
|
127
|
-
_normalize(value, min, max) {
|
|
128
|
-
if (max === min) return 0.5; // Avoid division by zero
|
|
129
|
-
const normalized = (value - min) / (max - min);
|
|
130
|
-
return Math.max(0, Math.min(1, normalized)); // Clamp between 0 and 1
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
process(portfolioData, yesterdayPortfolio, userId, context) {
|
|
134
|
-
// This score is based on speculator actions
|
|
135
|
-
if (portfolioData?.context?.userType !== 'speculator') {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 1. Get this metric's history from yesterday (pre-loaded)
|
|
140
|
-
if (this.assetData.size === 0) { // Only run once
|
|
141
|
-
const yHistoryData = context.yesterdaysDependencyData['crowd_conviction_score'];
|
|
142
|
-
if (yHistoryData) {
|
|
143
|
-
if (!this.mappings) {
|
|
144
|
-
// We need mappings to convert *yesterday's* tickers back to IDs
|
|
145
|
-
this.mappings = context.mappings;
|
|
146
|
-
}
|
|
147
|
-
for (const [ticker, data] of Object.entries(yHistoryData)) {
|
|
148
|
-
const instrumentId = this.mappings.tickerToInstrument[ticker];
|
|
149
|
-
if (instrumentId) {
|
|
150
|
-
this._initAsset(instrumentId);
|
|
151
|
-
this.assetData.get(instrumentId).history = data.history || [];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const positions = portfolioData.PublicPositions;
|
|
158
|
-
if (!positions || !Array.isArray(positions)) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
for (const pos of positions) {
|
|
163
|
-
const instrumentId = pos.InstrumentID;
|
|
164
|
-
if (!instrumentId) continue;
|
|
165
|
-
|
|
166
|
-
this._initAsset(instrumentId);
|
|
167
|
-
|
|
168
|
-
const pnl_percent = (pos.NetProfit || 0) / (pos.Amount || 1);
|
|
169
|
-
const holding_duration = this._getHoldingDurationHours(pos.OpenDateTime);
|
|
170
|
-
const leverage = pos.Leverage || 1;
|
|
171
|
-
|
|
172
|
-
const sl_rate = pos.StopLossRate || 0;
|
|
173
|
-
const tp_rate = pos.TakeProfitRate || 0;
|
|
174
|
-
const open_rate = pos.OpenRate || 1;
|
|
175
|
-
|
|
176
|
-
let rr_ratio = 0;
|
|
177
|
-
if (sl_rate > 0 && tp_rate > 0) {
|
|
178
|
-
const risk = Math.abs(open_rate - sl_rate);
|
|
179
|
-
const reward = Math.abs(tp_rate - open_rate);
|
|
180
|
-
if (risk > 0) {
|
|
181
|
-
rr_ratio = reward / risk;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this.assetData.get(instrumentId).metrics.push({
|
|
186
|
-
holding_duration,
|
|
187
|
-
pnl_percent,
|
|
188
|
-
rr_ratio,
|
|
189
|
-
leverage
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async getResult() {
|
|
195
|
-
if (!this.mappings) {
|
|
196
|
-
this.mappings = await loadInstrumentMappings();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const result = {};
|
|
200
|
-
for (const [instrumentId, data] of this.assetData.entries()) {
|
|
201
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
202
|
-
const metrics = data.metrics;
|
|
203
|
-
const yHistory = data.history; // Yesterday's 30-day history
|
|
204
|
-
|
|
205
|
-
if (metrics.length === 0) continue;
|
|
206
|
-
|
|
207
|
-
// 1. Calculate today's averages
|
|
208
|
-
const count = metrics.length;
|
|
209
|
-
const avg_holding_duration = metrics.reduce((s, m) => s + m.holding_duration, 0) / count;
|
|
210
|
-
const avg_pnl = metrics.reduce((s, m) => s + m.pnl_percent, 0) / count;
|
|
211
|
-
const avg_rr = metrics.reduce((s, m) => s + m.rr_ratio, 0) / count;
|
|
212
|
-
const avg_leverage = metrics.reduce((s, m) => s + m.leverage, 0) / count;
|
|
213
|
-
|
|
214
|
-
const todayMetrics = { avg_holding_duration, avg_pnl, avg_rr, avg_leverage };
|
|
215
|
-
|
|
216
|
-
// 2. Create new 30-day history
|
|
217
|
-
const newHistory = [todayMetrics, ...yHistory].slice(0, 30);
|
|
218
|
-
|
|
219
|
-
// 3. Calculate 30-day rolling averages
|
|
220
|
-
const historyCount = newHistory.length;
|
|
221
|
-
const roll_avg_duration = newHistory.reduce((s, m) => s + m.avg_holding_duration, 0) / historyCount;
|
|
222
|
-
const roll_avg_pnl = newHistory.reduce((s, m) => s + m.avg_pnl, 0) / historyCount;
|
|
223
|
-
const roll_avg_rr = newHistory.reduce((s, m) => s + m.avg_rr, 0) / historyCount;
|
|
224
|
-
const roll_avg_leverage = newHistory.reduce((s, m) => s + m.avg_leverage, 0) / historyCount;
|
|
225
|
-
|
|
226
|
-
// 4. Calculate Conviction Score (example normalization)
|
|
227
|
-
// (Assumes a 0-1 range for normalization, then scales to 0-100)
|
|
228
|
-
const norm_duration = this._normalize(roll_avg_duration, 0, 240); // 0-10 days
|
|
229
|
-
const norm_pnl = this._normalize(roll_avg_pnl, -0.5, 0.5); // -50% to +50%
|
|
230
|
-
const norm_rr = this._normalize(roll_avg_rr, 0, 3); // 0 to 3 R:R
|
|
231
|
-
const norm_leverage = 1 - this._normalize(roll_avg_leverage, 1, 10); // 1x to 10x (inverted)
|
|
232
|
-
|
|
233
|
-
// Combine scores (equal weight for this example)
|
|
234
|
-
const score_pct = (norm_duration + norm_pnl + norm_rr + norm_leverage) / 4;
|
|
235
|
-
|
|
236
|
-
result[ticker] = {
|
|
237
|
-
conviction_score: score_pct * 100, // Final score 0-100
|
|
238
|
-
roll_avg_holding_duration,
|
|
239
|
-
roll_avg_pnl,
|
|
240
|
-
roll_avg_rr,
|
|
241
|
-
roll_avg_leverage,
|
|
242
|
-
|
|
243
|
-
// Also include today's raw averages
|
|
244
|
-
avg_holding_duration,
|
|
245
|
-
avg_pnl,
|
|
246
|
-
avg_rr,
|
|
247
|
-
avg_leverage,
|
|
248
|
-
|
|
249
|
-
history: newHistory
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
reset() {
|
|
256
|
-
this.assetData.clear();
|
|
257
|
-
this.mappings = null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
module.exports = CrowdConvictionScore;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for crowd sharpe ratio proxy.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the crowd's risk-adjusted return
|
|
5
|
-
* (Sharpe Ratio proxy) for each asset?"
|
|
6
|
-
*
|
|
7
|
-
* It uses the distribution of P&L from 'pnl_distribution_per_stock'
|
|
8
|
-
* to calculate variance (risk).
|
|
9
|
-
*
|
|
10
|
-
* --- FIX: 2025-11-12 ---
|
|
11
|
-
* Refactored this file to be a "meta" calculation.
|
|
12
|
-
* 1. Removed constructor, getResult, reset, and the no-op 'process'.
|
|
13
|
-
* 2. Added the required `async process(dStr, deps, config, fetchedDeps)` method.
|
|
14
|
-
* 3. Moved all logic into `process`.
|
|
15
|
-
* 4. Updated logic to read from `fetchedDeps['pnl_distribution_per_stock']`.
|
|
16
|
-
* 5. Updated data access to read from the new `data.stats` object
|
|
17
|
-
* provided by the fixed dependency.
|
|
18
|
-
*/
|
|
19
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
20
|
-
|
|
21
|
-
class CrowdSharpeRatioProxy {
|
|
22
|
-
/**
|
|
23
|
-
* Defines the output schema for this calculation.
|
|
24
|
-
* @returns {object} JSON Schema object
|
|
25
|
-
*/
|
|
26
|
-
static getSchema() {
|
|
27
|
-
const tickerSchema = {
|
|
28
|
-
"type": "object",
|
|
29
|
-
"description": "Sharpe Ratio proxy metrics for a specific asset.",
|
|
30
|
-
"properties": {
|
|
31
|
-
"sharpe_ratio_proxy": {
|
|
32
|
-
"type": "number",
|
|
33
|
-
"description": "Risk-adjusted return proxy (Mean P&L / StdDev P&L). Assumes risk-free rate is 0."
|
|
34
|
-
},
|
|
35
|
-
"average_pnl": { "type": "number" },
|
|
36
|
-
"std_dev_pnl": { "type": "number" },
|
|
37
|
-
"variance_pnl": { "type": "number" },
|
|
38
|
-
"position_count": { "type": "number" }
|
|
39
|
-
},
|
|
40
|
-
"required": ["sharpe_ratio_proxy", "average_pnl", "std_dev_pnl", "position_count"]
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
"type": "object",
|
|
45
|
-
"description": "Calculates a risk-adjusted return (Sharpe Ratio proxy) for each asset based on P&L distribution.",
|
|
46
|
-
"patternProperties": {
|
|
47
|
-
"^.*$": tickerSchema // Matches any string key (ticker)
|
|
48
|
-
},
|
|
49
|
-
"additionalProperties": tickerSchema
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Statically declare dependencies.
|
|
55
|
-
*/
|
|
56
|
-
static getDependencies() {
|
|
57
|
-
return [
|
|
58
|
-
'pnl_distribution_per_stock' // Pass 1
|
|
59
|
-
];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* --- FIX: This is the new main execution method for meta-calcs ---
|
|
64
|
-
* It receives all dependencies from the orchestrator.
|
|
65
|
-
*/
|
|
66
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
67
|
-
// --- FIX: Load dependency data from the argument ---
|
|
68
|
-
const pnlDistData = fetchedDependencies['pnl_distribution_per_stock'];
|
|
69
|
-
|
|
70
|
-
if (!pnlDistData) {
|
|
71
|
-
dependencies.logger.log('WARN', `[crowd_sharpe_ratio_proxy] Missing dependency 'pnl_distribution_per_stock' for ${dateStr}. Skipping.`);
|
|
72
|
-
return {};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// --- FIX: Load mappings inside the process method ---
|
|
76
|
-
const mappings = await loadInstrumentMappings();
|
|
77
|
-
if (!mappings || !mappings.instrumentToTicker) {
|
|
78
|
-
dependencies.logger.log('ERROR', `[crowd_sharpe_ratio_proxy] Failed to load instrument mappings.`);
|
|
79
|
-
return {};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const result = {};
|
|
83
|
-
|
|
84
|
-
for (const [ticker, data] of Object.entries(pnlDistData)) {
|
|
85
|
-
|
|
86
|
-
// --- FIX: Read from the new 'stats' sub-object ---
|
|
87
|
-
if (!data.stats) {
|
|
88
|
-
continue; // Skip if data is malformed
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const { sum, sumSq, count } = data.stats;
|
|
92
|
-
|
|
93
|
-
if (count < 2) {
|
|
94
|
-
continue; // Need at least 2 data points for variance
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Calculate Mean (Average P&L)
|
|
98
|
-
const mean = sum / count;
|
|
99
|
-
|
|
100
|
-
// Calculate Variance
|
|
101
|
-
// Var(X) = E[X^2] - (E[X])^2
|
|
102
|
-
const meanSq = sumSq / count;
|
|
103
|
-
const variance = meanSq - (mean * mean);
|
|
104
|
-
|
|
105
|
-
// Handle potential float precision errors
|
|
106
|
-
if (variance < 0) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Calculate Standard Deviation (Risk)
|
|
111
|
-
const stdDev = Math.sqrt(variance);
|
|
112
|
-
|
|
113
|
-
if (stdDev === 0) {
|
|
114
|
-
continue; // No risk, Sharpe ratio is infinite/undefined
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Calculate Sharpe Ratio Proxy (assuming risk-free rate = 0)
|
|
118
|
-
// Sharpe = Mean(Return) / StdDev(Return)
|
|
119
|
-
const sharpeProxy = mean / stdDev;
|
|
120
|
-
|
|
121
|
-
// --- FIX: Data is already keyed by ticker, no mapping needed ---
|
|
122
|
-
// const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
123
|
-
|
|
124
|
-
result[ticker] = {
|
|
125
|
-
sharpe_ratio_proxy: sharpeProxy,
|
|
126
|
-
average_pnl: mean,
|
|
127
|
-
std_dev_pnl: stdDev,
|
|
128
|
-
variance_pnl: variance,
|
|
129
|
-
position_count: count
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
module.exports = CrowdSharpeRatioProxy;
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tracks the flow of unique users opening or closing positions
|
|
3
|
-
* on a per-asset basis. This measures the "focus" of the crowd's activity.
|
|
4
|
-
*/
|
|
5
|
-
const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
|
|
6
|
-
|
|
7
|
-
class DailyAssetActivity {
|
|
8
|
-
constructor() {
|
|
9
|
-
// We will store { [instrumentId]: { new_users: Set(), closed_users: Set() } }
|
|
10
|
-
this.assetActivity = new Map();
|
|
11
|
-
this.mappings = null;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Defines the output schema for this calculation.
|
|
16
|
-
* @returns {object} JSON Schema object
|
|
17
|
-
*/
|
|
18
|
-
static getSchema() {
|
|
19
|
-
return {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"description": "Tracks the net flow of unique users opening or closing positions per asset.",
|
|
22
|
-
"patternProperties": {
|
|
23
|
-
// This matches any string key (which will be a ticker)
|
|
24
|
-
"^.*$": {
|
|
25
|
-
"type": "object",
|
|
26
|
-
"description": "User flow metrics for a specific asset ticker.",
|
|
27
|
-
"properties": {
|
|
28
|
-
"opened_by_user_count": {
|
|
29
|
-
"type": "number",
|
|
30
|
-
"description": "Count of unique users who opened a position in this asset."
|
|
31
|
-
},
|
|
32
|
-
"closed_by_user_count": {
|
|
33
|
-
"type": "number",
|
|
34
|
-
"description": "Count of unique users who closed their position in this asset."
|
|
35
|
-
},
|
|
36
|
-
"net_user_flow": {
|
|
37
|
-
"type": "number",
|
|
38
|
-
"description": "Net change in users (opened - closed)."
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"required": ["opened_by_user_count", "closed_by_user_count", "net_user_flow"]
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
// Use 'additionalProperties' as a fallback for patternProperties
|
|
45
|
-
"additionalProperties": {
|
|
46
|
-
"type": "object",
|
|
47
|
-
"properties": {
|
|
48
|
-
"opened_by_user_count": { "type": "number" },
|
|
49
|
-
"closed_by_user_count": { "type": "number" },
|
|
50
|
-
"net_user_flow": { "type": "number" }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
_initAsset(instrumentId) {
|
|
57
|
-
if (!this.assetActivity.has(instrumentId)) {
|
|
58
|
-
this.assetActivity.set(instrumentId, {
|
|
59
|
-
new_users: new Set(),
|
|
60
|
-
closed_users: new Set()
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
_getInstrumentIds(portfolio) {
|
|
66
|
-
const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
67
|
-
if (!positions || !Array.isArray(positions)) {
|
|
68
|
-
return new Set();
|
|
69
|
-
}
|
|
70
|
-
return new Set(positions.map(p => p.InstrumentID).filter(Boolean));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
74
|
-
if (!todayPortfolio || !yesterdayPortfolio) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const yIds = this._getInstrumentIds(yesterdayPortfolio);
|
|
79
|
-
const tIds = this._getInstrumentIds(todayPortfolio);
|
|
80
|
-
|
|
81
|
-
// Find new positions (in today but not yesterday)
|
|
82
|
-
for (const tId of tIds) {
|
|
83
|
-
if (!yIds.has(tId)) {
|
|
84
|
-
this._initAsset(tId);
|
|
85
|
-
this.assetActivity.get(tId).new_users.add(userId);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Find closed positions (in yesterday but not today)
|
|
90
|
-
for (const yId of yIds) {
|
|
91
|
-
if (!tIds.has(yId)) {
|
|
92
|
-
this._initAsset(yId);
|
|
93
|
-
this.assetActivity.get(yId).closed_users.add(userId);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async getResult() {
|
|
99
|
-
if (!this.mappings) {
|
|
100
|
-
this.mappings = await loadInstrumentMappings();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const result = {};
|
|
104
|
-
for (const [instrumentId, data] of this.assetActivity.entries()) {
|
|
105
|
-
const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
|
|
106
|
-
|
|
107
|
-
const openCount = data.new_users.size;
|
|
108
|
-
const closeCount = data.closed_users.size;
|
|
109
|
-
|
|
110
|
-
if (openCount > 0 || closeCount > 0) {
|
|
111
|
-
result[ticker] = {
|
|
112
|
-
opened_by_user_count: openCount,
|
|
113
|
-
closed_by_user_count: closeCount,
|
|
114
|
-
// "Net User Flow" - positive means more users joined than left
|
|
115
|
-
net_user_flow: openCount - closeCount
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return result;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
reset() {
|
|
123
|
-
this.assetActivity.clear();
|
|
124
|
-
this.mappings = null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
module.exports = DailyAssetActivity;
|