aiden-shared-calculations-unified 1.0.86 → 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,199 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
|
|
3
|
-
*
|
|
4
|
-
* - Changed 'type' from 'standard' to 'meta'.
|
|
5
|
-
* - Rewrote 'process' to use the 5-arg 'meta' signature.
|
|
6
|
-
* - Removed 'calculationUtils.loadAllPriceData()' (not in harness).
|
|
7
|
-
* - Logic now reads 'priceData' from Arg 1 (metaPayload) and
|
|
8
|
-
* mappings from Arg 4 (config), as provided by the fixed worker.
|
|
9
|
-
* * --- ** THIS IS THE FIX FOR THE EMPTY RESULT ** ---
|
|
10
|
-
* - Updated _findPriceOnOrBefore to look inside the 'prices' object
|
|
11
|
-
* (e.g., priceHistoryObj.prices[checkDateStr]) to match the
|
|
12
|
-
* data-generator.js and 'instrument-price-momentum-20d.js' structure.
|
|
3
|
+
* REFACTORED: Uses process(context) solely.
|
|
13
4
|
*/
|
|
14
|
-
|
|
15
5
|
const RANGES = [7, 30, 90, 365];
|
|
16
6
|
const TRADING_DAYS_PER_YEAR = 252;
|
|
17
|
-
const MAX_LOOKBACK_DAYS = 5;
|
|
18
7
|
|
|
19
8
|
class CorePriceMetrics {
|
|
20
|
-
|
|
21
9
|
constructor() {
|
|
22
10
|
this.result = { by_instrument: {}, by_sector: {} };
|
|
23
11
|
}
|
|
24
12
|
|
|
25
|
-
// --- THIS IS THE FIX (Part 1) ---
|
|
26
13
|
static getMetadata() {
|
|
27
14
|
return {
|
|
28
|
-
type: 'meta',
|
|
29
|
-
rootDataDependencies: ['price'],
|
|
30
|
-
isHistorical: true,
|
|
15
|
+
type: 'meta',
|
|
16
|
+
rootDataDependencies: ['price'],
|
|
17
|
+
isHistorical: true,
|
|
31
18
|
userType: 'n/a',
|
|
32
19
|
category: 'core_metrics'
|
|
33
20
|
};
|
|
34
21
|
}
|
|
35
|
-
// --- END FIX (Part 1) ---
|
|
36
22
|
|
|
37
|
-
static getDependencies() {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
23
|
+
static getDependencies() { return []; }
|
|
40
24
|
|
|
41
25
|
static getSchema() {
|
|
42
|
-
// [Schema omitted for brevity]
|
|
43
26
|
return {
|
|
44
27
|
"type": "object",
|
|
45
|
-
"description": "Calculates risk/return metrics (StdDev, Sharpe, Vol, Drawdown) for instruments and sectors.",
|
|
46
28
|
"properties": { "by_instrument": { "type": "object" }, "by_sector": { "type": "object" } },
|
|
47
29
|
"required": ["by_instrument", "by_sector"]
|
|
48
30
|
};
|
|
49
31
|
}
|
|
50
32
|
|
|
51
|
-
// --- THIS IS THE FIX (Part 2) ---
|
|
52
|
-
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
53
|
-
|
|
54
|
-
// Get mappings from Arg 4 (config)
|
|
55
|
-
const { instrumentToTicker, sectorMapping } = config;
|
|
56
|
-
|
|
57
|
-
// Get price data from Arg 1 (dateStr is metaPayload)
|
|
58
|
-
const priceData = dateStr.priceData;
|
|
59
|
-
// Get today's date from Arg 1
|
|
60
|
-
const todayDateStr = dateStr.date;
|
|
61
|
-
|
|
62
|
-
if (!priceData || priceData.length === 0 || !instrumentToTicker || !sectorMapping || !todayDateStr) {
|
|
63
|
-
this.result = { by_instrument: {}, by_sector: {} };
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const by_instrument = {};
|
|
68
|
-
|
|
69
|
-
// 1. Calculate Per-Instrument Metrics
|
|
70
|
-
for (const p of priceData) {
|
|
71
|
-
const instrumentId = p.instrumentId;
|
|
72
|
-
const ticker = instrumentToTicker[instrumentId];
|
|
73
|
-
if (!ticker) continue;
|
|
74
|
-
|
|
75
|
-
// 'p' is the price entry, which is the object with 'prices: { ... }'
|
|
76
|
-
const priceHistoryObj = p;
|
|
77
|
-
const instrumentMetrics = {};
|
|
78
|
-
|
|
79
|
-
for (const range of RANGES) {
|
|
80
|
-
instrumentMetrics[`stdev_${range}d`] = null;
|
|
81
|
-
instrumentMetrics[`volatility_annualized_${range}d`] = null;
|
|
82
|
-
instrumentMetrics[`sharpe_ratio_${range}d`] = null;
|
|
83
|
-
instrumentMetrics[`max_drawdown_${range}d`] = null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
for (const range of RANGES) {
|
|
87
|
-
// Pass the raw price object (p)
|
|
88
|
-
const priceArray = this._getHistoricalPriceArray(priceHistoryObj, todayDateStr, range + 1);
|
|
89
|
-
if (priceArray.length < 2) continue;
|
|
90
|
-
|
|
91
|
-
instrumentMetrics[`max_drawdown_${range}d`] = this._calculateMaxDrawdown(priceArray);
|
|
92
|
-
|
|
93
|
-
const dailyReturns = this._calculateDailyReturns(priceArray);
|
|
94
|
-
if (dailyReturns.length < 2) continue;
|
|
95
|
-
|
|
96
|
-
const meanReturn = this._calculateMean(dailyReturns);
|
|
97
|
-
const stdDev = this._calculateStdDev(dailyReturns, meanReturn);
|
|
98
|
-
|
|
99
|
-
instrumentMetrics[`stdev_${range}d`] = stdDev;
|
|
100
|
-
|
|
101
|
-
if (stdDev > 0) {
|
|
102
|
-
instrumentMetrics[`sharpe_ratio_${range}d`] = (meanReturn / stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR);
|
|
103
|
-
instrumentMetrics[`volatility_annualized_${range}d`] = stdDev * Math.sqrt(TRADING_DAYS_PER_YEAR);
|
|
104
|
-
} else {
|
|
105
|
-
instrumentMetrics[`sharpe_ratio_${range}d`] = 0;
|
|
106
|
-
instrumentMetrics[`volatility_annualized_${range}d`] = 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
by_instrument[ticker] = instrumentMetrics;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 2. Calculate Sector Aggregates
|
|
114
|
-
const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker, sectorMapping);
|
|
115
|
-
|
|
116
|
-
this.result = {
|
|
117
|
-
by_instrument,
|
|
118
|
-
by_sector,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// --- END FIX (Part 2) ---
|
|
122
|
-
|
|
123
|
-
async getResult(fetchedDependencies) {
|
|
124
|
-
return this.result;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
reset() {
|
|
128
|
-
this.result = { by_instrument: {}, by_sector: {} };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
_aggregateMetricsBySector(by_instrument, instrumentToTicker, instrumentToSector) {
|
|
132
|
-
const sectorAggregates = {};
|
|
133
|
-
const tickerToInstrument = Object.fromEntries(Object.entries(instrumentToTicker).map(([id, ticker]) => [ticker, id]));
|
|
134
|
-
|
|
135
|
-
for (const ticker in by_instrument) {
|
|
136
|
-
const instrumentId = tickerToInstrument[ticker];
|
|
137
|
-
const sector = instrumentToSector[instrumentId] || "Unknown";
|
|
138
|
-
const metrics = by_instrument[ticker];
|
|
139
|
-
|
|
140
|
-
if (!sectorAggregates[sector]) {
|
|
141
|
-
sectorAggregates[sector] = { metrics: {}, counts: {} };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
for (const metricName in metrics) {
|
|
145
|
-
const value = metrics[metricName];
|
|
146
|
-
if (value !== null && typeof value === 'number' && isFinite(value)) {
|
|
147
|
-
if (!sectorAggregates[sector].metrics[metricName]) {
|
|
148
|
-
sectorAggregates[sector].metrics[metricName] = 0;
|
|
149
|
-
sectorAggregates[sector].counts[metricName] = 0;
|
|
150
|
-
}
|
|
151
|
-
sectorAggregates[sector].metrics[metricName] += value;
|
|
152
|
-
sectorAggregates[sector].counts[metricName]++;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const by_sector = {};
|
|
158
|
-
for (const sector in sectorAggregates) {
|
|
159
|
-
by_sector[sector] = {};
|
|
160
|
-
const agg = sectorAggregates[sector];
|
|
161
|
-
const allMetricNames = Object.keys(agg.metrics);
|
|
162
|
-
|
|
163
|
-
for (const metricName of allMetricNames) {
|
|
164
|
-
const count = agg.counts[metricName];
|
|
165
|
-
if (count > 0) {
|
|
166
|
-
by_sector[sector][`average_${metricName}`] = agg.metrics[metricName] / count;
|
|
167
|
-
} else {
|
|
168
|
-
by_sector[sector][`average_${metricName}`] = null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
return by_sector;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// --- THIS IS THE FIX FOR THE EMPTY RESULT ---
|
|
176
33
|
_findPriceOnOrBefore(priceHistoryObj, dateStr) {
|
|
177
|
-
// Add a safety check for the nested 'prices' object
|
|
178
34
|
if (!priceHistoryObj || !priceHistoryObj.prices) return null;
|
|
179
|
-
|
|
180
35
|
let checkDate = new Date(dateStr + 'T00:00:00Z');
|
|
181
|
-
for (let i = 0; i <
|
|
36
|
+
for (let i = 0; i < 5; i++) {
|
|
182
37
|
const checkDateStr = checkDate.toISOString().slice(0, 10);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// Was: priceHistoryObj[`prices.${checkDateStr}`]
|
|
186
|
-
// Is: priceHistoryObj.prices[checkDateStr]
|
|
187
|
-
const price = priceHistoryObj.prices[checkDateStr];
|
|
188
|
-
|
|
189
|
-
if (price !== undefined && price !== null && price > 0) {
|
|
190
|
-
return price; // Found it
|
|
191
|
-
}
|
|
38
|
+
const price = priceHistoryObj.prices[checkDateStr];
|
|
39
|
+
if (price !== undefined && price !== null && price > 0) return price;
|
|
192
40
|
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
|
193
41
|
}
|
|
194
42
|
return null;
|
|
195
43
|
}
|
|
196
|
-
// --- END FIX ---
|
|
197
44
|
|
|
198
45
|
_getHistoricalPriceArray(priceHistoryObj, endDateStr, numDays) {
|
|
199
46
|
const prices = [];
|
|
@@ -203,68 +50,110 @@ class CorePriceMetrics {
|
|
|
203
50
|
for (let i = 0; i < numDays; i++) {
|
|
204
51
|
const targetDateStr = currentDate.toISOString().slice(0, 10);
|
|
205
52
|
let price = this._findPriceOnOrBefore(priceHistoryObj, targetDateStr);
|
|
53
|
+
if (price === null) price = lastPrice;
|
|
54
|
+
else lastPrice = price;
|
|
206
55
|
|
|
207
|
-
if (price
|
|
208
|
-
price = lastPrice;
|
|
209
|
-
} else {
|
|
210
|
-
lastPrice = price;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (price !== null) {
|
|
214
|
-
prices.push(price);
|
|
215
|
-
}
|
|
216
|
-
|
|
56
|
+
if (price !== null) prices.push(price);
|
|
217
57
|
currentDate.setUTCDate(currentDate.getUTCDate() - 1);
|
|
218
58
|
}
|
|
219
|
-
|
|
220
59
|
return prices.reverse().filter(p => p !== null);
|
|
221
60
|
}
|
|
222
61
|
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
const sum = arr.reduce((acc, val) => acc + val, 0);
|
|
226
|
-
return sum / arr.length;
|
|
227
|
-
}
|
|
62
|
+
_calculateStats(priceArray) {
|
|
63
|
+
if (priceArray.length < 2) return { stdDev: 0, mean: 0, drawdown: 0 };
|
|
228
64
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
65
|
+
// Drawdown
|
|
66
|
+
let maxDrawdown = 0, peak = -Infinity;
|
|
67
|
+
for (const price of priceArray) {
|
|
68
|
+
if (price > peak) peak = price;
|
|
69
|
+
if (peak > 0) {
|
|
70
|
+
const dd = (price - peak) / peak;
|
|
71
|
+
if (dd < maxDrawdown) maxDrawdown = dd;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
235
74
|
|
|
236
|
-
|
|
75
|
+
// Returns & StdDev
|
|
237
76
|
const returns = [];
|
|
238
|
-
for (let i = 1; i <
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
returns.push((currPrice - prevPrice) / prevPrice);
|
|
243
|
-
} else {
|
|
244
|
-
returns.push(0);
|
|
245
|
-
}
|
|
77
|
+
for (let i = 1; i < priceArray.length; i++) {
|
|
78
|
+
const prev = priceArray[i - 1];
|
|
79
|
+
const curr = priceArray[i];
|
|
80
|
+
returns.push(prev > 0 ? (curr - prev) / prev : 0);
|
|
246
81
|
}
|
|
247
|
-
|
|
82
|
+
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
|
83
|
+
const variance = returns.reduce((a, b) => a + (b - mean) ** 2, 0) / (returns.length - 1);
|
|
84
|
+
|
|
85
|
+
return { stdDev: Math.sqrt(variance), mean, drawdown: maxDrawdown };
|
|
248
86
|
}
|
|
249
87
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
88
|
+
process(context) {
|
|
89
|
+
const { mappings, prices, date } = context;
|
|
90
|
+
const { instrumentToTicker, sectorMapping } = mappings;
|
|
91
|
+
|
|
92
|
+
// Uses history from context
|
|
93
|
+
const priceData = prices?.history;
|
|
94
|
+
const todayDateStr = date.today;
|
|
95
|
+
|
|
96
|
+
if (!priceData || !todayDateStr) {
|
|
97
|
+
this.result = { by_instrument: {}, by_sector: {} };
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
254
100
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
101
|
+
const by_instrument = {};
|
|
102
|
+
const tickerToInstrument = {};
|
|
103
|
+
|
|
104
|
+
for (const p of priceData) {
|
|
105
|
+
const ticker = instrumentToTicker[p.instrumentId];
|
|
106
|
+
if (!ticker) continue;
|
|
107
|
+
tickerToInstrument[ticker] = p.instrumentId;
|
|
108
|
+
|
|
109
|
+
const metrics = {};
|
|
110
|
+
for (const range of RANGES) {
|
|
111
|
+
const priceArray = this._getHistoricalPriceArray(p, todayDateStr, range + 1);
|
|
112
|
+
const stats = this._calculateStats(priceArray);
|
|
113
|
+
|
|
114
|
+
metrics[`stdev_${range}d`] = stats.stdDev;
|
|
115
|
+
metrics[`max_drawdown_${range}d`] = stats.drawdown;
|
|
116
|
+
metrics[`volatility_annualized_${range}d`] = stats.stdDev * Math.sqrt(TRADING_DAYS_PER_YEAR);
|
|
117
|
+
metrics[`sharpe_ratio_${range}d`] = stats.stdDev > 0
|
|
118
|
+
? (stats.mean / stats.stdDev) * Math.sqrt(TRADING_DAYS_PER_YEAR)
|
|
119
|
+
: 0;
|
|
258
120
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
121
|
+
by_instrument[ticker] = metrics;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sector Aggregation
|
|
125
|
+
const sectorAggs = {};
|
|
126
|
+
for (const ticker in by_instrument) {
|
|
127
|
+
const instId = tickerToInstrument[ticker];
|
|
128
|
+
const sector = sectorMapping[instId] || "Unknown";
|
|
129
|
+
if (!sectorAggs[sector]) sectorAggs[sector] = { metrics: {}, counts: {} };
|
|
130
|
+
|
|
131
|
+
const data = by_instrument[ticker];
|
|
132
|
+
for (const key in data) {
|
|
133
|
+
if (!sectorAggs[sector].metrics[key]) {
|
|
134
|
+
sectorAggs[sector].metrics[key] = 0;
|
|
135
|
+
sectorAggs[sector].counts[key] = 0;
|
|
136
|
+
}
|
|
137
|
+
if (data[key] !== null) {
|
|
138
|
+
sectorAggs[sector].metrics[key] += data[key];
|
|
139
|
+
sectorAggs[sector].counts[key]++;
|
|
263
140
|
}
|
|
264
141
|
}
|
|
265
142
|
}
|
|
266
|
-
|
|
143
|
+
|
|
144
|
+
const by_sector = {};
|
|
145
|
+
for (const sector in sectorAggs) {
|
|
146
|
+
by_sector[sector] = {};
|
|
147
|
+
for (const key in sectorAggs[sector].metrics) {
|
|
148
|
+
const count = sectorAggs[sector].counts[key];
|
|
149
|
+
by_sector[sector][`average_${key}`] = count > 0 ? sectorAggs[sector].metrics[key] / count : null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.result = { by_instrument, by_sector };
|
|
267
154
|
}
|
|
268
|
-
}
|
|
269
155
|
|
|
156
|
+
async getResult() { return this.result; }
|
|
157
|
+
reset() { this.result = { by_instrument: {}, by_sector: {} }; }
|
|
158
|
+
}
|
|
270
159
|
module.exports = CorePriceMetrics;
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 1) for
|
|
3
|
-
*
|
|
4
|
-
* This metric provides the profitability ratio (profitable positions /
|
|
5
|
-
* unprofitable positions) for all open positions, grouped by sector.
|
|
2
|
+
* @fileoverview Calculation (Pass 1) for profitability ratio per sector.
|
|
3
|
+
* REFACTORED: Counts (No USD involved).
|
|
6
4
|
*/
|
|
7
|
-
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
8
|
-
|
|
9
5
|
class ProfitabilityRatioPerSector {
|
|
10
6
|
constructor() {
|
|
11
|
-
// We will store { [sectorName]: { profitable: 0, unprofitable: 0 } }
|
|
12
|
-
// --- STANDARD 0: RENAMED ---
|
|
13
7
|
this.sectorData = new Map();
|
|
14
|
-
|
|
15
|
-
this.sectorMap = null; // This will hold InstID -> SectorName
|
|
8
|
+
this.sectorMap = null;
|
|
16
9
|
}
|
|
17
10
|
|
|
18
|
-
/**
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
21
11
|
static getMetadata() {
|
|
22
12
|
return {
|
|
23
13
|
type: 'standard',
|
|
@@ -28,101 +18,66 @@ class ProfitabilityRatioPerSector {
|
|
|
28
18
|
};
|
|
29
19
|
}
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
* Statically declare dependencies.
|
|
33
|
-
*/
|
|
34
|
-
static getDependencies() {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
21
|
+
static getDependencies() { return []; }
|
|
37
22
|
|
|
38
|
-
/**
|
|
39
|
-
* Defines the output schema for this calculation.
|
|
40
|
-
*/
|
|
41
23
|
static getSchema() {
|
|
42
24
|
const sectorSchema = {
|
|
43
25
|
"type": "object",
|
|
44
26
|
"properties": {
|
|
45
|
-
"
|
|
46
|
-
"type": ["number", "null"],
|
|
47
|
-
"description": "Ratio of profitable to unprofitable positions (profitable / unprofitable). Null if 0 unprofitable."
|
|
48
|
-
},
|
|
27
|
+
"profitability_ratio": { "type": ["number", "null"] },
|
|
49
28
|
"profitable_count": { "type": "number" },
|
|
50
29
|
"unprofitable_count": { "type": "number" }
|
|
51
30
|
},
|
|
52
|
-
"required": ["
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"description": "Calculates the profitability ratio (profitable/unprofitable positions) per sector.",
|
|
58
|
-
"patternProperties": {
|
|
59
|
-
"^.*$": sectorSchema // Sector Name
|
|
60
|
-
},
|
|
61
|
-
"additionalProperties": sectorSchema
|
|
31
|
+
"required": ["profitability_ratio", "profitable_count", "unprofitable_count"]
|
|
62
32
|
};
|
|
33
|
+
return { "type": "object", "patternProperties": { "^.*$": sectorSchema } };
|
|
63
34
|
}
|
|
64
35
|
|
|
65
|
-
_initSector(
|
|
66
|
-
if (!this.sectorData.has(
|
|
67
|
-
this.sectorData.set(
|
|
36
|
+
_initSector(sector) {
|
|
37
|
+
if (!this.sectorData.has(sector)) {
|
|
38
|
+
this.sectorData.set(sector, { profitable: 0, unprofitable: 0 });
|
|
68
39
|
}
|
|
69
40
|
}
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!this.sectorMap)
|
|
75
|
-
// Load mappings on first process call from the standard context
|
|
76
|
-
this.sectorMap = context.sectorMapping;
|
|
77
|
-
}
|
|
42
|
+
process(context) {
|
|
43
|
+
const { extract } = context.math;
|
|
44
|
+
const { mappings, user } = context;
|
|
45
|
+
if (!this.sectorMap) this.sectorMap = mappings.sectorMapping;
|
|
78
46
|
|
|
79
|
-
const positions =
|
|
80
|
-
if (!positions || !Array.isArray(positions) || !this.sectorMap) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
47
|
+
const positions = extract.getPositions(user.portfolio.today, user.type);
|
|
83
48
|
|
|
84
49
|
for (const pos of positions) {
|
|
85
|
-
const
|
|
86
|
-
|
|
50
|
+
const instId = extract.getInstrumentId(pos);
|
|
51
|
+
if (!instId) continue;
|
|
87
52
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// --- STANDARD 0: FIXED ---
|
|
93
|
-
// Use the standard InstID -> SectorName map
|
|
94
|
-
const sectorName = this.sectorMap[instrumentId] || 'N/A';
|
|
95
|
-
this._initSector(sectorName);
|
|
96
|
-
const data = this.sectorData.get(sectorName);
|
|
53
|
+
const sector = this.sectorMap[instId] || 'Unknown';
|
|
54
|
+
const pnl = extract.getNetProfit(pos);
|
|
97
55
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
56
|
+
this._initSector(sector);
|
|
57
|
+
const data = this.sectorData.get(sector);
|
|
58
|
+
|
|
59
|
+
if (pnl > 0) data.profitable++;
|
|
60
|
+
else if (pnl < 0) data.unprofitable++;
|
|
104
61
|
}
|
|
105
62
|
}
|
|
106
63
|
|
|
107
|
-
getResult() {
|
|
64
|
+
async getResult() {
|
|
108
65
|
const result = {};
|
|
109
|
-
for (const [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
66
|
+
for (const [sector, data] of this.sectorData.entries()) {
|
|
67
|
+
if (data.profitable > 0 || data.unprofitable > 0) {
|
|
68
|
+
result[sector] = {
|
|
69
|
+
profitability_ratio: (data.unprofitable > 0) ? (data.profitable / data.unprofitable) : null,
|
|
70
|
+
profitable_count: data.profitable,
|
|
71
|
+
unprofitable_count: data.unprofitable
|
|
72
|
+
};
|
|
73
|
+
}
|
|
117
74
|
}
|
|
118
75
|
return result;
|
|
119
76
|
}
|
|
120
77
|
|
|
121
78
|
reset() {
|
|
122
79
|
this.sectorData.clear();
|
|
123
|
-
|
|
124
|
-
this.sectorMap = null; // Reset mappings
|
|
80
|
+
this.sectorMap = null;
|
|
125
81
|
}
|
|
126
82
|
}
|
|
127
|
-
|
|
128
83
|
module.exports = ProfitabilityRatioPerSector;
|