Expressive Bidding
Hedging Templates
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:
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:
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:
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 exposure as a result of the execution:
where is the weight of holding in dollars (price times quantity), is its sensitivity with respect to the market, and 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 -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 in addition to its net . (The net is the one that we're constraining to be approximately zero.) So, we'll make use of the quantities
...and specify a constraint that:
We can interpret this constraint as allowing a relative (percent) error with respect to the trade's long-only .
Bidder Data:
type beta = {
symbol: string,
side: side,
price: int,
beta_coeff: int,
};
type beta_data = {
positions: list(beta),
beta_tol: int,
};
Bidder Logic:
/* 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};
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
- 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.