Base currency and quote currency are the two halves of every forex price, and reading them in the wrong order is the most common error in FX data code. A pair is written base/quote. The base is the currency being priced, always one unit of it. The quote is the currency that one unit of the base costs. EUR/USD at 1.08 means one euro costs 1.08 US dollars. That is the whole definition. The rest of this post is about applying it: reading the direction of a move, bid and ask, the symbol string in a feed, cross rates, and the mistakes that follow from getting the order wrong.
What the number means in each direction#
When EUR/USD moves from 1.08 to 1.10, one euro now costs more dollars. The base strengthened against the quote. When it falls to 1.06, the base weakened. The number says nothing about the euro against any other currency, only against the dollar.
Now take USD/JPY at 150. The dollar is the base here, so one dollar costs 150 yen. A rising USD/JPY is the dollar strengthening against the yen. Notice what that does to a dashboard: "the dollar went up" is a rising number on USD/JPY and a falling number on EUR/USD. Any code that colours a rate green or red needs to know which side of the pair the currency it cares about sits on.
To read a rate the other way round, invert it. EUR per USD is 1 / 1.08, which is 0.9259. The general rule is rate(A/B) = 1 / rate(B/A). Keep full precision through the inversion and round only when you display the result. Inverting a rounded number and inverting it back does not return the original.
Bid and ask in base and quote terms#
A live quote has two sides. Take EUR/USD bid 1.08010, ask 1.08025. Both numbers are in the quote currency per one unit of the base. The bid is what the quoting side will pay, in dollars, for one euro. The ask is what it charges, in dollars, to hand over one euro. If you give up euros you receive the bid. If you give up dollars to receive euros you pay the ask. The ask is always the higher number.
The spread is ask minus bid, 0.00015 here, or 1.5 pips. In plain terms it is the round-trip cost of converting and converting back with no move in the market. It's also a data signal: a spread that suddenly widens on a major pair usually means the source is thin or stale at that moment, and a reference price should treat it with suspicion.
Inverting a two-sided quote is where most bugs live. The inverse of the bid is the new ask, and the inverse of the ask is the new bid. So USD/EUR is bid 1 / 1.08025 = 0.92571 and ask 1 / 1.08010 = 0.92584. Invert each side in place instead and the bid ends up above the ask, which is a negative spread and a sure sign of a flipped convention.
A mid price, (bid + ask) / 2, is what most reference and valuation work uses. Nobody deals at it, but it removes the spread from a time series and makes two sources comparable.
How the pair appears in a market data feed#
In an API the slash disappears. The pair is a six-character string, EURUSD, built from two ISO 4217 codes. The first three characters are the base, the last three are the quote. The price attached to that string is always quote units per one base unit, so EURUSD 1.08 carries exactly the same meaning as EUR/USD 1.08.
Fetching the live two-sided quote for one pair is a single call:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/quote/forex/EURUSD"
On the WebSocket the same pair arrives as a tick frame, where s is the symbol, p is the last price, and b and a are bid and ask, all in USD per EUR. The timestamp in t is Unix milliseconds.
{ "f":"tick", "s":"EURUSD", "p":1.08017, "b":1.08010, "a":1.08025, "t":1778019852426 }
The convention matters most at the point where you store or join data. Suppose one source gives you USDEUR and another gives you EURUSD. Both are valid pairs, both are numbers near 1, and a join on date will happily line them up and hand you rates that differ by roughly 17 percent with no error anywhere. The fix is to pick one canonical direction per currency pair at ingest, invert anything arriving the other way round, and record which direction the source used in its own column so the transform can be audited later.
Historical bars follow the same rule. An EURUSD daily bar from /v1/hist/forex/EURUSD/bars has open, high, low and close all in USD per EUR. If you invert a bar, the extremes swap: the new high is 1 / old low and the new low is 1 / old high. Inverting each field in place produces a bar whose high sits below its low.
Cross rates from two pairs#
When a pair isn't quoted directly, or you want to check a quoted one, compute it from two pairs that share a currency. Treat the codes like fraction units and cancel the common one.
EUR/USD times USD/JPY gives EUR/JPY, because USD is the quote in the first and the base in the second. EUR/USD divided by GBP/USD gives EUR/GBP, because USD is the quote in both. At the rates above, 1.08 times 150 is 162.00 yen per euro, and 1.08 divided by 1.27 is 0.8504 pounds per euro.
def invert(bid, ask):
# USD per EUR to EUR per USD; the two sides swap roles
return 1 / ask, 1 / bid
def cross_multiply(eurusd, usdjpy):
# EUR/USD x USD/JPY = EUR/JPY (USD cancels)
return eurusd * usdjpy
def cross_divide(eurusd, gbpusd):
# EUR/USD / GBP/USD = EUR/GBP (both quoted in USD)
return eurusd / gbpusd
print(invert(1.08010, 1.08025)) # (0.925711..., 0.925840...)
print(cross_multiply(1.08, 150.00)) # 162.0
print(cross_divide(1.08, 1.27)) # 0.85039...
Two things to keep straight. First, the two inputs have to carry the same timestamp, or the cross is a blend of two different moments. Second, a computed EURJPY will differ slightly from a directly quoted EURJPY, because the direct pair is priced on its own. Where a direct quote exists, use it as the reference and use the computed cross as the check. For two-sided crosses, multiply bid by bid and ask by ask. When dividing, the bid is the first pair's bid over the second pair's ask, and the ask is the first pair's ask over the second pair's bid.
Common pitfalls#
Flipped inversion of bid and ask. Covered above, and worth a guard: after any transform, assert that bid is less than or equal to ask before writing the row. A negative spread is never real data.
Mixed quoting conventions across sources. Some datasets quote every currency as US dollars per one foreign unit, others as foreign units per one dollar. USD/JPY at 150 in one file is 0.00667 in the other. Magnitude alone won't catch this for pairs near parity, so keep a small table of the canonical direction for each pair and test every incoming source against it.
Precision and display. Most pairs are quoted to five decimals, yen pairs to three. A formatter that assumes five decimals prints USDJPY as 150.12300 and makes a one-pip move look like a hundred. Store the raw value and pick the display precision from the quote currency. On the same theme, round once, at display time. Rounding 1 / 1.08 to four places gives 0.9259, and inverting that gives 1.08003, a drift of a third of a pip that compounds through any chain of conversions.
SiftingIO's forex data returns every pair in the direction the symbol string states, base first and quote second, on REST and WebSocket alike, so the canonical-direction step above is a decision you make once rather than per source. Read the docs



