0% found this document useful (0 votes)
3K views158 pages

Freqtrade

Freqtrade is an open-source crypto trading bot written in Python that supports major exchanges and offers features like strategy development, backtesting, and optimization. Users are advised to use it at their own risk and to have basic coding skills, as it requires configuration through JSON files and can be controlled via Telegram or a web UI. The bot supports various exchanges and has specific hardware and software requirements for optimal performance.

Uploaded by

ai.vazimba
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3K views158 pages

Freqtrade

Freqtrade is an open-source crypto trading bot written in Python that supports major exchanges and offers features like strategy development, backtesting, and optimization. Users are advised to use it at their own risk and to have basic coding skills, as it requires configuration through JSON files and can be controlled via Telegram or a web UI. The bot supports various exchanges and has specific hardware and software requirements for optimal performance.

Uploaded by

ai.vazimba
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

Introduction

Freqtrade is a free and open source crypto trading bot written in Python. It is
designed to support all major exchanges and be controlled via Telegram or webUI. It
contains backtesting, plotting and money management tools as well as strategy
optimization by machine learning.

DISCLAIMER

This software is for educational purposes only. Do not risk money which you are
afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES
ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.

Always start by running a trading bot in Dry-run and do not engage money before you
understand how it works and what profit/loss you should expect.

We strongly recommend you to have basic coding skills and Python knowledge. Do not
hesitate to read the source code and understand the mechanisms of this bot,
algorithms and techniques implemented in it.

freqtrade screenshot

Features
Develop your Strategy: Write your strategy in python, using pandas. Example
strategies to inspire you are available in the strategy repository.
Download market data: Download historical data of the exchange and the markets your
may want to trade with.
Backtest: Test your strategy on downloaded historical data.
Optimize: Find the best parameters for your strategy using hyperoptimization which
employs machining learning methods. You can optimize buy, sell, take profit (ROI),
stop-loss and trailing stop-loss parameters for your strategy.
Select markets: Create your static list or use an automatic one based on top traded
volumes and/or prices (not available during backtesting). You can also explicitly
blacklist markets you don't want to trade.
Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real
money (Live-Trade mode).
Run using Edge (optional module): The concept is to find the best historical trade
expectancy by markets based on variation of the stop-loss and then allow/reject
markets to trade. The sizing of the trade is based on a risk of a percentage of
your capital.
Control/Monitor: Use Telegram or a WebUI (start/stop the bot, show profit/loss,
daily summary, current open trades results, etc.).
Analyze: Further analysis can be performed on either Backtesting data or Freqtrade
trading history (SQL database), including automated standard plots, and methods to
load the data into interactive environments.
Supported exchange marketplaces
Please read the exchange specific notes to learn about eventual, special
configurations needed for each exchange.

Binance
Bitmart
BingX
[Link]
HTX (Former Huobi)
Kraken
OKX (Former OKEX)
potentially many others through ccxt. (We cannot guarantee they will work)
Supported Futures Exchanges (experimental)
Binance
[Link]
OKX
Bybit
Please make sure to read the exchange specific notes, as well as the trading with
leverage documentation before diving in.

Community tested
Exchanges confirmed working by the community:

Bitvavo
Kucoin
Community showcase
This section will highlight a few projects from members of the community.

Note

The projects below are for the most part not maintained by the freqtrade ,
therefore use your own caution before using them.

Example freqtrade strategies


FrequentHippo - Grafana dashboard with dry/live runs and backtests (by
hippocritical).
Online pairlist generator (by Blood4rc).
Freqtrade Backtesting Project (by Blood4rc).
Freqtrade analysis notebook (by Froggleston).
TUI for freqtrade (by Froggleston).
Bot Academy (by stash86) - Blog about crypto bot projects.
Requirements
Hardware requirements
To run this bot we recommend you a linux cloud instance with a minimum of:

2GB RAM
1GB disk space
2vCPU
Software requirements
Docker (Recommended)
Alternatively

Python 3.9+
pip (pip3)
git
TA-Lib
virtualenv (Recommended)
Support
Help / Discord
For any questions not covered by the documentation or for further information about
the bot, or to simply engage with like-minded individuals, we encourage you to join
the Freqtrade discord server.

Ready to try?
Begin by reading the installation guide for docker (recommended), or for
installation without docker.

Freqtrade basics
This page provides you some basic concepts on how Freqtrade works and operates.

Freqtrade terminology
Strategy: Your trading strategy, telling the bot what to do.
Trade: Open position.
Open Order: Order which is currently placed on the exchange, and is not yet
complete.
Pair: Tradable pair, usually in the format of Base/Quote (e.g. XRP/USDT for spot,
XRP/USDT:USDT for futures).
Timeframe: Candle length to use (e.g. "5m", "1h", ...).
Indicators: Technical indicators (SMA, EMA, RSI, ...).
Limit order: Limit orders which execute at the defined limit price or better.
Market order: Guaranteed to fill, may move price depending on the order size.
Current Profit: Currently pending (unrealized) profit for this trade. This is
mainly used throughout the bot and UI.
Realized Profit: Already realized profit. Only relevant in combination with partial
exits - which also explains the calculation logic for this.
Total Profit: Combined realized and unrealized profit. The relative number (%) is
calculated against the total investment in this trade.
Fee handling
All profit calculations of Freqtrade include fees. For Backtesting / Hyperopt /
Dry-run modes, the exchange default fee is used (lowest tier on the exchange). For
live operations, fees are used as applied by the exchange (this includes BNB
rebates etc.).

Pair naming
Freqtrade follows the ccxt naming convention for currencies. Using the wrong naming
convention in the wrong market will usually result in the bot not recognizing the
pair, usually resulting in errors like "this pair is not available".

Spot pair naming


For spot pairs, naming will be base/quote (e.g. ETH/USDT).

Futures pair naming


For futures pairs, naming will be base/quote:settle (e.g. ETH/USDT:USDT).

Bot execution logic


Starting freqtrade in dry-run or live mode (using freqtrade trade) will start the
bot and start the bot iteration loop. This will also run the bot_start() callback.

By default, the bot loop runs every few seconds (internals.process_throttle_secs)


and performs the following actions:

Fetch open trades from persistence.


Calculate current list of tradable pairs.
Download OHLCV data for the pairlist including all informative pairs
This step is only executed once per Candle to avoid unnecessary network traffic.
Call bot_loop_start() strategy callback.
Analyze strategy per pair.
Call populate_indicators()
Call populate_entry_trend()
Call populate_exit_trend()
Update trades open order state from exchange.
Call order_filled() strategy callback for filled orders.
Check timeouts for open orders.
Calls check_entry_timeout() strategy callback for open entry orders.
Calls check_exit_timeout() strategy callback for open exit orders.
Calls adjust_entry_price() strategy callback for open entry orders.
Verifies existing positions and eventually places exit orders.
Considers stoploss, ROI and exit-signal, custom_exit() and custom_stoploss().
Determine exit-price based on exit_pricing configuration setting or by using the
custom_exit_price() callback.
Before a exit order is placed, confirm_trade_exit() strategy callback is called.
Check position adjustments for open trades if enabled by calling
adjust_trade_position() and place additional order if required.
Check if trade-slots are still available (if max_open_trades is reached).
Verifies entry signal trying to enter new positions.
Determine entry-price based on entry_pricing configuration setting, or by using the
custom_entry_price() callback.
In Margin and Futures mode, leverage() strategy callback is called to determine the
desired leverage.
Determine stake size by calling the custom_stake_amount() callback.
Before an entry order is placed, confirm_trade_entry() strategy callback is called.
This loop will be repeated again and again until the bot is stopped.

Backtesting / Hyperopt execution logic


backtesting or hyperopt do only part of the above logic, since most of the trading
operations are fully simulated.

Load historic data for configured pairlist.


Calls bot_start() once.
Calculate indicators (calls populate_indicators() once per pair).
Calculate entry / exit signals (calls populate_entry_trend() and
populate_exit_trend() once per pair).
Loops per candle simulating entry and exit points.
Calls bot_loop_start() strategy callback.
Check for Order timeouts, either via the unfilledtimeout configuration, or via
check_entry_timeout() / check_exit_timeout() strategy callbacks.
Calls adjust_entry_price() strategy callback for open entry orders.
Check for trade entry signals (enter_long / enter_short columns).
Confirm trade entry / exits (calls confirm_trade_entry() and confirm_trade_exit()
if implemented in the strategy).
Call custom_entry_price() (if implemented in the strategy) to determine entry price
(Prices are moved to be within the opening candle).
In Margin and Futures mode, leverage() strategy callback is called to determine the
desired leverage.
Determine stake size by calling the custom_stake_amount() callback.
Check position adjustments for open trades if enabled and call
adjust_trade_position() to determine if an additional order is requested.
Call order_filled() strategy callback for filled entry orders.
Call custom_stoploss() and custom_exit() to find custom exit points.
For exits based on exit-signal, custom-exit and partial exits: Call
custom_exit_price() to determine exit price (Prices are moved to be within the
closing candle).
Call order_filled() strategy callback for filled exit orders.
Generate backtest report output
Note

Both Backtesting and Hyperopt include exchange default Fees in the calculation.
Custom fees can be passed to backtesting / hyperopt by specifying the --fee
argument.

Callback call frequency

Backtesting will call each callback at max. once per candle (--timeframe-detail
modifies this behavior to once per detailed candle). Most callbacks will be called
once per iteration in live (usually every ~5s) - which can cause backtesting
mismatches.

Configure the bot


Freqtrade has many configurable features and possibilities. By default, these
settings are configured via the configuration file (see below).
The Freqtrade configuration file
The bot uses a set of configuration parameters during its operation that all
together conform to the bot configuration. It normally reads its configuration from
a file (Freqtrade configuration file).

Per default, the bot loads the configuration from the [Link] file, located in
the current working directory.

You can specify a different configuration file used by the bot with the -c/--config
command-line option.

If you used the Quick start method for installing the bot, the installation script
should have already created the default configuration file ([Link]) for you.

If the default configuration file is not created we recommend to use freqtrade new-
config --config user_data/[Link] to generate a basic configuration file.

The Freqtrade configuration file is to be written in JSON format.

Additionally to the standard JSON syntax, you may use one-line // ... and multi-
line /* ... */ comments in your configuration files and trailing commas in the
lists of parameters.

Do not worry if you are not familiar with JSON format -- simply open the
configuration file with an editor of your choice, make some changes to the
parameters you need, save your changes and, finally, restart the bot or, if it was
previously stopped, run it again with the changes you made to the configuration.
The bot validates the syntax of the configuration file at startup and will warn you
if you made any errors editing it, pointing out problematic lines.

Environment variables
Set options in the Freqtrade configuration via environment variables. This takes
priority over the corresponding value in configuration or strategy.

Environment variables must be prefixed with FREQTRADE__ to be loaded to the


freqtrade configuration.

__ serves as level separator, so the format used should correspond to


FREQTRADE__{section}__{key}. As such - an environment variable defined as export
FREQTRADE__STAKE_AMOUNT=200 would result in {stake_amount: 200}.

A more complex example might be export FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>


to keep your exchange key secret. This will move the value to the [Link]
section of the configuration. Using this scheme, all configuration settings will
also be available as environment variables.

Please note that Environment variables will overwrite corresponding settings in


your configuration, but command line Arguments will always win.

Common example:

FREQTRADE__TELEGRAM__CHAT_ID=<telegramchatid>
FREQTRADE__TELEGRAM__TOKEN=<telegramToken>
FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>
FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
Note
Environment variables detected are logged at startup - so if you can't find why a
value is not what you think it should be based on the configuration, make sure it's
not loaded from an environment variable.

Validate combined result

You can use the show-config subcommand to see the final, combined configuration.

Loading sequence
Multiple configuration files
Multiple configuration files can be specified and used by the bot or the bot can
read its configuration parameters from the process standard input stream.

You can specify additional configuration files in add_config_files. Files specified


in this parameter will be loaded and merged with the initial config file. The files
are resolved relative to the initial configuration file. This is similar to using
multiple --config parameters, but simpler in usage as you don't have to specify all
files for all commands.

Validate combined result

You can use the show-config subcommand to see the final, combined configuration.

Use multiple configuration files to keep secrets secret

You can use a 2nd configuration file containing your secrets. That way you can
share your "primary" configuration file, while still keeping your API keys for
yourself. The 2nd file should only specify what you intend to override. If a key is
in more than one of the configurations, then the "last specified configuration"
wins (in the above example, [Link]).

For one-off commands, you can also use the below syntax by specifying multiple "--
config" parameters.

freqtrade trade --config user_data/[Link] --config user_data/config-


[Link] <...>
The below is equivalent to the example above - but having 2 configuration files in
the configuration, for easier reuse.

user_data/[Link]

"add_config_files": [
"[Link]",
"[Link]"
]

freqtrade trade --config user_data/[Link] <...>


config collision handling
Editor autocomplete and validation
If you are using an editor that supports JSON schema, you can use the schema
provided by Freqtrade to get autocompletion and validation of your configuration
file by adding the following line to the top of your configuration file:

{
"$schema": "[Link]
}
Develop version
Configuration parameters
The table below will list all configuration parameters available.

Freqtrade can also load many options via command line (CLI) arguments (check out
the commands --help output for details).

Configuration option prevalence


The prevalence for all Options is as follows:

CLI arguments override any other option


Environment Variables
Configuration files are used in sequence (the last file wins) and override Strategy
configurations.
Strategy configurations are only used if they are not set via configuration or
command-line arguments. These options are marked with Strategy Override in the
below table.
Parameters table
Mandatory parameters are marked as Required, which means that they are required to
be set in one of the possible ways.

Parameter Description
max_open_trades Required. Number of open trades your bot is allowed to have. Only
one open trade per pair is possible, so the length of your pairlist is another
limitation that can apply. If -1 then it is ignored (i.e. potentially unlimited
open trades, limited by the pairlist). More information below. Strategy Override.
Datatype: Positive integer or -1.
stake_currency Required. Crypto-currency used for trading.
Datatype: String
stake_amount Required. Amount of crypto-currency your bot will use for each
trade. Set it to "unlimited" to allow the bot to use all available balance. More
information below.
Datatype: Positive float or "unlimited".
tradable_balance_ratio Ratio of the total account balance the bot is allowed to
trade. More information below.
Defaults to 0.99 99%).
Datatype: Positive float between 0.1 and 1.0.
available_capital Available starting capital for the bot. Useful when running
multiple bots on the same exchange account. More information below.
Datatype: Positive float.
amend_last_stake_amount Use reduced last stake amount if necessary. More
information below.
Defaults to false.
Datatype: Boolean
last_stake_amount_min_ratio Defines minimum stake amount that has to be left and
executed. Applies only to the last stake amount when it's amended to a reduced
value (i.e. if amend_last_stake_amount is set to true). More information below.
Defaults to 0.5.
Datatype: Float (as ratio)
amount_reserve_percent Reserve some amount in min pair stake amount. The bot will
reserve amount_reserve_percent + stoploss value when calculating min pair stake
amount in order to avoid possible trade refusals.
Defaults to 0.05 (5%).
Datatype: Positive Float as ratio.
timeframe The timeframe to use (e.g 1m, 5m, 15m, 30m, 1h ...). Usually missing in
configuration, and specified in the strategy. Strategy Override.
Datatype: String
fiat_display_currency Fiat currency used to show your profits. More information
below.
Datatype: String
dry_run Required. Define if the bot must be in Dry Run or production mode.
Defaults to true.
Datatype: Boolean
dry_run_wallet Define the starting amount in stake currency for the simulated
wallet used by the bot running in Dry Run mode.
Defaults to 1000.
Datatype: Float
cancel_open_orders_on_exit Cancel open orders when the /stop RPC command is
issued, Ctrl+C is pressed or the bot dies unexpectedly. When set to true, this
allows you to use /stop to cancel unfilled and partially filled orders in the event
of a market crash. It does not impact open positions.
Defaults to false.
Datatype: Boolean
process_only_new_candles Enable processing of indicators only when new candles
arrive. If false each loop populates the indicators, this will mean the same candle
is processed many times creating system load but can be useful of your strategy
depends on tick data not only candle. Strategy Override.
Defaults to true.
Datatype: Boolean
minimal_roi Required. Set the threshold as ratio the bot will use to exit a trade.
More information below. Strategy Override.
Datatype: Dict
stoploss Required. Value as ratio of the stoploss used by the bot. More details
in the stoploss documentation. Strategy Override.
Datatype: Float (as ratio)
trailing_stop Enables trailing stoploss (based on stoploss in either
configuration or strategy file). More details in the stoploss documentation.
Strategy Override.
Datatype: Boolean
trailing_stop_positive Changes stoploss once profit has been reached. More details
in the stoploss documentation. Strategy Override.
Datatype: Float
trailing_stop_positive_offset Offset on when to apply trailing_stop_positive.
Percentage value which should be positive. More details in the stoploss
documentation. Strategy Override.
Defaults to 0.0 (no offset).
Datatype: Float
trailing_only_offset_is_reached Only apply trailing stoploss when the offset is
reached. stoploss documentation. Strategy Override.
Defaults to false.
Datatype: Boolean
fee Fee used during backtesting / dry-runs. Should normally not be configured,
which has freqtrade fall back to the exchange default fee. Set as ratio (e.g. 0.001
= 0.1%). Fee is applied twice for each trade, once when buying, once when selling.
Datatype: Float (as ratio)
futures_funding_rate User-specified funding rate to be used when historical
funding rates are not available from the exchange. This does not overwrite real
historical rates. It is recommended that this be set to 0 unless you are testing a
specific coin and you understand how the funding rate will affect freqtrade's
profit calculations. More information here
Defaults to None.
Datatype: Float
trading_mode Specifies if you want to trade regularly, trade with leverage, or
trade contracts whose prices are derived from matching cryptocurrency prices.
leverage documentation.
Defaults to "spot".
Datatype: String
margin_mode When trading with leverage, this determines if the collateral owned by
the trader will be shared or isolated to each trading pair leverage documentation.
Datatype: String
liquidation_buffer A ratio specifying how large of a safety net to place
between the liquidation price and the stoploss to prevent a position from reaching
the liquidation price leverage documentation.
Defaults to 0.05.
Datatype: Float
Unfilled timeout
[Link] Required. How long (in minutes or seconds) the bot will
wait for an unfilled entry order to complete, after which the order will be
cancelled and repeated at current (new) price, as long as there is a signal.
Strategy Override.
Datatype: Integer
[Link] Required. How long (in minutes or seconds) the bot will
wait for an unfilled exit order to complete, after which the order will be
cancelled and repeated at current (new) price, as long as there is a signal.
Strategy Override.
Datatype: Integer
[Link] Unit to use in unfilledtimeout setting. Note: If you set
[Link] to "seconds", "internals.process_throttle_secs" must be
inferior or equal to timeout Strategy Override.
Defaults to "minutes".
Datatype: String
unfilledtimeout.exit_timeout_count How many times can exit orders time out. Once
this number of timeouts is reached, an emergency exit is triggered. 0 to disable
and allow unlimited order cancels. Strategy Override.
Defaults to 0.
Datatype: Integer
Pricing
entry_pricing.price_side Select the side of the spread the bot should look at
to get the entry rate. More information below.
Defaults to "same".
Datatype: String (either ask, bid, same or other).
entry_pricing.price_last_balance Required. Interpolate the bidding price. More
information below.
entry_pricing.use_order_book Enable entering using the rates in Order Book Entry.
Defaults to true.
Datatype: Boolean
entry_pricing.order_book_top Bot will use the top N rate in Order Book
"price_side" to enter a trade. I.e. a value of 2 will allow the bot to pick the 2nd
entry in Order Book Entry.
Defaults to 1.
Datatype: Positive Integer
entry_pricing. check_depth_of_market.enabled Do not enter if the difference of
buy orders and sell orders is met in Order Book. Check market depth.
Defaults to false.
Datatype: Boolean
entry_pricing. check_depth_of_market.bids_to_ask_delta The difference ratio of
buy orders and sell orders found in Order Book. A value below 1 means sell order
size is greater, while value greater than 1 means buy order size is higher. Check
market depth
Defaults to 0.
Datatype: Float (as ratio)
exit_pricing.price_side Select the side of the spread the bot should look at to get
the exit rate. More information below.
Defaults to "same".
Datatype: String (either ask, bid, same or other).
exit_pricing.price_last_balance Interpolate the exiting price. More information
below.
exit_pricing.use_order_book Enable exiting of open trades using Order Book Exit.
Defaults to true.
Datatype: Boolean
exit_pricing.order_book_top Bot will use the top N rate in Order Book
"price_side" to exit. I.e. a value of 2 will allow the bot to pick the 2nd ask rate
in Order Book Exit
Defaults to 1.
Datatype: Positive Integer
custom_price_max_distance_ratio Configure maximum distance ratio between
current and custom entry or exit price.
Defaults to 0.02 2%).
Datatype: Positive float
TODO
use_exit_signal Use exit signals produced by the strategy in addition to the
minimal_roi.
Setting this to false disables the usage of "exit_long" and "exit_short" columns.
Has no influence on other exit methods (Stoploss, ROI, callbacks). Strategy
Override.
Defaults to true.
Datatype: Boolean
exit_profit_only Wait until the bot reaches exit_profit_offset before taking an
exit decision. Strategy Override.
Defaults to false.
Datatype: Boolean
exit_profit_offset Exit-signal is only active above this value. Only active in
combination with exit_profit_only=True. Strategy Override.
Defaults to 0.0.
Datatype: Float (as ratio)
ignore_roi_if_entry_signal Do not exit if the entry signal is still active. This
setting takes preference over minimal_roi and use_exit_signal. Strategy Override.
Defaults to false.
Datatype: Boolean
ignore_buying_expired_candle_after Specifies the number of seconds until a buy
signal is no longer used.
Datatype: Integer
order_types Configure order-types depending on the action ("entry", "exit",
"stoploss", "stoploss_on_exchange"). More information below. Strategy Override.
Datatype: Dict
order_time_in_force Configure time in force for entry and exit orders. More
information below. Strategy Override.
Datatype: Dict
position_adjustment_enable Enables the strategy to use position adjustments
(additional buys or sells). More information here.
Strategy Override.
Defaults to false.
Datatype: Boolean
max_entry_position_adjustment Maximum additional order(s) for each open trade on
top of the first entry Order. Set it to -1 for unlimited additional orders. More
information here.
Strategy Override.
Defaults to -1.
Datatype: Positive Integer or -1
Exchange
[Link] Required. Name of the exchange class to use.
Datatype: String
[Link] API key to use for the exchange. Only required when you are in
production mode.
Keep it in secret, do not disclose publicly.
Datatype: String
[Link] API secret to use for the exchange. Only required when you are in
production mode.
Keep it in secret, do not disclose publicly.
Datatype: String
[Link] API password to use for the exchange. Only required when you are
in production mode and for exchanges that use password for API requests.
Keep it in secret, do not disclose publicly.
Datatype: String
[Link] API uid to use for the exchange. Only required when you are in
production mode and for exchanges that use uid for API requests.
Keep it in secret, do not disclose publicly.
Datatype: String
exchange.pair_whitelist List of pairs to use by the bot for trading and to check
for potential trades during backtesting. Supports regex pairs as .*/BTC. Not used
by VolumePairList. More information.
Datatype: List
exchange.pair_blacklist List of pairs the bot must absolutely avoid for trading and
backtesting. More information.
Datatype: List
exchange.ccxt_config Additional CCXT parameters passed to both ccxt instances
(sync and async). This is usually the correct place for additional ccxt
configurations. Parameters may differ from exchange to exchange and are documented
in the ccxt documentation. Please avoid adding exchange secrets here (use the
dedicated fields instead), as they may be contained in logs.
Datatype: Dict
exchange.ccxt_sync_config Additional CCXT parameters passed to the regular
(sync) ccxt instance. Parameters may differ from exchange to exchange and are
documented in the ccxt documentation
Datatype: Dict
exchange.ccxt_async_config Additional CCXT parameters passed to the async ccxt
instance. Parameters may differ from exchange to exchange and are documented in the
ccxt documentation
Datatype: Dict
exchange.enable_ws Enable the usage of Websockets for the exchange.
More information.
Defaults to true.
Datatype: Boolean
exchange.markets_refresh_interval The interval in minutes in which markets are
reloaded.
Defaults to 60 minutes.
Datatype: Positive Integer
exchange.skip_pair_validation Skip pairlist validation on startup.
Defaults to false
Datatype: Boolean
exchange.skip_open_order_update Skips open order updates on startup should the
exchange cause problems. Only relevant in live conditions.
Defaults to false
Datatype: Boolean
exchange.unknown_fee_rate Fallback value to use when calculating trading fees.
This can be useful for exchanges which have fees in non-tradable currencies. The
value provided here will be multiplied with the "fee cost".
Defaults to None
Datatype:* float
exchange.log_responses Log relevant exchange responses. For debug mode only - use
with care.
Defaults to false
Datatype: Boolean
experimental.block_bad_exchanges Block exchanges known to not work with
freqtrade. Leave on default unless you want to test if that exchange works now.
Defaults to true.
Datatype: Boolean
Plugins
edge.* Please refer to edge configuration document for detailed explanation of
all possible configuration options.
pairlists Define one or more pairlists to be used. More information.
Defaults to StaticPairList.
Datatype: List of Dicts
protections Define one or more protections to be used. More information.
Datatype: List of Dicts
Telegram
[Link] Enable the usage of Telegram.
Datatype: Boolean
[Link] Your Telegram bot token. Only required if [Link] is
true.
Keep it in secret, do not disclose publicly.
Datatype: String
telegram.chat_id Your personal Telegram account id. Only required if
[Link] is true.
Keep it in secret, do not disclose publicly.
Datatype: String
telegram.balance_dust_level Dust-level (in stake currency) - currencies with a
balance below this will not be shown by /balance.
Datatype: float
[Link] Allow "reload" buttons on telegram messages.
Defaults to true.
Datatype:* boolean
telegram.notification_settings.* Detailed notification settings. Refer to the
telegram documentation for details.
Datatype: dictionary
telegram.allow_custom_messages Enable the sending of Telegram messages from
strategies via the dataprovider.send_msg() function.
Datatype: Boolean
Webhook
[Link] Enable usage of Webhook notifications
Datatype: Boolean
[Link] URL for the webhook. Only required if [Link] is true. See the
webhook documentation for more details.
Datatype: String
[Link] Payload to send on entry. Only required if [Link] is
true. See the webhook documentation for more details.
Datatype: String
webhook.entry_cancel Payload to send on entry order cancel. Only required if
[Link] is true. See the webhook documentation for more details.
Datatype: String
webhook.entry_fill Payload to send on entry order filled. Only required if
[Link] is true. See the webhook documentation for more details.
Datatype: String
[Link] Payload to send on exit. Only required if [Link] is
true. See the webhook documentation for more details.
Datatype: String
webhook.exit_cancel Payload to send on exit order cancel. Only required if
[Link] is true. See the webhook documentation for more details.
Datatype: String
webhook.exit_fill Payload to send on exit order filled. Only required if
[Link] is true. See the webhook documentation for more details.
Datatype: String
[Link] Payload to send on status calls. Only required if [Link]
is true. See the webhook documentation for more details.
Datatype: String
webhook.allow_custom_messages Enable the sending of Webhook messages from
strategies via the dataprovider.send_msg() function.
Datatype: Boolean
Rest API / FreqUI / Producer-Consumer
api_server.enabled Enable usage of API Server. See the API Server
documentation for more details.
Datatype: Boolean
api_server.listen_ip_address Bind IP address. See the API Server documentation for
more details.
Datatype: IPv4
api_server.listen_port Bind Port. See the API Server documentation for more
details.
Datatype: Integer between 1024 and 65535
api_server.verbosity Logging verbosity. info will print all RPC Calls, while
"error" will only display errors.
Datatype: Enum, either info or error. Defaults to info.
api_server.username Username for API server. See the API Server documentation
for more details.
Keep it in secret, do not disclose publicly.
Datatype: String
api_server.password Password for API server. See the API Server documentation
for more details.
Keep it in secret, do not disclose publicly.
Datatype: String
api_server.ws_token API token for the Message WebSocket. See the API Server
documentation for more details.
Keep it in secret, do not disclose publicly.
Datatype: String
bot_name Name of the bot. Passed via API to a client - can be shown to
distinguish / name bots.
Defaults to freqtrade
Datatype: String
external_message_consumer Enable Producer/Consumer mode for more details.
Datatype: Dict
Other
initial_state Defines the initial application state. If set to stopped, then
the bot has to be explicitly started via /start RPC command.
Defaults to stopped.
Datatype: Enum, either stopped or running
force_entry_enable Enables the RPC Commands to force a Trade entry. More
information below.
Datatype: Boolean
disable_dataframe_checks Disable checking the OHLCV dataframe returned from
the strategy methods for correctness. Only use when intentionally changing the
dataframe and understand what you are doing. Strategy Override.
Defaults to False.
Datatype: Boolean
internals.process_throttle_secs Set the process throttle, or minimum loop
duration for one bot iteration loop. Value in second.
Defaults to 5 seconds.
Datatype: Positive Integer
internals.heartbeat_interval Print heartbeat message every N seconds. Set to 0 to
disable heartbeat messages.
Defaults to 60 seconds.
Datatype: Positive Integer or 0
internals.sd_notify Enables use of the sd_notify protocol to tell systemd
service manager about changes in the bot state and issue keep-alive pings. See here
for more details.
Datatype: Boolean
strategy Required Defines Strategy class to use. Recommended to be set via --
strategy NAME.
Datatype: ClassName
strategy_path Adds an additional strategy lookup path (must be a directory).
Datatype: String
recursive_strategy_search Set to true to recursively search sub-directories
inside user_data/strategies for a strategy.
Datatype: Boolean
user_data_dir Directory containing user data.
Defaults to ./user_data/.
Datatype: String
db_url Declares database URL to use. NOTE: This defaults to
sqlite:///[Link] if dry_run is true, and to
sqlite:///[Link] for production instances.
Datatype: String, SQLAlchemy connect string
logfile Specifies logfile name. Uses a rolling strategy for log file rotation
for 10 files with the 1MB limit per file.
Datatype: String
add_config_files Additional config files. These files will be loaded and merged
with the current config file. The files are resolved relative to the initial file.
Defaults to [].
Datatype: List of strings
dataformat_ohlcv Data format to use to store historical candle (OHLCV) data.
Defaults to feather.
Datatype: String
dataformat_trades Data format to use to store historical trades data.
Defaults to feather.
Datatype: String
reduce_df_footprint Recast all numeric columns to float32/int32, with the
objective of reducing ram/disk usage (and decreasing train/inference timing in
FreqAI). (Currently only affects FreqAI use-cases)
Datatype: Boolean.
Default: False.
Parameters in the strategy
The following parameters can be set in the configuration file or strategy. Values
set in the configuration file always overwrite values set in the strategy.

minimal_roi
timeframe
stoploss
max_open_trades
trailing_stop
trailing_stop_positive
trailing_stop_positive_offset
trailing_only_offset_is_reached
use_custom_stoploss
process_only_new_candles
order_types
order_time_in_force
unfilledtimeout
disable_dataframe_checks
use_exit_signal
exit_profit_only
exit_profit_offset
ignore_roi_if_entry_signal
ignore_buying_expired_candle_after
position_adjustment_enable
max_entry_position_adjustment
Configuring amount per trade
There are several methods to configure how much of the stake currency the bot will
use to enter a trade. All methods respect the available balance configuration as
explained below.

Minimum trade stake


The minimum stake amount will depend on exchange and pair and is usually listed in
the exchange support pages.

Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange),
and the price is 0.6\(, the minimum stake amount to buy this pair is 20 * 0.6 ~=
12. This exchange has also a limit on USD - where all orders must be > 10\) - which
however does not apply in this case.

To guarantee safe execution, freqtrade will not allow buying with a stake-amount of
10.1$, instead, it'll make sure that there's enough space to place a stoploss below
the pair (+ an offset, defined by amount_reserve_percent, which defaults to 5%).

With a reserve of 5%, the minimum stake amount would be ~12.6$ (12 * (1 + 0.05)).
If we take into account a stoploss of 10% on top of that - we'd end up with a value
of ~14$ (12.6 / (1 - 0.1)).

To limit this calculation in case of large stoploss values, the calculated minimum
stake-limit will never be more than 50% above the real limit.

Warning

Since the limits on exchanges are usually stable and are not updated often, some
pairs can show pretty high minimum limits, simply because the price increased a lot
since the last limit adjustment by the exchange. Freqtrade adjusts the stake-amount
to this value, unless it's > 30% more than the calculated/desired stake-amount - in
which case the trade is rejected.

Tradable balance
By default, the bot assumes that the complete amount - 1% is at it's disposal, and
when using dynamic stake amount, it will split the complete balance into
max_open_trades buckets per trade. Freqtrade will reserve 1% for eventual fees when
entering a trade and will therefore not touch that by default.

You can configure the "untouched" amount by using the tradable_balance_ratio


setting.

For example, if you have 10 ETH available in your wallet on the exchange and
tradable_balance_ratio=0.5 (which is 50%), then the bot will use a maximum amount
of 5 ETH for trading and considers this as an available balance. The rest of the
wallet is untouched by the trades.

Danger

This setting should not be used when running multiple bots on the same account.
Please look at Available Capital to the bot instead.

Warning

The tradable_balance_ratio setting applies to the current balance (free balance +


tied up in trades). Therefore, assuming the starting balance of 1000, a
configuration with tradable_balance_ratio=0.99 will not guarantee that 10 currency
units will always remain available on the exchange. For example, the free amount
may reduce to 5 units if the total balance is reduced to 500 (either by a losing
streak or by withdrawing balance).
Assign available Capital
To fully utilize compounding profits when using multiple bots on the same exchange
account, you'll want to limit each bot to a certain starting balance. This can be
accomplished by setting available_capital to the desired starting balance.

Assuming your account has 10000 USDT and you want to run 2 different strategies on
this exchange. You'd set available_capital=5000 - granting each bot an initial
capital of 5000 USDT. The bot will then split this starting balance equally into
max_open_trades buckets. Profitable trades will result in increased stake-sizes for
this bot - without affecting the stake-sizes of the other bot.

Adjusting available_capital requires reloading the configuration to take effect.


Adjusting the available_capital adds the difference between the previous
available_capital and the new available_capital. Decreasing the available capital
when trades are open doesn't exit the trades. The difference is returned to the
wallet when the trades conclude. The outcome of this differs depending on the price
movement between the adjustment and exiting the trades.

Incompatible with tradable_balance_ratio

Setting this option will replace any configuration of tradable_balance_ratio.

Amend last stake amount


Assuming we have the tradable balance of 1000 USDT, stake_amount=400, and
max_open_trades=3. The bot would open 2 trades and will be unable to fill the last
trading slot, since the requested 400 USDT are no longer available since 800 USDT
are already tied in other trades.

To overcome this, the option amend_last_stake_amount can be set to True, which will
enable the bot to reduce stake_amount to the available balance to fill the last
trade slot.

In the example above this would mean:

Trade1: 400 USDT


Trade2: 400 USDT
Trade3: 200 USDT
Note

This option only applies with Static stake amount - since Dynamic stake amount
divides the balances evenly.

Note

The minimum last stake amount can be configured using last_stake_amount_min_ratio -


which defaults to 0.5 (50%). This means that the minimum stake amount that's ever
used is stake_amount * 0.5. This avoids very low stake amounts, that are close to
the minimum tradable amount for the pair and can be refused by the exchange.

Static stake amount


The stake_amount configuration statically configures the amount of stake-currency
your bot will use for each trade.

The minimal configuration value is 0.0001, however, please check your exchange's
trading minimums for the stake currency you're using to avoid problems.

This setting works in combination with max_open_trades. The maximum capital engaged
in trades is stake_amount * max_open_trades. For example, the bot will at most use
(0.05 BTC x 3) = 0.15 BTC, assuming a configuration of max_open_trades=3 and
stake_amount=0.05.

Note

This setting respects the available balance configuration.

Dynamic stake amount


Alternatively, you can use a dynamic stake amount, which will use the available
balance on the exchange, and divide that equally by the number of allowed trades
(max_open_trades).

To configure this, set stake_amount="unlimited". We also recommend to set


tradable_balance_ratio=0.99 (99%) - to keep a minimum balance for eventual fees.

In this case a trade amount is calculated as:

currency_balance / (max_open_trades - current_open_trades)


To allow the bot to trade all the available stake_currency in your account (minus
tradable_balance_ratio) set

"stake_amount" : "unlimited",
"tradable_balance_ratio": 0.99,
Compounding profits

This configuration will allow increasing/decreasing stakes depending on the


performance of the bot (lower stake if the bot is losing, higher stakes if the bot
has a winning record since higher balances are available), and will result in
profit compounding.

When using Dry-Run Mode

When using "stake_amount" : "unlimited", in combination with Dry-Run, Backtesting


or Hyperopt, the balance will be simulated starting with a stake of dry_run_wallet
which will evolve. It is therefore important to set dry_run_wallet to a sensible
value (like 0.05 or 0.01 for BTC and 1000 or 100 for USDT, for example), otherwise,
it may simulate trades with 100 BTC (or more) or 0.05 USDT (or less) at once -
which may not correspond to your real available balance or is less than the
exchange minimal limit for the order amount for the stake currency.

Dynamic stake amount with position adjustment


When you want to use position adjustment with unlimited stakes, you must also
implement custom_stake_amount to a return a value depending on your strategy.
Typical value would be in the range of 25% - 50% of the proposed stakes, but
depends highly on your strategy and how much you wish to leave into the wallet as
position adjustment buffer.

For example if your position adjustment assumes it can do 2 additional buys with
the same stake amounts then your buffer should be 66.6667% of the initially
proposed unlimited stake amount.

Or another example if your position adjustment assumes it can do 1 additional buy


with 3x the original stake amount then custom_stake_amount should return 25% of
proposed stake amount and leave 75% for possible later position adjustments.

Prices used for orders


Prices for regular orders can be controlled via the parameter structures
entry_pricing for trade entries and exit_pricing for trade exits. Prices are always
retrieved right before an order is placed, either by querying the exchange tickers
or by using the orderbook data.

Note

Orderbook data used by Freqtrade are the data retrieved from exchange by the ccxt's
function fetch_order_book(), i.e. are usually data from the L2-aggregated
orderbook, while the ticker data are the structures returned by the ccxt's
fetch_ticker()/fetch_tickers() functions. Refer to the ccxt library documentation
for more details.

Using market orders

Please read the section Market order pricing section when using market orders.

Entry price
Enter price side
The configuration setting entry_pricing.price_side defines the side of the
orderbook the bot looks for when buying.

The following displays an orderbook.

...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
If entry_pricing.price_side is set to "bid", then the bot will use 99 as entry
price.
In line with that, if entry_pricing.price_side is set to "ask", then the bot will
use 101 as entry price.

Depending on the order direction (long/short), this will lead to different results.
Therefore we recommend to use "same" or "other" for this configuration instead.
This would result in the following pricing matrix:

direction Order setting price crosses spread


long buy ask 101 yes
long buy bid 99 no
long buy same 99 no
long buy other 101 yes
short sell ask 101 no
short sell bid 99 yes
short sell same 101 no
short sell other 99 yes
Using the other side of the orderbook often guarantees quicker filled orders, but
the bot can also end up paying more than what would have been necessary. Taker fees
instead of maker fees will most likely apply even when using limit buy orders.
Also, prices at the "other" side of the spread are higher than prices at the "bid"
side in the orderbook, so the order behaves similar to a market order (however with
a maximum price).

Entry price with Orderbook enabled


When entering a trade with the orderbook enabled
(entry_pricing.use_order_book=True), Freqtrade fetches the
entry_pricing.order_book_top entries from the orderbook and uses the entry
specified as entry_pricing.order_book_top on the configured side
(entry_pricing.price_side) of the orderbook. 1 specifies the topmost entry in the
orderbook, while 2 would use the 2nd entry in the orderbook, and so on.

Entry price without Orderbook enabled


The following section uses side as the configured entry_pricing.price_side
(defaults to "same").

When not using orderbook (entry_pricing.use_order_book=False), Freqtrade uses the


best side price from the ticker if it's below the last traded price from the
ticker. Otherwise (when the side price is above the last price), it calculates a
rate between side and last price based on entry_pricing.price_last_balance.

The entry_pricing.price_last_balance configuration parameter controls this. A value


of 0.0 will use side price, while 1.0 will use the last price and values between
those interpolate between ask and last price.

Check depth of market


When check depth of market is enabled
(entry_pricing.check_depth_of_market.enabled=True), the entry signals are filtered
based on the orderbook depth (sum of all amounts) for each orderbook side.

Orderbook bid (buy) side depth is then divided by the orderbook ask (sell) side
depth and the resulting delta is compared to the value of the
entry_pricing.check_depth_of_market.bids_to_ask_delta parameter. The entry order is
only executed if the orderbook delta is greater than or equal to the configured
delta value.

Note

A delta value below 1 means that ask (sell) orderbook side depth is greater than
the depth of the bid (buy) orderbook side, while a value greater than 1 means
opposite (depth of the buy side is higher than the depth of the sell side).

Exit price
Exit price side
The configuration setting exit_pricing.price_side defines the side of the spread
the bot looks for when exiting a trade.

The following displays an orderbook:

...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
If exit_pricing.price_side is set to "ask", then the bot will use 101 as exiting
price.
In line with that, if exit_pricing.price_side is set to "bid", then the bot will
use 99 as exiting price.
Depending on the order direction (long/short), this will lead to different results.
Therefore we recommend to use "same" or "other" for this configuration instead.
This would result in the following pricing matrix:

Direction Order setting price crosses spread


long sell ask 101 no
long sell bid 99 yes
long sell same 101 no
long sell other 99 yes
short buy ask 101 yes
short buy bid 99 no
short buy same 99 no
short buy other 101 yes
Exit price with Orderbook enabled
When exiting with the orderbook enabled (exit_pricing.use_order_book=True),
Freqtrade fetches the exit_pricing.order_book_top entries in the orderbook and uses
the entry specified as exit_pricing.order_book_top from the configured side
(exit_pricing.price_side) as trade exit price.

1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in
the orderbook, and so on.

Exit price without Orderbook enabled


The following section uses side as the configured exit_pricing.price_side (defaults
to "ask").

When not using orderbook (exit_pricing.use_order_book=False), Freqtrade uses the


best side price from the ticker if it's above the last traded price from the
ticker. Otherwise (when the side price is below the last price), it calculates a
rate between side and last price based on exit_pricing.price_last_balance.

The exit_pricing.price_last_balance configuration parameter controls this. A value


of 0.0 will use side price, while 1.0 will use the last price and values between
those interpolate between side and last price.

Market order pricing


When using market orders, prices should be configured to use the "correct" side of
the orderbook to allow realistic pricing detection. Assuming both entry and exits
are using market orders, a configuration similar to the following must be used

"order_types": {
"entry": "market",
"exit": "market"
// ...
},
"entry_pricing": {
"price_side": "other",
// ...
},
"exit_pricing":{
"price_side": "other",
// ...
},
Obviously, if only one side is using limit orders, different pricing combinations
can be used.

Further Configuration details


Understand minimal_roi
The minimal_roi configuration parameter is a JSON object where the key is a
duration in minutes and the value is the minimum ROI as a ratio. See the example
below:

"minimal_roi": {
"40": 0.0, # Exit after 40 minutes if the profit is not negative
"30": 0.01, # Exit after 30 minutes if there is at least 1% profit
"20": 0.02, # Exit after 20 minutes if there is at least 2% profit
"0": 0.04 # Exit immediately if there is at least 4% profit
},
Most of the strategy files already include the optimal minimal_roi value. This
parameter can be set in either Strategy or Configuration file. If you use it in the
configuration file, it will override the minimal_roi value from the strategy file.
If it is not set in either Strategy or Configuration, a default of 1000% {"0": 10}
is used, and minimal ROI is disabled unless your trade generates 1000% profit.

Special case to forceexit after a specific time

A special case presents using "<N>": -1 as ROI. This forces the bot to exit a trade
after N Minutes, no matter if it's positive or negative, so represents a time-
limited force-exit.

Understand force_entry_enable
The force_entry_enable configuration parameter enables the usage of force-enter
(/forcelong, /forceshort) commands via Telegram and REST API. For security reasons,
it's disabled by default, and freqtrade will show a warning message on startup if
enabled. For example, you can send /forceenter ETH/BTC to the bot, which will
result in freqtrade buying the pair and holds it until a regular exit-signal (ROI,
stoploss, /forceexit) appears.

This can be dangerous with some strategies, so use with care.

See the telegram documentation for details on usage.

Ignoring expired candles


When working with larger timeframes (for example 1h or more) and using a low
max_open_trades value, the last candle can be processed as soon as a trade slot
becomes available. When processing the last candle, this can lead to a situation
where it may not be desirable to use the buy signal on that candle. For example,
when using a condition in your strategy where you use a cross-over, that point may
have passed too long ago for you to start a trade on it.

In these situations, you can enable the functionality to ignore candles that are
beyond a specified period by setting ignore_buying_expired_candle_after to a
positive number, indicating the number of seconds after which the buy signal
becomes expired.

For example, if your strategy is using a 1h timeframe, and you only want to buy
within the first 5 minutes when a new candle comes in, you can add the following
configuration to your strategy:

{
//...
"ignore_buying_expired_candle_after": 300,
// ...
}
Note
This setting resets with each new candle, so it will not prevent sticking-signals
from executing on the 2nd or 3rd candle they're active. Best use a "trigger"
selector for buy signals, which are only active for one candle.

Understand order_types
The order_types configuration parameter maps actions (entry, exit, stoploss,
emergency_exit, force_exit, force_entry) to order-types (market, limit, ...) as
well as configures stoploss to be on the exchange and defines stoploss on exchange
update interval in seconds.

This allows to enter using limit orders, exit using limit-orders, and create
stoplosses using market orders. It also allows to set the stoploss "on exchange"
which means stoploss order would be placed immediately once the buy order is
fulfilled.

order_types set in the configuration file overwrites values set in the strategy as
a whole, so you need to configure the whole order_types dictionary in one place.

If this is configured, the following 4 values (entry, exit, stoploss and


stoploss_on_exchange) need to be present, otherwise, the bot will fail to start.

For information on (emergency_exit,force_exit, force_entry,


stoploss_on_exchange,stoploss_on_exchange_interval,stoploss_on_exchange_limit_ratio
) please see stop loss documentation stop loss on exchange

Syntax for Strategy:

order_types = {
"entry": "limit",
"exit": "limit",
"emergency_exit": "market",
"force_entry": "market",
"force_exit": "market",
"stoploss": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60,
"stoploss_on_exchange_limit_ratio": 0.99,
}
Configuration:

"order_types": {
"entry": "limit",
"exit": "limit",
"emergency_exit": "market",
"force_entry": "market",
"force_exit": "market",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
}
Market order support

Not all exchanges support "market" orders. The following message will be shown if
your exchange does not support market orders: "Exchange <yourexchange> does not
support market orders." and the bot will refuse to start.
Using market orders

Please carefully read the section Market order pricing section when using market
orders.

Stoploss on exchange

order_types.stoploss_on_exchange_interval is not mandatory. Do not change its value


if you are unsure of what you are doing. For more information about how stoploss
works please refer to the stoploss documentation.

If order_types.stoploss_on_exchange is enabled and the stoploss is cancelled


manually on the exchange, then the bot will create a new stoploss order.

Warning: order_types.stoploss_on_exchange failures

If stoploss on exchange creation fails for some reason, then an "emergency exit" is
initiated. By default, this will exit the trade using a market order. The order-
type for the emergency-exit can be changed by setting the emergency_exit value in
the order_types dictionary - however, this is not advised.

Understand order_time_in_force
The order_time_in_force configuration parameter defines the policy by which the
order is executed on the exchange. Three commonly used time in force are:

GTC (Good Till Canceled):

This is most of the time the default time in force. It means the order will remain
on exchange till it is cancelled by the user. It can be fully or partially
fulfilled. If partially fulfilled, the remaining will stay on the exchange till
cancelled.

FOK (Fill Or Kill):

It means if the order is not executed immediately AND fully then it is cancelled by
the exchange.

IOC (Immediate Or Canceled):

It is the same as FOK (above) except it can be partially fulfilled. The remaining
part is automatically cancelled by the exchange.

PO (Post only):

Post only order. The order is either placed as a maker order, or it is canceled.
This means the order must be placed on orderbook for at least time in an unfilled
state.

time_in_force config
The order_time_in_force parameter contains a dict with entry and exit time in force
policy values. This can be set in the configuration file or in the strategy. Values
set in the configuration file overwrites values set in the strategy.

The possible values are: GTC (default), FOK or IOC.

"order_time_in_force": {
"entry": "GTC",
"exit": "GTC"
},
Warning

This is ongoing work. For now, it is supported only for binance, gate and kucoin.
Please don't change the default value unless you know what you are doing and have
researched the impact of using different values for your particular exchange.

Fiat conversion
Freqtrade uses the Coingecko API to convert the coin value to it's corresponding
fiat value for the Telegram reports. The FIAT currency can be set in the
configuration file as fiat_display_currency.

Removing fiat_display_currency completely from the configuration will skip


initializing coingecko, and will not show any FIAT currency conversion. This has no
importance for the correct functioning of the bot.

What values can be used for fiat_display_currency?


The fiat_display_currency configuration parameter sets the base currency to use for
the conversion from coin to fiat in the bot Telegram reports.

The valid values are:

"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF",
"IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD"
In addition to fiat currencies, a range of crypto currencies is supported.

The valid values are:

"BTC", "ETH", "XRP", "LTC", "BCH", "BNB"


Coingecko Rate limit problems
On some IP ranges, coingecko is heavily rate-limiting. In such cases, you may want
to add your coingecko API key to the configuration.

{
"fiat_display_currency": "USD",
"coingecko": {
"api_key": "your-api",
"is_demo": true
}
}
Freqtrade supports both Demo and Pro coingecko API keys.

The Coingecko API key is NOT required for the bot to function correctly. It is only
used for the conversion of coin to fiat in the Telegram reports, which usually also
work without API key.

Consuming exchange Websockets


Freqtrade can consume websockets through [Link].

Freqtrade aims ensure data is available at all times. Should the websocket
connection fail (or be disabled), the bot will fall back to REST API calls.

Should you experience problems you suspect are caused by websockets, you can
disable these via the setting exchange.enable_ws, which defaults to true.
"exchange": {
// ...
"enable_ws": false,
// ...
}
Should you be required to use a proxy, please refer to the proxy section for more
information.

Rollout

We're implementing this out slowly, ensuring stability of your bots. Currently,
usage is limited to ohlcv data streams. It's also limited to a few exchanges, with
new exchanges being added on an ongoing basis.

Using Dry-run mode


We recommend starting the bot in the Dry-run mode to see how your bot will behave
and what is the performance of your strategy. In the Dry-run mode, the bot does not
engage your money. It only runs a live simulation without creating trades on the
exchange.

Edit your [Link] configuration file.


Switch dry-run to true and specify db_url for a persistence database.

"dry_run": true,
"db_url": "sqlite:///[Link]",
Remove your Exchange API key and secret (change them by empty values or fake
credentials):

"exchange": {
"name": "binance",
"key": "key",
"secret": "secret",
...
}
Once you will be happy with your bot performance running in the Dry-run mode, you
can switch it to production mode.

Note

A simulated wallet is available during dry-run mode and will assume a starting
capital of dry_run_wallet (defaults to 1000).

Considerations for dry-run


API-keys may or may not be provided. Only Read-Only operations (i.e. operations
that do not alter account state) on the exchange are performed in dry-run mode.
Wallets (/balance) are simulated based on dry_run_wallet.
Orders are simulated, and will not be posted to the exchange.
Market orders fill based on orderbook volume the moment the order is placed, with a
maximum slippage of 5%.
Limit orders fill once the price reaches the defined level - or time out based on
unfilledtimeout settings.
Limit orders will be converted to market orders if they cross the price by more
than 1%, and will be filled immediately based regular market order rules (see point
about Market orders above).
In combination with stoploss_on_exchange, the stop_loss price is assumed to be
filled.
Open orders (not trades, which are stored in the database) are kept open after bot
restarts, with the assumption that they were not filled while being offline.
Switch to production mode
In production mode, the bot will engage your money. Be careful, since a wrong
strategy can lose all your money. Be aware of what you are doing when you run it in
production mode.

When switching to Production mode, please make sure to use a different / fresh
database to avoid dry-run trades messing with your exchange money and eventually
tainting your statistics.

Setup your exchange account


You will need to create API Keys (usually you get key and secret, some exchanges
require an additional password) from the Exchange website and you'll need to insert
this into the appropriate fields in the configuration or when asked by the
freqtrade new-config command. API Keys are usually only required for live trading
(trading for real money, bot running in "production mode", executing real orders on
the exchange) and are not required for the bot running in dry-run (trade
simulation) mode. When you set up the bot in dry-run mode, you may fill these
fields with empty values.

To switch your bot in production mode


Edit your [Link] file.

Switch dry-run to false and don't forget to adapt your database URL if set:

"dry_run": false,
Insert your Exchange API key (change them by fake API keys):

{
"exchange": {
"name": "binance",
"key": "af8ddd35195e9dc500b9a6f799f6f5c93d89193b",
"secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5",
//"password": "", // Optional, not needed by all exchanges)
// ...
}
//...
}
You should also make sure to read the Exchanges section of the documentation to be
aware of potential configuration details specific to your exchange.

Keep your secrets secret

To keep your secrets secret, we recommend using a 2nd configuration for your API
keys. Simply use the above snippet in a new configuration file (e.g. config-
[Link]) and keep your settings in this file. You can then start the bot with
freqtrade trade --config user_data/[Link] --config user_data/config-
[Link] <...> to have your keys loaded.

NEVER share your private configuration file or your exchange keys with anyone!

Using a proxy with Freqtrade


To use a proxy with freqtrade, export your proxy settings using the variables
"HTTP_PROXY" and "HTTPS_PROXY" set to the appropriate values. This will have the
proxy settings applied to everything (telegram, coingecko, ...) except for exchange
requests.
export HTTP_PROXY="[Link]
export HTTPS_PROXY="[Link]
freqtrade
Proxy exchange requests
To use a proxy for exchange connections - you will have to define the proxies as
part of the ccxt configuration.

{
"exchange": {
"ccxt_config": {
"httpsProxy": "[Link]
"wsProxy": "[Link]
}
}
}
For more information on available proxy types, please consult the ccxt proxy
documentation.

Next step
Now you have configured your [Link], the next step is to start your bot.

Strategy Customization
This page explains how to customize your strategies, add new indicators and set up
trading rules.

Please familiarize yourself with Freqtrade basics first, which provides overall
info on how the bot operates.

Develop your own strategy


The bot includes a default strategy file. Also, several other strategies are
available in the strategy repository.

You will however most likely have your own idea for a strategy. This document
intends to help you convert your strategy idea into your own strategy.

To get started, use freqtrade new-strategy --strategy AwesomeStrategy (you can


obviously use your own naming for your strategy). This will create a new strategy
file from a template, which will be located under
user_data/strategies/[Link].

Note

This is just a template file, which will most likely not be profitable out of the
box.

Different template levels


Anatomy of a strategy
A strategy file contains all the information needed to build a good strategy:

Indicators
Entry strategy rules
Exit strategy rules
Minimal ROI recommended
Stoploss strongly recommended
The bot also include a sample strategy called SampleStrategy you can update:
user_data/strategies/sample_strategy.py. You can test it with the parameter: --
strategy SampleStrategy
Additionally, there is an attribute called INTERFACE_VERSION, which defines the
version of the strategy interface the bot should use. The current version is 3 -
which is also the default when it's not set explicitly in the strategy.

Future versions will require this to be set.

freqtrade trade --strategy AwesomeStrategy


For the following section we will use the user_data/strategies/sample_strategy.py
file as reference.

Strategies and Backtesting

To avoid problems and unexpected differences between Backtesting and dry/live


modes, please be aware that during backtesting the full time range is passed to the
populate_*() methods at once. It is therefore best to use vectorized operations
(across the whole dataframe, not loops) and avoid index referencing ([Link][-1]),
but instead use [Link]() to get to the previous candle.

Warning: Using future data

Since backtesting passes the full time range to the populate_*() methods, the
strategy author needs to take care to avoid having the strategy utilize data from
the future. Some common patterns for this are listed in the Common Mistakes section
of this document.

Dataframe
Freqtrade uses pandas to store/provide the candlestick (OHLCV) data. Pandas is a
great library developed for processing large amounts of data.

Each row in a dataframe corresponds to one candle on a chart, with the latest
candle always being the last in the dataframe (sorted by date).

> [Link]()
date open high low close volume
0 2021-11-09 [Link]+00:00 67279.67 67321.84 67255.01 67300.97 44.62253
1 2021-11-09 [Link]+00:00 67300.97 67301.34 67183.03 67187.01 61.38076
2 2021-11-09 [Link]+00:00 67187.02 67187.02 67031.93 67123.81 113.42728
3 2021-11-09 [Link]+00:00 67123.80 67222.40 67080.33 67160.48 78.96008
4 2021-11-09 [Link]+00:00 67160.48 67160.48 66901.26 66943.37 111.39292
Pandas provides fast ways to calculate metrics. To benefit from this speed, it's
advised to not use loops, but use vectorized methods instead.

Vectorized operations perform calculations across the whole range of data and are
therefore, compared to looping through each row, a lot faster when calculating
indicators.

As a dataframe is a table, simple python comparisons like the following will not
work

if dataframe['rsi'] > 30:


dataframe['enter_long'] = 1
The above section will fail with The truth value of a Series is ambiguous. [...].

This must instead be written in a pandas-compatible way, so the operation is


performed across the whole dataframe.
[Link][
(dataframe['rsi'] > 30)
, 'enter_long'] = 1
With this section, you have a new column in your dataframe, which has 1 assigned
whenever RSI is above 30.

Customize Indicators
Buy and sell signals need indicators. You can add more indicators by extending the
list contained in the method populate_indicators() from your strategy file.

You should only add the indicators used in either populate_entry_trend(),


populate_exit_trend(), or to populate another indicator, otherwise performance may
suffer.

It's important to always return the dataframe without removing/modifying the


columns "open", "high", "low", "close", "volume", otherwise these fields would
contain something unexpected.

Sample:

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


"""
Adds several different TA indicators to the given DataFrame

Performance Note: For the best performance be frugal on the number of


indicators
you are using. Let uncomment only the indicator you are using in your
strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU
usage.
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
dataframe['sar'] = [Link](dataframe)
dataframe['adx'] = [Link](dataframe)
stoch = [Link](dataframe)
dataframe['fastd'] = stoch['fastd']
dataframe['fastk'] = stoch['fastk']
dataframe['blower'] = [Link](dataframe, nbdevup=2, nbdevdn=2)['lowerband']
dataframe['sma'] = [Link](dataframe, timeperiod=40)
dataframe['tema'] = [Link](dataframe, timeperiod=9)
dataframe['mfi'] = [Link](dataframe)
dataframe['rsi'] = [Link](dataframe)
dataframe['ema5'] = [Link](dataframe, timeperiod=5)
dataframe['ema10'] = [Link](dataframe, timeperiod=10)
dataframe['ema50'] = [Link](dataframe, timeperiod=50)
dataframe['ema100'] = [Link](dataframe, timeperiod=100)
dataframe['ao'] = awesome_oscillator(dataframe)
macd = [Link](dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
return dataframe
Want more indicator examples?

Look into the user_data/strategies/sample_strategy.py. Then uncomment indicators


you need.

Indicator libraries
Out of the box, freqtrade installs the following technical libraries:

ta-lib
pandas-ta
technical
Additional technical libraries can be installed as necessary, or custom indicators
may be written / invented by the strategy author.

Strategy startup period


Most indicators have an instable startup period, in which they are either not
available (NaN), or the calculation is incorrect. This can lead to inconsistencies,
since Freqtrade does not know how long this instable period should be. To account
for this, the strategy can be assigned the startup_candle_count attribute. This
should be set to the maximum number of candles that the strategy requires to
calculate stable indicators. In the case where a user includes higher timeframes
with informative pairs, the startup_candle_count does not necessarily change. The
value is the maximum period (in candles) that any of the informatives timeframes
need to compute stable indicators.

You can use recursive-analysis to check and find the correct startup_candle_count
to be used.

In this example strategy, this should be set to 400 (startup_candle_count = 400),


since the minimum needed history for ema100 calculation to make sure the value is
correct is 400 candles.

dataframe['ema100'] = [Link](dataframe, timeperiod=100)


By letting the bot know how much history is needed, backtest trades can start at
the specified timerange during backtesting and hyperopt.

Using x calls to get OHLCV

If you receive a warning like WARNING - Using 3 calls to get OHLCV. This can result
in slower operations for the bot. Please check if you really need 1500 candles for
your strategy - you should consider if you really need this much historic data for
your signals. Having this will cause Freqtrade to make multiple calls for the same
pair, which will obviously be slower than one network request. As a consequence,
Freqtrade will take longer to refresh candles - and should therefore be avoided if
possible. This is capped to 5 total calls to avoid overloading the exchange, or
make freqtrade too slow.

Warning

startup_candle_count should be below ohlcv_candle_limit * 5 (which is 500 * 5 for


most exchanges) - since only this amount of candles will be available during Dry-
Run/Live Trade operations.

Example
Let's try to backtest 1 month (January 2019) of 5m candles using an example
strategy with EMA100, as above.

freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m


Assuming startup_candle_count is set to 400, backtesting knows it needs 400 candles
to generate valid buy signals. It will load data from 20190101 - (400 * 5m) - which
is ~2018-12-30 [Link]. If this data is available, indicators will be calculated
with this extended timerange. The instable startup period (up to 2019-01-01
[Link]) will then be removed before starting backtesting.

Note

If data for the startup period is not available, then the timerange will be
adjusted to account for this startup period - so Backtesting would start at 2019-
01-02 [Link].

Entry signal rules


Edit the method populate_entry_trend() in your strategy file to update your entry
strategy.

It's important to always return the dataframe without removing/modifying the


columns "open", "high", "low", "close", "volume", otherwise these fields would
contain something unexpected.

This method will also define a new column, "enter_long" ("enter_short" for shorts),
which needs to contain 1 for entries, and 0 for "no action". enter_long is a
mandatory column that must be set even if the strategy is shorting only.

Sample from user_data/strategies/sample_strategy.py:

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


"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
[Link][
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses
above 30
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')

return dataframe
Enter short trades
Note

Buying requires sellers to buy from - therefore volume needs to be > 0


(dataframe['volume'] > 0) to make sure that the bot does not buy/sell in no-
activity periods.

Exit signal rules


Edit the method populate_exit_trend() into your strategy file to update your exit
strategy. The exit-signal can be suppressed by setting use_exit_signal to false in
the configuration or strategy. use_exit_signal will not influence signal collision
rules - which will still apply and can prevent entries.

It's important to always return the dataframe without removing/modifying the


columns "open", "high", "low", "close", "volume", otherwise these fields would
contain something unexpected.

This method will also define a new column, "exit_long" ("exit_short" for shorts),
which needs to contain 1 for exits, and 0 for "no action".

Sample from user_data/strategies/sample_strategy.py:

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


"""
Based on TA indicators, populates the exit signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
[Link][
(
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses
above 70
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard
(dataframe['volume'] > 0) # Make sure Volume is not 0
),
['exit_long', 'exit_tag']] = (1, 'rsi_too_high')
return dataframe
Exit short trades
Minimal ROI
This dict defines the minimal Return On Investment (ROI) a trade should reach
before exiting, independent from the exit signal.

It is of the following format, with the dict key (left side of the colon) being the
minutes passed since the trade opened, and the value (right side of the colon)
being the percentage.

minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
The above configuration would therefore mean:

Exit whenever 4% profit was reached


Exit when 2% profit was reached (in effect after 20 minutes)
Exit when 1% profit was reached (in effect after 30 minutes)
Exit when trade is non-loosing (in effect after 40 minutes)
The calculation does include fees.

To disable ROI completely, set it to an empty dictionary:

minimal_roi = {}
To use times based on candle duration (timeframe), the following snippet can be
handy. This will allow you to change the timeframe for the strategy, and ROI times
will still be set as candles (e.g. after 3 candles ...)

from [Link] import timeframe_to_minutes

class AwesomeStrategy(IStrategy):

timeframe = "1d"
timeframe_mins = timeframe_to_minutes(timeframe)
minimal_roi = {
"0": 0.05, # 5% for the first 3 candles
str(timeframe_mins * 3): 0.02, # 2% after 3 candles
str(timeframe_mins * 6): 0.01, # 1% After 6 candles
}
Orders that don't fill immediately
Stoploss
Setting a stoploss is highly recommended to protect your capital from strong moves
against you.

Sample of setting a 10% stoploss:

stoploss = -0.10
For the full documentation on stoploss features, look at the dedicated stoploss
page.

Timeframe
This is the set of candles the bot should download and use for the analysis. Common
values are "1m", "5m", "15m", "1h", however all values supported by your exchange
should work.

Please note that the same entry/exit signals may work well with one timeframe, but
not with the others.

This setting is accessible within the strategy methods as the [Link]


attribute.

Can short
To use short signals in futures markets, you will have to let us know to do so by
setting can_short=True. Strategies which enable this will fail to load on spot
markets. Disabling of this will have short signals ignored (also in futures
markets).

Metadata dict
The metadata-dict (available for populate_entry_trend, populate_exit_trend,
populate_indicators) contains additional information. Currently this is pair, which
can be accessed using metadata['pair'] - and will return a pair in the format
XRP/BTC.

The Metadata-dict should not be modified and does not persist information across
multiple calls. Instead, have a look at the Storing information section.

Imports necessary for a strategy


When creating a strategy, you will need to import the necessary modules and
classes. The following imports are required for a strategy:

By default, we recommend the following imports as a base line for your strategy:
This will cover all imports necessary for freqtrade functions to work. Obviously
you can add more imports as needed for your strategy.

# flake8: noqa: F401


# isort: skip_file
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple

from [Link] import (


IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)

# --------------------------------
# Add your lib to import here
import [Link] as ta
from technical import qtpylib
Strategy file loading
By default, freqtrade will attempt to load strategies from all .py files within
user_data/strategies.

Assuming your strategy is called AwesomeStrategy, stored in the file


user_data/strategies/[Link], then you can start freqtrade with
freqtrade trade --strategy AwesomeStrategy. Note that we're using the class-name,
not the file name.

You can use freqtrade list-strategies to see a list of all strategies Freqtrade is
able to load (all strategies in the correct folder). It will also include a
"status" field, highlighting potential problems.

Customize strategy directory


Informative Pairs
Get data for non-tradeable pairs
Data for additional, informative pairs (reference pairs) can be beneficial for some
strategies. OHLCV data for these pairs will be downloaded as part of the regular
whitelist refresh process and is available via DataProvider just as other pairs
(see below). These parts will not be traded unless they are also specified in the
pair whitelist, or have been selected by Dynamic Whitelisting.

The pairs need to be specified as tuples in the format ("pair", "timeframe"), with
pair as the first and timeframe as the second argument.

Sample:

def informative_pairs(self):
return [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
A full sample can be found in the DataProvider section.

Warning

As these pairs will be refreshed as part of the regular whitelist refresh, it's
best to keep this list short. All timeframes and all pairs can be specified as long
as they are available (and active) on the used exchange. It is however better to
use resampling to longer timeframes whenever possible to avoid hammering the
exchange with too many requests and risk being blocked.

Alternative candle types


Informative pairs decorator (@informative())
In most common case it is possible to easily define informative pairs by using a
decorator. All decorated populate_indicators_* methods run in isolation, not having
access to data from other informative pairs, in the end all informative dataframes
are merged and passed to main populate_indicators() method. When hyperopting, use
of hyperoptable parameter .value attribute is not supported. Please use .range
attribute. See optimizing an indicator parameter for more information.

Full documentation
Fast and easy way to define informative pairs
Note

Do not use @informative decorator if you need to use data of one informative pair
when generating another informative pair. Instead, define informative pairs
manually as described in the DataProvider section.

Note

Use string formatting when accessing informative dataframes of other pairs. This
will allow easily changing stake currency in config without having to adjust
strategy code.

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


stake = [Link]['stake_currency']
[Link][
(
(dataframe[f'btc_{stake}_rsi_1h'] < 35)
&
(dataframe['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi')

return dataframe
Alternatively column renaming may be used to remove stake currency from column
names: @informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}').
Duplicate method names

Methods tagged with @informative() decorator must always have unique names! Re-
using same name (for example when copy-pasting already defined informative method)
will overwrite previously defined method and not produce any errors due to
limitations of Python programming language. In such cases you will find that
indicators created in earlier-defined methods are not available in the dataframe.
Carefully review method names and make sure they are unique!

merge_informative_pair()
This method helps you merge an informative pair to a regular dataframe without
lookahead bias. It's there to help you merge the dataframe in a safe and consistent
way.

Options:

Rename the columns for you to create unique columns


Merge the dataframe without lookahead bias
Forward-fill (optional)
For a full sample, please refer to the complete data provider example below.

All columns of the informative dataframe will be available on the returning


dataframe in a renamed fashion:

Column renaming

Assuming inf_tf = '1d' the resulting columns will be:

'date', 'open', 'high', 'low', 'close', 'rsi' # from the


original dataframe
'date_1d', 'open_1d', 'high_1d', 'low_1d', 'close_1d', 'rsi_1d' # from the
informative dataframe
Column renaming - 1h
Custom implementation
Informative timeframe < timeframe

Using informative timeframes smaller than the dataframe timeframe is not


recommended with this method, as it will not use any of the additional information
this would provide. To use the more detailed information properly, more advanced
methods should be applied (which are out of scope for freqtrade documentation, as
it'll depend on the respective need).

Additional data (DataProvider)


The strategy provides access to the DataProvider. This allows you to get additional
data to use in your strategy.

All methods return None in case of failure (do not raise an exception).

Please always check the mode of operation to select the correct method to get data
(samples see below).

Hyperopt

Dataprovider is available during hyperopt, however it can only be used in


populate_indicators() within a strategy. It is not available in populate_buy() and
populate_sell() methods, nor in populate_indicators(), if this method located in
the hyperopt file.
Possible options for DataProvider
available_pairs - Property with tuples listing cached pairs with their timeframe
(pair, timeframe).
current_whitelist() - Returns a current list of whitelisted pairs. Useful for
accessing dynamic whitelists (i.e. VolumePairlist)
get_pair_dataframe(pair, timeframe) - This is a universal method, which returns
either historical data (for backtesting) or cached live data (for the Dry-Run and
Live-Run modes).
get_analyzed_dataframe(pair, timeframe) - Returns the analyzed dataframe (after
calling populate_indicators(), populate_buy(), populate_sell()) and the time of the
latest analysis.
historic_ohlcv(pair, timeframe) - Returns historical data stored on disk.
market(pair) - Returns market data for the pair: fees, limits, precisions, activity
flag, etc. See ccxt documentation for more details on the Market data structure.
ohlcv(pair, timeframe) - Currently cached candle (OHLCV) data for the pair, returns
DataFrame or empty DataFrame.
orderbook(pair, maximum) - Returns latest orderbook data for the pair, a dict with
bids/asks with a total of maximum entries.
ticker(pair) - Returns current ticker data for the pair. See ccxt documentation for
more details on the Ticker data structure.
runmode - Property containing the current runmode.
Example Usages
available_pairs

for pair, timeframe in [Link].available_pairs:


print(f"available {pair}, {timeframe}")
current_whitelist()
Imagine you've developed a strategy that trades the 5m timeframe using signals
generated from a 1d timeframe on the top 10 volume pairs by volume.

The strategy might look something like this:

Scan through the top 10 pairs by volume using the VolumePairList every 5 minutes
and use a 14 day RSI to buy and sell.

Due to the limited available data, it's very difficult to resample 5m candles into
daily candles for use in a 14 day RSI. Most exchanges limit us to just 500-1000
candles which effectively gives us around 1.74 daily candles. We need 14 days at
least!

Since we can't resample the data we will have to use an informative pair; and since
the whitelist will be dynamic we don't know which pair(s) to use.

This is where calling [Link].current_whitelist() comes in handy.

def informative_pairs(self):

# get access to all pairs available in whitelist.


pairs = [Link].current_whitelist()
# Assign tf to each pair so they can be downloaded and cached for strategy.
informative_pairs = [(pair, '1d') for pair in pairs]
return informative_pairs
Plotting with current_whitelist
get_pair_dataframe(pair, timeframe)

# fetch live / historical candle (OHLCV) data for the first informative pair
inf_pair, inf_timeframe = self.informative_pairs()[0]
informative = [Link].get_pair_dataframe(pair=inf_pair,
timeframe=inf_timeframe)
Warning about backtesting

In backtesting, dp.get_pair_dataframe() behavior differs depending on where it's


called. Within populate_*() methods, dp.get_pair_dataframe() returns the full
timerange. Please make sure to not "look into the future" to avoid surprises when
running in dry/live mode. Within callbacks, you'll get the full timerange up to the
current (simulated) candle.

get_analyzed_dataframe(pair, timeframe)
This method is used by freqtrade internally to determine the last signal. It can
also be used in specific callbacks to get the signal that caused the action (see
Advanced Strategy Documentation for more details on available callbacks).

# fetch current dataframe


dataframe, last_updated = [Link].get_analyzed_dataframe(pair=metadata['pair'],
timeframe=[Link])
No data available

Returns an empty dataframe if the requested pair was not cached. You can check for
this with if [Link]: and handle this case accordingly. This should not
happen when using whitelisted pairs.

orderbook(pair, maximum)

if [Link] in ('live', 'dry_run'):


ob = [Link](metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
The orderbook structure is aligned with the order structure from ccxt, so the
result will look as follows:

{
'bids': [
[ price, amount ], // [ float, float ]
[ price, amount ],
...
],
'asks': [
[ price, amount ],
[ price, amount ],
//...
],
//...
}
Therefore, using ob['bids'][0][0] as demonstrated above will result in using the
best bid price. ob['bids'][0][1] would look at the amount at this orderbook
position.

Warning about backtesting

The order book is not part of the historic data which means backtesting and
hyperopt will not work correctly if this method is used, as the method will return
up-to-date values.

ticker(pair)
if [Link] in ('live', 'dry_run'):
ticker = [Link](metadata['pair'])
dataframe['last_price'] = ticker['last']
dataframe['volume24h'] = ticker['quoteVolume']
dataframe['vwap'] = ticker['vwap']
Warning

Although the ticker data structure is a part of the ccxt Unified Interface, the
values returned by this method can vary for different exchanges. For instance, many
exchanges do not return vwap values, some exchanges does not always fills in the
last field (so it can be None), etc. So you need to carefully verify the ticker
data returned from the exchange and add appropriate error handling / defaults.

Warning about backtesting

This method will always return up-to-date values - so usage during backtesting /
hyperopt without runmode checks will lead to wrong results.

Send Notification
The dataprovider .send_msg() function allows you to send custom notifications from
your strategy. Identical notifications will only be sent once per candle, unless
the 2nd argument (always_send) is set to True.

[Link].send_msg(f"{metadata['pair']} just got hot!")

# Force send this notification, avoid caching (Please read warning below!)
[Link].send_msg(f"{metadata['pair']} just got hot!", always_send=True)
Notifications will only be sent in trading modes (Live/Dry-run) - so this method
can be called without conditions for backtesting.

Spamming

You can spam yourself pretty good by setting always_send=True in this method. Use
this with great care and only in conditions you know will not happen throughout a
candle to avoid a message every 5 seconds.

Complete Data-provider sample

from [Link] import IStrategy, merge_informative_pair


from pandas import DataFrame

class SampleStrategy(IStrategy):
# strategy init stuff...

timeframe = '5m'

# more strategy init stuff..

def informative_pairs(self):

# get access to all pairs available in whitelist.


pairs = [Link].current_whitelist()
# Assign tf to each pair so they can be downloaded and cached for strategy.
informative_pairs = [(pair, '1d') for pair in pairs]
# Optionally Add additional "static" pairs
informative_pairs += [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
return informative_pairs

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


DataFrame:
if not [Link]:
# Don't do anything if DataProvider is not available.
return dataframe

inf_tf = '1d'
# Get the informative pair
informative = [Link].get_pair_dataframe(pair=metadata['pair'],
timeframe=inf_tf)
# Get the 14 day rsi
informative['rsi'] = [Link](informative, timeperiod=14)

# Use the helper function merge_informative_pair to safely merge the pair


# Automatically renames the columns and merges a shorter timeframe
dataframe and a longer timeframe informative pair
# use ffill to have the 1d value available in every row throughout the day.
# Without this, comparisons between columns of the original and the
informative pair would only work once per day.
# Full documentation of this method, see below
dataframe = merge_informative_pair(dataframe, informative, [Link],
inf_tf, ffill=True)

# Calculate rsi of the original dataframe (5m timeframe)


dataframe['rsi'] = [Link](dataframe, timeperiod=14)

# Do other stuff
# ...

return dataframe

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


DataFrame:

[Link][
(
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI
crosses above 30
(dataframe['rsi_1d'] < 30) & # Ensure daily RSI
is < 30
(dataframe['volume'] > 0) # Ensure this
candle had volume (important for backtesting)
),
['enter_long', 'enter_tag']] = (1, 'rsi_cross')
Additional data (Wallets)
The strategy provides access to the wallets object. This contains the current
balances on the exchange.

Backtesting / Hyperopt

Wallets behaves differently depending on the function it's called. Within


populate_*() methods, it'll return the full wallet as configured. Within callbacks,
you'll get the wallet state corresponding to the actual simulated wallet at that
point in the simulation process.

Please always check if wallets is available to avoid failures during backtesting.


if [Link]:
free_eth = [Link].get_free('ETH')
used_eth = [Link].get_used('ETH')
total_eth = [Link].get_total('ETH')
Possible options for Wallets
get_free(asset) - currently available balance to trade
get_used(asset) - currently tied up balance (open orders)
get_total(asset) - total available balance - sum of the 2 above
Additional data (Trades)
A history of Trades can be retrieved in the strategy by querying the database.

At the top of the file, import Trade.

from [Link] import Trade


The following example queries for the current pair and trades from today, however
other filters can easily be added.

trades = Trade.get_trades_proxy(pair=metadata['pair'],
open_date=[Link]([Link]) -
timedelta(days=1),
is_open=False,
]).order_by(Trade.close_date).all()
# Summarize profit for this pair.
curdayprofit = sum(trade.close_profit for trade in trades)
For a full list of available methods, please consult the Trade object
documentation.

Warning

Trade history is not available in populate_* methods during backtesting or


hyperopt, and will result in empty results.

Prevent trades from happening for a specific pair


Freqtrade locks pairs automatically for the current candle (until that candle is
over) when a pair is sold, preventing an immediate re-buy of that pair.

Locked pairs will show the message Pair <pair> is currently locked..

Locking pairs from within the strategy


Sometimes it may be desired to lock a pair after certain events happen (e.g.
multiple losing trades in a row).

Freqtrade has an easy method to do this from within the strategy, by calling
self.lock_pair(pair, until, [reason]). until must be a datetime object in the
future, after which trading will be re-enabled for that pair, while reason is an
optional string detailing why the pair was locked.

Locks can also be lifted manually, by calling self.unlock_pair(pair) or


self.unlock_reason(<reason>) - providing reason the pair was locked with.
self.unlock_reason(<reason>) will unlock all pairs currently locked with the
provided reason.

To verify if a pair is currently locked, use self.is_pair_locked(pair).

Note
Locked pairs will always be rounded up to the next candle. So assuming a 5m
timeframe, a lock with until set to 10:18 will lock the pair until the candle from
10:15-10:20 will be finished.

Warning

Manually locking pairs is not available during backtesting, only locks via
Protections are allowed.

Pair locking example

from [Link] import Trade


from datetime import timedelta, datetime, timezone
# Put the above lines a the top of the strategy file, next to all the other imports
# --------

# Within populate indicators (or populate_buy):


if [Link]['runmode'].value in ('live', 'dry_run'):
# fetch closed trades for the last 2 days
trades = Trade.get_trades_proxy(
pair=metadata['pair'], is_open=False,
open_date=[Link]([Link]) - timedelta(days=2))
# Analyze the conditions you'd like to lock the pair .... will probably be
different for every strategy
sumprofit = sum(trade.close_profit for trade in trades)
if sumprofit < 0:
# Lock pair for 12 hours
self.lock_pair(metadata['pair'], until=[Link]([Link]) +
timedelta(hours=12))
Print created dataframe
To inspect the created dataframe, you can issue a print-statement in either
populate_entry_trend() or populate_exit_trend(). You may also want to print the
pair so it's clear what data is currently shown.

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


[Link][
(
#>> whatever condition<<<
),
['enter_long', 'enter_tag']] = (1, 'somestring')

# Print the Analyzed pair


print(f"result for {metadata['pair']}")

# Inspect the last 5 rows


print([Link]())

return dataframe
Printing more than a few rows is also possible (simply use print(dataframe) instead
of print([Link]())), however not recommended, as that will be very verbose
(~500 lines per pair every 5 seconds).

Common mistakes when developing strategies


Peeking into the future while backtesting
Backtesting analyzes the whole time-range at once for performance reasons. Because
of this, strategy authors need to make sure that strategies do not look-ahead into
the future. This is a common pain-point, which can cause huge differences between
backtesting and dry/live run methods, since they all use data which is not
available during dry/live runs, so these strategies will perform well during
backtesting, but will fail / perform badly in real conditions.

The following lists some common patterns which should be avoided to prevent
frustration:

don't use shift(-1) or other negative values. This uses data from the future in
backtesting, which is not available in dry or live modes.
don't use .iloc[-1] or any other absolute position in the dataframe within
populate_ functions, as this will be different between dry-run and backtesting.
Absolute iloc indexing is safe to use in callbacks however - see Strategy
Callbacks.
don't use dataframe['volume'].mean(). This uses the full DataFrame for backtesting,
including data from the future. Use dataframe['volume'].rolling(<window>).mean()
instead
don't use .resample('1h'). This uses the left border of the interval, so moves data
from an hour to the start of the hour. Use .resample('1h', label='right') instead.
Identifying problems

You may also want to check the 2 helper commands lookahead-analysis and recursive-
analysis, which can each help you figure out problems with your strategy in
different ways. Please treat them as what they are - helpers to identify most
common problems. A negative result of each does not guarantee that there's none of
the above errors included.

Colliding signals
When conflicting signals collide (e.g. both 'enter_long' and 'exit_long' are 1),
freqtrade will do nothing and ignore the entry signal. This will avoid trades that
enter, and exit immediately. Obviously, this can potentially lead to missed
entries.

The following rules apply, and entry signals will be ignored if more than one of
the 3 signals is set:

enter_long -> exit_long, enter_short


enter_short -> exit_short, enter_long
Further strategy ideas
To get additional Ideas for strategies, head over to the strategy repository. Feel
free to use them as they are - but results will depend on the current market
situation, pairs used etc. - therefore please backtest the strategy for your
exchange/desired pairs first, evaluate carefully, use at your own risk. Feel free
to use any of them as inspiration for your own strategies. We're happy to accept
Pull Requests containing new Strategies to that repo.

Next step
Now you have a perfect strategy you probably want to backtest it. Your next step is
to learn How to use the Backtesting.

Strategy Callbacks
While the main strategy functions (populate_indicators(), populate_entry_trend(),
populate_exit_trend()) should be used in a vectorized way, and are only called once
during backtesting, callbacks are called "whenever needed".

As such, you should avoid doing heavy calculations in callbacks to avoid delays
during operations. Depending on the callback used, they may be called when entering
/ exiting a trade, or throughout the duration of a trade.

Currently available callbacks:


bot_start()
bot_loop_start()
custom_stake_amount()
custom_exit()
custom_stoploss()
custom_entry_price() and custom_exit_price()
check_entry_timeout() and check_exit_timeout()
confirm_trade_entry()
confirm_trade_exit()
adjust_trade_position()
adjust_entry_price()
leverage()
order_filled()
Callback calling sequence

You can find the callback calling sequence in bot-basics

Imports necessary for a strategy


When creating a strategy, you will need to import the necessary modules and
classes. The following imports are required for a strategy:

By default, we recommend the following imports as a base line for your strategy:
This will cover all imports necessary for freqtrade functions to work. Obviously
you can add more imports as needed for your strategy.

# flake8: noqa: F401


# isort: skip_file
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple

from [Link] import (


IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)

# --------------------------------
# Add your lib to import here
import [Link] as ta
from technical import qtpylib
Bot start
A simple callback which is called once when the strategy is loaded. This can be
used to perform actions that must only be performed once and runs after
dataprovider and wallet are set

import requests

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def bot_start(self, **kwargs) -> None:


"""
Called only once after bot instantiation.
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
"""
if [Link]["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
self.custom_remote_data =
[Link]("[Link]
During hyperopt, this runs only once at startup.

Bot loop start


A simple callback which is called once at the start of every bot throttling
iteration in dry/live mode (roughly every 5 seconds, unless configured differently)
or once per candle in backtest/hyperopt mode. This can be used to perform
calculations which are pair independent (apply to all pairs), loading of external
data, etc.

# Default imports
import requests

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def bot_loop_start(self, current_time: datetime, **kwargs) -> None:


"""
Called at the start of the bot iteration (one loop).
Might be used to perform pair-independent tasks
(e.g. gather some remote resource for comparison)
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
"""
if [Link]["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
self.remote_data =
[Link]("[Link]
Stake size management
Called before entering a trade, makes it possible to manage your position size when
placing a new trade.

# Default imports

class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate:
float,
proposed_stake: float, min_stake: Optional[float],
max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:

dataframe, _ = [Link].get_analyzed_dataframe(pair=pair,
timeframe=[Link])
current_candle = [Link][-1].squeeze()

if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]:


if [Link]["stake_amount"] == "unlimited":
# Use entire available wallet during favorable conditions when in
compounding mode.
return max_stake
else:
# Compound profits during favorable conditions instead of using a
static stake.
return [Link].get_total_stake_amount() /
[Link]["max_open_trades"]

# Use default stake amount.


return proposed_stake
Freqtrade will fall back to the proposed_stake value should your code raise an
exception. The exception itself will be logged.

Tip

You do not have to ensure that min_stake <= returned_value <= max_stake. Trades
will succeed as the returned value will be clamped to supported range and this
action will be logged.

Tip

Returning 0 or None will prevent trades from being placed.

Custom exit signal


Called for open trade every throttling iteration (roughly every 5 seconds) until a
trade is closed.

Allows to define custom exit signals, indicating that specified position should be
sold. This is very useful when we need to customize exit conditions for each
individual trade, or if you need trade data to make an exit decision.

For example you could implement a 1:2 risk-reward ROI with custom_exit().

Using custom_exit() signals in place of stoploss though is not recommended. It is a


inferior method to using custom_stoploss() in this regard - which also allows you
to keep the stoploss on exchange.

Note
Returning a (none-empty) string or True from this method is equal to setting exit
signal on a candle at specified time. This method is not called when exit signal is
set already, or if exit signals are disabled (use_exit_signal=False). string max
length is 64 characters. Exceeding this limit will cause the message to be
truncated to 64 characters. custom_exit() will ignore exit_profit_only, and will
always be called unless use_exit_signal=False, even if there is a new enter signal.

An example of how we can use different indicators depending on the current profit
and also exit trades that were open longer than one day:

# Default imports

class AwesomeStrategy(IStrategy):
def custom_exit(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = [Link].get_analyzed_dataframe(pair, [Link])
last_candle = [Link][-1].squeeze()

# Above 20% profit, sell when rsi < 80


if current_profit > 0.2:
if last_candle["rsi"] < 80:
return "rsi_below_80"

# Between 2% and 10%, sell if EMA-long above EMA-short


if 0.02 < current_profit < 0.1:
if last_candle["emalong"] > last_candle["emashort"]:
return "ema_long_below_80"

# Sell any positions at a loss if they are held for more than one day.
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
return "unclog"
See Dataframe access for more information about dataframe use in strategy
callbacks.

Custom stoploss
Called for open trade every iteration (roughly every 5 seconds) until a trade is
closed.

The usage of the custom stoploss method must be enabled by setting


use_custom_stoploss=True on the strategy object.

The stoploss price can only ever move upwards - if the stoploss value returned from
custom_stoploss would result in a lower stoploss price than was previously set, it
will be ignored. The traditional stoploss value serves as an absolute lower level
and will be instated as the initial stoploss (before this method is called for the
first time for a trade), and is still mandatory.

The method must return a stoploss value (float / number) as a percentage of the
current price. E.g. If the current_rate is 200 USD, then returning 0.02 will set
the stoploss price 2% lower, at 196 USD. During backtesting, current_rate (and
current_profit) are provided against the candle's high (or low for short trades) -
while the resulting stoploss is evaluated against the candle's low (or high for
short trades).

The absolute value of the return value is used (the sign is ignored), so returning
0.05 or -0.05 have the same result, a stoploss 5% below the current price.
Returning None will be interpreted as "no desire to change", and is the only safe
way to return when you'd like to not modify the stoploss. NaN and inf values are
considered invalid and will be ignored (identical to None).

Stoploss on exchange works similar to trailing_stop, and the stoploss on exchange


is updated as configured in stoploss_on_exchange_interval (More details about
stoploss on exchange).

Use of dates

All time-based calculations should be done based on current_time - using


[Link]() or [Link]() is discouraged, as this will break backtesting
support.

Trailing stoploss

It's recommended to disable trailing_stop when using custom stoploss values. Both
can work in tandem, but you might encounter the trailing stop to move the price
higher while your custom function would not want this, causing conflicting
behavior.

Adjust stoploss after position adjustments


Depending on your strategy, you may encounter the need to adjust the stoploss in
both directions after a position adjustment. For this, freqtrade will make an
additional call with after_fill=True after an order fills, which will allow the
strategy to move the stoploss in any direction (also widening the gap between
stoploss and current price, which is otherwise forbidden).

backwards compatibility

This call will only be made if the after_fill parameter is part of the function
definition of your custom_stoploss function. As such, this will not impact (and
with that, surprise) existing, running strategies.

Custom stoploss examples


The next section will show some examples on what's possible with the custom
stoploss function. Of course, many more things are possible, and all examples can
be combined at will.

Trailing stop via custom stoploss


To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum
reached price) you would use the following very simple method:

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:
"""
Custom stoploss logic, returning the new distance relative to current_rate
(as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
The custom stoploss can never be below [Link], which serves as a
hard maximum loss.

For full documentation please go to


[Link]

When not implemented by a strategy, returns the initial stoploss value.


Only called when use_custom_stoploss is set to True.

:param pair: Pair that's currently analyzed


:param trade: trade object.
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in
exit_pricing.
:param current_profit: Current profit (as ratio), calculated based on
current_rate.
:param after_fill: True if the stoploss is called after the order was
filled.
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
:return float: New stoploss value, relative to the current_rate
"""
return -0.04
Time based trailing stop
Use the initial stoploss for the first 60 minutes, after this change to 10%
trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

# Make sure you have the longest interval first - these conditions are
evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date_utc:
return -0.05
elif current_time - timedelta(minutes=60) > trade.open_date_utc:
return -0.10
return None
Time based trailing stop with after-fill adjustments
Use the initial stoploss for the first 60 minutes, after this change to 10%
trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
If an additional order fills, set stoploss to -10% below the new open_rate
(Averaged across all entries).

# Default imports

class AwesomeStrategy(IStrategy):
# ... populate_* methods

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

if after_fill:
# After an additional order, start with a stoploss of 10% below the new
open rate
return stoploss_from_open(0.10, current_profit,
is_short=trade.is_short, leverage=[Link])
# Make sure you have the longest interval first - these conditions are
evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date_utc:
return -0.05
elif current_time - timedelta(minutes=60) > trade.open_date_utc:
return -0.10
return None
Different stoploss per pair
Use a different stoploss depending on the pair. In this example, we'll trail the
highest price with 10% trailing stoploss for ETH/BTC and XRP/BTC, with 5% trailing
stoploss for LTC/BTC and with 15% for all other pairs.

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

if pair in ("ETH/BTC", "XRP/BTC"):


return -0.10
elif pair in ("LTC/BTC"):
return -0.05
return -0.15
Trailing stoploss with positive offset
Use the initial stoploss until the profit is above 4%, then use a trailing stoploss
of 50% of the current profit with a minimum of 2.5% and a maximum of 5%.

Please note that the stoploss can only increase, values lower than the current
stoploss are ignored.

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods


use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

if current_profit < 0.04:


return None # return None to keep using the initial stoploss

# After reaching the desired offset, allow the stoploss to trail by half
the profit
desired_stoploss = current_profit / 2

# Use a minimum of 2.5% and a maximum of 5%


return max(min(desired_stoploss, 0.05), 0.025)
Stepped stoploss
Instead of continuously trailing behind the current price, this example sets fixed
stoploss price levels based on the current profit.

Use the regular stoploss until 20% profit is reached


Once profit is > 20% - set stoploss to 7% above open price.
Once profit is > 25% - set stoploss to 15% above open price.
Once profit is > 40% - set stoploss to 25% above open price.

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

# evaluate highest to lowest, so that highest possible stop is used


if current_profit > 0.40:
return stoploss_from_open(0.25, current_profit,
is_short=trade.is_short, leverage=[Link])
elif current_profit > 0.25:
return stoploss_from_open(0.15, current_profit,
is_short=trade.is_short, leverage=[Link])
elif current_profit > 0.20:
return stoploss_from_open(0.07, current_profit,
is_short=trade.is_short, leverage=[Link])

# return maximum stoploss value, keeping current stoploss price unchanged


return None
Custom stoploss using an indicator from dataframe example
Absolute stoploss value may be derived from indicators stored in dataframe. Example
uses parabolic SAR below the price as stoploss.

# Default imports
class AwesomeStrategy(IStrategy):

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


DataFrame:
# <...>
dataframe["sar"] = [Link](dataframe)

use_custom_stoploss = True

def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, after_fill:
bool,
**kwargs) -> Optional[float]:

dataframe, _ = [Link].get_analyzed_dataframe(pair, [Link])


last_candle = [Link][-1].squeeze()

# Use parabolic sar as absolute stoploss price


stoploss_price = last_candle["sar"]

# Convert absolute price to percentage relative to current_rate


if stoploss_price < current_rate:
return stoploss_from_absolute(stoploss_price, current_rate,
is_short=trade.is_short)

# return maximum stoploss value, keeping current stoploss price unchanged


return None
See Dataframe access for more information about dataframe use in strategy
callbacks.

Common helpers for stoploss calculations


Stoploss relative to open price
Stoploss values returned from custom_stoploss() must specify a percentage relative
to current_rate, but sometimes you may want to specify a stoploss relative to the
entry price instead. stoploss_from_open() is a helper function to calculate a
stoploss value that can be returned from custom_stoploss which will be equivalent
to the desired trade profit above the entry point.

Returning a stoploss relative to the open price from the custom stoploss function
Note

Providing invalid input to stoploss_from_open() may produce "CustomStoploss


function did not return valid stoploss" warnings. This may happen if current_profit
parameter is below specified open_relative_stop. Such situations may arise when
closing trade is blocked by confirm_trade_exit() method. Warnings can be solved by
never blocking stop loss sells by checking exit_reason in confirm_trade_exit(), or
by using return stoploss_from_open(...) or 1 idiom, which will request to not
change stop loss when current_profit < open_relative_stop.

Stoploss percentage from absolute price


Stoploss values returned from custom_stoploss() always specify a percentage
relative to current_rate. In order to set a stoploss at specified absolute price
level, we need to use stop_rate to calculate what percentage relative to the
current_rate will give you the same result as if the percentage was specified from
the open price.

The helper function stoploss_from_absolute() can be used to convert from an


absolute price, to a current price relative stop which can be returned from
custom_stoploss().
Returning a stoploss using absolute price from the custom stoploss function
Custom order price rules
By default, freqtrade use the orderbook to automatically set an order
price(Relevant documentation), you also have the option to create custom order
prices based on your strategy.

You can use this feature by creating a custom_entry_price() function in your


strategy file to customize entry prices and custom_exit_price() for exits.

Each of these methods are called right before placing an order on the exchange.

Note

If your custom pricing function return None or an invalid value, price will fall
back to proposed_rate, which is based on the regular pricing configuration.

Note

Using custom_entry_price, the Trade object will be available as soon as the first
entry order associated with the trade is created, for the first entry, trade
parameter value will be None.

Custom order entry and exit price example

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time:


datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:

dataframe, last_updated = [Link].get_analyzed_dataframe(pair=pair,

timeframe=[Link])
new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1]

return new_entryprice

def custom_exit_price(self, pair: str, trade: Trade,


current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs)
-> float:

dataframe, last_updated = [Link].get_analyzed_dataframe(pair=pair,

timeframe=[Link])
new_exitprice = dataframe["bollinger_10_upperband"].iat[-1]

return new_exitprice
Warning

Modifying entry and exit prices will only work for limit orders. Depending on the
price chosen, this can result in a lot of unfilled orders. By default the maximum
allowed distance between the current price and the custom price is 2%, this value
can be changed in config with the custom_price_max_distance_ratio parameter.
Example: If the new_entryprice is 97, the proposed_rate is 100 and the
custom_price_max_distance_ratio is set to 2%, The retained valid custom entry price
will be 98, which is 2% below the current (proposed) rate.

Backtesting

Custom prices are supported in backtesting (starting with 2021.12), and orders will
fill if the price falls within the candle's low/high range. Orders that don't fill
immediately are subject to regular timeout handling, which happens once per
(detail) candle. custom_exit_price() is only called for sells of type exit_signal,
Custom exit and partial exits. All other exit-types will use regular backtesting
prices.

Custom order timeout rules


Simple, time-based order-timeouts can be configured either via strategy or in the
configuration in the unfilledtimeout section.

However, freqtrade also offers a custom callback for both order types, which allows
you to decide based on custom criteria if an order did time out or not.

Note

Backtesting fills orders if their price falls within the candle's low/high range.
The below callbacks will be called once per (detail) candle for orders that don't
fill immediately (which use custom pricing).

Custom order timeout example


Called for every open order until that order is either filled or cancelled.
check_entry_timeout() is called for trade entries, while check_exit_timeout() is
called for trade exit orders.

A simple example, which applies different unfilled-timeouts depending on the price


of the asset can be seen below. It applies a tight timeout for higher priced
assets, while allowing more time to fill on cheap coins.

The function must return either True (cancel order) or False (keep order alive).

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24


hours.
unfilledtimeout = {
"entry": 60 * 25,
"exit": 60 * 25
}

def check_entry_timeout(self, pair: str, trade: Trade, order: Order,


current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time -
timedelta(minutes=5):
return True
elif trade.open_rate > 10 and trade.open_date_utc < current_time -
timedelta(minutes=3):
return True
elif trade.open_rate < 1 and trade.open_date_utc < current_time -
timedelta(hours=24):
return True
return False

def check_exit_timeout(self, pair: str, trade: Trade, order: Order,


current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time -
timedelta(minutes=5):
return True
elif trade.open_rate > 10 and trade.open_date_utc < current_time -
timedelta(minutes=3):
return True
elif trade.open_rate < 1 and trade.open_date_utc < current_time -
timedelta(hours=24):
return True
return False
Note

For the above example, unfilledtimeout must be set to something bigger than 24h,
otherwise that type of timeout will apply first.

Custom order timeout example (using additional data)

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24


hours.
unfilledtimeout = {
"entry": 60 * 25,
"exit": 60 * 25
}

def check_entry_timeout(self, pair: str, trade: Trade, order: Order,


current_time: datetime, **kwargs) -> bool:
ob = [Link](pair, 1)
current_price = ob["bids"][0][0]
# Cancel buy order if price is more than 2% above the order.
if current_price > [Link] * 1.02:
return True
return False

def check_exit_timeout(self, pair: str, trade: Trade, order: Order,


current_time: datetime, **kwargs) -> bool:
ob = [Link](pair, 1)
current_price = ob["asks"][0][0]
# Cancel sell order if price is more than 2% below the order.
if current_price < [Link] * 0.98:
return True
return False
Bot order confirmation
Confirm trade entry / exits. This are the last methods that will be called before
an order is placed.
Trade entry (buy order) confirmation
confirm_trade_entry() can be used to abort a trade entry at the latest second
(maybe because the price is not what we expect).

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate:


float,
time_in_force: str, current_time: datetime, entry_tag:
Optional[str],
side: str, **kwargs) -> bool:
"""
Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or
network requests in this method.

For full documentation please go to


[Link]

When not implemented by a strategy, returns True (always confirming).

:param pair: Pair that's about to be bought/shorted.


:param order_type: Order type (as configured in order_types). usually limit
or market.
:param amount: Amount in target (base) currency that's going to be traded.
:param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy
signal.
:param side: "long" or "short" - indicating the direction of the proposed
trade
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
:return bool: When True is returned, then the buy-order is placed on the
exchange.
False aborts the process
"""
return True
Trade exit (sell order) confirmation
confirm_trade_exit() can be used to abort a trade exit (sell) at the latest second
(maybe because the price is not what we expect).

confirm_trade_exit() may be called multiple times within one iteration for the same
trade if different exit-reasons apply. The exit-reasons (if applicable) will be in
the following sequence:

exit_signal / custom_exit
stop_loss
roi
trailing_stop_loss
# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount:


float,
rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or
network requests in this method.

For full documentation please go to


[Link]

When not implemented by a strategy, returns True (always confirming).

:param pair: Pair for trade that's about to be exited.


:param trade: trade object.
:param order_type: Order type (as configured in order_types). usually limit
or market.
:param amount: Amount in base currency.
:param rate: Rate that's going to be used when using limit orders
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason.
Can be any of ["roi", "stop_loss", "stoploss_on_exchange",
"trailing_stop_loss",
"exit_signal", "force_exit", "emergency_exit"]
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
:return bool: When True, then the exit-order is placed on the exchange.
False aborts the process
"""
if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0:
# Reject force-sells with negative profit
# This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're
force-selling)
return False
return True
Warning

confirm_trade_exit() can prevent stoploss exits, causing significant losses as this


would ignore stoploss exits. confirm_trade_exit() will not be called for
Liquidations - as liquidations are forced by the exchange, and therefore cannot be
rejected.

Adjust trade position


The position_adjustment_enable strategy property enables the usage of
adjust_trade_position() callback in the strategy. For performance reasons, it's
disabled by default and freqtrade will show a warning message on startup if
enabled. adjust_trade_position() can be used to perform additional orders, for
example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease
positions.
Additional orders also result in additional fees and those orders don't count
towards max_open_trades.

This callback is not called when there is an open order (either buy or sell)
waiting for execution.

adjust_trade_position() is called very frequently for the duration of a trade, so


you must keep your implementation as performant as possible.

Position adjustments will always be applied in the direction of the trade, so a


positive value will always increase your position (negative values will decrease
your position), no matter if it's a long or short trade. Adjustment orders can be
assigned with a tag by returning a 2 element Tuple, with the first element being
the adjustment amount, and the 2nd element the tag (e.g. return 250,
"increase_favorable_conditions").

Modifications to leverage are not possible, and the stake-amount returned is


assumed to be before applying leverage.

Increase position
The strategy is expected to return a positive stake_amount (in stake currency)
between min_stake and max_stake if and when an additional entry order should be
made (position is increased -> buy order for long trades, sell order for short
trades).

If there are not enough funds in the wallet (the return value is above max_stake)
then the signal will be ignored. max_entry_position_adjustment property is used to
limit the number of additional entries per trade (on top of the first entry order)
that the bot can execute. By default, the value is -1 which means the bot have no
limit on number of adjustment entries.

Additional entries are ignored once you have reached the maximum amount of extra
entries that you have set on max_entry_position_adjustment, but the callback is
called anyway looking for partial exits.

Decrease position
The strategy is expected to return a negative stake_amount (in stake currency) for
a partial exit. Returning the full owned stake at that point (-trade.stake_amount)
results in a full exit.
Returning a value more than the above (so remaining stake_amount would become
negative) will result in the bot ignoring the signal.

About stake size

Using fixed stake size means it will be the amount used for the first order, just
like without position adjustment. If you wish to buy additional orders with DCA,
then make sure to leave enough funds in the wallet for that. Using "unlimited"
stake amount with DCA orders requires you to also implement the
custom_stake_amount() callback to avoid allocating all funds to the initial order.

Stoploss calculation

Stoploss is still calculated from the initial opening price, not averaged price.
Regular stoploss rules still apply (cannot move down).

While /stopentry command stops the bot from entering new trades, the position
adjustment feature will continue buying new orders on existing trades.
Backtesting

During backtesting this callback is called for each candle in timeframe or


timeframe_detail, so run-time performance will be affected. This can also cause
deviating results between live and backtesting, since backtesting can adjust the
trade only once per candle, whereas live could adjust the trade multiple times per
candle.

Performance with many position adjustments

Position adjustments can be a good approach to increase a strategy's output - but


it can also have drawbacks if using this feature extensively.
Each of the orders will be attached to the trade object for the duration of the
trade - hence increasing memory usage. Trades with long duration and 10s or even
100ds of position adjustments are therefore not recommended, and should be closed
at regular intervals to not affect performance.

# Default imports

class DigDeeperStrategy(IStrategy):

position_adjustment_enable = True

# Attempts to handle large drops with DCA. High stoploss is required.


stoploss = -0.30

# ... populate_* methods

# Example specific variables


max_entry_position_adjustment = 3
# This number is explained a bit further down
max_dca_multiplier = 5.5

# This is called when placing the initial order (opening trade)


def custom_stake_amount(self, pair: str, current_time: datetime, current_rate:
float,
proposed_stake: float, min_stake: Optional[float],
max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
**kwargs) -> float:

# We need to leave most of the funds for possible further DCA orders
# This also applies to fixed stakes
return proposed_stake / self.max_dca_multiplier

def adjust_trade_position(self, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit:
float,
**kwargs
) -> Union[Optional[float], Tuple[Optional[float],
Optional[str]]]:
"""
Custom trade adjustment logic, returning the stake amount that a trade
should be
increased or decreased.
This means extra entry or exit orders with additional fees.
Only called when `position_adjustment_enable` is set to True.

For full documentation please go to


[Link]

When not implemented by a strategy, returns None

:param trade: trade object.


:param current_time: datetime object, containing the current datetime
:param current_rate: Current entry rate (same as current_entry_profit)
:param current_profit: Current profit (as ratio), calculated based on
current_rate
(same as current_entry_profit).
:param min_stake: Minimal stake size allowed by exchange (for both entries
and exits)
:param max_stake: Maximum stake allowed (either through balance, or by
exchange limits).
:param current_entry_rate: Current rate using entry pricing.
:param current_exit_rate: Current rate using exit pricing.
:param current_entry_profit: Current profit using entry pricing.
:param current_exit_profit: Current profit using exit pricing.
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
:return float: Stake amount to adjust your trade,
Positive values to increase position, Negative values to
decrease position.
Return None for no action.
Optionally, return a tuple with a 2nd element with an order
reason
"""

if current_profit > 0.05 and trade.nr_of_successful_exits == 0:


# Take half of the profit at +5%
return -(trade.stake_amount / 2), "half_profit_5%"

if current_profit > -0.05:


return None

# Obtain pair dataframe (just to show how to access it)


dataframe, _ = [Link].get_analyzed_dataframe([Link], [Link])
# Only buy when not actively falling price.
last_candle = [Link][-1].squeeze()
previous_candle = [Link][-2].squeeze()
if last_candle["close"] < previous_candle["close"]:
return None

filled_entries = trade.select_filled_orders(trade.entry_side)
count_of_entries = trade.nr_of_successful_entries
# Allow up to 3 additional increasingly larger buys (4 in total)
# Initial buy is 1x
# If that falls to -5% profit, we buy 1.25x more, average profit should
increase to roughly -2.2%
# If that falls down to -5% again, we buy 1.5x more
# If that falls once again down to -5%, we buy 1.75x more
# Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the
initial allowed stake.
# That is why max_dca_multiplier is 5.5
# Hope you have a deep wallet!
try:
# This returns first order stake size
stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount, "1/3rd_increase"
except Exception as exception:
return None

return None
Position adjust calculations
Entry rates are calculated using weighted averages.
Exits will not influence the average entry rate.
Partial exit relative profit is relative to the average entry price at this point.
Final exit relative profit is calculated based on the total invested capital. (See
example below)
Calculation example
Adjust Entry Price
The adjust_entry_price() callback may be used by strategy developer to
refresh/replace limit orders upon arrival of new candles. Be aware that
custom_entry_price() is still the one dictating initial entry limit order price
target at the time of entry trigger.

Orders can be cancelled out of this callback by returning None.

Returning current_order_rate will keep the order on the exchange "as is". Returning
any other price will cancel the existing order, and replace it with a new order.

The trade open-date (trade.open_date_utc) will remain at the time of the very first
order placed. Please make sure to be aware of this - and eventually adjust your
logic in other callbacks to account for this, and use the date of the first filled
order instead.

If the cancellation of the original order fails, then the order will not be
replaced - though the order will most likely have been canceled on exchange. Having
this happen on initial entries will result in the deletion of the order, while on
position adjustment orders, it'll result in the trade size remaining as is.

Regular timeout

Entry unfilledtimeout mechanism (as well as check_entry_timeout()) takes precedence


over this. Entry Orders that are cancelled via the above methods will not have this
callback called. Be sure to update timeout values to match your expectations.

# Default imports

class AwesomeStrategy(IStrategy):

# ... populate_* methods

def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,


current_time: datetime, proposed_rate: float,
current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
"""
Entry price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled
fully or partially)
and not timed out on subsequent candles after entry trigger.

When not implemented by a strategy, returns current_order_rate as default.


If current_order_rate is returned then the existing order is maintained.
If None is returned then order gets canceled but not replaced by a new one.

:param pair: Pair that's currently analyzed


:param trade: Trade object.
:param order: Order object
:param current_time: datetime object, containing the current datetime
:param proposed_rate: Rate, calculated based on pricing settings in
entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy
signal.
:param side: "long" or "short" - indicating the direction of the proposed
trade
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
:return float: New entry price value if provided

"""
# Limit orders to use and follow SMA200 as price target for the first 10
minutes since entry trigger for BTC/USDT pair.
if (
pair == "BTC/USDT"
and entry_tag == "long_sma200"
and side == "long"
and (current_time - timedelta(minutes=10)) > trade.open_date_utc
):
# just cancel the order if it has been filled more than half of the
amount
if [Link] > [Link]:
return None
else:
dataframe, _ = [Link].get_analyzed_dataframe(pair=pair,
timeframe=[Link])
current_candle = [Link][-1].squeeze()
# desired price
return current_candle["sma_200"]
# default: maintain existing order
return current_order_rate
Leverage Callback
When trading in markets that allow leverage, this method must return the desired
Leverage (Defaults to 1 -> No leverage).

Assuming a capital of 500USDT, a trade with leverage=3 would result in a position


with 500 x 3 = 1500 USDT.

Values that are above max_leverage will be adjusted to max_leverage. For markets /
exchanges that don't support leverage, this method is ignored.

# Default imports

class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag:
Optional[str], side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade. This method is only called in
futures mode.

:param pair: Pair that's currently analyzed


:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in
exit_pricing.
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy
signal.
:param side: "long" or "short" - indicating the direction of the proposed
trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0
All profit calculations include leverage. Stoploss / ROI also include leverage in
their calculation. Defining a stoploss of 10% at 10x leverage would trigger the
stoploss with a 1% move to the downside.

Order filled Callback


The order_filled() callback may be used to perform specific actions based on the
current trade state after an order is filled. It will be called independent of the
order type (entry, exit, stoploss or position adjustment).

Assuming that your strategy needs to store the high value of the candle at trade
entry, this is possible with this callback as the following example show.

# Default imports

class AwesomeStrategy(IStrategy):
def order_filled(self, pair: str, trade: Trade, order: Order, current_time:
datetime, **kwargs) -> None:
"""
Called right after an order fills.
Will be called for all order types (entry, exit, stoploss, position
adjustment).
:param pair: Pair for trade
:param trade: trade object.
:param order: Order object.
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break
your strategy.
"""
# Obtain pair dataframe (just to show how to access it)
dataframe, _ = [Link].get_analyzed_dataframe([Link], [Link])
last_candle = [Link][-1].squeeze()

if (trade.nr_of_successful_entries == 1) and (order.ft_order_side ==


trade.entry_side):
trade.set_custom_data(key="entry_candle_high",
value=last_candle["high"])

return None

Stop Loss
The stoploss configuration parameter is loss as ratio that should trigger a sale.
For example, value -0.10 will cause immediate sell if the profit dips below -10%
for a given trade. This parameter is optional. Stoploss calculations do include
fees, so a stoploss of -10% is placed exactly 10% below the entry point.

Most of the strategy files already include the optimal stoploss value.

Info

All stoploss properties mentioned in this file can be set in the Strategy, or in
the configuration.
Configuration values will override the strategy values.

Stop Loss On-Exchange/Freqtrade


Those stoploss modes can be on exchange or off exchange.

These modes can be configured with these values:

'emergency_exit': 'market',
'stoploss_on_exchange': False
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_limit_ratio': 0.99
Stoploss on exchange is only supported for the following exchanges, and not all
exchanges support both stop-limit and stop-market. The Order-type will be ignored
if only one mode is available.

Exchange stop-loss type


Binance limit
Binance Futures market, limit
Bingx market, limit
HTX (former Huobi) limit
kraken market, limit
Gate limit
Okx limit
Kucoin stop-limit, stop-market
Tight stoploss

Do not set too low/tight stoploss value when using stop loss on exchange!
If set to low/tight you will have greater risk of missing fill on the order and
stoploss will not work.

stoploss_on_exchange and stoploss_on_exchange_limit_ratio


Enable or Disable stop loss on exchange. If the stoploss is on exchange it means a
stoploss limit order is placed on the exchange immediately after buy order fills.
This will protect you against sudden crashes in market, as the order execution
happens purely within the exchange, and has no potential network overhead.

If stoploss_on_exchange uses limit orders, the exchange needs 2 prices, the


stoploss_price and the Limit price.
stoploss defines the stop-price where the limit order is placed - and limit should
be slightly below this.
If an exchange supports both limit and market stoploss orders, then the value of
stoploss will be used to determine the stoploss type.

Calculation example: we bought the asset at 100$.


Stop-price is 95$, then limit would be 95 * 0.99 = 94.05$ - so the limit order fill
can happen between 95$ and 94.05$.
For example, assuming the stoploss is on exchange, and trailing stoploss is
enabled, and the market is going up, then the bot automatically cancels the
previous stoploss order and puts a new one with a stop value higher than the
previous stoploss order.

Note

If stoploss_on_exchange is enabled and the stoploss is cancelled manually on the


exchange, then the bot will create a new stoploss order.

stoploss_on_exchange_interval
In case of stoploss on exchange there is another parameter called
stoploss_on_exchange_interval. This configures the interval in seconds at which the
bot will check the stoploss and update it if necessary.
The bot cannot do these every 5 seconds (at each iteration), otherwise it would get
banned by the exchange. So this parameter will tell the bot how often it should
update the stoploss order. The default value is 60 (1 minute). This same logic will
reapply a stoploss order on the exchange should you cancel it accidentally.

stoploss_price_type
Only applies to futures

stoploss_price_type only applies to futures markets (on exchanges where it's


available). Freqtrade will perform a validation of this setting on startup, failing
to start if an invalid setting for your exchange has been selected. Supported price
types are gonna differs between each exchanges. Please check with your exchange on
which price types it supports.

Stoploss on exchange on futures markets can trigger on different price types. The
naming for these prices in exchange terminology often varies, but is usually
something around "last" (or "contract price" ), "mark" and "index".

Acceptable values for this setting are "last", "mark" and "index" - which freqtrade
will transfer automatically to the corresponding API type, and place the stoploss
on exchange order correspondingly.

force_exit
force_exit is an optional value, which defaults to the same value as exit and is
used when sending a /forceexit command from Telegram or from the Rest API.

force_entry
force_entry is an optional value, which defaults to the same value as entry and is
used when sending a /forceentry command from Telegram or from the Rest API.

emergency_exit
emergency_exit is an optional value, which defaults to market and is used when
creating stop loss on exchange orders fails. The below is the default which is used
if not changed in strategy or configuration file.

Example from strategy file:

order_types = {
"entry": "limit",
"exit": "limit",
"emergency_exit": "market",
"stoploss": "market",
"stoploss_on_exchange": True,
"stoploss_on_exchange_interval": 60,
"stoploss_on_exchange_limit_ratio": 0.99
}
Stop Loss Types
At this stage the bot contains the following stoploss support modes:

Static stop loss.


Trailing stop loss.
Trailing stop loss, custom positive loss.
Trailing stop loss only once the trade has reached a certain offset.
Custom stoploss function
Static Stop Loss
This is very simple, you define a stop loss of x (as a ratio of price, i.e. x *
100% of price). This will try to sell the asset once the loss exceeds the defined
loss.

Example of stop loss:

stoploss = -0.10
For example, simplified math:

the bot buys an asset at a price of 100$


the stop loss is defined at -10%
the stop loss would get triggered once the asset drops below 90$
Trailing Stop Loss
The initial value for this is stoploss, just as you would define your static Stop
loss. To enable trailing stoploss:

stoploss = -0.10
trailing_stop = True
This will now activate an algorithm, which automatically moves the stop loss up
every time the price of your asset increases.

For example, simplified math:

the bot buys an asset at a price of 100$


the stop loss is defined at -10%
the stop loss would get triggered once the asset drops below 90$
assuming the asset now increases to 102$
the stop loss will now be -10% of 102$ = 91.8$
now the asset drops in value to 101$, the stop loss will still be 91.8$ and would
trigger at 91.8$.
In summary: The stoploss will be adjusted to be always be -10% of the highest
observed price.

Trailing stop loss, custom positive loss


You could also have a default stop loss when you are in the red with your buy (buy
- fee), but once you hit a positive result (or an offset you define) the system
will utilize a new stop loss, which can have a different value. For example, your
default stop loss is -10%, but once you have more than 0% profit (example 0.1%) a
different trailing stoploss will be used.

Note

If you want the stoploss to only be changed when you break even of making a profit
(what most users want) please refer to next section with offset enabled.

Both values require trailing_stop to be set to true and trailing_stop_positive with


a value.

stoploss = -0.10
trailing_stop = True
trailing_stop_positive = 0.02
trailing_stop_positive_offset = 0.0
trailing_only_offset_is_reached = False # Default - not necessary for this
example
For example, simplified math:

the bot buys an asset at a price of 100$


the stop loss is defined at -10%
the stop loss would get triggered once the asset drops below 90$
assuming the asset now increases to 102$
the stop loss will now be -2% of 102$ = 99.96$ (99.96$ stop loss will be locked in
and will follow asset price increments with -2%)
now the asset drops in value to 101$, the stop loss will still be 99.96$ and would
trigger at 99.96$
The 0.02 would translate to a -2% stop loss. Before this, stoploss is used for the
trailing stoploss.

Use an offset to change your stoploss

Use trailing_stop_positive_offset to ensure that your new trailing stoploss will be


in profit by setting trailing_stop_positive_offset higher than
trailing_stop_positive. Your first new stoploss value will then already have locked
in profits.

Example with simplified math:

stoploss = -0.10
trailing_stop = True
trailing_stop_positive = 0.02
trailing_stop_positive_offset = 0.03
the bot buys an asset at a price of 100$
the stop loss is defined at -10%, so the stop loss would get triggered once the
asset drops below 90$
assuming the asset now increases to 102$
the stoploss will now be at 91.8$ - 10% below the highest observed rate
assuming the asset now increases to 103.5$ (above the offset configured)
the stop loss will now be -2% of 103.5$ = 101.43$
now the asset drops in value to 102$, the stop loss will still be 101.43$ and would
trigger once price breaks below 101.43$
Trailing stop loss only once the trade has reached a certain offset
You can also keep a static stoploss until the offset is reached, and then trail the
trade to take profits once the market turns.

If trailing_only_offset_is_reached = True then the trailing stoploss is only


activated once the offset is reached. Until then, the stoploss remains at the
configured stoploss. This option can be used with or without
trailing_stop_positive, but uses trailing_stop_positive_offset as offset.

Configuration (offset is buy-price + 3%):

stoploss = -0.10
trailing_stop = True
trailing_stop_positive = 0.02
trailing_stop_positive_offset = 0.03
trailing_only_offset_is_reached = True
For example, simplified math:

the bot buys an asset at a price of 100$


the stop loss is defined at -10%
the stop loss would get triggered once the asset drops below 90$
stoploss will remain at 90$ unless asset increases to or above the configured
offset
assuming the asset now increases to 103$ (where we have the offset configured)
the stop loss will now be -2% of 103$ = 100.94$
now the asset drops in value to 101$, the stop loss will still be 100.94$ and would
trigger at 100.94$
Tip

Make sure to have this value (trailing_stop_positive_offset) lower than minimal


ROI, otherwise minimal ROI will apply first and sell the trade.

Stoploss and Leverage


Stoploss should be thought of as "risk on this trade" - so a stoploss of 10% on a
100$ trade means you are willing to lose 10$ (10%) on this trade - which would
trigger if the price moves 10% to the downside.

When using leverage, the same principle is applied - with stoploss defining the
risk on the trade (the amount you are willing to lose).

Therefore, a stoploss of 10% on a 10x trade would trigger on a 1% price move. If


your stake amount (own capital) was 100$ - this trade would be 1000$ at 10x (after
leverage). If price moves 1% - you've lost 10$ of your own capital - therefore
stoploss will trigger in this case.

Make sure to be aware of this, and avoid using too tight stoploss (at 10x leverage,
10% risk may be too little to allow the trade to "breath" a little).

Changing stoploss on open trades


A stoploss on an open trade can be changed by changing the value in the
configuration or strategy and use the /reload_config command (alternatively,
completely stopping and restarting the bot also works).

The new stoploss value will be applied to open trades (and corresponding log-
messages will be generated).

Limitations
Stoploss values cannot be changed if trailing_stop is enabled and the stoploss has
already been adjusted, or if Edge is enabled (since Edge would recalculate stoploss
based on the current market situation).

Plugins
Pairlists and Pairlist Handlers
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade.
They are configured in the pairlists section of the configuration settings.

In your configuration, you can use Static Pairlist (defined by the StaticPairList
Pairlist Handler) and Dynamic Pairlist (defined by the VolumePairList and
PercentChangePairList Pairlist Handlers).

Additionally, AgeFilter, PrecisionFilter, PriceFilter, ShuffleFilter, SpreadFilter


and VolatilityFilter act as Pairlist Filters, removing certain pairs and/or moving
their positions in the pairlist.

If multiple Pairlist Handlers are used, they are chained and a combination of all
Pairlist Handlers forms the resulting pairlist the bot uses for trading and
backtesting. Pairlist Handlers are executed in the sequence they are configured.
You can define either StaticPairList, VolumePairList, ProducerPairList,
RemotePairList, MarketCapPairList or PercentChangePairList as the starting Pairlist
Handler.

Inactive markets are always removed from the resulting pairlist. Explicitly
blacklisted pairs (those in the pair_blacklist configuration setting) are also
always removed from the resulting pairlist.

Pair blacklist
The pair blacklist (configured via exchange.pair_blacklist in the configuration)
disallows certain pairs from trading. This can be as simple as excluding DOGE/BTC -
which will remove exactly this pair.

The pair-blacklist does also support wildcards (in regex-style) - so BNB/.* will
exclude ALL pairs that start with BNB. You may also use something like .*DOWN/BTC
or .*UP/BTC to exclude leveraged tokens (check Pair naming conventions for your
exchange!)

Available Pairlist Handlers


StaticPairList (default, if not configured differently)
VolumePairList
PercentChangePairList
ProducerPairList
RemotePairList
MarketCapPairList
AgeFilter
FullTradesFilter
OffsetFilter
PerformanceFilter
PrecisionFilter
PriceFilter
ShuffleFilter
SpreadFilter
RangeStabilityFilter
VolatilityFilter
Testing pairlists

Pairlist configurations can be quite tricky to get right. Best use the test-
pairlist utility sub-command to test your configuration quickly.

Static Pair List


By default, the StaticPairList method is used, which uses a statically defined pair
whitelist from the configuration. The pairlist also supports wildcards (in regex-
style) - so .*/BTC will include all pairs with BTC as a stake.

It uses configuration from exchange.pair_whitelist and exchange.pair_blacklist.

"pairlists": [
{"method": "StaticPairList"}
],
By default, only currently enabled pairs are allowed. To skip pair validation
against active markets, set "allow_inactive": true within the StaticPairList
configuration. This can be useful for backtesting expired pairs (like quarterly
spot-markets). This option must be configured along with
exchange.skip_pair_validation in the exchange configuration.

When used in a "follow-up" position (e.g. after VolumePairlist), all pairs in


'pair_whitelist' will be added to the end of the pairlist.

Volume Pair List


VolumePairList employs sorting/filtering of pairs by their trading volume. It
selects number_assets top pairs with sorting based on the sort_key (which can only
be quoteVolume).

When used in the chain of Pairlist Handlers in a non-leading position (after


StaticPairList and other Pairlist Filters), VolumePairList considers outputs of
previous Pairlist Handlers, adding its sorting/selection of the pairs by the
trading volume.

When used in the leading position of the chain of Pairlist Handlers, the
pair_whitelist configuration setting is ignored. Instead, VolumePairList selects
the top assets from all available markets with matching stake-currency on the
exchange.

The refresh_period setting allows to define the period (in seconds), at which the
pairlist will be refreshed. Defaults to 1800s (30 minutes). The pairlist cache
(refresh_period) on VolumePairList is only applicable to generating pairlists.
Filtering instances (not the first position in the list) will not apply any cache
(beyond caching candles for the duration of the candle in advanced mode) and will
always use up-to-date data.

VolumePairList is per default based on the ticker data from exchange, as reported
by the ccxt library:

The quoteVolume is the amount of quote (stake) currency traded (bought or sold) in
last 24 hours.

"pairlists": [
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"min_value": 0,
"max_value": 8000000,
"refresh_period": 1800
}
],
You can define a minimum volume with min_value - which will filter out pairs with a
volume lower than the specified value in the specified timerange. In addition to
that, you can also define a maximum volume with max_value - which will filter out
pairs with a volume higher than the specified value in the specified timerange.

VolumePairList Advanced mode


VolumePairList can also operate in an advanced mode to build volume over a given
timerange of specified candle size. It utilizes exchange historical candle data,
builds a typical price (calculated by (open+high+low)/3) and multiplies the typical
price with every candle's volume. The sum is the quoteVolume over the given range.
This allows different scenarios, for a more smoothened volume, when using longer
ranges with larger candle sizes, or the opposite when using a short range with
small candles.

For convenience lookback_days can be specified, which will imply that 1d candles
will be used for the lookback. In the example below the pairlist would be created
based on the last 7 days:

"pairlists": [
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 7
}
],
Range look back and refresh period

When used in conjunction with lookback_days and lookback_timeframe the


refresh_period can not be smaller than the candle size in seconds. As this will
result in unnecessary requests to the exchanges API.

Performance implications when using lookback range

If used in first position in combination with lookback, the computation of the


range based volume can be time and resource consuming, as it downloads candles for
all tradable pairs. Hence it's highly advised to use the standard approach with
VolumeFilter to narrow the pairlist down for further range volume calculation.

Unsupported exchanges
More sophisticated approach can be used, by using lookback_timeframe for candle
size and lookback_period which specifies the amount of candles. This example will
build the volume pairs based on a rolling period of 3 days of 1h candles:

"pairlists": [
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"min_value": 0,
"refresh_period": 3600,
"lookback_timeframe": "1h",
"lookback_period": 72
}
],
Note

VolumePairList does not support backtesting mode.

Percent Change Pair List


PercentChangePairList filters and sorts pairs based on the percentage change in
their price over the last 24 hours or any defined timeframe as part of advanced
options. This allows traders to focus on assets that have experienced significant
price movements, either positive or negative.

Configuration Options

number_assets: Specifies the number of top pairs to select based on the 24-hour
percentage change.
min_value: Sets a minimum percentage change threshold. Pairs with a percentage
change below this value will be filtered out.
max_value: Sets a maximum percentage change threshold. Pairs with a percentage
change above this value will be filtered out.
sort_direction: Specifies the order in which pairs are sorted based on their
percentage change. Accepts two values: asc for ascending order and desc for
descending order.
refresh_period: Defines the interval (in seconds) at which the pairlist will be
refreshed. The default is 1800 seconds (30 minutes).
lookback_days: Number of days to look back. When lookback_days is selected, the
lookback_timeframe is defaulted to 1 day.
lookback_timeframe: Timeframe to use for the lookback period.
lookback_period: Number of periods to look back at.
When PercentChangePairList is used after other Pairlist Handlers, it will operate
on the outputs of those handlers. If it is the leading Pairlist Handler, it will
select pairs from all available markets with the specified stake currency.

PercentChangePairList uses ticker data from the exchange, provided via the ccxt
library: The percentage change is calculated as the change in price over the last
24 hours.

Unsupported exchanges
Example Configuration to Read from Ticker

"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"min_value": -10,
"max_value": 50
}
],
In this configuration:

The top 15 pairs are selected based on the highest percentage change in price over
the last 24 hours.
Only pairs with a percentage change between -10% and 50% are considered.
Example Configuration to Read from Candles

"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 3600,
"lookback_timeframe": "1h",
"lookback_period": 72
}
],
This example builds the percent change pairs based on a rolling period of 3 days of
1-hour candles by using lookback_timeframe for candle size and lookback_period
which specifies the number of candles.

The percent change in price is calculated using the following formula, which
expresses the percentage difference between the current candle's close price and
the previous candle's close price, as defined by the specified timeframe and
lookback period:
Range look back and refresh period

When used in conjunction with lookback_days and lookback_timeframe the


refresh_period can not be smaller than the candle size in seconds. As this will
result in unnecessary requests to the exchanges API.

Performance implications when using lookback range

If used in first position in combination with lookback, the computation of the


range-based percent change can be time and resource consuming, as it downloads
candles for all tradable pairs. Hence it's highly advised to use the standard
approach with PercentChangePairList to narrow the pairlist down for further
percent-change calculation.

Backtesting

PercentChangePairList does not support backtesting mode.

ProducerPairList
With ProducerPairList, you can reuse the pairlist from a Producer without
explicitly defining the pairlist on each consumer.

Consumer mode is required for this pairlist to work.

The pairlist will perform a check on active pairs against the current exchange
configuration to avoid attempting to trade on invalid markets.

You can limit the length of the pairlist with the optional parameter number_assets.
Using "number_assets"=0 or omitting this key will result in the reuse of all
producer pairs valid for the current setup.

"pairlists": [
{
"method": "ProducerPairList",
"number_assets": 5,
"producer_name": "default",
}
],
Combining pairlists

This pairlist can be combined with all other pairlists and filters for further
pairlist reduction, and can also act as an "additional" pairlist, on top of already
defined pairs. ProducerPairList can also be used multiple times in sequence,
combining the pairs from multiple producers. Obviously in complex such
configurations, the Producer may not provide data for all pairs, so the strategy
must be fit for this.

RemotePairList
It allows the user to fetch a pairlist from a remote server or a locally stored
json file within the freqtrade directory, enabling dynamic updates and
customization of the trading pairlist.

The RemotePairList is defined in the pairlists section of the configuration


settings. It uses the following configuration options:
"pairlists": [
{
"method": "RemotePairList",
"mode": "whitelist",
"processing_mode": "filter",
"pairlist_url": "[Link]
"number_assets": 10,
"refresh_period": 1800,
"keep_pairlist_on_failure": true,
"read_timeout": 60,
"bearer_token": "my-bearer-token",
"save_to_file": "user_data/[Link]"
}
]
The optional mode option specifies if the pairlist should be used as a blacklist or
as a whitelist. The default value is "whitelist".

The optional processing_mode option in the RemotePairList configuration determines


how the retrieved pairlist is processed. It can have two values: "filter" or
"append". The default value is "filter".

In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs
present in both the original pairlist and the retrieved pairlist are included in
the final pairlist. Other pairs are filtered out.

In "append" mode, the retrieved pairlist is added to the original pairlist. All
pairs from both lists are included in the final pairlist without any filtering.

The pairlist_url option specifies the URL of the remote server where the pairlist
is located, or the path to a local file (if [Link] is prepended). This allows the
user to use either a remote server or a local file as the source for the pairlist.

The save_to_file option, when provided with a valid filename, saves the processed
pairlist to that file in JSON format. This option is optional, and by default, the
pairlist is not saved to a file.

Multi bot with shared pairlist example


The user is responsible for providing a server or local file that returns a JSON
object with the following structure:

{
"pairs": ["XRP/USDT", "ETH/USDT", "LTC/USDT"],
"refresh_period": 1800
}
The pairs property should contain a list of strings with the trading pairs to be
used by the bot. The refresh_period property is optional and specifies the number
of seconds that the pairlist should be cached before being refreshed.

The optional keep_pairlist_on_failure specifies whether the previous received


pairlist should be used if the remote server is not reachable or returns an error.
The default value is true.

The optional read_timeout specifies the maximum amount of time (in seconds) to wait
for a response from the remote source, The default value is 60.

The optional bearer_token will be included in the requests Authorization Header.

Note
In case of a server error the last received pairlist will be kept if
keep_pairlist_on_failure is set to true, when set to false a empty pairlist is
returned.

MarketCapPairList
MarketCapPairList employs sorting/filtering of pairs by their marketcap rank based
of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The
returned pairlist will be sorted based of their marketcap ranks.

"pairlists": [
{
"method": "MarketCapPairList",
"number_assets": 20,
"max_rank": 50,
"refresh_period": 86400
}
]
number_assets defines the maximum number of pairs returned by the pairlist.
max_rank will determine the maximum rank used in creating/filtering the pairlist.
It's expected that some coins within the top max_rank marketcap will not be
included in the resulting pairlist since not all pairs will have active trading
pairs in your preferred market/stake/exchange combination.

refresh_period setting defines the period (in seconds) at which the marketcap rank
data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache
(refresh_period) is applicable on both generating pairlists (first position in the
list) and filtering instances (not the first position in the list).

AgeFilter
Removes pairs that have been listed on the exchange for less than min_days_listed
days (defaults to 10) or more than max_days_listed days (defaults None mean
infinity).

When pairs are first listed on an exchange they can suffer huge price drops and
volatility in the first few days while the pair goes through its price-discovery
period. Bots can often be caught out buying before the pair has finished dropping
in price.

This filter allows freqtrade to ignore pairs until they have been listed for at
least min_days_listed days and listed before max_days_listed.

FullTradesFilter
Shrink whitelist to consist only in-trade pairs when the trade slots are full (when
max_open_trades isn't being set to -1 in the config).

When the trade slots are full, there is no need to calculate indicators of the rest
of the pairs (except informative pairs) since no new trade can be opened. By
shrinking the whitelist to just the in-trade pairs, you can improve calculation
speeds and reduce CPU usage. When a trade slot is free (either a trade is closed or
max_open_trades value in config is increased), then the whitelist will return to
normal state.

When multiple pairlist filters are being used, it's recommended to put this filter
at second position directly below the primary pairlist, so when the trade slots are
full, the bot doesn't have to download data for the rest of the filters.

Backtesting
FullTradesFilter does not support backtesting mode.

OffsetFilter
Offsets an incoming pairlist by a given offset value.

As an example it can be used in conjunction with VolumeFilter to remove the top X


volume pairs. Or to split a larger pairlist on two bot instances.

Example to remove the first 10 pairs from the pairlist, and takes the next 20
(taking items 10-30 of the initial list):

"pairlists": [
// ...
{
"method": "OffsetFilter",
"offset": 10,
"number_assets": 20
}
],
Warning

When OffsetFilter is used to split a larger pairlist among multiple bots in


combination with VolumeFilter it can not be guaranteed that pairs won't overlap due
to slightly different refresh intervals for the VolumeFilter.

Note

An offset larger than the total length of the incoming pairlist will result in an
empty pairlist.

PerformanceFilter
Sorts pairs by past trade performance, as follows:

Positive performance.
No closed trades yet.
Negative performance.
Trade count is used as a tie breaker.

You can use the minutes parameter to only consider performance of the past X
minutes (rolling window). Not defining this parameter (or setting it to 0) will use
all-time performance.

The optional min_profit (as ratio -> a setting of 0.01 corresponds to 1%) parameter
defines the minimum profit a pair must have to be considered. Pairs below this
level will be filtered out. Using this parameter without minutes is highly
discouraged, as it can lead to an empty pairlist without a way to recover.

"pairlists": [
// ...
{
"method": "PerformanceFilter",
"minutes": 1440, // rolling 24h
"min_profit": 0.01 // minimal profit 1%
}
],
As this Filter uses past performance of the bot, it'll have some startup-period -
and should only be used after the bot has a few 100 trades in the database.

Backtesting

PerformanceFilter does not support backtesting mode.

PrecisionFilter
Filters low-value coins which would not allow setting stoplosses.

Namely, pairs are blacklisted if a variance of one percent or more in the stop
price would be caused by precision rounding on the exchange, i.e.
rounded(stop_price) <= rounded(stop_price * 0.99). The idea is to avoid coins with
a value VERY close to their lower trading boundary, not allowing setting of proper
stoploss.

PrecisionFilter is pointless for futures trading

The above does not apply to shorts. And for longs, in theory the trade will be
liquidated first.

Backtesting

PrecisionFilter does not support backtesting mode using multiple strategies.

PriceFilter
The PriceFilter allows filtering of pairs by price. Currently the following price
filters are supported:

min_price
max_price
max_value
low_price_ratio
The min_price setting removes pairs where the price is below the specified price.
This is useful if you wish to avoid trading very low-priced pairs. This option is
disabled by default, and will only apply if set to > 0.

The max_price setting removes pairs where the price is above the specified price.
This is useful if you wish to trade only low-priced pairs. This option is disabled
by default, and will only apply if set to > 0.

The max_value setting removes pairs where the minimum value change is above a
specified value. This is useful when an exchange has unbalanced limits. For
example, if step-size = 1 (so you can only buy 1, or 2, or 3, but not 1.1 Coins) -
and the price is pretty high (like 20$) as the coin has risen sharply since the
last limit adaption. As a result of the above, you can only buy for 20$, or 40$ -
but not for 25$. On exchanges that deduct fees from the receiving currency (e.g.
binance) - this can result in high value coins / amounts that are unsellable as the
amount is slightly below the limit.

The low_price_ratio setting removes pairs where a raise of 1 price unit (pip) is
above the low_price_ratio ratio. This option is disabled by default, and will only
apply if set to > 0.

For PriceFilter at least one of its min_price, max_price or low_price_ratio


settings must be applied.

Calculation example:

Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 -


one price step above would be 0.00000012, which is ~9% higher than the previous
price value. You may filter out this pair by using PriceFilter with low_price_ratio
set to 0.09 (9%) or with min_price set to 0.00000011, correspondingly.

Low priced pairs

Low priced pairs with high "1 pip movements" are dangerous since they are often
illiquid and it may also be impossible to place the desired stoploss, which can
often result in high losses since price needs to be rounded to the next tradable
price - so instead of having a stoploss of -5%, you could end up with a stoploss of
-9% simply due to price rounding.

ShuffleFilter
Shuffles (randomizes) pairs in the pairlist. It can be used for preventing the bot
from trading some of the pairs more frequently then others when you want all pairs
be treated with the same priority.

By default, ShuffleFilter will shuffle pairs once per candle. To shuffle on every
iteration, set "shuffle_frequency" to "iteration" instead of the default of
"candle".

{
"method": "ShuffleFilter",
"shuffle_frequency": "candle",
"seed": 42
}
Tip

You may set the seed value for this Pairlist to obtain reproducible results, which
can be useful for repeated backtesting sessions. If seed is not set, the pairs are
shuffled in the non-repeatable random order. ShuffleFilter will automatically
detect runmodes and apply the seed only for backtesting modes - if a seed value is
set.

SpreadFilter
Removes pairs that have a difference between asks and bids above the specified
ratio, max_spread_ratio (defaults to 0.005).

Example:

If DOGE/BTC maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio is
calculated as: 1 - bid/ask ~= 0.037 which is > 0.005 and this pair will be filtered
out.

RangeStabilityFilter
Removes pairs where the difference between lowest low and highest high over
lookback_days days is below min_rate_of_change or above max_rate_of_change. Since
this is a filter that requires additional data, the results are cached for
refresh_period.

In the below example: If the trading range over the last 10 days is <1% or >99%,
remove the pair from the whitelist.

"pairlists": [
{
"method": "RangeStabilityFilter",
"lookback_days": 10,
"min_rate_of_change": 0.01,
"max_rate_of_change": 0.99,
"refresh_period": 86400
}
]
Adding "sort_direction": "asc" or "sort_direction": "desc" enables sorting for this
pairlist.

Tip

This Filter can be used to automatically remove stable coin pairs, which have a
very low trading range, and are therefore extremely difficult to trade with profit.
Additionally, it can also be used to automatically remove pairs with extreme
high/low variance over a given amount of time.

VolatilityFilter
Volatility is the degree of historical variation of a pairs over time, it is
measured by the standard deviation of logarithmic daily returns. Returns are
assumed to be normally distributed, although actual distribution might be
different. In a normal distribution, 68% of observations fall within one standard
deviation and 95% of observations fall within two standard deviations. Assuming a
volatility of 0.05 means that the expected returns for 20 out of 30 days is
expected to be less than 5% (one standard deviation). Volatility is a positive
ratio of the expected deviation of return and can be greater than 1.00. Please
refer to the wikipedia definition of volatility.

This filter removes pairs if the average volatility over a lookback_days days is
below min_volatility or above max_volatility. Since this is a filter that requires
additional data, the results are cached for refresh_period.

This filter can be used to narrow down your pairs to a certain volatility or avoid
very volatile pairs.

In the below example: If the volatility over the last 10 days is not in the range
of 0.05-0.50, remove the pair from the whitelist. The filter is applied every 24h.

"pairlists": [
{
"method": "VolatilityFilter",
"lookback_days": 10,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 86400
}
]
Adding "sort_direction": "asc" or "sort_direction": "desc" enables sorting mode for
this pairlist.

Full example of Pairlist Handlers


The below example blacklists BNB/BTC, uses VolumePairList with 20 assets, sorting
pairs by quoteVolume and applies PrecisionFilter and PriceFilter, filtering all
assets where 1 price unit is > 1%. Then the SpreadFilter and VolatilityFilter is
applied and pairs are finally shuffled with the random seed set to some predefined
value.

"exchange": {
"pair_whitelist": [],
"pair_blacklist": ["BNB/BTC"]
},
"pairlists": [
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume"
},
{"method": "AgeFilter", "min_days_listed": 10},
{"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.01},
{"method": "SpreadFilter", "max_spread_ratio": 0.005},
{
"method": "RangeStabilityFilter",
"lookback_days": 10,
"min_rate_of_change": 0.01,
"refresh_period": 86400
},
{
"method": "VolatilityFilter",
"lookback_days": 10,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 86400
},
{"method": "ShuffleFilter", "seed": 42}
],
Protections
Beta feature

This feature is still in it's testing phase. Should you notice something you think
is wrong please let us know via Discord or via Github Issue.

Protections will protect your strategy from unexpected events and market conditions
by temporarily stop trading for either one pair, or for all pairs. All protection
end times are rounded up to the next candle to avoid sudden, unexpected intra-
candle buys.

Note

Not all Protections will work for all strategies, and parameters will need to be
tuned for your strategy to improve performance.

Tip

Each Protection can be configured multiple times with different parameters, to


allow different levels of protection (short-term / long-term).

Backtesting

Protections are supported by backtesting and hyperopt, but must be explicitly


enabled by using the --enable-protections flag.

Setting protections from the configuration

Setting protections from the configuration via "protections": [], key should be
considered deprecated and will be removed in a future version. It is also no longer
guaranteed that your protections apply to the strategy in cases where the strategy
defines protections as property.
Available Protections
StoplossGuard Stop trading if a certain amount of stoploss occurred within a
certain time window.
MaxDrawdown Stop trading if max-drawdown is reached.
LowProfitPairs Lock pairs with low profits
CooldownPeriod Don't enter a trade right after selling a trade.
Common settings to all Protections
Parameter Description
method Protection name to use.
Datatype: String, selected from available Protections
stop_duration_candles For how many candles should the lock be set?
Datatype: Positive integer (in candles)
stop_duration how many minutes should protections be locked.
Cannot be used together with stop_duration_candles.
Datatype: Float (in minutes)
lookback_period_candles Only trades that completed within the last
lookback_period_candles candles will be considered. This setting may be ignored by
some Protections.
Datatype: Positive integer (in candles).
lookback_period Only trades that completed after current_time - lookback_period
will be considered.
Cannot be used together with lookback_period_candles.
This setting may be ignored by some Protections.
Datatype: Float (in minutes)
trade_limit Number of trades required at minimum (not used by all Protections).
Datatype: Positive integer
unlock_at Time when trading will be unlocked regularly (not used by all
Protections).
Datatype: string
Input Format: "HH:MM" (24-hours)
Durations

Durations (stop_duration* and lookback_period* can be defined in either minutes or


candles). For more flexibility when testing different timeframes, all below
examples will use the "candle" definition.

Stoploss Guard
StoplossGuard selects all trades within lookback_period in minutes (or in candles
when using lookback_period_candles). If trade_limit or more trades resulted in
stoploss, trading will stop for stop_duration in minutes (or in candles when using
stop_duration_candles, or until the set time when using unlock_at).

This applies across all pairs, unless only_per_pair is set to true, which will then
only look at one pair at a time.

Similarly, this protection will by default look at all trades (long and short). For
futures bots, setting only_per_side will make the bot only consider one side, and
will then only lock this one side, allowing for example shorts to continue after a
series of long stoplosses.

required_profit will determine the required relative profit (or loss) for
stoplosses to consider. This should normally not be set and defaults to 0.0 - which
means all losing stoplosses will be triggering a block.

The below example stops trading for all pairs for 4 candles after the last trade if
the bot hit stoploss 4 times within the last 24 candles.
@property
def protections(self):
return [
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 4,
"required_profit": 0.0,
"only_per_pair": False,
"only_per_side": False
}
]
Note

StoplossGuard considers all trades with the results "stop_loss",


"stoploss_on_exchange" and "trailing_stop_loss" if the resulting profit was
negative. trade_limit and lookback_period will need to be tuned for your strategy.

MaxDrawdown
MaxDrawdown uses all trades within lookback_period in minutes (or in candles when
using lookback_period_candles) to determine the maximum drawdown. If the drawdown
is below max_allowed_drawdown, trading will stop for stop_duration in minutes (or
in candles when using stop_duration_candles) after the last trade - assuming that
the bot needs some time to let markets recover.

The below sample stops trading for 12 candles if max-drawdown is > 20% considering
all pairs - with a minimum of trade_limit trades - within the last 48 candles. If
desired, lookback_period and/or stop_duration can be used.

@property
def protections(self):
return [
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 12,
"max_allowed_drawdown": 0.2
},
]
Low Profit Pairs
LowProfitPairs uses all trades for a pair within lookback_period in minutes (or in
candles when using lookback_period_candles) to determine the overall profit ratio.
If that ratio is below required_profit, that pair will be locked for stop_duration
in minutes (or in candles when using stop_duration_candles, or until the set time
when using unlock_at).

For futures bots, setting only_per_side will make the bot only consider one side,
and will then only lock this one side, allowing for example shorts to continue
after a series of long losses.

The below example will stop trading a pair for 60 minutes if the pair does not have
a required profit of 2% (and a minimum of 2 trades) within the last 6 candles.

@property
def protections(self):
return [
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration": 60,
"required_profit": 0.02,
"only_per_pair": False,
}
]
Cooldown Period
CooldownPeriod locks a pair for stop_duration in minutes (or in candles when using
stop_duration_candles, or until the set time when using unlock_at) after exiting,
avoiding a re-entry for this pair for stop_duration minutes.

The below example will stop trading a pair for 2 candles after closing a trade,
allowing this pair to "cool down".

@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 2
}
]
Note

This Protection applies only at pair-level, and will never lock all pairs globally.
This Protection does not consider lookback_period as it only looks at the latest
trade.

Full example of Protections


All protections can be combined at will, also with different parameters, creating a
increasing wall for under-performing pairs. All protections are evaluated in the
sequence they are defined.

The below example assumes a timeframe of 1 hour:

Locks each pair after selling for an additional 5 candles (CooldownPeriod), giving
other pairs a chance to get filled.
Stops trading for 4 hours (4 * 1h candles) if the last 2 days (48 * 1h candles) had
20 trades, which caused a max-drawdown of more than 20%. (MaxDrawdown).
Stops trading if more than 4 stoploss occur for all pairs within a 1 day (24 * 1h
candles) limit (StoplossGuard).
Locks all pairs that had 2 Trades within the last 6 hours (6 * 1h candles) with a
combined profit ratio of below 0.02 (<2%) (LowProfitPairs).
Locks all pairs for 2 candles that had a profit of below 0.01 (<1%) within the last
24h (24 * 1h candles), a minimum of 4 trades.

from [Link] import IStrategy

class AwesomeStrategy(IStrategy)
timeframe = '1h'

@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 4,
"max_allowed_drawdown": 0.2
},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"only_per_pair": False
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration_candles": 60,
"required_profit": 0.02
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"required_profit": 0.01
}
]
# ...

Start the bot


This page explains the different parameters of the bot and how to run it.

Note

If you've used [Link], don't forget to activate your virtual environment


(source .venv/bin/activate) before running freqtrade commands.

Up-to-date clock

The clock on the system running the bot must be accurate, synchronized to a NTP
server frequently enough to avoid problems with communication to the exchanges.

Bot commands

usage: freqtrade [-h] [-V]


{trade,create-userdir,new-config,show-config,new-
strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-
data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-
list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-
freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-
dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-
analysis}
...
Free, open source crypto trading bot

positional arguments:
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-
data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-
show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-
exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-
timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-
profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
show-config Show resolved config
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
trades-to-ohlcv Convert trade data to OHLCV data.
list-data List downloaded data.
backtesting Backtesting module.
backtesting-show Show past Backtest results
backtesting-analysis
Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges.
list-markets Print markets on exchange.
list-pairs Print pairs on exchange.
list-strategies Print available strategies.
list-freqaimodels Print available freqAI models.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
convert-db Migrate database to different system
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
strategy-updater updates outdated strategy files to the current version
lookahead-analysis Check for potential look ahead bias.
recursive-analysis Check for potential recursive formula issue.

options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
Bot trading commands

usage: freqtrade trade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[--db-url PATH] [--sd-notify] [--dry-run]
[--dry-run-wallet DRY_RUN_WALLET]

optional arguments:
-h, --help show this help message and exit
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///[Link]` for
Live Run mode, `sqlite:///[Link]` for
Dry Run).
--sd-notify Notify systemd service manager.
--dry-run Enforce dry-run for trading (removes Exchange secrets
and simulates trades).
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
Starting balance, used for backtesting / hyperopt and
dry-runs.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.

Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
How to specify which configuration file be used?
The bot allows you to select which configuration file you want to use by means of
the -c/--config command line option:

freqtrade trade -c path/far/far/away/[Link]


Per default, the bot loads the [Link] configuration file from the current
working directory.

How to use multiple configuration files?


The bot allows you to use multiple configuration files by specifying multiple -c/--
config options in the command line. Configuration parameters defined in the latter
configuration files override parameters with the same name defined in the previous
configuration files specified in the command line earlier.

For example, you can make a separate configuration file with your key and secret
for the Exchange you use for trading, specify default configuration file with empty
key and secret values while running in the Dry Mode (which does not actually
require them):

freqtrade trade -c ./[Link]


and specify both configuration files when running in the normal Live Trade Mode:

freqtrade trade -c ./[Link] -c path/to/secrets/[Link]


This could help you hide your private Exchange key and Exchange secret on you local
machine by setting appropriate file permissions for the file which contains actual
secrets and, additionally, prevent unintended disclosure of sensitive private data
when you publish examples of your configuration in the project issues or in the
Internet.

See more details on this technique with examples in the documentation page on
configuration.

Where to store custom data


Freqtrade allows the creation of a user-data directory using freqtrade create-
userdir --userdir someDirectory. This directory will look as follows:

user_data/
├── backtest_results
├── data
├── hyperopts
├── hyperopt_results
├── plot
└── strategies
You can add the entry "user_data_dir" setting to your configuration, to always
point your bot to this directory. Alternatively, pass in --userdir to every
command. The bot will fail to start if the directory does not exist, but will
create necessary subdirectories.

This directory should contain your custom strategies, custom hyperopts and hyperopt
loss functions, backtesting historical data (downloaded using either backtesting
command or the download script) and plot outputs.

It is recommended to use version control to keep track of changes to your


strategies.

How to use --strategy?


This parameter will allow you to load your custom strategy class. To test the bot
installation, you can use the SampleStrategy installed by the create-userdir
subcommand (usually user_data/strategy/sample_strategy.py).

The bot will search your strategy file within user_data/strategies. To use other
directories, please read the next section about --strategy-path.

To load a strategy, simply pass the class name (e.g.: CustomStrategy) in this
parameter.

Example: In user_data/strategies you have a file my_awesome_strategy.py which has a


strategy class called AwesomeStrategy to load it:

freqtrade trade --strategy AwesomeStrategy


If the bot does not find your strategy file, it will display in an error message
the reason (File not found, or errors in your code).

Learn more about strategy file in Strategy Customization.

How to use --strategy-path?


This parameter allows you to add an additional strategy lookup path, which gets
checked before the default locations (The passed path must be a directory!):

freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory


How to install a strategy?
This is very simple. Copy paste your strategy file into the directory
user_data/strategies or use --strategy-path. And voila, the bot is ready to use it.

How to use --db-url?


When you run the bot in Dry-run mode, per default no transactions are stored in a
database. If you want to store your bot actions in a DB using --db-url. This can
also be used to specify a custom database in production mode. Example command:

freqtrade trade -c [Link] --db-url sqlite:///tradesv3.dry_run.sqlite


Next step
The optimal strategy of the bot will change with time depending of the market
trends. The next step is to Strategy Customization.

Data Downloading
Getting data for backtesting and hyperopt
To download data (candles / OHLCV) needed for backtesting and hyperoptimization use
the freqtrade download-data command.

If no additional parameter is specified, freqtrade will download data for "1m" and
"5m" timeframes for the last 30 days. Exchange and pairs will come from [Link]
(if specified using -c/--config). Without provided configuration, --exchange
becomes mandatory.

You can use a relative timerange (--days 20) or an absolute starting point (--
timerange 20200101-). For incremental downloads, the relative approach should be
used.

Tip: Updating existing data

If you already have backtesting data available in your data-directory and would
like to refresh this data up to today, freqtrade will automatically calculate the
data missing for the existing pairs and the download will occur from the latest
available point until "now", neither --days or --timerange parameters are required.
Freqtrade will keep the available data and only download the missing data. If you
are updating existing data after inserting new pairs that you have no data for, use
--new-pairs-days xx parameter. Specified number of days will be downloaded for new
pairs while old pairs will be updated with missing data only. If you use --days xx
parameter alone - data for specified number of days will be downloaded for all
pairs. Be careful, if specified number of days is smaller than gap between now and
last downloaded candle - freqtrade will delete all existing data to avoid gaps in
candle data.

Usage

usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] [--pairs-file FILE]
[--days INT] [--new-pairs-days INT]
[--include-inactive-pairs]
[--timerange TIMERANGE] [--dl-trades]
[--convert] [--exchange EXCHANGE]
[-t TIMEFRAMES [TIMEFRAMES ...]] [--erase]
[--data-format-ohlcv
{json,jsongz,hdf5,feather,parquet}]
[--data-format-trades
{json,jsongz,hdf5,feather,parquet}]
[--trading-mode {spot,margin,futures}]
[--prepend]
options:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--pairs-file FILE File containing a list of pairs. Takes precedence over
--pairs or pairs configured in the configuration.
--days INT Download data for given number of days.
--new-pairs-days INT Download data of new pairs for given number of days.
Default: `None`.
--include-inactive-pairs
Also download data from inactive pairs.
--timerange TIMERANGE
Specify what timerange of data to use.
--dl-trades Download trades instead of OHLCV data. The bot will
resample trades to the desired timeframe as specified
as --timeframes/-t.
--convert Convert downloaded trades to OHLCV data. Only
applicable in combination with `--dl-trades`. Will be
automatic for exchanges which don't have historic
OHLCV (e.g. Kraken). If not provided, use `trades-to-
ohlcv` to convert trades data to OHLCV data.
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
-t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
--erase Clean all existing data for the selected
exchange/pairs/timeframes.
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
--data-format-trades {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded trades data. (default:
`feather`).
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode
--prepend Allow data prepending. (Data-appending is disabled)

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Downloading all data for one quote currency

Often, you'll want to download data for all pairs of a specific quote-currency. In
such cases, you can use the following shorthand: freqtrade download-data --exchange
binance --pairs .*/USDT <...>. The provided "pairs" string will be expanded to
contain all active pairs on the exchange. To also download data for inactive
(delisted) pairs, add --include-inactive-pairs to the command.

Startup period

download-data is a strategy-independent command. The idea is to download a big


chunk of data once, and then iteratively increase the amount of data stored.

For that reason, download-data does not care about the "startup-period" defined in
a strategy. It's up to the user to download additional days if the backtest should
start at a specific point in time (while respecting startup period).

Start download
A very simple command (assuming an available [Link] file) can look as follows.

freqtrade download-data --exchange binance


This will download historical candle (OHLCV) data for all the currency pairs
defined in the configuration.

Alternatively, specify the pairs directly

freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT


or as regex (in this case, to download all active USDT pairs)

freqtrade download-data --exchange binance --pairs .*/USDT


Other Notes
To use a different directory than the exchange specific default, use --datadir
user_data/data/some_directory.
To change the exchange used to download the historical data from, please use a
different configuration file (you'll probably need to adjust rate limits etc.)
To use [Link] from some other directory, use --pairs-file
some_other_dir/[Link].
To download historical candle (OHLCV) data for only 10 days, use --days 10
(defaults to 30 days).
To download historical candle (OHLCV) data from a fixed starting point, use --
timerange 20200101- - which will download all data from January 1st, 2020.
Use --timeframes to specify what timeframe download the historical candle (OHLCV)
data for. Default is --timeframes 1m 5m which will download 1-minute and 5-minute
data.
To use exchange, timeframe and list of pairs as defined in your configuration file,
use the -c/--config option. With this, the script uses the whitelist defined in the
config as the list of currency pairs to download data for and does not require the
[Link] file. You can combine -c/--config with most other options.
Permission denied errors
Download additional data before the current timerange
Assuming you downloaded all data from 2022 (--timerange 20220101-) - but you'd now
like to also backtest with earlier data. You can do so by using the --prepend flag,
combined with --timerange - specifying an end-date.

freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT --


prepend --timerange 20210101-20220101
Note

Freqtrade will ignore the end-date in this mode if data is available, updating the
end-date to the existing data start point.

Data format
Freqtrade currently supports the following data-formats:

feather - a dataformat based on Apache Arrow


json - plain "text" json files
jsongz - a gzip-zipped version of json files
hdf5 - a high performance datastore
parquet - columnar datastore (OHLCV only)
By default, both OHLCV data and trades data are stored in the feather format.

This can be changed via the --data-format-ohlcv and --data-format-trades command


line arguments respectively. To persist this change, you should also add the
following snippet to your configuration, so you don't have to insert the above
arguments each time:

// ...
"dataformat_ohlcv": "hdf5",
"dataformat_trades": "hdf5",
// ...
If the default data-format has been changed during download, then the keys
dataformat_ohlcv and dataformat_trades in the configuration file need to be
adjusted to the selected dataformat as well.

Note

You can convert between data-formats using the convert-data and convert-trade-data
methods.

Dataformat comparison
The following comparisons have been made with the following data, and by using the
linux time command.

Found 6 pair / timeframe combinations.


+----------+-------------+--------+---------------------+---------------------+
| Pair | Timeframe | Type | From | To |
|----------+-------------+--------+---------------------+---------------------|
| BTC/USDT | 5m | spot | 2017-08-17 [Link] | 2022-09-13 [Link] |
| ETH/USDT | 1m | spot | 2017-08-17 [Link] | 2022-09-13 [Link] |
| BTC/USDT | 1m | spot | 2017-08-17 [Link] | 2022-09-13 [Link] |
| XRP/USDT | 5m | spot | 2018-05-04 [Link] | 2022-09-13 [Link] |
| XRP/USDT | 1m | spot | 2018-05-04 [Link] | 2022-09-13 [Link] |
| ETH/USDT | 5m | spot | 2017-08-17 [Link] | 2022-09-13 [Link] |
+----------+-------------+--------+---------------------+---------------------+
Timings have been taken in a not very scientific way with the following command,
which forces reading the data into memory.

time freqtrade list-data --show-timerange --data-format-ohlcv <dataformat>


Format Size timing
feather 72Mb 3.5s
json 149Mb 25.6s
jsongz 39Mb 27s
hdf5 145Mb 3.9s
parquet 83Mb 3.8s
Size has been taken from the BTC/USDT 1m spot combination for the timerange
specified above.

To have a best performance/size mix, we recommend using the default feather format,
or parquet.

Pairs file
In alternative to the whitelist from [Link], a [Link] file can be used. If
you are using Binance for example:

create a directory user_data/data/binance and copy or create the [Link] file in


that directory.
update the [Link] file to contain the currency pairs you are interested in.

mkdir -p user_data/data/binance
touch user_data/data/binance/[Link]
The format of the [Link] file is a simple json list. Mixing different stake-
currencies is allowed for this file, since it's only used for downloading.

[
"ETH/BTC",
"ETH/USDT",
"BTC/USDT",
"XRP/ETH"
]
Note

The [Link] file is only used when no configuration is loaded (implicitly by


naming, or via --config flag). You can force the usage of this file via --pairs-
file [Link] - however we recommend to use the pairlist from within the
configuration, either via exchange.pair_whitelist or pairs setting in the
configuration.

Sub-command convert data

usage: freqtrade convert-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from
{json,jsongz,hdf5,feather,parquet} --format-to
{json,jsongz,hdf5,feather,parquet} [--erase]
[--exchange EXCHANGE]
[-t TIMEFRAMES [TIMEFRAMES ...]]
[--trading-mode {spot,margin,futures}]
[--candle-types
{spot,futures,mark,index,premiumIndex,funding_rate}
[{spot,futures,mark,index,premiumIndex,funding_rate} ...]]

options:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--format-from {json,jsongz,hdf5,feather,parquet}
Source format for data conversion.
--format-to {json,jsongz,hdf5,feather,parquet}
Destination format for data conversion.
--erase Clean all existing data for the selected
exchange/pairs/timeframes.
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
-t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode
--candle-types {spot,futures,mark,index,premiumIndex,funding_rate}
[{spot,futures,mark,index,premiumIndex,funding_rate} ...]
Select candle type to convert. Defaults to all
available types.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Example converting data
The following command will convert all candle (OHLCV) data available in
~/.freqtrade/data/binance from json to jsongz, saving diskspace in the process.
It'll also remove original json data files (--erase parameter).

freqtrade convert-data --format-from json --format-to jsongz --datadir


~/.freqtrade/data/binance -t 5m 15m --erase
Sub-command convert trade data

usage: freqtrade convert-trade-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from
{json,jsongz,hdf5,feather,parquet}
--format-to
{json,jsongz,hdf5,feather,parquet}
[--erase] [--exchange EXCHANGE]

options:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--format-from {json,jsongz,hdf5,feather,parquet}
Source format for data conversion.
--format-to {json,jsongz,hdf5,feather,parquet}
Destination format for data conversion.
--erase Clean all existing data for the selected
exchange/pairs/timeframes.
--exchange EXCHANGE Exchange name. Only valid if no config is provided.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Example converting trades
The following command will convert all available trade-data in
~/.freqtrade/data/kraken from jsongz to json. It'll also remove original jsongz
data files (--erase parameter).

freqtrade convert-trade-data --format-from jsongz --format-to json --datadir


~/.freqtrade/data/kraken --erase
Sub-command trades to ohlcv
When you need to use --dl-trades (kraken only) to download data, conversion of
trades data to ohlcv data is the last step. This command will allow you to repeat
this last step for additional timeframes without re-downloading the data.

usage: freqtrade trades-to-ohlcv [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]]
[-t TIMEFRAMES [TIMEFRAMES ...]]
[--exchange EXCHANGE]
[--data-format-ohlcv
{json,jsongz,hdf5,feather,parquet}]
[--data-format-trades {json,jsongz,hdf5,feather}]

options:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
-t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
--data-format-trades {json,jsongz,hdf5,feather}
Storage format for downloaded trades data. (default:
`feather`).

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Example trade-to-ohlcv conversion

freqtrade trades-to-ohlcv --exchange kraken -t 5m 1h 1d --pairs BTC/EUR ETH/EUR


Sub-command list-data
You can get a list of downloaded data using the list-data sub-command.

usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [--exchange EXCHANGE]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--data-format-trades
{json,jsongz,hdf5,feather,parquet}]
[--trades] [-p PAIRS [PAIRS ...]]
[--trading-mode {spot,margin,futures}]
[--show-timerange]

options:
-h, --help show this help message and exit
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
--data-format-trades {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded trades data. (default:
`feather`).
--trades Work on trades data instead of OHLCV data.
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode
--show-timerange Show timerange available for available data. (May take
a while to calculate).

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Example list-data

> freqtrade list-data --userdir ~/.freqtrade/user_data/

Found 33 pair / timeframe combinations.


┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
┃ Pair ┃ Timeframe ┃ Type ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩
│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │
└───────────────┴───────────────────────────────────────────┴──────┘
Show all trades data including from/to timerange

> freqtrade list-data --show --trades


Found trades data for 1 pair.
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Pair ┃ Type ┃ From ┃ To ┃ Trades ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ XRP/ETH │ spot │ 2019-10-11 [Link] │ 2019-10-13 [Link] │ 12477 │
└─────────┴──────┴─────────────────────┴─────────────────────┴────────┘
Trades (tick) data
By default, download-data sub-command downloads Candles (OHLCV) data. Most
exchanges also provide historic trade-data via their API. This data can be useful
if you need many different timeframes, since it is only downloaded once, and then
resampled locally to the desired timeframes.

Since this data is large by default, the files use the feather file format by
default. They are stored in your data-directory with the naming convention of
<pair>-[Link] (ETH_BTC-[Link]). Incremental mode is also supported,
as for historic OHLCV data, so downloading the data once per week with --days 8
will create an incremental data-repository.

To use this mode, simply add --dl-trades to your call. This will swap the download
method to download trades. If --convert is also provided, the resample step will
happen automatically and overwrite eventually existing OHLCV data for the given
pair/timeframe combinations.

Do not use

You should not use this unless you're a kraken user (Kraken does not provide
historic OHLCV data).
Most other exchanges provide OHLCV data with sufficient history, so downloading
multiple timeframes through that method will still proof to be a lot faster than
downloading trades data.

Kraken user

Kraken users should read this before starting to download data.

Example call:

freqtrade download-data --exchange kraken --pairs XRP/EUR ETH/EUR --days 20 --dl-


trades
Note
While this method uses async calls, it will be slow, since it requires the result
of the previous call to generate the next request to the exchange.

Next step
Great, you now have some data downloaded, so you can now start backtesting your
strategy.

Backtesting
This page explains how to validate your strategy performance by using Backtesting.

Backtesting requires historic data to be available. To learn how to get data for
the pairs and exchange you're interested in, head over to the Data Downloading
section of the documentation.

Backtesting command reference

usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
[--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export {none,trades,signals}]
[--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--cache {none,day,week,month}]

optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for [Link] slow
backtesting down by a considerable amount, but will
include configured protections
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
Starting balance, used for backtesting / hyperopt and
dry-runs.
--timeframe-detail TIMEFRAME_DETAIL
Specify detail timeframe for backtesting (`1m`, `5m`,
`30m`, `1h`, `1d`).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to
backtest. Please note that timeframe needs to be set
either in config or via command line. When using this
together with `--export trades`, the strategy-name is
injected into the filename (so `[Link]`
becomes `[Link]`
--export {none,trades,signals}
Export backtest results (default: trades).
--export-filename PATH, --backtest-filename PATH
Use this filename for backtest [Link]
`--export` to be set as well. Example: `--export-filen
ame=user_data/backtest_results/backtest_today.json`
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
--cache {none,day,week,month}
Load a cached backtest result no older than specified
age (default: day).

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.

Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
Test your strategy with Backtesting
Now you have good Entry and exit strategies and some historic data, you want to
test it against real data. This is what we call backtesting.

Backtesting will use the crypto-currencies (pairs) from your config file and load
historical candle (OHLCV) data from user_data/data/<exchange> by default. If no
data is available for the exchange / pair / timeframe combination, backtesting will
ask you to download them first using freqtrade download-data. For details on
downloading, please refer to the Data Downloading section in the documentation.

The result of backtesting will confirm if your bot has better odds of making a
profit than a loss.

All profit calculations include fees, and freqtrade will use the exchange's default
fees for the calculation.

Using dynamic pairlists for backtesting

Using dynamic pairlists is possible (not all of the handlers are allowed to be used
in backtest mode), however it relies on the current market conditions - which will
not reflect the historic status of the pairlist. Also, when using pairlists other
than StaticPairlist, reproducibility of backtesting-results cannot be guaranteed.
Please read the pairlists documentation for more information.

To achieve reproducible results, best generate a pairlist via the test-pairlist


command and use that as static pairlist.

Note

By default, Freqtrade will export backtesting results to


user_data/backtest_results. The exported trades can be used for further analysis or
can be used by the plotting sub-command (freqtrade plot-dataframe) in the scripts
directory.

Starting balance
Backtesting will require a starting balance, which can be provided as --dry-run-
wallet <balance> or --starting-balance <balance> command line argument, or via
dry_run_wallet configuration setting. This amount must be higher than stake_amount,
otherwise the bot will not be able to simulate any trade.

Dynamic stake amount


Backtesting supports dynamic stake amount by configuring stake_amount as
"unlimited", which will split the starting balance into max_open_trades pieces.
Profits from early trades will result in subsequent higher stake amounts, resulting
in compounding of profits over the backtesting period.

Example backtesting commands


With 5 min candle (OHLCV) data (per default)

freqtrade backtesting --strategy AwesomeStrategy


Where --strategy AwesomeStrategy / -s AwesomeStrategy refers to the class name of
the strategy, which is within a python file in the user_data/strategies directory.

With 1 min candle (OHLCV) data

freqtrade backtesting --strategy AwesomeStrategy --timeframe 1m


Providing a custom starting balance of 1000 (in stake currency)

freqtrade backtesting --strategy AwesomeStrategy --dry-run-wallet 1000


Using a different on-disk historical candle (OHLCV) data source

Assume you downloaded the history data from the Binance exchange and kept it in the
user_data/data/binance-20180101 directory. You can then use this data for
backtesting as follows:

freqtrade backtesting --strategy AwesomeStrategy --datadir user_data/data/binance-


20180101
Comparing multiple Strategies

freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --timeframe


5m
Where SampleStrategy1 and AwesomeStrategy refer to class names of strategies.

Prevent exporting trades to file

freqtrade backtesting --strategy backtesting --export none --config [Link]


Only use this if you're sure you'll not want to plot or analyze your results
further.

Exporting trades to file specifying a custom filename

freqtrade backtesting --strategy backtesting --export trades --export-


filename=backtest_samplestrategy.json
Please also read about the strategy startup period.

Supplying custom fee value

Sometimes your account has certain fee rebates (fee reductions starting with a
certain account size or monthly volume), which are not visible to ccxt. To account
for this in backtesting, you can use the --fee command line option to supply this
value to backtesting. This fee must be a ratio, and will be applied twice (once for
trade entry, and once for trade exit).

For example, if the commission fee per order is 0.1% (i.e., 0.001 written as
ratio), then you would run backtesting as the following:

freqtrade backtesting --fee 0.001


Note

Only supply this option (or the corresponding configuration parameter) if you want
to experiment with different fee values. By default, Backtesting fetches the
default fee from the exchange pair/market info.

Running backtest with smaller test-set by using timerange

Use the --timerange argument to change how much of the test-set you want to use.

For example, running backtesting with the --timerange=20190501- option will use all
available data starting with May 1st, 2019 from your input data.

freqtrade backtesting --timerange=20190501-


You can also specify particular date ranges.

The full timerange specification:

Use data until 2018/01/31: --timerange=-20180131


Use data since 2018/01/31: --timerange=20180131-
Use data since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301
Use data between POSIX / epoch timestamps 1527595200 1527618600: --
timerange=1527595200-1527618600
Understand the backtesting result
The most important in the backtesting is to understand the result.

A backtesting result will look like that:

================================================ BACKTESTING REPORT


=================================================
| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg
Duration | Wins Draws Loss Win% |
|----------+--------+----------------+------------------+----------------
+--------------+--------------------------|
| ADA/BTC | 35 | -0.11 | -0.00019428 | -1.94 | [Link]
| 14 0 21 40.0 |
| ARK/BTC | 11 | -0.41 | -0.00022647 | -2.26 | [Link]
| 3 0 8 27.3 |
| BTS/BTC | 32 | 0.31 | 0.00048938 | 4.89 | [Link]
| 18 0 14 56.2 |
| DASH/BTC | 13 | -0.08 | -0.00005343 | -0.53 | [Link]
| 6 0 7 46.2 |
| ENG/BTC | 18 | 1.36 | 0.00122807 | 12.27 | [Link]
| 8 0 10 44.4 |
| EOS/BTC | 36 | 0.08 | 0.00015304 | 1.53 | [Link]
| 16 0 20 44.4 |
| ETC/BTC | 26 | 0.37 | 0.00047576 | 4.75 | [Link]
| 11 0 15 42.3 |
| ETH/BTC | 33 | 0.30 | 0.00049856 | 4.98 | [Link]
| 16 0 17 48.5 |
| IOTA/BTC | 32 | 0.03 | 0.00005444 | 0.54 | [Link]
| 14 0 18 43.8 |
| LSK/BTC | 15 | 1.75 | 0.00131413 | 13.13 | [Link]
| 6 0 9 40.0 |
| LTC/BTC | 32 | -0.04 | -0.00006886 | -0.69 | [Link]
| 11 0 21 34.4 |
| NANO/BTC | 17 | 1.26 | 0.00107058 | 10.70 | [Link]
| 10 0 7 58.5 |
| NEO/BTC | 23 | 0.82 | 0.00094936 | 9.48 | [Link]
| 10 0 13 43.5 |
| REQ/BTC | 9 | 1.17 | 0.00052734 | 5.27 | [Link]
| 4 0 5 44.4 |
| XLM/BTC | 16 | 1.22 | 0.00097800 | 9.77 | [Link]
| 7 0 9 43.8 |
| XMR/BTC | 23 | -0.18 | -0.00020696 | -2.07 | [Link]
| 12 0 11 52.2 |
| XRP/BTC | 35 | 0.66 | 0.00114897 | 11.48 | [Link]
| 12 0 23 34.3 |
| ZEC/BTC | 22 | -0.46 | -0.00050971 | -5.09 | [Link]
| 7 0 15 31.8 |
| TOTAL | 429 | 0.36 | 0.00762792 | 76.20 | [Link]
| 186 0 243 43.4 |
============================================= LEFT OPEN TRADES REPORT
=============================================
| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg
Duration | Win Draw Loss Win% |
|----------+---------+----------------+------------------+----------------
+----------------+---------------------|
| ADA/BTC | 1 | 0.89 | 0.00004434 | 0.44 | [Link]
| 1 0 0 100 |
| LTC/BTC | 1 | 0.68 | 0.00003421 | 0.34 | [Link]
| 1 0 0 100 |
| TOTAL | 2 | 0.78 | 0.00007855 | 0.78 | [Link]
| 2 0 0 100 |
==================== EXIT REASON STATS ====================
| Exit Reason | Exits | Wins | Draws | Losses |
|--------------------+---------+-------+--------+---------|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
| stop_loss | 166 | 0 | 0 | 166 |
| exit_signal | 56 | 36 | 0 | 20 |
| force_exit | 2 | 0 | 0 | 2 |

================== SUMMARY METRICS ==================


| Metric | Value |
|-----------------------------+---------------------|
| Backtesting from | 2019-01-01 [Link] |
| Backtesting to | 2019-05-01 [Link] |
| Max open trades | 3 |
| | |
| Total/Daily Avg Trades | 429 / 3.575 |
| Starting balance | 0.01000000 BTC |
| Final balance | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
| Total profit % | 76.2% |
| CAGR % | 460.87% |
| Sortino | 1.88 |
| Sharpe | 2.97 |
| Calmar | 6.29 |
| Profit factor | 1.11 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 BTC |
| | |
| Long / Short | 352 / 77 |
| Total profit Long % | 1250.58% |
| Total profit Short % | -15.02% |
| Absolute profit Long | 0.00838792 BTC |
| Absolute profit Short | -0.00076 BTC |
| | |
| Best Pair | LSK/BTC 26.26% |
| Worst Pair | ZEC/BTC -10.18% |
| Best Trade | LSK/BTC 4.25% |
| Worst Trade | ZEC/BTC -10.25% |
| Best day | 0.00076 BTC |
| Worst day | -0.00036 BTC |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | [Link] |
| Avg. Duration Loser | [Link] |
| Max Consecutive Wins / Loss | 3 / 4 |
| Rejected Entry signals | 3089 |
| Entry/Exit Timeouts | 0 / 0 |
| Canceled Trade Entries | 34 |
| Canceled Entry Orders | 123 |
| Replaced Entry Orders | 89 |
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
| Max % of account underwater | 25.19% |
| Absolute Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC |
| Drawdown Start | 2019-02-15 [Link] |
| Drawdown End | 2019-04-11 [Link] |
| Market change | -5.88% |
=====================================================
Backtesting report table
The 1st table contains all trades the bot made, including "left open trades".

The last line will give you the overall performance of your strategy, here:

| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 |


76.20 | [Link] | 186 0 243 43.4 |
The bot has made 429 trades for an average duration of [Link], with a performance
of 76.20% (profit), that means it has earned a total of 0.00762792 BTC starting
with a capital of 0.01 BTC.

The column Avg Profit % shows the average profit for all trades made. The column
Tot Profit % shows instead the total profit % in relation to the starting balance.
In the above results, we have a starting balance of 0.01 BTC and an absolute profit
of 0.00762792 BTC - so the Tot Profit % will be (0.00762792 / 0.01) * 100 ~= 76.2%.

Your strategy performance is influenced by your entry strategy, your exit strategy,
and also by the minimal_roi and stop_loss you have set.

For example, if your minimal_roi is only "0": 0.01 you cannot expect the bot to
make more profit than 1% (because it will exit every time a trade reaches 1%).

"minimal_roi": {
"0": 0.01
},
On the other hand, if you set a too high minimal_roi like "0": 0.55 (55%), there is
almost no chance that the bot will ever reach this profit. Hence, keep in mind that
your performance is an integral mix of all different elements of the strategy, your
configuration, and the crypto-currency pairs you have set up.

Exit reasons table


The 2nd table contains a recap of exit reasons. This table can tell you which area
needs some additional work (e.g. all or many of the exit_signal trades are losses,
so you should work on improving the exit signal, or consider disabling it).

Left open trades table


The 3rd table contains all trades the bot had to force_exit at the end of the
backtesting period to present you the full picture. This is necessary to simulate
realistic behavior, since the backtest period has to end at some point, while
realistically, you could leave the bot running forever. These trades are also
included in the first table, but are also shown separately in this table for
clarity.

Summary metrics
The last element of the backtest report is the summary metrics table. It contains
some useful key metrics about performance of your strategy on backtesting data.
================== SUMMARY METRICS ==================
| Metric | Value |
|-----------------------------+---------------------|
| Backtesting from | 2019-01-01 [Link] |
| Backtesting to | 2019-05-01 [Link] |
| Max open trades | 3 |
| | |
| Total/Daily Avg Trades | 429 / 3.575 |
| Starting balance | 0.01000000 BTC |
| Final balance | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
| Total profit % | 76.2% |
| CAGR % | 460.87% |
| Sortino | 1.88 |
| Sharpe | 2.97 |
| Calmar | 6.29 |
| Profit factor | 1.11 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 BTC |
| | |
| Long / Short | 352 / 77 |
| Total profit Long % | 1250.58% |
| Total profit Short % | -15.02% |
| Absolute profit Long | 0.00838792 BTC |
| Absolute profit Short | -0.00076 BTC |
| | |
| Best Pair | LSK/BTC 26.26% |
| Worst Pair | ZEC/BTC -10.18% |
| Best Trade | LSK/BTC 4.25% |
| Worst Trade | ZEC/BTC -10.25% |
| Best day | 0.00076 BTC |
| Worst day | -0.00036 BTC |
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | [Link] |
| Avg. Duration Loser | [Link] |
| Max Consecutive Wins / Loss | 3 / 4 |
| Rejected Entry signals | 3089 |
| Entry/Exit Timeouts | 0 / 0 |
| Canceled Trade Entries | 34 |
| Canceled Entry Orders | 123 |
| Replaced Entry Orders | 89 |
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
| Max % of account underwater | 25.19% |
| Absolute Drawdown (Account) | 13.33% |
| Drawdown | 0.0015 BTC |
| Drawdown high | 0.0013 BTC |
| Drawdown low | -0.0002 BTC |
| Drawdown Start | 2019-02-15 [Link] |
| Drawdown End | 2019-04-11 [Link] |
| Market change | -5.88% |
=====================================================
Backtesting from / Backtesting to: Backtesting range (usually defined with the --
timerange option).
Max open trades: Setting of max_open_trades (or --max-open-trades) - or number of
pairs in the pairlist (whatever is lower).
Total/Daily Avg Trades: Identical to the total trades of the backtest output
table / Total trades divided by the backtesting duration in days (this will give
you information about how many trades to expect from the strategy).
Starting balance: Start balance - as given by dry-run-wallet (config or command
line).
Final balance: Final balance - starting balance + absolute profit.
Absolute profit: Profit made in stake currency.
Total profit %: Total profit. Aligned to the TOTAL row's Tot Profit % from the
first table. Calculated as (End capital − Starting capital) / Starting capital.
CAGR %: Compound annual growth rate.
Sortino: Annualized Sortino ratio.
Sharpe: Annualized Sharpe ratio.
Calmar: Annualized Calmar ratio.
Profit factor: profit / loss.
Avg. stake amount: Average stake amount, either stake_amount or the average when
using dynamic stake amount.
Total trade volume: Volume generated on the exchange to reach the above profit.
Best Pair / Worst Pair: Best and worst performing pair, and it's corresponding Tot
Profit %.
Best Trade / Worst Trade: Biggest single winning trade and biggest single losing
trade.
Best day / Worst day: Best and worst day based on daily profit.
Days win/draw/lose: Winning / Losing days (draws are usually days without closed
trade).
Avg. Duration Winners / Avg. Duration Loser: Average durations for winning and
losing trades.
Max Consecutive Wins / Loss: Maximum consecutive wins/losses in a row.
Rejected Entry signals: Trade entry signals that could not be acted upon due to
max_open_trades being reached.
Entry/Exit Timeouts: Entry/exit orders which did not fill (only applicable if
custom pricing is used).
Canceled Trade Entries: Number of trades that have been canceled by user request
via adjust_entry_price.
Canceled Entry Orders: Number of entry orders that have been canceled by user
request via adjust_entry_price.
Replaced Entry Orders: Number of entry orders that have been replaced by user
request via adjust_entry_price.
Min balance / Max balance: Lowest and Highest Wallet balance during the backtest
period.
Max % of account underwater: Maximum percentage your account has decreased from the
top since the simulation started. Calculated as the maximum of (Max Balance -
Current Balance) / (Max Balance).
Absolute Drawdown (Account): Maximum Account Drawdown experienced. Calculated as
(Absolute Drawdown) / (DrawdownHigh + startingBalance).
Drawdown: Maximum, absolute drawdown experienced. Difference between Drawdown High
and Subsequent Low point.
Drawdown high / Drawdown low: Profit at the beginning and end of the largest
drawdown period. A negative low value means initial capital lost.
Drawdown Start / Drawdown End: Start and end datetime for this largest drawdown
(can also be visualized via the plot-dataframe sub-command).
Market change: Change of the market during the backtest period. Calculated as
average of all pairs changes from the first to the last candle using the "close"
column.
Long / Short: Split long/short values (Only shown when short trades were made).
Total profit Long % / Absolute profit Long: Profit long trades only (Only shown
when short trades were made).
Total profit Short % / Absolute profit Short: Profit short trades only (Only shown
when short trades were made).
Daily / Weekly / Monthly breakdown
You can get an overview over daily / weekly or monthly results by using the --
breakdown <> switch.

To visualize daily and weekly breakdowns, you can use the following:

freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day week

======================== DAY BREAKDOWN =========================


| Day | Tot Profit USDT | Wins | Draws | Losses |
|------------+-------------------+--------+---------+----------|
| 03/07/2021 | 200.0 | 2 | 0 | 0 |
| 04/07/2021 | -50.31 | 0 | 0 | 2 |
| 05/07/2021 | 220.611 | 3 | 2 | 0 |
| 06/07/2021 | 150.974 | 3 | 0 | 2 |
| 07/07/2021 | -70.193 | 1 | 0 | 2 |
| 08/07/2021 | 212.413 | 2 | 0 | 3 |
The output will show a table containing the realized absolute Profit (in stake
currency) for the given timeperiod, as well as wins, draws and losses that
materialized (closed) on this day. Below that there will be a second table for the
summarized values of weeks indicated by the date of the closing Sunday. The same
would apply to a monthly breakdown indicated by the last day of the month.

Backtest result caching


To save time, by default backtest will reuse a cached result from within the last
day when the backtested strategy and config match that of a previous backtest. To
force a new backtest despite existing result for an identical run specify --cache
none parameter.

Warning

Caching is automatically disabled for open-ended timeranges (--timerange


20210101-), as freqtrade cannot ensure reliably that the underlying data didn't
change. It can also use cached results where it shouldn't if the original backtest
had missing data at the end, which was fixed by downloading more data. In this
instance, please use --cache none once to force a fresh backtest.

Further backtest-result analysis


To further analyze your backtest results, freqtrade will export the trades to file
by default. You can then load the trades to perform further analysis as shown in
the data analysis backtesting section.

Assumptions made by backtesting


Since backtesting lacks some detailed information about what happens within a
candle, it needs to take a few assumptions:

Exchange trading limits are respected


Entries happen at open-price unless a custom price logic has been specified
All orders are filled at the requested price (no slippage) as long as the price is
within the candle's high/low range
Exit-signal exits happen at open-price of the consecutive candle
Exits free their trade slot for a new trade with a different pair
Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger
on candle's open
ROI
Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so
the exit will be at 2%)
Exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if
low was at 2.4% profit
ROI entries which came into effect on the triggering candle (e.g. 120: 0.02 for 1h
candles, from 60: 0.05) will use the candle's open as exit rate
Force-exits caused by <N>=-1 ROI entries use low as exit value, unless N falls on
the candle open (e.g. 120: -1 for 1h candles)
Stoploss exits happen exactly at stoploss price, even if low was lower, but the
loss will be 2 * fees higher than the stoploss price
Stoploss is evaluated before ROI within one candle. So you can often see more
trades with the stoploss exit reason comparing to the results obtained with the
same strategy in the Dry Run/Live Trade modes
Low happens before high for stoploss, protecting capital first
Trailing stoploss
Trailing Stoploss is only adjusted if it's below the candle's low (otherwise it
would be triggered)
On trade entry candles that trigger trailing stoploss, the "minimum offset"
(stop_positive_offset) is assumed (instead of high) - and the stop is calculated
from this point. This rule is NOT applicable to custom-stoploss scenarios, since
there's no information about the stoploss logic available.
High happens first - adjusting stoploss
Low uses the adjusted stoploss (so exits with large high-low difference are
backtested correctly)
ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both
ROI and trailing stop applies
Exit-reason does not explain if a trade was positive or negative, just what
triggered the exit (this can look odd if negative ROI values are used)
Evaluation sequence (if multiple signals happen on the same candle)
Exit-signal
Stoploss
ROI
Trailing stoploss
Taking these assumptions, backtesting tries to mirror real trading as closely as
possible. However, backtesting will never replace running a strategy in dry-run
mode. Also, keep in mind that past results don't guarantee future success.

In addition to the above assumptions, strategy authors should carefully read the
Common Mistakes section, to avoid using data in backtesting which is not available
in real market conditions.

Trading limits in backtesting


Exchanges have certain trading limits, like minimum (and maximum) base currency, or
minimum/maximum stake (quote) currency. These limits are usually listed in the
exchange documentation as "trading rules" or similar and can be quite different
between different pairs.

Backtesting (as well as live and dry-run) does honor these limits, and will ensure
that a stoploss can be placed below this value - so the value will be slightly
higher than what the exchange specifies. Freqtrade has however no information about
historic limits.

This can lead to situations where trading-limits are inflated by using a historic
price, resulting in minimum amounts > 50$.

For example:

BTC minimum tradable amount is 0.001. BTC trades at 22.000$ today (0.001 BTC is
related to this) - but the backtesting period includes prices as high as 50.000$.
Today's minimum would be 0.001 * 22_000 - or 22$.
However the limit could also be 50$ - based on 0.001 * 50_000 in some historic
setting.
Trading precision limits
Most exchanges pose precision limits on both price and amounts, so you cannot buy
1.0020401 of a pair, or at a price of 1.24567123123.
Instead, these prices and amounts will be rounded or truncated (based on the
exchange definition) to the defined trading precision. The above values may for
example be rounded to an amount of 1.002, and a price of 1.24567.

These precision values are based on current exchange limits (as described in the
above section), as historic precision limits are not available.

Improved backtest accuracy


One big limitation of backtesting is it's inability to know how prices moved intra-
candle (was high before close, or vice-versa?). So assuming you run backtesting
with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low,
Close).

While backtesting does take some assumptions (read above) about this - this can
never be perfect, and will always be biased in one way or the other. To mitigate
this, freqtrade can use a lower (faster) timeframe to simulate intra-candle
movements.

To utilize this, you can append --timeframe-detail 5m to your regular backtesting


command.

freqtrade backtesting --strategy AwesomeStrategy --timeframe 1h --timeframe-detail


5m
This will load 1h data as well as 5m data for the timeframe. The strategy will be
analyzed with the 1h timeframe, and Entry orders will only be placed at the main
timeframe, however Order fills and exit signals will be evaluated at the 5m candle,
simulating intra-candle movements.

All callback functions (custom_exit(), custom_stoploss(), ... ) will be running for


each 5m candle once the trade is opened (so 12 times in the above example of 1h
timeframe, and 5m detailed timeframe).

--timeframe-detail must be smaller than the original timeframe, otherwise


backtesting will fail to start.

Obviously this will require more memory (5m data is bigger than 1h data), and will
also impact runtime (depending on the amount of trades and trade durations). Also,
data must be available / downloaded already.

Tip

You can use this function as the last part of strategy development, to ensure your
strategy is not exploiting one of the backtesting assumptions. Strategies that
perform similarly well with this mode have a good chance to perform well in
dry/live modes too (although only forward-testing (dry-mode) can really confirm a
strategy).

Backtesting multiple strategies


To compare multiple strategies, a list of Strategies can be provided to
backtesting.

This is limited to 1 timeframe value per run. However, data is only loaded once
from disk so if you have multiple strategies you'd like to compare, this will give
a nice runtime boost.
All listed Strategies need to be in the same directory, unless also --recursive-
strategy-search is specified, where sub-directories within the strategy directory
are also considered.

freqtrade backtesting --timerange 20180401-20180410 --timeframe 5m --strategy-list


Strategy001 Strategy002 --export trades
This will save the results to user_data/backtest_results/backtest-result-
<datetime>.json, including results for both Strategy001 and Strategy002. There will
be an additional table comparing win/losses of the different strategies (identical
to the "Total" row in the first table). Detailed output for all strategies one
after the other will be available, so make sure to scroll up to see the details per
strategy.

================================================== STRATEGY SUMMARY


===================================================================
| Strategy | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg
Duration | Wins | Draws | Losses | Drawdown % |
|-------------+---------+----------------+------------------+----------------
+----------------+-------+--------+--------+------------|
| Strategy1 | 429 | 0.36 | 0.00762792 | 76.20 |
[Link] | 186 | 0 | 243 | 45.2 |
| Strategy2 | 1487 | -0.13 | -0.00988917 | -98.79 |
[Link] | 662 | 0 | 825 | 241.68 |
Next step
Great, your strategy is profitable. What if the bot can give your the optimal
parameters to use for your strategy? Your next step is to learn how to find optimal
parameters with Hyperopt

Hyperopt
This page explains how to tune your strategy by finding the optimal parameters, a
process called hyperparameter optimization. The bot uses algorithms included in the
scikit-optimize package to accomplish this. The search will burn all your CPU
cores, make your laptop sound like a fighter jet and still take a long time.

In general, the search for best parameters starts with a few random combinations
(see below for more details) and then uses Bayesian search with a ML regressor
algorithm (currently ExtraTreesRegressor) to quickly find a combination of
parameters in the search hyperspace that minimizes the value of the loss function.

Hyperopt requires historic data to be available, just as backtesting does (hyperopt


runs backtesting many times with different parameters). To learn how to get data
for the pairs and exchange you're interested in, head over to the Data Downloading
section of the documentation.

Bug

Hyperopt can crash when used with only 1 CPU Core as found out in Issue #1133

Note

Since 2021.4 release you no longer have to write a separate hyperopt class, but can
configure the parameters directly in the strategy. The legacy method was supported
up to 2021.8 and has been removed in 2021.9.

Install hyperopt dependencies


Since Hyperopt dependencies are not needed to run the bot itself, are heavy, can
not be easily built on some platforms (like Raspberry PI), they are not installed
by default. Before you run Hyperopt, you need to install the corresponding
dependencies, as described in this section below.

Note

Since Hyperopt is a resource intensive process, running it on a Raspberry Pi is not


recommended nor supported.

Docker
The docker-image includes hyperopt dependencies, no further action needed.

Easy installation script ([Link]) / Manual installation

source .venv/bin/activate
pip install -r [Link]
Hyperopt command reference

usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
[--eps] [--dmmp] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces
{all,buy,sell,roi,stoploss,trailing,protection,trades,default}
[{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
[--print-all] [--no-color] [--print-json] [-j JOBS]
[--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME] [--disable-param-export]
[--ignore-missing-spaces] [--analyze-per-epoch]

optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data.
(default: `json`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--hyperopt-path PATH Specify additional lookup path for Hyperopt Loss
functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for [Link] slow
backtesting down by a considerable amount, but will
include configured protections
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
Starting balance, used for backtesting / hyperopt and
dry-runs.
--timeframe-detail TIMEFRAME_DETAIL
Specify detail timeframe for backtesting (`1m`, `5m`,
`30m`, `1h`, `1d`).
-e INT, --epochs INT Specify number of epochs (default: 100).
--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default}
[{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]
Specify which parameters to hyperopt. Space-separated
list.
--print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--print-json Print output in JSON format.
-j JOBS, --job-workers JOBS
The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1
(default), all CPUs are used, for -2, all CPUs but one
are used, etc. If 1 is given, no parallel computing
code is used at all.
--random-state INT Set random state to some positive integer for
reproducible hyperopt results.
--min-trades INT Set minimal desired number of trades for evaluations
in the hyperopt optimization path (default: 1).
--hyperopt-loss NAME, --hyperoptloss NAME
Specify the class name of the hyperopt loss function
class (IHyperOptLoss). Different functions can
generate completely different results, since the
target for optimization is different. Built-in
Hyperopt-loss-functions are:
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
SortinoHyperOptLoss, SortinoHyperOptLossDaily,
CalmarHyperOptLoss, MaxDrawDownHyperOptLoss,
MaxDrawDownRelativeHyperOptLoss,
ProfitDrawDownHyperOptLoss
--disable-param-export
Disable automatic hyperopt parameter export.
--ignore-missing-spaces, --ignore-unparameterized-spaces
Suppress errors for any requested Hyperopt spaces that
do not contain any parameters.
--analyze-per-epoch Run populate_indicators once per epoch.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.

Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
--recursive-strategy-search
Recursively search for a strategy in the strategies
folder.
--freqaimodel NAME Specify a custom freqaimodels.
--freqaimodel-path PATH
Specify additional lookup path for freqaimodels.
Hyperopt checklist
Checklist on all tasks / possibilities in hyperopt

Depending on the space you want to optimize, only some of the below are required:

define parameters with space='buy' - for entry signal optimization


define parameters with space='sell' - for exit signal optimization
Note

populate_indicators needs to create all indicators any of the spaces may use,
otherwise hyperopt will not work.

Rarely you may also need to create a nested class named HyperOpt and implement

roi_space - for custom ROI optimization (if you need the ranges for the ROI
parameters in the optimization hyperspace that differ from default)
generate_roi_table - for custom ROI optimization (if you need the ranges for the
values in the ROI table that differ from default or the number of entries (steps)
in the ROI table which differs from the default 4 steps)
stoploss_space - for custom stoploss optimization (if you need the range for the
stoploss parameter in the optimization hyperspace that differs from default)
trailing_space - for custom trailing stop optimization (if you need the ranges for
the trailing stop parameters in the optimization hyperspace that differ from
default)
max_open_trades_space - for custom max_open_trades optimization (if you need the
ranges for the max_open_trades parameter in the optimization hyperspace that differ
from default)
Quickly optimize ROI, stoploss and trailing stoploss

You can quickly optimize the spaces roi, stoploss and trailing without changing
anything in your strategy.

# Have a working strategy at hand.


freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss
trailing --strategy MyWorkingStrategy --config [Link] -e 100
Hyperopt execution logic
Hyperopt will first load your data into memory and will then run
populate_indicators() once per Pair to generate all indicators, unless --analyze-
per-epoch is specified.

Hyperopt will then spawn into different processes (number of processors, or -j


<n>), and run backtesting over and over again, changing the parameters that are
part of the --spaces defined.

For every new set of parameters, freqtrade will run first populate_entry_trend()
followed by populate_exit_trend(), and then run the regular backtesting process to
simulate trades.

After backtesting, the results are passed into the loss function, which will
evaluate if this result was better or worse than previous results.
Based on the loss function result, hyperopt will determine the next set of
parameters to try in the next round of backtesting.

Configure your Guards and Triggers


There are two places you need to change in your strategy file to add a new buy
hyperopt for testing:

Define the parameters at the class level hyperopt shall be optimizing.


Within populate_entry_trend() - use defined parameter values instead of raw
constants.
There you have two different types of indicators: 1. guards and 2. triggers.

Guards are conditions like "never buy if ADX < 10", or never buy if current price
is over EMA10.
Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5
crosses over EMA10" or "buy when close price touches lower Bollinger band".
Guards and Triggers

Technically, there is no difference between Guards and Triggers.


However, this guide will make this distinction to make it clear that signals should
not be "sticking". Sticking signals are signals that are active for multiple
candles. This can lead into entering a signal late (right before the signal
disappears - which means that the chance of success is a lot lower than right at
the beginning).

Hyper-optimization will, for each epoch round, pick one trigger and possibly
multiple guards.

Exit signal optimization


Similar to the entry-signal above, exit-signals can also be optimized. Place the
corresponding settings into the following methods

Define the parameters at the class level hyperopt shall be optimizing, either
naming them sell_*, or by explicitly defining space='sell'.
Within populate_exit_trend() - use defined parameter values instead of raw
constants.
The configuration and rules are the same than for buy signals.

Solving a Mystery
Let's say you are curious: should you use MACD crossings or lower Bollinger Bands
to trigger your long entries. And you also wonder should you use RSI or ADX to help
with those decisions. If you decide to use RSI or ADX, which values should I use
for them?
So let's use hyperparameter optimization to solve this mystery.

Defining indicators to be used


We start by calculating the indicators our strategy is going to use.

class MyAwesomeStrategy(IStrategy):

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


DataFrame:
"""
Generate all indicators used by the strategy
"""
dataframe['adx'] = [Link](dataframe)
dataframe['rsi'] = [Link](dataframe)
macd = [Link](dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']

bollinger = [Link](dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)


dataframe['bb_lowerband'] = bollinger['lowerband']
dataframe['bb_middleband'] = bollinger['middleband']
dataframe['bb_upperband'] = bollinger['upperband']
return dataframe
Hyperoptable parameters
We continue to define hyperoptable parameters:

class MyAwesomeStrategy(IStrategy):
buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy")
buy_rsi = IntParameter(20, 40, default=30, space="buy")
buy_adx_enabled = BooleanParameter(default=True, space="buy")
buy_rsi_enabled = CategoricalParameter([True, False], default=False,
space="buy")
buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"],
default="bb_lower", space="buy")
The above definition says: I have five parameters I want to randomly combine to
find the best combination.
buy_rsi is an integer parameter, which will be tested between 20 and 40. This space
has a size of 20.
buy_adx is a decimal parameter, which will be evaluated between 20 and 40 with 1
decimal place (so values are 20.1, 20.2, ...). This space has a size of 200.
Then we have three category variables. First two are either True or False. We use
these to either enable or disable the ADX and RSI guards. The last one we call
trigger and use it to decide which buy trigger we want to use.

Parameter space assignment

Parameters must either be assigned to a variable named buy_* or sell_* - or contain


space='buy' | space='sell' to be assigned to a space correctly. If no parameter is
available for a space, you'll receive the error that no space was found when
running hyperopt.
Parameters with unclear space (e.g. adx_period = IntParameter(4, 24, default=14) -
no explicit nor implicit space) will not be detected and will therefore be ignored.

So let's write the buy strategy using these values:


def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) ->
DataFrame:
conditions = []
# GUARDS AND TRENDS
if self.buy_adx_enabled.value:
[Link](dataframe['adx'] > self.buy_adx.value)
if self.buy_rsi_enabled.value:
[Link](dataframe['rsi'] < self.buy_rsi.value)

# TRIGGERS
if self.buy_trigger.value == 'bb_lower':
[Link](dataframe['close'] < dataframe['bb_lowerband'])
if self.buy_trigger.value == 'macd_cross_signal':
[Link](qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))

# Check that volume is not 0


[Link](dataframe['volume'] > 0)

if conditions:
[Link][
reduce(lambda x, y: x & y, conditions),
'enter_long'] = 1

return dataframe
Hyperopt will now call populate_entry_trend() many times (epochs) with different
value combinations.
It will use the given historical data and simulate buys based on the buy signals
generated with the above function.
Based on the results, hyperopt will tell you which parameter combination produced
the best results (based on the configured loss function).

Note

The above setup expects to find ADX, RSI and Bollinger Bands in the populated
indicators. When you want to test an indicator that isn't used by the bot
currently, remember to add it to the populate_indicators() method in your strategy
or hyperopt file.

Parameter types
There are four parameter types each suited for different purposes.

IntParameter - defines an integral parameter with upper and lower boundaries of


search space.
DecimalParameter - defines a floating point parameter with a limited number of
decimals (default 3). Should be preferred instead of RealParameter in most cases.
RealParameter - defines a floating point parameter with upper and lower boundaries
and no precision limit. Rarely used as it creates a space with a near infinite
number of possibilities.
CategoricalParameter - defines a parameter with a predetermined number of choices.
BooleanParameter - Shorthand for CategoricalParameter([True, False]) - great for
"enable" parameters.
Parameter options
There are two parameter options that can help you to quickly test various ideas:

optimize - when set to False, the parameter will not be included in optimization
process. (Default: True)
load - when set to False, results of a previous hyperopt run (in buy_params and
sell_params either in your strategy or the JSON output file) will not be used as
the starting value for subsequent hyperopts. The default value specified in the
parameter will be used instead. (Default: True)
Effects of load=False on backtesting

Be aware that setting the load option to False will mean backtesting will also use
the default value specified in the parameter and not the value found through
hyperoptimisation.

Warning

Hyperoptable parameters cannot be used in populate_indicators - as hyperopt does


not recalculate indicators for each epoch, so the starting value would be used in
this case.

Optimizing an indicator parameter


Assuming you have a simple strategy in mind - a EMA cross strategy (2 Moving
averages crossing) - and you'd like to find the ideal parameters for this strategy.
By default, we assume a stoploss of 5% - and a take-profit (minimal_roi) of 10% -
which means freqtrade will sell the trade once 10% profit has been reached.

from pandas import DataFrame


from functools import reduce

import [Link] as ta

from [Link] import (BooleanParameter, CategoricalParameter,


DecimalParameter,
IStrategy, IntParameter)
import [Link] as qtpylib

class MyAwesomeStrategy(IStrategy):
stoploss = -0.05
timeframe = '15m'
minimal_roi = {
"0": 0.10
}
# Define the parameter spaces
buy_ema_short = IntParameter(3, 50, default=5)
buy_ema_long = IntParameter(15, 200, default=50)

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


DataFrame:
"""Generate all indicators used by the strategy"""

# Calculate all ema_short values


for val in self.buy_ema_short.range:
dataframe[f'ema_short_{val}'] = [Link](dataframe, timeperiod=val)

# Calculate all ema_long values


for val in self.buy_ema_long.range:
dataframe[f'ema_long_{val}'] = [Link](dataframe, timeperiod=val)

return dataframe

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


DataFrame:
conditions = []
[Link](qtpylib.crossed_above(
dataframe[f'ema_short_{self.buy_ema_short.value}'],
dataframe[f'ema_long_{self.buy_ema_long.value}']
))

# Check that volume is not 0


[Link](dataframe['volume'] > 0)

if conditions:
[Link][
reduce(lambda x, y: x & y, conditions),
'enter_long'] = 1
return dataframe

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


DataFrame:
conditions = []
[Link](qtpylib.crossed_above(
dataframe[f'ema_long_{self.buy_ema_long.value}'],
dataframe[f'ema_short_{self.buy_ema_short.value}']
))

# Check that volume is not 0


[Link](dataframe['volume'] > 0)

if conditions:
[Link][
reduce(lambda x, y: x & y, conditions),
'exit_long'] = 1
return dataframe
Breaking it down:

Using self.buy_ema_short.range will return a range object containing all entries


between the Parameters low and high value. In this case (IntParameter(3, 50,
default=5)), the loop would run for all numbers between 3 and 50 ([3, 4, 5, ... 49,
50]). By using this in a loop, hyperopt will generate 48 new columns (['buy_ema_3',
'buy_ema_4', ... , 'buy_ema_50']).

Hyperopt itself will then use the selected value to create the buy and sell
signals.

While this strategy is most likely too simple to provide consistent profit, it
should serve as an example how optimize indicator parameters.

Note

self.buy_ema_short.range will act differently between hyperopt and other modes. For
hyperopt, the above example may generate 48 new columns, however for all other
modes (backtesting, dry/live), it will only generate the column for the selected
value. You should therefore avoid using the resulting column with explicit values
(values other than self.buy_ema_short.value).

Note

range property may also be used with DecimalParameter and CategoricalParameter.


RealParameter does not provide this property due to infinite search space.

Performance tip
Optimizing protections
Freqtrade can also optimize protections. How you optimize protections is up to you,
and the following should be considered as example only.

The strategy will simply need to define the "protections" entry as property
returning a list of protection configurations.

from pandas import DataFrame


from functools import reduce

import [Link] as ta

from [Link] import (BooleanParameter, CategoricalParameter,


DecimalParameter,
IStrategy, IntParameter)
import [Link] as qtpylib

class MyAwesomeStrategy(IStrategy):
stoploss = -0.05
timeframe = '15m'
# Define the parameter spaces
cooldown_lookback = IntParameter(2, 48, default=5, space="protection",
optimize=True)
stop_duration = IntParameter(12, 200, default=5, space="protection",
optimize=True)
use_stop_protection = BooleanParameter(default=True, space="protection",
optimize=True)

@property
def protections(self):
prot = []

[Link]({
"method": "CooldownPeriod",
"stop_duration_candles": self.cooldown_lookback.value
})
if self.use_stop_protection.value:
[Link]({
"method": "StoplossGuard",
"lookback_period_candles": 24 * 3,
"trade_limit": 4,
"stop_duration_candles": self.stop_duration.value,
"only_per_pair": False
})

return prot

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


DataFrame:
# ...
You can then run hyperopt as follows: freqtrade hyperopt --hyperopt-loss
SharpeHyperOptLossDaily --strategy MyAwesomeStrategy --spaces protection

Note

The protection space is not part of the default space, and is only available with
the Parameters Hyperopt interface, not with the legacy hyperopt interface (which
required separate hyperopt files). Freqtrade will also automatically change the "--
enable-protections" flag if the protection space is selected.

Warning

If protections are defined as property, entries from the configuration will be


ignored. It is therefore recommended to not define protections in the
configuration.

Migrating from previous property setups


A migration from a previous setup is pretty simple, and can be accomplished by
converting the protections entry to a property. In simple terms, the following
configuration will be converted to the below.

class MyAwesomeStrategy(IStrategy):
protections = [
{
"method": "CooldownPeriod",
"stop_duration_candles": 4
}
]
Result

class MyAwesomeStrategy(IStrategy):

@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 4
}
]
You will then obviously also change potential interesting entries to parameters to
allow hyper-optimization.

Optimizing max_entry_position_adjustment
While max_entry_position_adjustment is not a separate space, it can still be used
in hyperopt by using the property approach shown above.

from pandas import DataFrame


from functools import reduce

import [Link] as ta

from [Link] import (BooleanParameter, CategoricalParameter,


DecimalParameter,
IStrategy, IntParameter)
import [Link] as qtpylib

class MyAwesomeStrategy(IStrategy):
stoploss = -0.05
timeframe = '15m'

# Define the parameter spaces


max_epa = CategoricalParameter([-1, 0, 1, 3, 5, 10], default=1, space="buy",
optimize=True)

@property
def max_entry_position_adjustment(self):
return self.max_epa.value

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


DataFrame:
# ...
Using IntParameter
Loss-functions
Each hyperparameter tuning requires a target. This is usually defined as a loss
function (sometimes also called objective function), which should decrease for more
desirable results, and increase for bad results.

A loss function must be specified via the --hyperopt-loss <Class-name> argument (or
optionally via the configuration under the "hyperopt_loss" key). This class should
be in its own file within the user_data/hyperopts/ directory.

Currently, the following loss functions are builtin:

ShortTradeDurHyperOptLoss - (default legacy Freqtrade hyperoptimization loss


function) - Mostly for short trade duration and avoiding losses.
OnlyProfitHyperOptLoss - takes only amount of profit into consideration.
SharpeHyperOptLoss - optimizes Sharpe Ratio calculated on trade returns relative to
standard deviation.
SharpeHyperOptLossDaily - optimizes Sharpe Ratio calculated on daily trade returns
relative to standard deviation.
SortinoHyperOptLoss - optimizes Sortino Ratio calculated on trade returns relative
to downside standard deviation.
SortinoHyperOptLossDaily - optimizes Sortino Ratio calculated on daily trade
returns relative to downside standard deviation.
MaxDrawDownHyperOptLoss - Optimizes Maximum absolute drawdown.
MaxDrawDownRelativeHyperOptLoss - Optimizes both maximum absolute drawdown while
also adjusting for maximum relative drawdown.
CalmarHyperOptLoss - Optimizes Calmar Ratio calculated on trade returns relative to
max drawdown.
ProfitDrawDownHyperOptLoss - Optimizes by max Profit & min Drawdown objective.
DRAWDOWN_MULT variable within the hyperoptloss file can be adjusted to be stricter
or more flexible on drawdown purposes.
Creation of a custom loss function is covered in the Advanced Hyperopt part of the
documentation.

Execute Hyperopt
Once you have updated your hyperopt configuration you can run it. Because hyperopt
tries a lot of combinations to find the best parameters it will take time to get a
good result.

We strongly recommend to use screen or tmux to prevent any connection loss.

freqtrade hyperopt --config [Link] --hyperopt-loss <hyperoptlossname> --


strategy <strategyname> -e 500 --spaces all
The -e option will set how many evaluations hyperopt will do. Since hyperopt uses
Bayesian search, running too many epochs at once may not produce greater results.
Experience has shown that best results are usually not improving much after 500-
1000 epochs.
Doing multiple runs (executions) with a few 1000 epochs and different random state
will most likely produce different results.

The --spaces all option determines that all possible parameters should be
optimized. Possibilities are listed below.

Note

Hyperopt will store hyperopt results with the timestamp of the hyperopt start time.
Reading commands (hyperopt-list, hyperopt-show) can use --hyperopt-filename
<filename> to read and display older hyperopt results. You can find a list of
filenames with ls -l user_data/hyperopt_results/.

Execute Hyperopt with different historical data source


If you would like to hyperopt parameters using an alternate historical data set
that you have on-disk, use the --datadir PATH option. By default, hyperopt uses
data from directory user_data/data.

Running Hyperopt with a smaller test-set


Use the --timerange argument to change how much of the test-set you want to use.
For example, to use one month of data, pass --timerange 20210101-20210201 (from
january 2021 - february 2021) to the hyperopt call.

Full command:

freqtrade hyperopt --strategy <strategyname> --timerange 20210101-20210201


Running Hyperopt with Smaller Search Space
Use the --spaces option to limit the search space used by hyperopt. Letting
Hyperopt optimize everything is a huuuuge search space. Often it might make more
sense to start by just searching for initial buy algorithm. Or maybe you just want
to optimize your stoploss or roi table for that awesome new buy strategy you have.

Legal values are:

all: optimize everything


buy: just search for a new buy strategy
sell: just search for a new sell strategy
roi: just optimize the minimal profit table for your strategy
stoploss: search for the best stoploss value
trailing: search for the best trailing stop values
trades: search for the best max open trades values
protection: search for the best protection parameters (read the protections section
on how to properly define these)
default: all except trailing and protection
space-separated list of any of the above values for example --spaces roi stoploss
The default Hyperopt Search Space, used when no --space command line option is
specified, does not include the trailing hyperspace. We recommend you to run
optimization for the trailing hyperspace separately, when the best parameters for
other hyperspaces were found, validated and pasted into your custom strategy.

Understand the Hyperopt Result


Once Hyperopt is completed you can use the result to update your strategy. Given
the following result from hyperopt:

Best result:

44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC
(0.7722%). Avg duration 180.4 mins. Objective: 1.94367
# Buy hyperspace params:
buy_params = {
'buy_adx': 44,
'buy_rsi': 29,
'buy_adx_enabled': False,
'buy_rsi_enabled': True,
'buy_trigger': 'bb_lower'
}
You should understand this result like:

The buy trigger that worked best was bb_lower.


You should not use ADX because 'buy_adx_enabled': False.
You should consider using the RSI indicator ('buy_rsi_enabled': True) and the best
value is 29.0 ('buy_rsi': 29.0)
Automatic parameter application to the strategy
When using Hyperoptable parameters, the result of your hyperopt-run will be written
to a json file next to your strategy (so for [Link], the file would
be [Link]).
This file is also updated when using the hyperopt-show sub-command, unless --
disable-param-export is provided to either of the 2 commands.

Your strategy class can also contain these results explicitly. Simply copy hyperopt
results block and paste them at class level, replacing old parameters (if any). New
parameters will automatically be loaded next time strategy is executed.

Transferring your whole hyperopt result to your strategy would then look like:

class MyAwesomeStrategy(IStrategy):
# Buy hyperspace params:
buy_params = {
'buy_adx': 44,
'buy_rsi': 29,
'buy_adx_enabled': False,
'buy_rsi_enabled': True,
'buy_trigger': 'bb_lower'
}
Note

Values in the configuration file will overwrite Parameter-file level parameters -


and both will overwrite parameters within the strategy. The prevalence is
therefore: config > parameter file > strategy *_params > parameter default

Understand Hyperopt ROI results


If you are optimizing ROI (i.e. if optimization search-space contains 'all',
'default' or 'roi'), your result will look as follows and include a ROI table:

Best result:

44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC
(0.7722%). Avg duration 180.4 mins. Objective: 1.94367

# ROI table:
minimal_roi = {
0: 0.10674,
21: 0.09158,
78: 0.03634,
118: 0
}
In order to use this best ROI table found by Hyperopt in backtesting and for live
trades/dry-run, copy-paste it as the value of the minimal_roi attribute of your
custom strategy:

# Minimal ROI designed for the strategy.


# This attribute will be overridden if the config file contains "minimal_roi"
minimal_roi = {
0: 0.10674,
21: 0.09158,
78: 0.03634,
118: 0
}
As stated in the comment, you can also use it as the value of the minimal_roi
setting in the configuration file.

Default ROI Search Space


If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for
you -- it's the hyperspace of components for the ROI tables. By default, each ROI
table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements
adaptive ranges for ROI tables with ranges for values in the ROI steps that depend
on the timeframe used. By default the values vary in the following ranges (for some
of the most used timeframes, values are rounded to 3 digits after the decimal
point):

# step 1m 5m 1h 1d
1 0 0.011...0.119 0 0.03...0.31 0 0.068...0.711 0
0.121...1.258
2 2...8 0.007...0.042 10...40 0.02...0.11 120...480 0.045...0.252
2880...11520 0.081...0.446
3 4...20 0.003...0.015 20...100 0.01...0.04 240...1200
0.022...0.091 5760...28800 0.040...0.162
4 6...44 0.0 30...220 0.0 360...2640 0.0 8640...63360 0.0
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict
keys) are scaled linearly depending on the timeframe used. The ROI values in the
steps (ROI dict values) are scaled logarithmically depending on the timeframe used.

If you have the generate_roi_table() and roi_space() methods in your custom


hyperopt, remove them in order to utilize these adaptive ROI tables and the ROI
hyperoptimization space generated by Freqtrade by default.

Override the roi_space() method if you need components of the ROI tables to vary in
other ranges. Override the generate_roi_table() and roi_space() methods and
implement your own custom approach for generation of the ROI tables during
hyperoptimization if you need a different structure of the ROI tables or other
amount of rows (steps).

A sample for these methods can be found in the overriding pre-defined spaces
section.

Reduced search space

To limit the search space further, Decimals are limited to 3 decimal places (a
precision of 0.001). This is usually sufficient, every value more precise than this
will usually result in overfitted results. You can however overriding pre-defined
spaces to change this to your needs.
Understand Hyperopt Stoploss results
If you are optimizing stoploss values (i.e. if optimization search-space contains
'all', 'default' or 'stoploss'), your result will look as follows and include
stoploss:

Best result:

44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC
(0.7722%). Avg duration 180.4 mins. Objective: 1.94367

# Buy hyperspace params:


buy_params = {
'buy_adx': 44,
'buy_rsi': 29,
'buy_adx_enabled': False,
'buy_rsi_enabled': True,
'buy_trigger': 'bb_lower'
}

stoploss: -0.27996
In order to use this best stoploss value found by Hyperopt in backtesting and for
live trades/dry-run, copy-paste it as the value of the stoploss attribute of your
custom strategy:

# Optimal stoploss designed for the strategy


# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.27996
As stated in the comment, you can also use it as the value of the stoploss setting
in the configuration file.

Default Stoploss Search Space


If you are optimizing stoploss values, Freqtrade creates the 'stoploss'
optimization hyperspace for you. By default, the stoploss values in that hyperspace
vary in the range -0.35...-0.02, which is sufficient in most cases.

If you have the stoploss_space() method in your custom hyperopt file, remove it in
order to utilize Stoploss hyperoptimization space generated by Freqtrade by
default.

Override the stoploss_space() method and define the desired range in it if you need
stoploss values to vary in other range during hyperoptimization. A sample for this
method can be found in the overriding pre-defined spaces section.

Reduced search space

To limit the search space further, Decimals are limited to 3 decimal places (a
precision of 0.001). This is usually sufficient, every value more precise than this
will usually result in overfitted results. You can however overriding pre-defined
spaces to change this to your needs.

Understand Hyperopt Trailing Stop results


If you are optimizing trailing stop values (i.e. if optimization search-space
contains 'all' or 'trailing'), your result will look as follows and include
trailing stop parameters:

Best result:
45/100: 606 trades. Avg profit 1.04%. Total profit 0.31555614 BTC
( 630.48%). Avg duration 150.3 mins. Objective: -1.10161

# Trailing stop:
trailing_stop = True
trailing_stop_positive = 0.02001
trailing_stop_positive_offset = 0.06038
trailing_only_offset_is_reached = True
In order to use these best trailing stop parameters found by Hyperopt in
backtesting and for live trades/dry-run, copy-paste them as the values of the
corresponding attributes of your custom strategy:

# Trailing stop
# These attributes will be overridden if the config file contains corresponding
values.
trailing_stop = True
trailing_stop_positive = 0.02001
trailing_stop_positive_offset = 0.06038
trailing_only_offset_is_reached = True
As stated in the comment, you can also use it as the values of the corresponding
settings in the configuration file.

Default Trailing Stop Search Space


If you are optimizing trailing stop values, Freqtrade creates the 'trailing'
optimization hyperspace for you. By default, the trailing_stop parameter is always
set to True in that hyperspace, the value of the trailing_only_offset_is_reached
vary between True and False, the values of the trailing_stop_positive and
trailing_stop_positive_offset parameters vary in the ranges 0.02...0.35 and
0.01...0.1 correspondingly, which is sufficient in most cases.

Override the trailing_space() method and define the desired range in it if you need
values of the trailing stop parameters to vary in other ranges during
hyperoptimization. A sample for this method can be found in the overriding pre-
defined spaces section.

Reduced search space

To limit the search space further, Decimals are limited to 3 decimal places (a
precision of 0.001). This is usually sufficient, every value more precise than this
will usually result in overfitted results. You can however overriding pre-defined
spaces to change this to your needs.

Reproducible results
The search for optimal parameters starts with a few (currently 30) random
combinations in the hyperspace of parameters, random Hyperopt epochs. These random
epochs are marked with an asterisk character (*) in the first column in the
Hyperopt output.

The initial state for generation of these random values (random state) is
controlled by the value of the --random-state command line option. You can set it
to some arbitrary value of your choice to obtain reproducible results.

If you have not set this value explicitly in the command line options, Hyperopt
seeds the random state with some random value for you. The random state value for
each Hyperopt run is shown in the log, so you can copy and paste it into the --
random-state command line option to repeat the set of the initial random epochs
used.
If you have not changed anything in the command line options, configuration,
timerange, Strategy and Hyperopt classes, historical data and the Loss Function --
you should obtain same hyper-optimization results with same random state value
used.

Output formatting
By default, hyperopt prints colorized results -- epochs with positive profit are
printed in the green color. This highlighting helps you find epochs that can be
interesting for later analysis. Epochs with zero total profit or with negative
profits (losses) are printed in the normal color. If you do not need colorization
of results (for instance, when you are redirecting hyperopt output to a file) you
can switch colorization off by specifying the --no-color option in the command
line.

You can use the --print-all command line option if you would like to see all
results in the hyperopt output, not only the best ones. When --print-all is used,
current best results are also colorized by default -- they are printed in bold
(bright) style. This can also be switched off with the --no-color command line
option.

Windows and color output

Windows does not support color-output natively, therefore it is automatically


disabled. To have color-output for hyperopt running under windows, please consider
using WSL.

Position stacking and disabling max market positions


In some situations, you may need to run Hyperopt (and Backtesting) with the --
eps/--enable-position-staking and --dmmp/--disable-max-market-positions arguments.

By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where
only one open trade is allowed for every traded pair. The total number of trades
open for all pairs is also limited by the max_open_trades setting. During
Hyperopt/Backtesting this may lead to some potential trades to be hidden (or
masked) by previously open trades.

The --eps/--enable-position-stacking argument allows emulation of buying the same


pair multiple times, while --dmmp/--disable-max-market-positions disables applying
max_open_trades during Hyperopt/Backtesting (which is equal to setting
max_open_trades to a very high number).

Note

Dry/live runs will NOT use position stacking - therefore it does make sense to also
validate the strategy without this as it's closer to reality.

You can also enable position stacking in the configuration file by explicitly
setting "position_stacking"=true.

Out of Memory errors


As hyperopt consumes a lot of memory (the complete data needs to be in memory once
per parallel backtesting process), it's likely that you run into "out of memory"
errors. To combat these, you have multiple options:

Reduce the amount of pairs.


Reduce the timerange used (--timerange <timerange>).
Avoid using --timeframe-detail (this loads a lot of additional data into memory).
Reduce the number of parallel processes (-j <n>).
Increase the memory of your machine.
Use --analyze-per-epoch if you're using a lot of parameters with .range
functionality.
The objective has been evaluated at this point before.
If you see The objective has been evaluated at this point before. - then this is a
sign that your space has been exhausted, or is close to that. Basically all points
in your space have been hit (or a local minima has been hit) - and hyperopt does no
longer find points in the multi-dimensional space it did not try yet. Freqtrade
tries to counter the "local minima" problem by using new, randomized points in this
case.

Example:

buy_ema_short = IntParameter(5, 20, default=10, space="buy", optimize=True)


# This is the only parameter in the buy space
The buy_ema_short space has 15 possible values (5, 6, ... 19, 20). If you now run
hyperopt for the buy space, hyperopt will only have 15 values to try before running
out of options. Your epochs should therefore be aligned to the possible values - or
you should be ready to interrupt a run if you norice a lot of The objective has
been evaluated at this point before. warnings.

Show details of Hyperopt results


After you run Hyperopt for the desired amount of epochs, you can later list all
results for analysis, select only best or profitable once, and show the details for
any of the epochs previously evaluated. This can be done with the hyperopt-list and
hyperopt-show sub-commands. The usage of these sub-commands is described in the
Utils chapter.

Validate backtesting results


Once the optimized strategy has been implemented into your strategy, you should
backtest this strategy to make sure everything is working as expected.

To achieve same the results (number of trades, their durations, profit, etc.) as
during Hyperopt, please use the same configuration and parameters (timerange,
timeframe, ...) used for hyperopt --dmmp/--disable-max-market-positions and --
eps/--enable-position-stacking for Backtesting.

Why do my backtest results not match my hyperopt results?


Should results not match, check the following factors:

You may have added parameters to hyperopt in populate_indicators() where they will
be calculated only once for all epochs. If you are, for example, trying to optimise
multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be
placed in populate_entry_trend() which is calculated every epoch. See Optimizing an
indicator parameter.
If you have disabled the auto-export of hyperopt parameters into the JSON
parameters file, double-check to make sure you transferred all hyperopted values
into your strategy correctly.
Check the logs to verify what parameters are being set and what values are being
used.
Pay special care to the stoploss, max_open_trades and trailing stoploss parameters,
as these are often set in configuration files, which override changes to the
strategy. Check the logs of your backtest to ensure that there were no parameters
inadvertently set by the configuration (like stoploss, max_open_trades or
trailing_stop).
Verify that you do not have an unexpected parameters JSON file overriding the
parameters or the default hyperopt settings in your strategy.
Verify that any protections that are enabled in backtesting are also enabled when
hyperopting, and vice versa. When using --space protection, protections are auto-
enabled for hyperopting.

Advanced Strategies
This page explains some advanced concepts available for strategies. If you're just
getting started, please familiarize yourself with the Freqtrade basics and methods
described in Strategy Customization first.

The call sequence of the methods described here is covered under bot execution
logic. Those docs are also helpful in deciding which method is most suitable for
your customisation needs.

Note

Callback methods should only be implemented if a strategy uses them.

Tip

Start off with a strategy template containing all available callback methods by
running freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced

Storing information (Persistent)


Freqtrade allows storing/retrieving user custom information associated with a
specific trade in the database.

Using a trade object, information can be stored using


trade.set_custom_data(key='my_key', value=my_value) and retrieved using
trade.get_custom_data(key='my_key'). Each data entry is associated with a trade and
a user supplied key (of type string). This means that this can only be used in
callbacks that also provide a trade object.

For the data to be able to be stored within the database, freqtrade must serialized
the data. This is done by converting the data to a JSON formatted string. Freqtrade
will attempt to reverse this action on retrieval, so from a strategy perspective,
this should not be relevant.

from [Link] import Trade


from datetime import timedelta

class AwesomeStrategy(IStrategy):

def bot_loop_start(self, **kwargs) -> None:


for trade in Trade.get_open_order_trades():
fills = trade.select_filled_orders(trade.entry_side)
if [Link] == 'ETH/USDT':
trade_entry_type = trade.get_custom_data(key='entry_type')
if trade_entry_type is None:
trade_entry_type = 'breakout' if 'entry_1' in trade.enter_tag
else 'dip'
elif fills > 1:
trade_entry_type = 'buy_up'
trade.set_custom_data(key='entry_type', value=trade_entry_type)
return super().bot_loop_start(**kwargs)

def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,


current_time: datetime, proposed_rate: float,
current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
# Limit orders to use and follow SMA200 as price target for the first 10
minutes since entry trigger for BTC/USDT pair.
if (
pair == 'BTC/USDT'
and entry_tag == 'long_sma200'
and side == 'long'
and (current_time - timedelta(minutes=10)) > trade.open_date_utc
and [Link] == 0.0
):
dataframe, _ = [Link].get_analyzed_dataframe(pair=pair,
timeframe=[Link])
current_candle = [Link][-1].squeeze()
# store information about entry adjustment
existing_count = trade.get_custom_data('num_entry_adjustments',
default=0)
if not existing_count:
existing_count = 1
else:
existing_count += 1
trade.set_custom_data(key='num_entry_adjustments',
value=existing_count)

# adjust order price


return current_candle['sma_200']

# default: maintain existing order


return current_order_rate

def custom_exit(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float, current_profit: float, **kwargs):

entry_adjustment_count = trade.get_custom_data(key='num_entry_adjustments')
trade_entry_type = trade.get_custom_data(key='entry_type')
if entry_adjustment_count is None:
if current_profit > 0.01 and (current_time - timedelta(minutes=100) >
trade.open_date_utc):
return True, 'exit_1'
else
if entry_adjustment_count > 0 and if current_profit > 0.05:
return True, 'exit_2'
if trade_entry_type == 'breakout' and current_profit > 0.1:
return True, 'exit_3

return False, None


The above is a simple example - there are simpler ways to retrieve trade data like
entry-adjustments.

Note

It is recommended that simple data types are used [bool, int, float, str] to ensure
no issues when serializing the data that needs to be stored. Storing big junks of
data may lead to unintended side-effects, like a database becoming big (and as a
consequence, also slow).

Non-serializable data

If supplied data cannot be serialized a warning is logged and the entry for the
specified key will contain None as data.
All attributes
Storing information (Non-Persistent)
Deprecated

This method of storing information is deprecated and we do advise against using


non-persistent storage.
Please use Persistent Storage instead.

It's content has therefore been collapsed.

Storing information
Dataframe access
You may access dataframe in various strategy functions by querying it from
dataprovider.

from [Link] import timeframe_to_prev_date

class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str,
amount: float,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool:
# Obtain pair dataframe.
dataframe, _ = [Link].get_analyzed_dataframe(pair, [Link])

# Obtain last available candle. Do not use current_time to look up latest


candle, because
# current_time points to current incomplete candle whose data is not
available.
last_candle = [Link][-1].squeeze()
# <...>

# In dry/live runs trade open date will not match candle open date
therefore it must be
# rounded.
trade_date = timeframe_to_prev_date([Link], trade.open_date_utc)
# Look up trade candle.
trade_candle = [Link][dataframe['date'] == trade_date]
# trade_candle may be empty for trades that just opened as it is still
incomplete.
if not trade_candle.empty:
trade_candle = trade_candle.squeeze()
# <...>
Using .iloc[-1]

You can use .iloc[-1] here because get_analyzed_dataframe() only returns candles
that backtesting is allowed to see. This will not work in populate_* methods, so
make sure to not use .iloc[] in that area. Also, this will only work starting with
version 2021.5.

Enter Tag
When your strategy has multiple buy signals, you can name the signal that
triggered. Then you can access your buy signal on custom_exit

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


[Link][
(
(dataframe['rsi'] < 35) &
(dataframe['volume'] > 0)
),
['enter_long', 'enter_tag']] = (1, 'buy_signal_rsi')

return dataframe

def custom_exit(self, pair: str, trade: Trade, current_time: datetime,


current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = [Link].get_analyzed_dataframe(pair, [Link])
last_candle = [Link][-1].squeeze()
if trade.enter_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
return 'sell_signal_rsi'
return None
Note

enter_tag is limited to 100 characters, remaining data will be truncated.

Warning

There is only one enter_tag column, which is used for both long and short trades.
As a consequence, this column must be treated as "last write wins" (it's just a
dataframe column after all). In fancy situations, where multiple signals collide
(or if signals are deactivated again based on different conditions), this can lead
to odd results with the wrong tag applied to an entry signal. These results are a
consequence of the strategy overwriting prior tags - where the last tag will
"stick" and will be the one freqtrade will use.

Exit tag
Similar to Entry Tagging, you can also specify an exit tag.

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


[Link][
(
(dataframe['rsi'] > 70) &
(dataframe['volume'] > 0)
),
['exit_long', 'exit_tag']] = (1, 'exit_rsi')

return dataframe
The provided exit-tag is then used as sell-reason - and shown as such in backtest
results.

Note

exit_reason is limited to 100 characters, remaining data will be truncated.

Strategy version
You can implement custom strategy versioning by using the "version" method, and
returning the version you would like this strategy to have.

def version(self) -> str:


"""
Returns version of the strategy.
"""
return "1.1"
Note

You should make sure to implement proper version control (like a git repository)
alongside this, as freqtrade will not keep historic versions of your strategy, so
it's up to the user to be able to eventually roll back to a prior version of the
strategy.

Derived strategies
The strategies can be derived from other strategies. This avoids duplication of
your custom strategy code. You can use this technique to override small parts of
your main strategy, leaving the rest untouched:

user_data/strategies/[Link]

class MyAwesomeStrategy(IStrategy):
...
stoploss = 0.13
trailing_stop = False
# All other attributes and methods are here as they
# should be in any custom strategy...
...
user_data/strategies/[Link]

from myawesomestrategy import MyAwesomeStrategy


class MyAwesomeStrategy2(MyAwesomeStrategy):
# Override something
stoploss = 0.08
trailing_stop = True
Both attributes and methods may be overridden, altering behavior of the original
strategy in a way you need.

While keeping the subclass in the same file is technically possible, it can lead to
some problems with hyperopt parameter files, we therefore recommend to use separate
strategy files, and import the parent strategy as shown above.

Embedding Strategies
Freqtrade provides you with an easy way to embed the strategy into your
configuration file. This is done by utilizing BASE64 encoding and providing this
string at the strategy configuration field, in your chosen config file.

Encoding a string as BASE64


This is a quick example, how to generate the BASE64 string in python

from base64 import urlsafe_b64encode

with open(file, 'r') as f:


content = [Link]()
content = urlsafe_b64encode([Link]('utf-8'))
The variable 'content', will contain the strategy file in a BASE64 encoded form.
Which can now be set in your configurations file as following

"strategy": "NameOfStrategy:BASE64String"
Please ensure that 'NameOfStrategy' is identical to the strategy name!

Performance warning
When executing a strategy, one can sometimes be greeted by the following in the
logs
PerformanceWarning: DataFrame is highly fragmented.

This is a warning from pandas and as the warning continues to say: use
[Link](axis=1). This can have slight performance implications, which are usually
only visible during hyperopt (when optimizing an indicator).

For example:

for val in self.buy_ema_short.range:


dataframe[f'ema_short_{val}'] = [Link](dataframe, timeperiod=val)
should be rewritten to

frames = [dataframe]
for val in self.buy_ema_short.range:
[Link](DataFrame({
f'ema_short_{val}': [Link](dataframe, timeperiod=val)
}))

# Combine all dataframes, and reassign the original dataframe column


dataframe = [Link](frames, axis=1)
Freqtrade does however also counter this by running [Link]() on the
dataframe right after the populate_indicators() method - so performance
implications of this should be low to non-existent.

Advanced Hyperopt
This page explains some advanced Hyperopt topics that may require higher coding
skills and Python knowledge than creation of an ordinal hyperoptimization class.

Creating and using a custom loss function


To use a custom loss function class, make sure that the function
hyperopt_loss_function is defined in your custom hyperopt loss class. For the
sample below, you then need to add the command line parameter --hyperopt-loss
SuperDuperHyperOptLoss to your hyperopt call so this function is being used.

A sample of this can be found below, which is identical to the Default Hyperopt
loss implementation. A full sample can be found in userdata/hyperopts.

from datetime import datetime


from typing import Any, Dict

from pandas import DataFrame

from [Link] import Config


from [Link] import IHyperOptLoss

TARGET_TRADES = 600
EXPECTED_MAX_PROFIT = 3.0
MAX_ACCEPTED_TRADE_DURATION = 300

class SuperDuperHyperOptLoss(IHyperOptLoss):
"""
Defines the default loss function for hyperopt
"""

@staticmethod
def hyperopt_loss_function(
*,
results: DataFrame,
trade_count: int,
min_date: datetime,
max_date: datetime,
config: Config,
processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
**kwargs,
) -> float:
"""
Objective function, returns smaller number for better results
This is the legacy algorithm (used until now in freqtrade).
Weights are distributed as follows:
* 0.4 to trade duration
* 0.25: Avoiding trade loss
* 1.0 to total profit, compared to the expected value
(`EXPECTED_MAX_PROFIT`) defined above
"""
total_profit = results['profit_ratio'].sum()
trade_duration = results['trade_duration'].mean()

trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 **


5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
result = trade_loss + profit_loss + duration_loss
return result
Currently, the arguments are:

results: DataFrame containing the resulting trades. The following columns are
available in results (corresponds to the output-file of backtesting when used with
--export trades):
pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date,
close_rate, fee_close, amount, trade_duration, is_open, exit_reason, stake_amount,
min_rate, max_rate, stop_loss_ratio, stop_loss_abs
trade_count: Amount of trades (identical to len(results))
min_date: Start date of the timerange used
min_date: End date of the timerange used
config: Config object used (Note: Not all strategy-related parameters will be
updated here if they are part of a hyperopt space).
processed: Dict of Dataframes with the pair as keys containing the data used for
backtesting.
backtest_stats: Backtesting statistics using the same format as the backtesting
file "strategy" substructure. Available fields can be seen in
generate_strategy_stats() in optimize_reports.py.
This function needs to return a floating point number (float). Smaller numbers will
be interpreted as better results. The parameters and balancing for this is up to
you.

Note

This function is called once per epoch - so please make sure to have this as
optimized as possible to not slow hyperopt down unnecessarily.

*args and **kwargs

Please keep the arguments *args and **kwargs in the interface to allow us to extend
this interface in the future.

Overriding pre-defined spaces


To override a pre-defined space (roi_space, generate_roi_table, stoploss_space,
trailing_space, max_open_trades_space), define a nested class called Hyperopt and
define the required spaces as follows:

from [Link] import Categorical, Dimension, Integer, SKDecimal

class MyAwesomeStrategy(IStrategy):
class HyperOpt:
# Define a custom stoploss space.
def stoploss_space():
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]

# Define custom ROI space


def roi_space() -> List[Dimension]:
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
SKDecimal(0.01, 0.04, decimals=3, name='roi_p1'),
SKDecimal(0.01, 0.07, decimals=3, name='roi_p2'),
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
]

def generate_roi_table(params: Dict) -> Dict[int, float]:

roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0

return roi_table

def trailing_space() -> List[Dimension]:


# All parameters here are mandatory, you can only modify their type or
the range.
return [
# Fixed to true, if optimizing trailing_stop we assume to use
trailing stop at all times.
Categorical([True], name='trailing_stop'),

SKDecimal(0.01, 0.35, decimals=3, name='trailing_stop_positive'),


# 'trailing_stop_positive_offset' should be greater than
'trailing_stop_positive',
# so this intermediate parameter is used as the value of the
difference between
# them. The value of the 'trailing_stop_positive_offset' is
constructed in the
# generate_trailing_params() method.
# This is similar to the hyperspace dimensions used for
constructing the ROI tables.
SKDecimal(0.001, 0.1, decimals=3,
name='trailing_stop_positive_offset_p1'),

Categorical([True, False], name='trailing_only_offset_is_reached'),


]

# Define a custom max_open_trades space


def max_open_trades_space(self) -> List[Dimension]:
return [
Integer(-1, 10, name='max_open_trades'),
]
Note

All overrides are optional and can be mixed/matched as necessary.

Dynamic parameters
Parameters can also be defined dynamically, but must be available to the instance
once the bot_start() callback has been called.

class MyAwesomeStrategy(IStrategy):

def bot_start(self, **kwargs) -> None:


self.buy_adx = IntParameter(20, 30, default=30, optimize=True)

# ...
Warning

Parameters created this way will not show up in the list-strategies parameter
count.

Overriding Base estimator


You can define your own estimator for Hyperopt by implementing generate_estimator()
in the Hyperopt subclass.

class MyAwesomeStrategy(IStrategy):
class HyperOpt:
def generate_estimator(dimensions: List['Dimension'], **kwargs):
return "RF"
Possible values are either one of "GP", "RF", "ET", "GBRT" (Details can be found in
the scikit-optimize documentation), or "an instance of a class that inherits from
RegressorMixin (from sklearn) and where the predict method has an optional
return_std argument, which returns std(Y | x) along with E[Y | x]".

Some research will be necessary to find additional Regressors.

Example for ExtraTreesRegressor ("ET") with additional parameters:

class MyAwesomeStrategy(IStrategy):
class HyperOpt:
def generate_estimator(dimensions: List['Dimension'], **kwargs):
from [Link] import ExtraTreesRegressor
# Corresponds to "ET" - but allows additional parameters.
return ExtraTreesRegressor(n_estimators=100)
The dimensions parameter is the list of [Link] objects corresponding
to the parameters to be optimized. It can be used to create isotropic kernels for
the [Link] estimator. Here's an example:

class MyAwesomeStrategy(IStrategy):
class HyperOpt:
def generate_estimator(dimensions: List['Dimension'], **kwargs):
from [Link] import cook_estimator
from [Link].gaussian_process.kernels import (Matern,
ConstantKernel)
kernel_bounds = (0.0001, 10000)
kernel = (
ConstantKernel(1.0, kernel_bounds) *
Matern(length_scale=[Link](len(dimensions)),
length_scale_bounds=[kernel_bounds for d in dimensions], nu=2.5)
)
kernel += (
ConstantKernel(1.0, kernel_bounds) *
Matern(length_scale=[Link](len(dimensions)),
length_scale_bounds=[kernel_bounds for d in dimensions], nu=1.5)
)

return cook_estimator("GP", space=dimensions, kernel=kernel,


n_restarts_optimizer=2)
Note

While custom estimators can be provided, it's up to you as User to do research on


possible parameters and analyze / understand which ones should be used. If you're
unsure about this, best use one of the Defaults ("ET" has proven to be the most
versatile) without further parameters.

Space options
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides
the following space types:

Categorical - Pick from a list of categories (e.g. Categorical(['a', 'b', 'c'],


name="cat"))
Integer - Pick from a range of whole numbers (e.g. Integer(1, 10, name='rsi'))
SKDecimal - Pick from a range of decimal numbers with limited precision (e.g.
SKDecimal(0.1, 0.5, decimals=3, name='adx')). Available only with freqtrade.
Real - Pick from a range of decimal numbers with full precision (e.g. Real(0.1,
0.5, name='adx')
You can import all of these from [Link], although Categorical,
Integer and Real are only aliases for their corresponding scikit-optimize Spaces.
SKDecimal is provided by freqtrade for faster optimizations.

from [Link] import Categorical, Dimension, Integer, SKDecimal,


Real # noqa
SKDecimal vs. Real

We recommend to use SKDecimal instead of the Real space in almost all cases. While
the Real space provides full accuracy (up to ~16 decimal places) - this precision
is rarely needed, and leads to unnecessary long hyperopt times.

Assuming the definition of a rather small space (SKDecimal(0.10, 0.15, decimals=2,


name='xxx')) - SKDecimal will have 5 possibilities ([0.10, 0.11, 0.12, 0.13, 0.14,
0.15]).

A corresponding real space Real(0.10, 0.15 name='xxx') on the other hand has an
almost unlimited number of possibilities ([0.10, 0.010000000001,
0.010000000002, ... 0.014999999999, 0.01500000000]).

Trade Object
Trade
A position freqtrade enters is stored in a Trade object - which is persisted to the
database. It's a core concept of freqtrade - and something you'll come across in
many sections of the documentation, which will most likely point you to this
location.

It will be passed to the strategy in many strategy callbacks. The object passed to
the strategy cannot be modified directly. Indirect modifications may occur based on
callback results.

Trade - Available attributes


The following attributes / properties are available for each individual trade - and
can be used with trade.<property> (e.g. [Link]).

Attribute DataType Description


pair string Pair of this trade.
is_open boolean Is the trade currently open, or has it been concluded.
open_rate float Rate this trade was entered at (Avg. entry rate in case of trade-
adjustments).
close_rate float Close rate - only set when is_open = False.
stake_amount float Amount in Stake (or Quote) currency.
amount float Amount in Asset / Base currency that is currently owned.
open_date datetime Timestamp when trade was opened use open_date_utc instead
open_date_utc datetime Timestamp when trade was opened - in UTC.
close_date datetime Timestamp when trade was closed use close_date_utc instead
close_date_utc datetime Timestamp when trade was closed - in UTC.
close_profit float Relative profit at the time of trade closure. 0.01 == 1%
close_profit_abs float Absolute profit (in stake currency) at the time of trade
closure.
leverage float Leverage used for this trade - defaults to 1.0 in spot markets.
enter_tag string Tag provided on entry via the enter_tag column in the
dataframe.
is_short boolean True for short trades, False otherwise.
orders Order[] List of order objects attached to this trade (includes both
filled and cancelled orders).
date_last_filled_utc datetime Time of the last filled order.
entry_side "buy" / "sell" Order Side the trade was entered.
exit_side "buy" / "sell" Order Side that will result in a trade exit /
position reduction.
trade_direction "long" / "short" Trade direction in text - long or short.
nr_of_successful_entries int Number of successful (filled) entry orders.
nr_of_successful_exits int Number of successful (filled) exit orders.
Class methods
The following are class methods - which return generic information, and usually
result in an explicit query against the database. They can be used as
Trade.<method> - e.g. open_trades = Trade.get_open_trade_count()

Backtesting/hyperopt

Most methods will work in both backtesting / hyperopt and live/dry modes. During
backtesting, it's limited to usage in strategy callbacks. Usage in populate_*()
methods is not supported and will result in wrong results.

get_trades_proxy
When your strategy needs some information on existing (open or close) trades - it's
best to use Trade.get_trades_proxy().

Usage:
from [Link] import Trade
from datetime import timedelta

# ...
trade_hist = Trade.get_trades_proxy(pair='ETH/USDT', is_open=False,
open_date=current_date - timedelta(days=2))
get_trades_proxy() supports the following keyword arguments. All arguments are
optional - calling get_trades_proxy() without arguments will return a list of all
trades in the database.

pair e.g. pair='ETH/USDT'


is_open e.g. is_open=False
open_date e.g. open_date=current_date - timedelta(days=2)
close_date e.g. close_date=current_date - timedelta(days=5)
get_open_trade_count
Get the number of currently open trades

from [Link] import Trade


# ...
open_trades = Trade.get_open_trade_count()
get_total_closed_profit
Retrieve the total profit the bot has generated so far. Aggregates close_profit_abs
for all closed trades.

from [Link] import Trade

# ...
profit = Trade.get_total_closed_profit()
total_open_trades_stakes
Retrieve the total stake_amount that's currently in trades.

from [Link] import Trade

# ...
profit = Trade.total_open_trades_stakes()
get_overall_performance
Retrieve the overall performance - similar to the /performance telegram command.

from [Link] import Trade

# ...
if [Link]['runmode'].value in ('live', 'dry_run'):
performance = Trade.get_overall_performance()
Sample return value: ETH/BTC had 5 trades, with a total profit of 1.5% (ratio of
0.015).

{"pair": "ETH/BTC", "profit": 0.015, "count": 5}


Order Object
An Order object represents an order on the exchange (or a simulated order in dry-
run mode). An Order object will always be tied to it's corresponding Trade, and
only really makes sense in the context of a trade.

Order - Available attributes


an Order object is typically attached to a trade. Most properties here can be None
as they are dependent on the exchange response.

Attribute DataType Description


trade Trade Trade object this order is attached to
ft_pair string Pair this order is for
ft_is_open boolean is the order filled?
order_type string Order type as defined on the exchange - usually market,
limit or stoploss
status string Status as defined by ccxt. Usually open, closed, expired or
canceled
side string Buy or Sell
price float Price the order was placed at
average float Average price the order filled at
amount float Amount in base currency
filled float Filled amount (in base currency)
remaining float Remaining amount
cost float Cost of the order - usually average * filled (Exchange dependent on
futures, may contain the cost with or without leverage and may be in contracts.)
stake_amount float Stake amount used for this order. Added in 2023.7.
order_date datetime Order creation date use order_date_utc instead
order_date_utc datetime Order creation date (in UTC)
order_fill_date datetime Order fill date use order_fill_utc instead
order_fill_date_utc datetime Order fill date

Utility Subcommands
Besides the Live-Trade and Dry-Run run modes, the backtesting, edge and hyperopt
optimization subcommands, and the download-data subcommand which prepares
historical data, the bot contains a number of utility subcommands. They are
described in this section.

Create userdir
Creates the directory structure to hold your files for freqtrade. Will also create
strategy and hyperopt examples for you to get started. Can be used multiple times -
using --reset will reset the sample strategy and hyperopt files to their default
state.

usage: freqtrade create-userdir [-h] [--userdir PATH] [--reset]

optional arguments:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
--reset Reset sample files to their original state.
Warning

Using --reset may result in loss of data, since this will overwrite all sample
files without asking again.

├── backtest_results
├── data
├── hyperopt_results
├── hyperopts
│ ├── sample_hyperopt_loss.py
├── notebooks
│ └── strategy_analysis_example.ipynb
├── plot
└── strategies
└── sample_strategy.py
Create new config
Creates a new configuration file, asking some questions which are important
selections for a configuration.

usage: freqtrade new-config [-h] [-c PATH]

optional arguments:
-h, --help show this help message and exit
-c PATH, --config PATH
Specify configuration file (default: `[Link]`).
Multiple --config options may be used. Can be set to `-`
to read config from stdin.
Warning

Only vital questions are asked. Freqtrade offers a lot more configuration
possibilities, which are listed in the Configuration documentation

Create config examples

$ freqtrade new-config --config user_data/config_binance.json

? Do you want to enable Dry-run (simulated trades)? Yes


? Please insert your stake currency: BTC
? Please insert your stake amount: 0.05
? Please insert max_open_trades (Integer or -1 for unlimited open trades): 3
? Please insert your desired timeframe (e.g. 5m): 5m
? Please insert your display Currency (for reporting): USD
? Select exchange binance
? Do you want to enable Telegram? No
Show config
Show configuration file (with sensitive values redacted by default). Especially
useful with split configuration files or environment variables, where this command
will show the merged configuration.

Show config output

usage: freqtrade show-config [-h] [--userdir PATH] [-c PATH]


[--show-sensitive]

options:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
--show-sensitive Show secrets in the output.

Your combined configuration is:


{
"exit_pricing": {
"price_side": "other",
"use_order_book": true,
"order_book_top": 1
},
"stake_currency": "USDT",
"exchange": {
"name": "binance",
"key": "REDACTED",
"secret": "REDACTED",
"ccxt_config": {},
"ccxt_async_config": {},
}
// ...
}
Sharing information provided by this command

We try to remove all known sensitive information from the default output (without
--show-sensitive). Yet, please do double-check for sensitive values in your output
to make sure you're not accidentally exposing some private info.

Create new strategy


Creates a new strategy from a template similar to SampleStrategy. The file will be
named inline with your class name, and will not overwrite existing files.

Results will be located in user_data/strategies/<strategyclassname>.py.

usage: freqtrade new-strategy [-h] [--userdir PATH] [-s NAME]


[--template {full,minimal,advanced}]

optional arguments:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--template {full,minimal,advanced}
Use a template which is either `minimal`, `full`
(containing multiple sample indicators) or `advanced`.
Default: `full`.
Sample usage of new-strategy

freqtrade new-strategy --strategy AwesomeStrategy


With custom user directory

freqtrade new-strategy --userdir ~/.freqtrade/ --strategy AwesomeStrategy


Using the advanced template (populates all optional functions and methods)

freqtrade new-strategy --strategy AwesomeStrategy --template advanced


List Strategies
Use the list-strategies subcommand to see all strategies in one particular
directory.

This subcommand is useful for finding problems in your environment with loading
strategies: modules with strategies that contain errors and failed to load are
printed in red (LOAD FAILED), while strategies with duplicate names are printed in
yellow (DUPLICATE NAME).
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--strategy-path PATH] [-1] [--no-color]
[--recursive-strategy-search]

optional arguments:
-h, --help show this help message and exit
--strategy-path PATH Specify additional strategy lookup path.
-1, --one-column Print output in one column.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--recursive-strategy-search
Recursively search for a strategy in the strategies
folder.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Warning

Using these commands will try to load all python files from a directory. This can
be a security risk if untrusted files reside in this directory, since all module-
level code is executed.

Example: Search default strategies directories (within the default userdir).

freqtrade list-strategies
Example: Search strategies directory within the userdir.

freqtrade list-strategies --userdir ~/.freqtrade/


Example: Search dedicated strategy path.

freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/


List freqAI models
Use the list-freqaimodels subcommand to see all freqAI models available.

This subcommand is useful for finding problems in your environment with loading
freqAI models: modules with models that contain errors and failed to load are
printed in red (LOAD FAILED), while models with duplicate names are printed in
yellow (DUPLICATE NAME).

usage: freqtrade list-freqaimodels [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--freqaimodel-path PATH] [-1] [--no-color]

optional arguments:
-h, --help show this help message and exit
--freqaimodel-path PATH
Specify additional lookup path for freqaimodels.
-1, --one-column Print output in one column.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
List Exchanges
Use the list-exchanges subcommand to see the exchanges available for the bot.

usage: freqtrade list-exchanges [-h] [-1] [-a]

optional arguments:
-h, --help show this help message and exit
-1, --one-column Print output in one column.
-a, --all Print all exchanges known to the ccxt library.
Example: see exchanges available for the bot:

$ freqtrade list-exchanges
Exchanges available for Freqtrade:
Exchange name Supported Markets Reason
------------------ ----------- ----------------------
------------------------------------------------------------------------
binance Official spot, isolated futures
bitmart Official spot
bybit spot, isolated futures
gate Official spot, isolated futures
htx Official spot
huobi spot
kraken Official spot
okx Official spot, isolated futures
Output reduced for clarity - supported and available exchanges may change over
time.

missing opt exchanges

Values with "missing opt:" might need special configuration (e.g. using orderbook
if fetchTickers is missing) - but should in theory work (although we cannot
guarantee they will).

Example: see all exchanges supported by the ccxt library (including 'bad' ones,
i.e. those that are known to not work with Freqtrade)

$ freqtrade list-exchanges -a
All exchanges supported by the ccxt library:
Exchange name Valid Supported Markets Reason
------------------ ------- ----------- ----------------------
---------------------------------------------------------------------------------
binance True Official spot, isolated futures
bitflyer False spot missing:
fetchOrder. missing opt: fetchTickers.
bitmart True Official spot
bybit True spot, isolated futures
gate True Official spot, isolated futures
htx True Official spot
kraken True Official spot
okx True Official spot, isolated futures
Reduced output - supported and available exchanges may change over time.

List Timeframes
Use the list-timeframes subcommand to see the list of timeframes available for the
exchange.

usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--exchange EXCHANGE] [-1]

options:
-h, --help show this help message and exit
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
-1, --one-column Print output in one column.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Example: see the timeframes for the 'binance' exchange, set in the configuration
file:

$ freqtrade list-timeframes -c config_binance.json


...
Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h,
6h, 8h, 12h, 1d, 3d, 1w, 1M
Example: enumerate exchanges available for Freqtrade and print timeframes supported
by each of them:

$ for i in `freqtrade list-exchanges -1`; do freqtrade list-timeframes --exchange


$i; done
List pairs/list markets
The list-pairs and list-markets subcommands allow to see the pairs/markets
available on exchange.

Pairs are markets with the '/' character between the base currency part and the
quote currency part in the market symbol. For example, in the 'ETH/BTC' pair 'ETH'
is the base currency, while 'BTC' is the quote currency.

For pairs traded by Freqtrade the pair quote currency is defined by the value of
the stake_currency configuration setting.

You can print info about any pair/market with these subcommands - and you can
filter output by quote-currency using --quote BTC, or by base-currency using --base
ETH options correspondingly.

These subcommands have same usage and same set of available options:

usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
[--print-list] [--print-json] [-1] [--print-csv]
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-a] [--trading-mode {spot,margin,futures}]
usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
[--print-list] [--print-json] [-1] [--print-csv]
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a]
[--trading-mode {spot,margin,futures}]
options:
-h, --help show this help message and exit
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
--print-list Print list of pairs or market symbols. By default data
is printed in the tabular format.
--print-json Print list of pairs or market symbols in JSON format.
-1, --one-column Print output in one column.
--print-csv Print exchange pair or market data in the csv format.
--base BASE_CURRENCY [BASE_CURRENCY ...]
Specify base currency(-ies). Space-separated list.
--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]
Specify quote currency(-ies). Space-separated list.
-a, --all Print all pairs or market symbols. By default only
active ones are shown.
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
By default, only active pairs/markets are shown. Active pairs/markets are those
that can currently be traded on the exchange. You can use the -a/-all option to see
the list of all pairs/markets, including the inactive ones. Pairs may be listed as
untradeable if the smallest tradeable price for the market is very small, i.e. less
than 1e-11 (0.00000000001)

Pairs/markets are sorted by its symbol string in the printed output.

Examples
Print the list of active pairs with quote currency USD on exchange, specified in
the default configuration file (i.e. pairs on the "Binance" exchange) in JSON
format:

$ freqtrade list-pairs --quote USD --print-json


Print the list of all pairs on the exchange, specified in the config_binance.json
configuration file (i.e. on the "Binance" exchange) with base currencies BTC or ETH
and quote currencies USDT or USD, as the human-readable list with summary:

$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD
--print-list
Print all markets on exchange "Kraken", in the tabular format:

$ freqtrade list-markets --exchange kraken --all


Test pairlist
Use the test-pairlist subcommand to test the configuration of dynamic pairlists.

Requires a configuration with specified pairlists attribute. Can be used to


generate static pairlists to be used during backtesting / hyperopt.

usage: freqtrade test-pairlist [-h] [--userdir PATH] [-v] [-c PATH]


[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-1] [--print-json] [--exchange EXCHANGE]

options:
-h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]
Specify quote currency(-ies). Space-separated list.
-1, --one-column Print output in one column.
--print-json Print list of pairs or market symbols in JSON format.
--exchange EXCHANGE Exchange name. Only valid if no config is provided.
Examples
Show whitelist when using a dynamic pairlist.

freqtrade test-pairlist --config [Link] --quote USDT BTC


Convert database
freqtrade convert-db can be used to convert your database from one system to
another (sqlite -> postgres, postgres -> other postgres), migrating all trades,
orders and Pairlocks.

Please refer to the corresponding documentation to learn about requirements for


different database systems.

usage: freqtrade convert-db [-h] [--db-url PATH] [--db-url-from PATH]

optional arguments:
-h, --help show this help message and exit
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///[Link]` for
Live Run mode, `sqlite:///[Link]` for
Dry Run).
--db-url-from PATH Source db url to use when migrating a database.
Warning

Please ensure to only use this on an empty target database. Freqtrade will perform
a regular migration, but may fail if entries already existed.

Webserver mode
Experimental

Webserver mode is an experimental mode to increase backesting and strategy


development productivity. There may still be bugs - so if you happen to stumble
across these, please report them as github issues, thanks.

Run freqtrade in webserver mode. Freqtrade will start the webserver and allow
FreqUI to start and control backtesting processes. This has the advantage that data
will not be reloaded between backtesting runs (as long as timeframe and timerange
remain identical). FreqUI will also show the backtesting results.

usage: freqtrade webserver [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH]

optional arguments:
-h, --help show this help message and exit

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Webserver mode - docker
You can also use webserver mode via docker. Starting a one-off container requires
the configuration of the port explicitly, as ports are not exposed by default. You
can use docker compose run --rm -p [Link]:8080:8080 freqtrade webserver to start
a one-off container that'll be removed once you stop it. This assumes that port
8080 is still available and no other bot is running on that port.

Alternatively, you can reconfigure the docker-compose file to have the command
updated:

command: >
webserver
--config /freqtrade/user_data/[Link]
You can now use docker compose up to start the webserver. This assumes that the
configuration has a webserver enabled and configured for docker (listening port =
[Link]).

Tip

Don't forget to reset the command back to the trade command if you want to start a
live or dry-run bot.

Show previous Backtest results


Allows you to show previous backtest results. Adding --show-pair-list outputs a
sorted pair list you can easily copy/paste into your configuration (omitting bad
pairs).

Strategy overfitting

usage: freqtrade backtesting-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--export-filename PATH] [--show-pair-list]

optional arguments:
-h, --help show this help message and exit
--export-filename PATH
Save backtest results to the file with this filename.
Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest
_today.json`
--show-pair-list Show backtesting pairlist sorted by profit.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Detailed backtest analysis
Advanced backtest result analysis.

More details in the Backtesting analysis Section.

usage: freqtrade backtesting-analysis [-h] [-v] [--logfile FILE] [-V]


[-c PATH] [-d PATH] [--userdir PATH]
[--export-filename PATH]
[--analysis-groups {0,1,2,3,4}
[{0,1,2,3,4} ...]]
[--enter-reason-list ENTER_REASON_LIST
[ENTER_REASON_LIST ...]]
[--exit-reason-list EXIT_REASON_LIST
[EXIT_REASON_LIST ...]]
[--indicator-list INDICATOR_LIST
[INDICATOR_LIST ...]]
[--timerange YYYYMMDD-[YYYYMMDD]]
[--rejected]
[--analysis-to-csv]
[--analysis-csv-path PATH]

optional arguments:
-h, --help show this help message and exit
--export-filename PATH, --backtest-filename PATH
Use this filename for backtest [Link]
`--export` to be set as well. Example: `--export-filen
ame=user_data/backtest_results/backtest_today.json`
--analysis-groups {0,1,2,3,4} [{0,1,2,3,4} ...]
grouping output - 0: simple wins/losses by enter tag,
1: by enter_tag, 2: by enter_tag and exit_tag, 3: by
pair and enter_tag, 4: by pair, enter_ and exit_tag
(this can get quite large)
--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]
Space separated list of entry signals to analyse.
Default: all. e.g. 'entry_tag_a entry_tag_b'
--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]
Space separated list of exit signals to analyse.
Default: all. e.g.
'exit_tag_a roi stop_loss trailing_stop_loss'
--indicator-list INDICATOR_LIST [INDICATOR_LIST ...]
Space separated list of indicators to analyse. e.g.
'close rsi bb_lowerband profit_abs'
--timerange YYYYMMDD-[YYYYMMDD]
Timerange to filter trades for analysis,
start inclusive, end exclusive. e.g.
20220101-20220201
--rejected
Print out rejected trades table
--analysis-to-csv
Write out tables to individual CSVs, by default to
'user_data/backtest_results' unless '--analysis-csv-path'
is given.
--analysis-csv-path [PATH]
Optional path where individual CSVs will be written. If not
used,
CSVs will be written to 'user_data/backtest_results'.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
List Hyperopt results
You can list the hyperoptimization epochs the Hyperopt module evaluated previously
with the hyperopt-list sub-command.

usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--best]
[--profitable] [--min-trades INT]
[--max-trades INT] [--min-avg-time FLOAT]
[--max-avg-time FLOAT] [--min-avg-profit FLOAT]
[--max-avg-profit FLOAT]
[--min-total-profit FLOAT]
[--max-total-profit FLOAT]
[--min-objective FLOAT] [--max-objective FLOAT]
[--no-color] [--print-json] [--no-details]
[--hyperopt-filename PATH] [--export-csv FILE]

optional arguments:
-h, --help show this help message and exit
--best Select only best epochs.
--profitable Select only profitable epochs.
--min-trades INT Select epochs with more than INT trades.
--max-trades INT Select epochs with less than INT trades.
--min-avg-time FLOAT Select epochs above average time.
--max-avg-time FLOAT Select epochs below average time.
--min-avg-profit FLOAT
Select epochs above average profit.
--max-avg-profit FLOAT
Select epochs below average profit.
--min-total-profit FLOAT
Select epochs above total profit.
--max-total-profit FLOAT
Select epochs below total profit.
--min-objective FLOAT
Select epochs above objective.
--max-objective FLOAT
Select epochs below objective.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--print-json Print output in JSON format.
--no-details Do not print best epoch details.
--hyperopt-filename FILENAME
Hyperopt result [Link]: `--hyperopt-
filename=hyperopt_results_2020-09-27_16-[Link]`
--export-csv FILE Export to CSV-File. This will disable table print.
Example: --export-csv [Link]

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Note

hyperopt-list will automatically use the latest available hyperopt results file.
You can override this using the --hyperopt-filename argument, and specify another,
available filename (without path!).

Examples
List all results, print details of the best result at the end:

freqtrade hyperopt-list
List only epochs with positive profit. Do not print the details of the best epoch,
so that the list can be iterated in a script:

freqtrade hyperopt-list --profitable --no-details


Show details of Hyperopt results
You can show the details of any hyperoptimization epoch previously evaluated by the
Hyperopt module with the hyperopt-show subcommand.

usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--best]
[--profitable] [-n INT] [--print-json]
[--hyperopt-filename FILENAME] [--no-header]
[--disable-param-export]
[--breakdown {day,week,month}
[{day,week,month} ...]]

optional arguments:
-h, --help show this help message and exit
--best Select only best epochs.
--profitable Select only profitable epochs.
-n INT, --index INT Specify the index of the epoch to print details for.
--print-json Print output in JSON format.
--hyperopt-filename FILENAME
Hyperopt result [Link]: `--hyperopt-
filename=hyperopt_results_2020-09-27_16-[Link]`
--no-header Do not print epoch details header.
--disable-param-export
Disable automatic hyperopt parameter export.
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Note

hyperopt-show will automatically use the latest available hyperopt results file.
You can override this using the --hyperopt-filename argument, and specify another,
available filename (without path!).

Examples
Print details for the epoch 168 (the number of the epoch is shown by the hyperopt-
list subcommand or by Hyperopt itself during hyperoptimization run):

freqtrade hyperopt-show -n 168


Prints JSON data with details for the last best epoch (i.e., the best of all
epochs):

freqtrade hyperopt-show --best -n -1 --print-json --no-header


Show trades
Print selected (or all) trades from database to screen.

usage: freqtrade show-trades [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--db-url PATH]
[--trade-ids TRADE_IDS [TRADE_IDS ...]]
[--print-json]

optional arguments:
-h, --help show this help message and exit
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///[Link]` for
Live Run mode, `sqlite:///[Link]` for
Dry Run).
--trade-ids TRADE_IDS [TRADE_IDS ...]
Specify the list of trade ids.
--print-json Print output in JSON format.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Examples
Print trades with id 2 and 3 as json

freqtrade show-trades --db-url sqlite:///[Link] --trade-ids 2 3 --print-


json
Strategy-Updater
Updates listed strategies or all strategies within the strategies folder to be v3
compliant. If the command runs without --strategy-list then all strategies inside
the strategies folder will be converted. Your original strategy will remain
available in the user_data/strategies_orig_updater/ directory.

Conversion results

Strategy updater will work on a "best effort" approach. Please do your due
diligence and verify the results of the conversion. We also recommend to run a
python formatter (e.g. black) to format results in a sane manner.

usage: freqtrade strategy-updater [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--strategy-list STRATEGY_LIST
[STRATEGY_LIST ...]]

options:
-h, --help show this help message and exit
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to
be converted.

Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/[Link]` or `[Link]` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.

Trading with Leverage


Beta feature

This feature is still in it's testing phase. Should you notice something you think
is wrong please let us know via Discord or via Github Issue.

Multiple bots on one account

You can't run 2 bots on the same account with leverage. For leveraged / margin
trading, freqtrade assumes it's the only user of the account, and all liquidation
levels are calculated based on this assumption.

Trading with leverage is very risky

Do not trade with a leverage > 1 using a strategy that hasn't shown positive
results in a live run using the spot market. Check the stoploss of your strategy.
With a leverage of 2, a stoploss of 0.5 (50%) would be too low, and these trades
would be liquidated before reaching that stoploss. We do not assume any
responsibility for eventual losses that occur from using this software or this
mode.

Please only use advanced trading modes when you know how freqtrade (and your
strategy) works. Also, never risk more than what you can afford to lose.

If you already have an existing strategy, please read the strategy migration guide
to migrate your strategy from a freqtrade v2 strategy, to strategy of version 3
which can short and trade futures.

Shorting
Shorting is not possible when trading with trading_mode set to spot. To short
trade, trading_mode must be set to margin(currently unavailable) or futures, with
margin_mode set to cross(currently unavailable) or isolated

For a strategy to short, the strategy class must set the class variable can_short =
True

Please read strategy customization for instructions on how to set signals to enter
and exit short trades.

Understand trading_mode
The possible values are: spot (default), margin(Currently unavailable) or futures.

Spot
Regular trading mode (low risk)

Long trades only (No short trades).


No leverage.
No Liquidation.
Profits gained/lost are equal to the change in value of the assets (minus trading
fees).
Leverage trading modes
With leverage, a trader borrows capital from the exchange. The capital must be re-
payed fully to the exchange (potentially with interest), and the trader keeps any
profits, or pays any losses, from any trades made using the borrowed capital.

Because the capital must always be re-payed, exchanges will liquidate (forcefully
sell the traders assets) a trade made using borrowed capital when the total value
of assets in the leverage account drops to a certain point (a point where the total
value of losses is less than the value of the collateral that the trader actually
owns in the leverage account), in order to ensure that the trader has enough
capital to pay the borrowed assets back to the exchange. The exchange will also
charge a liquidation fee, adding to the traders losses.

For this reason, DO NOT TRADE WITH LEVERAGE IF YOU DON'T KNOW EXACTLY WHAT YOUR
DOING. LEVERAGE TRADING IS HIGH RISK, AND CAN RESULT IN THE VALUE OF YOUR ASSETS
DROPPING TO 0 VERY QUICKLY, WITH NO CHANCE OF INCREASING IN VALUE AGAIN.

Margin (currently unavailable)


Trading occurs on the spot market, but the exchange lends currency to you in an
amount equal to the chosen leverage. You pay the amount lent to you back to the
exchange with interest, and your profits/losses are multiplied by the leverage
specified.

Futures
Perpetual swaps (also known as Perpetual Futures) are contracts traded at a price
that is closely tied to the underlying asset they are based off of (ex.). You are
not trading the actual asset but instead are trading a derivative contract.
Perpetual swap contracts can last indefinitely, in contrast to futures or option
contracts.

In addition to the gains/losses from the change in price of the futures contract,
traders also exchange funding fees, which are gains/losses worth an amount that is
derived from the difference in price between the futures contract and the
underlying asset. The difference in price between a futures contract and the
underlying asset varies between exchanges.

To trade in futures markets, you'll have to set trading_mode to "futures". You will
also have to pick a "margin mode" (explanation below) - with freqtrade currently
only supporting isolated margin.

"trading_mode": "futures",
"margin_mode": "isolated"
Pair namings
Freqtrade follows the ccxt naming conventions for futures. A futures pair will
therefore have the naming of base/quote:settle (e.g. ETH/USDT:USDT).

Margin mode
On top of trading_mode - you will also have to configure your margin_mode. While
freqtrade currently only supports one margin mode, this will change, and by
configuring it now you're all set for future updates.

The possible values are: isolated, or cross(currently unavailable).

Isolated margin mode


Each market(trading pair), keeps collateral in a separate account

"margin_mode": "isolated"
Cross margin mode (currently unavailable)
One account is used to share collateral between markets (trading pairs). Margin is
taken from total account balance to avoid liquidation when needed.

"margin_mode": "cross"
Please read the exchange specific notes for exchanges that support this mode and
how they differ.

Set leverage to use


Different strategies and risk profiles will require different levels of leverage.
While you could configure one static leverage value - freqtrade offers you the
flexibility to adjust this via strategy leverage callback - which allows you to use
different leverages by pair, or based on some other factor benefitting your
strategy result.

If not implemented, leverage defaults to 1x (no leverage).

Warning

Higher leverage also equals higher risk - be sure you fully understand the
implications of using leverage!

Understand liquidation_buffer
Defaults to 0.05

A ratio specifying how large of a safety net to place between the liquidation price
and the stoploss to prevent a position from reaching the liquidation price. This
artificial liquidation price is calculated as:

freqtrade_liquidation_price = liquidation_price ± (abs(open_rate -


liquidation_price) * liquidation_buffer)

± = + for long trades


± = - for short trades
Possible values are any floats between 0.0 and 0.99

ex: If a trade is entered at a price of 10 coin/USDT, and the liquidation price of


this trade is 8 coin/USDT, then with liquidation_buffer set to 0.05 the minimum
stoploss for this trade would be

A liquidation_buffer of 0.0, or a low liquidation_buffer is likely to result in


liquidations, and liquidation fees

Currently Freqtrade is able to calculate liquidation prices, but does not calculate
liquidation fees. Setting your liquidation_buffer to 0.0, or using a low
liquidation_buffer could result in your positions being liquidated. Freqtrade does
not track liquidation fees, so liquidations will result in inaccurate profit/loss
results for your bot. If you use a low liquidation_buffer, it is recommended to use
stoploss_on_exchange if your exchange supports this.

Unavailable funding rates


For futures data, exchanges commonly provide the futures candles, the marks, and
the funding rates. However, it is common that whilst candles and marks might be
available, the funding rates are not. This can affect backtesting timeranges, i.e.
you may only be able to test recent timeranges and not earlier, experiencing the No
data found. Terminating. error. To get around this, add the futures_funding_rate
config option as listed in [Link], and it is recommended that you set
this to 0, unless you know a given specific funding rate for your pair, exchange
and timerange. Setting this to anything other than 0 can have drastic effects on
your profit calculations within strategy, e.g. within the custom_exit,
custom_stoploss, etc functions.

This will mean your backtests are inaccurate.

This will not overwrite funding rates that are available from the exchange, but
bear in mind that setting a false funding rate will mean backtesting results will
be inaccurate for historical timeranges where funding rates are not available.
Developer
Margin mode
For shorts, the currency which pays the interest fee for the borrowed currency is
purchased at the same time of the closing trade (This means that the amount
purchased in short closing trades is greater than the amount sold in short opening
trades).

For longs, the currency which pays the interest fee for the borrowed will already
be owned by the user and does not need to be purchased. The interest is subtracted
from the close_value of the trade.

All Fees are included in current_profit calculations during the trade.

Futures mode
Funding fees are either added or subtracted from the total amount of a trade

You might also like