# What a crypto backtest silently assumes

Every engine ships a fill rule and a cost model with defaults. What those defaults assume about fees, funding and liquidation, and which engines model which.

*https://cryptomarkets.tools/guides/what-a-crypto-backtest-assumes · background to Backtesting & Research Libraries*

**Answer:** A backtest engine is a fill rule and a cost model, and both ship with defaults. Most crypto engines default to zero fees, zero slippage, a fill at the price you asked for, no funding payment and no liquidation check. On a perpetual that omits the two cash flows which define the instrument, so the curve you are reading is a spot strategy wearing a perpetual's ticker. None of it is hidden; it is documented one level below the README.

You pick a backtesting library from a feature list, point it at two years of candles, and get a
curve. The curve is not a lie and the library is not broken. It is the honest output of a set of
assumptions you did not choose, most of which are the defaults, and several of which are the
difference between a strategy and an artefact.

Every fact below comes from a vendor's own documentation or its source code, and the better-behaved
engines in this catalogue say the awkward part out loud — Freqtrade's documentation describes its
own fill model as optimistic before you have finished reading the page. The problem is not
concealment. It is that the defaults sit in a settings module and a docstring, and the curve
renders whether or not you went and read them.

## How it works

Strip a backtester of its API and three things are left: a clock, a fill rule and a cost model.
Everything a vendor sells you sits on top of those three, and every one of them has a default.

### A bar is not a path

Most crypto backtests run on OHLCV candles, and a candle records four prices and no order. It does
not say whether the high came before the low. For a strategy with both a stop and a target inside
one bar, that missing ordering is the whole result — the same bar is a win or a loss depending on
an assumption the data cannot settle.

Engines resolve it differently and mostly silently.
[NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader) is unusually explicit: on a bar backtest it splits each
bar into four synthetic L1 updates in open, high, low, close order, and its documentation calls
that a plausible path rather than the real one. [Freqtrade](https://cryptomarkets.tools/tools/freqtrade) fills entries at the
candle open and exit signals at the next candle's open, and offers `--timeframe-detail` to replay a
faster timeframe inside each bar — at the cost of the memory, the runtime and the extra download.
[3Commas](https://cryptomarkets.tools/tools/3commas), a hosted bot rather than a library, waits for the one-minute candle to
close and then fills at the close of the *next* one, which is a conservative rule and a coarse one.

Granularity is also a commercial variable once you leave the open-source half of the category.
[Bitsgap](https://cryptomarkets.tools/tools/bitsgap) scales backtest depth by plan: 30 days on the entry tier, 180 on the
middle one, 365 on the top. [Cryptohopper](https://cryptomarkets.tools/tools/cryptohopper) caps a run at about a month where
its documentation describes the feature at all, runs one backtest at a time, and asks you to type
the commission in by hand.

### The fill rule

The default in almost every candle engine is: if price touched your limit, you were filled, in
full, at your price. That is a statement about queue position, and a candle contains no
information about queue position whatever.

[OctoBot](https://cryptomarkets.tools/tools/octobot) fills a limit order completely on a price-hit event against the simulated
feed, with no queue and no volume check. NautilusTrader's default fill model treats a touched limit
as filled and applies no probabilistic slippage — ten other fill models ship with the engine, from
one-tick slippage to tiered synthetic books, and picking one is something you have to do on
purpose. [Hummingbot](https://cryptomarkets.tools/tools/hummingbot) executes at the candle close.

[hftbacktest](https://cryptomarkets.tools/tools/hftbacktest) is the one engine here built on the opposite assumption. It
replays every book update and every trade, tracks where your order sits in the queue at its price
level, fills it only when the size in front of it is gone, and models feed and order latency
separately. Its queue models are graded by pessimism, with the most conservative advancing your
order only when a trade consumes the level. For a market-making strategy the two methods do not
disagree by a few percent — they disagree about whether there is a strategy.

Even that engine names the thing it cannot do: a replay cannot simulate your own effect on the
market. Your orders were not in the book being replayed, so nobody reacted to them. For a size that
would have moved the level, the backtest is optimistic in a way no fill model repairs.

### The cost model

Fees and slippage are usually parameters, and parameters have defaults.

- [VectorBT](https://cryptomarkets.tools/tools/vectorbt) ships `fees=0.0`, `fixed_fees=0.0` and `slippage=0.0` in its settings
  module. Every README example that does not override them is a frictionless run.
- [backtesting.py](https://cryptomarkets.tools/tools/backtesting-py) defaults to `spread=0`, `commission=0` and `margin=1`.
- [Lumibot](https://cryptomarkets.tools/tools/lumibot)'s backtesting broker checks for attached fee objects and returns
  immediately when there are none, with a comment in the code noting that fees are frequently empty
  in backtests.
- OctoBot's simulated maker and taker fees come from the exchange profile and default to zero, and
  the repository contains no slippage model at all.

Two engines do better and are worth naming for it. [Jesse](https://cryptomarkets.tools/tools/jesse) pre-fills a real
per-exchange taker rate rather than zero — 0.1% for Binance spot, 0.04% for Binance perpetual
futures — though it applies one rate to both sides and has no slippage model. Hummingbot charges a
flat round-trip cost, 0.0002 by default, identical for maker and taker, which is a real number and
the wrong shape for any strategy whose margin *is* the maker-taker spread. Freqtrade reads the
venue's own maker and taker fee from market info instead of assuming zero.

## What a perpetual adds

A perpetual swap has two cash flows a spot position does not: a periodic funding payment, and a
forced close at a maintenance-margin threshold. An engine that models neither is not backtesting a
perpetual. It is backtesting a spot series that happens to carry a perpetual's ticker.

### Funding

The mechanics are public and specified per venue, and the specifications do not agree with each
other. That disagreement is what most engines paper over.

**OKX** settles funding every eight hours at 00:00, 08:00 and 16:00 UTC by default, "unless
specified otherwise (i.e. every 1, 2 or 4 hours)", charging
[position value multiplied by the funding rate](https://www.okx.com/en-us/help/iv-introduction-to-perpetual-swap-funding-fee).
You pay or receive only if the position is open at the assessment instant; close before it and you
are exempt. The rate itself is a clamped function of an average premium index and a fixed 0.01%
interest rate, divided by eight over the interval length.

**Binance** uses the same eight-hour default and the same shape of formula, with an interest rate
of 0.03% daily — 0.01% per interval — and zero on certain pairs. The
[funding-rate documentation](https://www.binance.com/en/support/faq/introduction-to-binance-futures-funding-rates-360033525031)
states that Binance charges nothing on funding and that payments are transferred directly between
traders holding opposing positions. It also records something an engine with a hard-coded schedule
cannot express: since May 2025, settlement switches to hourly automatically when the rate reaches
its cap or floor. The cap for major contracts is ±0.75 times the maintenance margin ratio, and ±2%
for other USDⓈ-M contracts.

**Hyperliquid** pays [every hour](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/funding),
dividing the eight-hour rate into hourly payments, converts at the spot oracle price rather than
the mark price, and caps funding at 4% per hour. Its fixed interest component is 0.01% per eight
hours, which its documentation states as 11.6% APR paid to shorts.

**dYdX** runs a funding tick every hour by default, computing the rate as the premium component
divided by eight plus an interest component that is
[0% on cross markets](https://docs.dydx.xyz/concepts/trading/funding) and 0.125 basis points per
hour on isolated ones.

Four venues, three settlement frequencies, two conversion prices and a rate that can flip sign
between any two intervals. Now look at what the engines do with that:

- [NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader) settles funding properly — at each boundary the
  simulated venue debits longs and credits shorts and moves the account balance — but only from
  `FundingRateUpdate` records you supplied. No adapter fetches the history for you, and a rate with
  no settlement boundary attached is explicitly inert.
- [Freqtrade](https://cryptomarkets.tools/tools/freqtrade) downloads mark and funding-rate candles alongside OHLCV in futures
  mode, so funding is part of the result rather than something you add afterwards. The venue-level
  holes are worth reading first: OKX serves mark candles for roughly the last three months only, so
  funding before that window cannot be computed correctly, and Hyperliquid returns no historical
  data at all, so a venue you can trade live cannot be backtested.
- [LEAN](https://cryptomarkets.tools/tools/lean) computes funding as rate times position notional at 00:00, 08:00 and 16:00
  UTC. It ships three crypto-venue classes; two of them are empty subclasses of the Binance one and
  change nothing. That is right for Bybit and wrong for dYdX, whose chain runs a funding tick every
  hour. Worse, the model needs a funding-rate feed loaded, and with none it charges nothing and
  says nothing.
- [OctoBot](https://cryptomarkets.tools/tools/octobot) charges funding, which puts it ahead of most of the category, at a
  single constant rate for the entire run — default 0.00005 per interval, always positive. Real
  funding changes sign and spikes; its data collector only downloads candles, so there is no
  historical series to charge against.
- [Jesse](https://cryptomarkets.tools/tools/jesse) returns zero for the funding rate unless the bot is running live, so a
  perpetuals backtest contains no funding in either direction.
- [VectorBT](https://cryptomarkets.tools/tools/vectorbt), its commercial edition, [backtesting.py](https://cryptomarkets.tools/tools/backtesting-py),
  [Lumibot](https://cryptomarkets.tools/tools/lumibot), [Hummingbot](https://cryptomarkets.tools/tools/hummingbot) and
  [hftbacktest](https://cryptomarkets.tools/tools/hftbacktest) have no funding model at all. In several of them the word does
  not appear in the package.
- [3Commas](https://cryptomarkets.tools/tools/3commas) does not mention funding anywhere in its backtesting documentation,
  which matters because two of its three paid plans sell futures bots.

### Liquidation

The second missing cash flow is the one that ends the strategy rather than taxing it. Binance
computes maintenance margin as
[position notional times a tiered maintenance margin rate, minus a tiered maintenance amount](https://www.binance.com/en/support/faq/how-to-calculate-liquidation-price-of-usd%E2%93%A2-m-futures-contracts-b3c689c1f50a44cabb3a84e663b81d93),
and triggers on the mark price rather than the last trade. Both halves matter to a simulation: the
rate is a step function of position size, so the same strategy at four times the size is liquidated
at a different price, and a mark price is not in your candle file.

The engines split three ways. NautilusTrader models liquidation and ships it switched off —
`liquidation_enabled` is false on a backtest venue until you set it — and margining reads whatever
mark prices are in the cache, so a run with none marks to something else. Jesse checks for
liquidations only in isolated margin mode while shipping cross as the default, in which the
liquidation price is `NaN` and the check never fires. Freqtrade computes liquidation prices and
keeps a buffer above them, but does not track liquidation *fees* at all — its documentation says
plainly that this makes the profit and loss inaccurate for any run that actually gets liquidated,
and warns that cross-margin's cross-position effects may not be fully simulated.

backtesting.py is the clearest of all, in a docstring: `margin` is the required margin ratio of a
leveraged account, and "no difference is made between initial and maintenance margins". One number
sets leverage and nothing checks it again.

## What it costs

Put the defaults into arithmetic. The numbers below are ordinary published rates, not worst cases.

**Fees.** Hyperliquid's base tier, which is where an account with no volume history sits, is
[0.045% taker and 0.015% maker on perpetuals](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees).
A strategy that enters and exits once a day, taking liquidity on both sides, pays 0.09% a day —
about 33 percentage points of annual cost. Run that strategy in an engine defaulted to zero fees
and you have added 33 points of annual return to it before the first line of your logic executes.

**Funding.** Hyperliquid's interest component alone is 11.6% a year paid to shorts, before any
premium. A backtest of a carry-negative long that charges no funding is overstating the result by
at least that, and by considerably more whenever the premium is positive. The tail is worse than
the average: Hyperliquid caps funding at 4% per hour, and Binance switches to hourly settlement
precisely when the rate is at its cap, which is exactly the regime in which a constant-rate model
such as OctoBot's diverges furthest from the venue.

**Liquidation.** This one does not have an average. It is the difference between a drawdown and a
zero, and an engine with the check switched off will happily carry a position through a price at
which the venue would have closed it, then recover, then report the recovery as profit.

**Slippage and queue position.** No published rate exists, which is the point: it is a property of
your size against the book at that instant. An engine with no slippage model is not estimating it
at zero, it is not estimating it. For anything passive, that gap is the strategy.

## What you can do about it

**Read three files before the README.** The default fee, the default slippage and the fill rule.
In an open-source engine they are usually a settings module and one class, and in this catalogue's
cards they are quoted with the file they came from. If the answer is `0.0`, the first number you
change is that one — your venue's real maker and taker rates, from its fee schedule, before you
read any curve.

**Decide whether you are backtesting spot or a perpetual, and say it out loud.** If the instrument
is a perpetual and the engine charges no funding, you have two honest options: switch to an engine
that settles funding from real data, or restrict the conclusion to strategies whose holding period
does not cross settlement boundaries. What is not honest is the third option, which is reading the
curve anyway.

**Bring the funding history yourself, because most engines will not.** NautilusTrader settles it
and fetches nothing; LEAN's model is silent when the feed is absent; OctoBot invents one constant.
Funding rate history is a public endpoint on every venue named on this page, and it is a small
download. The engines that do fetch it — Freqtrade in futures mode — are the exception and a good
reason to pick one.

**Check the venue's funding schedule against the engine's, by name.** Eight-hourly is a default,
not a rule: dYdX and Hyperliquid pay hourly, OKX documents one-, two- and four-hour intervals for
some contracts, and Binance switches to hourly at the cap. An engine with one hard-coded schedule
is right for some of your venues and wrong for the others, and it will not tell you which.

**Turn the liquidation check on, and give it a mark price.** It is off by default in
NautilusTrader, inert under Jesse's default margin mode, and fee-free in Freqtrade. If your
strategy uses leverage, a backtest that cannot be liquidated is measuring a different strategy.

**Match the engine to the question, not to the feature list.** If the result turns on whether a
passive quote was filled, a candle engine cannot answer it at any setting and a queue-position
simulator can, at the price of tick data and a much longer week. If the result turns on a signal
held for days, tick simulation is expensive precision on the wrong axis, and the first thing to fix
is the funding leg.

**Then re-run the winner with pessimistic settings and see what survives.** Real taker fees on both
sides, a slippage model if the engine has one, funding from the venue's history, liquidation armed.
A strategy whose edge disappears under its venue's published rates did not have an edge; it had a
cost model. That run takes an afternoon, and it is the cheapest thing on this page.

The category page for [backtesting and research libraries](https://cryptomarkets.tools/categories/backtesting-libraries) lists
what each engine models, card by card, with the file the default came from.

## Tools this bears on

- [NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader.md) — Event-driven Rust engine that settles perpetual funding at the venue boundary.
- [Freqtrade](https://cryptomarkets.tools/tools/freqtrade.md) — Write the strategy in Python, backtest it, then run the same class live.
- [LEAN](https://cryptomarkets.tools/tools/lean.md) — The engine behind QuantConnect, Apache-2.0 and runnable on your own machine.
- [Jesse](https://cryptomarkets.tools/tools/jesse.md) — Python backtesting and research framework whose live-trading half is a paid plugin.
- [OctoBot](https://cryptomarkets.tools/tools/octobot.md) — GPL-3.0 Python bot with a web interface for grid, DCA, basket and TradingView strategies.
- [HftBacktest](https://cryptomarkets.tools/tools/hftbacktest.md) — Tick-by-tick backtesting that models order queue position and feed and order latency.
- [Backtesting.py](https://cryptomarkets.tools/tools/backtesting-py.md) — Two thousand lines, one OHLC series, and no idea what a funding payment is.
- [VectorBT](https://cryptomarkets.tools/tools/vectorbt.md) — Vectorised Python backtesting that has never heard of funding or liquidation.

## FAQ

### Why does my crypto backtest look so much better than live trading?

In roughly this order — fees, funding, slippage and the fill rule. Several engines in this catalogue default to zero fees and zero slippage and fill a limit order the instant price touches it, and most of them charge no funding at all on a perpetual. Those four defaults together can manufacture an edge in a strategy that has none.

### Which backtesting engines actually model perpetual funding?

A minority. NautilusTrader settles funding, but only from funding data you supplied yourself. Freqtrade downloads mark and funding-rate candles automatically in futures mode. LEAN models one eight-hour schedule and applies it to every crypto venue it supports. OctoBot charges a single constant rate for the whole run. The rest of the category charges nothing.

### Is a backtest with no funding model useless for perpetuals?

Not useless, but it is answering a different question. Without funding you are measuring the price leg of the trade only, which is fine for a strategy that holds for minutes and misleading for one that holds through settlement boundaries. The longer the average hold, the more of the result is the leg you did not simulate.

### Can any backtest tell me whether my limit order would have been filled?

Only one kind, and it needs tick data. An engine that fills when the candle's high touches your price is asserting something the candle cannot know. A queue-position simulator replays every book update and fills you when the size ahead of you is gone — which is a different answer, and usually a worse one.

## Sources

1. [Perpetual funding fee mechanism](https://www.okx.com/en-us/help/iv-introduction-to-perpetual-swap-funding-fee) — OKX, 2026-08-27
2. [Introduction to Binance Futures Funding Rates](https://www.binance.com/en/support/faq/introduction-to-binance-futures-funding-rates-360033525031) — Binance, 2026-03-06
3. [How to Calculate Liquidation Price of USDⓈ-M Futures Contracts](https://www.binance.com/en/support/faq/how-to-calculate-liquidation-price-of-usd%E2%93%A2-m-futures-contracts-b3c689c1f50a44cabb3a84e663b81d93) — Binance, 2025-12-31
4. [Funding](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/funding) — Hyperliquid, read 2026-09-21
5. [Fees](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees) — Hyperliquid, read 2026-09-21
6. [Funding](https://docs.dydx.xyz/concepts/trading/funding) — dYdX, read 2026-09-21

*Last updated 2026-09-21. A reference page, corrected in place — not a dated post.*
