Skip to content

The factor model's fair value, live

An instrument's fair value as a ratio to its reference price, V / V₀ = exp(Σⱼ Lⱼ fⱼ + e): the single-index and multi-factor models, prices co-moving through shared factors (lusine.finance.fair_value). The kernel takes a game's whole table of loadings, each its instrument, its weight and its factor, a relation to whatever table holds the factor's level, and folds the loadings of each instrument's own in Trait's fixed tree. Below, a game's tables are written out and run on your GPU as you read: four factors (the market, banks, industry and rates) and a fifth for the instruments that load on one factor alone; two banks loading on the market, on banks and against rates, one more on banks than the other; an industrial on the market, industry and against rates; and forty-one instruments each loading on the fifth factor by a weight from −0.2 to 0.2. The first seven days are set by hand; after them each factor walks from a seed.

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 market: the chart draws the two banks' and the industrial's fair values, in percent from their reference prices, a day a step: the seven days set by hand, then the walk. Below it, the day.
  • The checks: no move is the reference, on a still day every instrument's ratio exactly 1; a rate rise, rates up a point lowering both banks and the industrial, which load against it, the two banks alike; the binary32 model, every instrument's ratio at every step the bits of a model of the fold, each of its loadings' L f added in Trait's fixed tree over the table's rows in slot order, the other instruments' rows adding nothing, and the idiosyncratic term added, through the same program's exp; float64, and e^L, on a day's levels (the market up 2%, banks 1%, industry 3%, rates 0.5%, an idiosyncratic 0.4%) the three within 8 unit roundoffs (2⁻²⁴) of e^(Σ L f + e) in float64, and on the still day, the fifth factor at 1, each of the forty-one e^L within 8; days compose, the ratio after two days' moves together each day's ratio multiplied, within 32 unit roundoffs; the more exposed moves more, with the market and the banks up, the bank loading more on banks gaining more. 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 walk's spread are the program's settings, which the host may write; this page offers no control to change them yet.

The program

The factors, the instruments, the loadings, the days and the checks, the panels the page draws, and the one procedure, main, run each step: the day's levels, every instrument's ratio by the kernel and by the model, and the checks.

// The fair value's live page: a game's tables of factors, instruments and loadings, the library's fair value folded
// over the loadings for every instrument, through a fixed run of days, a still day, a rate rise, a day's levels, two
// days apart and together, and a rally, and then a market walking from a seed; each instrument held to a binary32 model
// of the fold bit for bit, and the textbook's days to float64, with the page's checks computed here on the GPU and
// shown by the panels the program declares.

namespace fair_value;

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

// The reader's settings: the seed the walk is drawn from, and its step's spread a factor.
host struct { integer seed = 591; float spread = 0.004; } settings;

// The factors, each with its level, a log-return from its start: the market, banks, industry, rates, and one held at 1
// for the instruments that load on it alone.
struct factor { text name; integer index; float level; };
table<factor> factors(capacity = 5, key = name) = {
  { name = "market", index = 0 }, { name = "banks", index = 1 }, { name = "industry", index = 2 },
  { name = "rates", index = 3 }, { name = "unit", index = 4 },
};

// The instruments: two banks, loading on the market, on banks and against rates, one more on banks than the other; an
// industrial, on the market, industry and against rates; and forty-one loading on the unit factor alone, by a weight
// from -0.2 to 0.2. Each with its idiosyncratic term; its fair value's ratio to its reference price, the kernel's; the
// binary32 model's; and the float64 reference: e^(sum L f + e) at the day's levels for the three, e^L for the
// forty-one, as two binary32 numbers, its rounding and the rest; its ratios on the two days apart; and whether the
// ratio is exactly 1.
struct instrument {
  text name; integer index; float idiosyncratic; float ratio; float model; float reference; float low; bool exact;
  bool near; float first; float second; bool composes; bool one;
};
host table<instrument> instruments(capacity = 44, key = name) = {
  { name = "bank_a", index = 0, reference = 1.0222437381744385, low = 4.629599814620633e-08 },
  { name = "bank_b", index = 1, reference = 1.0212219953536987, low = 5.628383092926015e-08 },
  { name = "works", index = 2, reference = 1.0470744371414185, low = -2.618448036173504e-08 },
  { name = "unit0", index = 3, reference = 0.8187307715415955, low = -2.090362194451245e-08 },
  { name = "unit1", index = 4, reference = 0.8269591331481934, low = 2.7667930169172905e-09 },
  { name = "unit2", index = 5, reference = 0.8352702260017395, low = -2.0564785430110533e-08 },
  { name = "unit3", index = 6, reference = 0.843664824962616, low = -9.874822737288014e-09 },
  { name = "unit4", index = 7, reference = 0.8521437644958496, low = 2.751786531973721e-08 },
  { name = "unit5", index = 8, reference = 0.8607079982757568, low = -2.698091883246434e-08 },
  { name = "unit6", index = 9, reference = 0.869358241558075, low = -6.6774470397490404e-09 },
  { name = "unit7", index = 10, reference = 0.8780954480171204, low = -1.2909473490196888e-08 },
  { name = "unit8", index = 11, reference = 0.8869204521179199, low = -1.3021856481998384e-08 },
  { name = "unit9", index = 12, reference = 0.8958341479301453, low = -1.2099658164288485e-08 },
  { name = "unit10", index = 13, reference = 0.904837429523468, low = -1.2835821294743255e-08 },
  { name = "unit11", index = 14, reference = 0.9139311909675598, low = -8.964804010247462e-09 },
  { name = "unit12", index = 15, reference = 0.9231163263320923, low = 2.1705204744648654e-08 },
  { name = "unit13", index = 16, reference = 0.9323938488960266, low = -2.926795339419641e-08 },
  { name = "unit14", index = 17, reference = 0.9417645335197449, low = 1.3275085475328297e-09 },
  { name = "unit15", index = 18, reference = 0.951229453086853, low = -2.9294859871242807e-08 },
  { name = "unit16", index = 19, reference = 0.9607894420623779, low = -2.051042002904069e-09 },
  { name = "unit17", index = 20, reference = 0.9704455137252808, low = 2.0473962081268837e-08 },
  { name = "unit18", index = 21, reference = 0.9801986813545227, low = -7.609584073975384e-09 },
  { name = "unit19", index = 22, reference = 0.9900498390197754, low = -5.049313855920445e-09 },
  { name = "unit20", index = 23, reference = 1.0, low = 0.0 },
  { name = "unit21", index = 24, reference = 1.0100501775741577, low = -1.0715753617773771e-08 },
  { name = "unit22", index = 25, reference = 1.020201325416565, low = 1.4154124983178917e-08 },
  { name = "unit23", index = 26, reference = 1.0304545164108276, low = 1.6851714690346853e-08 },
  { name = "unit24", index = 27, reference = 1.0408108234405518, low = -5.017871984591693e-08 },
  { name = "unit25", index = 28, reference = 1.0512710809707642, low = 1.6188517193427288e-08 },
  { name = "unit26", index = 29, reference = 1.06183660030365, low = -5.518232271128909e-08 },
  { name = "unit27", index = 30, reference = 1.0725082159042358, low = -3.4330387421732667e-08 },
  { name = "unit28", index = 31, reference = 1.0832871198654175, low = -5.41275255727669e-08 },
  { name = "unit29", index = 32, reference = 1.0941742658615112, low = 2.1756770607339604e-08 },
  { name = "unit30", index = 33, reference = 1.1051709651947021, low = -4.547221976736182e-08 },
  { name = "unit31", index = 34, reference = 1.116278052330017, low = 1.7463500867620496e-08 },
  { name = "unit32", index = 35, reference = 1.1274968385696411, low = 9.985551940872028e-09 },
  { name = "unit33", index = 36, reference = 1.1388283967971802, low = -1.8902914789009628e-08 },
  { name = "unit34", index = 37, reference = 1.1502737998962402, low = -3.5339642323606313e-10 },
  { name = "unit35", index = 38, reference = 1.1618342399597168, low = 9.693637892382867e-09 },
  { name = "unit36", index = 39, reference = 1.1735109090805054, low = -4.228549599361031e-08 },
  { name = "unit37", index = 40, reference = 1.185304880142212, low = -2.670235588198011e-08 },
  { name = "unit38", index = 41, reference = 1.197217345237732, low = 2.6447244394489644e-08 },
  { name = "unit39", index = 42, reference = 1.2092496156692505, low = -2.0895074115401258e-08 },
  { name = "unit40", index = 43, reference = 1.2214027643203735, low = -2.5201398745622328e-09 },
};

// The loadings: each one's instrument, its weight and its factor.
struct weight { relation<instruments> instrument; float loading; relation<factors> factor; };
host table<weight> loadings(capacity = 64) = {
  { instrument = instruments["bank_a"], loading = 1.0, factor = factors["market"] },
  { instrument = instruments["bank_a"], loading = 0.8, factor = factors["banks"] },
  { instrument = instruments["bank_a"], loading = 0.0, factor = factors["industry"] },
  { instrument = instruments["bank_a"], loading = -2.0, factor = factors["rates"] },
  { instrument = instruments["bank_b"], loading = 1.0, factor = factors["market"] },
  { instrument = instruments["bank_b"], loading = 0.7, factor = factors["banks"] },
  { instrument = instruments["bank_b"], loading = 0.0, factor = factors["industry"] },
  { instrument = instruments["bank_b"], loading = -2.0, factor = factors["rates"] },
  { instrument = instruments["works"], loading = 1.0, factor = factors["market"] },
  { instrument = instruments["works"], loading = 0.0, factor = factors["banks"] },
  { instrument = instruments["works"], loading = 0.9, factor = factors["industry"] },
  { instrument = instruments["works"], loading = -1.0, factor = factors["rates"] },
  { instrument = instruments["unit0"], loading = -0.2, factor = factors["unit"] },
  { instrument = instruments["unit1"], loading = -0.19, factor = factors["unit"] },
  { instrument = instruments["unit2"], loading = -0.18, factor = factors["unit"] },
  { instrument = instruments["unit3"], loading = -0.17, factor = factors["unit"] },
  { instrument = instruments["unit4"], loading = -0.16, factor = factors["unit"] },
  { instrument = instruments["unit5"], loading = -0.15, factor = factors["unit"] },
  { instrument = instruments["unit6"], loading = -0.14, factor = factors["unit"] },
  { instrument = instruments["unit7"], loading = -0.13, factor = factors["unit"] },
  { instrument = instruments["unit8"], loading = -0.12, factor = factors["unit"] },
  { instrument = instruments["unit9"], loading = -0.11, factor = factors["unit"] },
  { instrument = instruments["unit10"], loading = -0.1, factor = factors["unit"] },
  { instrument = instruments["unit11"], loading = -0.09, factor = factors["unit"] },
  { instrument = instruments["unit12"], loading = -0.08, factor = factors["unit"] },
  { instrument = instruments["unit13"], loading = -0.07, factor = factors["unit"] },
  { instrument = instruments["unit14"], loading = -0.06, factor = factors["unit"] },
  { instrument = instruments["unit15"], loading = -0.05, factor = factors["unit"] },
  { instrument = instruments["unit16"], loading = -0.04, factor = factors["unit"] },
  { instrument = instruments["unit17"], loading = -0.03, factor = factors["unit"] },
  { instrument = instruments["unit18"], loading = -0.02, factor = factors["unit"] },
  { instrument = instruments["unit19"], loading = -0.01, factor = factors["unit"] },
  { instrument = instruments["unit20"], loading = 0.0, factor = factors["unit"] },
  { instrument = instruments["unit21"], loading = 0.01, factor = factors["unit"] },
  { instrument = instruments["unit22"], loading = 0.02, factor = factors["unit"] },
  { instrument = instruments["unit23"], loading = 0.03, factor = factors["unit"] },
  { instrument = instruments["unit24"], loading = 0.04, factor = factors["unit"] },
  { instrument = instruments["unit25"], loading = 0.05, factor = factors["unit"] },
  { instrument = instruments["unit26"], loading = 0.06, factor = factors["unit"] },
  { instrument = instruments["unit27"], loading = 0.07, factor = factors["unit"] },
  { instrument = instruments["unit28"], loading = 0.08, factor = factors["unit"] },
  { instrument = instruments["unit29"], loading = 0.09, factor = factors["unit"] },
  { instrument = instruments["unit30"], loading = 0.1, factor = factors["unit"] },
  { instrument = instruments["unit31"], loading = 0.11, factor = factors["unit"] },
  { instrument = instruments["unit32"], loading = 0.12, factor = factors["unit"] },
  { instrument = instruments["unit33"], loading = 0.13, factor = factors["unit"] },
  { instrument = instruments["unit34"], loading = 0.14, factor = factors["unit"] },
  { instrument = instruments["unit35"], loading = 0.15, factor = factors["unit"] },
  { instrument = instruments["unit36"], loading = 0.16, factor = factors["unit"] },
  { instrument = instruments["unit37"], loading = 0.17, factor = factors["unit"] },
  { instrument = instruments["unit38"], loading = 0.18, factor = factors["unit"] },
  { instrument = instruments["unit39"], loading = 0.19, factor = factors["unit"] },
  { instrument = instruments["unit40"], loading = 0.2, factor = factors["unit"] },
};

// The days: the step; and, for the chart, each of the three's ratio in percent above its reference price.
struct { integer step; float bank_a; float bank_b; float works; } days;

// The checks: on a still day every ratio exactly 1; on a rate rise both banks and the industrial below 1, the banks
// alike; every instrument's ratio the binary32 model's bit for bit at every step, each L f added in Trait's fixed tree
// over the loadings' rows and the idiosyncratic term added, through the same program's exp; at the day's levels the
// three within 8 unit roundoffs (2^-24) of float64, and on the still day the forty-one each e^L within 8; two days'
// moves composing, the ratio after both each one's multiplied, within 32; and with the market and the banks up the bank
// more exposed to banks gaining more.
struct {
  bool still = true; bool rates = true; bool model = true; bool reference = true; bool compose = true;
  bool exposure = 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; }

float u() { return 1.0 / 16777216.0; }

// The day's levels: 1 a still day; 2 rates up a point; 3 a day's levels; 4 and 5 two days; 6 the two together; 7 the
// market and the banks up; after that, each factor walking a step uniform with the spread. The unit factor is 1 on the
// still day and nothing after.
float level_of(integer index, float level, integer step, integer seed, float spread)
{
  if (index == 4) return step == 1 ? 1.0 : 0.0;
  if (step == 1) return 0.0;
  if (step == 2) return index == 3 ? 0.01 : 0.0;
  if (step == 3) return index == 0 ? 0.02 : (index == 1 ? 0.01 : (index == 2 ? 0.03 : 0.005));
  if (step == 4) return index == 0 ? 0.01 : (index == 1 ? -0.02 : (index == 2 ? 0.0 : 0.0005));
  if (step == 5) return index == 0 ? -0.004 : (index == 1 ? 0.03 : (index == 2 ? 0.0 : 0.001));
  if (step == 6) return index == 0 ? 0.006 : (index == 1 ? 0.01 : (index == 2 ? 0.0 : 0.0015));
  if (step == 7) return index == 0 ? 0.02 : (index == 1 ? 0.01 : 0.0);
  let u = lusine.error.uniform(drawn = lusine.error.draw(seed = bits(seed), step = bits(step), key = fnv1a("walk"),
                                                         index = bits(index)));
  return (step == 8 ? 0.0 : level) + spread * (2.0 * u - 1.0);
}

float idiosyncratic_of(integer step) { return step == 3 ? 0.004 : 0.0; }

// The binary32 model: each of the instrument's loadings L f, added in the fixed tree over the table's fifty-three rows
// in slot order, a first part of ceil(n / 2) and the rest, each folded so, the other instruments' rows adding nothing;
// the idiosyncratic term added; and the program's exp of that. The first bank's rows are 0 to 3, so (m + b) + (i + r);
// the second's 4 to 7, split between the parts of 0 to 6 and 7 to 13, so ((m + b) + i) + r; the industrial's 8 to 11,
// so (m + (b + i)) + r; and each of the forty-one has one row.
float modelled(integer index, table<relation<instruments>> instrument, table<float> loading,
               table<relation<factors>> factor, float idiosyncratic)
{
  let n = count(loading) where instrument == it;
  let m = sum(loading * factor.level) where instrument == it && factor.index == 0;
  let b = sum(loading * factor.level) where instrument == it && factor.index == 1;
  let i = sum(loading * factor.level) where instrument == it && factor.index == 2;
  let r = sum(loading * factor.level) where instrument == it && factor.index == 3;
  let unit = sum(loading * factor.level) where instrument == it && factor.index == 4;
  let folded = n == 1 ? unit
               : (index == 0 ? (m + b) + (i + r) : (index == 1 ? ((m + b) + i) + r : (m + (b + i)) + r));
  return exp(folded + idiosyncratic);
}

bool near(float ratio, float reference, float low) { return abs(ratio - reference - low) <= 8.0 * u() * reference; }
integer failing(table<bool> holds) { return count(holds) where !holds; }

// Whether every one of the three, or every one of the forty-one, holds.
bool named(table<integer> index, table<bool> holds) { return (count(holds) where index < 3 && !holds) == 0; }
bool unit(table<integer> index, table<bool> holds) { return (count(holds) where index >= 3 && !holds) == 0; }

bool composing(float ratio, float first, float second) { return abs(ratio / (first * second) - 1.0) <= 32.0 * u(); }

float ratio_of(table<integer> index, table<float> ratio, integer which)
{
  let k = which;
  return sum(ratio) where index == k;
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the market", chart = "% from the reference price", quantity = days.bank_a, steps = 300,
                    label = "bank a" } },
  lusine.series { { panel = "the market", chart = "% from the reference price", quantity = days.bank_b, steps = 300,
                    label = "bank b" } },
  lusine.series { { panel = "the market", chart = "% from the reference price", quantity = days.works, steps = 300,
                    label = "works" } },
  lusine.value { { panel = "the market", quantity = days.step, label = "day" } },
  lusine.value { { panel = "the checks", quantity = checks.still, label = "no move is the reference" } },
  lusine.value { { panel = "the checks", quantity = checks.rates, label = "a rate rise" } },
  lusine.value { { panel = "the checks", quantity = checks.model, label = "the binary32 model" } },
  lusine.value { { panel = "the checks", quantity = checks.reference, label = "float64, and e^L" } },
  lusine.value { { panel = "the checks", quantity = checks.compose, label = "days compose" } },
  lusine.value { { panel = "the checks", quantity = checks.exposure, label = "the more exposed moves more" } },
};

void main()
{
  days.step = days.step + 1;
  factors.level = level_of(factors.index, factors.level, step = days.step, settings.seed, settings.spread);
  instruments.idiosyncratic = idiosyncratic_of(step = days.step);
  instruments.ratio = lusine.finance.fair_value(loadings, instruments.idiosyncratic);
  instruments.model = modelled(instruments.index, loadings.instrument, loadings.loading, loadings.factor,
                               instruments.idiosyncratic);
  instruments.exact = instruments.ratio == instruments.model;
  instruments.one = instruments.ratio == 1.0;
  instruments.near = near(instruments.ratio, instruments.reference, instruments.low);
  instruments.first = days.step == 4 ? instruments.ratio : instruments.first;
  instruments.second = days.step == 5 ? instruments.ratio : instruments.second;
  instruments.composes = composing(instruments.ratio, instruments.first, instruments.second);
  days.bank_a = 100.0 * (ratio_of(instruments.index, instruments.ratio, which = 0) - 1.0);
  days.bank_b = 100.0 * (ratio_of(instruments.index, instruments.ratio, which = 1) - 1.0);
  days.works = 100.0 * (ratio_of(instruments.index, instruments.ratio, which = 2) - 1.0);
  checks.still = checks.still && (days.step != 1 || named(instruments.index, holds = instruments.one));
  checks.rates = checks.rates
                 && (days.step != 2 || (days.bank_a < 0.0 && days.works < 0.0 && days.bank_a == days.bank_b));
  checks.model = checks.model && failing(holds = instruments.exact) == 0;
  checks.reference = checks.reference && (days.step != 3 || named(instruments.index, holds = instruments.near))
                     && (days.step != 1 || unit(instruments.index, holds = instruments.near));
  checks.compose = checks.compose && (days.step != 6 || named(instruments.index, holds = instruments.composes));
  checks.exposure = checks.exposure && (days.step != 7 || (days.bank_a > days.bank_b && days.bank_b > 0.0));
  lamps.colour = lamp_colour(holds = checks.still && checks.rates && checks.model && checks.reference
                                     && checks.compose && checks.exposure);
}

The kernel's other proof (the same tables, and two wrong kernels planted in the library's place and caught) is on its proof page.