Class LastLookExecution

java.lang.Object
com.quantfinlib.backtest.LastLookExecution
All Implemented Interfaces:
ExecutionModel

public final class LastLookExecution extends Object implements ExecutionModel
Last-look execution model — the missing realism for FX backtests: on ECN and single-dealer FX liquidity, the provider holds your order briefly and may reject it if the price moves against them during the hold. A backtest that fills every FX order unconditionally is fiction; rejects cluster exactly on the flow that was about to be profitable.

Bar-level model of the hold window: the order arrives at the bar open and the LP watches the intra-bar move.

Signal-bar handling: when worked through ExecutionAwareBacktester, the parent is created at the signal bar's CLOSE — so on that first bar there is no hold window left to observe, and filling at that bar's open would credit a price from before the signal existed (intrabar time travel). The model therefore HOLDS on the parent's signal bar (no fill, no reject counted — it is pure latency, the LP has seen nothing yet); the first real attempt is the next bar, whose open is the price standing when the order actually arrived. Direct calls without onParentOrder(com.quantfinlib.orderbook.Side, long, int) keep the plain arrives-at-the-open semantics.

  • Move in the taker's favor beyond rejectThresholdBps (price rising on a buy — adverse to the LP who would sell) → reject; the parent quantity carries to the next bar, exactly like real requote-and-chase.
  • Otherwise → full fill at the open plus the taker pays spreadBps half-spread (all-in price).

The asymmetry here is deliberate as a taker's worst-case model: it simulates the adverse LP behavior the FX Global Code prohibits but a taker must still budget for. The Code-compliant maker-side mechanism — symmetric rejection in both directions — is com.quantfinlib.trading.LastLookGate; when calibrating this model's threshold from an LP's published (symmetric) disclosures, note those statistics count rejects in both directions while this model rejects only one. Rejection statistics are exposed for TCA — a live desk watches its reject rate per LP for exactly this pattern.

  • Constructor Details

    • LastLookExecution

      public LastLookExecution(double spreadBps, double rejectThresholdBps)
      Parameters:
      spreadBps - half-spread paid on accepted fills
      rejectThresholdBps - intra-bar move (bps, in the taker's favor) beyond which the LP rejects
  • Method Details

    • onParentOrder

      public void onParentOrder(Side side, long totalQuantity, int signalIndex)
      Description copied from interface: ExecutionModel
      Notification that a new parent order has been created (entry or exit). Stateful models (e.g. IcebergExecution) reset per-parent state here.
      Specified by:
      onParentOrder in interface ExecutionModel
    • referencePrice

      public double referencePrice(BarSeries series, int index)
      Description copied from interface: ExecutionModel
      The price this model's fills are anchored to on the given bar — the engine budgets entry requests as cash / (referencePrice * (1 + worstCaseCostFraction())). Default: the bar close. A model that fills off a different price point (e.g. LastLookExecution fills at the OPEN) must override this, or a gap between close and its actual anchor lets a fully-filled request overdraw cash.
      Specified by:
      referencePrice in interface ExecutionModel
    • worstCaseCostFraction

      public double worstCaseCostFraction()
      Description copied from interface: ExecutionModel
      Upper bound on this model's all-in cost as a fraction of ExecutionModel.referencePrice(com.quantfinlib.core.BarSeries, int) (spread + fees + slippage). The engine uses it to size entries so that a fully-filled parent can never overdraw cash — a model whose fills can cost more than referencePrice * (1 + worstCaseCostFraction()) MUST override one or both methods, or the backtest silently trades on margin it doesn't have. Wrappers must DELEGATE both to the model that actually prices the fills. Default 1%.
      Specified by:
      worstCaseCostFraction in interface ExecutionModel
    • execute

      public List<Execution> execute(Side side, long requestedQty, BarSeries series, int index)
      Description copied from interface: ExecutionModel
      Executes up to requestedQty on this bar. Must never fill more than requested; may fill less (or nothing) — the remainder is retried on subsequent bars.
      Specified by:
      execute in interface ExecutionModel
    • fillCount

      public long fillCount()
      Accepted fills (parent-bar attempts, not shares).
    • rejectCount

      public long rejectCount()
      Last-look rejections — the number a real desk tracks per LP.
    • rejectRate

      public double rejectRate()
      Reject rate across all attempts, 0 when nothing was attempted.