Last-Look Reject-Asymmetry FX Slippage Playbook
Focus: model and control the hidden execution tax created when streaming FX liquidity looks tight on the screen but rejects toxic or stale requests asymmetrically, forcing the taker into a slower, worse second attempt.
1) Why this matters in production
In spot FX, a stream can look cheap and deep right until you try to trade it.
The usual failure pattern:
- a taker sees an attractive quote,
- sends an order/request,
- the liquidity provider (LP) applies last look over a short evaluation window,
- toxic or stale requests get rejected disproportionately,
- the taker reroutes a few milliseconds later into a worse market.
This creates a slippage component that is easy to miss because the rejected trade never becomes a fill. If you only analyze accepted fills, you systematically under-measure true execution cost.
Operationally, this produces four recurring distortions:
- Phantom best-price bias — streams with low displayed spread but high reject probability look artificially good.
- Second-hop deterioration — the realized cost happens on the fallback venue, not the original venue that caused the delay.
- Toxicity selection bias — accepted flow looks clean because the worst flow was screened out.
- Protocol fragility — in fast markets, reject asymmetry can dominate ordinary spread differences.
This is exactly why market guidance around FX last look has focused on transparency around symmetric vs asymmetric checks and on preventing extra hold time that is used to wait for favorable future price moves rather than complete genuine validity/risk checks.
2) Core setup
Suppose the taker receives quote
[ q_t = (b_t, a_t) ]
and submits a buy or sell request at time (t). The LP evaluates it over a last-look window (\Delta), using a reference price observed at decision time (t+\Delta).
Let side (s \in {+1,-1}), where (+1) is buy and (-1) is sell. Let the requested execution price be (p_t), and let the LP's reference mid at decision time be (m_{t+\Delta}).
Define the signed move against the LP / in favor of the taker as:
[ X = s \cdot (m_{t+\Delta} - p_t) ]
A simple last-look decision rule is:
[ \text{Accept if } X \le \theta^+ \text{ and } X \ge -\theta^- ]
where:
- (\theta^+): tolerance for adverse selection against the LP,
- (\theta^-): tolerance for price improvement in favor of the LP / taker.
If (\theta^+ = \theta^-), the rule is symmetric. If (\theta^+ \ne \theta^-), the rule is asymmetric.
When rejected, the taker usually reroutes after latency (L_r) and executes at fallback price (p'_t). The realized reject-induced slippage is:
[ S_{rej} = s \cdot (p'_t - p_t) ]
This quantity is often the missing term in FX TCA.
3) What to measure live
3.1 Reject Asymmetry Ratio (RAR)
Compare reject rates on favorable vs unfavorable short-horizon price moves.
[ RAR = \frac{P(\text{reject} \mid X > x_0)}{P(\text{reject} \mid X < -x_0)} ]
for a small threshold (x_0) in bps or ticks.
Interpretation:
- (RAR \approx 1): closer to symmetric behavior,
- (RAR \gg 1): adverse-selection-heavy filtering,
- very large values: stream may be informational bait rather than true executable liquidity.
3.2 Reject Hold-Time Gap (RHG)
Difference between median reject and accept decision times:
[ RHG = \operatorname{Median}(T_{reject}) - \operatorname{Median}(T_{accept}) ]
A persistently positive RHG is suspicious because the taker pays not only the reject, but also the time spent waiting to be rejected.
3.3 Fallback-Hop Cost (FHC)
Mean cost of the second execution after rejection:
[ FHC = \mathbb{E}[S_{rej} \mid \text{reject}] ]
This is the cleanest economic measure of reject damage.
3.4 Display-to-Executable Spread Gap (DESG)
Difference between displayed spread and effective spread after reject handling:
[ DESG = \mathbb{E}[\text{effective spread}] - \mathbb{E}[a_t - b_t] ]
High DESG means the stream looks better than it trades.
3.5 Conditional Accept Drift (CAD)
Signed post-accept move of accepted trades:
[ CAD(\tau) = \mathbb{E}[s \cdot (m_{t+\tau} - p_t) \mid \text{accept}] ]
If accepts still have poor post-fill drift, the LP may be filtering only the most obviously toxic requests while still leaving the taker with weak executions.
3.6 Requote/Reject Replacement Cost (RRC)
If some venues requote instead of hard reject, normalize both into one cost bucket:
[ RRC = \mathbb{E}[s \cdot (p^{final}_t - p_t) \mid \text{reject or requote}] ]
This avoids undercounting “soft rejects.”
4) Modeling framework
Treat last look as a joint acceptance-and-cost problem, not a fill-only problem.
4.1 Two-stage model
For each LP / stream / protocol bucket, estimate:
- Acceptance model
[ \hat{P}_{acc} = P(\text{accept} \mid Z_t) ]
- Conditional fallback slippage model
[ \hat{S}{rej} = \mathbb{E}[S{rej} \mid \text{reject}, Z_t] ]
where features (Z_t) include:
- quote age,
- local and venue-wide volatility,
- short-horizon returns,
- pair/session bucket,
- stream/provider identity,
- request size relative to stream size,
- time since previous update,
- market-wide sweep intensity,
- local latency / jitter,
- prior reject streaks.
4.2 Effective expected cost
Rank candidate liquidity by expected executable cost:
[ EEC = \hat{P}{acc} \cdot C{acc} + (1-\hat{P}{acc}) \cdot (C{rej} + \hat{S}_{rej}) ]
where:
- (C_{acc}): expected cost if accepted,
- (C_{rej}): protocol/latency penalty of a reject before rerouting.
This prevents “best displayed quote wins” from over-routing to low-quality streams.
4.3 Survival-style view
Another clean approach is to treat accept/reject as competing outcomes over the evaluation window.
Model hazards:
- accept hazard (\lambda_A(t)),
- reject hazard (\lambda_R(t)).
This helps separate:
- fast accepts,
- slow accepts,
- fast rejects,
- slow rejects.
Slow rejects are economically much worse than equally probable fast rejects.
4.4 Regime gate
Gate by state:
CALMFAST_TAPENEWS_SHOCKTHIN_LIQUIDITY
Then use regime-specific acceptance and fallback models:
[ \hat{P}{acc} = \sum_r P(r \mid Z_t) \hat{P}{acc}^{(r)} ]
Reject asymmetry is usually small in calm states and dominant in shock states.
5) Correct TCA decomposition
A robust FX TCA stack should split total cost into:
[ TC = S_{display} + S_{accept} + S_{reject-delay} + S_{fallback} + S_{residual} ]
where:
- (S_{display}): cost implied by visible spread/quote,
- (S_{accept}): ordinary fill slippage on accepted trades,
- (S_{reject-delay}): time lost before learning the stream was not executable,
- (S_{fallback}): extra slippage on the second venue/provider,
- (S_{residual}): remaining opportunity cost from partial completion.
Most dashboards only capture the first two terms. In last-look-heavy protocols, the third and fourth terms are often the real story.
6) Live control rules
Define operator states:
STREAM_OKASYMMETRIC_WARNREJECT_TOXICSAFE_FIRM_ONLY
STREAM_OK
Conditions:
- RAR near 1,
- RHG small,
- FHC bounded.
Action:
- normal ranking by effective expected cost.
ASYMMETRIC_WARN
Conditions:
- RAR elevated,
- reject damage still tolerable.
Action:
- down-rank LP score,
- reduce size clips,
- diversify requests across more providers,
- prefer streams with better executable consistency over nominal spread.
REJECT_TOXIC
Conditions:
- high RAR,
- high RHG,
- large FHC during fast tape.
Action:
- suppress routing to affected streams for aggressive flow,
- cap retry count,
- switch to firm liquidity / more explicit RFQ flow,
- widen internal expectation of all-in spread.
SAFE_FIRM_ONLY
Conditions:
- news shock,
- repeated reject bursts,
- escalating fallback-hop cost.
Action:
- disable fragile streaming sources,
- route only to firm or pre-cleared liquidity,
- prioritize execution certainty over displayed tightness.
7) Practical feature ideas
Useful features that often predict reject asymmetry better than raw spread:
- quote age at request time,
- update intensity over last 5-50 ms,
- micro return from request to expected decision time,
- pair/session (Tokyo, London, NY overlap, roll windows),
- provider-specific reject history,
- accept/reject latency gap by provider,
- size-to-top-of-book ratio,
- cross-venue dispersion (if your target stream is already stale relative to peers),
- network jitter percentile,
- prior request burstiness from your own engine.
A very common result: reject behavior is more stable by provider × session × protocol than by pair alone.
8) Backtest protocol
Step A — Reconstruct hidden rejects
For every request:
- quoted price,
- request timestamp,
- accept/reject timestamp,
- final execution venue/provider,
- final execution price,
- interim market path.
Without request-level logs, you cannot estimate last-look tax honestly.
Step B — Build executable-cost leaderboard
Compare providers by:
- displayed spread,
- acceptance rate,
- RHG,
- FHC,
- all-in expected executable cost.
This often reorders the leaderboard dramatically.
Step C — Stress by regime
Slice results by:
- calm vs fast tape,
- macro event windows,
- major vs minor FX pairs,
- large clips vs small clips,
- high-latency vs low-latency network periods.
Reject asymmetry usually matters most where visible spread signals matter least.
Step D — Counterfactual routing
Replay with routing objective:
[ \min EEC ]
instead of best displayed quote.
Report:
- mean all-in slippage,
- p95/p99 slippage,
- completion rate,
- reject burst frequency,
- average number of routing hops.
Step E — Canary rollout
- start with small notional share,
- shadow-score all streams,
- hard rollback if p95 all-in slippage or reject-hop count worsens.
9) Typical pitfalls
- Accepted-fill survivorship bias: rejected requests disappear from fill-based TCA.
- Spread worship: tight quotes are not the same as cheap executable liquidity.
- Ignoring reject timing: a slow reject is worse than a fast reject.
- Provider pooling: averaging providers together hides asymmetric bad actors.
- No protocol normalization: streaming, RFQ, firm, and disclosed last-look flows should not be mixed naively.
- No second-hop attribution: the fallback venue gets blamed for slippage caused upstream.
10) Minimal 2-week implementation plan
Week 1
- Capture request-level accept/reject telemetry.
- Build RAR / RHG / FHC / DESG dashboards by provider and session.
- Estimate baseline acceptance and fallback-cost models.
Week 2
- Replace displayed-spread ranking with executable-cost ranking.
- Add state machine (
STREAM_OK→SAFE_FIRM_ONLY). - Canary rollout on selected pairs and volatile windows.
References
- Global Foreign Exchange Committee, Execution Principles Working Group Report on Last Look.
- Financial Conduct Authority (2021), FCA confirms recognition of the revised FX Global Code and the Global Precious Metals Code.
- Oomen, R. C. A. (2017). Last Look. Quantitative Finance, 17(7), 1057-1070.
- Norges Bank Investment Management, Asset Manager Perspective: Foreign Exchange Market Structure.
- Cartea, Á., Jaimungal, S., & Penalva, J. (2015). Algorithmic and High-Frequency Trading.
One-line takeaway
In FX, a quote that rejects asymmetrically is not tight liquidity but a hidden option sold by the taker; model the reject path, or your slippage numbers are fiction.