Expressive Bidding

Portfolios

Trading portfolios of securities in a traditional marketplace involves trading each security individually. Using bidder logic, it is possible to trade full portfolios with constraints on desired fill ratios, factor exposure, total notional value, dollar neutrality, etc. While the range of possible portfolio objectives and parameters is vast, the bidder logic to meet those objectives is often very similar.

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.

Note: these templates are meant to demonstrate bidder logic code in a simulated environment. Templates may require modification for use in production to meet your specific trading objectives and standards.

Target Portfolio Composition

Objective: Trade components of a given basket of securities simultaneously while enforcing specific quantity ratios between securities.

Each component is a security defined in bidder data as a symbol, side, and weighting. Symbol and side are used to map bidder data to corresponding target orders. Weighting is used to enforce execution in atomic "slices", i.e., single increments with each component filled in the desired ratio.

Bidder data:

reason
type component = {symbol: string, side: side, weighting: int}
type weightings = {basket: list(component)}

/* example; provided over FIX */
let ratios_data_sample = {
  basket: [
    {symbol: "ASDF", side: Sell, weighting: 6},
    {symbol: "QWER", side: Sell, weighting: 3},
    {symbol: "ZXCV", side: Sell, weighting: 1}
  ]
};

Bidder Logic:

reason
let weighted_basket: bidder(_) = (arg, ~mkt) => {
    open Bid;
    let weighted_qtys = List.map(o =>
        o.data.weighting * qty_order(o.order), arg.orders);

    Ok ([subject_to(all_eq(weighted_qtys),
        place_orders(arg.orders))]);
}

Net Price Basket

Objective: Buy and/or sell a mix of securities in single "unit" increments and at a net price per unit, where "unit" is a desired fill quantity ratio across securities.

Instead of specifying a limit price for each security in the basket, we specify a limit amount we would need to pay or receive for a combined "unit" of the securities in the basket. Here the basket_component is identical to the component type defined above. The price is now represented as unit_net_price in the basket_data record.

Bidder data:

reason
type basket_component = {symbol: string, side: side, weighting: int};
type basket_data = {
  weightings: list(basket_component),
  max_units: int,
  unit_net_price: with_side(int)
};

/* example; provided over FIX */
let basket_data_sample = {
  weightings: [
    { symbol: "ASDF", side: Sell, weighting: 7 },
    { symbol: "QWER", side: Buy, weighting: 3 },
    { symbol: "ZXCV", side: Sell, weighting: 1 }
  ],
  max_units: 1000,
  unit_net_price: (Buy, 2309)
};

Bidder Logic:

reason
let net_price_basket: bidder(_) = (arg, ~mkt) => {
    open Bid;
    let weighted_qtys = List.map(o => qty(o)/o.data.weighting, arg.orders);

    Ok ([subject_to(all_eq(weighted_qtys),
        place_basket(
            sum_map(o => o.data.weighting * qty(o), arg.orders),
            arg.bidder_data.unit_net_price,
            arg.bidder_data.max_units
        ))
    ]);
}

Basket of substitutes

Objective: Buy or sell a basket of stocks (e.g. an industry / sector), with indifference as to symbol-wise composition of the basket.

We can express willingness to accept up to the entire desired notional amount in each stock, while ensuring the total quantity filled remains below the desired amount. In this example, we assume true indifference between each security. For examples that incorporate a measure of correlation (beta) with the desired sector (or other factor), see the examples in hedging.ipynb.

Bidder data:

reason
type sector_data = {
  symbols: list(string),
  notional_max: int,
  side: side
};

/* example; provided over FIX */
let sector_data_sample = {
  symbols: ["FB", "AMZN", "AAPL", "NFLX", "GOOG"],
  notional_max: 100000,
  side: Buy
};

Bidder Logic:

reason
let sector_exposure: bidder(_) = (arg, ~mkt) => {
  open Bid;
  let total_notional = sum_map(o =>
        o.order.price * qty_order(o.order), arg.orders);

  Ok ([subject_to(total_notional <= const(arg.bidder_data.notional_max),
      place_orders(arg.orders))
  ]);
}

Dollar Neutral Basket

Objective: Execute a basket of stocks in any dollar-neutral combination, within some tolerance (provided via FIX along with component orders).

Given a set of target orders to buy and sell a basket of securities, enforce a constraint that any executions must be dollar neutral within a given tolerance bound provided at runtime as bidder data.

reason
type dollar_neutral_data = {
    tolerance: price,
};

reason
let dollar_neutral_wave: bidder(_) = (arg, ~mkt) => {
    let side_coeff = fun | Buy => 1 | Sell => -1;
    open Bid;

    /* Compute net notional dollars -filled- using 'qty()' function */
    let net_notional = sum_map(o =>
        side_coeff(o.order.side) * qty(o), arg.orders);

    Ok ([subject_to(abs(net_notional) <= const(arg.bidder_data.tolerance),
        place_orders(arg.orders))]);
};

Next Steps

  1. 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.
  2. Modify to your needs and reach out to the OneChronos team at [email protected] for usage in production.