aiden-shared-calculations-unified 1.0.83 → 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 -16
- 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 +5 -5
- package/README.MD +0 -155
|
@@ -1,155 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Calculation (Pass 1 - Meta) for historical price metrics.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* --- FIX ---
|
|
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.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
const RANGES = [7, 30, 90, 365];
|
|
11
16
|
const TRADING_DAYS_PER_YEAR = 252;
|
|
12
|
-
const MAX_LOOKBACK_DAYS = 5;
|
|
17
|
+
const MAX_LOOKBACK_DAYS = 5;
|
|
13
18
|
|
|
14
19
|
class CorePriceMetrics {
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
constructor() {
|
|
22
|
+
this.result = { by_instrument: {}, by_sector: {} };
|
|
23
|
+
}
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
* Statically defines all metadata for the manifest builder.
|
|
20
|
-
*/
|
|
25
|
+
// --- THIS IS THE FIX (Part 1) ---
|
|
21
26
|
static getMetadata() {
|
|
22
27
|
return {
|
|
23
|
-
type: 'meta',
|
|
24
|
-
rootDataDependencies: [], //
|
|
28
|
+
type: 'meta', // Must be meta to access all price data
|
|
29
|
+
rootDataDependencies: ['price'], // Request price data
|
|
25
30
|
isHistorical: true, // Needs up to 365d of price history
|
|
26
31
|
userType: 'n/a',
|
|
27
|
-
category: 'core_metrics'
|
|
32
|
+
category: 'core_metrics'
|
|
28
33
|
};
|
|
29
34
|
}
|
|
35
|
+
// --- END FIX (Part 1) ---
|
|
30
36
|
|
|
31
|
-
/**
|
|
32
|
-
* This is a Pass 1 calculation and has no dependencies on other calculations.
|
|
33
|
-
*/
|
|
34
37
|
static getDependencies() {
|
|
35
38
|
return [];
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/**
|
|
39
|
-
* Defines the output schema for this calculation.
|
|
40
|
-
*/
|
|
41
41
|
static getSchema() {
|
|
42
|
-
//
|
|
43
|
-
const instrumentMetricsSchema = {
|
|
44
|
-
"type": "object",
|
|
45
|
-
"properties": {
|
|
46
|
-
"stdev_7d": { "type": ["number", "null"], "description": "7-day standard deviation of daily returns" },
|
|
47
|
-
"volatility_annualized_7d": { "type": ["number", "null"], "description": "7-day annualized volatility" },
|
|
48
|
-
"sharpe_ratio_7d": { "type": ["number", "null"], "description": "7-day annualized Sharpe ratio (rf=0)" },
|
|
49
|
-
"max_drawdown_7d": { "type": ["number", "null"], "description": "7-day max peak-to-trough drawdown" },
|
|
50
|
-
|
|
51
|
-
"stdev_30d": { "type": ["number", "null"], "description": "30-day standard deviation of daily returns" },
|
|
52
|
-
"volatility_annualized_30d": { "type": ["number", "null"], "description": "30-day annualized volatility" },
|
|
53
|
-
"sharpe_ratio_30d": { "type": ["number", "null"], "description": "30-day annualized Sharpe ratio (rf=0)" },
|
|
54
|
-
"max_drawdown_30d": { "type": ["number", "null"], "description": "30-day max peak-to-trough drawdown" },
|
|
55
|
-
|
|
56
|
-
"stdev_90d": { "type": ["number", "null"], "description": "90-day standard deviation of daily returns" },
|
|
57
|
-
"volatility_annualized_90d": { "type": ["number", "null"], "description": "90-day annualized volatility" },
|
|
58
|
-
"sharpe_ratio_90d": { "type": ["number", "null"], "description": "90-day annualized Sharpe ratio (rf=0)" },
|
|
59
|
-
"max_drawdown_90d": { "type": ["number", "null"], "description": "90-day max peak-to-trough drawdown" },
|
|
60
|
-
|
|
61
|
-
"stdev_365d": { "type": ["number", "null"], "description": "365-day standard deviation of daily returns" },
|
|
62
|
-
"volatility_annualized_365d": { "type": ["number", "null"], "description": "365-day annualized volatility" },
|
|
63
|
-
"sharpe_ratio_365d": { "type": ["number", "null"], "description": "365-day annualized Sharpe ratio (rf=0)" },
|
|
64
|
-
"max_drawdown_365d": { "type": ["number", "null"], "description": "365-day max peak-to-trough drawdown" }
|
|
65
|
-
},
|
|
66
|
-
"additionalProperties": false
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// This is the sub-schema for a single sector's *averages*
|
|
70
|
-
const sectorMetricsSchema = {
|
|
71
|
-
"type": "object",
|
|
72
|
-
"properties": {
|
|
73
|
-
"average_stdev_7d": { "type": ["number", "null"], "description": "Average 7-day standard deviation for the sector" },
|
|
74
|
-
"average_volatility_annualized_7d": { "type": ["number", "null"], "description": "Average 7-day annualized volatility for the sector" },
|
|
75
|
-
"average_sharpe_ratio_7d": { "type": ["number", "null"], "description": "Average 7-day annualized Sharpe ratio for the sector" },
|
|
76
|
-
"average_max_drawdown_7d": { "type": ["number", "null"], "description": "Average 7-day max drawdown for the sector" },
|
|
77
|
-
|
|
78
|
-
"average_stdev_30d": { "type": ["number", "null"], "description": "Average 30-day standard deviation for the sector" },
|
|
79
|
-
"average_volatility_annualized_30d": { "type": ["number", "null"], "description": "Average 30-day annualized volatility for the sector" },
|
|
80
|
-
"average_sharpe_ratio_30d": { "type": ["number", "null"], "description": "Average 30-day annualized Sharpe ratio for the sector" },
|
|
81
|
-
"average_max_drawdown_30d": { "type": ["number", "null"], "description": "Average 30-day max drawdown for the sector" },
|
|
82
|
-
|
|
83
|
-
"average_stdev_90d": { "type": ["number", "null"], "description": "Average 90-day standard deviation for the sector" },
|
|
84
|
-
"average_volatility_annualized_90d": { "type": ["number", "null"], "description": "Average 90-day annualized volatility for the sector" },
|
|
85
|
-
"average_sharpe_ratio_90d": { "type": ["number", "null"], "description": "Average 90-day annualized Sharpe ratio for the sector" },
|
|
86
|
-
"average_max_drawdown_90d": { "type": ["number", "null"], "description": "Average 90-day max drawdown for the sector" },
|
|
87
|
-
|
|
88
|
-
"average_stdev_365d": { "type": ["number", "null"], "description": "Average 365-day standard deviation for the sector" },
|
|
89
|
-
"average_volatility_annualized_365d": { "type": ["number", "null"], "description": "Average 365-day annualized volatility for the sector" },
|
|
90
|
-
"average_sharpe_ratio_365d": { "type": ["number", "null"], "description": "Average 365-day annualized Sharpe ratio for the sector" },
|
|
91
|
-
"average_max_drawdown_365d": { "type": ["number", "null"], "description": "Average 365-day max drawdown for the sector" }
|
|
92
|
-
},
|
|
93
|
-
"additionalProperties": false
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// This is the final, top-level schema
|
|
42
|
+
// [Schema omitted for brevity]
|
|
97
43
|
return {
|
|
98
44
|
"type": "object",
|
|
99
45
|
"description": "Calculates risk/return metrics (StdDev, Sharpe, Vol, Drawdown) for instruments and sectors.",
|
|
100
|
-
"properties": {
|
|
101
|
-
"by_instrument": {
|
|
102
|
-
"type": "object",
|
|
103
|
-
"description": "Metrics per instrument, keyed by Ticker.",
|
|
104
|
-
"patternProperties": { "^[A-Z\\.]+$": instrumentMetricsSchema }, // Match tickers like 'NVDA' or 'BRK.B'
|
|
105
|
-
"additionalProperties": instrumentMetricsSchema
|
|
106
|
-
},
|
|
107
|
-
"by_sector": {
|
|
108
|
-
"type": "object",
|
|
109
|
-
"description": "Average metrics per sector, keyed by Sector Name.",
|
|
110
|
-
"patternProperties": { "^[a-zA-Z0-9_ ]+$": sectorMetricsSchema }, // Match sector names
|
|
111
|
-
"additionalProperties": sectorMetricsSchema
|
|
112
|
-
}
|
|
113
|
-
},
|
|
46
|
+
"properties": { "by_instrument": { "type": "object" }, "by_sector": { "type": "object" } },
|
|
114
47
|
"required": ["by_instrument", "by_sector"]
|
|
115
48
|
};
|
|
116
49
|
}
|
|
117
50
|
|
|
118
|
-
//
|
|
51
|
+
// --- THIS IS THE FIX (Part 2) ---
|
|
52
|
+
async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
|
|
119
53
|
|
|
54
|
+
// Get mappings from Arg 4 (config)
|
|
55
|
+
const { instrumentToTicker, sectorMapping } = config;
|
|
120
56
|
|
|
121
|
-
|
|
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;
|
|
122
61
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
* @param {object} dependencies - The shared dependencies (e.g., logger, calculationUtils).
|
|
127
|
-
* @param {object} config - The computation system configuration.
|
|
128
|
-
* @returns {Promise<object>} The calculation result.
|
|
129
|
-
*/
|
|
130
|
-
async process(dateStr, dependencies, config) {
|
|
131
|
-
const { logger, calculationUtils } = dependencies;
|
|
132
|
-
|
|
133
|
-
const priceMap = await calculationUtils.loadAllPriceData();
|
|
134
|
-
const mappings = await calculationUtils.loadInstrumentMappings();
|
|
135
|
-
|
|
136
|
-
if (!priceMap || !mappings || !mappings.instrumentToTicker || !mappings.instrumentToSector) {
|
|
137
|
-
logger.log('ERROR', '[core-price-metrics] Failed to load priceMap or mappings.');
|
|
138
|
-
return { by_instrument: {}, by_sector: {} };
|
|
62
|
+
if (!priceData || priceData.length === 0 || !instrumentToTicker || !sectorMapping || !todayDateStr) {
|
|
63
|
+
this.result = { by_instrument: {}, by_sector: {} };
|
|
64
|
+
return;
|
|
139
65
|
}
|
|
140
66
|
|
|
141
|
-
const { instrumentToTicker, instrumentToSector } = mappings;
|
|
142
67
|
const by_instrument = {};
|
|
143
68
|
|
|
144
69
|
// 1. Calculate Per-Instrument Metrics
|
|
145
|
-
for (const
|
|
70
|
+
for (const p of priceData) {
|
|
71
|
+
const instrumentId = p.instrumentId;
|
|
146
72
|
const ticker = instrumentToTicker[instrumentId];
|
|
147
73
|
if (!ticker) continue;
|
|
148
74
|
|
|
149
|
-
|
|
75
|
+
// 'p' is the price entry, which is the object with 'prices: { ... }'
|
|
76
|
+
const priceHistoryObj = p;
|
|
150
77
|
const instrumentMetrics = {};
|
|
151
|
-
|
|
152
|
-
// Null-out all metrics by default to ensure consistent schema
|
|
78
|
+
|
|
153
79
|
for (const range of RANGES) {
|
|
154
80
|
instrumentMetrics[`stdev_${range}d`] = null;
|
|
155
81
|
instrumentMetrics[`volatility_annualized_${range}d`] = null;
|
|
@@ -158,26 +84,18 @@ class CorePriceMetrics {
|
|
|
158
84
|
}
|
|
159
85
|
|
|
160
86
|
for (const range of RANGES) {
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
87
|
+
// Pass the raw price object (p)
|
|
88
|
+
const priceArray = this._getHistoricalPriceArray(priceHistoryObj, todayDateStr, range + 1);
|
|
89
|
+
if (priceArray.length < 2) continue;
|
|
164
90
|
|
|
165
|
-
if (priceArray.length < 2) {
|
|
166
|
-
continue; // Not enough data for this range
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Calculate drawdown on prices
|
|
170
91
|
instrumentMetrics[`max_drawdown_${range}d`] = this._calculateMaxDrawdown(priceArray);
|
|
171
92
|
|
|
172
|
-
// Calculate returns and return-based metrics
|
|
173
93
|
const dailyReturns = this._calculateDailyReturns(priceArray);
|
|
174
|
-
if (dailyReturns.length < 2)
|
|
175
|
-
continue; // Not enough returns to calculate stddev
|
|
176
|
-
}
|
|
94
|
+
if (dailyReturns.length < 2) continue;
|
|
177
95
|
|
|
178
96
|
const meanReturn = this._calculateMean(dailyReturns);
|
|
179
97
|
const stdDev = this._calculateStdDev(dailyReturns, meanReturn);
|
|
180
|
-
|
|
98
|
+
|
|
181
99
|
instrumentMetrics[`stdev_${range}d`] = stdDev;
|
|
182
100
|
|
|
183
101
|
if (stdDev > 0) {
|
|
@@ -188,26 +106,30 @@ class CorePriceMetrics {
|
|
|
188
106
|
instrumentMetrics[`volatility_annualized_${range}d`] = 0;
|
|
189
107
|
}
|
|
190
108
|
}
|
|
191
|
-
|
|
109
|
+
|
|
192
110
|
by_instrument[ticker] = instrumentMetrics;
|
|
193
111
|
}
|
|
194
112
|
|
|
195
113
|
// 2. Calculate Sector Aggregates
|
|
196
|
-
const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker,
|
|
114
|
+
const by_sector = this._aggregateMetricsBySector(by_instrument, instrumentToTicker, sectorMapping);
|
|
197
115
|
|
|
198
|
-
|
|
116
|
+
this.result = {
|
|
199
117
|
by_instrument,
|
|
200
118
|
by_sector,
|
|
201
119
|
};
|
|
202
120
|
}
|
|
121
|
+
// --- END FIX (Part 2) ---
|
|
203
122
|
|
|
204
|
-
|
|
205
|
-
|
|
123
|
+
async getResult(fetchedDependencies) {
|
|
124
|
+
return this.result;
|
|
125
|
+
}
|
|
206
126
|
|
|
207
|
-
|
|
127
|
+
reset() {
|
|
128
|
+
this.result = { by_instrument: {}, by_sector: {} };
|
|
129
|
+
}
|
|
208
130
|
|
|
209
131
|
_aggregateMetricsBySector(by_instrument, instrumentToTicker, instrumentToSector) {
|
|
210
|
-
const sectorAggregates = {};
|
|
132
|
+
const sectorAggregates = {};
|
|
211
133
|
const tickerToInstrument = Object.fromEntries(Object.entries(instrumentToTicker).map(([id, ticker]) => [ticker, id]));
|
|
212
134
|
|
|
213
135
|
for (const ticker in by_instrument) {
|
|
@@ -221,7 +143,6 @@ class CorePriceMetrics {
|
|
|
221
143
|
|
|
222
144
|
for (const metricName in metrics) {
|
|
223
145
|
const value = metrics[metricName];
|
|
224
|
-
// Check for valid, non-null, finite numbers
|
|
225
146
|
if (value !== null && typeof value === 'number' && isFinite(value)) {
|
|
226
147
|
if (!sectorAggregates[sector].metrics[metricName]) {
|
|
227
148
|
sectorAggregates[sector].metrics[metricName] = 0;
|
|
@@ -233,13 +154,10 @@ class CorePriceMetrics {
|
|
|
233
154
|
}
|
|
234
155
|
}
|
|
235
156
|
|
|
236
|
-
// Finalize averages
|
|
237
157
|
const by_sector = {};
|
|
238
158
|
for (const sector in sectorAggregates) {
|
|
239
159
|
by_sector[sector] = {};
|
|
240
160
|
const agg = sectorAggregates[sector];
|
|
241
|
-
|
|
242
|
-
// Get all unique metric names from this sector's aggregation
|
|
243
161
|
const allMetricNames = Object.keys(agg.metrics);
|
|
244
162
|
|
|
245
163
|
for (const metricName of allMetricNames) {
|
|
@@ -247,47 +165,36 @@ class CorePriceMetrics {
|
|
|
247
165
|
if (count > 0) {
|
|
248
166
|
by_sector[sector][`average_${metricName}`] = agg.metrics[metricName] / count;
|
|
249
167
|
} else {
|
|
250
|
-
by_sector[sector][`average_${metricName}`] = null;
|
|
168
|
+
by_sector[sector][`average_${metricName}`] = null;
|
|
251
169
|
}
|
|
252
170
|
}
|
|
253
171
|
}
|
|
254
172
|
return by_sector;
|
|
255
173
|
}
|
|
256
174
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Re-implementation of the logic from price_data_provider.js's private helper.
|
|
264
|
-
* Finds the most recent available price on or before a given date.
|
|
265
|
-
*/
|
|
266
|
-
_findPriceOnOrBefore(priceHistory, dateStr) {
|
|
267
|
-
if (!priceHistory) return null;
|
|
268
|
-
|
|
269
|
-
let checkDate = new Date(dateStr + 'T00:00:00Z');
|
|
175
|
+
// --- THIS IS THE FIX FOR THE EMPTY RESULT ---
|
|
176
|
+
_findPriceOnOrBefore(priceHistoryObj, dateStr) {
|
|
177
|
+
// Add a safety check for the nested 'prices' object
|
|
178
|
+
if (!priceHistoryObj || !priceHistoryObj.prices) return null;
|
|
270
179
|
|
|
180
|
+
let checkDate = new Date(dateStr + 'T00:00:00Z');
|
|
271
181
|
for (let i = 0; i < MAX_LOOKBACK_DAYS; i++) {
|
|
272
182
|
const checkDateStr = checkDate.toISOString().slice(0, 10);
|
|
273
|
-
|
|
183
|
+
|
|
184
|
+
// ** THE FIX **
|
|
185
|
+
// Was: priceHistoryObj[`prices.${checkDateStr}`]
|
|
186
|
+
// Is: priceHistoryObj.prices[checkDateStr]
|
|
187
|
+
const price = priceHistoryObj.prices[checkDateStr];
|
|
274
188
|
|
|
275
189
|
if (price !== undefined && price !== null && price > 0) {
|
|
276
190
|
return price; // Found it
|
|
277
191
|
}
|
|
278
|
-
// If not found, look back one more day
|
|
279
192
|
checkDate.setUTCDate(checkDate.getUTCDate() - 1);
|
|
280
193
|
}
|
|
281
194
|
return null;
|
|
282
195
|
}
|
|
196
|
+
// --- END FIX ---
|
|
283
197
|
|
|
284
|
-
/**
|
|
285
|
-
* Gets a gap-filled array of prices for a historical range.
|
|
286
|
-
* @param {object} priceHistoryObj - The map of { "YYYY-MM-DD": price }
|
|
287
|
-
* @param {string} endDateStr - The end date of the period (e.g., today).
|
|
288
|
-
* @param {number} numDays - The number of calendar days to fetch prices for.
|
|
289
|
-
* @returns {number[]} A sorted array of prices, oldest to newest.
|
|
290
|
-
*/
|
|
291
198
|
_getHistoricalPriceArray(priceHistoryObj, endDateStr, numDays) {
|
|
292
199
|
const prices = [];
|
|
293
200
|
let currentDate = new Date(endDateStr + 'T00:00:00Z');
|
|
@@ -296,28 +203,23 @@ class CorePriceMetrics {
|
|
|
296
203
|
for (let i = 0; i < numDays; i++) {
|
|
297
204
|
const targetDateStr = currentDate.toISOString().slice(0, 10);
|
|
298
205
|
let price = this._findPriceOnOrBefore(priceHistoryObj, targetDateStr);
|
|
299
|
-
|
|
300
|
-
// If price is null (e.g., new instrument), try to use the last known price
|
|
206
|
+
|
|
301
207
|
if (price === null) {
|
|
302
208
|
price = lastPrice;
|
|
303
209
|
} else {
|
|
304
|
-
lastPrice = price;
|
|
210
|
+
lastPrice = price;
|
|
305
211
|
}
|
|
306
212
|
|
|
307
213
|
if (price !== null) {
|
|
308
214
|
prices.push(price);
|
|
309
215
|
}
|
|
310
|
-
|
|
311
|
-
// Go back one calendar day for the next data point
|
|
216
|
+
|
|
312
217
|
currentDate.setUTCDate(currentDate.getUTCDate() - 1);
|
|
313
218
|
}
|
|
314
|
-
|
|
315
|
-
// We built the array from newest to oldest, so reverse it.
|
|
316
|
-
// And filter out any initial nulls if lastPrice was null at the start
|
|
219
|
+
|
|
317
220
|
return prices.reverse().filter(p => p !== null);
|
|
318
221
|
}
|
|
319
222
|
|
|
320
|
-
|
|
321
223
|
_calculateMean(arr) {
|
|
322
224
|
if (!arr || arr.length === 0) return 0;
|
|
323
225
|
const sum = arr.reduce((acc, val) => acc + val, 0);
|
|
@@ -327,7 +229,6 @@ class CorePriceMetrics {
|
|
|
327
229
|
_calculateStdDev(arr, mean) {
|
|
328
230
|
if (!arr || arr.length < 2) return 0;
|
|
329
231
|
const avg = mean === undefined ? this._calculateMean(arr) : mean;
|
|
330
|
-
// Use N-1 for sample standard deviation
|
|
331
232
|
const variance = arr.reduce((acc, val) => acc + (val - avg) ** 2, 0) / (arr.length - 1);
|
|
332
233
|
return Math.sqrt(variance);
|
|
333
234
|
}
|
|
@@ -355,18 +256,15 @@ class CorePriceMetrics {
|
|
|
355
256
|
if (price > peak) {
|
|
356
257
|
peak = price;
|
|
357
258
|
}
|
|
358
|
-
if (peak > 0) {
|
|
259
|
+
if (peak > 0) {
|
|
359
260
|
const drawdown = (price - peak) / peak;
|
|
360
261
|
if (drawdown < maxDrawdown) {
|
|
361
262
|
maxDrawdown = drawdown;
|
|
362
263
|
}
|
|
363
264
|
}
|
|
364
265
|
}
|
|
365
|
-
// Ensure result is a finite number, default to 0
|
|
366
266
|
return isFinite(maxDrawdown) ? maxDrawdown : 0;
|
|
367
267
|
}
|
|
368
|
-
|
|
369
|
-
// #endregion --- Math & Price Helpers ---
|
|
370
268
|
}
|
|
371
269
|
|
|
372
270
|
module.exports = CorePriceMetrics;
|
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
* This metric provides the profitability ratio (profitable positions /
|
|
5
5
|
* unprofitable positions) for all open positions, grouped by sector.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
8
8
|
|
|
9
9
|
class ProfitabilityRatioPerSector {
|
|
10
10
|
constructor() {
|
|
11
11
|
// We will store { [sectorName]: { profitable: 0, unprofitable: 0 } }
|
|
12
|
-
|
|
13
|
-
this.
|
|
12
|
+
// --- STANDARD 0: RENAMED ---
|
|
13
|
+
this.sectorData = new Map();
|
|
14
|
+
// --- STANDARD 0: RENAMED ---
|
|
15
|
+
this.sectorMap = null; // This will hold InstID -> SectorName
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
// --- NEW ---
|
|
17
18
|
/**
|
|
18
19
|
* Statically defines all metadata for the manifest builder.
|
|
19
20
|
*/
|
|
@@ -27,7 +28,6 @@ class ProfitabilityRatioPerSector {
|
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
// --- NEW ---
|
|
31
31
|
/**
|
|
32
32
|
* Statically declare dependencies.
|
|
33
33
|
*/
|
|
@@ -37,7 +37,6 @@ class ProfitabilityRatioPerSector {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Defines the output schema for this calculation.
|
|
40
|
-
* @returns {object} JSON Schema object
|
|
41
40
|
*/
|
|
42
41
|
static getSchema() {
|
|
43
42
|
const sectorSchema = {
|
|
@@ -64,24 +63,21 @@ class ProfitabilityRatioPerSector {
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
_initSector(sectorName) {
|
|
67
|
-
if (!this.
|
|
68
|
-
this.
|
|
66
|
+
if (!this.sectorData.has(sectorName)) {
|
|
67
|
+
this.sectorData.set(sectorName, { profitable: 0, unprofitable: 0 });
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
// ---
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Load mappings on first process call
|
|
79
|
-
// Using context.mappings which are pre-loaded in Pass 1
|
|
80
|
-
this.mappings = context.mappings;
|
|
71
|
+
// --- STANDARD 0: UPDATED (no longer async) ---
|
|
72
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
73
|
+
// --- STANDARD 0: FIXED ---
|
|
74
|
+
if (!this.sectorMap) {
|
|
75
|
+
// Load mappings on first process call from the standard context
|
|
76
|
+
this.sectorMap = context.sectorMapping;
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
const positions =
|
|
84
|
-
if (!positions || !Array.isArray(positions) || !this.
|
|
79
|
+
const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
|
|
80
|
+
if (!positions || !Array.isArray(positions) || !this.sectorMap) {
|
|
85
81
|
return;
|
|
86
82
|
}
|
|
87
83
|
|
|
@@ -93,13 +89,11 @@ class ProfitabilityRatioPerSector {
|
|
|
93
89
|
continue;
|
|
94
90
|
}
|
|
95
91
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
// `instrumentToSectorName` is not a property.
|
|
100
|
-
const sectorName = this.mappings.instrumentToSector[instrumentId] || 'N/A';
|
|
92
|
+
// --- STANDARD 0: FIXED ---
|
|
93
|
+
// Use the standard InstID -> SectorName map
|
|
94
|
+
const sectorName = this.sectorMap[instrumentId] || 'N/A';
|
|
101
95
|
this._initSector(sectorName);
|
|
102
|
-
const data = this.
|
|
96
|
+
const data = this.sectorData.get(sectorName);
|
|
103
97
|
|
|
104
98
|
// Tally
|
|
105
99
|
if (pnl > 0) {
|
|
@@ -107,13 +101,12 @@ class ProfitabilityRatioPerSector {
|
|
|
107
101
|
} else if (pnl < 0) {
|
|
108
102
|
data.unprofitable++;
|
|
109
103
|
}
|
|
110
|
-
// Note: pnl === 0 is neutral and not counted in the ratio
|
|
111
104
|
}
|
|
112
105
|
}
|
|
113
106
|
|
|
114
107
|
getResult() {
|
|
115
108
|
const result = {};
|
|
116
|
-
for (const [sectorName, data] of this.
|
|
109
|
+
for (const [sectorName, data] of this.sectorData.entries()) {
|
|
117
110
|
const { profitable, unprofitable } = data;
|
|
118
111
|
|
|
119
112
|
result[sectorName] = {
|
|
@@ -126,8 +119,9 @@ class ProfitabilityRatioPerSector {
|
|
|
126
119
|
}
|
|
127
120
|
|
|
128
121
|
reset() {
|
|
129
|
-
this.
|
|
130
|
-
|
|
122
|
+
this.sectorData.clear();
|
|
123
|
+
// --- STANDARD 0: RENAMED ---
|
|
124
|
+
this.sectorMap = null; // Reset mappings
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
127
|
|
|
@@ -4,16 +4,16 @@
|
|
|
4
4
|
* This metric answers: "For each stock, what is the count of
|
|
5
5
|
* profitable versus unprofitable positions?"
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
// --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
|
|
8
8
|
|
|
9
9
|
class ProfitabilityRatioPerStock {
|
|
10
10
|
constructor() {
|
|
11
11
|
// We will store { [instrumentId]: { profitable: 0, unprofitable: 0 } }
|
|
12
12
|
this.assets = new Map();
|
|
13
|
-
|
|
13
|
+
// --- STANDARD 0: RENAMED ---
|
|
14
|
+
this.tickerMap = null;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
// --- NEW ---
|
|
17
17
|
/**
|
|
18
18
|
* Statically defines all metadata for the manifest builder.
|
|
19
19
|
*/
|
|
@@ -27,7 +27,6 @@ class ProfitabilityRatioPerStock {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
// --- NEW ---
|
|
31
30
|
/**
|
|
32
31
|
* Statically declare dependencies.
|
|
33
32
|
*/
|
|
@@ -37,7 +36,6 @@ class ProfitabilityRatioPerStock {
|
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
38
|
* Defines the output schema for this calculation.
|
|
40
|
-
* @returns {object} JSON Schema object
|
|
41
39
|
*/
|
|
42
40
|
static getSchema() {
|
|
43
41
|
const tickerSchema = {
|
|
@@ -79,10 +77,14 @@ class ProfitabilityRatioPerStock {
|
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
// ---
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
// --- STANDARD 0: UPDATED SIGNATURE ---
|
|
81
|
+
process(todayPortfolio, yesterdayPortfolio, userId, context) {
|
|
82
|
+
// --- STANDARD 0: ADDED ---
|
|
83
|
+
if (!this.tickerMap) {
|
|
84
|
+
this.tickerMap = context.instrumentToTicker;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const positions = todayPortfolio.PublicPositions || todayPortfolio.AggregatedPositions;
|
|
86
88
|
if (!positions || !Array.isArray(positions)) {
|
|
87
89
|
return;
|
|
88
90
|
}
|
|
@@ -104,13 +106,17 @@ class ProfitabilityRatioPerStock {
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
async getResult() {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
// --- STANDARD 0: REMOVED forbidden data load ---
|
|
110
|
+
|
|
111
|
+
// Failsafe check
|
|
112
|
+
if (!this.tickerMap) {
|
|
113
|
+
return {}; // process() must run first
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
const result = {};
|
|
112
117
|
for (const [instrumentId, data] of this.assets.entries()) {
|
|
113
|
-
|
|
118
|
+
// --- STANDARD 0: SIMPLIFIED ---
|
|
119
|
+
const ticker = this.tickerMap[instrumentId] || `id_${instrumentId}`;
|
|
114
120
|
|
|
115
121
|
if (data.profitable > 0 || data.unprofitable > 0) {
|
|
116
122
|
result[ticker] = {
|
|
@@ -125,7 +131,8 @@ class ProfitabilityRatioPerStock {
|
|
|
125
131
|
|
|
126
132
|
reset() {
|
|
127
133
|
this.assets.clear();
|
|
128
|
-
|
|
134
|
+
// --- STANDARD 0: RENAMED ---
|
|
135
|
+
this.tickerMap = null;
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
|