Freqtrade from install to live trading — the crypto bot that put Dry-Run in the first section of its docs

A 54,000-star crypto trading bot installed in 5 seconds, 90 days of candles pulled with no API key, a simulated order round-tripped, a stop-loss actually trigge

Part 4. Freqtrade — the crypto bot that wrote "start with Dry-Run" into the first section of its docs

Done when: you have pulled candles and run a backtest without issuing a single API key, and then used lookahead-analysis to check whether that backtest is lying to you.

What it does Downloads candles, turns a Python strategy into signals, sends orders to an exchange, and runs the same strategy as a backtest. On top of that sit a web UI, Telegram control, a REST API and hyperparameter optimization
Who it fits People doing crypto only, who can write a strategy in Python and want to take backtesting seriously — especially anyone who has ever felt they could not trust their own backtest results
Who it doesn't fit Anyone who needs stocks (this is crypto only) · anyone who wants to drive it by conversation instead of code · anyone who cannot leave a laptop on 24 hours a day (while it is off, nobody enforces your stop)
What you end up with 90 days of candles pulled with no key, a 4-second backtest summary, a record of a simulated order round-tripping and a stop-loss firing, and the checker catching a lookahead bias I planted on purpose
What you need · cost Code free (GPL-3.0) · Model none (this tool does not use an LLM) · Market data free, 0 API keys · weltiq only if you follow section 5
Time · difficulty 5 seconds to install, about a minute to the first backtest. Difficulty medium — you need to be able to make a venv and edit a JSON config
Relationship to weltiq Execution does not connect. Freqtrade does everything through simulated fills inside itself. The one seam is data — I checked whether both sides' candles carry the same values (section 5)
Test environment · date macOS 26.6.2 · arm64 · uv 0.10.12 · CPython 3.12.13 · freqtrade 2026.8 · CCXT 4.5.81 · 2026-09-21 (commands and numbers) / 2026-09-22 (screens)
Disclosure weltiq pays me to write this series. Everything said about the tool comes from runs I did myself

I created no exchange account and no API key. The only thing I asked the exchange for was public market data, and every order in this article happened inside freqtrade's own Dry-Run. Not one order went out to an exchange. The screenshots were taken separately a day later, in the same environment with the same settings, which is why the prices and clock times inside the images run a day ahead of the numbers in the text. FreqUI is on 9160 rather than the default 8080 (8080 was already in use on that laptop).


1. What this is

freqtrade/freqtrade is an open-source crypto trading bot written in Python. It downloads candles, reads a strategy file and computes indicators, generates entry and exit signals, sends orders, manages positions, and runs all of that as a backtest as well.

It started in May 2017 and is still going, with 11,000 forks. It was here long before LLMs arrived in this space, so what there is to see is not "how good is the AI" but "how a bot acquired everything a bot needs, over nine years".

Item Value How it was checked
URL github.com/freqtrade/freqtrade
License GPL-3.0 GitHub API
Stars ★54,625 · 11,325 forks (observed 2026-09-21) GitHub API
Last push 2026-09-21 (the day I wrote this) GitHub API
Created 2017-05-17 · 30 open issues · not archived GitHub API
Version PyPI freqtrade 2026.8 · CCXT 4.5.81 freqtrade --version
Exchanges 78 officially supported (104 with -a) freqtrade list-exchanges

Dry-Run is not a slogan here, it is code

The first section of the README is Disclaimer, and it says to always start by running the bot in Dry-Run. Plenty of projects write sentences like that. What is different is that the code enforces it. I opened the installed package and checked three places.

# freqtrade/constants.py:196
MINIMAL_CONFIG = {
    "stake_currency": "",
    "dry_run": True,
    ...
}

The minimal config carries dry_run: True, and removing that key entirely still defaults to Dry-Run (config.get("dry_run", True)). Delete it by accident and you fall into simulation, not live trading. And the config wizard asks about Dry-Run first, defaulting to yes, and only asks for an API key once you say no. That I issued no key anywhere in this article is not an accident — the tool is built that way. (The file and line numbers for all three are in the run log.)

A nine-year-old project keeping "simulated first" in the very first slot of its setup wizard is worth reading on its own.


2. Installing it

All you need is Python 3.12. No API key and no exchange account. Use a dedicated virtual environment — it pulls in 83 packages, including heavyweights like ta-lib, scipy and pandas.

uv venv -p 3.12 /private/tmp/tut-venvs/freqtrade
uv pip install --python /private/tmp/tut-venvs/freqtrade/bin/python freqtrade
freqtrade install-ui          # the web UI (FreqUI) is a separate download
freqtrade create-userdir --userdir ./user_data

With the cache cleared that was 5.01 seconds cold, 83 packages (0.04 seconds warm). ta-lib used to require installing a C library separately; wheels are published now, so that step is gone.

The output of freqtrade --version and install-ui

The screen after installation. In the top block, Freqtrade Version: freqtrade 2026.8 means the bot is installed; in the bottom block, UI already up-to-date, FreqUI Version 3.1.2 means the web UI is ready too (on a first run you get Downloading… instead). Skip that one line and the browser later shows nothing but a grey "Freqtrade UI not installed" page.

Last comes the config file. The freqtrade new-config wizard is interactive and therefore unusable from a script, so I wrote config.json by hand. The parts that matter look like this (the full file is in the evidence folder).

{
    "max_open_trades": 3,
    "stake_currency": "USDT",
    "stake_amount": 100,
    "dry_run": true,
    "dry_run_wallet": 1000,
    "exchange": {
        "name": "binance", "key": "", "secret": "",
        "pair_whitelist": ["BTC/USDT", "ETH/USDT", "SOL/USDT"]
    },
    "api_server": {
        "enabled": true, "listen_ip_address": "127.0.0.1", "listen_port": 8080,
        "username": "tony", "password": "…", "jwt_secret_key": "…"
    },
    "force_entry_enable": true
}

key and secret stay empty strings. Dry-Run does not use them.

Where you get stuck

Symptom Cause Way around it
Invalid configuration. Reason: '…' is too short jwt_secret_key shorter than 32 characters Make it 32 or more. This is where I got stuck first
new-config hangs in a script Interactive wizard Write config.json directly, or run it in a terminal by hand
Market entry orders require entry_pricing.price_side = "other" Some commands force market orders while the config says same Set price_side to other in entry_pricing and exit_pricing. You meet this at the checker command in section 4
The DB file appears somewhere unexpected tradesv3.dryrun.sqlite lands in the current directory, not --userdir Always run from the same folder, or pin it with --db-url
Browser shows "Freqtrade UI not installed" install-ui was never run Run freqtrade install-ui, then restart the bot

The DB file is tradesv3.dryrun.sqlite for Dry-Run and tradesv3.sqlite for live. They are split at the filename, so simulated records and live records cannot mix.


3. First result

The smallest example is downloading candles. No account and no key.

freqtrade download-data -c config.json --userdir ./user_data --timeframes 1d 5m --days 90
freqtrade list-data -c config.json --userdir ./user_data
INFO - Instance is running with dry_run enabled
INFO - Downloaded data for BTC/USDT, 1d, spot with length 90.
INFO - Downloaded data for BTC/USDT, 5m, spot with length 26065.
real 26.70

      Found 6 pair / timeframe combinations.

That output means it worked. 270 daily candles and 78,195 five-minute candles in 26.7 seconds, and not one key was used.

Next is a backtest with the sample strategy. 66 trades simulated in 4.12 seconds.

│ Total/Daily Avg Trades          │ 66 / 0.74           │
│ Final balance                   │ 1040.739 USDT       │
│ Total profit %                  │ 4.07%               │
│ Market change                   │ 56.16%              │
│ Win%                            │ 93.9%               │
│ Absolute drawdown               │ 10.184 USDT (0.97%) │

The most important line in that table is not the return, it is Market change 56.16%. Over the same period, simply holding returned 56%, and this strategy returned 4.07%. A strategy with a 93.9% win rate lost to the market by 52 percentage points. What I want to point out is that freqtrade puts that line in the summary by default — a benchmark set right next to a result that would otherwise look impressive. (This is a sample result from a sample strategy, and it is not a trading opinion of any kind.)

Bringing up the UI — FreqUI

There is no separate command. Start the bot and the UI comes up with it. The only condition is api_server.enabled being true in the config. If you want the screen without any trading, there is a separate freqtrade webserver mode.

freqtrade trade -c config.json --userdir ./user_data

Freqtrade bot startup log — Dry run is enabled, HTTP Server at 127.0.0.1:9160

The terminal after starting the bot. Two lines are all you need — Starting HTTP Server at 127.0.0.1:9160 is the UI address, and Dry run is enabled. All trades are simulated. is the declaration that this is simulated. The dry_run_buy_BTC/USDT_… at the bottom is a simulated order I placed from the UI shortly afterwards.

The address defaults to http://127.0.0.1:8080 and moves with api_server.listen_port. Leave listen_ip_address at 127.0.0.1 — set it to 0.0.0.0 and freqtrade writes SECURITY WARNING - Local Rest Server listening to external connections into the log. This is a screen that can force entries, force exits and stop the bot.

The FreqUI login dialog

The login dialog. You are not creating an account: of the four fields, API Url takes the bot's address (http://127.0.0.1:9160) and Username · Password take the values written in config.json. Bot Name is just the label the UI shows, so anything works — the field exists because FreqUI can register several bots and show them on one screen. Leave the password blank and you get a warning at startup. Do not leave it blank.

FreqUI on first open — No bot selected

The screen on first open. Look at No bot selected in the top right and the Login button next to it — in this state no data appears at all. This screen renders even when no bot is running, so seeing it does not mean your bot is up.

FreqUI Dashboard — the Dry badge and cumulative profit

The Dashboard right after logging in. Look first at the green Dry badge next to the bot name — it marks this account as simulated, and on a live bot that slot reads Live instead. To its right, Closed Profit −0.08% (−0.76) and Balance 989.247 USDT(dry) are the four simulated trades from the previous day still sitting there (W/L 0 / 4 — all four lost). The staircase in Cumulative Profit at the bottom right is those four closing one by one between 21:10 and 21:25.

The FreqUI Trade screen — a 1-minute chart and open-position P/L

The Trade screen. On the right is the BTC/USDT 1-minute chart the bot is actually watching, with ManualStrategy | 1m above it giving the strategy name and timeframe currently running. The red −0.14% (−0.141) attached to the BTC/USDT row on the left is the live P/L of the simulated position being held — the instrument list doubles as the P/L board.

It can also be driven from Telegram (turn on telegram.enabled and it attaches at startup; the chat ID is the authentication). I did not actually create a Telegram bot this time, so I am leaving that unverified — the details I checked in the config and the code are in the run log.


4. Getting the most out of it — asking the bot whether the backtest is lying to me

First, everything I ran, at a glance. All of it finished inside freqtrade, with no key.

What I did Result Time
Narrow candidates down from the whole USDT market (volume → age → price → spread → range) 493 → 100 → 97 → 88 → 86 → 78 9.04s
One simulated order, there and back (ETH/USDT in at 2728.80, out at 2725.35) Fees 0.0999 + 0.0997, P/L −0.32589189 USDT (−0.33%) 34s
Set the stop at −0.2% and watch Did not fire in 10 minutes — the displayed P/L already includes the 0.2% round-trip fee, so it has to reach roughly −0.4% 10 min
Fix the stop to −0.05% and re-enter In at 85198.18 → Reason: stop_loss after 91 seconds 91s
Compare the stop level 85155.59 against the actual fill Filled at 85145.81 — 9.78 dollars (about 1.1bp) below —
kill -9 with a position open, then restart Found open trade: Trade(id=2, …) — amount, entry price and stop level all recovered 20s

Three things to add. It does not go easy on you just because it is simulated (the stop level and the fill price are recorded as different numbers). You cannot change a stop without restarting (reload_config does not move the stop on an already-open position). And while the bot is dead, nobody enforces anything — recovery is not "undoing what happened in the meantime", it is "starting again from where you are". The raw logs and trade records for each row are in the run log, exactly as they came.

FreqUI Open Trades — one simulated position

The Open Trades panel under the chart. It went in at Open rate 85868.2 and Current rate 85884.98, yet Current profit % −0.18% is red — the price went up and the P/L is negative. The 0.2% round-trip fee is already charged against it. It does not go easy on you for being simulated is all in that one line. The icon on the far right is the force-exit button.

And this is where this tool earns its place

The most common and least visible accident in backtesting is lookahead bias: while computing an indicator you accidentally pull a value from a candle that has not happened yet into the current one. Backtest results then improve unbelievably, and in live trading that value does not exist, so the strategy collapses outright. One wrong sign in shift(), one rolling(center=True), and it is there.

Freqtrade ships a command that catches this. It is lookahead-analysis. It runs the same strategy over the full range and over a truncated range, and looks at whether past signals change depending on whether future data is present. Asked about the sample strategy, it returned has_bias: No in 5.28 seconds.

But that alone does not tell you whether the checker actually catches anything. So I wrote a strategy that peeks at the future on purpose. It pulls the close 12 candles ahead (one hour on a 5-minute timeframe) into the current candle, and buys when that is 0.5% higher than now.

class CheatingStrategy(IStrategy):
    timeframe = "5m"
    minimal_roi = {"0": 100}      # so ROI never exits
    stoploss = -0.99              # so the stop never exits either

    def populate_indicators(self, dataframe, metadata):
        # shift(-12) pulls the close 12 candles ahead into this one. That is lookahead.
        dataframe["future_close"] = dataframe["close"].shift(-12)
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        dataframe.loc[dataframe["future_close"] > dataframe["close"] * 1.005,
                      "enter_long"] = 1
        return dataframe

The backtest came back with 44.11% over 51 days, a 92.8% win rate and a 0.06% maximum drawdown. Results like that make most people think they have finally got it. A 0.06% drawdown means there was effectively no losing stretch at all, which is not a number that exists in reality.

Now ask the checker.

freqtrade lookahead-analysis -c config.json --userdir ./user_data \
  --strategy CheatingStrategy --timerange 20260801- --timeframe 5m
┃ strategy         ┃ has_bias ┃ total_signals ┃ biased_entry_signals ┃ biased_indicators                   ┃
│ CheatingStrategy │      Yes │            20 │                   20 │ future_close, enter_long, exit_long │

has_bias: Yes. And it names the contaminated column outright — future_close. That is the exact variable name I planted the problem in. All 20 of the 20 signals were flagged as biased, and it took 3.10 seconds. You do not get "something looks off", you get "it is this column". The clean strategy passed with No, so both the catching side and the not-catching side are confirmed.

That said, this only looks at lookahead bias. It cannot catch overfitting, survivorship bias or ignored liquidity, and has_bias: No says nothing whatsoever about a strategy being good — the sample strategy in section 3 got a No and still lost to the market by 52 percentage points.

Why this is this tool's place. There are plenty of backtesting frameworks; shipping a command that doubts your own backtest inside the product is rare. It is the kind of feature only a project that knows how many people fell into this trap over nine years would build. If you use freqtrade, making a habit of running this command every time you write a strategy is the biggest thing you can get out of it.


5. Moving it to weltiq — the seam is data, not execution

Let me put this plainly first. This tool has no seam that connects to weltiq's execution. Freqtrade downloads its own candles, makes its own decisions, does its own simulated fills, and keeps its own records in its own sqlite. That I ran all of section 4 inside freqtrade is the proof. If you do crypto with freqtrade, there is no reason to bolt a paper account onto it.

There is one seam, and it is data. I checked whether the daily candles freqtrade downloaded from Binance and the daily candles weltiq's /v1/bars returns carry the same values, across 30 BTC/USDT bars.

weltiq n = 30   (첫 2026-08-23T09:00+09:00  마지막 2026-09-21T09:00+09:00)

겹치는 일봉 30개 중 네 값(OHLC) 모두 일치 30 · 다름 0
  2026-09-19  freqtrade o=80883.86 c=81249.99 | weltiq o=80883.86 c=81249.99  → 일치
  2026-09-20  freqtrade o=81249.99 c=81178.00 | weltiq o=81249.99 c=81178.00  → 일치

(The script prints in Korean: the first line gives the count and the first and last bar, and the block below reports that of 30 overlapping daily bars, all 30 matched on all four OHLC values and 0 differed — 일치 means "match".)

The output of the freqtrade-vs-weltiq candle comparison

That comparison actually running. Note the +09:00 after 마지막 2026-09-21T09:00:00 on the top line — the labelling difference discussed just below is right there. The script is compare_bars.py, and it only reads the two saved files, so it touches neither the exchange nor the API.

All 30 matched to the decimal. Both sides were also leaving out today's unfinished candle — freqtrade drops it at download time with a data ends at … warning, and weltiq only stores closed bars. Same judgement.

The labels do look a day apart, though. That is a convention difference, not a fault.

Timestamp attached to a bar The same bar, written out
freqtrade Start time, UTC 2026-09-20 00:00 UTC
weltiq Close time, in the requested timezone 2026-09-21T09:00:00+09:00

If you ever put the two sets of numbers side by side, you have to shift by one bar to line them up. That is what produced the 30/30 above.

What this combination does not do, also worth writing down: weltiq's /v1/bars only serves up to 720 hours (30 days). It is not the right fit for pulling 90 days of candles to backtest with, and that is exactly what freqtrade's download-data does in 26.7 seconds. Confirming that both systems' candles carry the same values means their records can be measured with the same ruler — not that you should source candles here.


6. After using it

What was good

  1. Dry-Run is code, not a slogan. It is the default, it is the first question in the setup wizard, deleting it by accident lands you on the safe side, and in simulation it does not even ask for an API key. Even the DB filenames are split.
  2. You get this far with no keys. 5 seconds to install, 26.7 seconds for 90 days of candles, 4.1 seconds for a backtest, 34 seconds for a simulated round trip — all without creating a single account.
  3. lookahead-analysis is the real thing. It caught a lookahead I planted on purpose and named the contaminated column. That one feature is reason enough to use the tool.
  4. It does not go easy on you for being simulated. The stop level (85,155.59) and the fill (85,145.81) are recorded as different numbers, and the round-trip fee means you are at −0.2% even if the price does not move.

What wasn't

  1. Crypto only. No stocks, no traditional futures, and no sign of a plan to expand.
  2. Rules cannot be changed without downtime. Changing a stop requires a restart, and an already-open position keeps the old stop level even after one.
  3. While the bot is dead, nobody enforces anything. Restarting brings the position back, but a stop that was breached in the meantime is simply gone. For anyone running this on a laptop, that is a practical problem.
  4. The third-party MCP wrapper (kukapay/freqtrade-mcp, ★145) does not install as-is today. Its code has been frozen since 2025-04-05, so following the README fails at startup, and even after fixing two lines to bring it up, only 11 of its 17 tools work and the order-placing tool is not one of them. If you were planning to drive this by conversation, fork it or call freqtrade's official REST client directly — the causes of all six failures are in the run log.

Cost

Item Value
Code · market data Free (GPL-3.0 · 0 API keys)
Model None — this tool does not use an LLM
Time Install 5s · 90 days of candles 26.7s · backtest 4.1s

7. Coming up next

Next is tradermonty/claude-trading-skills (★2.9k), and its character is the opposite. Freqtrade is one solid block where the candles, the strategy, the fills and the records are all its own, whereas claude-trading-skills is a bundle of skills you attach to Claude, and it does not run by itself. If this part's question was "there is no seam to connect to", the next one becomes "what has to go into that empty slot".

weltiq setup — 3 lines 1. Sign up: {{CTA_URL}} 2. My → Connections → issue an API key 3. claude mcp add weltiq --header "Authorization: Bearer <key>" https://weltiq.ai/mcp

If you want to put the same rule on the stock and overseas markets freqtrade cannot reach, that is when to look.


Every command, output and number in this article came from runs actually performed on 2026-09-21 (commands) and 2026-09-22 (screens). No real money moved in any order; all of it is simulated. None of it is a trading opinion. The trial and error, raw logs and defect list not written up here are in the run log.