Skip to content
python.financial
Platform
Crypto trading bot
License
GPL-3.0
Pricing
Free and open source
Live trading
Dry-run and live execution on supported exchanges
Best for
Candle-based crypto strategies that need backtesting, dry-run validation, monitoring, and live exchange execution in one tool

Freqtrade is a practical choice for a candle-based crypto strategy that must move from historical testing to a live-data dry run and then exchange execution. The same Python strategy class supplies indicators and entry or exit signals in each mode. Its backtester is useful for screening and debugging, but its fill model is intentionally simpler than a live exchange.

How the workflow fits together

Freqtrade reads OHLCV candles, calculates a strategy's indicators and signals, and then simulates or submits trades. Backtesting loops through stored candles. Dry-run mode consumes live exchange data and records simulated orders. Live mode submits orders through CCXT and stores operational state in a database. FreqUI, a REST API, Telegram integration, and exported backtest results support analysis and monitoring.

The supported exchange table distinguishes exchanges and modes tested by the maintainers from the wider set exposed by CCXT. Spot, futures, margin modes, and on-exchange stop-loss support vary by venue. Moving a strategy between exchanges therefore requires a capability and market-structure review, not only a configuration change.

Stage What it establishes What it does not establish
Backtest Signal behavior on downloaded candles under documented assumptions Real fill probability, latency, market impact, or live reliability
Hyperopt Parameters that score well under a chosen loss and data range Out-of-sample performance or protection from multiple testing
lookahead-analysis and recursive-analysis Common future-data and unstable-indicator problems Absence of every possible leak or implementation error
Dry run Live-data signal timing and bot operations without real orders Actual queue position, rejection behavior, slippage, or exchange custody risk
Live trading Behavior with real exchange orders and balances Future profitability or safety without independent monitoring and limits

A complete pinned backtest example

This small fixture makes Freqtrade's candle timing visible. It uses the official freqtradeorg/freqtrade:2026.8 image, one static spot pair, twelve synthetic five-minute candles, a 1,000 USDT stake, a 10,000 USDT starting wallet, and an explicit 0.1% fee charged on entry and exit. Create this layout:

user_data/
  config.json
  data/binance/BTC_USDT-5m.json
  strategies/DocumentedCross.py

Save the following as user_data/config.json. The empty API fields are intentional because this is a backtest. Do not place real credentials in a committed configuration file.

{
  "$schema": "https://schema.freqtrade.io/schema.json",
  "max_open_trades": 1,
  "stake_currency": "USDT",
  "stake_amount": 1000,
  "tradable_balance_ratio": 1.0,
  "dry_run_wallet": 10000,
  "timeframe": "5m",
  "dry_run": true,
  "entry_pricing": {
    "price_side": "same",
    "use_order_book": false,
    "order_book_top": 1,
    "price_last_balance": 0.0,
    "check_depth_of_market": {
      "enabled": false,
      "bids_to_ask_delta": 1
    }
  },
  "exit_pricing": {
    "price_side": "same",
    "use_order_book": false,
    "order_book_top": 1
  },
  "exchange": {
    "name": "binance",
    "api_key": "",
    "secret": "",
    "ccxt_config": {},
    "ccxt_async_config": {},
    "pair_whitelist": ["BTC/USDT"],
    "pair_blacklist": []
  },
  "pairlists": [{"method": "StaticPairList"}]
}

Freqtrade's JSON candle rows contain Unix milliseconds, open, high, low, close, and volume. Save these constructed bars as user_data/data/binance/BTC_USDT-5m.json:

[
  [1767225600000, 100000, 100500, 99500, 100000, 10],
  [1767225900000, 100000, 101500, 99800, 101000, 10],
  [1767226200000, 101000, 102500, 100800, 102000, 10],
  [1767226500000, 102000, 103500, 101500, 103000, 10],
  [1767226800000, 103000, 104500, 102500, 104000, 10],
  [1767227100000, 104000, 105500, 103500, 105000, 10],
  [1767227400000, 105000, 105200, 103800, 104000, 10],
  [1767227700000, 104000, 104200, 102800, 103000, 10],
  [1767228000000, 103000, 103200, 101800, 102000, 10],
  [1767228300000, 102000, 102200, 100800, 101000, 10],
  [1767228600000, 101000, 101200, 99800, 100000, 10],
  [1767228900000, 100000, 100200, 98800, 99000, 10]
]

Save the strategy as user_data/strategies/DocumentedCross.py:

from pandas import DataFrame

from freqtrade.strategy import IStrategy


class DocumentedCross(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = "5m"
    can_short = False
    startup_candle_count = 1
    minimal_roi = {"0": 10.0}
    stoploss = -0.50
    trailing_stop = False
    use_exit_signal = True

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["close"] > 101_500)
            & (dataframe["close"].shift(1) <= 101_500)
            & (dataframe["volume"] > 0),
            ["enter_long", "enter_tag"],
        ] = (1, "cross_above_101500")
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        dataframe.loc[
            (dataframe["close"] < 104_500)
            & (dataframe["close"].shift(1) >= 104_500)
            & (dataframe["volume"] > 0),
            ["exit_long", "exit_tag"],
        ] = (1, "cross_below_104500")
        return dataframe

Run it from the directory containing user_data:

docker run --rm \
  -v "$PWD/user_data:/freqtrade/user_data" \
  freqtradeorg/freqtrade:2026.8 backtesting \
  --config /freqtrade/user_data/config.json \
  --strategy DocumentedCross \
  --data-format-ohlcv json \
  --timerange 20260101-20260102 \
  --fee 0.001 \
  --cache none \
  --export trades \
  --backtest-directory /freqtrade/user_data/backtest_results

The exact fixture produced one trade. The report and exported trade record contained:

Entry:       2026-01-01 00:15 UTC at 102000.0
Exit:        2026-01-01 00:35 UTC at 104000.0
Fee:         0.1% on entry and exit
Profit:      17.5812 USDT (1.7571% of the trade)
Final wallet: 10017.5812 USDT

The signal above 101,500 occurs on the 00:10 candle, then the entry fills at the next candle's 102,000 open. The signal below 104,500 occurs at 00:30, then the exit fills at the 00:35 open of 104,000. --cache none prevents an older result from hiding code or data changes, and the exported archive preserves the resolved configuration and strategy.

This is an execution-semantics fixture, not a strategy. The thresholds, prices, volume, permissive stop loss, and effectively disabled return-on-investment exit were constructed to force one readable trade. The positive result and 100% win rate have no statistical meaning. The backtester does not use the volume to limit this fill and applies no spread or slippage. Although candles are local, Freqtrade still contacted Binance during this run to load current market metadata, so it does not recreate January 2026 precision or minimum-order rules and requires network access unless those internals are supplied another way.

Backtesting assumptions that matter

The official backtesting documentation states that an entry normally fills at the candle open. An order at a custom price fills without slippage when that price falls inside the candle's high-low range. Exit signals normally fill at the next candle's open, and exit signal, stop loss, return-on-investment, and trailing-stop conditions follow a defined priority when they collide within one candle.

Fees are included, using exchange market information by default or an explicit --fee ratio applied on entry and exit. The backtester does not reconstruct order-book depth, queue priority, latency, rejected orders, or price impact. It also uses current trading limits and precision because historical exchange limits are unavailable. Dynamic pairlists can reflect current rather than historical membership, so the documentation recommends a static pairlist for reproducible tests.

Backtest results may be cached for a day. Changes in imported strategy modules or newly downloaded data may not invalidate the cache, so use --cache none when validating a changed experiment. Archive the candle data and dependency versions as well as Freqtrade's exported strategy and sanitized configuration.

Hyperopt and FreqAI need separate validation

Hyperopt searches strategy parameters against a selected loss function. Repeatedly optimizing and inspecting the same period creates multiple-testing bias, even when the command runs correctly. Reserve untouched evaluation data and prefer rolling or walk-forward checks across different market regimes.

FreqAI trains supervised prediction models from user-defined features and targets. It can simulate periodic retraining in historical tests and retrain in a background thread during dry or live runs. The tooling does not make arbitrary features safe. FreqAI's documentation warns that feature engineering runs across the full training timerange, so a feature that reads future values can still leak information. Model retraining also adds compute, dependency, staleness, and monitoring requirements.

Futures and live-operation risks

Futures support is venue-specific. Funding data may be unavailable for older periods, liquidation fees are not included, and cross-position effects may be incomplete in dry-run or backtesting mode. The leverage documentation also assumes that Freqtrade is the only system trading the leveraged account. These limits can materially change profit and liquidation estimates.

Before enabling live mode, use a separate exchange subaccount with restricted API keys, withdrawals disabled, conservative exposure limits, and exchange-supported stop losses where available. Monitor clock synchronization, connectivity, rejected or stale orders, database health, balances, and discrepancies between local and exchange state. A dry run is necessary, but it cannot reproduce real fills or custody failures.

Maintenance and license

Freqtrade is licensed under GPL-3.0. Review the license with counsel if modified code will be distributed as part of a proprietary product. Keeping current matters operationally because exchange APIs and rules change, but upgrades should first be tested against pinned data and a dry-run deployment.

When to choose Freqtrade

Choose Freqtrade for systematic crypto strategies expressed on candles when an integrated backtest, optimization, dry-run, monitoring, and execution workflow is more important than order-book simulation. Choose an execution-focused engine such as NautilusTrader when tick ordering, latency, or detailed order lifecycle drives the result. Compare Freqtrade with Jesse separately when both candle-based crypto bot workflows fit the project.

Choose which optional services may run. You can change these settings at any time.