85 lines
2.3 KiB
JavaScript
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;
|
|
}
|