Skip to content

The crossing price, live

The price a bid and an ask trade at, the resting order's limit, the one that arrived first: the bid's when its arrival is the earlier, else the ask's (lusine.finance.crossing_price). Two orders of one stamp take the ask's. The kernel only chooses between two prices, so its result carries the game's money. Below, sixty-four crossings are drawn each step on your GPU as you read, each a resting order, a bid or an ask, and an order arriving 1 to 100 stamps after it, their limits from 90.00 EUR to 110.00 EUR to the cent; each is priced in euros, in yen (the same counts, a yen each), and with the two arrivals swapped. Where the bid meets the ask, the crossing is a trade of 1 to 10 shares between two of eight traders, the buyer paying the seller the price. Beside them, an incoming ask is swept past a bid resting at 100.00 EUR, and the bid comes in against the same ask resting.

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 sweep: the chart draws the price, in cents above 99.00 EUR, against the ask's limit, from 99.00 EUR to 100.00 EUR a cent a step: with the bid at 100.00 EUR resting, the price is its limit whatever the ask's, a flat line at 100; with the ask resting, the price is the ask's own limit, the diagonal. Below it, the ask's limit.
  • The market: the steps, how many of this step's crossings took the bid's price and how many the ask's, the trades and the shares traded, and the cash and the shares the traders hold.
  • The checks: the resting order's, every crossing at the limit of the order drawn to rest, and the sweep's prices each its resting order's; arrivals swapped, every crossing priced with its two arrivals swapped at the other order's limit; in yen, the same side, the kernel bound to yen choosing the bid where it chose the bid in euros; never past a limit, every trade's price at or under the bid's limit and at or over the ask's; cash and shares kept, the eight traders holding 80 000 EUR and 800 shares in all at every step; the textbook's two, a bid resting at 101.00 EUR hit by an ask at 100.00 EUR trading at 101.00 EUR, and an ask resting at 100.00 EUR lifted by a bid at 101.00 EUR trading at 100.00 EUR, to the cent (Harris, Trading and Exchanges, ch. 6); one stamp, the ask's, two orders of one stamp taking the ask's price. 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 lowest limit and the cents the limits span are the program's settings, which the host may write; this page offers no control to change them yet.

The program

The crossings, the traders, the sweep and the checks, the panels the page draws, and the one procedure, main, run each step: the crossings drawn and priced, the trades settled, the sweep, and the checks.

// The crossing price's live page: sixty-four crossings a step, each a resting order and one arriving after it, drawn
// from a seed and priced by the library's kernel in euros to the cent and in yen, with their arrivals swapped; each
// crossing whose bid meets its ask a trade between two of eight traders; an incoming ask swept past a resting bid and
// the other way round; and the textbook's cases worked by hand, with the page's checks computed here on the GPU and
// shown by the panels the program declares.

namespace crossing_price;

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

// The reader's settings: the seed the crossings are drawn from, and the lowest limit and the cents the limits span,
// from 90.00 EUR to 110.00 EUR.
host struct {
  integer seed = 3011; integer<EUR> low = 9000 EUR_minor quantum = 1 EUR_minor; integer ticks = 2001;
} settings;

// A crossing: which order rests, a bid or an ask, and the two orders' limits and arrivals, the resting order's the
// earlier; its price, in euros and, the same counts, in yen, and with the arrivals swapped; and, where the bid meets
// the ask, a trade of a few shares between two of the traders.
struct crossing {
  integer index; bool bid_rests; integer<EUR> bid quantum = 1 EUR_minor; integer<EUR> ask quantum = 1 EUR_minor;
  integer bid_arrival; integer ask_arrival; integer<JPY> bid_yen; integer<JPY> ask_yen;
  integer<EUR> price quantum = 1 EUR_minor; integer<JPY> price_yen; integer<EUR> swapped quantum = 1 EUR_minor;
  integer buyer; integer seller; integer quantity;
};
table<crossing> crossings(capacity = 64) = {
  { index = 0 }, { index = 1 }, { index = 2 }, { index = 3 }, { index = 4 }, { index = 5 }, { index = 6 },
  { index = 7 }, { index = 8 }, { index = 9 }, { index = 10 }, { index = 11 }, { index = 12 }, { index = 13 },
  { index = 14 }, { index = 15 }, { index = 16 }, { index = 17 }, { index = 18 }, { index = 19 }, { index = 20 },
  { index = 21 }, { index = 22 }, { index = 23 }, { index = 24 }, { index = 25 }, { index = 26 }, { index = 27 },
  { index = 28 }, { index = 29 }, { index = 30 }, { index = 31 }, { index = 32 }, { index = 33 }, { index = 34 },
  { index = 35 }, { index = 36 }, { index = 37 }, { index = 38 }, { index = 39 }, { index = 40 }, { index = 41 },
  { index = 42 }, { index = 43 }, { index = 44 }, { index = 45 }, { index = 46 }, { index = 47 }, { index = 48 },
  { index = 49 }, { index = 50 }, { index = 51 }, { index = 52 }, { index = 53 }, { index = 54 }, { index = 55 },
  { index = 56 }, { index = 57 }, { index = 58 }, { index = 59 }, { index = 60 }, { index = 61 }, { index = 62 },
  { index = 63 },
};

// The textbook's cases (kind 0): a bid resting at 101.00 EUR hit by an ask at 100.00 EUR, and an ask resting at 100.00
// EUR lifted by a bid at 101.00 EUR; and two orders of one stamp (kind 1), the ask's price; each its price wanted.
struct case {
  integer kind; integer<EUR> bid quantum = 1 EUR_minor; integer bid_arrival; integer<EUR> ask quantum = 1 EUR_minor;
  integer ask_arrival; integer<EUR> want quantum = 1 EUR_minor; integer<EUR> got quantum = 1 EUR_minor;
};
host table<case> cases(capacity = 4) = {
  { kind = 0, bid = 10100 EUR_minor, bid_arrival = 1, ask = 10000 EUR_minor, ask_arrival = 2, want = 10100 EUR_minor },
  { kind = 0, bid = 10100 EUR_minor, bid_arrival = 2, ask = 10000 EUR_minor, ask_arrival = 1, want = 10000 EUR_minor },
  { kind = 1, bid = 10100 EUR_minor, bid_arrival = 7, ask = 10000 EUR_minor, ask_arrival = 7, want = 10000 EUR_minor },
  { kind = 1, bid = 9950 EUR_minor, bid_arrival = 0, ask = 9925 EUR_minor, ask_arrival = 0, want = 9925 EUR_minor },
};

// Eight traders, each with 10 000 EUR and 100 shares.
struct trader { integer number; integer<EUR> cash quantum = 1 EUR_minor; integer shares; };
table<trader> traders(capacity = 8) = {
  { number = 0, cash = 10000 EUR, shares = 100 }, { number = 1, cash = 10000 EUR, shares = 100 },
  { number = 2, cash = 10000 EUR, shares = 100 }, { number = 3, cash = 10000 EUR, shares = 100 },
  { number = 4, cash = 10000 EUR, shares = 100 }, { number = 5, cash = 10000 EUR, shares = 100 },
  { number = 6, cash = 10000 EUR, shares = 100 }, { number = 7, cash = 10000 EUR, shares = 100 },
};

// The market: the step, the crossings that traded and the shares, the bids' and the asks' prices this step, the cash
// and the shares the traders hold, the cash in euros.
struct {
  integer step; integer trades; integer volume; integer bids; integer asks; integer<EUR> cash quantum = 1 EUR_minor;
  integer shares; float<EUR> held;
} market;

// The sweep: a resting bid at 100.00 EUR met by an incoming ask whose limit sweeps from 99.00 EUR to 100.00 EUR a cent
// a step, and the same limit resting as an ask met by the bid arriving after it; each price, and the ask's limit, in
// cents above 99.00 EUR, and the limit in euros.
struct {
  integer cents = -1; integer<EUR> limit quantum = 1 EUR_minor; integer bid_rests; integer ask_rests;
  integer ask_limit; float<EUR> reading;
} sweep;

// The checks: every price one of the two limits, the resting order's; the arrivals swapped, the other's; in yen, the
// same side's; every trade within the bid's and the ask's limits; the traders' cash and shares in all as they began;
// the textbook's resting bid and resting ask to the cent; and two orders of one stamp taking the ask's price.
struct {
  bool resting = true; bool swapped = true; bool yen = true; bool limits = true; bool kept = true;
  bool textbook = true; bool stamps = 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);
}

integer drawn(integer seed, integer step, bits key, integer index, integer below)
{
  return integer(lusine.error.draw(seed = bits(seed), step = bits(step), key, index = bits(index)) >> 8) % below;
}

// A crossing drawn: the resting order's side, the two limits, the resting order's arrival and the incoming order's
// after it, by 1 to 100 stamps.
(bool, integer<EUR>, integer<EUR>, integer, integer) drawing(integer index, integer seed, integer step,
                                                            integer<EUR> low, integer ticks)
{
  let rests = drawn(seed, step, key = fnv1a("rests"), index, below = 2) == 0;
  let resting = drawn(seed, step, key = fnv1a("resting"), index, below = 100000);
  let incoming = resting + 1 + drawn(seed, step, key = fnv1a("incoming"), index, below = 100);
  return (rests, low + drawn(seed, step, key = fnv1a("bid"), index, below = ticks) * 1 EUR_minor,
          low + drawn(seed, step, key = fnv1a("ask"), index, below = ticks) * 1 EUR_minor,
          rests ? resting : incoming, rests ? incoming : resting);
}

integer<JPY> in_yen(integer<EUR> euros) { return euros / 1 EUR_minor * 1 JPY; }

// A trade where the bid meets the ask: 1 to 10 shares, between two of the eight traders; none where it does not.
(integer, integer, integer) trading(integer index, integer seed, integer step, integer<EUR> bid, integer<EUR> ask)
{
  if (bid < ask) return (0, 0, 0);
  return (drawn(seed, step, key = fnv1a("buyer"), index, below = 8),
          drawn(seed, step, key = fnv1a("seller"), index, below = 8),
          1 + drawn(seed, step, key = fnv1a("quantity"), index, below = 10));
}

integer<EUR> paid(integer number, integer<EUR> cash, table<integer> buyers, table<integer> sellers,
                  table<integer<EUR>> prices, table<integer> quantities)
{
  let n = number;
  return cash + (sum(prices * quantities) where sellers == n && quantities > 0)
       - (sum(prices * quantities) where buyers == n && quantities > 0);
}

integer bought(integer number, integer shares, table<integer> buyers, table<integer> sellers,
               table<integer> quantities)
{
  let n = number;
  return shares + (sum(quantities) where buyers == n) - (sum(quantities) where sellers == n);
}

// Each price the resting order's limit; with the arrivals swapped the other order's; and in yen the same side's.
integer wrong_resting(table<bool> rests, table<integer<EUR>> bids, table<integer<EUR>> asks,
                      table<integer<EUR>> prices)
{
  return count(prices) where rests ? prices != bids : prices != asks;
}

integer wrong_swapped(table<bool> rests, table<integer<EUR>> bids, table<integer<EUR>> asks,
                      table<integer<EUR>> swaps)
{
  return count(swaps) where rests ? swaps != asks : swaps != bids;
}

integer wrong_yen(table<integer<EUR>> bids, table<integer<EUR>> prices, table<integer<JPY>> bid_yens,
                  table<integer<JPY>> yens)
{
  return count(yens) where (prices == bids) != (yens == bid_yens);
}

integer outside(table<integer<EUR>> bids, table<integer<EUR>> asks, table<integer<EUR>> prices,
                table<integer> quantities)
{
  return count(prices) where quantities > 0 && (prices > bids || prices < asks);
}

// The cases of one kind whose price is not the one wanted.
integer wrong(table<integer> kinds, table<integer<EUR>> got, table<integer<EUR>> want, integer kind)
{
  let k = kind;
  return count(got) where kinds == k && got != want;
}

output table<lusine.widget> panels = {
  lusine.series { { panel = "the sweep", chart = "price, cents above 99.00 EUR", quantity = sweep.bid_rests,
                    steps = 101, against = sweep.ask_limit, label = "the bid at 100 rests" } },
  lusine.series { { panel = "the sweep", chart = "price, cents above 99.00 EUR", quantity = sweep.ask_rests,
                    steps = 101, against = sweep.ask_limit, label = "the ask rests" } },
  lusine.value { { panel = "the sweep", quantity = sweep.reading, label = "the ask's limit" } },
  lusine.value { { panel = "the market", quantity = market.step, label = "steps" } },
  lusine.value { { panel = "the market", quantity = market.bids, label = "the bid's price, this step" } },
  lusine.value { { panel = "the market", quantity = market.asks, label = "the ask's price, this step" } },
  lusine.value { { panel = "the market", quantity = market.trades, label = "trades" } },
  lusine.value { { panel = "the market", quantity = market.volume, label = "shares traded" } },
  lusine.value { { panel = "the market", quantity = market.held, label = "cash held" } },
  lusine.value { { panel = "the market", quantity = market.shares, label = "shares held" } },
  lusine.value { { panel = "the checks", quantity = checks.resting, label = "the resting order's" } },
  lusine.value { { panel = "the checks", quantity = checks.swapped, label = "arrivals swapped" } },
  lusine.value { { panel = "the checks", quantity = checks.yen, label = "in yen, the same side" } },
  lusine.value { { panel = "the checks", quantity = checks.limits, label = "never past a limit" } },
  lusine.value { { panel = "the checks", quantity = checks.kept, label = "cash and shares kept" } },
  lusine.value { { panel = "the checks", quantity = checks.textbook, label = "the textbook's two" } },
  lusine.value { { panel = "the checks", quantity = checks.stamps, label = "one stamp, the ask's" } },
};

void main()
{
  market.step = market.step + 1;
  (crossings.bid_rests, crossings.bid, crossings.ask, crossings.bid_arrival, crossings.ask_arrival)
    = drawing(crossings.index, settings.seed, step = market.step, settings.low, settings.ticks);
  crossings.bid_yen = in_yen(euros = crossings.bid);
  crossings.ask_yen = in_yen(euros = crossings.ask);
  crossings.price = lusine.finance.crossing_price(crossings.bid, crossings.bid_arrival, crossings.ask,
                                                  crossings.ask_arrival);
  crossings.price_yen = lusine.finance.crossing_price(bid = crossings.bid_yen, bid_arrival = crossings.bid_arrival,
                                                      ask = crossings.ask_yen, ask_arrival = crossings.ask_arrival);
  crossings.swapped = lusine.finance.crossing_price(crossings.bid, bid_arrival = crossings.ask_arrival, crossings.ask,
                                                    ask_arrival = crossings.bid_arrival);
  (crossings.buyer, crossings.seller, crossings.quantity)
    = trading(crossings.index, settings.seed, step = market.step, crossings.bid, crossings.ask);
  traders.cash = paid(traders.number, traders.cash, buyers = crossings.buyer, sellers = crossings.seller,
                      prices = crossings.price, quantities = crossings.quantity);
  traders.shares = bought(traders.number, traders.shares, buyers = crossings.buyer, sellers = crossings.seller,
                          quantities = crossings.quantity);
  market.trades = market.trades + (count(crossings.quantity) where crossings.quantity > 0);
  market.volume = market.volume + sum(crossings.quantity);
  market.bids = count(crossings.price) where crossings.price == crossings.bid;
  market.asks = count(crossings.price) where crossings.price == crossings.ask;
  market.cash = sum(traders.cash);
  market.held = float(market.cash);
  market.shares = sum(traders.shares);

  sweep.cents = sweep.cents >= 100 ? 0 : sweep.cents + 1;
  sweep.limit = 9900 EUR_minor + sweep.cents * 1 EUR_minor;
  sweep.bid_rests = (lusine.finance.crossing_price(bid = 10000 EUR_minor, bid_arrival = 1, ask = sweep.limit,
                                                   ask_arrival = 2) - 9900 EUR_minor) / 1 EUR_minor;
  sweep.ask_rests = (lusine.finance.crossing_price(bid = 10000 EUR_minor, bid_arrival = 2, ask = sweep.limit,
                                                   ask_arrival = 1) - 9900 EUR_minor) / 1 EUR_minor;
  sweep.ask_limit = sweep.cents;
  sweep.reading = float(sweep.limit);

  checks.resting = checks.resting && wrong_resting(rests = crossings.bid_rests, bids = crossings.bid,
                                                   asks = crossings.ask, prices = crossings.price) == 0
                   && sweep.bid_rests == 100 && sweep.ask_rests == sweep.cents;
  checks.swapped = checks.swapped && wrong_swapped(rests = crossings.bid_rests, bids = crossings.bid,
                                                   asks = crossings.ask, swaps = crossings.swapped) == 0;
  checks.yen = checks.yen && wrong_yen(bids = crossings.bid, prices = crossings.price, bid_yens = crossings.bid_yen,
                                       yens = crossings.price_yen) == 0;
  checks.limits = checks.limits && outside(bids = crossings.bid, asks = crossings.ask, prices = crossings.price,
                                           quantities = crossings.quantity) == 0;
  checks.kept = checks.kept && market.cash == 80000 EUR && market.shares == 800;
  cases.got = lusine.finance.crossing_price(cases.bid, cases.bid_arrival, cases.ask, cases.ask_arrival);
  checks.textbook = checks.textbook && wrong(kinds = cases.kind, got = cases.got, want = cases.want, kind = 0) == 0;
  checks.stamps = checks.stamps && wrong(kinds = cases.kind, got = cases.got, want = cases.want, kind = 1) == 0;
  lamps.colour = lamp_colour(holds = checks.resting && checks.swapped && checks.yen && checks.limits && checks.kept
                                     && checks.textbook && checks.stamps);
}

The kernel's other proof (five hundred random crossings read back on the host) is on its proof page.