Expressive Bidding

Hedging Templates

Entering into or exiting a position in a security often involves offsetting transactions in hedging instruments. Bidder logic can be constructed to atomically execute trades in target securities and their hedges in a single transaction, to manage legging risks associated with sequential or parallel execution in correlated instruments.

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.

Single Stock Hedging

Objective: Given a list of securities with weightings representing the degree to which they hedge each other, execute a single trade in some combination of those securities.

Each primary and hedge component includes a coefficient to its fill quantity, hedge_qty_coeff which takes into consideration per-share price differences and correlation. What we refer to as "primary" positions take a positive value for this coefficient, meaning those positions need to be hedged. Likewise, the hedging positions take a negative coefficient value. By constructing a constraint wherein the positive values (primaries) are offset by the negative values (hedges), we can automatically hedge our position. As an example, being filled as follows would represent a fully hedged position:

  • security: a; coefficient: 1; fill quantity: 2
  • security: b; coefficient: -2; fill quantity: 1

Since:

latex
1 * qty(a) + -2 * qty(b)
= 1 * 2 + -2 * 1
= 0

This equation is the basis for how we construct our hedging constraint in the example below. Note that in this example, we are targeting an exact hedging ratio of 50%. Below this example, we provide an alternative constraint that accepts some tolerance (i.e., error) on this hedging ratio.

Bidder Data:

reason
type primary = {side: side, symbol: string};
type hedge = {side: side, symbol: string, hedge_qty_coeff: int};
type hedge_data = {positions: list(hedge), target_hedge_ratio: int};

/* example; provided over FIX */
let hedge_data_sample = {
  positions: [
    /* Outright(s) (positive hedge coeff) */
    {symbol: "AAAA", hedge_qty_coeff: 4, side: Buy},
    /* Hedge(s) (negative hedge coeff) */
    {symbol: "BBBB", hedge_qty_coeff: -12, side: Sell},
    {symbol: "CCCC", hedge_qty_coeff: -3, side: Buy},
  ],
  target_hedge_ratio: 2
};

Bidder Logic:

reason
let single_factor_hedged: bidder(hedge, hedge_data) = (arg, ~mkt) => {
    let orders = arg.orders;
    let target_hedge_ratio = arg.bidder_data.target_hedge_ratio;

    open Bid;
    let abs_weighted_qty = sum_map(o =>
        abs(o.data.hedge_qty_coeff * qty(o)), orders);
    let hedge_adjusted_qty = sum_map(o =>
        o.data.hedge_qty_coeff * qty(o), orders);
    let hedging_constraint =
        abs_weighted_qty == target_hedge_ratio * hedge_adjusted_qty;

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

Note: a variant of this could express flexibility on the hedge ratio constraint:

reason
let hedging_constraint =
    /* hedge_ratio_min and hedge_ratio_max would represent
     * minimum and maximum acceptable hedging ratios, between -1 and 1 respectively */
    arg.bidder_data.hedge_ratio_min * hedge_adjusted_qty <= abs_weighted_qty
    && abs_weighted_qty <= arg.bidder_data.hedge_ratio_max * hedge_adjusted_qty;

Beta Neutral Execution

Objective: seek a trade that is approximately beta-neutral, meaning that we can tolerate some deviation from exact equality.

This Expressive Bid aims to constrain an execution across a set of securitites such that there is a tolerable increase or decrease in β \beta exposure as a result of the execution:

j=1nβjwj0    j=1nβjwjεtol.\sum_{j=1}^n \beta_j w_j \approx 0 \implies \left| \sum_{j=1}^n \beta_j w_j \right| \le \varepsilon_\textsf{tol}.

where wjw_j is the weight of holding jj in dollars (price times quantity), βj\beta_j is its sensitivity with respect to the market, and nn is the number of holdings in the portfolio. In this formulation, the portfolio can include short positions, i.e., have weights less than 0. Allowing for approximately neutral trades expands the feasible region under which a bidder can be matched; in addition, this flexibility can be important for receiving the price improvement effects of the auction process.

We can use limit prices in place of realized (i.e., auction-determined) prices to formulate our β\beta-neutral bidder; this approach leaves the free quantity variable as symbolic (to-be-determined) subject to maximum available price improvement.

To enforce our approximately neutral constraint, we'll use the trade's absolute β\beta in addition to its net β\beta. (The net β\beta is the one that we're constraining to be approximately zero.) So, we'll make use of the quantities

βnet=j=1nβjpjqj,βabs=j=1nβjpjqj,\beta_\textsf{net} = \sum_{j=1}^n \beta_j p_j q_j, \quad \beta_\textsf{abs} = \sum_{j=1}^n |\beta_j| p_j q_j,

...and specify a constraint that:

βnetβabsεtol.\left| \frac{\beta_\textsf{net}}{\beta_\textsf{abs}} \right| \le \varepsilon_\textsf{tol}.

We can interpret this constraint as allowing a relative (percent) error with respect to the trade's long-only β\beta.

Bidder Data:

reason
type beta = {
    symbol: string,
    side: side,
    price: int,
    beta_coeff: int,
};

type beta_data = {
    positions: list(beta),
    beta_tol: int,
};

Bidder Logic:

reason
/* Helper function to flip sign on beta coeff if selling */
let beta_side_adjusted = (side: side, beta: weight) => switch (side) {
      | Buy  => beta;
      | Sell => -1 * beta;
};

/* Strongly typed helper for constructing beta-adjusted price */
let beta_adjusted = (beta: weight, price: price) => {beta * price};

reason
let beta_neutral_portfolio_bidder: bidder(beta, beta_data) = (arg, ~mkt) => {
    open Bid;
    let beta_abs = sum_map(
        x => beta_adjusted(abs_val(x.data.beta_coeff), x.order.price) * qty(x),
        arg.orders
    );
    let beta_net = sum_map(
        x => beta_adjusted(
            beta_side_adjusted(x.order.side, x.data.beta_coeff),
            x.order.price
        ) * qty(x),
        arg.orders
    );

    /* proportional change in beta (net-beta divided by gross) within some tolerance */
    let beta_constraint = (
        /* rearrange terms as linear: -tol <= net/abs <= tol*/
        arg.bidder_data.beta_tol * beta_net + beta_abs >= const(0) &&
        arg.bidder_data.beta_tol * beta_net - beta_abs <= const(0)
    );
    Ok ([subject_to(beta_constraint, 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.