Divided by the Sum, Not the Average — JavaScript Bug Hunt

Modelled on JPMorgan Chase, 2012 (the "London Whale"): the bank's own task-force report into the Chief Investment Office's trading losses of more than $6…

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Easy
  • Concepts: Money, Math
  • Modelled on: JPMorgan Chase · 2012
  • Visible tests: a flat series has no volatility; a relative change divides by the average of the two rates
  • Reward: 50 XP for a complete fix

Briefing

Modelled on JPMorgan Chase, 2012 (the "London Whale"): the bank's own task-force report into the Chief Investment Office's trading losses of more than $6 billion found that a new value-at-risk model was run through spreadsheets, and that one step — after subtracting an old rate from a new one — divided by their sum instead of their average. That muted the measured volatility by a factor of two and lowered the reported VaR.

risk.js is a reconstruction of that step: it turns a series of daily hazard-rate marks into relative changes, then into a volatility and a 95% VaR.

Fix relativeChange.

Bug report

BUG-VAR-CIO · Priority: Critical · Reported by: model review

relativeChange(oldRate, newRate) = (newRate - oldRate) / average(oldRate, newRate) where average(a, b) = (a + b) / 2.

  • changes(rates): relativeChange for each consecutive pair
  • volatility(rates): stats.round6(population stdev of changes(rates))
  • valueAtRisk(position, rates): Math.round(stats.Z_95 volatility(rates) position)

Examples: relativeChange(0.02, 0.03) = 0.4; volatility([0.02, 0.03, 0.02, 0.03]) = 0.377124; valueAtRisk(1000000, that series) = 620369.

Observed: every volatility the model reports is exactly half the desk's own recalculation.

Logs

[var] book=synthetic-credit vol=0.188562 var95=310185
[model-review] independent recalculation: vol=0.377124 var95=620369

The code as shipped

src/risk/risk.js (editable)

var stats = require("./stats");

// Relative change in a hazard rate between two daily marks.
function relativeChange(oldRate, newRate) {
  return (newRate - oldRate) / (oldRate + newRate);
}
exports.relativeChange = relativeChange;

exports.changes = function (rates) {
  var out = [];
  for (var i = 1; i < rates.length; i++) out.push(relativeChange(rates[i - 1], rates[i]));
  return out;
};

exports.volatility = function (rates) {
  return stats.round6(stats.stdev(exports.changes(rates)));
};

exports.valueAtRisk = function (position, rates) {
  return Math.round(stats.Z_95 * exports.volatility(rates) * position);
};

Read-only context: src/risk/stats.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.