aiden-shared-calculations-unified 1.0.82 → 1.0.84
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 +122 -104
- package/calculations/core/asset-position-size.js +110 -73
- package/calculations/core/average-daily-pnl-all-users.js +17 -3
- package/calculations/core/average-daily-pnl-per-sector.js +83 -75
- package/calculations/core/average-daily-pnl-per-stock.js +84 -73
- package/calculations/core/average-daily-position-pnl.js +2 -2
- package/calculations/core/holding-duration-per-asset.js +24 -23
- package/calculations/core/instrument-price-change-1d.js +72 -82
- package/calculations/core/instrument-price-momentum-20d.js +66 -100
- package/calculations/core/long-position-per-stock.js +21 -13
- package/calculations/core/overall-holding-duration.js +8 -3
- package/calculations/core/overall-profitability-ratio.js +2 -2
- package/calculations/core/platform-buy-sell-sentiment.js +75 -22
- package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
- package/calculations/core/platform-daily-ownership-delta.js +39 -15
- package/calculations/core/platform-ownership-per-sector.js +38 -18
- package/calculations/core/platform-total-positions-held.js +36 -14
- package/calculations/core/pnl-distribution-per-stock.js +39 -36
- package/calculations/core/price-metrics.js +70 -172
- package/calculations/core/profitability-ratio-per-sector.js +23 -29
- package/calculations/core/profitability-ratio-per-stock.js +20 -13
- package/calculations/core/profitability-skew-per-stock.js +20 -13
- package/calculations/core/profitable-and-unprofitable-status.js +34 -10
- package/calculations/core/sentiment-per-stock.js +20 -9
- package/calculations/core/short-position-per-stock.js +23 -37
- package/calculations/core/social-activity-aggregation.js +41 -115
- package/calculations/core/social-asset-posts-trend.js +77 -94
- package/calculations/core/social-event-correlation.js +87 -106
- package/calculations/core/social-sentiment-aggregation.js +56 -138
- package/calculations/core/social-top-mentioned-words.js +74 -106
- package/calculations/core/social-topic-interest-evolution.js +94 -94
- package/calculations/core/social-topic-sentiment-matrix.js +90 -74
- package/calculations/core/social-word-mentions-trend.js +92 -106
- package/calculations/core/speculator-asset-sentiment.js +63 -92
- package/calculations/core/speculator-danger-zone.js +77 -90
- package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
- package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
- package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
- package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
- package/calculations/core/speculator-leverage-per-asset.js +62 -57
- package/calculations/core/speculator-leverage-per-sector.js +53 -65
- package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
- package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
- package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
- package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
- package/calculations/core/speculator-take-profit-per-asset.js +45 -69
- package/calculations/core/speculator-tsl-per-asset.js +42 -49
- package/calculations/core/total-long-figures.js +19 -19
- package/calculations/core/total-long-per-sector.js +39 -36
- package/calculations/core/total-short-figures.js +19 -19
- package/calculations/core/total-short-per-sector.js +39 -36
- package/calculations/core/users-processed.js +52 -25
- package/calculations/gauss/cohort-capital-flow.js +38 -29
- package/calculations/gauss/cohort-definer.js +17 -25
- package/calculations/gauss/daily-dna-filter.js +10 -4
- package/calculations/gauss/gauss-divergence-signal.js +28 -6
- package/calculations/gem/cohort-momentum-state.js +113 -92
- package/calculations/gem/cohort-skill-definition.js +23 -53
- package/calculations/gem/platform-conviction-divergence.js +62 -116
- package/calculations/gem/quant-skill-alpha-signal.js +107 -123
- package/calculations/gem/skilled-cohort-flow.js +178 -167
- package/calculations/gem/skilled-unskilled-divergence.js +73 -113
- package/calculations/gem/unskilled-cohort-flow.js +176 -166
- package/calculations/helix/helix-contrarian-signal.js +91 -83
- package/calculations/helix/herd-consensus-score.js +135 -97
- package/calculations/helix/winner-loser-flow.js +14 -14
- package/calculations/pyro/risk-appetite-index.js +121 -123
- package/calculations/pyro/squeeze-potential.js +93 -125
- package/calculations/pyro/volatility-signal.js +109 -97
- package/package.json +9 -9
- package/README.MD +0 -78
|
@@ -1,44 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview HELIX Product Line (Pass
|
|
2
|
+
* @fileoverview HELIX Product Line (Pass 2)
|
|
3
3
|
*
|
|
4
|
-
* This metric answers: "
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* It blends sentiment from three different views of the herd:
|
|
8
|
-
* 1. The entire Platform (from gem/platform-conviction-divergence)
|
|
9
|
-
* 2. Our 20k User Sample (from core/sentiment-per-stock)
|
|
10
|
-
* 3. Social Media (from core/social-sentiment-aggregation)
|
|
11
|
-
*
|
|
12
|
-
* The score is normalized from -10 (Extreme Bearish Consensus)
|
|
13
|
-
* to +10 (Extreme Bullish Consensus).
|
|
4
|
+
* This metric answers: "For each asset, what is the
|
|
5
|
+
* net 'conviction' (avg. position change) of the
|
|
6
|
+
* 'Herd' (in-loss) cohort?"
|
|
14
7
|
*/
|
|
8
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
9
|
+
|
|
10
|
+
|
|
15
11
|
class HerdConsensusScore {
|
|
12
|
+
constructor() {
|
|
13
|
+
// { [instrumentId]: { ... } }
|
|
14
|
+
this.assetConviction = new Map();
|
|
15
|
+
|
|
16
|
+
// --- STANDARD 0: RENAMED ---
|
|
17
|
+
this.tickerMap = null;
|
|
18
|
+
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Defines the output schema for this calculation.
|
|
19
|
-
* @returns {object} JSON Schema object
|
|
20
22
|
*/
|
|
21
23
|
static getSchema() {
|
|
22
24
|
const tickerSchema = {
|
|
23
25
|
"type": "object",
|
|
24
26
|
"properties": {
|
|
25
|
-
"
|
|
27
|
+
"herd_conviction_score": {
|
|
26
28
|
"type": "number",
|
|
27
|
-
"description": "
|
|
29
|
+
"description": "Avg. % change in position size for 'Loser' cohort. Positive = Buying the Dip, Negative = Capitulating."
|
|
28
30
|
},
|
|
29
|
-
"
|
|
30
|
-
"sample_sentiment": { "type": "number" },
|
|
31
|
-
"social_sentiment": { "type": "number" }
|
|
31
|
+
"user_count": { "type": "number" }
|
|
32
32
|
},
|
|
33
|
-
"required": ["
|
|
33
|
+
"required": ["herd_conviction_score", "user_count"]
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
return {
|
|
37
37
|
"type": "object",
|
|
38
|
-
"description": "
|
|
39
|
-
"patternProperties": {
|
|
40
|
-
"^.*$": tickerSchema // Ticker
|
|
41
|
-
},
|
|
38
|
+
"description": "Tracks the change in avg. position size for the 'Loser' (in-loss) cohort.",
|
|
39
|
+
"patternProperties": { "^.*$": tickerSchema },
|
|
42
40
|
"additionalProperties": tickerSchema
|
|
43
41
|
};
|
|
44
42
|
}
|
|
@@ -48,10 +46,10 @@ class HerdConsensusScore {
|
|
|
48
46
|
*/
|
|
49
47
|
static getMetadata() {
|
|
50
48
|
return {
|
|
51
|
-
type: '
|
|
52
|
-
rootDataDependencies: [],
|
|
53
|
-
isHistorical:
|
|
54
|
-
userType: '
|
|
49
|
+
type: 'standard',
|
|
50
|
+
rootDataDependencies: ['portfolio'],
|
|
51
|
+
isHistorical: true, // Needs T-1 portfolio
|
|
52
|
+
userType: 'all',
|
|
55
53
|
category: 'helix'
|
|
56
54
|
};
|
|
57
55
|
}
|
|
@@ -60,93 +58,133 @@ class HerdConsensusScore {
|
|
|
60
58
|
* Statically declare dependencies.
|
|
61
59
|
*/
|
|
62
60
|
static getDependencies() {
|
|
63
|
-
return [
|
|
64
|
-
// This is the only source of *per-ticker* platform sentiment
|
|
65
|
-
'platform-conviction-divergence', // from gem
|
|
66
|
-
'sentiment-per-stock', // from core
|
|
67
|
-
'social-sentiment-aggregation' // from core
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Normalizes a 0-100 value (like platform_long_pct) to -1.0 to +1.0
|
|
73
|
-
*/
|
|
74
|
-
_normalizePercent(v) {
|
|
75
|
-
if (v === null || v === undefined) return 0;
|
|
76
|
-
const score = (v - 50) / 50; // 50 -> 0, 100 -> 1, 0 -> -1
|
|
77
|
-
return Math.max(-1, Math.min(1, score));
|
|
61
|
+
return [];
|
|
78
62
|
}
|
|
79
63
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*/
|
|
83
|
-
_normalizeNetPercent(v) {
|
|
84
|
-
if (v === null || v === undefined) return 0;
|
|
85
|
-
const score = v / 100;
|
|
86
|
-
return Math.max(-1, Math.min(1, score));
|
|
64
|
+
_getPortfolioPositions(portfolio) {
|
|
65
|
+
return portfolio?.AggregatedPositions || portfolio?.PublicPositions;
|
|
87
66
|
}
|
|
88
67
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return Math.max(-1, Math.min(1, score));
|
|
68
|
+
_initAsset(instrumentId) {
|
|
69
|
+
if (!this.assetConviction.has(instrumentId)) {
|
|
70
|
+
this.assetConviction.set(instrumentId, {
|
|
71
|
+
total_pos_size_yesterday: 0,
|
|
72
|
+
total_pos_size_today: 0,
|
|
73
|
+
user_count_yesterday: 0,
|
|
74
|
+
user_count_today: 0
|
|
75
|
+
});
|
|
76
|
+
}
|
|
99
77
|
}
|
|
100
78
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const { logger } = dependencies;
|
|
79
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
80
|
+
if (!todayPortfolio) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
106
83
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!platformData || !sampleData || !socialData) {
|
|
112
|
-
logger.log('WARN', `[helix/herd-consensus-score] Missing dependencies for ${dateStr}. Skipping.`);
|
|
113
|
-
return {};
|
|
84
|
+
// --- STANDARD 0: FIXED ---
|
|
85
|
+
if (!this.tickerMap) {
|
|
86
|
+
this.tickerMap = context.instrumentToTicker;
|
|
114
87
|
}
|
|
115
88
|
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
...Object.keys(sampleData),
|
|
119
|
-
...Object.keys(socialData)
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
const result = {};
|
|
89
|
+
const tPos = this._getPortfolioPositions(todayPortfolio);
|
|
90
|
+
if (!tPos) return;
|
|
123
91
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
92
|
+
// --- Determine Cohort ---
|
|
93
|
+
// We *only* care about users who are *currently* in the
|
|
94
|
+
// 'Loser' cohort for any given asset.
|
|
95
|
+
const tPosMap = new Map(tPos.map(p => [p.InstrumentID, p]));
|
|
96
|
+
const yPosMap = new Map((this._getPortfolioPositions(yesterdayPortfolio) || []).map(p => [p.InstrumentID, p]));
|
|
97
|
+
|
|
98
|
+
const allInstrumentIds = new Set([...yPosMap.keys(), ...tPosMap.keys()]);
|
|
99
|
+
if (allInstrumentIds.size === 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const instrumentId of allInstrumentIds) {
|
|
104
|
+
const tP = tPosMap.get(instrumentId);
|
|
105
|
+
const yP = yPosMap.get(instrumentId);
|
|
128
106
|
|
|
129
|
-
//
|
|
130
|
-
const
|
|
131
|
-
const
|
|
107
|
+
// --- Check Cohort Status (In-Loss) ---
|
|
108
|
+
const tPnl = tP?.NetProfit || 0;
|
|
109
|
+
const isLoser = tPnl < 0;
|
|
132
110
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const consensus_score = ((s_plat + s_samp + s_soc) / 3) * 10;
|
|
111
|
+
// We only process conviction for assets this user
|
|
112
|
+
// is *currently* losing on.
|
|
113
|
+
if (!isLoser) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
139
116
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
117
|
+
// --- This user is in the 'Herd' for this asset ---
|
|
118
|
+
this._initAsset(instrumentId);
|
|
119
|
+
const asset = this.assetConviction.get(instrumentId);
|
|
120
|
+
|
|
121
|
+
// ---
|
|
122
|
+
// FIX: The field 'PositionSizeInUnits' does not exist in schema.md.
|
|
123
|
+
// For Normal users, the field is 'Invested'.
|
|
124
|
+
// For Speculator users, the field is 'Amount'.
|
|
125
|
+
// This line now correctly checks for either.
|
|
126
|
+
// ---
|
|
127
|
+
const ySize = yP?.Invested || yP?.Amount || 0;
|
|
128
|
+
const tSize = tP?.Invested || tP?.Amount || 0;
|
|
129
|
+
|
|
130
|
+
if (ySize > 0) {
|
|
131
|
+
asset.total_pos_size_yesterday += ySize;
|
|
132
|
+
asset.user_count_yesterday++;
|
|
133
|
+
}
|
|
134
|
+
if (tSize > 0) {
|
|
135
|
+
asset.total_pos_size_today += tSize;
|
|
136
|
+
asset.user_count_today++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getResult() {
|
|
142
|
+
// --- STANDARD 0: REMOVED forbidden data load ---
|
|
143
|
+
|
|
144
|
+
// Failsafe check
|
|
145
|
+
if (!this.tickerMap) {
|
|
146
|
+
return {}; // process() must run first
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = {};
|
|
150
|
+
for (const [instrumentId, data] of this.assetConviction.entries()) {
|
|
151
|
+
// --- STANDARD 0: SIMPLIFIED ---
|
|
152
|
+
const ticker = this.tickerMap[instrumentId];
|
|
153
|
+
if (!ticker) continue;
|
|
154
|
+
|
|
155
|
+
const {
|
|
156
|
+
total_pos_size_yesterday, total_pos_size_today,
|
|
157
|
+
user_count_yesterday, user_count_today
|
|
158
|
+
} = data;
|
|
159
|
+
|
|
160
|
+
let avg_position_change_pct = 0;
|
|
161
|
+
|
|
162
|
+
if (user_count_yesterday > 0 && user_count_today > 0) {
|
|
163
|
+
const avg_pos_y = total_pos_size_yesterday / user_count_yesterday;
|
|
164
|
+
const avg_pos_t = total_pos_size_today / user_count_today;
|
|
165
|
+
if (avg_pos_y > 0) {
|
|
166
|
+
avg_position_change_pct = ((avg_pos_t - avg_pos_y) / avg_pos_y) * 100;
|
|
167
|
+
}
|
|
168
|
+
} else if (user_count_today > 0) {
|
|
169
|
+
// Pure inflow, conviction is max
|
|
170
|
+
avg_position_change_pct = Infinity;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (isFinite(avg_position_change_pct) && user_count_today > 0) {
|
|
174
|
+
result[ticker] = {
|
|
175
|
+
herd_conviction_score: avg_position_change_pct,
|
|
176
|
+
user_count: user_count_today
|
|
177
|
+
};
|
|
178
|
+
}
|
|
146
179
|
}
|
|
147
|
-
|
|
148
180
|
return result;
|
|
149
181
|
}
|
|
182
|
+
|
|
183
|
+
reset() {
|
|
184
|
+
this.assetConviction.clear();
|
|
185
|
+
// --- STANDARD 0: RENAMED ---
|
|
186
|
+
this.tickerMap = null;
|
|
187
|
+
}
|
|
150
188
|
}
|
|
151
189
|
|
|
152
190
|
module.exports = HerdConsensusScore;
|
|
@@ -10,14 +10,15 @@
|
|
|
10
10
|
* This calculation now determines "winner/loser" status internally
|
|
11
11
|
* by reading the user's portfolio P&L directly.
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class WinnerLoserFlow {
|
|
17
17
|
constructor() {
|
|
18
18
|
// We will store { [ticker]: { winners_joined: 0, ... } }
|
|
19
19
|
this.assetFlows = new Map();
|
|
20
|
-
|
|
20
|
+
// --- STANDARD 0: RENAMED ---
|
|
21
|
+
this.tickerMap = null; // Caches mappings
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -74,15 +75,12 @@ class WinnerLoserFlow {
|
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
77
|
* Statically declare dependencies.
|
|
77
|
-
* --- MODIFIED ---
|
|
78
|
-
* Removed 'asset-pnl-status' dependency.
|
|
79
78
|
*/
|
|
80
79
|
static getDependencies() {
|
|
81
80
|
return [];
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
/**
|
|
85
|
-
* --- MODIFIED ---
|
|
86
84
|
* Helper to get a user's holdings (Instrument IDs) AND their P&L.
|
|
87
85
|
* @returns {Map<InstrumentID, {pnl: number}>}
|
|
88
86
|
*/
|
|
@@ -119,8 +117,13 @@ class WinnerLoserFlow {
|
|
|
119
117
|
return;
|
|
120
118
|
}
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// --- STANDARD 0: FIXED ---
|
|
121
|
+
if (!this.tickerMap) {
|
|
122
|
+
this.tickerMap = context.instrumentToTicker;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!this.tickerMap) {
|
|
126
|
+
return; // Failsafe if context is missing map
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
const yHoldings = this._getHoldingsWithPnl(yesterdayPortfolio); // Map<InstID, {pnl}>
|
|
@@ -132,17 +135,14 @@ class WinnerLoserFlow {
|
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
for (const instrumentId of allInstrumentIds) {
|
|
135
|
-
|
|
138
|
+
// --- STANDARD 0: FIXED ---
|
|
139
|
+
const ticker = this.tickerMap[instrumentId];
|
|
136
140
|
if (!ticker) continue;
|
|
137
141
|
|
|
138
|
-
// --- MODIFIED ---
|
|
139
|
-
// Determine cohort status from today's portfolio.
|
|
140
142
|
const tPnl = tHoldings.get(instrumentId)?.pnl;
|
|
141
143
|
const isWinner = tPnl > 0;
|
|
142
144
|
const isLoser = tPnl < 0;
|
|
143
|
-
// --- END MODIFIED ---
|
|
144
145
|
|
|
145
|
-
// We only care about users who are *currently* in a cohort
|
|
146
146
|
if (!isWinner && !isLoser) {
|
|
147
147
|
continue;
|
|
148
148
|
}
|
|
@@ -150,7 +150,6 @@ class WinnerLoserFlow {
|
|
|
150
150
|
const heldYesterday = yHoldings.has(instrumentId);
|
|
151
151
|
const holdsToday = tHoldings.has(instrumentId);
|
|
152
152
|
|
|
153
|
-
// Skip if no change in holding status
|
|
154
153
|
if (heldYesterday === holdsToday) {
|
|
155
154
|
continue;
|
|
156
155
|
}
|
|
@@ -183,7 +182,8 @@ class WinnerLoserFlow {
|
|
|
183
182
|
|
|
184
183
|
reset() {
|
|
185
184
|
this.assetFlows.clear();
|
|
186
|
-
|
|
185
|
+
// --- STANDARD 0: RENAMED ---
|
|
186
|
+
this.tickerMap = null;
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
@@ -1,153 +1,151 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview PYRO Product Line (Pass 2)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* It combines the average leverage being used with the
|
|
8
|
-
* average Risk/Reward ratio being set. A high score means
|
|
9
|
-
* speculators are confident: using high leverage *and*
|
|
10
|
-
* setting wide R/R targets.
|
|
3
|
+
* --- FIX ---
|
|
4
|
+
* - Updated 'process' signature to the 7-arg standard to get context.
|
|
5
|
+
* - Logic is already schema-compliant (uses pos.Leverage).
|
|
11
6
|
*/
|
|
7
|
+
|
|
12
8
|
class RiskAppetiteIndex {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.tickerLeverage = new Map();
|
|
11
|
+
this.sectorLeverage = new Map();
|
|
12
|
+
this.tickerMap = null;
|
|
13
|
+
this.sectorMap = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static getMetadata() {
|
|
17
|
+
return {
|
|
18
|
+
type: 'standard',
|
|
19
|
+
rootDataDependencies: ['portfolio'],
|
|
20
|
+
isHistorical: false,
|
|
21
|
+
userType: 'speculator', // Only processes speculators
|
|
22
|
+
category: 'pyro'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getDependencies() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
13
29
|
|
|
14
|
-
/**
|
|
15
|
-
* Defines the output schema for this calculation.
|
|
16
|
-
* @returns {object} JSON Schema object
|
|
17
|
-
*/
|
|
18
30
|
static getSchema() {
|
|
19
|
-
const
|
|
31
|
+
const schema = {
|
|
20
32
|
"type": "object",
|
|
21
33
|
"properties": {
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
"description": "Blended score (0-10) of leverage use and R/R conviction."
|
|
25
|
-
},
|
|
26
|
-
"avg_leverage": {
|
|
27
|
-
"type": "number",
|
|
28
|
-
"description": "Weighted average leverage used on this asset."
|
|
29
|
-
},
|
|
30
|
-
"avg_rr_ratio": {
|
|
31
|
-
"type": "number",
|
|
32
|
-
"description": "Average R/R ratio set on this asset."
|
|
33
|
-
}
|
|
34
|
+
"avg_leverage": { "type": "number" },
|
|
35
|
+
"user_count": { "type": "number" }
|
|
34
36
|
},
|
|
35
|
-
"required": ["
|
|
37
|
+
"required": ["avg_leverage", "user_count"]
|
|
36
38
|
};
|
|
37
|
-
|
|
39
|
+
|
|
38
40
|
return {
|
|
39
41
|
"type": "object",
|
|
40
|
-
"description": "Calculates
|
|
41
|
-
"
|
|
42
|
-
"^.*$":
|
|
42
|
+
"description": "Calculates the average leverage used by speculators, aggregated by ticker and sector.",
|
|
43
|
+
"properties": {
|
|
44
|
+
"by_ticker": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema },
|
|
45
|
+
"by_sector": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema }
|
|
43
46
|
},
|
|
44
|
-
"
|
|
47
|
+
"required": ["by_ticker", "by_sector"]
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
type: 'meta',
|
|
54
|
-
rootDataDependencies: [],
|
|
55
|
-
isHistorical: false,
|
|
56
|
-
userType: 'n/a',
|
|
57
|
-
category: 'pyro'
|
|
58
|
-
};
|
|
51
|
+
_init(map, key) {
|
|
52
|
+
if (!map.has(key)) {
|
|
53
|
+
map.set(key, { leverage_sum: 0, user_count: 0 });
|
|
54
|
+
}
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Helper to calculate weighted average leverage from buckets.
|
|
73
|
-
* e.g., { "1x": 10, "5x": 5 }
|
|
74
|
-
*/
|
|
75
|
-
_calculateAvgLeverage(levBuckets) {
|
|
76
|
-
if (!levBuckets || typeof levBuckets !== 'object') {
|
|
77
|
-
return 1; // Default to 1x leverage
|
|
57
|
+
// --- THIS IS THE FIX ---
|
|
58
|
+
// Updated signature to match the test worker's standard 7-arg call
|
|
59
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
|
|
60
|
+
// --- END FIX ---
|
|
61
|
+
|
|
62
|
+
if (!todayPortfolio?.PublicPositions) {
|
|
63
|
+
return; // Not a speculator or no positions
|
|
78
64
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
weightedSum += (leverage * count);
|
|
88
|
-
}
|
|
65
|
+
|
|
66
|
+
if (!this.tickerMap) {
|
|
67
|
+
this.tickerMap = context.instrumentToTicker;
|
|
68
|
+
this.sectorMap = context.sectorMapping;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.tickerMap || !this.sectorMap) {
|
|
72
|
+
return;
|
|
89
73
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
74
|
+
|
|
75
|
+
const positions = todayPortfolio.PublicPositions;
|
|
76
|
+
|
|
77
|
+
const seenTickers = new Set();
|
|
78
|
+
const seenSectors = new Set();
|
|
79
|
+
|
|
80
|
+
for (const pos of positions) {
|
|
81
|
+
if (!pos.InstrumentID || !pos.Leverage) continue;
|
|
82
|
+
|
|
83
|
+
const leverage = pos.Leverage;
|
|
84
|
+
if (leverage <= 1) continue; // Only care about leveraged positions
|
|
85
|
+
|
|
86
|
+
const ticker = this.tickerMap[pos.InstrumentID];
|
|
87
|
+
const sector = this.sectorMap[pos.InstrumentID];
|
|
88
|
+
|
|
89
|
+
if (ticker) {
|
|
90
|
+
this._init(this.tickerLeverage, ticker);
|
|
91
|
+
const asset = this.tickerLeverage.get(ticker);
|
|
92
|
+
asset.leverage_sum += leverage;
|
|
93
|
+
|
|
94
|
+
if (!seenTickers.has(ticker)) {
|
|
95
|
+
asset.user_count++;
|
|
96
|
+
seenTickers.add(ticker);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (sector) {
|
|
101
|
+
this._init(this.sectorLeverage, sector);
|
|
102
|
+
const sec = this.sectorLeverage.get(sector);
|
|
103
|
+
sec.leverage_sum += leverage;
|
|
104
|
+
|
|
105
|
+
if (!seenSectors.has(sector)) {
|
|
106
|
+
sec.user_count++;
|
|
107
|
+
seenSectors.add(sector);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
93
110
|
}
|
|
94
|
-
return weightedSum / totalCount;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Simple min-max normalization, clamped 0-1.
|
|
99
|
-
*/
|
|
100
|
-
_normalize(value, min, max) {
|
|
101
|
-
const normalized = (value - min) / (max - min);
|
|
102
|
-
return Math.max(0, Math.min(1, normalized));
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
113
|
+
async getResult() {
|
|
114
|
+
if (!this.tickerMap) {
|
|
115
|
+
return { by_ticker: {}, by_sector: {} };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result = {
|
|
119
|
+
by_ticker: {},
|
|
120
|
+
by_sector: {}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
for (const [ticker, data] of this.tickerLeverage.entries()) {
|
|
124
|
+
if (data.user_count > 0) {
|
|
125
|
+
result.by_ticker[ticker] = {
|
|
126
|
+
avg_leverage: data.leverage_sum / data.user_count,
|
|
127
|
+
user_count: data.user_count
|
|
128
|
+
};
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for (const ticker of allTickers) {
|
|
132
|
-
const avg_leverage = this._calculateAvgLeverage(leverageData[ticker]);
|
|
133
|
-
const avg_rr_ratio = rrData[ticker]?.avg_rr_ratio || 0;
|
|
134
|
-
|
|
135
|
-
// Normalize: Leverage from 1x-10x, R/R from 0-5
|
|
136
|
-
const norm_lev = this._normalize(avg_leverage, 1, 10);
|
|
137
|
-
const norm_rr = this._normalize(avg_rr_ratio, 0, 5);
|
|
138
|
-
|
|
139
|
-
// Combine scores and scale to 0-10
|
|
140
|
-
const risk_appetite_score = (norm_lev * 0.6 + norm_rr * 0.4) * 10;
|
|
141
|
-
|
|
142
|
-
result[ticker] = {
|
|
143
|
-
risk_appetite_score: risk_appetite_score,
|
|
144
|
-
avg_leverage: avg_leverage,
|
|
145
|
-
avg_rr_ratio: avg_rr_ratio
|
|
146
|
-
};
|
|
132
|
+
for (const [sector, data] of this.sectorLeverage.entries()) {
|
|
133
|
+
if (data.user_count > 0) {
|
|
134
|
+
result.by_sector[sector] = {
|
|
135
|
+
avg_leverage: data.leverage_sum / data.user_count,
|
|
136
|
+
user_count: data.user_count
|
|
137
|
+
};
|
|
138
|
+
}
|
|
147
139
|
}
|
|
148
|
-
|
|
140
|
+
|
|
149
141
|
return result;
|
|
150
142
|
}
|
|
151
|
-
}
|
|
152
143
|
|
|
144
|
+
reset() {
|
|
145
|
+
this.tickerLeverage.clear();
|
|
146
|
+
this.sectorLeverage.clear();
|
|
147
|
+
this.tickerMap = null;
|
|
148
|
+
this.sectorMap = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
153
151
|
module.exports = RiskAppetiteIndex;
|