Expressive Bidding
Indifference Curves
Single-Stock Indifference Curve
Objective: Express a willingness to trade different quantities at different price levels, accepting a precise max cost of liquidity for different size fills.
This example involves specifying "tiers", where each tier is a range of fill quantities (min and max) and an associated limit price for fills within the range. This is represented by tier
data type below. By combining multiple tiers, we create a price-quantity curve.
Bidder Data:
type tier = {
min: quantity,
max: quantity,
px: int,
};
type curve_data = {
tiers: list(tier),
};
/* example; provided over FIX */
let curve_data_sample = {
tiers: [
{min: 0, max: 999, px: 201},
{min: 1000, max: 5000, px: 202},
{min: 5000, max: 9999, px: 203},
{min: 15000, max: 25000, px: 204},
],
};
Bidder Logic:
let price_volume_curve: bidder(_) = (arg, ~mkt) => {
open Bid;
let o = List.hd(arg.orders); // Assumes only one order
Ok (List.map(
tier => {
/* Construct an action for a given price/qty tier */
let within_qty_tier = qty(o) >= const(tier.min)
&& qty(o) <= const(tier.max);
subject_to(within_qty_tier,
place_notional(tier.px * qty(o)));
},
arg.bidder_data.tiers)
);
};
Midpoint Discretion
Objective: Trade different quantities at either the midpoint or at the near touch (NBB or NBO).
Express willingness to trade a larger quantity at a more favorable price (near touch) than at the midpoint. Here we don't need to enumerate tiers as above; we simply denote the quantity thresholds for the two tiers, mid_qty
and nbbo_qty
. The limit prices for each tier are determined later within bidder logic, through market data accessors such as MID
and NBB
that expose prices from the latest market snapshot.
Bidder Data:
type midpoint_discretion_data = {
mid_qty: quantity,
nbbo_qty: quantity,
};
let midpoint_discretion_data_sample = {
mid_qty: 200,
nbbo_qty: 500,
};
Bidder Logic:
let midpoint_discretion: bidder(_) = (arg, ~mkt) => {
open Bid;
let o = List.hd(arg.orders); // Assumes only one order
let mid_qty = o.data.mid_qty;
let nbbo_qty = o.data.nbbo_qty;
let nbbo_px = switch (o.order.side) {
| Buy => mkt(o.order.symbol) |> nbb;
| Sell => mkt(o.order.symbol) |> nbo;
};
let mid_px = mkt(o.order.symbol) |> mid;
Ok ([subject_to(qty(o) <= const(mid_qty),
place_notional(mid_px * qty(o))),
subject_to(const(mid_qty) < qty(o) && qty(o) <= const(nbbo_qty),
place_notional(nbbo_px * qty(o)))]);
};
Dynamic Percent-of-Spread
Objective: Express willingness to trade different quantities at different percentages of spread (with 0% meaning near touch, 50% meaning midpoint, and 100% meaning far touch).
Rather than selecting between NBBO and midpoint as in the "midpoint discretion" template, we can express more specific price preferences by including a "percent of spread" factor in bidder data. Instead of expressing price as a specific value, we include pct_of_spread
which will be used to compute the limit price in bidder logic based on momentary market conditions.
Bidder Data:
type spread_tier = {
min_qty: quantity,
max_qty: quantity,
pct_of_spread: int,
};
type spread_data = {tiers: list(spread_tier)};
let spread_data_sample = {
tiers: [
{min_qty: 500, max_qty: 1000, pct_of_spread: 0},
{min_qty: 200, max_qty: 500, pct_of_spread: 20},
{min_qty: 0, max_qty: 200, pct_of_spread: 50},
],
};
Bidder Logic:
let spread_discretion: bidder(_) = (arg, ~mkt) => {
let o = List.hd(arg.orders); // Assumes only one order
let nbb = mkt(o.order.symbol) |> nbb;
let nbo = mkt(o.order.symbol) |> nbo;
let computed_limit (tier) = switch (o.order.side) {
| Buy => nbo - (tier.pct_of_spread / 100) * (nbo - nbb);
| Sell => nbb + (tier.pct_of_spread / 100) * (nbo - nbb);
};
open Bid;
Ok (List.map(
tier => { subject_to(
qty(o) >= const(tier.min_qty) && qty(o) <= const(tier.max_qty),
place_notional(computed_limit(tier) * qty(o)))
},
arg.bidder_data.tiers
));
};
Next Steps
- Try this code in a simulated auction. Load the simulator walkthrough, and replace the bidder logic and data type with an example of your choosing. Tune the runtime arguments and auction parameters to your liking, and run the auction to see results.
- Modify to your needs and reach out to the OneChronos team at [email protected] for usage in production.