Crossing price: the resting order's¶
The price a trade between a bid and an ask crosses at, the resting order's, the one that arrived first: the bid's when its arrival is the earlier, else the ask's.
Generic over its unit: it only chooses between two prices, so its result carries the game's money unit. The arrivals are the engine's stamps; two equal stamps take the ask's price, so a game whose orders can share a stamp breaks the tie in the stamps it passes. Source: Harris, Trading and Exchanges, ch. 6 (the price at which orders trade). Checked against a reference in numpy, which has its own tests.
- Source
- Harris, Trading and Exchanges, ch. 6.
- Proof
- reference, invariant, limit.
crossing_price.trait
namespace lusine.finance;
integer<_> crossing_price(integer<_> bid, integer bid_arrival, integer<_> ask, integer ask_arrival)
{
return bid_arrival < ask_arrival ? bid : ask;
}
The price a bid and an ask trade at, the resting order's, the one that arrived first. The kernel is generic over its unit: it only chooses between two prices, so its result carries the game's money unit.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
import matplotlib.pyplot as plt
from lusine import trait_run
EPS = float(np.finfo(np.float32).eps) # Trait's float is binary32
The kernel¶
The law as the library states it.
print(trait_run.source('finance/crossing_price.trait'))
// lusine.finance.crossing_price: the price a trade between a bid and an ask crosses at, the resting order's, the one
// that arrived first: the bid's when its arrival is the earlier, else the ask's. Generic over its unit: it only
// chooses between two prices, so its result carries the game's money unit. The
// arrivals are the engine's stamps; two equal stamps take the ask's price, so a game whose orders can share a stamp
// breaks the tie in the stamps it passes.
// Source: Harris, Trading and Exchanges, ch. 6 (the price at which orders trade).
// Checked against a reference in numpy, which has its own tests.
namespace lusine.finance;
integer<_> crossing_price(integer<_> bid, integer bid_arrival, integer<_> ask, integer ask_arrival)
{
return bid_arrival < ask_arrival ? bid : ask;
}
The program¶
A lattice of crossings, each a bid and an ask with their arrivals, priced twice by the one kernel: in euros stored to the cent (EUR_minor, the currency's minor unit, as the price's quantum), and in yen. The host speaks euros and yen; each price is stored as a count of its quantum.
CROSS = """namespace cross;
lusine.version version = { { number = 1 } };
struct crossing {
host integer<EUR> bid quantum = 1 EUR_minor; host integer bid_arrival;
host integer<EUR> ask quantum = 1 EUR_minor; host integer ask_arrival;
integer<EUR> price quantum = 1 EUR_minor;
host integer<JPY> bid_yen; host integer<JPY> ask_yen; integer<JPY> price_yen;
};
lattice<crossing, 1> crossings(spacing = 1 m, boundary = wall);
void main()
{
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);
}
"""
print(CROSS)
def cross(bid, bid_arrival, ask, ask_arrival, bid_yen=0, ask_yen=0):
"""Each crossing's price in euros (to the cent) and in yen."""
n = len(bid_arrival)
run = trait_run.Program(['finance/crossing_price.trait'], CROSS, {'cross.crossings': [n]})
for field, v in (('bid', bid), ('ask', ask), ('bid_arrival', bid_arrival), ('ask_arrival', ask_arrival),
('bid_yen', bid_yen), ('ask_yen', ask_yen)):
run.store('cross.crossings', field, np.broadcast_to(v, (n,)))
price, yen = run.steps('cross.main', 1, [('cross.crossings', 'price'), ('cross.crossings', 'price_yen')])
return price[0], yen[0]
namespace cross;
lusine.version version = { { number = 1 } };
struct crossing {
host integer<EUR> bid quantum = 1 EUR_minor; host integer bid_arrival;
host integer<EUR> ask quantum = 1 EUR_minor; host integer ask_arrival;
integer<EUR> price quantum = 1 EUR_minor;
host integer<JPY> bid_yen; host integer<JPY> ask_yen; integer<JPY> price_yen;
};
lattice<crossing, 1> crossings(spacing = 1 m, boundary = wall);
void main()
{
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);
}
Reference: the resting order sets the price¶
A bid resting at 101.00 hit by an ask at 100.00 trades at 101.00; an ask resting at 100.00 lifted by a bid at 101.00 trades at 100.00 (Harris, Trading and Exchanges, ch. 6). Read back to the cent.
price, _ = cross([101.0, 101.0], [1, 2], [100.0, 100.0], [2, 1])
assert list(price) == [101.0, 100.0], price
print('resting bid:', price[0], ' resting ask:', price[1])
resting bid: 101.0 resting ask: 100.0
Invariant: one of the two, the earlier one's, in any money¶
Five hundred random crossings: the price is always the bid's or the ask's, the earlier order's; swapping the arrivals swaps it; and the same kernel bound to yen chooses the same side.
rng = np.random.default_rng(3011)
b, a = rng.integers(9000, 11000, 500), rng.integers(9000, 11000, 500) # cents
tb, ta = rng.permutation(1000)[:500], rng.permutation(1000)[500:] # distinct stamps
price, yen = cross(b / 100, tb, a / 100, ta, b, a)
cents = np.round(price * 100).astype(int)
first = tb < ta
assert np.all(np.where(first, cents == b, cents == a))
assert np.all(np.where(first, yen == b, yen == a))
swapped, _ = cross(b / 100, ta, a / 100, tb)
assert np.all(np.where(first, np.round(swapped * 100) == a, np.round(swapped * 100) == b))
print('bids set the price', int(first.sum()), 'times, asks', int((~first).sum()))
bids set the price 257 times, asks 243
Limit: equal stamps¶
Two orders with one stamp take the ask's price: a game whose orders can share a stamp breaks the tie in the stamps it passes.
price, _ = cross([101.0, 99.5], [7, 0], [100.0, 99.25], [7, 0])
assert list(price) == [100.0, 99.25], price
print(price)
[100. 99.25]