The realised variance, live¶
The realised variance one sample on, v′ = v + r²: the sum of the squared log returns over the window so far, the mean return taken as zero, each sample's square added as it is taken (lusine.finance.realised_variance). The window and the sampling are the game's. Below, RiskMetrics' twenty daily returns of USD/DEM are summed on your GPU as you read, a day a step, and again; and four series of returns drawn uniform with a spread of 1% are summed over two hundred samples, and over their first hundred and their second hundred apart.
This page runs the program on your GPU through WebGPU, so it needs a browser with WebGPU (a recent Chrome, Edge, Safari or Firefox). Without it the program does not run and nothing is drawn below.
What it shows¶
- The strip above the controls is green while every check holds, and turns red for good once one fails.
- RiskMetrics' window: the chart draws the realised variance so far, in percent squared, a day a step, rising by each day's square to 3.085 at the twentieth. Below it, the day, the kernel's total and the model's.
- Windows end to end: the chart draws the first series' total over all its samples so far, and its two halves' totals added, in percent squared, one line over the other. Below it, the sample.
- The checks: the binary32 model, the kernel's total after each of RiskMetrics' days the bits of a model worked on the host, each square and each sum rounded once to binary32, in order; RiskMetrics' 3.085, the twenty days' total 3.0849 percent squared within 0.0005, the digits the returns are printed to carry (RiskMetrics Technical Document, 4th ed., 1996, Table 5.3); windows end to end, each series' total over all its samples the first hundred's and the second's added, within 200 unit roundoffs (2⁻²⁴); nothing, and a fall, a sample of nothing keeping the total bit for bit, and a fall of 2% adding what a rise of 2% would. Each is held at every step.
Every number here is computed by the program on the GPU; the page only reads the values back and draws them. The seed and the series' spread are the program's settings, which the host may write; this page offers no control to change them yet.
The program¶
RiskMetrics' days, the window, the series and the checks, the panels the page draws, and the one procedure, main, run each step: a day added to the window, a sample to each series and its halves, and the checks.
// The realised variance's live page: RiskMetrics' twenty days of USD/DEM summed a day a step by the library's kernel
// against a binary32 model's sums written out, and four series of drawn returns summed over two hundred samples beside
// their two halves summed apart, with the page's checks computed here on the GPU and shown by the panels the program
// declares.
namespace realised_variance;
lusine.version version = { { number = 1 } };
// The reader's settings: the seed the series are drawn from, and their spread a sample.
host struct { integer seed = 418; float spread = 0.01; } settings;
// RiskMetrics' Table 5.3: USD/DEM's daily returns from 28 March to 24 April 1996, as binary32 holds them, and a
// binary32 model's total after each day, the squares added in order, each square and each sum rounded once, worked on
// the host.
struct day { integer number; float sample; float model; };
host table<day> days(capacity = 20) = {
{ number = 0, sample = 0.0063299997709691525, model = 4.0068898670142516e-05 },
{ number = 1, sample = 0.0011500000255182385, model = 4.139139855396934e-05 },
{ number = 2, sample = -0.0045900000259280205, model = 6.245949771255255e-05 },
{ number = 3, sample = 0.0009299999801442027, model = 6.332439807010815e-05 },
{ number = 4, sample = 0.0017600000137463212, model = 6.642199878115207e-05 },
{ number = 5, sample = -0.0008699999889358878, model = 6.717890209984034e-05 },
{ number = 6, sample = -0.0014199999859556556, model = 6.919530278537422e-05 },
{ number = 7, sample = 0.0032399999909102917, model = 7.969290163600817e-05 },
{ number = 8, sample = -0.009429999627172947, model = 0.00016861778567545116 },
{ number = 9, sample = -0.005280000157654285, model = 0.00019649618479888886 },
{ number = 10, sample = -0.001069999998435378, model = 0.00019764108583331108 },
{ number = 11, sample = -0.0015899999998509884, model = 0.00020016919006593525 },
{ number = 12, sample = -0.004449999891221523, model = 0.00021997168369125575 },
{ number = 13, sample = 0.0005300000193528831, model = 0.00022025257931090891 },
{ number = 14, sample = 0.0015200000489130616, model = 0.0002225629723398015 },
{ number = 15, sample = -0.0031799999997019768, model = 0.00023267537471838295 },
{ number = 16, sample = 0.004240000154823065, model = 0.0002506529854144901 },
{ number = 17, sample = -0.007079999893903732, model = 0.00030077938572503626 },
{ number = 18, sample = -0.0010499999625608325, model = 0.00030188189703039825 },
{ number = 19, sample = -0.002570000011473894, model = 0.00030848680762574077 },
};
// For the limits: a total of 0.0003, and a rise of 2%.
host struct { float total = 0.0003; float rise = 0.02; } limit;
// The window: the day, from 0 to 19 and round again; the kernel's total so far, the model's, and the total in percent
// squared for the chart.
struct { integer day = -1; float total; float model; float percent; } window;
// Four series, a sample each step uniform with the spread: the total over all two hundred samples, over the first
// hundred and over the second.
struct series { integer index; float sample; float whole; float first; float second; bool joins; };
table<series> serieses(capacity = 4) = { { index = 0 }, { index = 1 }, { index = 2 }, { index = 3 } };
// The samples: the step within two hundred, and the first series' totals, for the chart, in percent squared.
struct { integer step = -1; float whole; float halves; } samples;
// The checks: the kernel's total after each of RiskMetrics' days the model's bit for bit, and the twenty days 3.085
// percent squared to the digits the printed returns carry; each series' total over all its samples its two halves'
// summed within 200 unit roundoffs (2^-24); a sample of nothing keeping the total bit for bit, and a fall adding what
// its rise would.
struct { bool model = true; bool printed = true; bool joins = true; bool limits = true; } checks;
// A strip of lamps the page draws, lit green while every check holds and red once one fails.
struct lamp { vector<float, 4> colour; };
lattice<lamp, 2> lamps(spacing = 1 m, boundary = wall);
vector<float, 4> lamp_colour(bool holds)
{
return holds ? vector<float, 4>(0.15, 0.6, 0.3, 1.0) : vector<float, 4>(0.75, 0.15, 0.15, 1.0);
}
float u() { return 1.0 / 16777216.0; }
// Day k's return and the model's total after it.
float on_day(table<integer> numbers, table<float> values, integer day)
{
let k = day;
return sum(values) where numbers == k;
}
float drawn(integer seed, integer step, integer index, float spread)
{
let u = lusine.error.uniform(drawn = lusine.error.draw(seed = bits(seed), step = bits(step), key = fnv1a("series"),
index = bits(index)));
return sqrt(3.0) * spread * (2.0 * u - 1.0);
}
bool joining(float whole, float first, float second) { return abs(whole / (first + second) - 1.0) <= 200.0 * u(); }
integer broken(table<bool> joins) { return count(joins) where !joins; }
bool limits(float total, float rise)
{
let kept = lusine.finance.realised_variance(total, sample = 0.0 * rise);
let up = lusine.finance.realised_variance(total, sample = rise);
let down = lusine.finance.realised_variance(total, sample = -rise);
return kept == total && up == down && up > total;
}
output table<lusine.widget> panels = {
lusine.series { { panel = "RiskMetrics' window", chart = "realised variance so far, %²", quantity = window.percent,
steps = 20, label = "the kernel's" } },
lusine.value { { panel = "RiskMetrics' window", quantity = window.day, label = "day" } },
lusine.value { { panel = "RiskMetrics' window", quantity = window.total, label = "the kernel's total" } },
lusine.value { { panel = "RiskMetrics' window", quantity = window.model, label = "the model's" } },
lusine.series { { panel = "windows end to end", chart = "the first series, %²", quantity = samples.whole,
steps = 200, label = "over all" } },
lusine.series { { panel = "windows end to end", chart = "the first series, %²", quantity = samples.halves,
steps = 200, label = "two halves" } },
lusine.value { { panel = "windows end to end", quantity = samples.step, label = "sample" } },
lusine.value { { panel = "the checks", quantity = checks.model, label = "the binary32 model" } },
lusine.value { { panel = "the checks", quantity = checks.printed, label = "RiskMetrics' 3.085" } },
lusine.value { { panel = "the checks", quantity = checks.joins, label = "windows end to end" } },
lusine.value { { panel = "the checks", quantity = checks.limits, label = "nothing, and a fall" } },
};
void main()
{
window.day = window.day >= 19 ? 0 : window.day + 1;
window.total = lusine.finance.realised_variance(total = window.day == 0 ? 0.0 : window.total,
sample = on_day(numbers = days.number, values = days.sample,
day = window.day));
window.model = on_day(numbers = days.number, values = days.model, day = window.day);
window.percent = window.total * 10000.0;
checks.model = checks.model && window.total == window.model;
checks.printed = checks.printed && (window.day < 19 || abs(window.percent - 3.0849) < 0.0005);
samples.step = samples.step >= 199 ? 0 : samples.step + 1;
serieses.sample = drawn(settings.seed, step = samples.step, serieses.index, settings.spread);
serieses.whole = lusine.finance.realised_variance(total = samples.step == 0 ? 0.0 : serieses.whole,
serieses.sample);
serieses.first = samples.step < 100 ? lusine.finance.realised_variance(total = samples.step == 0 ? 0.0
: serieses.first,
serieses.sample)
: serieses.first;
serieses.second = samples.step < 100 ? 0.0 : lusine.finance.realised_variance(total = serieses.second,
serieses.sample);
serieses.joins = joining(serieses.whole, serieses.first, serieses.second);
samples.whole = 10000.0 * (sum(serieses.whole) where serieses.index == 0);
samples.halves = 10000.0 * (sum(serieses.first + serieses.second) where serieses.index == 0);
checks.joins = checks.joins && broken(joins = serieses.joins) == 0;
checks.limits = checks.limits && limits(limit.total, limit.rise);
lamps.colour = lamp_colour(holds = checks.model && checks.printed && checks.joins && checks.limits);
}
The kernel's other proof (the same days, and four series of two hundred normal samples, read back on the host) is on its proof page.