How to calculate a currency cross rate comes down to one operation: divide or multiply the two US dollar legs. To get EURGBP, divide EURUSD by GBPUSD. With EURUSD at 1.1425 and GBPUSD at 1.3708, the cross is 1.1425 / 1.3708 = 0.83345. One euro buys 0.83345 pounds. That single division is the core of the answer, and the rest of this post covers the parts that tutorials tend to skip: which pairs you divide and which you multiply, how bid and ask compound when you chain two quotes, and how to pull both legs programmatically so the result is current and internally consistent.
A cross rate is the exchange rate between two currencies where neither one is the US dollar. Most interbank FX liquidity is quoted against USD, so EURUSD and GBPUSD update constantly, while a direct EURGBP quote may be thinner on any given feed or absent from it entirely. To price euros in pounds you route through the dollar: euros to dollars, dollars to pounds. That routing is called triangulation, and it's how conversion services, portfolio trackers, and pricing engines derive most non-dollar rates. If the base and quote conventions are unfamiliar, read base currency vs quote currency first, because the direction of every formula below depends on which currency sits where.
The cross rate formula: when to divide and when to multiply#
Treat each pair as a fraction and cancel the dollar. EURUSD means EUR/USD, dollars per euro. GBPUSD means GBP/USD, dollars per pound. Dividing the first by the second cancels USD and leaves EUR/GBP:
EURGBP = EURUSD / GBPUSD = 1.1425 / 1.3708 = 0.83345
The rule: when both legs quote the dollar as the quote currency (EURUSD, GBPUSD, AUDUSD), divide one by the other. When one leg has the dollar as the base (USDJPY, USDTRY), multiply, because the fractions only cancel that way. A second worked example, EURJPY from EURUSD and USDJPY:
EURJPY = EURUSD x USDJPY = 1.1425 x 147.20 = 168.18
EUR/USD times USD/JPY leaves EUR/JPY, yen per euro. The same pattern gives EURTRY from EURUSD and USDTRY.
Inversion trips people up more than the formula does. EURGBP = 0.83345 is the price of one euro in pounds. The price of one pound in euros is the reciprocal: 1 / 0.83345 = 1.19983. Both numbers describe the same market, quoted from opposite sides. Before shipping a conversion function, sanity-check the magnitude: a euro is worth less than a pound, so EURGBP must sit below 1, and if your code produces 1.19983 where you expected EURGBP, you inverted a leg.
Bid and ask compound through the cross#
A mid price is fine for a display widget, but the moment you convert an amount or measure cost, you need the derived bid and ask, and you can't get them by triangulating a single mid. Follow the money through the dollar. To sell euros for pounds, you sell EUR at the EURUSD bid, then use those dollars to buy GBP at the GBPUSD ask. To go the other way, you cross at the EURUSD ask and the GBPUSD bid:
cross bid = EURUSD bid / GBPUSD ask
cross ask = EURUSD ask / GBPUSD bid
With EURUSD at 1.14245 / 1.14255 and GBPUSD at 1.37075 / 1.37085:
EURGBP bid = 1.14245 / 1.37085 = 0.83339
EURGBP ask = 1.14255 / 1.37075 = 0.83352
Each leg carried a one-pip spread, yet the derived cross shows 0.00013, about 1.3 pips. Chaining two quotes stacks the uncertainty of both, so a synthetic cross is always at least as wide as its widest leg and usually wider. For multiply-style crosses like EURJPY, the same logic gives bid times bid for the cross bid and ask times ask for the cross ask. If pip arithmetic on pairs like these is new, the pip size and pip value post covers it.
How to calculate the cross rate in code#
A derived rate is only as good as its two inputs. A quote from a single source can drift, gap, or freeze while the market moves, and any error in a leg passes straight through the division into every conversion downstream. A base rate aggregated across sources holds up better. SiftingIO's forex data API publishes one consensus quote per pair, formed as a weighted median across multiple independent venues, and its coverage includes the major crosses alongside the dollar pairs.
For a point-in-time conversion, pull both legs from the REST snapshot endpoint and compute. Note that the snapshot returns each price and size as a quoted string, so cast before you divide:
import requests
BASE = "https://api.sifting.io/v1"
HEADERS = {"X-API-Key": "sft_your_key"}
def quote(pair):
r = requests.get(f"{BASE}/last/quote/forex/{pair}", headers=HEADERS)
r.raise_for_status()
return r.json() # fields s, b, B, a, A come back as quoted strings; t is an int64 epoch-ms
eur = quote("EURUSD")
gbp = quote("GBPUSD")
if abs(eur["t"] - gbp["t"]) > 2000:
raise ValueError("legs are more than 2s apart, refetch")
cross_bid = float(eur["b"]) / float(gbp["a"])
cross_ask = float(eur["a"]) / float(gbp["b"])
cross_mid = (cross_bid + cross_ask) / 2
print(f"EURGBP {cross_bid:.5f} / {cross_ask:.5f} (mid {cross_mid:.5f})")
For a screen that updates continuously, streaming beats polling. Connect to wss://stream.sifting.io/ws/v1?key=$SIFTING_KEY, send {"op":"subscribe","product":"fx","symbols":["EURUSD","GBPUSD"]}, keep the latest tick per leg in memory, and recompute the cross whenever either leg updates. The server replays the last cached value on subscribe, so the cross is computable immediately, and it closes idle connections after 90 seconds, so send a ping frame at least once a minute. You can eyeball the live EURUSD leg against your own numbers on the EURUSD symbol page.
Common pitfalls#
Mismatched timestamps between the legs. In a fast market, a EURUSD tick from now divided by a GBPUSD quote from four seconds ago produces a cross that never existed. Both REST snapshots and stream ticks carry an epoch-milliseconds timestamp in t. Compare the two, pick a tolerance that fits your use case (a couple of seconds is reasonable for conversion display), and refetch when the skew exceeds it rather than averaging your way past it.
Treating snapshot fields as numbers. The /v1/last/quote response returns prices and sizes as quoted strings, for example "b": "1.14245", while the timestamp t is a plain integer. Cast each price with float() before you divide, or the arithmetic raises an error or silently concatenates. Historical bars from /v1/hist are already numeric, so the rule applies to snapshots, not bars.
Using last trade instead of the quote. Trades print irregularly, so the last trade can sit noticeably away from the current market on quieter pairs, and a single traded price tells you nothing about the spread you'd cross. For conversion math, hit /v1/last/quote rather than /v1/last/trade, and build the cross from bid and ask as shown above.
Rounding too early. FX convention quotes EURGBP to five decimals, but round only at display time. If you round each leg to four decimals before dividing, the cross can shift by several tenths of a pip, which matters when the entire spread is 1.3 pips. Keep full float precision through the division and format at the end.
Inverting the wrong way. GBPUSD / EURUSD is a valid division; it just yields GBPEUR, not EURGBP. Wire a range assertion into anything that feeds real conversions, for example that EURGBP stays within a broad historical band below 1, so a silent inversion fails loudly instead of mispricing every conversion by 44 percent.
The method generalizes to any pair you can route through a common currency: cancel the shared leg, carry bid and ask separately, and check the timestamps before you divide. The free tier includes live forex quotes, so you can test the snippet above with your own key. Start building free.



