Expressive Bidding

Dynamic Peg


Production Status: Expressive Bidding is currently enabled for "Pairs" only (i.e. Expressive Orders consisting of two symbols with constraints on relative price and quantity), and does not currently include the `mkt()` function. Examples are included beyond what is currently live to facilitate discussion of expected use cases and testing. For details on currently available functionality, please see our Form ATS-N.

Dynamic Peg Walkthrough

In this document:

  • A walkthrough of building limit and peg orders with bidder logic
  • A simple example of how bidder data introduces runtime flexibility

In this section, we'll walk through the simplest examples of bidder logic: limit and peg orders. To familiarize ourselves with bidder logic structure, we'll start out with a basic limit order without a pegged price. (Note that this limit bidder example is purely illustrative and would have the same effect as submitting a standard Limit Order)

Simple Limit Order

Buy up to 100 shares of AAPL at 140 dollars:

reason
let limit_bidder: bidder(_) = (arg, ~mkt) => {
  let aapl = List.hd(arg.orders);       /* expects 1 order in the `orders` parameter */
  open Bid;                             /* opens `Bid` module needed for return actions */
  Ok ([
    subject_to(qty(aapl) <= const(100),
      place_notional(140 * qty(aapl)))
  ])
}

The above uses a hard-coded value for price (140), making it difficult to reuse this code. By instead providing these terms through a target order (the order argument), our bidder can be generalized to any symbol, price, and quantity. Here, we use the symbol, price, and quantity from the target order. We also make use of the place_orders return for simplicity:

reason
let limit_bidder: bidder(_) = (arg, ~mkt) => {
  let aapl = List.hd(arg.orders);       /* expects 1 order in the `orders parameter */
  open Bid;
  Ok ([
    subject_to(qty(aapl) <= const(100),
      place_orders([aapl]))
  ])
}

Primary Peg

Now let's make the order more interesting and peg our limit price to the market data measured by OneChronos. Suppose you want to always peg to one increment below the NBB (e.g. more aggressive than NBB for a sell order). You can use the nbb helper function to access the NBB measured by OneChronos at the time of auction. The |> is a "pipe" symbol, making the statement equivalent to nbb(mkt(o.order.symbol)).

reason
let limit_peg: bidder(_) = (arg, ~mkt) => {
  let o = List.hd(arg.orders);      /* expects 1 order in the `orders parameter */
  let peg_price = (mkt(o.order.symbol) |> nbb) - 1;

  open Bid;
  Ok ([subject_to(qty(o) <= const(100),
      place_notional(peg_price * qty(o)))  /* place order with pegged price as new limit */
  ])
}

Dynamic Peg

We reference the symbol of our target order using o.order.symbol. Each element in the arg.orders list has an order property that includes data from the FIX order. The properties available in this underlying order type are described in the programming guide below.

Rather than hard-coding the 1 dollars offset from NBB, we can use bidder_data to dynamically adjust the amount of the offset at runtime. Our JSON bidder_data containing our abs_offset value provided over FIX would look like:

json
/* JSON bidder data */
{
  "abs_offset": 1
}

Allowing us to set our peg price as follows:

reason
type data = {
  abs_offset: price
};

let limit_peg: bidder(_) = (arg, ~mkt) => {
  let o = List.hd(arg.orders); /* First order in the `arg.orders` input (assumes only one) */
  let peg_price = mkt((NBB, o.order.symbol)) - arg.bidder_data.abs_offset;
  /* [...] same as above */
};

We can add support for both buying and selling, using the side specified in the target order to simulate a primary peg:

reason
type data = {
  abs_offset: price
};

let limit_peg_2: bidder(_) = (arg, ~mkt) => {
  let o = List.hd(arg.orders);
  let sym = o.order.symbol;
  let pegged_price = switch (o.order.side) {
    | Buy => (mkt(sym) |> nbb) + arg.bidder_data.abs_offset
    | Sell => (mkt(sym) |> nbo) - arg.bidder_data.abs_offset
  };

  open Bid;
  Ok ([place_notional(pegged_price * qty(o))]);
}

Here we've included a switch statement to handle "buy" and "sell" target orders, changing the semantic meaning to "Buy at {offset} inside the best bid, or sell at {offset} inside the best offer."

Next Steps

1: Check the Price-Quantity Indifference Curves templates for runnable, full-featured examples of bidder logic similar to the above.

2: Try this code in a simulated auction. Load the Auction Simulator 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.