# How to make a crypto backtest charge fees and funding

Four engines, four ways to charge maker and taker fees and perpetual funding — and the settings each one falls back to when you give it nothing.

*https://cryptomarkets.tools/how-to/backtest-with-fees-and-funding · next to Backtesting & Research Libraries*

**Answer:** Give the engine your own tier's maker and taker rates rather than its default, and charge funding from the venue's settled history at mark-price notional. Freqtrade does both from exchange data in futures mode; NautilusTrader and LEAN settle funding only from records you load; hftbacktest charges maker and taker per fill and has no funding at all. Then check which side of a settlement instant your bar-open fills land on.

## Approaches

*In the author’s order. Paid placement does not affect it.*

1. [Freqtrade](https://cryptomarkets.tools/tools/freqtrade.md) — Futures mode downloads mark and funding-rate candles and charges funding at mark price. The fee defaults to the worse of maker and taker, at the lowest tier.
2. [NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader.md) — Maker and taker rates per venue through a fee model, and funding settled at each boundary from FundingRateUpdate records you build from venue history.
3. [LEAN](https://cryptomarkets.tools/tools/lean.md) — Maker or taker decided by whether the limit order was marketable, first-tier rates unless you pass yours, and funding read from a CSV in the data folder.
4. [HftBacktest](https://cryptomarkets.tools/tools/hftbacktest.md) — Maker and taker decided by the queue simulation, rebates as negative fees — and no funding model, so the funding leg is yours to add.

## The short way

Use [Freqtrade](https://cryptomarkets.tools/tools/freqtrade) in futures mode, and type in your own fee. It is the one engine in
this category that downloads the funding history itself and charges it without being asked.

```text
freqtrade download-data --trading-mode futures --timeframes 5m --pairs BTC/USDT:USDT
freqtrade backtesting --fee 0.0005
```

The first command pulls the price candles and, because the mode is futures, the `mark` and
`funding_rate` series beside them. The backtest takes its mode from the configuration rather than a
flag, so the config file needs `"trading_mode": "futures"` as well. It then charges each open trade the funding rate
times the mark price times its size at every settlement it holds through, so the payment follows
the mark rather than your entry price — which is how the venue computes it too: Binance defines
the funding amount as the nominal value of the position, and the nominal value as mark price times
contract size.

The second flag is the one people leave out. Without it Freqtrade reads the fee from the
exchange's market info, takes the maker and the taker rate, and uses whichever is higher, which it
logs as the "worst case fee from exchange (lowest tier)". That is a sensible default for a strategy
that takes liquidity and a wrong one for anything else, and neither number is your account's if
you have a volume tier or a token discount. `--fee` is a single ratio, applied once on entry and
once on exit.

That is the whole short way for a strategy that crosses the spread. If your result depends on
earning the maker rate, or on a venue Freqtrade cannot fetch funding from, read on.

## What the options are

**Freqtrade, when crossing the spread is the strategy.** Covered above. What it cannot express is
two rates: one fee covers entries and exits, maker and taker alike, so a strategy whose margin is
the gap between the two is being charged the wrong shape of cost whichever number you pick.

**NautilusTrader, when you want both rates and are willing to bring the data.**
[NautilusTrader](https://cryptomarkets.tools/tools/nautilus-trader) takes a maker and a taker rate per simulated venue and
charges each fill by the side it actually took. Funding it settles properly, but only from
`FundingRateUpdate` records you construct, and its documentation is exact about when one becomes a
payment: at `next_funding_ns` if the record carries it, otherwise only when `ts_event` lands on the
`interval` boundary, and otherwise never — "updates without a boundary remain strategy data". So
build them from the venue's settled history with the boundary written in:

```python
from decimal import Decimal
from nautilus_trader.model import FundingRateUpdate, InstrumentId

PERP = InstrumentId.from_str("BTCUSDT-PERP.BINANCE")

def to_update(row):  # one row of Binance GET /fapi/v1/fundingRate
    settle_ns = row["fundingTime"] * 1_000_000  # milliseconds to nanoseconds
    return FundingRateUpdate(
        instrument_id=PERP,
        rate=Decimal(row["fundingRate"]),
        ts_event=settle_ns - 1,
        ts_init=settle_ns - 1,
        next_funding_ns=settle_ns,
    )
```

Stamping the record a nanosecond before the settlement and naming the settlement explicitly means
the payment happens at the venue's own timestamp, whatever the symbol's interval was that day. A
positive rate debits longs and credits shorts, and the payment moves realised profit and the
account balance together.

**LEAN, when the maker-or-taker decision should follow the order.** [LEAN](https://cryptomarkets.tools/tools/lean)'s Binance
fee models charge the maker rate only to a limit order that was post-only or not marketable when
it arrived, and the taker rate to everything else — the venue's own rule, applied per order. The
rates are constructor arguments with first-tier defaults, and a comment in the code says why: it
does "not model 30-day volume, so we use the first tier". Pass yours. Funding is read from a
two-column CSV per symbol under `cryptofuture/<market>/margin_interest/` in the data folder, one
line per settlement, `20221213 00:00:00,0.00010000`. Write it from the venue's history the same
way you would for Nautilus.

**hftbacktest, when the maker rate is the edge.** [hftbacktest](https://cryptomarkets.tools/tools/hftbacktest) charges maker
or taker by how each fill happened — resting in the simulated queue, or crossing on arrival — rather
than by the order type you asked for, and its fee builder states
that "a negative fee represents rebates". If you never call one, both rates are zero:

```python
asset = BacktestAsset().trading_value_fee_model(-0.00005, 0.0004)  # maker rebate, taker fee
```

It has no funding model. For a quoting strategy that holds inventory for seconds that is often
fine; for one that holds across a settlement, the funding leg has to be computed separately from
the recorded position series and subtracted.

## Where this breaks

**A fill at the settlement instant is charged by one engine and not by another.** Candle engines
fill at bar boundaries, and on any timeframe that divides eight hours some bar opens exactly at
08:00:00. Freqtrade sums every funding row from the trade's open time onward, inclusive, so a trade
opened on that bar pays the 08:00 settlement. LEAN schedules the first payment for the next
boundary after the position opens, so the same trade opened at 08:00:00 skips it and pays at
16:00. The venue is no help in settling the argument: Binance says there is a 15-second deviation
in the actual funding time, and that a position opened at 08:00:05 may still be charged. On a
strategy that enters around settlements the difference is one payment per trade, systematically,
in one direction.

**A missing row is a free settlement.** Freqtrade joins the funding series to the mark series on
timestamp and keeps only the rows present in both, so a settlement with no mark candle at that
instant drops out of the sum and is charged nothing. When the funding history does not reach back
far enough, the documented workaround is the `futures_funding_rate` setting, which fills the gap
with one constant. It fills only the stretch before the first real row; a hole in the middle of the
series is still charged nothing. The docs recommend zero and say plainly that "this will mean your backtests are
inaccurate" for those periods. Either way nothing in the output tells you which trades paid real
funding and which paid a fill value. Count your funding rows against the number of settlements in
the range before you run anything.

**The notional is not always the mark.** Binance charges the rate on mark price times size.
Freqtrade does the same from its mark series. LEAN's funding model multiplies the rate by the
holding's value at the security's current price, which in a backtest built on trade bars is the
last trade, not a mark. Usually the two are close; in a dislocation, which is when funding is
largest, they are not. See [mark price](https://cryptomarkets.tools/glossary/mark-price) for why the venue keeps them apart.

**A normalised funding series is the wrong input.** Some aggregators rescale rates to a common
per-eight-hour figure so venues can be compared side by side, which is the job described in
[how to get funding rates and open interest](https://cryptomarkets.tools/how-to/get-funding-and-open-interest). An engine
wants the opposite: the rate each settlement actually paid, at the time it actually paid it. Feed a
per-eight-hour figure into an hourly settlement schedule and the engine charges eight times the
funding, every hour.

**Maker is an outcome, not a setting.** A limit order priced through the book fills immediately
and pays the [taker fee](https://cryptomarkets.tools/glossary/taker-fee); LEAN encodes exactly that rule, and it is the one
the venue applies. Typing the maker rate into an engine that takes one fee for everything, as
Freqtrade does, gives it to every fill, including the ones that crossed. And the rate itself is
your tier's, not the schedule's first line: Freqtrade's docs note that volume and balance
discounts are "not visible to ccxt", and LEAN's code says it does not model 30-day volume at all.

This page makes the cost model honest. It does not touch the fill rule, the mark price that
liquidation reads, or the settlement schedule an engine assumes when it has no data — those are
the subject of [what a crypto backtest silently assumes](https://cryptomarkets.tools/guides/what-a-crypto-backtest-assumes),
and none of them is fixed by the steps above.

## If you outgrow this

If the problem is **writing funding files by hand for LEAN**,
[QuantConnect Cloud](https://cryptomarkets.tools/tools/quantconnect-cloud) is the same engine with the margin-rate datasets
already loaded — the piece the open-source engine leaves to you, and the one whose absence fails
silently.

If the problem is **funding history that stops too early**, the settled series is a public endpoint
on [Binance](https://cryptomarkets.tools/tools/binance-api), [Bybit](https://cryptomarkets.tools/tools/bybit-api) and [OKX](https://cryptomarkets.tools/tools/okx-api), and
Binance also publishes it in its flat-file archive, under a non-commercial licence that the card
spells out. Beyond that it is an archive purchase:
[Tardis.dev](https://cryptomarkets.tools/tools/tardis-dev) sells funding beside the tick data, which matters if you also want
the book for a fill model.

If the problem is **that the maker rate is the whole result**, a candle engine cannot answer it at
any fee setting, because it cannot tell you whether the resting order would have been reached.
That is hftbacktest's question, at the price of tick data and a funding leg you write yourself.

And if the problem is **the rest of the cost model** — the fill rule, liquidation, a mark price the
engine never received — that is the guide linked above, not a fee setting.

## FAQ

### Which fee should I put in a crypto backtest, maker or taker?

Whichever each fill would actually have paid. A limit order that rests pays maker and one that crosses the book on arrival pays taker, which is the rule LEAN applies per order. If your engine takes a single rate, as Freqtrade does, use the taker rate unless the strategy only ever rests — Freqtrade's own default picks the higher of the two for that reason.

### Does Freqtrade include funding fees in backtests?

In futures mode, yes. Downloading data with the futures trading mode also fetches mark and funding-rate series, and the backtest charges the rate times the mark price times the position size at each settlement a trade holds through. Where the funding history starts later than the backtest, the futures_funding_rate setting fills that leading stretch with a constant, and the docs recommend zero. A gap in the middle of the history is not filled and is charged nothing.

### How do I add funding to NautilusTrader?

Build FundingRateUpdate records from the venue's settled funding history and add them as data. A record only becomes a payment at a boundary — next_funding_ns if you set it, otherwise when ts_event lands on the interval — so set next_funding_ns to the settlement time the venue reported. A positive rate then debits longs and credits shorts at that instant.

### Why would two engines charge different funding on the same trade?

Three common reasons. They disagree on whether a position opened exactly at the settlement instant pays it — Freqtrade charges it, LEAN waits for the next one. They multiply the rate by different prices, mark in Freqtrade and the last price in LEAN. And a gap in the funding history is either skipped or filled with a constant, depending on the engine and how it was configured.

## Sources

1. [Backtesting — supplying a custom fee value](https://www.freqtrade.io/en/stable/backtesting/) — Freqtrade, read 2026-09-27
2. [Trading with leverage — unavailable funding rates](https://www.freqtrade.io/en/stable/leverage/) — Freqtrade, read 2026-09-27
3. [freqtrade/optimize/backtesting.py, set_fee](https://github.com/freqtrade/freqtrade/blob/stable/freqtrade/optimize/backtesting.py) — Freqtrade, read 2026-09-27
4. [freqtrade/exchange/exchange.py, combine_funding_and_mark and calculate_funding_fees](https://github.com/freqtrade/freqtrade/blob/stable/freqtrade/exchange/exchange.py) — Freqtrade, read 2026-09-27
5. [Backtest accounts and margin](https://github.com/nautechsystems/nautilus_trader/blob/develop/docs/concepts/backtesting/accounts-and-margin.md) — Nautech Systems, read 2026-09-27
6. [FundingRateUpdate](https://github.com/nautechsystems/nautilus_trader/blob/develop/docs/concepts/data/funding_rate_update.md) — Nautech Systems, read 2026-09-27
7. [Common/Orders/Fees/BinanceFeeModel.cs](https://github.com/QuantConnect/Lean/blob/master/Common/Orders/Fees/BinanceFeeModel.cs) — QuantConnect, read 2026-09-27
8. [Common/Orders/Fees/BinanceFuturesFeeModel.cs](https://github.com/QuantConnect/Lean/blob/master/Common/Orders/Fees/BinanceFuturesFeeModel.cs) — QuantConnect, read 2026-09-27
9. [Common/Securities/CryptoFuture/BinanceFutureMarginInterestRateModel.cs](https://github.com/QuantConnect/Lean/blob/master/Common/Securities/CryptoFuture/BinanceFutureMarginInterestRateModel.cs) — QuantConnect, read 2026-09-27
10. [Common/Data/Market/MarginInterestRate.cs, GetSource](https://github.com/QuantConnect/Lean/blob/master/Common/Data/Market/MarginInterestRate.cs) — QuantConnect, read 2026-09-27
11. [py-hftbacktest/src/lib.rs, BacktestAsset fee models](https://github.com/nkaz001/hftbacktest/blob/master/py-hftbacktest/src/lib.rs) — hftbacktest, read 2026-09-27
12. [Introduction to Binance Futures Funding Rates](https://www.binance.com/en/support/faq/introduction-to-binance-futures-funding-rates-360033525031) — Binance, 2026-03-06

*Last updated 2026-09-27. Corrected in place — an endpoint that moves is a bug here, not a new post.*
