/** Pearson correlation of daily log returns between two aligned series (ยง186). */ export function alignedLogReturns(a: Array<{ date: string; value: number }>, b: Array<{ date: string; value: number }>): Array<[number, number]> { const mb = new Map(b.map((p) => [p.date, p.value])); const pairs: Array<[number, number]> = []; let prevA: number | null = null; let prevB: number | null = null; for (const p of a) { const vb = mb.get(p.date); if (vb === undefined) continue; if (prevA !== null && prevB !== null && prevA > 0 && prevB > 0 && p.value > 0 && vb > 0) pairs.push([Math.log(p.value / prevA), Math.log(vb / prevB)]); prevA = p.value; prevB = vb; } return pairs; } export function pearson(pairs: Array<[number, number]>): { r: number; n: number } | null { const n = pairs.length; if (n < 10) return null; const mx = pairs.reduce((s, p) => s + p[0], 0) / n; const my = pairs.reduce((s, p) => s + p[1], 0) / n; let sxy = 0; let sxx = 0; let syy = 0; for (const [x, y] of pairs) { sxy += (x - mx) * (y - my); sxx += (x - mx) ** 2; syy += (y - my) ** 2; } if (sxx === 0 || syy === 0) return { r: 0, n }; return { r: Math.round((sxy / Math.sqrt(sxx * syy)) * 1000) / 1000, n }; } export function correlationMatrix(series: Record>, sinceDate?: string): Array<{ a: string; b: string; r: number; n: number }> { const keys = Object.keys(series); const out: Array<{ a: string; b: string; r: number; n: number }> = []; for (let i = 0; i < keys.length; i++) { for (let j = i; j < keys.length; j++) { const A = sinceDate ? series[keys[i]!]!.filter((p) => p.date >= sinceDate) : series[keys[i]!]!; const B = sinceDate ? series[keys[j]!]!.filter((p) => p.date >= sinceDate) : series[keys[j]!]!; const p = pearson(alignedLogReturns(A, B)); if (p) out.push({ a: keys[i]!, b: keys[j]!, r: i === j ? 1 : p.r, n: p.n }); } } return out; }