FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
hedge_executor.hpp File Reference

HedgeExecutor: keeps a strategy's exposure hedged with taker orders (docs/how-to/strategies/ hedge-executor.md). More...

#include "fastmm/core/enums.hpp"
#include "fastmm/core/fixed_point.hpp"
#include "fastmm/core/instrument.hpp"
#include "fastmm/core/log.hpp"
#include "fastmm/core/messages.hpp"
#include "fastmm/core/oms.hpp"
#include "fastmm/core/order.hpp"
#include "fastmm/core/risk_limits.hpp"
#include "fastmm/core/time.hpp"
#include "fastmm/core/venue_health.hpp"
#include "fastmm/strategies/hooks.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string_view>

Go to the source code of this file.

Classes

class  fastmm::HedgeExecutor
 
struct  fastmm::HedgeExecutor::Config
 
struct  fastmm::HedgeExecutor::Stats
 
struct  fastmm::HedgeExecutor::Status
 

Namespaces

namespace  fastmm
 

Detailed Description

HedgeExecutor: keeps a strategy's exposure hedged with taker orders (docs/how-to/strategies/ hedge-executor.md).

A strategy owns one, configures it in on_start and forwards its hooks.

Exposure and residual, in base units (linear contracts: quantity * contract multiplier):

residual = sum of source positions + sum of hedge positions - target

The sources are the instruments whose position is the exposure (a maker's quote instrument); the target is what the strategy wants to hold net (0 by default). The hedges are instruments that can take the other side, in preference order, each with its own price tolerance.

Sizing comes from positions only, never from a count of fills: a restart, a replayed or duplicated fill and an order whose outcome arrives late all end in the same place.

One order in flight at a time, across every hedge instrument and the de-risk orders: the residual is one number, and two orders sized from it on two venues would both fill. While an order is open on any hedge instrument (a previous session's included, once reconciliation adopts it), nothing else goes out; its end sends the next one. No order goes out while a venue reconciles (StrategyContext::reconciling): a position may still lack a replayed execution.

A hedge is an IOC limit on the first usable hedge instrument, priced its tolerance through the touch, of -residual in its contracts rounded down to the lot and capped at the instrument's max_qty (the remainder follows when it ends). None goes out when that rounds under the lot, min_qty or min_notional: the remainder waits for more exposure rather than being refused.

The order is then cut to what [risk] admits now (StrategyContext::risk_headroom): max_order_qty, max_order_notional at the order's price, and on its side the room of max_position and of [risk.underlying] max_net. A hedge larger than one of them goes out in pieces, each after the one before it ends, instead of being refused whole every time. When the limits admit nothing the instrument's minimums allow (the position room is used up), the order goes out whole: the engine refuses it and that is a failure of the instrument, as before. The exposure caps (max_gross_notional, max_net_notional) size it the same way, through the room the engine gives for one order in the instrument's own currency (RiskHeadroom::exposure_buy_notional).

A hedge instrument is usable when its venue's order channel is up, its venue is not killed, its book is valid, not older than Config::stale and not held by the feed-lag gate, and it is not benched. The first usable one takes the hedge; when its balance or margin cannot cover the order (StrategyContext::balance_room), the next usable one that can does. When none can, the hedge is held (logged and counted once per episode) until a balance report or a fill changes that.

Failures: a hedge that ends with nothing filled, or that the engine refuses, is a failure of its instrument; the next hedge waits Config::retry. max_failures within failure_window bench the instrument for Config::bench while another is not benched; when it is the last one, the executor halts (no more hedges) until restart(). A hedge whose outcome the venue never reported (the ack-timeout cancel, a reconciliation drop with quantity unaccounted for, a generic VenueReject such as a REST timeout) holds every order for Config::uncertain_hold, so a fill still on its way is booked before the positions are trusted again.

De-risking (Config::derisk_after > 0): when no hedge instrument has taken the residual for that long (unusable, balance short or halted), the executor reduces the exposure itself: reduce-only IOC orders on a source whose position has the residual's sign, priced derisk_tolerance through its touch, each at most derisk_step (base units), one at a time and derisk_interval apart. It stops as soon as a hedge instrument can take the residual again, or the residual is gone. The strategy must not quote the side a de-risk order crosses: pulling the quotes while can_hedge() is false and quoting only the reducing side while held() does that.

The executor has no timer of its own: the strategy calls on_timer from one of its timers (the retry, hold and de-risk waits end at the next call after them). Every call is noexcept and allocation-free. Linear instruments only: an inverse leg leaves it idle with an error.