You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Feature request: declare the unit of OHLCV volume in features (base / quote / contracts) #30673
The unit of the volume in fetchOHLCV candles differs between exchanges, and between market types on the same exchange. The Manual allows this: "usually in terms of the base currency, the exchanges docstring may list whether quote or base units are used". But this is not exposed anywhere a program can read it, and test.ohlcv.ts only checks the structure, so a caller cannot find out what candle[5] means.
The differences are big. Multiplying the volume by the close (the natural reading of the base-currency convention) is off by the contract size (10⁻⁴…10²×) or by the price (~10⁵× for BTC). Some results are obviously wrong, but others look plausible, and those are the dangerous ones.
Examples
Yesterday's 1d candle of each venue's BTC market (ccxt 4.5.84), shown as volume × close, compared with the venue's own 24h ticker quoteVolume:
The rows are not all bugs. Contracts are CCXT's unit for amounts on derivatives everywhere else (trades, orders, positions), so gate or mexc returning contracts is arguably consistent. hibachi has no base volume in its kline response at all. xt shows that the parser already knows the unit per market type (it switches between the a and v fields on market['inverse']), but that knowledge is not exposed to callers.
The hashkey row is the important one. $515M/day for a BTC perpetual passes any sanity check, but it is 1000× the real volume. Without knowing the unit, a consumer cannot tell.
Proposal
Declare the unit next to the existing OHLCV capabilities in features, per market type:
values: 'base' | 'quote' | 'contracts' (contracts × contractSize is base for linear and quote for inverse, as elsewhere in CCXT). It stays undefined where nobody has checked.
It is additive: no behaviour change for anyone who already compensates.
It gives the live tests something to assert. With the declared unit, the candle notional and the ticker notional must agree within an order of magnitude. That catches unit errors (≥10⁴×) without being flaky. A few venues need a skip because their ticker is itself off, e.g. deepcoin's "24h" ticker covers only the current UTC day.
Normalising every venue to base volume would also solve it, but it breaks everyone who compensates today. Metadata first would be a safe step towards that.
Current downstream workaround
We keep our own map (exchange × {spot, linear, inverse} → unit, default base) and convert every candle:
The map comes from a probe that we re-run after every CCXT upgrade. For each venue it reads yesterday's BTC candle for spot, linear and inverse markets and computes the USD notional under each hypothesis (base, quote, contracts). It keeps the one that falls within $10k–$50B/day. When two hypotheses are plausible, the venue's 24h ticker has to match within 3×, and anything else is left undecided and checked by hand against the raw payload.
It works, but every consumer has to rediscover it, and it silently breaks whenever a parser or an exchange changes a field. I'm happy to contribute the initial volume values for the venues above.
Thanks for the detailed write-up and the per-venue measurements — noted as a feature request.
Declaring the unit as an optional volume key under features.<type>.fetchOHLCV looks like a low-risk, additive first step: it reuses the existing per-market-type features tree (spot / swap.linear / swap.inverse / future.*), leaves the parsers untouched, and undefined keeps meaning "unverified". The features shape is validated in ts/src/test/Exchange/test.features.ts, so a PR would also add 'volume' to the fetchOHLCV entry there (and ideally the candle-vs-ticker order-of-magnitude check you describe in test.ohlcv.ts, gated on the declared unit).
The hashkey row (contracts × 0.001 on BTC/USDT:USDT) is worth a separate look regardless, since that is the case where the number looks plausible.
Since this touches the unified features schema, I'll leave the go/no-go on the key name and value set (base / quote / contracts) to the maintainers before anyone fills in per-exchange values.
fetchOHLCV blocks in features listed limits and related flags and had no volume field. The features builder left that key absent, so featureValue could not report a unit and callers following the manual treated the volume as base currency.
fetchOHLCV.volume is now 'base', 'quote', 'contracts', or undefined, and BaseExchange sets undefined when an exchange does not declare it. The declarations are binance and bybit linear 'base'; gate, mexc, hashkey, and deepcoin linear 'contracts'; hibachi linear 'quote'; phemex inverse 'contracts'; and xt linear 'contracts' with xt inverse 'base'.
Activity
carlotestor commentedon Sep 28, 2026
Thanks for the detailed write-up and the per-venue measurements — noted as a feature request.
Declaring the unit as an optional
volumekey underfeatures.<type>.fetchOHLCVlooks like a low-risk, additive first step: it reuses the existing per-market-typefeaturestree (spot / swap.linear / swap.inverse / future.*), leaves the parsers untouched, andundefinedkeeps meaning "unverified". Thefeaturesshape is validated ints/src/test/Exchange/test.features.ts, so a PR would also add'volume'to thefetchOHLCVentry there (and ideally the candle-vs-ticker order-of-magnitude check you describe intest.ohlcv.ts, gated on the declared unit).The
hashkeyrow (contracts × 0.001 on BTC/USDT:USDT) is worth a separate look regardless, since that is the case where the number looks plausible.Since this touches the unified
featuresschema, I'll leave the go/no-go on the key name and value set (base/quote/contracts) to the maintainers before anyone fills in per-exchange values.mvanhorn commentedon Oct 6, 2026
fetchOHLCV blocks in features listed limits and related flags and had no volume field. The features builder left that key absent, so featureValue could not report a unit and callers following the manual treated the volume as base currency.
fetchOHLCV.volume is now 'base', 'quote', 'contracts', or undefined, and BaseExchange sets undefined when an exchange does not declare it. The declarations are binance and bybit linear 'base'; gate, mexc, hashkey, and deepcoin linear 'contracts'; hibachi linear 'quote'; phemex inverse 'contracts'; and xt linear 'contracts' with xt inverse 'base'.