FX Pricing Engine Architecture: Streaming Quotes, Aggregation, Skew and Last Look
Every second, a bank’s e-FX desk may publish thousands of two-way prices to hundreds of clients, and each of those prices is a promise it may be asked to honour a few milliseconds later. The system that makes and withdraws those promises is the fx pricing engine. Get it wrong by a fraction of a pip, or by a few milliseconds of staleness, and the desk either bleeds to faster counterparties or quotes so wide that clients leave.
This matters in 2026 because the market keeps growing. The Bank for International Settlements (BIS) Triennial Survey put average daily FX turnover at about $9.6 trillion in April 2025, up from $7.5 trillion in 2022, and most of it is OTC, fragmented and increasingly electronic. This post gives you a reference architecture for the pricing path, from raw liquidity provider (LP) feeds to the quote a client sees, plus the arithmetic and failure modes that decide whether it makes money.
What this covers: FX market structure as an engineering constraint, the six-stage price construction pipeline, aggregation and skewing mechanics with worked numbers, cross-rate triangulation, last look and the FX Global Code, a latency budget, a runnable Python aggregator sketch, and the failure modes that hurt in production. This is systems analysis, not trading or investment advice.
Context and Background
Foreign exchange is not an exchange. There is no central limit order book with a single price. Trades happen bilaterally between dealers (banks), non-bank market makers, and clients, with a layer of multi-dealer platforms and electronic communication networks (ECNs) in between. Interdealer venues such as EBS (now part of CME Group) and Refinitiv Matching (LSEG) provide anonymous central-limit-order-book style trading in major pairs, while platforms such as Currenex, Hotspot and FXall (verify current ownership and naming before relying on any of these, since the venue landscape has been through repeated consolidation) provide streaming and request-for-quote workflows. The consequence for engineers is simple: there is no authoritative tape. Every participant builds its own view of “the market” from the feeds it can afford to connect to.
The BIS survey gives scale. Spot was about $3 trillion per day (31% of turnover) and FX swaps about $4 trillion per day (42%), with the US dollar on one side of 89.2% of trades, according to the BIS 2025 Triennial Survey FX statistics. Those figures describe all venues and channels, not just the electronic streaming segment. They do explain why the dollar pairs dominate pricing engine design: the USD legs are the ones you hedge, and the cross pairs are derived from them.
Two roles matter in this post. A liquidity provider (LP) streams prices that others can trade on. A liquidity consumer may be a hedge fund, corporate treasury, or another bank. The engine described here sits inside a bank or non-bank market maker that is both: it consumes upstream prices from other LPs and ECNs, and it produces downstream prices for its own clients. This “price taker upstream, price maker downstream” duality is the core design tension. Your downstream quote is only as good as your upstream view, and your upstream hedge is only as cheap as your downstream flow is benign.
Comparable low-latency components appear elsewhere in this series. The ingestion layer shares much with a low-latency market data feed handler, the order interaction semantics echo an exchange matching engine, and the credit and limit checks that gate a fill are a cousin of the pre-trade risk engine. Where this post differs is in the absence of a central book: aggregation, skew and last look exist precisely because FX has no single matching venue.
Protocol landscape, stated carefully. FIX (Financial Information eXchange) is the dominant session protocol for FX streaming and order flow, and FIX Trading Community publishes the FX-relevant message sets (market data, quotes, orders). Many large LPs and ECNs also offer proprietary binary or low-latency APIs, which I will not enumerate because specifications are private and change. FpML (Financial products Markup Language) appears on the post-trade and derivatives side, not in the hot pricing path. The article treats “ITCH-like” binary feeds as an architecture pattern (sequenced, incremental, fixed-width messages) rather than an assertion that any particular FX venue uses ITCH; that is unverified here.
The Reference Architecture: Six Stages from LP Feed to Client Quote
The pricing path is a pipeline with one job: turn many noisy upstream price streams into one defensible two-way price per client segment per currency pair, and withdraw it before it becomes dangerous.
An fx pricing engine ingests streaming quotes from liquidity providers and ECNs, normalizes them into one book per currency pair, aggregates a best executable price, adds spread and markup for each client tier, skews the result by inventory, and publishes it with sequence numbers and expiry rules. Stale-quote protection and last look sit at the edges of that pipeline.

Figure 1: Reference architecture of an fx pricing engine, six stages from LP feeds to client streaming quotes and the risk and hedging loop.
Figure 1 shows the stages left to right. Upstream feed handlers terminate sessions with each LP and ECN. A normalizer converts each venue’s dialect into one internal tick format and stamps it with receive time. An aggregator builds a consolidated view per pair. A pricing core applies spreads, markups and skew. A distribution layer fans quotes out to client sessions, and a risk and hedging loop watches fills and feeds inventory back to the pricing core. The feedback arrow is the point: pricing is a control loop, not a feed-forward function.
Stage 1 and 2: Ingest and Normalize
Each upstream session delivers either full-depth snapshots, incremental updates, or a stream of “firm” quotes with a size and sometimes a validity time. The normalizer must reduce these to a common record: pair, side, price, size, venue id, venue timestamp, local receive timestamp, sequence number, and a flag for whether the quote is firm or subject to last look. That last flag is easy to overlook and expensive to omit, because a last-look quote is not the same asset as a firm quote when you want to hedge against it.
Normalization has three non-obvious jobs. First, pair conventions: the engine must agree on base and quote currency, standard lot size, and pip precision (four decimals for most pairs, two for JPY pairs, with many venues quoting an extra fractional pip). Second, size semantics: some venues quote in base currency, others in terms of quote currency or in multiples of a minimum clip. Third, clock handling: venue timestamps are not trusted for ordering, because clocks drift and not every venue disciplines time to the same standard. Receive-time stamping at the NIC, with hardware timestamps where available, gives a consistent local ordering, and venue timestamps are retained only for latency analytics and dispute resolution.
Stage 3: Aggregation
Aggregation merges the per-venue books into one consolidated book per pair. This is where liquidity aggregation lives, and it is not the same as a securities consolidated tape. Because some venues are firm and some are last look, because credit relationships limit who you can trade with, and because each venue has different fees, the “best price” is a function of who is asking and what they can actually trade. The aggregator therefore produces a view that is conditional on the pricing profile, a concept developed in the next section.
Stage 4: Pricing Core
The pricing core takes the aggregated bid and offer, converts them to a mid and a skew-adjusted pair, then layers on spread and markup per client segment. It also applies position limits, minimum and maximum quote sizes, and tiered quantity ladders so that a client asking for 50 million EUR sees a different price from one asking for 1 million.
Stage 5 and 6: Distribution and the Risk Loop
Distribution publishes quotes to clients over FIX or proprietary APIs, assigning each quote an id, a validity window and (for last-look flows) a disclosure of the acceptance rules. The risk loop consumes executions, updates net open position by currency, and decides whether to hedge immediately, hold, or skew prices to encourage offsetting flow. The next sections walk each mechanism with numbers.
Deeper Analysis: Aggregation, Skew, Triangulation and Last Look
This section goes through the mechanisms that distinguish a competitive engine from a naive one. All numbers are illustrative unless stated otherwise; no real LP’s prices or latencies are implied.
Liquidity Aggregation: Building an Executable Book
Suppose three LPs stream EUR/USD. LP-A quotes 1.0850 / 1.0852 for 5 million, LP-B quotes 1.0849 / 1.0853 for 10 million, and LP-C quotes 1.0851 / 1.0854 for 3 million. A naive best-of-breed view uses the highest bid and lowest offer: bid 1.0851 (LP-C, 3 million) and offer 1.0852 (LP-A, 5 million). That is a one-pip spread, but only for 3 and 5 million. Anything larger walks the book.
A depth-aware aggregator builds a cumulative ladder. For a client who wants to buy 8 million, the cost to hedge is the volume-weighted offer across the cheapest levels: 5 million at 1.0852 and 3 million at 1.0853 (LP-B’s offer, since LP-C’s 1.0854 is worse). The weighted average is (5 x 1.0852 + 3 x 1.0853) / 8 = 1.085238. Notice that the aggregator quotes a price the desk can actually hedge, not the touch. This is the first principle of aggregation: quote what you can cover.
Three design choices recur. Full-amount versus partial fills: some LPs fill only the complete requested size, others partially, which changes whether a 5-million offer can be combined with another. Sweepable versus non-sweepable: some venues allow a single order to hit multiple price levels, others do not. And the netting question: if one client is buying and another is selling, the engine may internalize part of the flow and hedge only the residual, so the aggregate book used for pricing should reflect that internal offset potential.

Figure 2: Liquidity aggregation and price skewing flow, from per-LP books through a pricing profile to the final skewed streaming quote.
Figure 2 depicts the data flow. Each LP book is filtered by the pricing profile (which LPs this client segment may be priced from), credit availability, and quote quality score. The filtered books feed a depth ladder, which yields a size-dependent bid and offer. Spread, markup and skew then adjust that raw ladder into the published quote.
Quote quality scoring deserves a note. A sensible aggregator does not treat all LPs equally. It tracks, per LP and per pair, the fill rate on hedges, the rejection rate on last-look quotes, the observed hold time, and the price move following a fill (“markout”). An LP whose quotes disappear exactly when you want to trade, or whose fills are consistently followed by adverse moves, deserves a lower weight or a wider penalty. This is where the aggregator quietly becomes a statistical system rather than a data merger.
Spread and Markup
The raw executable price is the cost of hedging. The published price adds a spread (compensation for risk, and the half-spread around mid) and a markup (client-specific charge reflecting relationship, credit cost and expected flow toxicity). Many desks decompose the final spread into three additive components: a base market spread tied to volatility and liquidity, a client tier markup, and a skew term. Expressing each as a separate, auditable term pays off in two ways: sales can explain the price, and risk can disable a single term without redeploying the engine.
Static spreads are the weak baseline. Dynamic spreads widen with realized volatility, with news calendar events, with thin-liquidity hours (for example, the roll window around 17:00 New York time, when many desks treat the value-date roll as an operationally sensitive period), and with the size of the quote requested. The widening functions are policy, not physics, so a good engine keeps them as data-driven configuration with version control and simulation tooling.
Price Skewing by Inventory
Price skewing shifts both sides of the quote in the same direction to attract flow that reduces inventory. If the desk is long 20 million EUR against USD and does not want more, it lowers both bid and offer: the lower bid discourages clients from selling more EUR to the desk, and the lower offer encourages clients to buy EUR from it.
A simple linear skew rule: skew_pips = k x net_position_millions, capped at a maximum. With k = 0.05 pip per million and a cap of 2 pips, a 20 million EUR long position gives a 1.0 pip downward shift. Starting from a symmetric quote of 1.08495 / 1.08525 (a 3-pip spread, mid 1.08510), the skewed quote becomes 1.08485 / 1.08515. A client wanting to buy EUR now pays 1.08515 instead of 1.08525 and sees a more attractive offer.
The control-theory view is useful. Skew is proportional feedback on inventory. Too small a gain and the book accumulates risk; too large a gain and the desk’s prices oscillate, and a sophisticated client can exploit the visible skew to infer positions. Production engines often add a deadband (no skew inside a small position), a rate limiter on skew changes, and an asymmetric rule so that skew widens only the side the desk wants to avoid rather than shifting both. Skew should also be gated by hedging cost: if hedging is cheap, tolerate more inventory; if it is expensive or the market is volatile, skew harder.
A subtle point: skew is distinct from hedging, and the two interact. Aggressive skew reduces the need to hedge at the market but reveals information in the prices. Immediate hedging protects P&L but pays the spread and leaks information to the hedging venues. Mature desks run a policy: internalize within limits, skew within a band, hedge residual above thresholds, with the thresholds depending on pair, time of day and volatility regime.
Cross-Rate Triangulation Worked Example
Most crosses are not directly liquid; they are synthesised from two USD legs. To price EUR/JPY from EUR/USD and USD/JPY:
- EUR/USD = 1.0850 / 1.0852 (bid / ask)
- USD/JPY = 150.20 / 150.23
Start with the desk’s bid. If a client sells EUR/JPY to the desk, the desk is long EUR and short JPY. To hedge it, the desk sells EUR for USD at the EUR/USD bid, receiving 1.0850 USD per EUR, then sells USD for JPY at the USD/JPY bid, receiving 150.20 JPY per USD. The synthetic EUR/JPY bid is therefore 1.0850 x 150.20 = 162.967.
For the offer, the desk hedges a client’s EUR purchase by buying EUR with USD at 1.0852 and buying USD with JPY at 150.23: 1.0852 x 150.23 = 163.0296 (rounded). The synthetic spread is 163.0296 – 162.967 = 0.0626 JPY, about 6.3 pips on the 0.01 JPY pip convention, versus 2 pips on EUR/USD and 3 pips on USD/JPY (0.03 JPY). The cross is wider than either leg because the two half-spreads compound through the multiplication. This is why a direct EUR/JPY liquidity source can beat triangulation, and why aggregators prefer to compare direct and synthetic prices continuously.
Triangulation has operational traps. The two legs must be time-aligned: using a EUR/USD tick from 5 ms ago and a USD/JPY tick from now creates phantom arbitrage. Rounding must follow the venue’s tick size, not the engine’s float representation (use integer scaled prices, discussed later). And the currency hedge is two trades, so the risk model must consider the leg risk window between them.

Figure 3: Sequence of a client trade request through last look, validity and price checks, hedge decision and acknowledgement.
Last Look and the FX Global Code
Last look gives a liquidity provider a final opportunity to accept or reject a client’s trade request against its quoted price. The FX Global Code, maintained by the Global Foreign Exchange Committee (GFXC), addresses it in Principle 17. According to the GFXC’s August 2021 report on last look, last look should be a risk control mechanism used for a price and validity check only. The report says it should not be used for information gathering, and that a trade request creates confidential information which must be handled under the Code’s information-sharing principles (Principles 19 and 20).
The same report lists what the two checks mean. The validity check confirms the trade is operationally appropriate and that credit is sufficient. The price check confirms the quoted price is still consistent with the current price within a defined tolerance, and it may be symmetric (same tolerance both ways) or asymmetric (accepting more readily when the market moved in the LP’s favour). The report asks LPs to disclose how last look works, including symmetry, typical decision time and any “cover and deal” arrangement, and to complete the standardised GFXC disclosures cover sheet. Read the primary text at the GFXC last look report rather than relying on summaries, including mine, since guidance evolves; I have not verified any changes after the 2021 publication.
For the engineer, last look becomes a state machine with an explicit time budget. A trade request arrives with a quote id and the client’s price. The engine looks up the quote, checks validity (credit, limits, session state), compares the quote price with the current internal price against a tolerance, and returns accept or reject. The Code says that additional delay beyond what the checks require is contrary to the principle, so the architectural goal is to make the check fast, deterministic and auditable, not to add a deliberate hold. Every reject must carry a reason code (validity or price) and be logged with the prices used so the desk can answer a client’s question.
The asymmetric price check is the controversial design choice. If the market moves against the LP between quote and request, reject; if it moves in the LP’s favour, accept, possibly with price improvement for the client. The client sees a one-way option exercised by the LP. Whether this is acceptable depends on disclosure and on how the LP prices the quote in the first place. A symmetric tolerance is simpler to defend.
Finally, last look is only one of several execution models. Firm streaming (no last look) shifts the risk to the LP and is typically priced wider; central-limit-order-book venues match firm orders; request-for-quote (RFQ) gives the LP time to price a specific size. A pricing engine that serves all of them needs the quote’s “firmness” encoded in the data model from the start.
Latency Budget, Data Structures and a Runnable Aggregator Sketch
Latency in an FX pricing engine has two faces. The first is internal: how long from an upstream tick arriving to a client quote leaving. The second is relative: how stale your quote is compared with the market at the instant a client acts on it. Only the second determines whether you get picked off, but the first is what you control.
A Tick-to-Quote Budget (Illustrative)
The table below is a planning budget, not a measurement. Every figure is an assumption to be replaced by your own profiling; none comes from a published benchmark.
| Stage | Illustrative budget | What dominates |
|---|---|---|
| NIC receive and hardware timestamp | 1-5 microseconds | kernel bypass or not |
| Feed decode and normalize | 5-20 microseconds | parsing, allocation |
| Book update and aggregation | 10-50 microseconds | ladder rebuild cost |
| Pricing core (spread, skew, tier) | 10-50 microseconds | number of client tiers |
| Distribution fan-out | 50-500 microseconds | client count, serialization |
| Network to client | site dependent | physical distance |
Two observations follow. First, fan-out usually dominates internal compute, because one pair update may produce a quote for each of hundreds of client-tier combinations. Second, a budget measured in microseconds inside the engine is irrelevant if the client’s network path adds milliseconds, which is why proximity hosting in the major FX data centres is a commercial decision as much as a technical one. Teams that colocate near the main liquidity hubs do so to shrink the window in which their quote can be stale, not to win a race against the clock.

Figure 4: Latency budget and stale-quote protection path, from tick arrival through conflation and quote expiry.
Ring Buffers and Conflation
The hot path should avoid locks and allocation. A common layout gives each feed handler thread a single-producer single-consumer ring buffer of fixed-size tick records, and a single pricing thread per pair group consumes from all of them. Because only one thread mutates a pair’s book, no locks are needed on the book itself. Prices are stored as scaled integers (for example, price in units of one tenth of a pip) to avoid floating-point drift in triangulation and to make equality checks exact.
Conflation is the practice of collapsing multiple updates into one when a consumer cannot keep up. For client distribution, conflation is essential: if EUR/USD ticks 200 times a second and a client session can absorb 20 quotes a second, sending every tick builds a queue, and the client sees old prices. The engine should publish the latest quote per pair per session at a bounded rate and drop the intermediate ones. Conflation must be applied at the distribution edge only. Upstream, the aggregator should see every tick, because a dropped update can leave a phantom stale level in the book.
The design rule: never queue prices, always overwrite. A queue of prices is a queue of lies, each one older than the last. Replacing the pending value with the newest one keeps latency bounded by the send rate, not by the backlog.
Stale-Quote Protection
Staleness protection has three layers. At ingest, each LP stream has a heartbeat monitor; if no tick or heartbeat arrives within a configured interval, the LP’s contribution is removed from the aggregate immediately and the quote is widened or pulled if the remaining liquidity is insufficient. At the pricing core, each published quote carries an expiry; if the underlying book has not refreshed within a short horizon, the quote is withdrawn. At last look, the price check compares the client’s request price with the current internal price, rejecting beyond tolerance (subject to the disclosure and Code constraints discussed earlier).
Sequence-gap handling belongs in the same family. If a venue’s incremental feed has a gap, the book for that venue is untrustworthy until a snapshot resyncs it, so the engine marks it suspect and excludes it. Excluding it temporarily is almost always safer than trusting a book that may contain a level that no longer exists.
A Runnable Python Aggregator Sketch
The sketch below demonstrates the core logic: per-LP books, staleness expiry, a depth-aware executable price, and an inventory skew. It is a teaching model in pure Python, not a low-latency implementation; a production system would use a systems language, ring buffers and integer prices as described above.
import time
from dataclasses import dataclass, field
PIP = 0.0001
@dataclass
class Quote:
lp: str
bid: float
ask: float
bid_size: float # millions
ask_size: float
ts: float # local receive time, seconds
@dataclass
class Aggregator:
max_age: float = 0.250 # seconds before an LP quote is stale
skew_k: float = 0.05 # pips per million of net position
skew_cap: float = 2.0 # max skew in pips
half_spread_pips: float = 1.0 # base markup each side
books: dict = field(default_factory=dict)
def on_quote(self, q: Quote):
self.books[q.lp] = q
def _live(self, now):
return [q for q in self.books.values() if now - q.ts <= self.max_age]
def executable(self, side, size, now=None):
"""Volume-weighted price to hedge `size` million on `side`."""
now = now or time.monotonic()
live = self._live(now)
if side == "buy": # client buys, we buy from LPs at their ask
levels = sorted(((q.ask, q.ask_size) for q in live))
else: # client sells, we sell to LPs at their bid
levels = sorted(((q.bid, q.bid_size) for q in live), reverse=True)
remaining, cost = size, 0.0
for price, avail in levels:
take = min(remaining, avail)
cost += take * price
remaining -= take
if remaining <= 1e-9:
return cost / size
return None # not enough depth: refuse to quote this size
def client_quote(self, size, net_position_m, now=None):
buy = self.executable("buy", size, now) # client buy -> our offer
sell = self.executable("sell", size, now) # client sell -> our bid
if buy is None or sell is None:
return None
skew = max(-self.skew_cap, min(self.skew_cap, -self.skew_k * net_position_m))
offer = buy + (self.half_spread_pips + skew) * PIP
bid = sell - (self.half_spread_pips - skew) * PIP
return round(bid, 5), round(offer, 5)
if __name__ == "__main__":
agg = Aggregator()
t = time.monotonic()
agg.on_quote(Quote("LP-A", 1.0850, 1.0852, 5, 5, t))
agg.on_quote(Quote("LP-B", 1.0849, 1.0853, 10, 10, t))
agg.on_quote(Quote("LP-C", 1.0851, 1.0854, 3, 3, t))
print("flat :", agg.client_quote(8, 0.0, t))
print("long20:", agg.client_quote(8, 20.0, t)) # prices shift down
Run it and the flat quote for 8 million shows the depth-weighted cost with a one-pip markup each side; the long-20-million case lowers both sides by one pip. Three simplifications are deliberate. Last look status, quality scoring and credit are omitted. Sizes are assumed fully fillable. And the stale check uses a fixed 250 ms, where a production system would set it per LP and pair from measured behaviour.
Why Integers and Why Single Writers
Floating-point prices introduce errors that matter when triangulating and when comparing prices at tolerance boundaries. Storing prices as integers in the smallest quoted fractional-pip unit removes those errors and speeds comparisons. Single-writer-per-pair also keeps the model auditable: for any published quote, you can reconstruct the exact book state by replaying the input sequence in order, which matters when a client disputes a fill. Deterministic replay is the cheapest audit tool the engine has, and it requires that the pricing core be a pure function of sequenced inputs plus configuration version.
Trade-offs, Gotchas, and What Goes Wrong
The pricing engine fails in characteristic ways, and most of them are economic rather than technical.
Latency arbitrage. A faster participant sees a price move on another venue before your quote updates and trades against the stale quote. You lose the difference, repeatedly, in small amounts. Mitigations are speed (shorter internal path), quote protection (widen when cross-venue prices diverge), and last look. Last look itself is contested precisely because it shifts the cost of staleness from the LP to the client, which is why the Code insists it be a risk control rather than a profit option.
Toxic flow. Some clients trade only when your price is wrong, and their flow is consistently followed by a market move in their favour (adverse markout). Engines need per-client markout analytics, and the pricing core needs the ability to assign a client to a wider tier, a slower stream, or a last-look quote. The risk is to over-segment: a tier scheme that tries to detect every pattern becomes brittle and starts penalising ordinary hedgers.
Feed gaps and venue outages. If an LP’s stream stops and the engine is slow to remove its levels, the aggregate looks better than reality, and the engine quotes tight prices it cannot hedge. The fix is aggressive staleness removal plus an explicit minimum-liquidity rule: if the number of live LPs falls below a threshold, widen or stop quoting.
Skew oscillation and information leakage. A badly tuned skew whipsaws, and clients reading your price as a function of your inventory can learn your position. Deadbands, rate limits and noise are common countermeasures; none is free.
Hedge feedback loops. If your hedging orders hit the same LPs whose prices you quote from, you move your own aggregate. A naive engine hedges, sees its own impact as a market move, re-prices and hedges again. Mark your own hedge fills so the aggregator discounts them.
Triangulation errors. Time misalignment between legs produces phantom arbitrage that clients can hit. Quote crosses only when both legs are fresh within a tight skew window.
Regulatory and disclosure drift. Practices around last look, pre-hedging and information use are shaped by the FX Global Code and by regulators’ expectations (verify jurisdiction-specific rules independently). Engineers should keep decision logs sufficient to reconstruct why a request was accepted or rejected, because a disclosure that does not match behaviour is the real exposure.
Configuration risk. Most major incidents in pricing systems come from bad parameters rather than bad code: a spread multiplier typed as 0.1 instead of 10, a skew cap removed, a stale threshold set in the wrong unit. Treat configuration as code, with review, staged rollout, and automatic kill switches on outlier prices (for example, a reasonableness collar against an independent reference).
Practical Recommendations
Start with the quote contract, not the algorithm. Decide what a published quote promises: firm or last look, valid for how long, for what size ladder, to whom. Everything downstream follows from that contract, and ambiguity here is where disputes begin.
Build aggregation around executability. Quote only sizes you can hedge, weight LPs by measured markout and reject rate, and keep firm and last-look liquidity labelled throughout the data model. Keep inventory skew as a bounded, rate-limited, observable control with a deadband, and record every skew decision with its inputs.
Make staleness a first-class concern at three layers: ingest heartbeats, quote expiry, and the last-look price check. Conflate at the edge, never upstream, and overwrite rather than queue. Use integer prices, single writers and deterministic replay, so that every fill can be explained.
Treat the FX Global Code and your own disclosures as design inputs. If the policy says last look is a price and validity check only, the code path should make anything else impossible.
A short checklist before go-live:
- Quote contract documented: firmness, validity, size ladder, client segments.
- LP feeds tagged firm or last look, with per-LP quality scores and markout tracking.
- Heartbeat, sequence-gap and minimum-liquidity rules tested with injected failures.
- Skew has deadband, cap, rate limit and an off switch.
- Cross rates built from time-aligned legs on integer prices.
- Last look checks limited to validity and price, with reason codes and decision logs.
- Configuration changes reviewed, staged and protected by a price collar.
- Replay tooling can reproduce any quote from logged inputs and config version.
Frequently Asked Questions
What is an FX pricing engine?
An fx pricing engine is the software that turns raw prices from liquidity providers and venues into the two-way streaming quotes a bank or market maker shows its clients. It normalizes feeds, aggregates liquidity, adds spread and markup, skews prices to manage inventory, enforces stale-quote protection, and publishes quotes with validity rules. It is typically paired with a risk and hedging component that feeds position information back into pricing, making the whole system a control loop rather than a one-way feed.
How does liquidity aggregation work in FX?
Liquidity aggregation merges the order books or quote streams from many LPs and ECNs into one consolidated view per currency pair. A good aggregator is depth-aware, building a cumulative ladder so the price reflects the size being quoted, and conditional, filtering by credit lines, firm versus last-look status and the client’s pricing profile. Because FX has no central tape, the consolidated book is each firm’s own construction and differs between participants.
What is price skewing and why do dealers use it?
Price skewing shifts a dealer’s bid and offer together in one direction based on inventory. A dealer long a currency lowers both sides, which discourages more purchases from clients and encourages clients to buy from the dealer, reducing the position without crossing a spread in the interdealer market. It is proportional feedback on risk. Poorly tuned skew can oscillate or reveal the dealer’s position, so production systems add deadbands, caps and rate limits.
What is last look in FX trading?
Last look is a final opportunity for a liquidity provider to accept or reject a client’s trade request at the quoted price. Under the FX Global Code’s Principle 17, as described in the GFXC’s 2021 report, it should be a risk control mechanism limited to a validity check (credit, operational suitability) and a price check against a tolerance, not a tool for gathering information. LPs are expected to disclose how it operates, including whether price checks are symmetric or asymmetric.
How do you calculate a cross rate from two USD pairs?
Multiply the legs that would hedge the trade. For EUR/JPY from EUR/USD and USD/JPY, the synthetic bid is the EUR/USD bid times the USD/JPY bid, and the synthetic offer is the EUR/USD ask times the USD/JPY ask. With EUR/USD at 1.0850 / 1.0852 and USD/JPY at 150.20 / 150.23, the cross is about 162.967 / 163.030, wider than either leg because half-spreads compound. The two legs must be time-aligned.
What latency does an FX pricing engine need?
There is no universal number. What matters is how stale a quote can become before a client trades on it, which depends on the engine’s internal path, the network to the client, and how quickly it reacts to moves elsewhere. Internal budgets in the tens to hundreds of microseconds are common design targets for competitive engines, but treat any such figure as an assumption to be measured against your own flow, venues and markout data.
Further Reading
- Low-latency market data feed handler design, the ingestion layer feeding the pricing path.
- Exchange matching engine architecture for low latency, how central-book venues match firm orders.
- Pre-trade risk engine architecture, limits and credit checks that gate execution.
- Video analytics edge architecture for low latency, a cross-domain look at latency budgets under load.
- BIS Triennial Survey: OTC FX turnover in April 2025, market size and structure.
- GFXC report on last look, primary guidance on Principle 17.
This article is systems and architecture analysis for educational purposes only and is not trading, investment or financial advice.
By Riju — about
