Skip to content
python.financial

A Python crypto bot needs more than a trading signal. It needs careful testing, clear order and risk rules, a reliable exchange connection, and a safe response when something breaks. Bad API keys, duplicate orders, stale data, liquidation, exchange failure, or a broken restart can lose money even when the signal is correct.

This guide uses Freqtrade for a spot, long-only example. Start with dry run. Treat any live launch as a controlled systems test with capital you can lose, not as confirmation that a backtest will repeat.

1. Choose the exchange and account first

Check the exchange's legal availability, terms, market structure, data access, order types, minimums, fee schedule, API permissions, rate limits, maintenance behavior, and incident history for the account's jurisdiction. Framework support is adapter-specific. A library using CCXT does not mean every CCXT exchange or feature is maintained by that framework.

Freqtrade publishes separate lists for maintainer-supported spot and futures venues and labels other integrations more cautiously in its current exchange overview. Jesse has a different free-core and commercial live-plugin boundary. Compare the actual market and order features you need in Freqtrade vs Jesse before committing to either.

For the first live system, prefer a dedicated account or subaccount if the venue supports one. Keep only the working balance there. Exchange custody, account freezes, venue outages, and insolvency are external risks that strategy code cannot control.

Avoid leverage during initial validation. Leverage adds liquidation rules, mark prices, funding, collateral coupling, and losses beyond the strategy's modeled stop. Freqtrade's leverage documentation notes that cross-position effects may not be fully simulated and that some liquidation fees are not tracked.

2. Define the strategy and order rules

The following Freqtrade strategy is intentionally small. It is an interface example, not a recommended trading rule.

from pandas import DataFrame
import talib.abstract as ta

from freqtrade.strategy import IStrategy


class RsiMeanReversion(IStrategy):
    timeframe = "1h"
    startup_candle_count = 50
    can_short = False

    stoploss = -0.08
    minimal_roi = {"0": 0.03, "60": 0.015, "240": 0.0}

    def populate_indicators(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
        return dataframe

    def populate_entry_trend(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        dataframe.loc[
            (dataframe["rsi"] < 30) & (dataframe["volume"] > 0),
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(
        self, dataframe: DataFrame, metadata: dict
    ) -> DataFrame:
        dataframe.loc[
            (dataframe["rsi"] > 70) & (dataframe["volume"] > 0),
            "exit_long",
        ] = 1
        return dataframe

The minimal_roi keys are elapsed minutes from trade creation, and their values are minimum return thresholds. The stop value is a strategy instruction, not a guaranteed maximum loss. A local stop still depends on the bot, network, exchange, and executable liquidity. An on-exchange stop can reduce that dependency where the adapter and venue support it, but stop-market orders can slip and stop-limit orders can remain unfilled. Review the venue-specific table in Freqtrade's exchange notes.

Write down the remaining rules outside this class:

  • Pair list and whether it is historically point-in-time.
  • Stake size, maximum simultaneous positions, total exposure, and cash reserve.
  • Entry, exit, stop, emergency-exit, and time-in-force order types.
  • Rules for unfilled, rejected, partially filled, and canceled orders.
  • Daily loss, drawdown, data-staleness, and error-rate limits.
  • Whether a halt cancels open orders, leaves positions, or reduces exposure.

3. Download and audit historical data

Use the framework's downloader so the stored format matches the backtester:

freqtrade download-data \
  --exchange kraken \
  --pairs BTC/USDT \
  --timeframes 1h \
  --timerange 20240101-20260101

Before testing, inspect gaps, duplicate bars, timestamp meaning, available history, delisted pairs, symbol migrations, quote currency, and current versus historical market minimums. OHLCV bars do not contain queue position, spread throughout the bar, hidden liquidity, or the exact order of intrabar prices.

CCXT provides a unified API, but support is capability-specific. Check exchange.has, exchange-specific parameters, and the venue's native documentation. CCXT's official manual states that its built-in rate limiter is enabled by default and that sandbox mode, when supported, must be enabled immediately after creating the exchange. Sandbox keys and production keys are separate.

4. Check the trades before chasing returns

Run a fixed configuration first:

freqtrade backtesting \
  --config user_data/config.json \
  --strategy RsiMeanReversion \
  --timeframe 1h \
  --timerange 20240101-20260101 \
  --export trades

Inspect exported trades around entry signals, exits, gaps, stop events, and simultaneous conditions. Freqtrade documents candle-level assumptions for event ordering, fills, limits, and stops in its backtesting guide. Those assumptions can differ from dry and live behavior.

Run the framework's diagnostic commands where applicable:

freqtrade lookahead-analysis \
  --config user_data/config.json \
  --strategy RsiMeanReversion \
  --timerange 20240101-20260101

freqtrade recursive-analysis \
  --config user_data/config.json \
  --strategy RsiMeanReversion \
  --timerange 20240101-20260101

These checks target particular look-ahead and startup-recursion problems. Passing them does not validate the hypothesis, data universe, costs, or execution model.

Use chronological development and final periods. Log every strategy, parameter, pair-list, timeframe, and cost variant. Stress spread, slippage, fees, latency, funding, and capacity. The overfitting guide explains how to keep optimization and final evaluation separate.

5. Run the whole bot with fake money

Use a dedicated dry-run configuration and database:

{
  "dry_run": true,
  "dry_run_wallet": 10000,
  "db_url": "sqlite:///tradesv3.dryrun.sqlite",
  "stake_currency": "USDT",
  "stake_amount": 100,
  "max_open_trades": 2,
  "cancel_open_orders_on_exit": true,
  "exchange": {
    "name": "kraken",
    "key": "",
    "secret": "",
    "pair_whitelist": ["BTC/USDT"]
  }
}
freqtrade trade \
  --config user_data/config.dry.json \
  --strategy RsiMeanReversion \
  --dry-run

Dry run should prove that data remains fresh, signals occur once, sizing respects limits, restarts recover state, alerts arrive, and simulated orders reconcile. It does not prove real fill quality. Freqtrade also warns that exchange testnets have different participation and liquidity, so a venue sandbox is primarily an integration test rather than a market-realism test.

Test failures deliberately:

  1. Restart during an open position and during an open order.
  2. Interrupt the network and restore it.
  3. Deliver stale, missing, duplicate, and out-of-order data in a fixture.
  4. Force insufficient balance, invalid precision, minimum-notional, and rate-limit errors.
  5. Verify that repeated callbacks cannot create duplicate client order IDs or positions.
  6. Exercise pause, cancel, reduce, and shutdown procedures from an operator account.

6. Protect live credentials

Create a separate production key with the least privileges the bot requires. Usually that means query, create or modify orders, and cancel or close orders. Do not grant withdrawal or withdrawal-address permissions. Use an IP allowlist, key expiry, and API-key two-factor control if the venue offers them.

Kraken's API-key information schema illustrates why this must be checked precisely: query, trade modification, trade cancellation, fund withdrawal, withdrawal-address changes, expiry, and IP allowlisting are separate attributes.

Never commit keys to Git, bake them into an image, paste them into chat, or print them in logs. Load secrets at runtime from a restricted secret store or protected private configuration outside the repository. Separate paper and live keys, databases, notification channels, and service identities. Rotate and revoke keys after suspected exposure.

Security limits financial damage but does not make the venue solvent or the strategy sound.

7. Make sure the bot can recover

A process being alive does not mean the bot is safe. Monitor at least:

Area Signal to monitor
Process Heartbeat, restart count, memory, disk, and clock synchronization
Market data Last event time, sequence gaps, symbol status, and abnormal prices
Orders Submission latency, rejects, open-order age, partial fills, and cancel failures
Account Exchange balances and positions versus the bot's ledger
Risk Gross and net exposure, concentration, drawdown, and daily loss
Venue API status, rate-limit pressure, maintenance, and websocket reconnects

Alerts need an owner, severity, delivery test, and response deadline. Keep a runbook that answers what happens after stale data, repeated authentication failure, unresolved reconciliation, database corruption, or an exchange outage.

Persist enough state to rebuild the bot from exchange truth. Back up and restore the database in a disposable environment. On startup, reconcile balances, positions, and open orders before enabling new entries. Manual trades and other bots on the same account complicate ownership, which is another reason to isolate the account.

Understand shutdown semantics. Freqtrade's cancel_open_orders_on_exit concerns unfilled and partially filled orders. It does not close existing positions. A kill procedure must state whether positions remain, are reduced, or are closed, and under which order and liquidity constraints.

8. Start live with a very small test

Use the smallest practical stake and a small pair list. Set hard caps outside the strategy where possible: per-order notional, open positions, total exposure, daily loss, and API permissions. Confirm actual fees, minimums, precision, order acknowledgements, partial fills, and reconciliation before increasing scope.

Stop new entries on unexplained discrepancies. Compare intended orders, acknowledged orders, exchange fills, internal positions, exchange positions, and cash movement. Increasing capital is a new capacity test because slippage and market impact need not scale linearly.

Do not infer live readiness from a stoploss setting. A gap, outage, delisting, liquidation, exchange intervention, or lack of executable liquidity can exceed it.

Checklist before live trading

  • The exchange, market, jurisdiction, account, and custody risks are documented.
  • The strategy uses only information available at the time and still works with higher trading costs.
  • Dry-run restarts, network faults, stale data, rejects, and duplicate prevention were tested.
  • Production keys have no withdrawal capability and are isolated from source and logs.
  • Every order, fill, balance, and position can be reconciled with the venue.
  • Exposure and loss limits exist outside optimistic strategy assumptions.
  • Monitoring detects a live but stale or divergent bot, not only a dead process.
  • Pause, cancel, reduce, close, restore, rotate, and revoke procedures were rehearsed.
  • The first live test uses little money and stops when the bot and exchange disagree.

No tool removes exchange, market, or software risk. The goal is to make the rules clear and catch failures before they grow.

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