Skip to content

The log return, live

A price's log return from the one before, r = ln(p / p₀): the continuously compounded return between two samples, dimensionless (lusine.finance.log_return). The kernel takes the two prices in the game's money, which divides out. Below, a price is walked on your GPU as you read from 100.00 USD, up or down by 1 to 50 cents a step, for a hundred steps and then again; each step's log return is the library's, and the returns are summed beside the return over the walk so far.

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.
  • The walk: the chart draws the steps' returns summed and the walk's return so far, in percent, a step at a time: one line over the other. Below it, the price, this step's return, the sum and the walk's.
  • The checks: RiskMetrics' Table 4.1, USD/DEM from 29 March to 12 April 1996, each day's log price change the table's to the 0.0005% it prints them to (RiskMetrics Technical Document, 4th ed., 1996); returns add, the walk's returns summed its return over the whole walk, and the fortnight's its return over the fortnight, within a binary32 epsilon (2⁻²³) a return, each the log of a ratio near one; a rise, the fall, in yen, a rise of 10% ln 1.1 within two epsilons and the fall back its opposite within two, and from 10 000 JPY to 11 000 JPY and back, integers, the same bits; no move, a price that has not moved returning exactly nothing. 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, the starting price and the walk's length are the program's settings, which the host may write; this page offers no control to change them yet.

The program

The fortnight, the walk and the checks, the panels the page draws, and the one procedure, main, run each step: the price a step on, its return, the sums, and the checks.

// The log return's live page: a price walked by up to fifty cents a step from a seed, its log return each step by the
// library's kernel, summed beside the return over the whole walk; RiskMetrics' USD/DEM fortnight against its printed
// table; a rise and the fall back, in dollars and in yen; and a price that has not moved, with the page's checks
// computed here on the GPU and shown by the panels the program declares.

namespace log_return;

lusine.version version = { { number = 1 } };

// The reader's settings: the seed the walk is drawn from, the price it starts at in cents, and the steps a walk runs
// before it starts again.
host struct { integer seed = 391; integer start = 10000; integer length = 100; } settings;

// RiskMetrics' Table 4.1: USD/DEM from 29 March to 12 April 1996, each day's price and the one before, and each day's
// log price change in percent as the table prints it, to 0.001%; the kernel's return.
struct day { float<USD> price; float<USD> previous; float printed; float r; };
host table<day> days(capacity = 11) = {
  { price = 0.67732 USD, previous = 0.67654 USD, printed = 0.115 },
  { price = 0.67422 USD, previous = 0.67732 USD, printed = -0.459 },
  { price = 0.67485 USD, previous = 0.67422 USD, printed = 0.093 },
  { price = 0.67604 USD, previous = 0.67485 USD, printed = 0.176 },
  { price = 0.67545 USD, previous = 0.67604 USD, printed = -0.087 },
  { price = 0.67449 USD, previous = 0.67545 USD, printed = -0.142 },
  { price = 0.67668 USD, previous = 0.67449 USD, printed = 0.324 },
  { price = 0.67033 USD, previous = 0.67668 USD, printed = -0.943 },
  { price = 0.66680 USD, previous = 0.67033 USD, printed = -0.528 },
  { price = 0.66609 USD, previous = 0.66680 USD, printed = -0.107 },
  { price = 0.66503 USD, previous = 0.66609 USD, printed = -0.159 },
};

// A rise of 10% and the fall back, from 100 USD to 110 USD and back, and from 10 000 JPY to 11 000 JPY, counted as
// integers; for no move, RiskMetrics' first price, 101.25 USD and 11 000 JPY; and RiskMetrics' last price, for the
// fortnight's return.
host struct {
  float<USD> low = 100.0 USD; float<USD> high = 110.0 USD; integer<JPY> low_yen = 10000 JPY;
  integer<JPY> high_yen = 11000 JPY; float<USD> mark = 0.67654 USD; float<USD> last = 0.66503 USD;
  float<USD> other = 101.25 USD;
} moves;

// The walk: the walks begun and the step within this one; the price in cents and the one before; each in dollars, and
// the start; the step's log return, the returns summed, and the return over the walk so far; and, for the chart, each
// in percent.
struct {
  integer walks; integer step = -1; integer cents; integer previous_cents; float<USD> price; float<USD> previous;
  float<USD> start; float r; float summed; float whole; float summed_percent; float whole_percent;
} walk;

// The checks: the fortnight's returns RiskMetrics' printed ones to the 0.0005% they round to; the walk's returns
// summed, and the fortnight's, the return over the whole within an epsilon (2^-23) a return; a rise of 10% ln 1.1 and
// the fall back its opposite, each within two epsilons, in yen the same bits as in dollars; and a price that has not
// moved returning exactly nothing.
struct { bool table = true; bool adds = true; bool rise = true; bool still = 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 eps() { return 1.0 / 8388608.0; }

// A step of the walk: up or down by 1 to 50 cents.
integer moved(integer cents, integer seed, integer step)
{
  let drawn = integer(lusine.error.draw(seed = bits(seed), step = bits(step), key = fnv1a("walk"),
                                        index = bits(0)) >> 8) % 100;
  return drawn < 50 ? cents + 1 + drawn : cents + 49 - drawn;
}

float<USD> in_dollars(integer cents) { return float(cents) * 0.01 USD; }

integer misprinted(table<float> r, table<float> printed)
{
  return count(r) where abs(100.0 * r - printed) > 0.0005;
}

bool rise(float<USD> low, float<USD> high, integer<JPY> low_yen, integer<JPY> high_yen)
{
  let up = lusine.finance.log_return(price = high, previous = low);
  let down = lusine.finance.log_return(price = low, previous = high);
  let up_yen = lusine.finance.log_return(price = high_yen, previous = low_yen);
  let down_yen = lusine.finance.log_return(price = low_yen, previous = high_yen);
  return abs(up + down) <= 2.0 * eps && abs <= 2.0 * eps && up_yen == up
      && down_yen == down;
}

bool still(float<USD> mark, float<USD> low, integer<JPY> yen)
{
  return lusine.finance.log_return(price = mark, previous = mark) == 0.0
      && lusine.finance.log_return(price = low, previous = low) == 0.0
      && lusine.finance.log_return(price = yen, previous = yen) == 0.0;
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the walk", chart = "return so far, %", quantity = walk.summed_percent, steps = 300,
                    label = "the steps' summed" } },
  lusine.series { { panel = "the walk", chart = "return so far, %", quantity = walk.whole_percent, steps = 300,
                    label = "the walk's" } },
  lusine.value { { panel = "the walk", quantity = walk.price, label = "price" } },
  lusine.value { { panel = "the walk", quantity = walk.r, label = "this step's return" } },
  lusine.value { { panel = "the walk", quantity = walk.summed, label = "the steps' summed" } },
  lusine.value { { panel = "the walk", quantity = walk.whole, label = "the walk's" } },
  lusine.value { { panel = "the checks", quantity = checks.table, label = "RiskMetrics' Table 4.1" } },
  lusine.value { { panel = "the checks", quantity = checks.adds, label = "returns add" } },
  lusine.value { { panel = "the checks", quantity = checks.rise, label = "a rise, the fall, in yen" } },
  lusine.value { { panel = "the checks", quantity = checks.still, label = "no move" } },
};

void main()
{
  walk.step = walk.step >= settings.length ? 0 : walk.step + 1;
  walk.walks = walk.step == 0 ? walk.walks + 1 : walk.walks;
  walk.previous_cents = walk.step == 0 ? settings.start : walk.cents;
  walk.cents = walk.step == 0 ? settings.start : moved(walk.cents, settings.seed, step = 1000 * walk.walks + walk.step);
  walk.price = in_dollars(cents = walk.cents);
  walk.previous = in_dollars(cents = walk.previous_cents);
  walk.start = in_dollars(cents = settings.start);
  walk.r = lusine.finance.log_return(walk.price, walk.previous);
  walk.summed = walk.step == 0 ? 0.0 : walk.summed + walk.r;
  walk.whole = lusine.finance.log_return(walk.price, previous = walk.start);
  walk.summed_percent = 100.0 * walk.summed;
  walk.whole_percent = 100.0 * walk.whole;
  days.r = lusine.finance.log_return(days.price, days.previous);
  checks.table = checks.table && misprinted(days.r, days.printed) == 0;
  checks.adds = checks.adds && abs(walk.summed - walk.whole) <= float(walk.step + 1) * eps()
                && abs(sum(days.r) - lusine.finance.log_return(price = moves.last, previous = moves.mark))
                   <= 12.0 * eps();
  checks.rise = checks.rise && rise(moves.low, moves.high, moves.low_yen, moves.high_yen);
  checks.still = checks.still && still(moves.mark, low = moves.other, yen = moves.high_yen);
  lamps.colour = lamp_colour(holds = checks.table && checks.adds && checks.rise && checks.still);
}

The kernel's other proof (the same table and its sums read back on the host) is on its proof page.