institutional-trader/backend/src/factorlab/metrics.js

85 lines
2.3 KiB
JavaScript

export function rank(arr) {
const sorted = arr.map((val, ind) => ({ val, ind })).sort((a, b) => a.val - b.val);
const ranks = new Array(arr.length);
for (let i = 0; i < sorted.length; i++) {
ranks[sorted[i].ind] = i + 1;
}
// Handle ties (average rank) if needed, but simple rank is fine for large N
return ranks;
}
export function spearman(x, y) {
if (x.length !== y.length || x.length === 0) return null;
const rankX = rank(x);
const rankY = rank(y);
const n = x.length;
let dSqSum = 0;
for (let i = 0; i < n; i++) {
dSqSum += Math.pow(rankX[i] - rankY[i], 2);
}
return 1 - ((6 * dSqSum) / (n * (Math.pow(n, 2) - 1)));
}
// Simple mean
export function mean(arr) {
if (!arr || arr.length === 0) return 0;
return arr.reduce((a, b) => a + b, 0) / arr.length;
}
// Simple standard deviation
export function std(arr, m) {
if (!arr || arr.length <= 1) return 1;
const variance = arr.reduce((sum, v) => sum + Math.pow(v - m, 2), 0) / (arr.length - 1);
return Math.sqrt(variance) || 1;
}
/**
* Calculate Newey-West standard error for a time-series of means
* Lags = 0 is equivalent to standard error.
* For N-month overlapping returns, lag = N - 1.
*/
export function neweyWestStdErr(ts, lags) {
const n = ts.length;
if (n <= 1) return 1;
const m = mean(ts);
// Variance
let s0 = 0;
for (let i = 0; i < n; i++) {
s0 += Math.pow(ts[i] - m, 2);
}
s0 = s0 / n;
// Covariances
let sLags = 0;
for (let l = 1; l <= lags; l++) {
let cov = 0;
for (let i = l; i < n; i++) {
cov += (ts[i] - m) * (ts[i - l] - m);
}
cov = cov / n;
const weight = 1 - (l / (lags + 1));
sLags += 2 * weight * cov;
}
const S = s0 + sLags;
return Math.sqrt(S / n) || (std(ts, m) / Math.sqrt(n)); // fallback
}
/**
* Compute turnover given two sets of weights.
* w1, w2 are objects: { 'AAPL': 0.05, 'MSFT': 0.02, ... }
* turnover = 0.5 * sum(|w2_i - w1_i|)
*/
export function calculateTurnover(wOld, wNew) {
if (!wOld) return 1.0; // 100% turnover on first month
let turnover = 0;
const allTickers = new Set([...Object.keys(wOld), ...Object.keys(wNew)]);
for (const t of allTickers) {
const oldWt = wOld[t] || 0;
const newWt = wNew[t] || 0;
turnover += Math.abs(newWt - oldWt);
}
return 0.5 * turnover;
}