OneChronos
OneChronos
    • Expressive Bidding

    • Introduction
    • Getting Started
    • Expressive Bidding Guide
    • Quickstart Examples
    • Runnable Templates
      • About These Templates
      • Single Stock Execution
      • Price-Quantity Indifference Curves
      • Hedging
        • Single Stock Hedging
        • Beta Neutral Execution
        • Next Steps
      • Pairs
      • ETFs
      • Portfolios
    • Developer Reference
    • Expressive Bidding FAQ

Hedging

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.

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:

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:

Try This!
reasonml
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:

Try This!
reasonml
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:

reasonml
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βjwj≈0  ⟹  ∣∑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}.j=1∑n​βj​wj​≈0⟹∣∣​j=1∑n​βj​wj​∣∣​≤εtol​.

where wjw_jwj​ is the weight of holding jjj in dollars (price times quantity), βj\beta_jβj​ is its sensitivity with respect to the market, and nnn 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∣βj∣pjqj,\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,βnet​=j=1∑n​βj​pj​qj​,βabs​=j=1∑n​∣βj​∣pj​qj​,

...and specify a constraint that:

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

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

Bidder Data:

Try This!
reasonml
type beta = {
    symbol: string,
    side: side,
    price: int,
    beta_coeff: int,
};

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

Bidder Logic:

Try This!
reasonml
/* 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};
Try This!
reasonml
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 check the submission guide for instructions on usage in production.
Try This!
reasonml