Markets II: Rates, FX and Credit · Markets
15FX Spot Microstructure
On 15 December 2010 a client of a large bank’s electronic FX platform wrote to it: “we have noticed that there were over 300 rejected orders with you today and the reason is ‘NACK’, could you pls have a look at them”. Two days later, with no answer, it wrote again: “We kept receiving top of the book rates from you and hitting your rate, but we got rejected by you 9 times out of 10”. The New York State Department of Financial Services quoted both messages five years later, in the consent order by which Barclays paid USD 150 million, and found no evidence that the bank had ever replied. The rejections came from a practice called last look: the bank held each order for a few tens or hundreds of milliseconds and refused it if the price had moved against the bank in the meantime. This chapter is about the machinery of electronic FX spot: how prices are streamed to clients, why the price a client gets depends on who it is, how last look works and what it costs (Figure 15.1), how dealers skew and internalise, and the code of conduct the market wrote for itself.
15.1 Streaming prices and tiered liquidity
Definition 15.1 (Streaming quote, liquidity tier)
A streaming quote is a bid and an ask, for given sizes, that a liquidity provider publishes continuously to a client or a platform and updates many times a second; the client trades by sending a request at a displayed price. A liquidity tier is the set of spreads and sizes a provider shows to a group of clients, set by its view of their flow.
Electronic spot FX is mostly traded on streams. A dealer’s pricing engine takes in the primary venues, other platforms and its own flow, forms a view of the mid, and publishes to each client a price around it: tight for clients whose trades rarely precede price moves, wider for the others, and sometimes wider again when markets are fast. Principal trading firms stream too, directly to banks and on platforms (Chapter 14). A client with access to many streams sees the best bid and ask across them, aggregated, and sends its request to whoever shows the best price.
The dealer’s problem is that a stream is a free option. It shows a price for a few milliseconds, and anyone who knows the market has moved before the stream has caught up can trade on the stale side. The spread is the dealer’s protection against uninformed noise; tiers, last look and skews are its protection against the rest.
15.2 Adverse selection
A market maker’s worst clients are those who trade just before the price moves their way, the adverse selection of One Quant Book 1, chapter 1. In FX spot the purest form is latency arbitrage: a fast firm sees the price move on one venue and hits the stale quotes of slower providers elsewhere before they update. A provider measures its clients with mark-outs.
Definition 15.2 (Mark-out)
The mark-out of a trade at horizon is the provider’s profit from marking the position at the mid after the trade: the half-spread earned minus the move of the mid in the client’s favour over . Averaged over a client’s trades at several horizons, from milliseconds to minutes, it measures how informed the client’s flow is.
A client whose trades have mark-outs close to the half-spread at every horizon is uninformed; its flow is valuable, and it gets the best tier. A client whose mark-outs turn negative within a second is trading on information the provider did not have; it is shown wider prices, slower confirmations, or nothing.
15.3 Last look and hold times
Definition 15.3 (Last look, hold time, reject rate)
Last look is a practice by which a liquidity provider receiving a trade request at its quoted price has a final opportunity, a short window after the request arrives, to accept or reject it. The hold time is the length of that window; the reject rate is the share of requests rejected. The price check can be asymmetric, rejecting only requests whose price has moved in the client’s favour beyond a threshold, or symmetric, rejecting moves beyond it in either direction.
The provider’s argument for last look is that it can quote tighter if it may refuse the trades that arrive on stale prices. The client’s objection is that a rejected request leaves it with the market risk it wanted to shed, at a worse price, and that an asymmetric check lets the provider keep the trades in which the price moved its way and refuse the others.
Proposition 15.4 (What an asymmetric window takes)
Let the price move in the client’s favour during the hold time by , independent of the request, as it is for an uninformed client. An asymmetric check with threshold rejects when , and the client gives up, per request,
A symmetric check rejects and takes nothing in expectation (Figure 15.2).
Proof. For with , . Under the symmetric check the rejected region is symmetric about zero, so the rejected moves average to zero. ∎
The symmetric check still costs the client something, the fills it loses, but it does not select against it. This is the distinction regulators drew. In the New York order, Barclays had compared the price at the start and at the end of a hold of tens to hundreds of milliseconds and rejected the orders that had become unprofitable to it; it made the check symmetric in September and October 2014, and one platform, with 7% of its volume, stayed asymmetric until August 2015.
Figure 15.3 shows both effects on a simulated stream. Informed requests, which arrive after a move of 0.2 basis points the quote has not caught up with, are almost all rejected by either check at short holds. Uninformed requests are rejected more by the symmetric check, which refuses moves either way, but that costs them nothing on average; the asymmetric check rejects fewer of them and its mark-out rises with the hold, because every extra millisecond gives the price more time to move in the client’s favour and be refused.
Example 15.5 (The cost of a window)
With the stream’s volatility, 0.1 basis points over 100 milliseconds, an asymmetric check with a threshold of 0.05 basis points and a 100-millisecond hold takes basis points per request from uninformed clients; with no threshold, 0.0399. On USD 2 billion of requests a day this is about USD 7 041 a day, USD 1.76 million over 250 trading days. The symmetric check with the same threshold rejects 62% of the requests and takes nothing on average.
15.4 Skewing and internalisation
Definition 15.6 (Quote skewing)
Quote skewing is moving a stream’s bid and ask together, off the provider’s estimate of the mid, to attract trades that reduce its inventory: a provider long euros lowers both prices so that clients buy euros from it and fewer sell to it (Figure 15.4).
A large provider that receives thousands of client requests a minute can internalise much of its risk (One Quant Book 1, chapter 10): a sale by one client is offset by a purchase by another, and only the residual needs to be hedged on a venue. Skewing makes the netting happen sooner, at the cost of a small concession on price, and every hedge the provider does not send to a venue is information it does not reveal. The inventory models behind skews, which trade off expected spread against the risk of holding a position, are those of One Quant Book 1 and of the market-making games of Chapter 30.
15.5 The global code of conduct
Definition 15.7 (FX Global Code)
The FX Global Code is a set of principles of good practice for the wholesale FX market, maintained by the Global Foreign Exchange Committee of central banks and private-sector participants. It is not law: participants adhere by signing a statement of commitment.
The Code arrived in 2017, two years after the enforcement cases over the fixing of Chapter 17, and last look is among its most argued-over sections. Principle 17, as updated in December 2024, requires providers that use last look to be transparent about it: to say whether and how price changes in either direction affect the decision, how long it takes and why they use it. It defines last look as a risk control, to check that a request is valid and that its price is still consistent with the market, and forbids using it to gather information or to trade on the request during the window, whether by adjusting prices or by hedging. In August 2021 the Committee published templates on which providers disclose their practices.
Remark 15.8 (What the Code leaves open)
The Code does not ban asymmetric checks, nor does it set hold times. It asks providers to disclose them and clients to read the disclosures; the pressure that remains is competitive. Clients measure their fill ratios, hold times and mark-outs by provider, as the tutorial does, and route their requests accordingly.
As of September 2026 — The Code today
The FX Global Code’s current text is dated December 2024; its Principle 17 on last look is as summarised above. The Global Foreign Exchange Committee, set up in May 2017 to maintain it, published in August 2021 a guidance paper on last look and standard templates on which liquidity providers disclose their practices. Adherence remains voluntary, by statement of commitment.
15.6 Tutorial: last-look transaction-cost analysis
Goal. Simulate a stream with informed and uninformed clients, apply asymmetric and symmetric last-look checks, and measure reject rates, fill ratios and mark-outs as a function of the hold time. End state: Figure 15.3, Example 15.5 and the numbers of the weekend problem.
The check and the stream: each request’s move during the hold and after it.
def check(move: float, threshold: float, policy: str) -> bool: """True if the request is accepted.""" if policy == "none": return True if policy == "asymmetric": return move <= threshold return abs(move) <= threshold # symmetric def simulate(n: int, sigma: float, hold: float, threshold: float, policy: str, informed_share: float = 0.0, edge: float = 0.0, horizon: float = 1000.0, seed: int = 1) -> list[Request]: rng = random.Random(seed) out = [] for _ in range(n): informed = rng.random() < informed_share jump = edge if informed else 0.0 at_decision = jump + rng.gauss(0.0, sigma * math.sqrt(hold)) after = at_decision + rng.gauss(0.0, sigma * math.sqrt(horizon)) out.append(Request(informed, at_decision, after, check(at_decision, threshold, policy))) return outListing 15.1. The last-look check and a simulated stream of requests. code/firm/lastlook/firm_lastlook.py The analysis: fill ratio, reject rates by client type, mark-out, and the closed form of Proposition 15.4.
def tca(requests: list[Request], half: float) -> dict[str, float]: """Fill ratio, reject rates by client type, and the provider's mark-out per filled request (half-spread earned less the client-favourable move by the horizon), in basis points.""" filled = [r for r in requests if r.accepted] inf = [r for r in requests if r.informed] uninf = [r for r in requests if not r.informed] def reject_rate(rs: list[Request]) -> float: return sum(not r.accepted for r in rs) / len(rs) if rs else 0.0 return {"fill_ratio": len(filled) / len(requests), "reject_informed": reject_rate(inf), "reject_uninformed": reject_rate(uninf), "markout": sum(half - r.move_after for r in filled) / len(filled) if filled else 0.0, "client_gain_filled": sum(r.move_at_decision for r in filled) / len(requests)} def expected_transfer(sigma: float, hold: float, threshold: float, policy: str) -> float: """Expected client-favourable move given up per request by rejections, uninformed flow: E[move * 1{rejected}] for move ~ N(0, sigma^2 * hold).""" s = sigma * math.sqrt(hold) phi = math.exp(-0.5 * (threshold / s) ** 2) / math.sqrt(2 * math.pi) if policy == "asymmetric": return s * phi # E[X 1{X > t}] return 0.0 # symmetric: the two tails cancelListing 15.2. Transaction-cost analysis and the expected transfer. code/firm/lastlook/firm_lastlook.py - Run
lastlook_demo.by_holdfor each policy,lastlook_demo.window()andfig_lastlook.py.
What to change next. Replace the informed clients’ fixed edge by a random one, and find the hold time at which a symmetric check has rejected 90% of them; then plot the uninformed clients’ fill ratio at that hold.
15.7 Build: the last-look analyser
Purpose. The miniature firm both takes and gives liquidity in FX spot: as a taker it must know what each provider’s last look costs it; as a maker it must be able to show its clients that its own check is symmetric and short.
Interface. check(move, threshold, policy); simulate(n, sigma, hold, threshold, policy, informed_share, edge, horizon, seed) returning Request records; tca(requests, half); expected_transfer(sigma, hold, threshold, policy).
Rules. Moves in basis points, positive in the client’s favour; Brownian mid; policies none, asymmetric, symmetric; seeded simulation.
Acceptance tests. code/firm/lastlook/tests/: the policies’ definitions; no last look fills everything; the simulated asymmetric transfer within 2% of the closed form; informed requests rejected far more often than uninformed ones.
Stretch. Read real fill and reject records (a FIX log) and compute each provider’s hold-time distribution and mark-outs; test statistically whether a provider’s rejects depend on the direction of the move.
Sources and further reading
- New York State Department of Financial Services, Consent Order, In the Matter of Barclays Bank PLC, November 2015.
- Global Foreign Exchange Committee, FX Global Code, updated December 2024; press release on last-look guidance and disclosure templates, August 2021.
- Bank for International Settlements, Quarterly Review, December 2022, box on the fragmented spot market.
15.8 Exercises
Exercise 15.1 ★
A provider sells a client EUR 1 million at 1.1464 when the mid is 1.1463. One second later the mid is 1.1465. Give the provider’s one-second mark-out in dollars and in basis points.
Solution
Solution of Exercise 15.1.
The provider earned the half-spread, USD 100, and lost the two-pip move of the mid in the client’s favour, USD 200: a mark-out of , or basis points of the mid.
Exercise 15.2 ★
Give the standard deviation of the price move over holds of 25, 100 and 400 milliseconds at 0.01 basis points per square-root millisecond.
Solution
Solution of Exercise 15.2.
: 0.05, 0.1 and 0.2 basis points.
Exercise 15.3 ★
What must a provider that uses last look disclose under Principle 17, and what must it not do during the window?
Solution
Solution of Exercise 15.3.
Whether, and how, price changes in either direction affect the decision to accept or reject; the expected or typical time taken to decide; and the purpose of last look. During the window it must not use the request to gather information with no intention of trading, nor trade on it, by adjusting its prices or hedging.
Exercise 15.4 ★★
Give the transfer per request of an asymmetric check at a 25-millisecond hold and zero threshold, and compare it with the 100-millisecond hold.
Solution
Solution of Exercise 15.4.
basis points at 25 milliseconds, against 0.0399 at 100: a quarter of the hold, half the cost.
Exercise 15.5 ★★
A provider long EUR 20 million skews its EURUSD stream by 0.1 pip. Which way, and why may this be cheaper than hedging on a primary venue?
Solution
Solution of Exercise 15.5.
Down: it lowers both bid and ask by 0.1 pip, so that clients buying euros come to it and clients selling go elsewhere. It pays a tenth of a pip on the trades it attracts, less than the spread and market impact of selling EUR 20 million on a venue, and reveals nothing to the market.
Exercise 15.6 ★★
Why does a symmetric check reject more uninformed requests than an asymmetric one with the same threshold, yet cost them less?
Solution
Solution of Exercise 15.6.
It rejects moves beyond the threshold in both directions, twice the region of the asymmetric check. But the rejected moves in the client’s favour are offset by rejected moves against it, which the client would otherwise have had to accept; the asymmetric check keeps those, and refuses only the good ones.
Exercise 15.7 ★★★
Coding. From by_hold, give the informed and uninformed reject rates at 100 milliseconds under each policy, and the mark-outs.
Exercise 15.8 ★★★
Find the flaw. “Our reject rate is only 5%, so our last look costs our clients nothing.” Correct it.
Solution
Solution of Exercise 15.8.
The reject rate does not measure what is rejected. An asymmetric check that rejects 5% of requests, all of them those in which the price moved in the client’s favour, still transfers the value of those moves; and a hold time without rejects costs the client the option the provider holds during it. Look at the direction of the moves on rejected and accepted requests, the hold times, and the mark-outs, not only at the reject count.
15.9 Problem: The Asymmetric Window
Problem 15.1
Weekend problem — what a last-look window takes from a client
An asset manager sends USD 2 billion of EURUSD requests a day to a provider that uses last look with a 100-millisecond hold and a 0.05-basis-point threshold. The mid moves 0.01 basis points per square-root millisecond, the half-spread is 0.4 basis points, and the manager’s flow is uninformed.
Part I — The window.
- Give the standard deviation of the move over the hold.
- Under an asymmetric check, what share of requests is rejected?
- Under a symmetric check with the same threshold?
- Give the transfer per request under each.
- Give the asymmetric transfer with no threshold.
Part II — In money.
- Give the daily cost of the asymmetric window.
- Give it over 250 trading days.
- How does it compare with the half-spread the manager pays?
- Give the cost with a 25-millisecond hold and no threshold.
- Why does the cost grow with the square root of the hold?
Part III — What the manager sees.
- Which statistics reveal an asymmetric check?
- Why is a low reject rate not enough?
- What should the manager ask the provider, citing the Code?
- What else does a rejection cost, beyond the transfer?
- Why might the manager still prefer a provider with last look?
Part IV — Judgement.
- What did the New York order find wrong with Barclays’s check?
- Is a symmetric check fair to the client?
- How does a firm-price, no-last-look stream protect itself instead?
- State the named result: what the asymmetric window takes from the manager each year.
- In one sentence: what does a last-look window cost a client?
Solution
Solution of Problem 15.1.
1. basis points. 2. . 3. . 4. Asymmetric, basis points; symmetric, zero. 5. basis points. 6. , about USD 7 041. 7. About USD 1.76 million. 8. The half-spread costs USD 80 000 a day; the window adds 8.8% to it, hidden. 9. 0.0199 basis points, about USD 3 989 a day. 10. The move over the hold has a standard deviation proportional to , and the transfer is proportional to that standard deviation. 11. The direction of the price move on rejected requests (all in the client’s favour), the mark-outs of accepted requests against rejected ones, and hold times that differ by direction. 12. It counts rejects, not their direction: a small share of rejects, all on favourable moves, is exactly what a costly asymmetric check produces. 13. Under Principle 17, whether and how price moves in either direction affect acceptance, the typical hold time, and the purpose; and confirmation that the request is not used for trading during the window. 14. The market risk it wanted to shed, left with it at the moment the price moved against it, and the cost of trading again at a worse price. 15. Its quotes may be tighter or larger, and a symmetric, short, well disclosed check may be worth its lost fills. 16. That it was applied broadly and indiscriminately, without checking for latency arbitrage, that it rejected only trades unprofitable to the bank, and that clients were given misleading or no explanations. 17. It takes nothing on average, but it still refuses trades and leaves the client with the risk; its fairness depends on a short hold and on disclosure. 18. By wider spreads for clients with poor mark-outs, smaller sizes, tiers, faster price updates, and by refusing to stream to toxic flow at all. 19. Named result: the asymmetric window of 100 milliseconds at a 0.05-basis-point threshold takes 0.0352 basis points per request, about USD 1.76 million a year on USD 2 billion of requests a day. 20. The value of the price moves in its favour that the provider refuses to honour, plus the risk of every rejected trade.
15.10 Interview questions
Interview question 15.1 ★ trader, developer
What is last look, and why do liquidity providers use it?
Solution
Solution of Interview question 15.1.
A short window after a trade request arrives during which the provider may accept or reject it at the quoted price. Providers use it to protect streamed prices against stale-price and latency arbitrage, to check credit and validity, and so to quote tighter to everyone; the cost falls on clients whose requests are rejected.
What the interviewer is looking for: the definition, the protection argument and its cost.
Interview question 15.2 ★ researcher, trader
How would you tell, from your fills, whether a provider’s last look is asymmetric?
Solution
Solution of Interview question 15.2.
Record for every request the mid at the request, at the decision and after it. Compare the distribution of the move during the hold for rejected and accepted requests: with a symmetric check, rejects occur on large moves in both directions; with an asymmetric one, only on moves in your favour. Test whether reject probability depends on the sign of the move, and whether hold times differ by direction.
What the interviewer is looking for: conditioning on the direction of the move.
Interview question 15.3 ★★ researcher
What is a mark-out, and how do you use mark-outs to tier clients?
Solution
Solution of Interview question 15.3.
The provider’s profit on a trade marked at the mid at a later horizon: the half-spread minus the client-favourable move. Averaged by client over horizons from milliseconds to minutes it shows who trades ahead of moves. Tier by it: tight prices for clients with positive mark-outs, wider or slower for the others, re-estimated regularly with confidence intervals.
What the interviewer is looking for: the definition and a sensible tiering procedure.
Interview question 15.4 ★★ trader
You are long EUR 50 million from client flow. Do you skew, hedge, or wait?
Solution
Solution of Interview question 15.4.
It depends on the flow expected and the risk limits. If clients are likely to buy euros soon, skew the price to attract them and internalise; if the position is large against the limit or the market is fast, hedge part of it on a venue or with an execution algorithm. Waiting without a skew is a view on the price, which a market maker should take only deliberately.
What the interviewer is looking for: inventory, expected flow and limits, not a single rule.
Interview question 15.5 ★★ bank, trader
What does the FX Global Code require of last look, and what does it not require?
Solution
Solution of Interview question 15.5.
Transparency and disclosure of how last look works, including its symmetry and timing; use only as a validity and price check; no information gathering and no trading on the request during the window. It does not ban asymmetric checks or fix hold times, and it is not law: adherence is voluntary, through statements of commitment.
What the interviewer is looking for: the substance of Principle 17 and its limits.
Interview question 15.6 ★★★ developer
Design a pricing engine that streams EURUSD to a thousand clients in several tiers and applies a symmetric last-look check, within a latency budget of tens of microseconds.
Solution
Solution of Interview question 15.6.
One core computes the mid from venue feeds; per-tier spreads and skews are applied in a precomputed table; quotes are published through a fan-out layer with per-client throttles. Requests arrive on a separate path; the check compares the current mid with the mid at the quoted time, stored with each quote identifier, against a symmetric threshold, after a fixed short hold; the decision and its inputs are logged for later analysis. No allocation on the hot path, lock-free queues, and a replay test against recorded market data.
What the interviewer is looking for: separation of pricing and acceptance paths, symmetric logic, logging for audit.