Merge branch 'develop' into pr/cryptomeisternox/5150
This commit is contained in:
commit
6f1e719216
@ -1,11 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "freqtrade Develop",
|
"name": "freqtrade Develop",
|
||||||
|
"build": {
|
||||||
"dockerComposeFile": [
|
"dockerfile": "Dockerfile",
|
||||||
"docker-compose.yml"
|
"context": ".."
|
||||||
|
},
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [
|
||||||
|
8080
|
||||||
],
|
],
|
||||||
|
"mounts": [
|
||||||
|
"source=freqtrade-bashhistory,target=/home/ftuser/commandhistory,type=volume"
|
||||||
|
],
|
||||||
|
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "ftuser",
|
||||||
|
|
||||||
"service": "ft_vscode",
|
"postCreateCommand": "freqtrade create-userdir --userdir user_data/",
|
||||||
|
|
||||||
"workspaceFolder": "/freqtrade/",
|
"workspaceFolder": "/freqtrade/",
|
||||||
|
|
||||||
@ -25,20 +34,6 @@
|
|||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
"davidanson.vscode-markdownlint",
|
"davidanson.vscode-markdownlint",
|
||||||
"ms-azuretools.vscode-docker",
|
"ms-azuretools.vscode-docker",
|
||||||
|
"vscode-icons-team.vscode-icons",
|
||||||
],
|
],
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
// "forwardPorts": [],
|
|
||||||
|
|
||||||
// Uncomment the next line if you want start specific services in your Docker Compose config.
|
|
||||||
// "runServices": [],
|
|
||||||
|
|
||||||
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
|
|
||||||
// "shutdownAction": "none",
|
|
||||||
|
|
||||||
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
|
||||||
// "postCreateCommand": "sudo apt-get update && apt-get install -y git",
|
|
||||||
|
|
||||||
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
|
||||||
"remoteUser": "ftuser"
|
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
version: '3'
|
|
||||||
services:
|
|
||||||
ft_vscode:
|
|
||||||
build:
|
|
||||||
context: ..
|
|
||||||
dockerfile: ".devcontainer/Dockerfile"
|
|
||||||
volumes:
|
|
||||||
# Allow git usage within container
|
|
||||||
- "${HOME}/.ssh:/home/ftuser/.ssh:ro"
|
|
||||||
- "${HOME}/.gitconfig:/home/ftuser/.gitconfig:ro"
|
|
||||||
- ..:/freqtrade:cached
|
|
||||||
# Persist bash-history
|
|
||||||
- freqtrade-vscode-server:/home/ftuser/.vscode-server
|
|
||||||
- freqtrade-bashhistory:/home/ftuser/commandhistory
|
|
||||||
# Expose API port
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:8080:8080"
|
|
||||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
|
||||||
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
freqtrade-vscode-server:
|
|
||||||
freqtrade-bashhistory:
|
|
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,14 +2,16 @@ Thank you for sending your pull request. But first, have you included
|
|||||||
unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Explain in one sentence the goal of this PR
|
Explain in one sentence the goal of this PR
|
||||||
|
|
||||||
Solve the issue: #___
|
Solve the issue: #___
|
||||||
|
|
||||||
## Quick changelog
|
## Quick changelog
|
||||||
|
|
||||||
- <change log #1>
|
- <change log 1>
|
||||||
- <change log #2>
|
- <change log 1>
|
||||||
|
|
||||||
## What's new?
|
## What's new?
|
||||||
|
|
||||||
*Explain in details what this PR solve or improve. You can include visuals.*
|
*Explain in details what this PR solve or improve. You can include visuals.*
|
||||||
|
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@ -79,15 +79,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Backtesting
|
- name: Backtesting
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
||||||
|
|
||||||
- name: Hyperopt
|
- name: Hyperopt
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
||||||
|
|
||||||
- name: Flake8
|
- name: Flake8
|
||||||
run: |
|
run: |
|
||||||
@ -172,15 +172,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Backtesting
|
- name: Backtesting
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
||||||
|
|
||||||
- name: Hyperopt
|
- name: Hyperopt
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
||||||
|
|
||||||
- name: Flake8
|
- name: Flake8
|
||||||
run: |
|
run: |
|
||||||
@ -239,15 +239,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Backtesting
|
- name: Backtesting
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
||||||
|
|
||||||
- name: Hyperopt
|
- name: Hyperopt
|
||||||
run: |
|
run: |
|
||||||
cp config_bittrex.json.example config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
||||||
|
|
||||||
- name: Flake8
|
- name: Flake8
|
||||||
run: |
|
run: |
|
||||||
@ -334,6 +334,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
|
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
@ -411,3 +412,31 @@ jobs:
|
|||||||
channel: '#notifications'
|
channel: '#notifications'
|
||||||
url: ${{ secrets.SLACK_WEBHOOK }}
|
url: ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
|
||||||
|
|
||||||
|
deploy_arm:
|
||||||
|
needs: [ deploy ]
|
||||||
|
# Only run on 64bit machines
|
||||||
|
runs-on: [self-hosted, linux, ARM64]
|
||||||
|
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Extract branch name
|
||||||
|
shell: bash
|
||||||
|
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
|
||||||
|
id: extract_branch
|
||||||
|
|
||||||
|
- name: Dockerhub login
|
||||||
|
env:
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
run: |
|
||||||
|
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
|
||||||
|
|
||||||
|
- name: Build and test and push docker images
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: freqtradeorg/freqtrade
|
||||||
|
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
||||||
|
run: |
|
||||||
|
build_helpers/publish_docker_arm64.sh
|
||||||
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -95,3 +95,8 @@ target/
|
|||||||
|
|
||||||
#exceptions
|
#exceptions
|
||||||
!*.gitkeep
|
!*.gitkeep
|
||||||
|
!config_examples/config_binance.example.json
|
||||||
|
!config_examples/config_bittrex.example.json
|
||||||
|
!config_examples/config_ftx.example.json
|
||||||
|
!config_examples/config_full.example.json
|
||||||
|
!config_examples/config_kraken.example.json
|
||||||
|
@ -26,14 +26,14 @@ jobs:
|
|||||||
# - coveralls || true
|
# - coveralls || true
|
||||||
name: pytest
|
name: pytest
|
||||||
- script:
|
- script:
|
||||||
- cp config_bittrex.json.example config.json
|
- cp config_examples/config_bittrex.example.json config.json
|
||||||
- freqtrade create-userdir --userdir user_data
|
- freqtrade create-userdir --userdir user_data
|
||||||
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
|
||||||
name: backtest
|
name: backtest
|
||||||
- script:
|
- script:
|
||||||
- cp config_bittrex.json.example config.json
|
- cp config_examples/config_bittrex.example.json config.json
|
||||||
- freqtrade create-userdir --userdir user_data
|
- freqtrade create-userdir --userdir user_data
|
||||||
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily
|
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily
|
||||||
name: hyperopt
|
name: hyperopt
|
||||||
- script: flake8
|
- script: flake8
|
||||||
name: flake8
|
name: flake8
|
||||||
|
@ -12,7 +12,7 @@ Few pointers for contributions:
|
|||||||
- New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR.
|
- New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR.
|
||||||
- PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished).
|
- PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished).
|
||||||
|
|
||||||
If you are unsure, discuss the feature on our [discord server](https://discord.gg/p7nuUNVfP7), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
|
If you are unsure, discuss the feature on our [discord server](https://discord.gg/p7nuUNVfP7) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a Pull Request.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.9.5-slim-buster as base
|
FROM python:3.9.7-slim-buster as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
@ -13,7 +13,7 @@ RUN mkdir /freqtrade \
|
|||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
|
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& useradd -u 1000 -G sudo -U -m ftuser \
|
&& useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
|
||||||
&& chown ftuser:ftuser /freqtrade \
|
&& chown ftuser:ftuser /freqtrade \
|
||||||
# Allow sudoers
|
# Allow sudoers
|
||||||
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
|
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
|
||||||
|
28
README.md
28
README.md
@ -26,10 +26,11 @@ hesitate to read the source code and understand the mechanism of this bot.
|
|||||||
|
|
||||||
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||||
|
|
||||||
|
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist))
|
||||||
- [X] [Bittrex](https://bittrex.com/)
|
- [X] [Bittrex](https://bittrex.com/)
|
||||||
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists))
|
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [FTX](https://ftx.com)
|
- [X] [FTX](https://ftx.com)
|
||||||
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
|
|
||||||
### Community tested
|
### Community tested
|
||||||
@ -37,6 +38,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
|
|||||||
Exchanges confirmed working by the community:
|
Exchanges confirmed working by the community:
|
||||||
|
|
||||||
- [X] [Bitvavo](https://bitvavo.com/)
|
- [X] [Bitvavo](https://bitvavo.com/)
|
||||||
|
- [X] [Kucoin](https://www.kucoin.com/)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@ -51,7 +53,7 @@ Please find the complete documentation on our [website](https://www.freqtrade.io
|
|||||||
- [x] **Dry-run**: Run the bot without paying money.
|
- [x] **Dry-run**: Run the bot without paying money.
|
||||||
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
|
||||||
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
|
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
|
||||||
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/).
|
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/).
|
||||||
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
|
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
|
||||||
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
|
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
|
||||||
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
|
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
|
||||||
@ -64,12 +66,12 @@ Please find the complete documentation on our [website](https://www.freqtrade.io
|
|||||||
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
|
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone -b develop https://github.com/freqtrade/freqtrade.git
|
git clone -b develop https://github.com/freqtrade/freqtrade.git
|
||||||
cd freqtrade
|
cd freqtrade
|
||||||
./setup.sh --install
|
./setup.sh --install
|
||||||
```
|
```
|
||||||
|
|
||||||
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
|
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/).
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
@ -77,22 +79,22 @@ For any other type of installation please refer to [Installation doc](https://ww
|
|||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-V]
|
usage: freqtrade [-h] [-V]
|
||||||
{trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
|
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
||||||
...
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
|
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
||||||
trade Trade module.
|
trade Trade module.
|
||||||
create-userdir Create user-data directory.
|
create-userdir Create user-data directory.
|
||||||
new-config Create new config
|
new-config Create new config
|
||||||
new-hyperopt Create new hyperopt
|
|
||||||
new-strategy Create new strategy
|
new-strategy Create new strategy
|
||||||
download-data Download backtesting data.
|
download-data Download backtesting data.
|
||||||
convert-data Convert candle (OHLCV) data from one format to
|
convert-data Convert candle (OHLCV) data from one format to
|
||||||
another.
|
another.
|
||||||
convert-trade-data Convert trade data from one format to another.
|
convert-trade-data Convert trade data from one format to another.
|
||||||
|
list-data List downloaded data.
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
@ -106,8 +108,10 @@ positional arguments:
|
|||||||
list-timeframes Print available timeframes for the exchange.
|
list-timeframes Print available timeframes for the exchange.
|
||||||
show-trades Show trades.
|
show-trades Show trades.
|
||||||
test-pairlist Test your pairlist configuration.
|
test-pairlist Test your pairlist configuration.
|
||||||
|
install-ui Install FreqUI
|
||||||
plot-dataframe Plot candles with indicators.
|
plot-dataframe Plot candles with indicators.
|
||||||
plot-profit Generate plot showing profits.
|
plot-profit Generate plot showing profits.
|
||||||
|
webserver Webserver module.
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -141,13 +145,9 @@ The project is currently setup in two main branches:
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
### Help / Discord / Slack
|
### 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 our slack channel.
|
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](https://discord.gg/p7nuUNVfP7).
|
||||||
|
|
||||||
Please check out our [discord server](https://discord.gg/p7nuUNVfP7).
|
|
||||||
|
|
||||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
|
|
||||||
|
|
||||||
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ to understand the requirements before sending your pull-requests.
|
|||||||
Coding is not a necessity to contribute - maybe start with improving our documentation?
|
Coding is not a necessity to contribute - maybe start with improving our documentation?
|
||||||
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
|
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
|
||||||
|
|
||||||
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/p7nuUNVfP7) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
|
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/p7nuUNVfP7) (please use the #dev channel for this). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
|
||||||
|
|
||||||
**Important:** Always create your PR against the `develop` branch, not `stable`.
|
**Important:** Always create your PR against the `develop` branch, not `stable`.
|
||||||
|
|
||||||
|
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
Normal file
Binary file not shown.
@ -11,10 +11,18 @@ if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
|
|||||||
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess \
|
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' -o config.guess \
|
||||||
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \
|
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \
|
||||||
&& ./configure --prefix=${INSTALL_LOC}/ \
|
&& ./configure --prefix=${INSTALL_LOC}/ \
|
||||||
&& make -j$(nproc) \
|
&& make
|
||||||
&& which sudo && sudo make install || make install \
|
if [ $? -ne 0 ]; then
|
||||||
&& cd ..
|
echo "Failed building ta-lib."
|
||||||
|
cd .. && rm -rf ./ta-lib/
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
which sudo && sudo make install || make install
|
||||||
|
if [ -x "$(command -v apt-get)" ]; then
|
||||||
|
echo "Updating library path using ldconfig"
|
||||||
|
sudo ldconfig
|
||||||
|
fi
|
||||||
|
cd .. && rm -rf ./ta-lib/
|
||||||
else
|
else
|
||||||
echo "TA-lib already installed, skipping installation"
|
echo "TA-lib already installed, skipping installation"
|
||||||
fi
|
fi
|
||||||
# && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
|
|
||||||
|
@ -6,10 +6,13 @@ python -m pip install --upgrade pip
|
|||||||
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||||
|
|
||||||
if ($pyv -eq '3.7') {
|
if ($pyv -eq '3.7') {
|
||||||
pip install build_helpers\TA_Lib-0.4.20-cp37-cp37m-win_amd64.whl
|
pip install build_helpers\TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
|
||||||
}
|
}
|
||||||
if ($pyv -eq '3.8') {
|
if ($pyv -eq '3.8') {
|
||||||
pip install build_helpers\TA_Lib-0.4.20-cp38-cp38-win_amd64.whl
|
pip install build_helpers\TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
|
||||||
|
}
|
||||||
|
if ($pyv -eq '3.9') {
|
||||||
|
pip install build_helpers\TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
|
||||||
}
|
}
|
||||||
|
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
78
build_helpers/publish_docker_arm64.sh
Executable file
78
build_helpers/publish_docker_arm64.sh
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Use BuildKit, otherwise building on ARM fails
|
||||||
|
export DOCKER_BUILDKIT=1
|
||||||
|
|
||||||
|
# Replace / with _ to create a valid tag
|
||||||
|
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
|
||||||
|
TAG_PLOT=${TAG}_plot
|
||||||
|
TAG_PI="${TAG}_pi"
|
||||||
|
|
||||||
|
TAG_ARM=${TAG}_arm
|
||||||
|
TAG_PLOT_ARM=${TAG_PLOT}_arm
|
||||||
|
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
||||||
|
|
||||||
|
echo "Running for ${TAG}"
|
||||||
|
|
||||||
|
# Add commit and commit_message to docker container
|
||||||
|
echo "${GITHUB_SHA}" > freqtrade_commit
|
||||||
|
|
||||||
|
if [ "${GITHUB_EVENT_NAME}" = "schedule" ]; then
|
||||||
|
echo "event ${GITHUB_EVENT_NAME}: full rebuild - skipping cache"
|
||||||
|
# Build regular image
|
||||||
|
docker build -t freqtrade:${TAG_ARM} .
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "event ${GITHUB_EVENT_NAME}: building with cache"
|
||||||
|
# Build regular image
|
||||||
|
docker pull ${IMAGE_NAME}:${TAG_ARM}
|
||||||
|
docker build --cache-from ${IMAGE_NAME}:${TAG_ARM} -t freqtrade:${TAG_ARM} .
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "failed building multiarch images"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# Tag image for upload and next build step
|
||||||
|
docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM
|
||||||
|
|
||||||
|
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
|
||||||
|
|
||||||
|
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
|
|
||||||
|
# Run backtest
|
||||||
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "failed running backtest"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker images
|
||||||
|
|
||||||
|
# docker push ${IMAGE_NAME}
|
||||||
|
docker push ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
|
docker push ${CACHE_IMAGE}:$TAG_ARM
|
||||||
|
|
||||||
|
# Create multi-arch image
|
||||||
|
# Make sure that all images contained here are pushed to github first.
|
||||||
|
# Otherwise installation might fail.
|
||||||
|
echo "create manifests"
|
||||||
|
|
||||||
|
docker manifest create --amend ${IMAGE_NAME}:${TAG} ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
||||||
|
docker manifest push -p ${IMAGE_NAME}:${TAG}
|
||||||
|
|
||||||
|
docker manifest create ${IMAGE_NAME}:${TAG_PLOT} ${CACHE_IMAGE}:${TAG_PLOT_ARM} ${CACHE_IMAGE}:${TAG_PLOT}
|
||||||
|
docker manifest push -p ${IMAGE_NAME}:${TAG_PLOT}
|
||||||
|
|
||||||
|
# Tag as latest for develop builds
|
||||||
|
if [ "${TAG}" = "develop" ]; then
|
||||||
|
docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
||||||
|
docker manifest push -p ${IMAGE_NAME}:latest
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker images
|
||||||
|
|
||||||
|
# Cleanup old images from arm64 node.
|
||||||
|
docker image prune -a --force --filter "until=24h"
|
@ -9,7 +9,8 @@ TAG_PI="${TAG}_pi"
|
|||||||
|
|
||||||
PI_PLATFORM="linux/arm/v7"
|
PI_PLATFORM="linux/arm/v7"
|
||||||
echo "Running for ${TAG}"
|
echo "Running for ${TAG}"
|
||||||
CACHE_TAG=freqtradeorg/freqtrade_cache:${TAG}_cache
|
CACHE_IMAGE=freqtradeorg/freqtrade_cache
|
||||||
|
CACHE_TAG=${CACHE_IMAGE}:${TAG_PI}_cache
|
||||||
|
|
||||||
# Add commit and commit_message to docker container
|
# Add commit and commit_message to docker container
|
||||||
echo "${GITHUB_SHA}" > freqtrade_commit
|
echo "${GITHUB_SHA}" > freqtrade_commit
|
||||||
@ -45,14 +46,14 @@ if [ $? -ne 0 ]; then
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
# Tag image for upload and next build step
|
# Tag image for upload and next build step
|
||||||
docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG
|
docker tag freqtrade:$TAG ${CACHE_IMAGE}:$TAG
|
||||||
|
|
||||||
docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
|
docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
|
||||||
|
|
||||||
docker tag freqtrade:$TAG_PLOT ${IMAGE_NAME}:$TAG_PLOT
|
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_bittrex.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
@ -61,22 +62,9 @@ fi
|
|||||||
|
|
||||||
docker images
|
docker images
|
||||||
|
|
||||||
docker push ${IMAGE_NAME}
|
docker push ${CACHE_IMAGE}
|
||||||
docker push ${IMAGE_NAME}:$TAG_PLOT
|
docker push ${CACHE_IMAGE}:$TAG_PLOT
|
||||||
docker push ${IMAGE_NAME}:$TAG
|
docker push ${CACHE_IMAGE}:$TAG
|
||||||
|
|
||||||
# Create multiarch image
|
|
||||||
# Make sure that all images contained here are pushed to github first.
|
|
||||||
# Otherwise installation might fail.
|
|
||||||
|
|
||||||
docker manifest create freqtradeorg/freqtrade:${TAG} ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:${TAG_PI}
|
|
||||||
docker manifest push freqtradeorg/freqtrade:${TAG}
|
|
||||||
|
|
||||||
# Tag as latest for develop builds
|
|
||||||
if [ "${TAG}" = "develop" ]; then
|
|
||||||
docker manifest create freqtradeorg/freqtrade:latest ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:${TAG_PI}
|
|
||||||
docker manifest push freqtradeorg/freqtrade:latest
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
docker images
|
docker images
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@ -21,21 +21,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"ask_strategy": {
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_min": 1,
|
"order_book_top": 1
|
||||||
"order_book_max": 1,
|
|
||||||
"use_sell_signal": true,
|
|
||||||
"sell_profit_only": false,
|
|
||||||
"ignore_roi_if_buy_signal": false
|
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "binance",
|
"name": "binance",
|
||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": true,
|
|
||||||
"rateLimit": 200
|
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"ALGO/BTC",
|
"ALGO/BTC",
|
@ -12,7 +12,7 @@
|
|||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
@ -21,12 +21,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_min": 1,
|
"order_book_top": 1
|
||||||
"order_book_max": 1,
|
|
||||||
"use_sell_signal": true,
|
|
||||||
"sell_profit_only": false,
|
|
||||||
"ignore_roi_if_buy_signal": false
|
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "bittrex",
|
"name": "bittrex",
|
@ -13,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@ -21,22 +21,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy": {
|
"ask_strategy": {
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_min": 1,
|
"order_book_top": 1
|
||||||
"order_book_max": 1,
|
|
||||||
"use_sell_signal": true,
|
|
||||||
"sell_profit_only": false,
|
|
||||||
"ignore_roi_if_buy_signal": false
|
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "ftx",
|
"name": "ftx",
|
||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {},
|
||||||
"enableRateLimit": true,
|
|
||||||
"rateLimit": 50
|
|
||||||
},
|
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC/USD",
|
"BTC/USD",
|
||||||
"ETH/USD",
|
"ETH/USD",
|
@ -14,6 +14,10 @@
|
|||||||
"trailing_stop_positive": 0.005,
|
"trailing_stop_positive": 0.005,
|
||||||
"trailing_stop_positive_offset": 0.0051,
|
"trailing_stop_positive_offset": 0.0051,
|
||||||
"trailing_only_offset_is_reached": false,
|
"trailing_only_offset_is_reached": false,
|
||||||
|
"use_sell_signal": true,
|
||||||
|
"sell_profit_only": false,
|
||||||
|
"sell_profit_offset": 0.0,
|
||||||
|
"ignore_roi_if_buy_signal": false,
|
||||||
"minimal_roi": {
|
"minimal_roi": {
|
||||||
"40": 0.0,
|
"40": 0.0,
|
||||||
"30": 0.01,
|
"30": 0.01,
|
||||||
@ -28,7 +32,7 @@
|
|||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"price_side": "bid",
|
"price_side": "bid",
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
@ -38,13 +42,8 @@
|
|||||||
},
|
},
|
||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"price_side": "ask",
|
"price_side": "ask",
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_min": 1,
|
"order_book_top": 1
|
||||||
"order_book_max": 1,
|
|
||||||
"use_sell_signal": true,
|
|
||||||
"sell_profit_only": false,
|
|
||||||
"sell_profit_offset": 0.0,
|
|
||||||
"ignore_roi_if_buy_signal": false
|
|
||||||
},
|
},
|
||||||
"order_types": {
|
"order_types": {
|
||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
@ -79,45 +78,14 @@
|
|||||||
"refresh_period": 1440
|
"refresh_period": 1440
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"protections": [
|
|
||||||
{
|
|
||||||
"method": "StoplossGuard",
|
|
||||||
"lookback_period_candles": 60,
|
|
||||||
"trade_limit": 4,
|
|
||||||
"stop_duration_candles": 60,
|
|
||||||
"only_per_pair": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "CooldownPeriod",
|
|
||||||
"stop_duration_candles": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "MaxDrawdown",
|
|
||||||
"lookback_period_candles": 200,
|
|
||||||
"trade_limit": 20,
|
|
||||||
"stop_duration_candles": 10,
|
|
||||||
"max_allowed_drawdown": 0.2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "LowProfitPairs",
|
|
||||||
"lookback_period_candles": 360,
|
|
||||||
"trade_limit": 1,
|
|
||||||
"stop_duration_candles": 2,
|
|
||||||
"required_profit": 0.02
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "binance",
|
"name": "binance",
|
||||||
"sandbox": false,
|
"sandbox": false,
|
||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"password": "",
|
"password": "",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {},
|
||||||
"enableRateLimit": true,
|
|
||||||
"rateLimit": 500,
|
|
||||||
"aiohttp_trust_env": false
|
|
||||||
},
|
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"ALGO/BTC",
|
"ALGO/BTC",
|
||||||
"ATOM/BTC",
|
"ATOM/BTC",
|
||||||
@ -177,8 +145,12 @@
|
|||||||
},
|
},
|
||||||
"sell_fill": "on",
|
"sell_fill": "on",
|
||||||
"buy_cancel": "on",
|
"buy_cancel": "on",
|
||||||
"sell_cancel": "on"
|
"sell_cancel": "on",
|
||||||
}
|
"protection_trigger": "off",
|
||||||
|
"protection_trigger_global": "on"
|
||||||
|
},
|
||||||
|
"reload": true,
|
||||||
|
"balance_dust_level": 0.01
|
||||||
},
|
},
|
||||||
"api_server": {
|
"api_server": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@ -200,7 +172,7 @@
|
|||||||
"heartbeat_interval": 60
|
"heartbeat_interval": 60
|
||||||
},
|
},
|
||||||
"disable_dataframe_checks": false,
|
"disable_dataframe_checks": false,
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "SampleStrategy",
|
||||||
"strategy_path": "user_data/strategies/",
|
"strategy_path": "user_data/strategies/",
|
||||||
"dataformat_ohlcv": "json",
|
"dataformat_ohlcv": "json",
|
||||||
"dataformat_trades": "jsongz"
|
"dataformat_trades": "jsongz"
|
@ -12,7 +12,7 @@
|
|||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"ask_last_balance": 0.0,
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
@ -21,21 +21,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ask_strategy":{
|
"ask_strategy":{
|
||||||
"use_order_book": false,
|
"use_order_book": true,
|
||||||
"order_book_min": 1,
|
"order_book_top": 1
|
||||||
"order_book_max": 1,
|
|
||||||
"use_sell_signal": true,
|
|
||||||
"sell_profit_only": false,
|
|
||||||
"ignore_roi_if_buy_signal": false
|
|
||||||
},
|
},
|
||||||
"exchange": {
|
"exchange": {
|
||||||
"name": "kraken",
|
"name": "kraken",
|
||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_key",
|
"secret": "your_exchange_key",
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
"ccxt_config": {},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": true,
|
|
||||||
"rateLimit": 1000
|
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"ADA/EUR",
|
"ADA/EUR",
|
@ -15,10 +15,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- "./user_data:/freqtrade/user_data"
|
- "./user_data:/freqtrade/user_data"
|
||||||
# Expose api on port 8080 (localhost only)
|
# Expose api on port 8080 (localhost only)
|
||||||
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
# Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation
|
||||||
# before enabling this.
|
# before enabling this.
|
||||||
# ports:
|
ports:
|
||||||
# - "127.0.0.1:8080:8080"
|
- "127.0.0.1:8080:8080"
|
||||||
# Default command used when running `docker compose up`
|
# Default command used when running `docker compose up`
|
||||||
command: >
|
command: >
|
||||||
trade
|
trade
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
FROM --platform=linux/arm64/v8 python:3.9.4-slim-buster as base
|
|
||||||
|
|
||||||
# Setup env
|
|
||||||
ENV LANG C.UTF-8
|
|
||||||
ENV LC_ALL C.UTF-8
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
|
||||||
ENV PYTHONFAULTHANDLER 1
|
|
||||||
ENV PATH=/home/ftuser/.local/bin:$PATH
|
|
||||||
ENV FT_APP_ENV="docker"
|
|
||||||
|
|
||||||
# Prepare environment
|
|
||||||
RUN mkdir /freqtrade \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& useradd -u 1000 -G sudo -U -m ftuser \
|
|
||||||
&& chown ftuser:ftuser /freqtrade \
|
|
||||||
# Allow sudoers
|
|
||||||
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
|
|
||||||
|
|
||||||
WORKDIR /freqtrade
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
FROM base as python-deps
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& pip install --upgrade pip
|
|
||||||
|
|
||||||
# Install TA-lib
|
|
||||||
COPY build_helpers/* /tmp/
|
|
||||||
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
|
|
||||||
ENV LD_LIBRARY_PATH /usr/local/lib
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/
|
|
||||||
USER ftuser
|
|
||||||
RUN pip install --user --no-cache-dir numpy \
|
|
||||||
&& pip install --user --no-cache-dir -r requirements-hyperopt.txt
|
|
||||||
|
|
||||||
# Copy dependencies to runtime-image
|
|
||||||
FROM base as runtime-image
|
|
||||||
COPY --from=python-deps /usr/local/lib /usr/local/lib
|
|
||||||
ENV LD_LIBRARY_PATH /usr/local/lib
|
|
||||||
|
|
||||||
COPY --from=python-deps --chown=ftuser:ftuser /home/ftuser/.local /home/ftuser/.local
|
|
||||||
|
|
||||||
USER ftuser
|
|
||||||
# Install and execute
|
|
||||||
COPY --chown=ftuser:ftuser . /freqtrade/
|
|
||||||
|
|
||||||
RUN pip install -e . --user --no-cache-dir --no-build-isolation\
|
|
||||||
&& mkdir /freqtrade/user_data/ \
|
|
||||||
&& freqtrade install-ui
|
|
||||||
|
|
||||||
ENTRYPOINT ["freqtrade"]
|
|
||||||
# Default to trade mode
|
|
||||||
CMD [ "trade" ]
|
|
@ -1,5 +1,6 @@
|
|||||||
ARG sourceimage=develop
|
ARG sourceimage=freqtradeorg/freqtrade
|
||||||
FROM freqtradeorg/freqtrade:${sourceimage}
|
ARG sourcetag=develop
|
||||||
|
FROM ${sourceimage}:${sourcetag}
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
COPY requirements-plot.txt /freqtrade/
|
COPY requirements-plot.txt /freqtrade/
|
||||||
|
@ -32,6 +32,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
|
|||||||
def hyperopt_loss_function(results: DataFrame, trade_count: int,
|
def hyperopt_loss_function(results: DataFrame, trade_count: int,
|
||||||
min_date: datetime, max_date: datetime,
|
min_date: datetime, max_date: datetime,
|
||||||
config: Dict, processed: Dict[str, DataFrame],
|
config: Dict, processed: Dict[str, DataFrame],
|
||||||
|
backtest_stats: Dict[str, Any],
|
||||||
*args, **kwargs) -> float:
|
*args, **kwargs) -> float:
|
||||||
"""
|
"""
|
||||||
Objective function, returns smaller number for better results
|
Objective function, returns smaller number for better results
|
||||||
@ -53,7 +54,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
|
|||||||
|
|
||||||
Currently, the arguments are:
|
Currently, the arguments are:
|
||||||
|
|
||||||
* `results`: DataFrame containing the result
|
* `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`):
|
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, sell_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
|
`pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, sell_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
|
||||||
* `trade_count`: Amount of trades (identical to `len(results)`)
|
* `trade_count`: Amount of trades (identical to `len(results)`)
|
||||||
@ -61,14 +62,15 @@ Currently, the arguments are:
|
|||||||
* `min_date`: End 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).
|
* `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.
|
* `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.
|
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
|
!!! Note
|
||||||
This function is called once per iteration - so please make sure to have this as optimized as possible to not slow hyperopt down unnecessarily.
|
This function is called once per epoch - so please make sure to have this as optimized as possible to not slow hyperopt down unnecessarily.
|
||||||
|
|
||||||
!!! Note
|
!!! Note "`*args` and `**kwargs`"
|
||||||
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface later.
|
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface in the future.
|
||||||
|
|
||||||
## Overriding pre-defined spaces
|
## Overriding pre-defined spaces
|
||||||
|
|
||||||
@ -78,10 +80,56 @@ To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_sp
|
|||||||
class MyAwesomeStrategy(IStrategy):
|
class MyAwesomeStrategy(IStrategy):
|
||||||
class HyperOpt:
|
class HyperOpt:
|
||||||
# Define a custom stoploss space.
|
# Define a custom stoploss space.
|
||||||
def stoploss_space(self):
|
def stoploss_space():
|
||||||
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
|
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'),
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
All overrides are optional and can be mixed/matched as necessary.
|
||||||
|
|
||||||
|
### Overriding Base estimator
|
||||||
|
|
||||||
|
You can define your own estimator for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
class HyperOpt:
|
||||||
|
def generate_estimator():
|
||||||
|
return "RF"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Possible values are either one of "GP", "RF", "ET", "GBRT" (Details can be found in the [scikit-optimize documentation](https://scikit-optimize.github.io/)), 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:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
class HyperOpt:
|
||||||
|
def generate_estimator():
|
||||||
|
from skopt.learning import ExtraTreesRegressor
|
||||||
|
# Corresponds to "ET" - but allows additional parameters.
|
||||||
|
return ExtraTreesRegressor(n_estimators=100)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! 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
|
## Space options
|
||||||
|
|
||||||
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
|
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
|
||||||
@ -103,281 +151,3 @@ from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal,
|
|||||||
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]`).
|
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]`).
|
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]`).
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Legacy Hyperopt
|
|
||||||
|
|
||||||
This Section explains the configuration of an explicit Hyperopt file (separate to the strategy).
|
|
||||||
|
|
||||||
!!! Warning "Deprecated / legacy mode"
|
|
||||||
Since the 2021.4 release you no longer have to write a separate hyperopt class, but all strategies can be hyperopted.
|
|
||||||
Please read the [main hyperopt page](hyperopt.md) for more details.
|
|
||||||
|
|
||||||
### Prepare hyperopt file
|
|
||||||
|
|
||||||
Configuring an explicit hyperopt file is similar to writing your own strategy, and many tasks will be similar.
|
|
||||||
|
|
||||||
!!! Tip "About this page"
|
|
||||||
For this page, we will be using a fictional strategy called `AwesomeStrategy` - which will be optimized using the `AwesomeHyperopt` class.
|
|
||||||
|
|
||||||
#### Create a Custom Hyperopt File
|
|
||||||
|
|
||||||
The simplest way to get started is to use the following command, which will create a new hyperopt file from a template, which will be located under `user_data/hyperopts/AwesomeHyperopt.py`.
|
|
||||||
|
|
||||||
Let assume you want a hyperopt file `AwesomeHyperopt.py`:
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Legacy Hyperopt checklist
|
|
||||||
|
|
||||||
Checklist on all tasks / possibilities in hyperopt
|
|
||||||
|
|
||||||
Depending on the space you want to optimize, only some of the below are required:
|
|
||||||
|
|
||||||
* fill `buy_strategy_generator` - for buy signal optimization
|
|
||||||
* fill `indicator_space` - for buy signal optimization
|
|
||||||
* fill `sell_strategy_generator` - for sell signal optimization
|
|
||||||
* fill `sell_indicator_space` - for sell signal optimization
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
`populate_indicators` needs to create all indicators any of thee spaces may use, otherwise hyperopt will not work.
|
|
||||||
|
|
||||||
Optional in hyperopt - can also be loaded from a strategy (recommended):
|
|
||||||
|
|
||||||
* `populate_indicators` - fallback to create indicators
|
|
||||||
* `populate_buy_trend` - fallback if not optimizing for buy space. should come from strategy
|
|
||||||
* `populate_sell_trend` - fallback if not optimizing for sell space. should come from strategy
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods.
|
|
||||||
Assuming the optional methods are not in your hyperopt file, please use `--strategy AweSomeStrategy` which contains these methods so hyperopt can use these methods instead.
|
|
||||||
|
|
||||||
Rarely you may also need to override:
|
|
||||||
|
|
||||||
* `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)
|
|
||||||
|
|
||||||
#### Defining a buy signal optimization
|
|
||||||
|
|
||||||
Let's say you are curious: should you use MACD crossings or lower Bollinger
|
|
||||||
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
|
|
||||||
help with those buy 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.
|
|
||||||
|
|
||||||
We will start by defining a search space:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def indicator_space() -> List[Dimension]:
|
|
||||||
"""
|
|
||||||
Define your Hyperopt space for searching strategy parameters
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
Integer(20, 40, name='adx-value'),
|
|
||||||
Integer(20, 40, name='rsi-value'),
|
|
||||||
Categorical([True, False], name='adx-enabled'),
|
|
||||||
Categorical([True, False], name='rsi-enabled'),
|
|
||||||
Categorical(['bb_lower', 'macd_cross_signal'], name='trigger')
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Above definition says: I have five parameters I want you to randomly combine
|
|
||||||
to find the best combination. Two of them are integer values (`adx-value` and `rsi-value`) and I want you test in the range of values 20 to 40.
|
|
||||||
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.
|
|
||||||
|
|
||||||
So let's write the buy strategy generator using these values:
|
|
||||||
|
|
||||||
```python
|
|
||||||
@staticmethod
|
|
||||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
|
||||||
"""
|
|
||||||
Define the buy strategy parameters to be used by Hyperopt.
|
|
||||||
"""
|
|
||||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
conditions = []
|
|
||||||
# GUARDS AND TRENDS
|
|
||||||
if 'adx-enabled' in params and params['adx-enabled']:
|
|
||||||
conditions.append(dataframe['adx'] > params['adx-value'])
|
|
||||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
|
||||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
|
||||||
|
|
||||||
# TRIGGERS
|
|
||||||
if 'trigger' in params:
|
|
||||||
if params['trigger'] == 'bb_lower':
|
|
||||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
|
||||||
if params['trigger'] == 'macd_cross_signal':
|
|
||||||
conditions.append(qtpylib.crossed_above(
|
|
||||||
dataframe['macd'], dataframe['macdsignal']
|
|
||||||
))
|
|
||||||
|
|
||||||
# Check that volume is not 0
|
|
||||||
conditions.append(dataframe['volume'] > 0)
|
|
||||||
|
|
||||||
if conditions:
|
|
||||||
dataframe.loc[
|
|
||||||
reduce(lambda x, y: x & y, conditions),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
return populate_buy_trend
|
|
||||||
```
|
|
||||||
|
|
||||||
Hyperopt will now call `populate_buy_trend()` many times (`epochs`) with different value combinations.
|
|
||||||
It will use the given historical data and make 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](#loss-functions)).
|
|
||||||
|
|
||||||
!!! 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.
|
|
||||||
|
|
||||||
#### Sell optimization
|
|
||||||
|
|
||||||
Similar to the buy-signal above, sell-signals can also be optimized.
|
|
||||||
Place the corresponding settings into the following methods
|
|
||||||
|
|
||||||
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
|
|
||||||
* Within `sell_strategy_generator()` - populate the nested method `populate_sell_trend()` to apply the parameters.
|
|
||||||
|
|
||||||
The configuration and rules are the same than for buy signals.
|
|
||||||
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
|
|
||||||
|
|
||||||
### 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. More time usually results in better results.
|
|
||||||
|
|
||||||
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
freqtrade hyperopt --config config.json --hyperopt <hyperoptname> --hyperopt-loss <hyperoptlossname> --strategy <strategyname> -e 500 --spaces all
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
|
||||||
|
|
||||||
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/`.
|
|
||||||
|
|
||||||
#### Running Hyperopt using methods from a strategy
|
|
||||||
|
|
||||||
Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
freqtrade hyperopt --hyperopt AwesomeHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy AwesomeStrategy
|
|
||||||
```
|
|
||||||
|
|
||||||
### Understand the Hyperopt Result
|
|
||||||
|
|
||||||
Once Hyperopt is completed you can use the result to create a new 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:
|
|
||||||
{ 'adx-value': 44,
|
|
||||||
'rsi-value': 29,
|
|
||||||
'adx-enabled': False,
|
|
||||||
'rsi-enabled': True,
|
|
||||||
'trigger': 'bb_lower'}
|
|
||||||
```
|
|
||||||
|
|
||||||
You should understand this result like:
|
|
||||||
|
|
||||||
* The buy trigger that worked best was `bb_lower`.
|
|
||||||
* You should not use ADX because `adx-enabled: False`)
|
|
||||||
* You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
|
|
||||||
|
|
||||||
You have to look inside your strategy file into `buy_strategy_generator()`
|
|
||||||
method, what those values match to.
|
|
||||||
|
|
||||||
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
|
|
||||||
|
|
||||||
```python
|
|
||||||
(dataframe['rsi'] < 29.0)
|
|
||||||
```
|
|
||||||
|
|
||||||
Translating your whole hyperopt result as the new buy-signal would then look like:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
|
||||||
dataframe.loc[
|
|
||||||
(
|
|
||||||
(dataframe['rsi'] < 29.0) & # rsi-value
|
|
||||||
dataframe['close'] < dataframe['bb_lowerband'] # trigger
|
|
||||||
),
|
|
||||||
'buy'] = 1
|
|
||||||
return dataframe
|
|
||||||
```
|
|
||||||
|
|
||||||
### Validate backtesting results
|
|
||||||
|
|
||||||
Once the optimized parameters and conditions have been implemented into your strategy, you should backtest the strategy to make sure everything is working as expected.
|
|
||||||
|
|
||||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
|
||||||
|
|
||||||
### Sharing methods with your strategy
|
|
||||||
|
|
||||||
Hyperopt classes provide access to the Strategy via the `strategy` class attribute.
|
|
||||||
This can be a great way to reduce code duplication if used correctly, but will also complicate usage for inexperienced users.
|
|
||||||
|
|
||||||
``` python
|
|
||||||
from pandas import DataFrame
|
|
||||||
from freqtrade.strategy.interface import IStrategy
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|
||||||
|
|
||||||
class MyAwesomeStrategy(IStrategy):
|
|
||||||
|
|
||||||
buy_params = {
|
|
||||||
'rsi-value': 30,
|
|
||||||
'adx-value': 35,
|
|
||||||
}
|
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
return self.buy_strategy_generator(self.buy_params, dataframe, metadata)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def buy_strategy_generator(params, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
dataframe.loc[
|
|
||||||
(
|
|
||||||
qtpylib.crossed_above(dataframe['rsi'], params['rsi-value']) &
|
|
||||||
dataframe['adx'] > params['adx-value']) &
|
|
||||||
dataframe['volume'] > 0
|
|
||||||
)
|
|
||||||
, 'buy'] = 1
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
class MyAwesomeHyperOpt(IHyperOpt):
|
|
||||||
...
|
|
||||||
@staticmethod
|
|
||||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
|
||||||
"""
|
|
||||||
Define the buy strategy parameters to be used by Hyperopt.
|
|
||||||
"""
|
|
||||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
# Call strategy's buy strategy generator
|
|
||||||
return self.StrategyClass.buy_strategy_generator(params, dataframe, metadata)
|
|
||||||
|
|
||||||
return populate_buy_trend
|
|
||||||
```
|
|
||||||
|
@ -52,6 +52,71 @@ freqtrade trade -c MyConfigUSDT.json -s MyCustomStrategy --db-url sqlite:///user
|
|||||||
|
|
||||||
For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md).
|
For more information regarding usage of the sqlite databases, for example to manually enter or remove trades, please refer to the [SQL Cheatsheet](sql_cheatsheet.md).
|
||||||
|
|
||||||
|
### Multiple instances using docker
|
||||||
|
|
||||||
|
To run multiple instances of freqtrade using docker you will need to edit the docker-compose.yml file and add all the instances you want as separate services. Remember, you can separate your configuration into multiple files, so it's a good idea to think about making them modular, then if you need to edit something common to all bots, you can do that in a single config file.
|
||||||
|
``` yml
|
||||||
|
---
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
freqtrade1:
|
||||||
|
image: freqtradeorg/freqtrade:stable
|
||||||
|
# image: freqtradeorg/freqtrade:develop
|
||||||
|
# Use plotting image
|
||||||
|
# image: freqtradeorg/freqtrade:develop_plot
|
||||||
|
# Build step - only needed when additional dependencies are needed
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: "./docker/Dockerfile.custom"
|
||||||
|
restart: always
|
||||||
|
container_name: freqtrade1
|
||||||
|
volumes:
|
||||||
|
- "./user_data:/freqtrade/user_data"
|
||||||
|
# Expose api on port 8080 (localhost only)
|
||||||
|
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
||||||
|
# before enabling this.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8080:8080"
|
||||||
|
# Default command used when running `docker compose up`
|
||||||
|
command: >
|
||||||
|
trade
|
||||||
|
--logfile /freqtrade/user_data/logs/freqtrade1.log
|
||||||
|
--db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade1.sqlite
|
||||||
|
--config /freqtrade/user_data/config.json
|
||||||
|
--config /freqtrade/user_data/config.freqtrade1.json
|
||||||
|
--strategy SampleStrategy
|
||||||
|
|
||||||
|
freqtrade2:
|
||||||
|
image: freqtradeorg/freqtrade:stable
|
||||||
|
# image: freqtradeorg/freqtrade:develop
|
||||||
|
# Use plotting image
|
||||||
|
# image: freqtradeorg/freqtrade:develop_plot
|
||||||
|
# Build step - only needed when additional dependencies are needed
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: "./docker/Dockerfile.custom"
|
||||||
|
restart: always
|
||||||
|
container_name: freqtrade2
|
||||||
|
volumes:
|
||||||
|
- "./user_data:/freqtrade/user_data"
|
||||||
|
# Expose api on port 8080 (localhost only)
|
||||||
|
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
|
||||||
|
# before enabling this.
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8081:8080"
|
||||||
|
# Default command used when running `docker compose up`
|
||||||
|
command: >
|
||||||
|
trade
|
||||||
|
--logfile /freqtrade/user_data/logs/freqtrade2.log
|
||||||
|
--db-url sqlite:////freqtrade/user_data/tradesv3_freqtrade2.sqlite
|
||||||
|
--config /freqtrade/user_data/config.json
|
||||||
|
--config /freqtrade/user_data/config.freqtrade2.json
|
||||||
|
--strategy SampleStrategy
|
||||||
|
|
||||||
|
```
|
||||||
|
You can use whatever naming convention you want, freqtrade1 and 2 are arbitrary. Note, that you will need to use different database files, port mappings and telegram configurations for each instance, as mentioned above.
|
||||||
|
|
||||||
|
|
||||||
## Configure the bot running as a systemd service
|
## Configure the bot running as a systemd service
|
||||||
|
|
||||||
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
Copy the `freqtrade.service` file to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
|
||||||
|
@ -18,8 +18,10 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
|
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
|
||||||
[--enable-protections]
|
[--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
|
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
[--export EXPORT] [--export-filename PATH]
|
[--export {none,trades}] [--export-filename PATH]
|
||||||
|
[--breakdown {day,week,month} [{day,week,month} ...]]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -29,7 +31,7 @@ optional arguments:
|
|||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
--data-format-ohlcv {json,jsongz,hdf5}
|
--data-format-ohlcv {json,jsongz,hdf5}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `None`).
|
(default: `json`).
|
||||||
--max-open-trades INT
|
--max-open-trades INT
|
||||||
Override the value of the `max_open_trades`
|
Override the value of the `max_open_trades`
|
||||||
configuration setting.
|
configuration setting.
|
||||||
@ -55,21 +57,25 @@ optional arguments:
|
|||||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||||
Starting balance, used for backtesting / hyperopt and
|
Starting balance, used for backtesting / hyperopt and
|
||||||
dry-runs.
|
dry-runs.
|
||||||
|
--timeframe-detail TIMEFRAME_DETAIL
|
||||||
|
Specify detail timeframe for backtesting (`1m`, `5m`,
|
||||||
|
`30m`, `1h`, `1d`).
|
||||||
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
||||||
Provide a space-separated list of strategies to
|
Provide a space-separated list of strategies to
|
||||||
backtest. Please note that ticker-interval needs to be
|
backtest. Please note that ticker-interval needs to be
|
||||||
set either in config or via command line. When using
|
set either in config or via command line. When using
|
||||||
this together with `--export trades`, the strategy-
|
this together with `--export trades`, the strategy-
|
||||||
name is injected into the filename (so `backtest-
|
name is injected into the filename (so `backtest-
|
||||||
data.json` becomes `backtest-data-
|
data.json` becomes `backtest-data-SampleStrategy.json`
|
||||||
DefaultStrategy.json`
|
--export {none,trades}
|
||||||
--export EXPORT Export backtest results, argument are: trades.
|
Export backtest results (default: trades).
|
||||||
Example: `--export=trades`
|
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
Save backtest results to the file with this filename.
|
Save backtest results to the file with this filename.
|
||||||
Requires `--export` to be set as well. Example:
|
Requires `--export` to be set as well. Example:
|
||||||
`--export-filename=user_data/backtest_results/backtest
|
`--export-filename=user_data/backtest_results/backtest
|
||||||
_today.json`
|
_today.json`
|
||||||
|
--breakdown {day,week,month} [{day,week,month} ...]
|
||||||
|
Show backtesting breakdown per [day, week, month].
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
@ -100,7 +106,7 @@ Strategy arguments:
|
|||||||
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
||||||
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||||
|
|
||||||
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default.
|
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`.
|
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](data-download.md) section in the documentation.
|
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
|
||||||
|
|
||||||
@ -110,11 +116,16 @@ All profit calculations include fees, and freqtrade will use the exchange's defa
|
|||||||
|
|
||||||
!!! Warning "Using dynamic pairlists for backtesting"
|
!!! Warning "Using dynamic pairlists for backtesting"
|
||||||
Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
|
Using dynamic pairlists is possible, 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, reproducability of backtesting-results cannot be guaranteed.
|
Also, when using pairlists other than StaticPairlist, reproducibility of backtesting-results cannot be guaranteed.
|
||||||
Please read the [pairlists documentation](plugins.md#pairlists) for more information.
|
Please read the [pairlists documentation](plugins.md#pairlists) for more information.
|
||||||
|
|
||||||
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist.
|
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#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](#further-backtest-result-analysis) or can be used by the [plotting sub-command](plotting.md#plot-price-and-indicators) (`freqtrade plot-dataframe`) in the scripts directory.
|
||||||
|
|
||||||
|
|
||||||
### Starting balance
|
### 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.
|
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.
|
||||||
@ -174,13 +185,13 @@ Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Exporting trades to file
|
Prevent exporting trades to file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade backtesting --strategy backtesting --export trades --config config.json
|
freqtrade backtesting --strategy backtesting --export none --config config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts directory.
|
Only use this if you're sure you'll not want to plot or analyze your results further.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -279,7 +290,7 @@ A backtesting result will look like that:
|
|||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total trades | 429 |
|
| Total/Daily Avg Trades| 429 / 3.575 |
|
||||||
| Starting balance | 0.01000000 BTC |
|
| Starting balance | 0.01000000 BTC |
|
||||||
| Final balance | 0.01762792 BTC |
|
| Final balance | 0.01762792 BTC |
|
||||||
| Absolute profit | 0.00762792 BTC |
|
| Absolute profit | 0.00762792 BTC |
|
||||||
@ -297,7 +308,6 @@ A backtesting result will look like that:
|
|||||||
| Days win/draw/lose | 12 / 82 / 25 |
|
| Days win/draw/lose | 12 / 82 / 25 |
|
||||||
| Avg. Duration Winners | 4:23:00 |
|
| Avg. Duration Winners | 4:23:00 |
|
||||||
| Avg. Duration Loser | 6:55:00 |
|
| Avg. Duration Loser | 6:55:00 |
|
||||||
| Zero Duration Trades | 4.6% (20) |
|
|
||||||
| Rejected Buy signals | 3089 |
|
| Rejected Buy signals | 3089 |
|
||||||
| | |
|
| | |
|
||||||
| Min balance | 0.00945123 BTC |
|
| Min balance | 0.00945123 BTC |
|
||||||
@ -368,12 +378,11 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
| Backtesting to | 2019-05-01 00:00:00 |
|
| Backtesting to | 2019-05-01 00:00:00 |
|
||||||
| Max open trades | 3 |
|
| Max open trades | 3 |
|
||||||
| | |
|
| | |
|
||||||
| Total trades | 429 |
|
| Total/Daily Avg Trades| 429 / 3.575 |
|
||||||
| Starting balance | 0.01000000 BTC |
|
| Starting balance | 0.01000000 BTC |
|
||||||
| Final balance | 0.01762792 BTC |
|
| Final balance | 0.01762792 BTC |
|
||||||
| Absolute profit | 0.00762792 BTC |
|
| Absolute profit | 0.00762792 BTC |
|
||||||
| Total profit % | 76.2% |
|
| Total profit % | 76.2% |
|
||||||
| Trades per day | 3.575 |
|
|
||||||
| Avg. stake amount | 0.001 BTC |
|
| Avg. stake amount | 0.001 BTC |
|
||||||
| Total trade volume | 0.429 BTC |
|
| Total trade volume | 0.429 BTC |
|
||||||
| | |
|
| | |
|
||||||
@ -386,7 +395,6 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
| Days win/draw/lose | 12 / 82 / 25 |
|
| Days win/draw/lose | 12 / 82 / 25 |
|
||||||
| Avg. Duration Winners | 4:23:00 |
|
| Avg. Duration Winners | 4:23:00 |
|
||||||
| Avg. Duration Loser | 6:55:00 |
|
| Avg. Duration Loser | 6:55:00 |
|
||||||
| Zero Duration Trades | 4.6% (20) |
|
|
||||||
| Rejected Buy signals | 3089 |
|
| Rejected Buy signals | 3089 |
|
||||||
| | |
|
| | |
|
||||||
| Min balance | 0.00945123 BTC |
|
| Min balance | 0.00945123 BTC |
|
||||||
@ -404,12 +412,11 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
|
|
||||||
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
|
- `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).
|
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
||||||
- `Total trades`: Identical to the total trades of the backtest output table.
|
- `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).
|
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
||||||
- `Final balance`: Final balance - starting balance + absolute profit.
|
- `Final balance`: Final balance - starting balance + absolute profit.
|
||||||
- `Absolute profit`: Profit made in stake currency.
|
- `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`.
|
- `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital − Starting capital) / Starting capital`.
|
||||||
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
|
||||||
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
|
- `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.
|
- `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 `Cum Profit %`.
|
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
||||||
@ -417,7 +424,6 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
- `Best day` / `Worst day`: Best and worst day based on daily profit.
|
- `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).
|
- `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.
|
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
|
||||||
- `Zero Duration Trades`: A number of trades that completed within same candle as they opened and had `trailing_stop_loss` sell reason. A significant amount of such trades may indicate that strategy is exploiting trailing stoploss behavior in backtesting and produces unrealistic results.
|
|
||||||
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
|
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
|
||||||
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
|
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
|
||||||
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
|
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
|
||||||
@ -425,7 +431,37 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
- `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.
|
- `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.
|
||||||
|
|
||||||
### Assumptions made by backtesting
|
### 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:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day month
|
||||||
|
```
|
||||||
|
|
||||||
|
``` output
|
||||||
|
======================== 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.
|
||||||
|
|
||||||
|
### Further backtest-result analysis
|
||||||
|
|
||||||
|
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
||||||
|
You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) 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:
|
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
||||||
|
|
||||||
@ -441,6 +477,7 @@ Since backtesting lacks some detailed information about what happens within a ca
|
|||||||
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes
|
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell 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
|
- Low happens before high for stoploss, protecting capital first
|
||||||
- Trailing stoploss
|
- Trailing stoploss
|
||||||
|
- Trailing Stoploss is only adjusted if it's below the candle's low (otherwise it would be triggered)
|
||||||
- High happens first - adjusting stoploss
|
- High happens first - adjusting stoploss
|
||||||
- Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly)
|
- Low uses the adjusted stoploss (so sells 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
|
- ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies
|
||||||
@ -455,10 +492,30 @@ 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](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions.
|
In addition to the above assumptions, strategy authors should carefully read the [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions.
|
||||||
|
|
||||||
### Further backtest-result analysis
|
### Improved backtest accuracy
|
||||||
|
|
||||||
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
|
||||||
You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section.
|
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.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
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 for every "open trade candle" (candles where a trade is open) the 5m data will be used to simulate intra-candle movements.
|
||||||
|
All callback functions (`custom_sell()`, `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](#assumptions-made-by-backtesting). 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
|
## Backtesting multiple strategies
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ This page provides you some basic concepts on how Freqtrade works and operates.
|
|||||||
* **Strategy**: Your trading strategy, telling the bot what to do.
|
* **Strategy**: Your trading strategy, telling the bot what to do.
|
||||||
* **Trade**: Open position.
|
* **Trade**: Open position.
|
||||||
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
||||||
* **Pair**: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT).
|
* **Pair**: Tradable pair, usually in the format of Base/Quote (e.g. XRP/USDT).
|
||||||
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
||||||
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
||||||
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
||||||
@ -35,12 +35,13 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
|
|||||||
* Calls `check_buy_timeout()` strategy callback for open buy orders.
|
* Calls `check_buy_timeout()` strategy callback for open buy orders.
|
||||||
* Calls `check_sell_timeout()` strategy callback for open sell orders.
|
* Calls `check_sell_timeout()` strategy callback for open sell orders.
|
||||||
* Verifies existing positions and eventually places sell orders.
|
* Verifies existing positions and eventually places sell orders.
|
||||||
* Considers stoploss, ROI and sell-signal.
|
* Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`.
|
||||||
* Determine sell-price based on `ask_strategy` configuration setting.
|
* Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback.
|
||||||
* Before a sell order is placed, `confirm_trade_exit()` strategy callback is called.
|
* Before a sell order is placed, `confirm_trade_exit()` strategy callback is called.
|
||||||
* Check if trade-slots are still available (if `max_open_trades` is reached).
|
* Check if trade-slots are still available (if `max_open_trades` is reached).
|
||||||
* Verifies buy signal trying to enter new positions.
|
* Verifies buy signal trying to enter new positions.
|
||||||
* Determine buy-price based on `bid_strategy` configuration setting.
|
* Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback.
|
||||||
|
* Determine stake size by calling the `custom_stake_amount()` callback.
|
||||||
* Before a buy order is placed, `confirm_trade_entry()` strategy callback is called.
|
* Before a buy order is placed, `confirm_trade_entry()` strategy callback is called.
|
||||||
|
|
||||||
This loop will be repeated again and again until the bot is stopped.
|
This loop will be repeated again and again until the bot is stopped.
|
||||||
@ -52,9 +53,10 @@ This loop will be repeated again and again until the bot is stopped.
|
|||||||
* Load historic data for configured pairlist.
|
* Load historic data for configured pairlist.
|
||||||
* Calls `bot_loop_start()` once.
|
* Calls `bot_loop_start()` once.
|
||||||
* Calculate indicators (calls `populate_indicators()` once per pair).
|
* Calculate indicators (calls `populate_indicators()` once per pair).
|
||||||
* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair)
|
* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair).
|
||||||
* Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy)
|
|
||||||
* Loops per candle simulating entry and exit points.
|
* Loops per candle simulating entry and exit points.
|
||||||
|
* Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy).
|
||||||
|
* Call `custom_stoploss()` and `custom_sell()` to find custom exit points.
|
||||||
* Generate backtest report output
|
* Generate backtest report output
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
@ -12,22 +12,22 @@ This page explains the different parameters of the bot and how to run it.
|
|||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade [-h] [-V]
|
usage: freqtrade [-h] [-V]
|
||||||
{trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
|
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
||||||
...
|
...
|
||||||
|
|
||||||
Free, open source crypto trading bot
|
Free, open source crypto trading bot
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
{trade,create-userdir,new-config,new-hyperopt,new-strategy,download-data,convert-data,convert-trade-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,plot-dataframe,plot-profit}
|
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
|
||||||
trade Trade module.
|
trade Trade module.
|
||||||
create-userdir Create user-data directory.
|
create-userdir Create user-data directory.
|
||||||
new-config Create new config
|
new-config Create new config
|
||||||
new-hyperopt Create new hyperopt
|
|
||||||
new-strategy Create new strategy
|
new-strategy Create new strategy
|
||||||
download-data Download backtesting data.
|
download-data Download backtesting data.
|
||||||
convert-data Convert candle (OHLCV) data from one format to
|
convert-data Convert candle (OHLCV) data from one format to
|
||||||
another.
|
another.
|
||||||
convert-trade-data Convert trade data from one format to another.
|
convert-trade-data Convert trade data from one format to another.
|
||||||
|
list-data List downloaded data.
|
||||||
backtesting Backtesting module.
|
backtesting Backtesting module.
|
||||||
edge Edge module.
|
edge Edge module.
|
||||||
hyperopt Hyperopt module.
|
hyperopt Hyperopt module.
|
||||||
@ -41,8 +41,10 @@ positional arguments:
|
|||||||
list-timeframes Print available timeframes for the exchange.
|
list-timeframes Print available timeframes for the exchange.
|
||||||
show-trades Show trades.
|
show-trades Show trades.
|
||||||
test-pairlist Test your pairlist configuration.
|
test-pairlist Test your pairlist configuration.
|
||||||
|
install-ui Install FreqUI
|
||||||
plot-dataframe Plot candles with indicators.
|
plot-dataframe Plot candles with indicators.
|
||||||
plot-profit Generate plot showing profits.
|
plot-profit Generate plot showing profits.
|
||||||
|
webserver Webserver module.
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
|
@ -5,11 +5,42 @@ By default, these settings are configured via the configuration file (see below)
|
|||||||
|
|
||||||
## The Freqtrade configuration file
|
## The Freqtrade configuration file
|
||||||
|
|
||||||
The bot uses a set of configuration parameters during its operation that all together conform the bot configuration. It normally reads its configuration from a file (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 `config.json` file, located in the current working directory.
|
Per default, the bot loads the configuration from the `config.json` 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.
|
You can specify a different configuration file used by the bot with the `-c/--config` command-line option.
|
||||||
|
|
||||||
|
If you used the [Quick start](installation.md/#quick-start) method for installing
|
||||||
|
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
||||||
|
|
||||||
|
If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` 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 `exchange.key` 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.
|
||||||
|
|
||||||
|
!!! 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.
|
||||||
|
|
||||||
|
### 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.
|
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.
|
||||||
|
|
||||||
@ -22,36 +53,27 @@ Multiple configuration files can be specified and used by the bot or the bot can
|
|||||||
The 2nd file should only specify what you intend to override.
|
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, `config-private.json`).
|
If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`).
|
||||||
|
|
||||||
If you used the [Quick start](installation.md/#quick-start) method for installing
|
|
||||||
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
|
||||||
|
|
||||||
If default configuration file is not created we recommend you to use `freqtrade new-config --config config.json` to generate a basic configuration file.
|
|
||||||
|
|
||||||
The Freqtrade configuration file is to be written in the 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 syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines.
|
|
||||||
|
|
||||||
## Configuration parameters
|
## Configuration parameters
|
||||||
|
|
||||||
The table below will list all configuration parameters available.
|
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).
|
Freqtrade can also load many options via command line (CLI) arguments (check out the commands `--help` output for details).
|
||||||
The prevelance for all Options is as follows:
|
The prevalence for all Options is as follows:
|
||||||
|
|
||||||
- CLI arguments override any other option
|
- CLI arguments override any other option
|
||||||
- Configuration files are used in sequence (last file wins), and override Strategy configurations.
|
- [Environment Variables](#environment-variables)
|
||||||
- Strategy configurations are only used if they are not set via configuration or via command line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
|
- 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](#parameters-in-the-strategy) in the below table.
|
||||||
|
|
||||||
Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways.
|
Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways.
|
||||||
|
|
||||||
| Parameter | Description |
|
| 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 which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades, limited by the pairlist). [More information below](#configuring-amount-per-trade).<br> **Datatype:** Positive integer or -1.
|
| `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](#configuring-amount-per-trade).<br> **Datatype:** Positive integer or -1.
|
||||||
| `stake_currency` | **Required.** Crypto-currency used for trading. <br> **Datatype:** String
|
| `stake_currency` | **Required.** Crypto-currency used for trading. <br> **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](#configuring-amount-per-trade). <br> **Datatype:** Positive float or `"unlimited"`.
|
| `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](#configuring-amount-per-trade). <br> **Datatype:** Positive float or `"unlimited"`.
|
||||||
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **Datatype:** Positive float between `0.1` and `1.0`.
|
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **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](#configuring-amount-per-trade). <br> **Datatype:** Positive float.
|
||||||
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **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](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
|
| `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](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **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. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive 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. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
|
||||||
@ -74,21 +96,21 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
|
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
|
||||||
| `bid_strategy.ask_last_balance` | **Required.** Interpolate the bidding price. More information [below](#buy-price-without-orderbook-enabled).
|
| `bid_strategy.ask_last_balance` | **Required.** Interpolate the bidding price. More information [below](#buy-price-without-orderbook-enabled).
|
||||||
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
||||||
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `bid_strategy. 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](#check-depth-of-market) <br> *Defaults to `0`.* <br> **Datatype:** Float (as ratio)
|
| `bid_strategy. 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](#check-depth-of-market) <br> *Defaults to `0`.* <br> **Datatype:** Float (as ratio)
|
||||||
| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).<br> *Defaults to `ask`.* <br> **Datatype:** String (either `ask` or `bid`).
|
| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).<br> *Defaults to `ask`.* <br> **Datatype:** String (either `ask` or `bid`).
|
||||||
| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled).
|
| `ask_strategy.bid_last_balance` | Interpolate the selling price. More information [below](#sell-price-without-orderbook-enabled).
|
||||||
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `ask_strategy.order_book_top` | Bot will use the top N rate in Order Book "price_side" to sell. I.e. a value of 2 will allow the bot to pick the 2nd ask rate in [Order Book Asks](#sell-price-with-orderbook-enabled)<br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
| `use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
| `sell_profit_only` | Wait until the bot reaches `sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `sell_profit_offset` | Sell-signal is only active above this value. Only active in combination with `sell_profit_only=True`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
||||||
| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
|
| `ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
| `ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer
|
||||||
| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer
|
|
||||||
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
|
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
|
||||||
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||||
|
| `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price. <br>*Defaults to `0.02` 2%).*<br> **Datatype:** Positive float
|
||||||
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
||||||
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean
|
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean
|
||||||
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
@ -102,10 +124,11 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
|
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
|
||||||
| `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`<br> **Datatype:** Boolean
|
| `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`<br> **Datatype:** Boolean
|
||||||
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean
|
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean
|
||||||
|
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`<br> **Datatype:** Boolean
|
||||||
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
|
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
|
||||||
| `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. <br>*Defaults to `true`.* <br> **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. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
|
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
|
||||||
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** List of Dicts
|
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts
|
||||||
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
|
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
|
||||||
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
@ -140,7 +163,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
|
|
||||||
### Parameters in the strategy
|
### Parameters in the strategy
|
||||||
|
|
||||||
The following parameters can be set in either configuration file or 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.
|
Values set in the configuration file always overwrite values set in the strategy.
|
||||||
|
|
||||||
* `minimal_roi`
|
* `minimal_roi`
|
||||||
@ -156,52 +179,67 @@ Values set in the configuration file always overwrite values set in the strategy
|
|||||||
* `order_time_in_force`
|
* `order_time_in_force`
|
||||||
* `unfilledtimeout`
|
* `unfilledtimeout`
|
||||||
* `disable_dataframe_checks`
|
* `disable_dataframe_checks`
|
||||||
* `protections`
|
* `use_sell_signal`
|
||||||
* `use_sell_signal` (ask_strategy)
|
* `sell_profit_only`
|
||||||
* `sell_profit_only` (ask_strategy)
|
* `sell_profit_offset`
|
||||||
* `sell_profit_offset` (ask_strategy)
|
* `ignore_roi_if_buy_signal`
|
||||||
* `ignore_roi_if_buy_signal` (ask_strategy)
|
* `ignore_buying_expired_candle_after`
|
||||||
* `ignore_buying_expired_candle_after` (ask_strategy)
|
|
||||||
|
|
||||||
### Configuring amount per trade
|
### 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](#available-balance) as explained below.
|
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](#tradable-balance) as explained below.
|
||||||
|
|
||||||
#### Minimum trade stake
|
#### Minimum trade stake
|
||||||
|
|
||||||
The minimum stake amount will depend by exchange and pair, and is usually listed in the exchange support pages.
|
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.4$.
|
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 therefore `20 * 0.6 ~= 12`.
|
The minimum stake amount to buy this pair is, therefore, `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.
|
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%).
|
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 in account a stoploss of 10% on top of that - we'd end up with a value of ~14$ (`12.6 / (1 - 0.1)`).
|
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.
|
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
|
!!! 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.
|
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.
|
||||||
|
|
||||||
#### Available balance
|
#### Tradable balance
|
||||||
|
|
||||||
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
|
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#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.
|
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.
|
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 available balance. The rest of the wallet is untouched by the trades.
|
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](#assign-available-capital) instead.
|
||||||
|
|
||||||
!!! Warning
|
!!! 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).
|
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 10.000 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.
|
||||||
|
|
||||||
|
!!! Warning "Incompatible with `tradable_balance_ratio`"
|
||||||
|
Setting this option will replace any configuration of `tradable_balance_ratio`.
|
||||||
|
|
||||||
#### Amend last stake amount
|
#### Amend last stake amount
|
||||||
|
|
||||||
Assuming we have the tradable balance of 1000 USDT, `stake_amount=400`, and `max_open_trades=3`.
|
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.
|
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 in order to fill the last trade slot.
|
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:
|
In the example above this would mean:
|
||||||
|
|
||||||
@ -229,7 +267,7 @@ For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a conf
|
|||||||
|
|
||||||
#### Dynamic stake amount
|
#### 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 amount of allowed trades (`max_open_trades`).
|
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.
|
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.
|
||||||
|
|
||||||
@ -247,18 +285,18 @@ To allow the bot to trade all the available `stake_currency` in your account (mi
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Tip "Compounding profits"
|
!!! Tip "Compounding profits"
|
||||||
This configuration will allow increasing / decreasing stakes depending on the performance of the bot (lower stake if bot is loosing, higher stakes if the bot has a winning record, since higher balances are available), and will result in profit compounding.
|
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.
|
||||||
|
|
||||||
!!! Note "When using Dry-Run Mode"
|
!!! Note "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 over time.
|
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.
|
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.
|
||||||
|
|
||||||
--8<-- "includes/pricing.md"
|
--8<-- "includes/pricing.md"
|
||||||
|
|
||||||
### Understand minimal_roi
|
### Understand minimal_roi
|
||||||
|
|
||||||
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
|
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 ratio.
|
in minutes and the value is the minimum ROI as a ratio.
|
||||||
See the example below:
|
See the example below:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@ -273,7 +311,7 @@ See the example below:
|
|||||||
Most of the strategy files already include the optimal `minimal_roi` value.
|
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
|
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.
|
`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.
|
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.
|
||||||
|
|
||||||
!!! Note "Special case to forcesell after a specific time"
|
!!! Note "Special case to forcesell after a specific time"
|
||||||
A special case presents using `"<N>": -1` as ROI. This forces the bot to sell a trade after N Minutes, no matter if it's positive or negative, so represents a time-limited force-sell.
|
A special case presents using `"<N>": -1` as ROI. This forces the bot to sell a trade after N Minutes, no matter if it's positive or negative, so represents a time-limited force-sell.
|
||||||
@ -292,16 +330,16 @@ See [the telegram documentation](telegram-usage.md) for details on usage.
|
|||||||
|
|
||||||
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.
|
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 `ask_strategy.ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired.
|
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:
|
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:
|
||||||
|
|
||||||
``` json
|
``` json
|
||||||
"ask_strategy":{
|
{
|
||||||
|
//...
|
||||||
"ignore_buying_expired_candle_after": 300,
|
"ignore_buying_expired_candle_after": 300,
|
||||||
"price_side": "bid",
|
|
||||||
// ...
|
// ...
|
||||||
},
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
@ -319,7 +357,7 @@ 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.
|
`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 (`buy`, `sell`, `stoploss` and
|
If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and
|
||||||
`stoploss_on_exchange`) need to be present, otherwise the bot will fail to start.
|
`stoploss_on_exchange`) need to be present, otherwise, the bot will fail to start.
|
||||||
|
|
||||||
For information on (`emergencysell`,`forcesell`, `forcebuy`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
|
For information on (`emergencysell`,`forcesell`, `forcebuy`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
|
||||||
|
|
||||||
@ -370,7 +408,7 @@ Configuration:
|
|||||||
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
|
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
|
||||||
|
|
||||||
!!! Warning "Warning: stoploss_on_exchange failures"
|
!!! Warning "Warning: stoploss_on_exchange failures"
|
||||||
If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however this is not advised.
|
If stoploss on exchange creation fails for some reason, then an "emergency sell" is initiated. By default, this will sell the asset using a market order. The order-type for the emergency-sell can be changed by setting the `emergencysell` value in the `order_types` dictionary - however, this is not advised.
|
||||||
|
|
||||||
### Understand order_time_in_force
|
### Understand order_time_in_force
|
||||||
|
|
||||||
@ -380,12 +418,12 @@ is executed on the exchange. Three commonly used time in force are:
|
|||||||
**GTC (Good Till Canceled):**
|
**GTC (Good Till Canceled):**
|
||||||
|
|
||||||
This is most of the time the default time in force. It means the order will remain
|
This is most of the time the default time in force. It means the order will remain
|
||||||
on exchange till it is canceled by user. It can be fully or partially fulfilled.
|
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.
|
If partially fulfilled, the remaining will stay on the exchange till cancelled.
|
||||||
|
|
||||||
**FOK (Fill Or Kill):**
|
**FOK (Fill Or Kill):**
|
||||||
|
|
||||||
It means if the order is not executed immediately AND fully then it is canceled by the exchange.
|
It means if the order is not executed immediately AND fully then it is cancelled by the exchange.
|
||||||
|
|
||||||
**IOC (Immediate Or Canceled):**
|
**IOC (Immediate Or Canceled):**
|
||||||
|
|
||||||
@ -406,47 +444,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
This is an ongoing work. For now it is supported only for binance.
|
This is ongoing work. For now, it is supported only for binance 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.
|
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.
|
||||||
|
|
||||||
### Exchange configuration
|
|
||||||
|
|
||||||
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
|
|
||||||
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
|
||||||
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python).
|
|
||||||
However, the bot was tested by the development team with only Bittrex, Binance and Kraken,
|
|
||||||
so the these are the only officially supported exchanges:
|
|
||||||
|
|
||||||
- [Bittrex](https://bittrex.com/): "bittrex"
|
|
||||||
- [Binance](https://www.binance.com/): "binance"
|
|
||||||
- [Kraken](https://kraken.com/): "kraken"
|
|
||||||
|
|
||||||
Feel free to test other exchanges and submit your PR to improve the bot.
|
|
||||||
|
|
||||||
Some exchanges require special configuration, which can be found on the [Exchange-specific Notes](exchanges.md) documentation page.
|
|
||||||
|
|
||||||
#### Sample exchange configuration
|
|
||||||
|
|
||||||
A exchange configuration for "binance" would look as follows:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"exchange": {
|
|
||||||
"name": "binance",
|
|
||||||
"key": "your_exchange_key",
|
|
||||||
"secret": "your_exchange_secret",
|
|
||||||
"ccxt_config": {"enableRateLimit": true},
|
|
||||||
"ccxt_async_config": {
|
|
||||||
"enableRateLimit": true,
|
|
||||||
"rateLimit": 200
|
|
||||||
},
|
|
||||||
```
|
|
||||||
|
|
||||||
This configuration enables binance, as well as rate limiting to avoid bans from the exchange.
|
|
||||||
`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Optimal settings for rate limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
|
|
||||||
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
|
|
||||||
|
|
||||||
### What values can be used for fiat_display_currency?
|
### What values can be used for fiat_display_currency?
|
||||||
|
|
||||||
@ -459,7 +458,7 @@ 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"
|
"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 cryto currencies are supported.
|
In addition to fiat currencies, a range of crypto currencies is supported.
|
||||||
|
|
||||||
The valid values are:
|
The valid values are:
|
||||||
|
|
||||||
@ -470,7 +469,7 @@ The valid values are:
|
|||||||
## Using Dry-run mode
|
## Using Dry-run mode
|
||||||
|
|
||||||
We recommend starting the bot in the Dry-run mode to see how your bot will
|
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
|
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
|
bot does not engage your money. It only runs a live simulation without
|
||||||
creating trades on the exchange.
|
creating trades on the exchange.
|
||||||
|
|
||||||
@ -496,27 +495,29 @@ creating trades on the exchange.
|
|||||||
Once you will be happy with your bot performance running in the Dry-run mode, you can switch it to production mode.
|
Once you will be happy with your bot performance running in the Dry-run mode, you can switch it to production mode.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
A simulated wallet is available during dry-run mode, and will assume a starting capital of `dry_run_wallet` (defaults to 1000).
|
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
|
### 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.
|
* 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`.
|
* Wallets (`/balance`) are simulated based on `dry_run_wallet`.
|
||||||
* Orders are simulated, and will not be posted to the exchange.
|
* Orders are simulated, and will not be posted to the exchange.
|
||||||
* Orders are assumed to fill immediately, and will never time out.
|
* Market orders fill based on orderbook volume the moment the order is placed.
|
||||||
|
* Limit orders fill once the price reaches the defined level - or time out based on `unfilledtimeout` settings.
|
||||||
* In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled.
|
* 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 reset on bot restart.
|
* Open orders (not trades, which are stored in the database) are reset on bot restart.
|
||||||
|
|
||||||
## Switch to production mode
|
## Switch to production mode
|
||||||
|
|
||||||
In production mode, the bot will engage your money. Be careful, since a wrong
|
In production mode, the bot will engage your money. Be careful, since a wrong strategy can lose all your money.
|
||||||
strategy can lose all your money. Be aware of what you are doing when
|
Be aware of what you are doing when you run it in production mode.
|
||||||
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
|
### 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.
|
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 setup the bot in dry-run mode, you may fill these fields with empty values.
|
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
|
### To switch your bot in production mode
|
||||||
|
|
||||||
@ -528,7 +529,7 @@ API Keys are usually only required for live trading (trading for real money, bot
|
|||||||
"dry_run": false,
|
"dry_run": false,
|
||||||
```
|
```
|
||||||
|
|
||||||
**Insert your Exchange API key (change them by fake api keys):**
|
**Insert your Exchange API key (change them by fake API keys):**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -546,7 +547,7 @@ API Keys are usually only required for live trading (trading for real money, bot
|
|||||||
You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange.
|
You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange.
|
||||||
|
|
||||||
!!! Hint "Keep your secrets secret"
|
!!! Hint "Keep your secrets secret"
|
||||||
To keep your secrets secret, we recommend to use a 2nd configuration for your API keys.
|
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-private.json`) and keep your settings in this file.
|
Simply use the above snippet in a new configuration file (e.g. `config-private.json`) and keep your settings in this file.
|
||||||
You can then start the bot with `freqtrade trade --config user_data/config.json --config user_data/config-private.json <...>` to have your keys loaded.
|
You can then start the bot with `freqtrade trade --config user_data/config.json --config user_data/config-private.json <...>` to have your keys loaded.
|
||||||
|
|
||||||
@ -556,7 +557,7 @@ You should also make sure to read the [Exchanges](exchanges.md) section of the d
|
|||||||
|
|
||||||
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration.
|
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration.
|
||||||
|
|
||||||
An example for this can be found in `config_full.json.example`
|
An example for this can be found in `config_examples/config_full.example.json`
|
||||||
|
|
||||||
``` json
|
``` json
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
|
@ -11,7 +11,7 @@ Otherwise `--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.
|
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 "Tip: Updating existing data"
|
!!! Tip "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, do not use `--days` or `--timerange` parameters. Freqtrade will keep the available data and only download the missing 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 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.
|
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.
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[-d PATH] [--userdir PATH]
|
[-d PATH] [--userdir PATH]
|
||||||
[-p PAIRS [PAIRS ...]] [--pairs-file FILE]
|
[-p PAIRS [PAIRS ...]] [--pairs-file FILE]
|
||||||
[--days INT] [--new-pairs-days INT]
|
[--days INT] [--new-pairs-days INT]
|
||||||
|
[--include-inactive-pairs]
|
||||||
[--timerange TIMERANGE] [--dl-trades]
|
[--timerange TIMERANGE] [--dl-trades]
|
||||||
[--exchange EXCHANGE]
|
[--exchange EXCHANGE]
|
||||||
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]]
|
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]]
|
||||||
@ -38,6 +39,8 @@ optional arguments:
|
|||||||
--days INT Download data for given number of days.
|
--days INT Download data for given number of days.
|
||||||
--new-pairs-days INT Download data of new pairs for given number of days.
|
--new-pairs-days INT Download data of new pairs for given number of days.
|
||||||
Default: `None`.
|
Default: `None`.
|
||||||
|
--include-inactive-pairs
|
||||||
|
Also download data from inactive pairs.
|
||||||
--timerange TIMERANGE
|
--timerange TIMERANGE
|
||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
--dl-trades Download trades instead of OHLCV data. The bot will
|
--dl-trades Download trades instead of OHLCV data. The bot will
|
||||||
@ -52,10 +55,10 @@ optional arguments:
|
|||||||
exchange/pairs/timeframes.
|
exchange/pairs/timeframes.
|
||||||
--data-format-ohlcv {json,jsongz,hdf5}
|
--data-format-ohlcv {json,jsongz,hdf5}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `None`).
|
(default: `json`).
|
||||||
--data-format-trades {json,jsongz,hdf5}
|
--data-format-trades {json,jsongz,hdf5}
|
||||||
Storage format for downloaded trades data. (default:
|
Storage format for downloaded trades data. (default:
|
||||||
`None`).
|
`jsongz`).
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
@ -80,6 +83,82 @@ Common arguments:
|
|||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
|
### Pairs file
|
||||||
|
|
||||||
|
In alternative to the whitelist from `config.json`, a `pairs.json` file can be used.
|
||||||
|
If you are using Binance for example:
|
||||||
|
|
||||||
|
- create a directory `user_data/data/binance` and copy or create the `pairs.json` file in that directory.
|
||||||
|
- update the `pairs.json` file to contain the currency pairs you are interested in.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p user_data/data/binance
|
||||||
|
touch user_data/data/binance/pairs.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The format of the `pairs.json` file is a simple json list.
|
||||||
|
Mixing different stake-currencies is allowed for this file, since it's only used for downloading.
|
||||||
|
|
||||||
|
``` json
|
||||||
|
[
|
||||||
|
"ETH/BTC",
|
||||||
|
"ETH/USDT",
|
||||||
|
"BTC/USDT",
|
||||||
|
"XRP/ETH"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Tip "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.
|
||||||
|
|
||||||
|
??? Note "Permission denied errors"
|
||||||
|
If your configuration directory `user_data` was made by docker, you may get the following error:
|
||||||
|
|
||||||
|
```
|
||||||
|
cp: cannot create regular file 'user_data/data/binance/pairs.json': Permission denied
|
||||||
|
```
|
||||||
|
|
||||||
|
You can fix the permissions of your user-data directory as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo chown -R $UID:$GID user_data
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start download
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade download-data --exchange binance
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`.
|
||||||
|
|
||||||
|
Alternatively, specify the pairs directly
|
||||||
|
|
||||||
|
```bash
|
||||||
|
freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT
|
||||||
|
```
|
||||||
|
|
||||||
|
or as regex (to download all active USDT pairs)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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 `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
|
||||||
|
- 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. Eventually set end dates are ignored.
|
||||||
|
- 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 pairs.json file. You can combine `-c/--config` with most other options.
|
||||||
|
|
||||||
|
|
||||||
### Data format
|
### Data format
|
||||||
|
|
||||||
Freqtrade currently supports 3 data-formats for both OHLCV and trades data:
|
Freqtrade currently supports 3 data-formats for both OHLCV and trades data:
|
||||||
@ -204,6 +283,61 @@ 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
|
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 {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]]
|
||||||
|
[--exchange EXCHANGE]
|
||||||
|
[--data-format-ohlcv {json,jsongz,hdf5}]
|
||||||
|
[--data-format-trades {json,jsongz,hdf5}]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
|
||||||
|
Limit command to these pairs. Pairs are space-
|
||||||
|
separated.
|
||||||
|
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]
|
||||||
|
Specify which tickers to download. Space-separated
|
||||||
|
list. Default: `1m 5m`.
|
||||||
|
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
|
||||||
|
config is provided.
|
||||||
|
--data-format-ohlcv {json,jsongz,hdf5}
|
||||||
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
|
(default: `json`).
|
||||||
|
--data-format-trades {json,jsongz,hdf5}
|
||||||
|
Storage format for downloaded trades data. (default:
|
||||||
|
`jsongz`).
|
||||||
|
|
||||||
|
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/config.json` or `config.json` 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.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example trade-to-ohlcv conversion
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
freqtrade trades-to-ohlcv --exchange kraken -t 5m 1h 1d --pairs BTC/EUR ETH/EUR
|
||||||
|
```
|
||||||
|
|
||||||
### Sub-command list-data
|
### Sub-command list-data
|
||||||
|
|
||||||
You can get a list of downloaded data using the `list-data` sub-command.
|
You can get a list of downloaded data using the `list-data` sub-command.
|
||||||
@ -257,64 +391,6 @@ ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
|
|||||||
ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h
|
ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pairs file
|
|
||||||
|
|
||||||
In alternative to the whitelist from `config.json`, a `pairs.json` file can be used.
|
|
||||||
|
|
||||||
If you are using Binance for example:
|
|
||||||
|
|
||||||
- create a directory `user_data/data/binance` and copy or create the `pairs.json` file in that directory.
|
|
||||||
- update the `pairs.json` file to contain the currency pairs you are interested in.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p user_data/data/binance
|
|
||||||
cp tests/testdata/pairs.json user_data/data/binance
|
|
||||||
```
|
|
||||||
|
|
||||||
If you your configuration directory `user_data` was made by docker, you may get the following error:
|
|
||||||
|
|
||||||
```
|
|
||||||
cp: cannot create regular file 'user_data/data/binance/pairs.json': Permission denied
|
|
||||||
```
|
|
||||||
|
|
||||||
You can fix the permissions of your user-data directory as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo chown -R $UID:$GID user_data
|
|
||||||
```
|
|
||||||
|
|
||||||
The format of the `pairs.json` file is a simple json list.
|
|
||||||
Mixing different stake-currencies is allowed for this file, since it's only used for downloading.
|
|
||||||
|
|
||||||
``` json
|
|
||||||
[
|
|
||||||
"ETH/BTC",
|
|
||||||
"ETH/USDT",
|
|
||||||
"BTC/USDT",
|
|
||||||
"XRP/ETH"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Start download
|
|
||||||
|
|
||||||
Then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
freqtrade download-data --exchange binance
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`.
|
|
||||||
|
|
||||||
### 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 `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
|
|
||||||
- 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. Eventually set end dates are ignored.
|
|
||||||
- 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 pairs.json file. You can combine `-c/--config` with most other options.
|
|
||||||
|
|
||||||
### Trades (tick) data
|
### Trades (tick) data
|
||||||
|
|
||||||
By default, `download-data` sub-command downloads Candles (OHLCV) data. Some exchanges also provide historic trade-data via their API.
|
By default, `download-data` sub-command downloads Candles (OHLCV) data. Some exchanges also provide historic trade-data via their API.
|
||||||
|
@ -33,3 +33,13 @@ The old section of configuration parameters (`"pairlist"`) has been deprecated i
|
|||||||
### deprecation of bidVolume and askVolume from volume-pairlist
|
### deprecation of bidVolume and askVolume from volume-pairlist
|
||||||
|
|
||||||
Since only quoteVolume can be compared between assets, the other options (bidVolume, askVolume) have been deprecated in 2020.4, and have been removed in 2020.9.
|
Since only quoteVolume can be compared between assets, the other options (bidVolume, askVolume) have been deprecated in 2020.4, and have been removed in 2020.9.
|
||||||
|
|
||||||
|
### Using order book steps for sell price
|
||||||
|
|
||||||
|
Using `order_book_min` and `order_book_max` used to allow stepping the orderbook and trying to find the next ROI slot - trying to place sell-orders early.
|
||||||
|
As this does however increase risk and provides no benefit, it's been removed for maintainability purposes in 2021.7.
|
||||||
|
|
||||||
|
### Legacy Hyperopt mode
|
||||||
|
|
||||||
|
Using separate hyperopt files was deprecated in 2021.4 and was removed in 2021.9.
|
||||||
|
Please switch to the new [Parametrized Strategies](hyperopt.md) to benefit from the new hyperopt interface.
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running.
|
This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running.
|
||||||
|
|
||||||
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/p7nuUNVfP7) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) where you can ask questions.
|
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/p7nuUNVfP7) where you can ask questions.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR.
|
Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR.
|
||||||
|
|
||||||
Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/extensions/admonition/).
|
Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/reference/admonitions/).
|
||||||
|
|
||||||
To test the documentation locally use the following commands.
|
To test the documentation locally use the following commands.
|
||||||
|
|
||||||
@ -240,11 +240,18 @@ The `IProtection` parent class provides a helper method for this in `calculate_l
|
|||||||
!!! Note
|
!!! Note
|
||||||
This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade.
|
This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Make sure to use an up-to-date version of CCXT before running any of the below tests.
|
||||||
|
You can get the latest version of ccxt by running `pip install -U ccxt` with activated virtual environment.
|
||||||
|
Native docker is not supported for these tests, however the available dev-container will support all required actions and eventually necessary changes.
|
||||||
|
|
||||||
Most exchanges supported by CCXT should work out of the box.
|
Most exchanges supported by CCXT should work out of the box.
|
||||||
|
|
||||||
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`.
|
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`.
|
||||||
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
|
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
|
||||||
|
|
||||||
|
Also try to use `freqtrade download-data` for an extended timerange and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded).
|
||||||
|
|
||||||
### Stoploss On Exchange
|
### Stoploss On Exchange
|
||||||
|
|
||||||
Check if the new exchange supports Stoploss on Exchange orders through their API.
|
Check if the new exchange supports Stoploss on Exchange orders through their API.
|
||||||
|
@ -24,82 +24,21 @@ Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.co
|
|||||||
|
|
||||||
Create a new directory and place the [docker-compose file](https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml) in this directory.
|
Create a new directory and place the [docker-compose file](https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml) in this directory.
|
||||||
|
|
||||||
=== "PC/MAC/Linux"
|
``` bash
|
||||||
``` bash
|
mkdir ft_userdata
|
||||||
mkdir ft_userdata
|
cd ft_userdata/
|
||||||
cd ft_userdata/
|
# Download the docker-compose file from the repository
|
||||||
# Download the docker-compose file from the repository
|
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
||||||
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
|
||||||
|
|
||||||
# Pull the freqtrade image
|
# Pull the freqtrade image
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
|
|
||||||
# Create user directory structure
|
# Create user directory structure
|
||||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
||||||
|
|
||||||
# Create configuration - Requires answering interactive questions
|
# Create configuration - Requires answering interactive questions
|
||||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "RaspberryPi"
|
|
||||||
``` bash
|
|
||||||
mkdir ft_userdata
|
|
||||||
cd ft_userdata/
|
|
||||||
# Download the docker-compose file from the repository
|
|
||||||
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
|
||||||
|
|
||||||
# Edit the compose file to use an image named `*_pi` (stable_pi or develop_pi)
|
|
||||||
|
|
||||||
# Pull the freqtrade image
|
|
||||||
docker-compose pull
|
|
||||||
|
|
||||||
# Create user directory structure
|
|
||||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
|
||||||
|
|
||||||
# Create configuration - Requires answering interactive questions
|
|
||||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! Note "Change your docker Image"
|
|
||||||
You have to change the docker image in the docker-compose file for your Raspberry build to work properly.
|
|
||||||
``` yml
|
|
||||||
image: freqtradeorg/freqtrade:stable_pi
|
|
||||||
# image: freqtradeorg/freqtrade:develop_pi
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "ARM 64 Systenms (Mac M1, Raspberry Pi 4, Jetson Nano)"
|
|
||||||
In case of a Mac M1, make sure that your docker installation is running in native mode
|
|
||||||
Arm64 images are not yet provided via Docker Hub and need to be build locally first.
|
|
||||||
Depending on the device, this may take a few minutes (Apple M1) or multiple hours (Raspberry Pi)
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
# Clone Freqtrade repository
|
|
||||||
git clone https://github.com/freqtrade/freqtrade.git
|
|
||||||
cd freqtrade
|
|
||||||
# Optionally switch to the stable version
|
|
||||||
git checkout stable
|
|
||||||
|
|
||||||
# Modify your docker-compose file to enable building and change the image name
|
|
||||||
# (see the Note Box below for necessary changes)
|
|
||||||
|
|
||||||
# Build image
|
|
||||||
docker-compose build
|
|
||||||
|
|
||||||
# Create user directory structure
|
|
||||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
|
||||||
|
|
||||||
# Create configuration - Requires answering interactive questions
|
|
||||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! Note "Change your docker Image"
|
|
||||||
You have to change the docker image in the docker-compose file for your arm64 build to work properly.
|
|
||||||
``` yml
|
|
||||||
image: freqtradeorg/freqtrade:custom_arm64
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: "./docker/Dockerfile.aarch64"
|
|
||||||
```
|
|
||||||
|
|
||||||
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
||||||
The last 2 steps in the snippet create the directory with `user_data`, as well as (interactively) the default configuration based on your selections.
|
The last 2 steps in the snippet create the directory with `user_data`, as well as (interactively) the default configuration based on your selections.
|
||||||
@ -117,7 +56,7 @@ The last 2 steps in the snippet create the directory with `user_data`, as well a
|
|||||||
|
|
||||||
The `SampleStrategy` is run by default.
|
The `SampleStrategy` is run by default.
|
||||||
|
|
||||||
!!! Warning "`SampleStrategy` is just a demo!"
|
!!! Danger "`SampleStrategy` is just a demo!"
|
||||||
The `SampleStrategy` is there for your reference and give you ideas for your own strategy.
|
The `SampleStrategy` is there for your reference and give you ideas for your own strategy.
|
||||||
Please always backtest your strategy and use dry-run for some time before risking real money!
|
Please always backtest your strategy and use dry-run for some time before risking real money!
|
||||||
You will find more information about Strategy development in the [Strategy documentation](strategy-customization.md).
|
You will find more information about Strategy development in the [Strategy documentation](strategy-customization.md).
|
||||||
@ -131,6 +70,18 @@ docker-compose up -d
|
|||||||
!!! Warning "Default configuration"
|
!!! Warning "Default configuration"
|
||||||
While the configuration generated will be mostly functional, you will still need to verify that all options correspond to what you want (like Pricing, pairlist, ...) before starting the bot.
|
While the configuration generated will be mostly functional, you will still need to verify that all options correspond to what you want (like Pricing, pairlist, ...) before starting the bot.
|
||||||
|
|
||||||
|
#### Accessing the UI
|
||||||
|
|
||||||
|
If you've selected to enable FreqUI in the `new-config` step, you will have freqUI available at port `localhost:8080`.
|
||||||
|
|
||||||
|
You can now access the UI by typing localhost:8080 in your browser.
|
||||||
|
|
||||||
|
??? Note "UI Access on a remote servers"
|
||||||
|
If you're running on a VPS, you should consider using either a ssh tunnel, or setup a VPN (openVPN, wireguard) to connect to your bot.
|
||||||
|
This will ensure that freqUI is not directly exposed to the internet, which is not recommended for security reasons (freqUI does not support https out of the box).
|
||||||
|
Setup of these tools is not part of this tutorial, however many good tutorials can be found on the internet.
|
||||||
|
Please also read the [API configuration with docker](rest-api.md#configuration-with-docker) section to learn more about this configuration.
|
||||||
|
|
||||||
#### Monitoring the bot
|
#### Monitoring the bot
|
||||||
|
|
||||||
You can check for running instances with `docker-compose ps`.
|
You can check for running instances with `docker-compose ps`.
|
||||||
@ -167,6 +118,11 @@ Advanced users may edit the docker-compose file further to include all possible
|
|||||||
|
|
||||||
All freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
|
All freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
|
||||||
|
|
||||||
|
!!! Warning "`docker-compose` for trade commands"
|
||||||
|
Trade commands (`freqtrade trade <...>`) should not be ran via `docker-compose run` - but should use `docker-compose up -d` instead.
|
||||||
|
This makes sure that the container is properly started (including port forwardings) and will make sure that the container will restart after a system reboot.
|
||||||
|
If you intend to use freqUI, please also ensure to adjust the [configuration accordingly](rest-api.md#configuration-with-docker), otherwise the UI will not be available.
|
||||||
|
|
||||||
!!! Note "`docker-compose run --rm`"
|
!!! Note "`docker-compose run --rm`"
|
||||||
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
||||||
|
|
||||||
@ -204,9 +160,9 @@ You'll then also need to modify the `docker-compose.yml` file and uncomment the
|
|||||||
dockerfile: "./Dockerfile.<yourextension>"
|
dockerfile: "./Dockerfile.<yourextension>"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then run `docker-compose build` to build the docker image, and run it using the commands described above.
|
You can then run `docker-compose build --pull` to build the docker image, and run it using the commands described above.
|
||||||
|
|
||||||
## Plotting with docker-compose
|
### Plotting with docker-compose
|
||||||
|
|
||||||
Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file.
|
Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file.
|
||||||
You can then use these commands as follows:
|
You can then use these commands as follows:
|
||||||
@ -217,7 +173,7 @@ docker-compose run --rm freqtrade plot-dataframe --strategy AwesomeStrategy -p B
|
|||||||
|
|
||||||
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
|
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
|
||||||
|
|
||||||
## Data analysis using docker compose
|
### Data analysis using docker compose
|
||||||
|
|
||||||
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
||||||
You can run this server using the following command:
|
You can run this server using the following command:
|
||||||
@ -234,3 +190,22 @@ Since part of this image is built on your machine, it is recommended to rebuild
|
|||||||
``` bash
|
``` bash
|
||||||
docker-compose -f docker/docker-compose-jupyter.yml build --no-cache
|
docker-compose -f docker/docker-compose-jupyter.yml build --no-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Docker on Windows
|
||||||
|
|
||||||
|
* Error: `"Timestamp for this request is outside of the recvWindow."`
|
||||||
|
* The market api requests require a synchronized clock but the time in the docker container shifts a bit over time into the past.
|
||||||
|
To fix this issue temporarily you need to run `wsl --shutdown` and restart docker again (a popup on windows 10 will ask you to do so).
|
||||||
|
A permanent solution is either to host the docker container on a linux host or restart the wsl from time to time with the scheduler.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
taskkill /IM "Docker Desktop.exe" /F
|
||||||
|
wsl --shutdown
|
||||||
|
start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
Due to the above, we do not recommend the usage of docker on windows for production setups, but only for experimentation, datadownload and backtesting.
|
||||||
|
Best use a linux-VPS for running freqtrade reliably.
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
|
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
WHen using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
|
When using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
|
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
|
||||||
|
@ -2,8 +2,60 @@
|
|||||||
|
|
||||||
This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges.
|
This page combines common gotchas and informations which are exchange-specific and most likely don't apply to other exchanges.
|
||||||
|
|
||||||
|
## Exchange configuration
|
||||||
|
|
||||||
|
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
|
||||||
|
exchange markets and trading APIs. The complete up-to-date list can be found in the
|
||||||
|
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python).
|
||||||
|
However, the bot was tested by the development team with only a few exchanges.
|
||||||
|
A current list of these can be found in the "Home" section of this documentation.
|
||||||
|
|
||||||
|
Feel free to test other exchanges and submit your feedback or PR to improve the bot or confirm exchanges that work flawlessly..
|
||||||
|
|
||||||
|
Some exchanges require special configuration, which can be found below.
|
||||||
|
|
||||||
|
### Sample exchange configuration
|
||||||
|
|
||||||
|
A exchange configuration for "binance" would look as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"exchange": {
|
||||||
|
"name": "binance",
|
||||||
|
"key": "your_exchange_key",
|
||||||
|
"secret": "your_exchange_secret",
|
||||||
|
"ccxt_config": {},
|
||||||
|
"ccxt_async_config": {},
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting rate limits
|
||||||
|
|
||||||
|
Usually, rate limits set by CCXT are reliable and work well.
|
||||||
|
In case of problems related to rate-limits (usually DDOS Exceptions in your logs), it's easy to change rateLimit settings to other values.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"exchange": {
|
||||||
|
"name": "kraken",
|
||||||
|
"key": "your_exchange_key",
|
||||||
|
"secret": "your_exchange_secret",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 3100
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration enables kraken, as well as rate-limiting to avoid bans from the exchange.
|
||||||
|
`"rateLimit": 3100` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Optimal settings for rate-limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
|
||||||
|
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
|
||||||
|
|
||||||
## Binance
|
## Binance
|
||||||
|
|
||||||
|
Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "Stoploss on Exchange"
|
||||||
Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||||
|
|
||||||
@ -56,6 +108,12 @@ Bittrex does not support market orders. If you have a message at the bot startup
|
|||||||
Bittrex also does not support `VolumePairlist` due to limited / split API constellation at the moment.
|
Bittrex also does not support `VolumePairlist` due to limited / split API constellation at the moment.
|
||||||
Please use `StaticPairlist`. Other pairlists (other than `VolumePairlist`) should not be affected.
|
Please use `StaticPairlist`. Other pairlists (other than `VolumePairlist`) should not be affected.
|
||||||
|
|
||||||
|
### Volume pairlist
|
||||||
|
|
||||||
|
Bittrex does not support the direct usage of VolumePairList. This can however be worked around by using the advanced mode with `lookback_days: 1` (or more), which will emulate 24h volume.
|
||||||
|
|
||||||
|
Read more in the [pairlist documentation](plugins.md#volumepairlist-advanced-mode).
|
||||||
|
|
||||||
### Restricted markets
|
### Restricted markets
|
||||||
|
|
||||||
Bittrex split its exchange into US and International versions.
|
Bittrex split its exchange into US and International versions.
|
||||||
@ -77,8 +135,9 @@ You can get a list of restricted markets by using the following snippet:
|
|||||||
``` python
|
``` python
|
||||||
import ccxt
|
import ccxt
|
||||||
ct = ccxt.bittrex()
|
ct = ccxt.bittrex()
|
||||||
_ = ct.load_markets()
|
lm = ct.load_markets()
|
||||||
res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']]
|
|
||||||
|
res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']]
|
||||||
print(res)
|
print(res)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -104,7 +163,7 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll
|
|||||||
|
|
||||||
## Kucoin
|
## Kucoin
|
||||||
|
|
||||||
Kucoin requries a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
Kucoin requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"exchange": {
|
"exchange": {
|
||||||
@ -112,8 +171,12 @@ Kucoin requries a passphrase for each api key, you will therefore need to add th
|
|||||||
"key": "your_exchange_key",
|
"key": "your_exchange_key",
|
||||||
"secret": "your_exchange_secret",
|
"secret": "your_exchange_secret",
|
||||||
"password": "your_exchange_api_key_password",
|
"password": "your_exchange_api_key_password",
|
||||||
|
// ...
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||||
|
|
||||||
### Kucoin Blacklists
|
### Kucoin Blacklists
|
||||||
|
|
||||||
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues.
|
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues.
|
||||||
@ -157,6 +220,8 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
|
|||||||
"order_time_in_force": ["gtc", "fok"],
|
"order_time_in_force": ["gtc", "fok"],
|
||||||
"ohlcv_candle_limit": 200
|
"ohlcv_candle_limit": 200
|
||||||
}
|
}
|
||||||
|
//...
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
|
47
docs/faq.md
47
docs/faq.md
@ -54,9 +54,26 @@ you can't say much from few trades.
|
|||||||
|
|
||||||
Yes. You can edit your config and use the `/reload_config` command to reload the configuration. The bot will stop, reload the configuration and strategy and will restart with the new configuration and strategy.
|
Yes. You can edit your config and use the `/reload_config` command to reload the configuration. The bot will stop, reload the configuration and strategy and will restart with the new configuration and strategy.
|
||||||
|
|
||||||
### I want to improve the bot with a new strategy
|
### Why does my bot not sell everything it bought?
|
||||||
|
|
||||||
That's great. We have a nice backtesting and hyperoptimization setup. See the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
|
This is called "coin dust" and can happen on all exchanges.
|
||||||
|
It happens because many exchanges subtract fees from the "receiving currency" - so you buy 100 COIN - but you only get 99.9 COIN.
|
||||||
|
As COIN is trading in full lot sizes (1COIN steps), you cannot sell 0.9 COIN (or 99.9 COIN) - but you need to round down to 99 COIN.
|
||||||
|
|
||||||
|
This is not a bot-problem, but will also happen while manual trading.
|
||||||
|
|
||||||
|
While freqtrade can handle this (it'll sell 99 COIN), fees are often below the minimum tradable lot-size (you can only trade full COIN, not 0.9 COIN).
|
||||||
|
Leaving the dust (0.9 COIN) on the exchange makes usually sense, as the next time freqtrade buys COIN, it'll eat into the remaining small balance, this time selling everything it bought, and therefore slowly declining the dust balance (although it most likely will never reach exactly 0).
|
||||||
|
|
||||||
|
Where possible (e.g. on binance), the use of the exchange's dedicated fee currency will fix this.
|
||||||
|
On binance, it's sufficient to have BNB in your account, and have "Pay fees in BNB" enabled in your profile. Your BNB balance will slowly decline (as it's used to pay fees) - but you'll no longer encounter dust (Freqtrade will include the fees in the profit calculations).
|
||||||
|
Other exchanges don't offer such possibilities, where it's simply something you'll have to accept or move to a different exchange.
|
||||||
|
|
||||||
|
### I want to use incomplete candles
|
||||||
|
|
||||||
|
Freqtrade will not provide incomplete candles to strategies. Using incomplete candles will lead to repainting and consequently to strategies with "ghost" buys, which are impossible to both backtest, and verify after they happened.
|
||||||
|
|
||||||
|
You can use "current" market data by using the [dataprovider](strategy-customization.md#orderbookpair-maximum)'s orderbook or ticker methods - which however cannot be used during backtesting.
|
||||||
|
|
||||||
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
|
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
|
||||||
|
|
||||||
@ -82,11 +99,11 @@ Currently known to happen for US Bittrex users.
|
|||||||
|
|
||||||
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
||||||
|
|
||||||
### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy
|
### I'm getting the "Exchange XXX does not support market orders." message and cannot run my strategy
|
||||||
|
|
||||||
As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex).
|
As the message says, your exchange does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex and Gate.io).
|
||||||
|
|
||||||
To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market":
|
To fix this, redefine order types in the strategy to use "limit" instead of "market":
|
||||||
|
|
||||||
```
|
```
|
||||||
order_types = {
|
order_types = {
|
||||||
@ -138,6 +155,22 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us
|
|||||||
|
|
||||||
## Hyperopt module
|
## Hyperopt module
|
||||||
|
|
||||||
|
### Why does freqtrade not have GPU support?
|
||||||
|
|
||||||
|
First of all, most indicator libraries don't have GPU support - as such, there would be little benefit for indicator calculations.
|
||||||
|
The GPU improvements would only apply to pandas-native calculations - or ones written by yourself.
|
||||||
|
|
||||||
|
For hyperopt, freqtrade is using scikit-optimize, which is built on top of scikit-learn.
|
||||||
|
Their statement about GPU support is [pretty clear](https://scikit-learn.org/stable/faq.html#will-you-add-gpu-support).
|
||||||
|
|
||||||
|
GPU's also are only good at crunching numbers (floating point operations).
|
||||||
|
For hyperopt, we need both number-crunching (find next parameters) and running python code (running backtesting).
|
||||||
|
As such, GPU's are not too well suited for most parts of hyperopt.
|
||||||
|
|
||||||
|
The benefit of using GPU would therefore be pretty slim - and will not justify the complexity introduced by trying to add GPU support.
|
||||||
|
|
||||||
|
There is however nothing preventing you from using GPU-enabled indicators within your strategy if you think you must have this - you will however probably be disappointed by the slim gain that will give you (compared to the complexity).
|
||||||
|
|
||||||
### How many epochs do I need to get a good Hyperopt result?
|
### How many epochs do I need to get a good Hyperopt result?
|
||||||
|
|
||||||
Per default Hyperopt called without the `-e`/`--epochs` command line option will only
|
Per default Hyperopt called without the `-e`/`--epochs` command line option will only
|
||||||
@ -151,12 +184,12 @@ Since hyperopt uses Bayesian search, running for too many epochs may not produce
|
|||||||
It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going.
|
It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade hyperopt --hyperopt SampleHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000
|
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000
|
||||||
```
|
```
|
||||||
|
|
||||||
### Why does it take a long time to run hyperopt?
|
### Why does it take a long time to run hyperopt?
|
||||||
|
|
||||||
* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) - or the Freqtrade [discord community](https://discord.gg/p7nuUNVfP7). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
|
* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [discord community](https://discord.gg/p7nuUNVfP7). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
|
||||||
|
|
||||||
* If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers:
|
* If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers:
|
||||||
|
|
||||||
|
177
docs/hyperopt.md
177
docs/hyperopt.md
@ -44,14 +44,14 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
|||||||
[--data-format-ohlcv {json,jsongz,hdf5}]
|
[--data-format-ohlcv {json,jsongz,hdf5}]
|
||||||
[--max-open-trades INT]
|
[--max-open-trades INT]
|
||||||
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[-p PAIRS [PAIRS ...]] [--hyperopt NAME]
|
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
|
||||||
[--hyperopt-path PATH] [--eps] [--dmmp]
|
[--eps] [--dmmp] [--enable-protections]
|
||||||
[--enable-protections]
|
|
||||||
[--dry-run-wallet DRY_RUN_WALLET] [-e INT]
|
[--dry-run-wallet DRY_RUN_WALLET] [-e INT]
|
||||||
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]]
|
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]]
|
||||||
[--print-all] [--no-color] [--print-json] [-j JOBS]
|
[--print-all] [--no-color] [--print-json] [-j JOBS]
|
||||||
[--random-state INT] [--min-trades INT]
|
[--random-state INT] [--min-trades INT]
|
||||||
[--hyperopt-loss NAME]
|
[--hyperopt-loss NAME] [--disable-param-export]
|
||||||
|
[--ignore-missing-spaces]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -61,7 +61,7 @@ optional arguments:
|
|||||||
Specify what timerange of data to use.
|
Specify what timerange of data to use.
|
||||||
--data-format-ohlcv {json,jsongz,hdf5}
|
--data-format-ohlcv {json,jsongz,hdf5}
|
||||||
Storage format for downloaded candle (OHLCV) data.
|
Storage format for downloaded candle (OHLCV) data.
|
||||||
(default: `None`).
|
(default: `json`).
|
||||||
--max-open-trades INT
|
--max-open-trades INT
|
||||||
Override the value of the `max_open_trades`
|
Override the value of the `max_open_trades`
|
||||||
configuration setting.
|
configuration setting.
|
||||||
@ -73,10 +73,8 @@ optional arguments:
|
|||||||
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
|
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
|
||||||
Limit command to these pairs. Pairs are space-
|
Limit command to these pairs. Pairs are space-
|
||||||
separated.
|
separated.
|
||||||
--hyperopt NAME Specify hyperopt class name which will be used by the
|
--hyperopt-path PATH Specify additional lookup path for Hyperopt Loss
|
||||||
bot.
|
functions.
|
||||||
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
|
||||||
Hyperopt Loss functions.
|
|
||||||
--eps, --enable-position-stacking
|
--eps, --enable-position-stacking
|
||||||
Allow buying the same pair multiple times (position
|
Allow buying the same pair multiple times (position
|
||||||
stacking).
|
stacking).
|
||||||
@ -92,7 +90,7 @@ optional arguments:
|
|||||||
Starting balance, used for backtesting / hyperopt and
|
Starting balance, used for backtesting / hyperopt and
|
||||||
dry-runs.
|
dry-runs.
|
||||||
-e INT, --epochs INT Specify number of epochs (default: 100).
|
-e INT, --epochs INT Specify number of epochs (default: 100).
|
||||||
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
|
--spaces {all,buy,sell,roi,stoploss,trailing,protection,default} [{all,buy,sell,roi,stoploss,trailing,protection,default} ...]
|
||||||
Specify which parameters to hyperopt. Space-separated
|
Specify which parameters to hyperopt. Space-separated
|
||||||
list.
|
list.
|
||||||
--print-all Print all results, not only the best ones.
|
--print-all Print all results, not only the best ones.
|
||||||
@ -117,7 +115,13 @@ optional arguments:
|
|||||||
Hyperopt-loss-functions are:
|
Hyperopt-loss-functions are:
|
||||||
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
|
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
|
||||||
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
|
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
|
||||||
SortinoHyperOptLoss, SortinoHyperOptLossDaily
|
SortinoHyperOptLoss, SortinoHyperOptLossDaily,
|
||||||
|
CalmarHyperOptLoss, MaxDrawDownHyperOptLoss
|
||||||
|
--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.
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
@ -251,7 +255,7 @@ We continue to define hyperoptable parameters:
|
|||||||
class MyAwesomeStrategy(IStrategy):
|
class MyAwesomeStrategy(IStrategy):
|
||||||
buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy")
|
buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy")
|
||||||
buy_rsi = IntParameter(20, 40, default=30, space="buy")
|
buy_rsi = IntParameter(20, 40, default=30, space="buy")
|
||||||
buy_adx_enabled = CategoricalParameter([True, False], default=True, space="buy")
|
buy_adx_enabled = BooleanParameter(default=True, space="buy")
|
||||||
buy_rsi_enabled = CategoricalParameter([True, False], default=False, 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")
|
buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"], default="bb_lower", space="buy")
|
||||||
```
|
```
|
||||||
@ -314,6 +318,7 @@ There are four parameter types each suited for different purposes.
|
|||||||
* `DecimalParameter` - defines a floating point parameter with a limited number of decimals (default 3). Should be preferred instead of `RealParameter` in most cases.
|
* `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.
|
* `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.
|
* `CategoricalParameter` - defines a parameter with a predetermined number of choices.
|
||||||
|
* `BooleanParameter` - Shorthand for `CategoricalParameter([True, False])` - great for "enable" parameters.
|
||||||
|
|
||||||
!!! Tip "Disabling parameter optimization"
|
!!! Tip "Disabling parameter optimization"
|
||||||
Each parameter takes two boolean parameters:
|
Each parameter takes two boolean parameters:
|
||||||
@ -324,7 +329,7 @@ There are four parameter types each suited for different purposes.
|
|||||||
!!! Warning
|
!!! 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.
|
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
|
## 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.
|
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.
|
||||||
|
|
||||||
@ -334,8 +339,8 @@ from functools import reduce
|
|||||||
|
|
||||||
import talib.abstract as ta
|
import talib.abstract as ta
|
||||||
|
|
||||||
from freqtrade.strategy import IStrategy
|
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
|
||||||
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
|
IStrategy, IntParameter)
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
|
|
||||||
class MyAwesomeStrategy(IStrategy):
|
class MyAwesomeStrategy(IStrategy):
|
||||||
@ -403,11 +408,106 @@ While this strategy is most likely too simple to provide consistent profit, it s
|
|||||||
!!! Note
|
!!! 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`).
|
`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.
|
||||||
|
|
||||||
??? Hint "Performance tip"
|
??? Hint "Performance tip"
|
||||||
By doing the calculation of all possible indicators in `populate_indicators()`, the calculation of the indicator happens only once for every parameter.
|
By doing the calculation of all possible indicators in `populate_indicators()`, the calculation of the indicator happens only once for every parameter.
|
||||||
While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values).
|
While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values).
|
||||||
You should however try to use space ranges as small as possible. Every new column will require more memory, and every possibility hyperopt can try will increase the search space.
|
You should however try to use space ranges as small as possible. Every new column will require more memory, and every possibility hyperopt can try will increase the search space.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
from pandas import DataFrame
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
import talib.abstract as ta
|
||||||
|
|
||||||
|
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
|
||||||
|
IStrategy, IntParameter)
|
||||||
|
import freqtrade.vendor.qtpylib.indicators 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 = []
|
||||||
|
|
||||||
|
prot.append({
|
||||||
|
"method": "CooldownPeriod",
|
||||||
|
"stop_duration_candles": self.cooldown_lookback.value
|
||||||
|
})
|
||||||
|
if self.use_stop_protection.value:
|
||||||
|
prot.append({
|
||||||
|
"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.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
protections = [
|
||||||
|
{
|
||||||
|
"method": "CooldownPeriod",
|
||||||
|
"stop_duration_candles": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Result
|
||||||
|
|
||||||
|
``` python
|
||||||
|
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.
|
||||||
|
|
||||||
## Loss-functions
|
## 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.
|
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.
|
||||||
@ -417,12 +517,14 @@ This class should be in its own file within the `user_data/hyperopts/` directory
|
|||||||
|
|
||||||
Currently, the following loss functions are builtin:
|
Currently, the following loss functions are builtin:
|
||||||
|
|
||||||
* `ShortTradeDurHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
|
* `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
|
||||||
* `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration)
|
* `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration.
|
||||||
* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to standard deviation)
|
* `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)
|
* `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)
|
* `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)
|
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
|
||||||
|
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown.
|
||||||
|
* `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown.
|
||||||
|
|
||||||
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
|
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
|
||||||
|
|
||||||
@ -460,7 +562,7 @@ For example, to use one month of data, pass `--timerange 20210101-20210201` (fro
|
|||||||
Full command:
|
Full command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
freqtrade hyperopt --hyperopt <hyperoptname> --strategy <strategyname> --timerange 20210101-20210201
|
freqtrade hyperopt --strategy <strategyname> --timerange 20210101-20210201
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running Hyperopt with Smaller Search Space
|
### Running Hyperopt with Smaller Search Space
|
||||||
@ -478,7 +580,8 @@ Legal values are:
|
|||||||
* `roi`: just optimize the minimal profit table for your strategy
|
* `roi`: just optimize the minimal profit table for your strategy
|
||||||
* `stoploss`: search for the best stoploss value
|
* `stoploss`: search for the best stoploss value
|
||||||
* `trailing`: search for the best trailing stop values
|
* `trailing`: search for the best trailing stop values
|
||||||
* `default`: `all` except `trailing`
|
* `protection`: search for the best protection parameters (read the [protections section](#optimizing-protections) 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`
|
* 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.
|
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.
|
||||||
@ -509,7 +612,13 @@ You should understand this result like:
|
|||||||
* You should not use ADX because `'buy_adx_enabled': False`.
|
* 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`)
|
* You should **consider** using the RSI indicator (`'buy_rsi_enabled': True`) and the best value is `29.0` (`'buy_rsi': 29.0`)
|
||||||
|
|
||||||
Your strategy class can immediately take advantage of these results. 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.
|
### 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 `MyAwesomeStrategy.py`, the file would be `MyAwesomeStrategy.json`).
|
||||||
|
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:
|
Transferring your whole hyperopt result to your strategy would then look like:
|
||||||
|
|
||||||
@ -525,6 +634,10 @@ class MyAwesomeStrategy(IStrategy):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! 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
|
||||||
|
|
||||||
### Understand Hyperopt ROI results
|
### 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:
|
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:
|
||||||
@ -571,11 +684,11 @@ If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace f
|
|||||||
|
|
||||||
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.
|
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 file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
|
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).
|
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 [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
||||||
|
|
||||||
!!! Note "Reduced search space"
|
!!! Note "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](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
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](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
||||||
@ -617,7 +730,7 @@ If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimiza
|
|||||||
|
|
||||||
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.
|
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 [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
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](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
||||||
|
|
||||||
!!! Note "Reduced search space"
|
!!! Note "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](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
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](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
||||||
@ -655,10 +768,10 @@ As stated in the comment, you can also use it as the values of the corresponding
|
|||||||
|
|
||||||
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.
|
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 [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
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](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
||||||
|
|
||||||
!!! Note "Reduced search space"
|
!!! Note "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](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
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](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs.
|
||||||
|
|
||||||
### Reproducible results
|
### Reproducible results
|
||||||
|
|
||||||
@ -718,8 +831,8 @@ After you run Hyperopt for the desired amount of epochs, you can later list all
|
|||||||
|
|
||||||
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
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 results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
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.
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||||
|
@ -23,6 +23,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
|
|||||||
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
|
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
|
||||||
* [`VolumePairList`](#volume-pair-list)
|
* [`VolumePairList`](#volume-pair-list)
|
||||||
* [`AgeFilter`](#agefilter)
|
* [`AgeFilter`](#agefilter)
|
||||||
|
* [`OffsetFilter`](#offsetfilter)
|
||||||
* [`PerformanceFilter`](#performancefilter)
|
* [`PerformanceFilter`](#performancefilter)
|
||||||
* [`PrecisionFilter`](#precisionfilter)
|
* [`PrecisionFilter`](#precisionfilter)
|
||||||
* [`PriceFilter`](#pricefilter)
|
* [`PriceFilter`](#pricefilter)
|
||||||
@ -51,29 +52,95 @@ To skip pair validation against active markets, set `"allow_inactive": true` wit
|
|||||||
This can be useful for backtesting expired pairs (like quarterly spot-markets).
|
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.
|
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
|
#### 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`).
|
`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 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 on the leading position of the chain of Pairlist Handlers, it does not consider `pair_whitelist` configuration setting, but selects the top assets from all available markets (with matching stake-currency) on the exchange.
|
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 `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.
|
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 and will always use up-to-date data.
|
Filtering instances (not the first position in the list) will not apply any cache and will always use up-to-date data.
|
||||||
|
|
||||||
`VolumePairList` is based on the ticker data from exchange, as reported by the ccxt library:
|
`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.
|
* The `quoteVolume` is the amount of quote (stake) currency traded (bought or sold) in last 24 hours.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"pairlists": [{
|
"pairlists": [
|
||||||
|
{
|
||||||
"method": "VolumePairList",
|
"method": "VolumePairList",
|
||||||
"number_assets": 20,
|
"number_assets": 20,
|
||||||
"sort_key": "quoteVolume",
|
"sort_key": "quoteVolume",
|
||||||
|
"min_value": 0,
|
||||||
"refresh_period": 1800
|
"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.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "VolumePairList",
|
||||||
|
"number_assets": 20,
|
||||||
|
"sort_key": "quoteVolume",
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 86400,
|
||||||
|
"lookback_days": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning "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.
|
||||||
|
|
||||||
|
!!! Warning "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.
|
||||||
|
|
||||||
|
??? Tip "Unsupported exchanges (Bittrex, Gemini)"
|
||||||
|
On some exchanges (like Bittrex and Gemini), regular VolumePairList does not work as the api does not natively provide 24h volume. This can be worked around by using candle data to build the volume.
|
||||||
|
To roughly simulate 24h volume, you can use the following configuration.
|
||||||
|
Please note that These pairlists will only refresh once per day.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "VolumePairList",
|
||||||
|
"number_assets": 20,
|
||||||
|
"sort_key": "quoteVolume",
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 86400,
|
||||||
|
"lookback_days": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "VolumePairList",
|
||||||
|
"number_assets": 20,
|
||||||
|
"sort_key": "quoteVolume",
|
||||||
|
"min_value": 0,
|
||||||
|
"refresh_period": 3600,
|
||||||
|
"lookback_timeframe": "1h",
|
||||||
|
"lookback_period": 72
|
||||||
|
}
|
||||||
|
],
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
@ -81,13 +148,40 @@ Filtering instances (not the first position in the list) will not apply any cach
|
|||||||
|
|
||||||
#### AgeFilter
|
#### AgeFilter
|
||||||
|
|
||||||
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`).
|
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
|
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
|
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.
|
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.
|
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`.
|
||||||
|
|
||||||
|
#### 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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
// ...
|
||||||
|
{
|
||||||
|
"method": "OffsetFilter",
|
||||||
|
"offset": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! 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 then the total length of the incoming pairlist will result in an empty pairlist.
|
||||||
|
|
||||||
#### PerformanceFilter
|
#### PerformanceFilter
|
||||||
|
|
||||||
@ -99,7 +193,25 @@ Sorts pairs by past trade performance, as follows:
|
|||||||
|
|
||||||
Trade count is used as a tie breaker.
|
Trade count is used as a tie breaker.
|
||||||
|
|
||||||
!!! Note
|
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` 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 without a way to recover.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
// ...
|
||||||
|
{
|
||||||
|
"method": "PerformanceFilter",
|
||||||
|
"minutes": 1440, // rolling 24h
|
||||||
|
"min_profit": 0.01
|
||||||
|
}
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning "Backtesting"
|
||||||
`PerformanceFilter` does not support backtesting mode.
|
`PerformanceFilter` does not support backtesting mode.
|
||||||
|
|
||||||
#### PrecisionFilter
|
#### PrecisionFilter
|
||||||
@ -155,10 +267,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio
|
|||||||
|
|
||||||
#### RangeStabilityFilter
|
#### RangeStabilityFilter
|
||||||
|
|
||||||
Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
|
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:
|
In the below example:
|
||||||
If the trading range over the last 10 days is <1%, remove the pair from the whitelist.
|
If the trading range over the last 10 days is <1% or >99%, remove the pair from the whitelist.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"pairlists": [
|
"pairlists": [
|
||||||
@ -166,6 +278,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
|
|||||||
"method": "RangeStabilityFilter",
|
"method": "RangeStabilityFilter",
|
||||||
"lookback_days": 10,
|
"lookback_days": 10,
|
||||||
"min_rate_of_change": 0.01,
|
"min_rate_of_change": 0.01,
|
||||||
|
"max_rate_of_change": 0.99,
|
||||||
"refresh_period": 1440
|
"refresh_period": 1440
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -173,6 +286,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
|
|||||||
|
|
||||||
!!! Tip
|
!!! 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.
|
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
|
#### VolatilityFilter
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ Also, prices at the "ask" side of the spread are higher than prices at the "bid"
|
|||||||
|
|
||||||
#### Buy price with Orderbook enabled
|
#### Buy price with Orderbook enabled
|
||||||
|
|
||||||
When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.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.
|
When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.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.
|
||||||
|
|
||||||
#### Buy price without Orderbook enabled
|
#### Buy price without Orderbook enabled
|
||||||
|
|
||||||
@ -82,22 +82,9 @@ In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot
|
|||||||
|
|
||||||
#### Sell price with Orderbook enabled
|
#### Sell price with Orderbook enabled
|
||||||
|
|
||||||
When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration (`minimal_roi` conditions) and the sell order is placed at the first profitable spot.
|
When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_top` entries in the orderbook and uses the entry specified as `ask_strategy.order_book_top` from the configured side (`ask_strategy.price_side`) as selling price.
|
||||||
|
|
||||||
!!! Note
|
1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
|
||||||
Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`.
|
|
||||||
|
|
||||||
The idea here is to place the sell order early, to be ahead in the queue.
|
|
||||||
|
|
||||||
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
|
|
||||||
|
|
||||||
!!! Warning "Order_book_max > 1 - increased risks for stoplosses!"
|
|
||||||
Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed.
|
|
||||||
Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange).
|
|
||||||
|
|
||||||
!!! Warning "Order_book_max > 1 in dry-run"
|
|
||||||
Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly.
|
|
||||||
It is therefore advised to not use this setting for dry-runs.
|
|
||||||
|
|
||||||
#### Sell price without Orderbook enabled
|
#### Sell price without Orderbook enabled
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
## Protections
|
## Protections
|
||||||
|
|
||||||
!!! Warning "Beta feature"
|
!!! Warning "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, Slack or via Github Issue.
|
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.
|
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.
|
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
|
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
|
||||||
To align your protection with your strategy, you can define protections in the strategy.
|
|
||||||
|
|
||||||
!!! Tip
|
!!! Tip
|
||||||
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
|
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
|
||||||
@ -16,6 +15,10 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
|||||||
!!! Note "Backtesting"
|
!!! Note "Backtesting"
|
||||||
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
|
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
|
||||||
|
|
||||||
|
!!! Warning "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](hyperopt.md#optimizing-protections).
|
||||||
|
|
||||||
### Available Protections
|
### Available Protections
|
||||||
|
|
||||||
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
|
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
|
||||||
@ -47,16 +50,18 @@ This applies across all pairs, unless `only_per_pair` is set to true, which will
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```json
|
``` python
|
||||||
"protections": [
|
@property
|
||||||
{
|
def protections(self):
|
||||||
"method": "StoplossGuard",
|
return [
|
||||||
"lookback_period_candles": 24,
|
{
|
||||||
"trade_limit": 4,
|
"method": "StoplossGuard",
|
||||||
"stop_duration_candles": 4,
|
"lookback_period_candles": 24,
|
||||||
"only_per_pair": false
|
"trade_limit": 4,
|
||||||
}
|
"stop_duration_candles": 4,
|
||||||
],
|
"only_per_pair": False
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
@ -69,16 +74,18 @@ The below example stops trading for all pairs for 4 candles after the last trade
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```json
|
``` python
|
||||||
"protections": [
|
@property
|
||||||
{
|
def protections(self):
|
||||||
"method": "MaxDrawdown",
|
return [
|
||||||
"lookback_period_candles": 48,
|
{
|
||||||
"trade_limit": 20,
|
"method": "MaxDrawdown",
|
||||||
"stop_duration_candles": 12,
|
"lookback_period_candles": 48,
|
||||||
"max_allowed_drawdown": 0.2
|
"trade_limit": 20,
|
||||||
},
|
"stop_duration_candles": 12,
|
||||||
],
|
"max_allowed_drawdown": 0.2
|
||||||
|
},
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Low Profit Pairs
|
#### Low Profit Pairs
|
||||||
@ -88,16 +95,18 @@ If that ratio is below `required_profit`, that pair will be locked for `stop_dur
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```json
|
``` python
|
||||||
"protections": [
|
@property
|
||||||
{
|
def protections(self):
|
||||||
"method": "LowProfitPairs",
|
return [
|
||||||
"lookback_period_candles": 6,
|
{
|
||||||
"trade_limit": 2,
|
"method": "LowProfitPairs",
|
||||||
"stop_duration": 60,
|
"lookback_period_candles": 6,
|
||||||
"required_profit": 0.02
|
"trade_limit": 2,
|
||||||
}
|
"stop_duration": 60,
|
||||||
],
|
"required_profit": 0.02
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Cooldown Period
|
#### Cooldown Period
|
||||||
@ -106,13 +115,15 @@ The below example will stop trading a pair for 60 minutes if the pair does not h
|
|||||||
|
|
||||||
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
|
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
|
||||||
|
|
||||||
```json
|
``` python
|
||||||
"protections": [
|
@property
|
||||||
{
|
def protections(self):
|
||||||
"method": "CooldownPeriod",
|
return [
|
||||||
"stop_duration_candles": 2
|
{
|
||||||
}
|
"method": "CooldownPeriod",
|
||||||
],
|
"stop_duration_candles": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
@ -132,84 +143,47 @@ The below example assumes a timeframe of 1 hour:
|
|||||||
* Locks all pairs that had 4 Trades within the last 6 hours (`6 * 1h candles`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`).
|
* Locks all pairs that had 4 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.
|
* 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.
|
||||||
|
|
||||||
```json
|
|
||||||
"timeframe": "1h",
|
|
||||||
"protections": [
|
|
||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
],
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use the same in your strategy, the syntax is only slightly different:
|
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from freqtrade.strategy import IStrategy
|
from freqtrade.strategy import IStrategy
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy)
|
class AwesomeStrategy(IStrategy)
|
||||||
timeframe = '1h'
|
timeframe = '1h'
|
||||||
protections = [
|
|
||||||
{
|
@property
|
||||||
"method": "CooldownPeriod",
|
def protections(self):
|
||||||
"stop_duration_candles": 5
|
return [
|
||||||
},
|
{
|
||||||
{
|
"method": "CooldownPeriod",
|
||||||
"method": "MaxDrawdown",
|
"stop_duration_candles": 5
|
||||||
"lookback_period_candles": 48,
|
},
|
||||||
"trade_limit": 20,
|
{
|
||||||
"stop_duration_candles": 4,
|
"method": "MaxDrawdown",
|
||||||
"max_allowed_drawdown": 0.2
|
"lookback_period_candles": 48,
|
||||||
},
|
"trade_limit": 20,
|
||||||
{
|
"stop_duration_candles": 4,
|
||||||
"method": "StoplossGuard",
|
"max_allowed_drawdown": 0.2
|
||||||
"lookback_period_candles": 24,
|
},
|
||||||
"trade_limit": 4,
|
{
|
||||||
"stop_duration_candles": 2,
|
"method": "StoplossGuard",
|
||||||
"only_per_pair": False
|
"lookback_period_candles": 24,
|
||||||
},
|
"trade_limit": 4,
|
||||||
{
|
"stop_duration_candles": 2,
|
||||||
"method": "LowProfitPairs",
|
"only_per_pair": False
|
||||||
"lookback_period_candles": 6,
|
},
|
||||||
"trade_limit": 2,
|
{
|
||||||
"stop_duration_candles": 60,
|
"method": "LowProfitPairs",
|
||||||
"required_profit": 0.02
|
"lookback_period_candles": 6,
|
||||||
},
|
"trade_limit": 2,
|
||||||
{
|
"stop_duration_candles": 60,
|
||||||
"method": "LowProfitPairs",
|
"required_profit": 0.02
|
||||||
"lookback_period_candles": 24,
|
},
|
||||||
"trade_limit": 4,
|
{
|
||||||
"stop_duration_candles": 2,
|
"method": "LowProfitPairs",
|
||||||
"required_profit": 0.01
|
"lookback_period_candles": 24,
|
||||||
}
|
"trade_limit": 4,
|
||||||
]
|
"stop_duration_candles": 2,
|
||||||
|
"required_profit": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
@ -36,10 +36,11 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python
|
|||||||
|
|
||||||
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#blacklists))
|
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist))
|
||||||
- [X] [Bittrex](https://bittrex.com/)
|
- [X] [Bittrex](https://bittrex.com/)
|
||||||
- [X] [FTX](https://ftx.com)
|
- [X] [FTX](https://ftx.com)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
|
|
||||||
### Community tested
|
### Community tested
|
||||||
@ -47,6 +48,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
|
|||||||
Exchanges confirmed working by the community:
|
Exchanges confirmed working by the community:
|
||||||
|
|
||||||
- [X] [Bitvavo](https://bitvavo.com/)
|
- [X] [Bitvavo](https://bitvavo.com/)
|
||||||
|
- [X] [Kucoin](https://www.kucoin.com/)
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@ -72,13 +74,9 @@ Alternatively
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
### Help / Discord / Slack
|
### 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 our slack channel.
|
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](https://discord.gg/p7nuUNVfP7).
|
||||||
|
|
||||||
Please check out our [discord server](https://discord.gg/p7nuUNVfP7).
|
|
||||||
|
|
||||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
|
|
||||||
|
|
||||||
## Ready to try?
|
## Ready to try?
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ OS Specific steps are listed first, the [Common](#common) section below is neces
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
|
|
||||||
# install packages
|
# install packages
|
||||||
sudo apt install -y python3-pip python3-venv python3-pandas git
|
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "RaspberryPi/Raspbian"
|
=== "RaspberryPi/Raspbian"
|
||||||
@ -113,6 +113,13 @@ git checkout develop
|
|||||||
|
|
||||||
You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands.
|
You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands.
|
||||||
|
|
||||||
|
??? Note "Install from pypi"
|
||||||
|
An alternative way to install Freqtrade is from [pypi](https://pypi.org/project/freqtrade/). The downside is that this method requires ta-lib to be correctly installed beforehand, and is therefore currently not the recommended way to install Freqtrade.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
pip install freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Script Installation
|
## Script Installation
|
||||||
@ -203,6 +210,8 @@ sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
|
|||||||
./configure --prefix=/usr/local
|
./configure --prefix=/usr/local
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
|
# On debian based systems (debian, ubuntu, ...) - updating ldconfig might be necessary.
|
||||||
|
sudo ldconfig
|
||||||
cd ..
|
cd ..
|
||||||
rm -rf ./ta-lib*
|
rm -rf ./ta-lib*
|
||||||
```
|
```
|
||||||
|
68
docs/overrides/main.html
Normal file
68
docs/overrides/main.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
{% block site_nav %}
|
||||||
|
|
||||||
|
<!-- Main navigation -->
|
||||||
|
{% if nav %}
|
||||||
|
{% if page and page.meta and page.meta.hide %}
|
||||||
|
{% set hidden = "hidden" if "navigation" in page.meta.hide %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" {{ hidden }}>
|
||||||
|
<div class="md-sidebar__scrollwrap">
|
||||||
|
<div id="widget-wrapper">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="md-sidebar__inner">
|
||||||
|
{% include "partials/nav.html" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Table of contents -->
|
||||||
|
{% if page.toc and not "toc.integrate" in features %}
|
||||||
|
{% if page and page.meta and page.meta.hide %}
|
||||||
|
{% set hidden = "hidden" if "toc" in page.meta.hide %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" {{ hidden }}>
|
||||||
|
<div class="md-sidebar__scrollwrap">
|
||||||
|
<div class="md-sidebar__inner">
|
||||||
|
{% include "partials/toc.html" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block footer %}
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
<!-- Place this tag in your head or just before your close body tag. -->
|
||||||
|
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
|
||||||
|
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<!-- Load binance SDK -->
|
||||||
|
<script async defer src="https://public.bnbstatic.com/static/js/broker-sdk/broker-sdk@1.0.0.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.onload = function () {
|
||||||
|
var sidebar = document.getElementById('widget-wrapper')
|
||||||
|
var newDiv = document.createElement("div");
|
||||||
|
newDiv.id = "widget";
|
||||||
|
try {
|
||||||
|
sidebar.prepend(newDiv);
|
||||||
|
|
||||||
|
window.binanceBrokerPortalSdk.initBrokerSDK('#widget', {
|
||||||
|
apiHost: 'https://www.binance.com',
|
||||||
|
brokerId: 'R4BD3S82',
|
||||||
|
slideTime: 4e4,
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@ -1,72 +0,0 @@
|
|||||||
{#-
|
|
||||||
This file was automatically generated - do not edit
|
|
||||||
-#}
|
|
||||||
{% set site_url = config.site_url | d(nav.homepage.url, true) | url %}
|
|
||||||
{% if not config.use_directory_urls and site_url[0] == site_url[-1] == "." %}
|
|
||||||
{% set site_url = site_url ~ "/index.html" %}
|
|
||||||
{% endif %}
|
|
||||||
<header class="md-header" data-md-component="header">
|
|
||||||
<nav class="md-header__inner md-grid" aria-label="{{ lang.t('header.title') }}">
|
|
||||||
<a href="{{ site_url }}" title="{{ config.site_name | e }}" class="md-header__button md-logo"
|
|
||||||
aria-label="{{ config.site_name }}">
|
|
||||||
{% include "partials/logo.html" %}
|
|
||||||
</a>
|
|
||||||
<label class="md-header__button md-icon" for="__drawer">
|
|
||||||
{% include ".icons/material/menu" ~ ".svg" %}
|
|
||||||
</label>
|
|
||||||
<div class="md-header__title" data-md-component="header-title">
|
|
||||||
<div class="md-header__ellipsis">
|
|
||||||
<div class="md-header__topic">
|
|
||||||
<span class="md-ellipsis">
|
|
||||||
{{ config.site_name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="md-header__topic" data-md-component="header-topic">
|
|
||||||
<span class="md-ellipsis">
|
|
||||||
{% if page and page.meta and page.meta.title %}
|
|
||||||
{{ page.meta.title }}
|
|
||||||
{% else %}
|
|
||||||
{{ page.title }}
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="md-header__options">
|
|
||||||
{% if config.extra.alternate %}
|
|
||||||
<div class="md-select">
|
|
||||||
{% set icon = config.theme.icon.alternate or "material/translate" %}
|
|
||||||
<span class="md-header__button md-icon">
|
|
||||||
{% include ".icons/" ~ icon ~ ".svg" %}
|
|
||||||
</span>
|
|
||||||
<div class="md-select__inner">
|
|
||||||
<ul class="md-select__list">
|
|
||||||
{% for alt in config.extra.alternate %}
|
|
||||||
<li class="md-select__item">
|
|
||||||
<a href="{{ alt.link | url }}" class="md-select__link">
|
|
||||||
{{ alt.name }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% if "search" in config["plugins"] %}
|
|
||||||
<label class="md-header__button md-icon" for="__search">
|
|
||||||
{% include ".icons/material/magnify.svg" %}
|
|
||||||
</label>
|
|
||||||
{% include "partials/search.html" %}
|
|
||||||
{% endif %}
|
|
||||||
{% if config.repo_url %}
|
|
||||||
<div class="md-header__source">
|
|
||||||
{% include "partials/source.html" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</nav>
|
|
||||||
<!-- Place this tag in your head or just before your close body tag. -->
|
|
||||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
|
||||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
|
|
||||||
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
|
|
||||||
</header>
|
|
@ -1,4 +1,4 @@
|
|||||||
mkdocs==1.2.1
|
mkdocs==1.2.3
|
||||||
mkdocs-material==7.1.8
|
mkdocs-material==7.3.4
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==8.2
|
pymdown-extensions==9.0
|
||||||
|
@ -78,7 +78,7 @@ If you run your bot using docker, you'll need to have the bot listen to incoming
|
|||||||
},
|
},
|
||||||
```
|
```
|
||||||
|
|
||||||
Uncomment the following from your docker-compose file:
|
Make sure that the following 2 lines are available in your docker-compose file:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
ports:
|
ports:
|
||||||
|
@ -110,7 +110,7 @@ DELETE FROM trades WHERE id = 31;
|
|||||||
Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems.
|
Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems.
|
||||||
|
|
||||||
Installation:
|
Installation:
|
||||||
`pip install psycopg2`
|
`pip install psycopg2-binary`
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
`... --db-url postgresql+psycopg2://<username>:<password>@localhost:5432/<database>`
|
`... --db-url postgresql+psycopg2://<username>:<password>@localhost:5432/<database>`
|
||||||
|
@ -55,7 +55,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
|
||||||
# Obtain last available candle. Do not use current_time to look up latest candle, because
|
# Obtain last available candle. Do not use current_time to look up latest candle, because
|
||||||
# current_time points to curret incomplete candle whose data is not available.
|
# current_time points to current incomplete candle whose data is not available.
|
||||||
last_candle = dataframe.iloc[-1].squeeze()
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
# <...>
|
# <...>
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ It is possible to define custom sell signals, indicating that specified position
|
|||||||
|
|
||||||
For example you could implement a 1:2 risk-reward ROI with `custom_sell()`.
|
For example you could implement a 1:2 risk-reward ROI with `custom_sell()`.
|
||||||
|
|
||||||
Using custom_sell() signals in place of stoplosses 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.
|
Using custom_sell() 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
|
!!! Note
|
||||||
Returning a `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters.
|
Returning a `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters.
|
||||||
@ -114,6 +114,36 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks.
|
See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks.
|
||||||
|
|
||||||
|
## Buy Tag
|
||||||
|
|
||||||
|
When your strategy has multiple buy signals, you can name the signal that triggered.
|
||||||
|
Then you can access you buy signal on `custom_sell`
|
||||||
|
|
||||||
|
```python
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe['rsi'] < 35) &
|
||||||
|
(dataframe['volume'] > 0)
|
||||||
|
),
|
||||||
|
['buy', 'buy_tag']] = (1, 'buy_signal_rsi')
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||||
|
current_profit: float, **kwargs):
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
|
||||||
|
return 'sell_signal_rsi'
|
||||||
|
return None
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
`buy_tag` is limited to 100 characters, remaining data will be truncated.
|
||||||
|
|
||||||
|
|
||||||
## Custom stoploss
|
## Custom stoploss
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@ -243,7 +273,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
current_rate: float, current_profit: float, **kwargs) -> float:
|
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||||
|
|
||||||
if current_profit < 0.04:
|
if current_profit < 0.04:
|
||||||
return -1 # return a value bigger than the inital stoploss to keep using the inital stoploss
|
return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss
|
||||||
|
|
||||||
# After reaching the desired offset, allow the stoploss to trail by half the profit
|
# After reaching the desired offset, allow the stoploss to trail by half the profit
|
||||||
desired_stoploss = current_profit / 2
|
desired_stoploss = current_profit / 2
|
||||||
@ -258,6 +288,12 @@ Stoploss values returned from `custom_stoploss()` always specify a percentage re
|
|||||||
|
|
||||||
The helper function [`stoploss_from_open()`](strategy-customization.md#stoploss_from_open) can be used to convert from an open price relative stop, to a current price relative stop which can be returned from `custom_stoploss()`.
|
The helper function [`stoploss_from_open()`](strategy-customization.md#stoploss_from_open) can be used to convert from an open price relative stop, to a current price relative stop which can be returned from `custom_stoploss()`.
|
||||||
|
|
||||||
|
### Calculating 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()`](strategy-customization.md#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()`.
|
||||||
|
|
||||||
#### Stepped stoploss
|
#### Stepped stoploss
|
||||||
|
|
||||||
Instead of continuously trailing behind the current price, this example sets fixed stoploss price levels based on the current profit.
|
Instead of continuously trailing behind the current price, this example sets fixed stoploss price levels based on the current profit.
|
||||||
@ -327,6 +363,55 @@ See [Dataframe access](#dataframe-access) for more information about dataframe u
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Custom order price rules
|
||||||
|
|
||||||
|
By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), 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.
|
||||||
|
|
||||||
|
!!! 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.
|
||||||
|
|
||||||
|
### Custom order entry and exit price example
|
||||||
|
|
||||||
|
``` python
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
|
# ... populate_* methods
|
||||||
|
|
||||||
|
def custom_entry_price(self, pair: str, current_time: datetime,
|
||||||
|
proposed_rate, **kwargs) -> float:
|
||||||
|
|
||||||
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
|
timeframe=self.timeframe)
|
||||||
|
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, **kwargs) -> float:
|
||||||
|
|
||||||
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
|
||||||
|
timeframe=self.timeframe)
|
||||||
|
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.
|
||||||
|
|
||||||
|
!!! Warning "No backtesting support"
|
||||||
|
Custom entry-prices are currently not supported during backtesting.
|
||||||
|
|
||||||
## Custom order timeout rules
|
## Custom order timeout rules
|
||||||
|
|
||||||
Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
|
Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
|
||||||
@ -454,7 +539,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
|
||||||
time_in_force: str, **kwargs) -> bool:
|
time_in_force: str, current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a buy order.
|
Called right before placing a buy order.
|
||||||
Timing for this function is critical, so avoid doing heavy computations or
|
Timing for this function is critical, so avoid doing heavy computations or
|
||||||
@ -469,6 +554,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param amount: Amount in target (quote) currency that's going to be traded.
|
:param amount: Amount in target (quote) currency that's going to be traded.
|
||||||
:param rate: Rate that's going to be used when using limit orders
|
:param rate: Rate that's going to be used when using limit orders
|
||||||
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
|
||||||
|
: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.
|
: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.
|
:return bool: When True is returned, then the buy-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
@ -490,7 +576,8 @@ class AwesomeStrategy(IStrategy):
|
|||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
|
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
|
||||||
rate: float, time_in_force: str, sell_reason: str, **kwargs) -> bool:
|
rate: float, time_in_force: str, sell_reason: str,
|
||||||
|
current_time: datetime, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Called right before placing a regular sell order.
|
Called right before placing a regular sell order.
|
||||||
Timing for this function is critical, so avoid doing heavy computations or
|
Timing for this function is critical, so avoid doing heavy computations or
|
||||||
@ -508,6 +595,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
:param sell_reason: Sell reason.
|
:param sell_reason: Sell reason.
|
||||||
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
|
||||||
'sell_signal', 'force_sell', 'emergency_sell']
|
'sell_signal', 'force_sell', 'emergency_sell']
|
||||||
|
: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.
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
:return bool: When True is returned, then the sell-order is placed on the exchange.
|
:return bool: When True is returned, then the sell-order is placed on the exchange.
|
||||||
False aborts the process
|
False aborts the process
|
||||||
@ -521,6 +609,39 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Stake size management
|
||||||
|
|
||||||
|
It is possible to manage your risk by reducing or increasing stake amount when placing a new trade.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
|
||||||
|
proposed_stake: float, min_stake: float, max_stake: float,
|
||||||
|
**kwargs) -> float:
|
||||||
|
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
||||||
|
current_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
|
if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']:
|
||||||
|
if self.config['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 self.wallets.get_total_stake_amount() / self.config['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 acton will be logged.
|
||||||
|
|
||||||
|
!!! Tip
|
||||||
|
Returning `0` or `None` will prevent trades from being placed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Derived strategies
|
## Derived strategies
|
||||||
@ -580,3 +701,33 @@ The variable 'content', will contain the strategy file in a BASE64 encoded form.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Please ensure that 'NameOfStrategy' is identical to the strategy name!
|
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`](https://github.com/pandas-dev/pandas) and as the warning continues to say:
|
||||||
|
use `pd.concat(axis=1)`.
|
||||||
|
This can have slight performance implications, which are usually only visible during hyperopt (when optimizing an indicator).
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for val in self.buy_ema_short.range:
|
||||||
|
dataframe[f'ema_short_{val}'] = ta.EMA(dataframe, timeperiod=val)
|
||||||
|
```
|
||||||
|
|
||||||
|
should be rewritten to
|
||||||
|
|
||||||
|
```python
|
||||||
|
frames = [dataframe]
|
||||||
|
for val in self.buy_ema_short.range:
|
||||||
|
frames.append({
|
||||||
|
f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Append columns to existing dataframe
|
||||||
|
merged_frame = pd.concat(frames, axis=1)
|
||||||
|
```
|
||||||
|
@ -122,6 +122,16 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py).
|
Look into the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py).
|
||||||
Then uncomment indicators you need.
|
Then uncomment indicators you need.
|
||||||
|
|
||||||
|
#### Indicator libraries
|
||||||
|
|
||||||
|
Out of the box, freqtrade installs the following technical libraries:
|
||||||
|
|
||||||
|
* [ta-lib](http://mrjbq7.github.io/ta-lib/)
|
||||||
|
* [pandas-ta](https://twopirllc.github.io/pandas-ta/)
|
||||||
|
* [technical](https://github.com/freqtrade/technical/)
|
||||||
|
|
||||||
|
Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author.
|
||||||
|
|
||||||
### Strategy startup period
|
### Strategy startup period
|
||||||
|
|
||||||
Most indicators have an instable startup period, in which they are either not available, or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
|
Most indicators have an instable startup period, in which they are either not available, or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
|
||||||
@ -302,7 +312,7 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w
|
|||||||
The Metadata-dict should not be modified and does not persist information across multiple calls.
|
The Metadata-dict should not be modified and does not persist information across multiple calls.
|
||||||
Instead, have a look at the section [Storing information](strategy-advanced.md#Storing-information)
|
Instead, have a look at the section [Storing information](strategy-advanced.md#Storing-information)
|
||||||
|
|
||||||
## Additional data (informative_pairs)
|
## Informative Pairs
|
||||||
|
|
||||||
### Get data for non-tradeable pairs
|
### Get data for non-tradeable pairs
|
||||||
|
|
||||||
@ -331,6 +341,133 @@ A full sample can be found [in the DataProvider section](#complete-data-provider
|
|||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
### 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](hyperopt.md#optimizing-an-indicator-parameter)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
??? info "Full documentation"
|
||||||
|
``` python
|
||||||
|
def informative(timeframe: str, asset: str = '',
|
||||||
|
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
|
||||||
|
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
|
||||||
|
"""
|
||||||
|
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
|
||||||
|
define informative indicators.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
@informative('1h')
|
||||||
|
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
:param timeframe: Informative timeframe. Must always be equal or higher than strategy timeframe.
|
||||||
|
:param asset: Informative asset, for example BTC, BTC/USDT, ETH/BTC. Do not specify to use
|
||||||
|
current pair.
|
||||||
|
:param fmt: Column format (str) or column formatter (callable(name, asset, timeframe)). When not
|
||||||
|
specified, defaults to:
|
||||||
|
* {base}_{quote}_{column}_{timeframe} if asset is specified.
|
||||||
|
* {column}_{timeframe} if asset is not specified.
|
||||||
|
Format string supports these format variables:
|
||||||
|
* {asset} - full name of the asset, for example 'BTC/USDT'.
|
||||||
|
* {base} - base currency in lower case, for example 'eth'.
|
||||||
|
* {BASE} - same as {base}, except in upper case.
|
||||||
|
* {quote} - quote currency in lower case, for example 'usdt'.
|
||||||
|
* {QUOTE} - same as {quote}, except in upper case.
|
||||||
|
* {column} - name of dataframe column.
|
||||||
|
* {timeframe} - timeframe of informative dataframe.
|
||||||
|
:param ffill: ffill dataframe after merging informative pair.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
??? Example "Fast and easy way to define informative pairs"
|
||||||
|
|
||||||
|
Most of the time we do not need power and flexibility offered by `merge_informative_pair()`, therefore we can use a decorator to quickly define informative pairs.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy import IStrategy, informative
|
||||||
|
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
|
# This method is not required.
|
||||||
|
# def informative_pairs(self): ...
|
||||||
|
|
||||||
|
# Define informative upper timeframe for each pair. Decorators can be stacked on same
|
||||||
|
# method. Available in populate_indicators as 'rsi_30m' and 'rsi_1h'.
|
||||||
|
@informative('30m')
|
||||||
|
@informative('1h')
|
||||||
|
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
# Define BTC/STAKE informative pair. Available in populate_indicators and other methods as
|
||||||
|
# 'btc_rsi_1h'. Current stake currency should be specified as {stake} format variable
|
||||||
|
# instead of hardcoding actual stake currency. Available in populate_indicators and other
|
||||||
|
# methods as 'btc_usdt_rsi_1h' (when stake currency is USDT).
|
||||||
|
@informative('1h', 'BTC/{stake}')
|
||||||
|
def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
# Define BTC/ETH informative pair. You must specify quote currency if it is different from
|
||||||
|
# stake currency. Available in populate_indicators and other methods as 'eth_btc_rsi_1h'.
|
||||||
|
@informative('1h', 'ETH/BTC')
|
||||||
|
def populate_indicators_eth_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
|
||||||
|
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
|
||||||
|
# formatting. Available in populate_indicators and other methods as 'rsi_upper'.
|
||||||
|
@informative('1h', 'BTC/{stake}', '{column}')
|
||||||
|
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# Strategy timeframe indicators for current pair.
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
# Informative pairs are available in this method.
|
||||||
|
dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h']
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! 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](#complete-data-provider-sample).
|
||||||
|
|
||||||
|
!!! 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.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
stake = self.config['stake_currency']
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(dataframe[f'btc_{stake}_rsi_1h'] < 35)
|
||||||
|
&
|
||||||
|
(dataframe['volume'] > 0)
|
||||||
|
),
|
||||||
|
['buy', 'buy_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}')`.
|
||||||
|
|
||||||
|
!!! Warning "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!
|
||||||
|
|
||||||
|
|
||||||
## Additional data (DataProvider)
|
## Additional data (DataProvider)
|
||||||
|
|
||||||
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
|
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
|
||||||
@ -639,6 +776,42 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
|
|||||||
|
|
||||||
Full examples can be found in the [Custom stoploss](strategy-advanced.md#custom-stoploss) section of the Documentation.
|
Full examples can be found in the [Custom stoploss](strategy-advanced.md#custom-stoploss) section of the Documentation.
|
||||||
|
|
||||||
|
!!! 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 `sell_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_from_absolute()*
|
||||||
|
|
||||||
|
In some situations it may be confusing to deal with stops relative to current rate. Instead, you may define a stoploss level using an absolute price.
|
||||||
|
|
||||||
|
??? Example "Returning a stoploss using absolute price from the custom stoploss function"
|
||||||
|
|
||||||
|
If we want to trail a stop price at 2xATR below current proce we can call `stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate)`.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
from freqtrade.strategy import IStrategy, stoploss_from_open
|
||||||
|
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
|
use_custom_stoploss = True
|
||||||
|
|
||||||
|
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
|
||||||
|
current_rate: float, current_profit: float, **kwargs) -> float:
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||||
|
candle = dataframe.iloc[-1].squeeze()
|
||||||
|
return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
## Additional data (Wallets)
|
## Additional data (Wallets)
|
||||||
|
|
||||||
@ -723,7 +896,8 @@ Sometimes it may be desired to lock a pair after certain events happen (e.g. mul
|
|||||||
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`.
|
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.
|
`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)`.
|
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)`.
|
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
|
||||||
|
|
||||||
@ -781,6 +955,8 @@ Printing more than a few rows is also possible (simply use `print(dataframe)` i
|
|||||||
|
|
||||||
## Common mistakes when developing strategies
|
## 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.
|
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.
|
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.
|
||||||
|
|
||||||
|
@ -130,6 +130,44 @@ trades = load_backtest_data(backtest_dir)
|
|||||||
trades.groupby("pair")["sell_reason"].value_counts()
|
trades.groupby("pair")["sell_reason"].value_counts()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Plotting daily profit / equity line
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)
|
||||||
|
|
||||||
|
from freqtrade.configuration import Configuration
|
||||||
|
from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats
|
||||||
|
import plotly.express as px
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
# strategy = 'SampleStrategy'
|
||||||
|
# config = Configuration.from_files(["user_data/config.json"])
|
||||||
|
# backtest_dir = config["user_data_dir"] / "backtest_results"
|
||||||
|
|
||||||
|
stats = load_backtest_stats(backtest_dir)
|
||||||
|
strategy_stats = stats['strategy'][strategy]
|
||||||
|
|
||||||
|
dates = []
|
||||||
|
profits = []
|
||||||
|
for date_profit in strategy_stats['daily_profit']:
|
||||||
|
dates.append(date_profit[0])
|
||||||
|
profits.append(date_profit[1])
|
||||||
|
|
||||||
|
equity = 0
|
||||||
|
equity_daily = []
|
||||||
|
for daily_profit in profits:
|
||||||
|
equity_daily.append(equity)
|
||||||
|
equity += float(daily_profit)
|
||||||
|
|
||||||
|
|
||||||
|
df = pd.DataFrame({'dates': dates,'equity_daily': equity_daily})
|
||||||
|
|
||||||
|
fig = px.line(df, x="dates", y="equity_daily")
|
||||||
|
fig.show()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Load live trading results into a pandas dataframe
|
### Load live trading results into a pandas dataframe
|
||||||
|
|
||||||
In case you did already some trading and want to analyze your performance
|
In case you did already some trading and want to analyze your performance
|
||||||
@ -190,7 +228,7 @@ graph = generate_candlestick_graph(pair=pair,
|
|||||||
# Show graph inline
|
# Show graph inline
|
||||||
# graph.show()
|
# graph.show()
|
||||||
|
|
||||||
# Render graph in a seperate window
|
# Render graph in a separate window
|
||||||
graph.show(renderer="browser")
|
graph.show(renderer="browser")
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -11,3 +11,18 @@
|
|||||||
.rst-versions .rst-other-versions {
|
.rst-versions .rst-other-versions {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#widget-wrapper {
|
||||||
|
height: calc(220px * 0.5625 + 18px);
|
||||||
|
width: 220px;
|
||||||
|
margin: 0 auto 16px auto;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--md-code-bg-color);
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: calc(76.25em - 1px)) {
|
||||||
|
#widget-wrapper { display: none; }
|
||||||
|
}
|
||||||
|
@ -93,8 +93,11 @@ Example configuration showing the different settings:
|
|||||||
"buy_cancel": "silent",
|
"buy_cancel": "silent",
|
||||||
"sell_cancel": "on",
|
"sell_cancel": "on",
|
||||||
"buy_fill": "off",
|
"buy_fill": "off",
|
||||||
"sell_fill": "off"
|
"sell_fill": "off",
|
||||||
|
"protection_trigger": "off",
|
||||||
|
"protection_trigger_global": "on"
|
||||||
},
|
},
|
||||||
|
"reload": true,
|
||||||
"balance_dust_level": 0.01
|
"balance_dust_level": 0.01
|
||||||
},
|
},
|
||||||
```
|
```
|
||||||
@ -102,9 +105,11 @@ Example configuration showing the different settings:
|
|||||||
`buy` notifications are sent when the order is placed, while `buy_fill` notifications are sent when the order is filled on the exchange.
|
`buy` notifications are sent when the order is placed, while `buy_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`sell` notifications are sent when the order is placed, while `sell_fill` notifications are sent when the order is filled on the exchange.
|
`sell` notifications are sent when the order is placed, while `sell_fill` notifications are sent when the order is filled on the exchange.
|
||||||
`*_fill` notifications are off by default and must be explicitly enabled.
|
`*_fill` notifications are off by default and must be explicitly enabled.
|
||||||
|
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
|
||||||
|
|
||||||
|
|
||||||
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
||||||
|
`reload` allows you to disable reload-buttons on selected messages.
|
||||||
|
|
||||||
## Create a custom keyboard (command shortcut buttons)
|
## Create a custom keyboard (command shortcut buttons)
|
||||||
|
|
||||||
@ -166,7 +171,7 @@ official commands. You can ask at any moment for help with `/help`.
|
|||||||
| `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default)
|
| `/profit [<n>]` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default)
|
||||||
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
|
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
|
||||||
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
|
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
|
||||||
| `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
|
| `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`forcebuy_enable` must be set to True)
|
||||||
| `/performance` | Show performance of each finished trade grouped by pair
|
| `/performance` | Show performance of each finished trade grouped by pair
|
||||||
| `/balance` | Show account balance per currency
|
| `/balance` | Show account balance per currency
|
||||||
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
|
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
|
||||||
@ -243,10 +248,10 @@ current max
|
|||||||
Return a summary of your profit/loss and performance.
|
Return a summary of your profit/loss and performance.
|
||||||
|
|
||||||
> **ROI:** Close trades
|
> **ROI:** Close trades
|
||||||
> ∙ `0.00485701 BTC (258.45%)`
|
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
|
||||||
> ∙ `62.968 USD`
|
> ∙ `62.968 USD`
|
||||||
> **ROI:** All trades
|
> **ROI:** All trades
|
||||||
> ∙ `0.00255280 BTC (143.43%)`
|
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
|
||||||
> ∙ `33.095 EUR`
|
> ∙ `33.095 EUR`
|
||||||
>
|
>
|
||||||
> **Total Trade Count:** `138`
|
> **Total Trade Count:** `138`
|
||||||
@ -255,6 +260,10 @@ Return a summary of your profit/loss and performance.
|
|||||||
> **Avg. Duration:** `2:33:45`
|
> **Avg. Duration:** `2:33:45`
|
||||||
> **Best Performing:** `PAY/BTC: 50.23%`
|
> **Best Performing:** `PAY/BTC: 50.23%`
|
||||||
|
|
||||||
|
The relative profit of `1.2%` is the average profit per trade.
|
||||||
|
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
|
||||||
|
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
|
||||||
|
|
||||||
### /forcesell <trade_id>
|
### /forcesell <trade_id>
|
||||||
|
|
||||||
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
|
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
|
||||||
|
129
docs/utils.md
129
docs/utils.md
@ -26,9 +26,7 @@ optional arguments:
|
|||||||
├── data
|
├── data
|
||||||
├── hyperopt_results
|
├── hyperopt_results
|
||||||
├── hyperopts
|
├── hyperopts
|
||||||
│ ├── sample_hyperopt_advanced.py
|
|
||||||
│ ├── sample_hyperopt_loss.py
|
│ ├── sample_hyperopt_loss.py
|
||||||
│ └── sample_hyperopt.py
|
|
||||||
├── notebooks
|
├── notebooks
|
||||||
│ └── strategy_analysis_example.ipynb
|
│ └── strategy_analysis_example.ipynb
|
||||||
├── plot
|
├── plot
|
||||||
@ -111,46 +109,11 @@ Using the advanced template (populates all optional functions and methods)
|
|||||||
freqtrade new-strategy --strategy AwesomeStrategy --template advanced
|
freqtrade new-strategy --strategy AwesomeStrategy --template advanced
|
||||||
```
|
```
|
||||||
|
|
||||||
## Create new hyperopt
|
## List Strategies
|
||||||
|
|
||||||
Creates a new hyperopt from a template similar to SampleHyperopt.
|
Use the `list-strategies` subcommand to see all strategies in one particular directory.
|
||||||
The file will be named inline with your class name, and will not overwrite existing files.
|
|
||||||
|
|
||||||
Results will be located in `user_data/hyperopts/<classname>.py`.
|
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).
|
||||||
|
|
||||||
``` output
|
|
||||||
usage: freqtrade new-hyperopt [-h] [--userdir PATH] [--hyperopt 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.
|
|
||||||
--hyperopt NAME Specify hyperopt 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-hyperopt
|
|
||||||
|
|
||||||
```bash
|
|
||||||
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
|
|
||||||
```
|
|
||||||
|
|
||||||
With custom user directory
|
|
||||||
|
|
||||||
```bash
|
|
||||||
freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt
|
|
||||||
```
|
|
||||||
|
|
||||||
## List Strategies and List Hyperopts
|
|
||||||
|
|
||||||
Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts.
|
|
||||||
|
|
||||||
These subcommands are useful for finding problems in your environment with loading strategies or hyperopt classes: modules with strategies or hyperopt classes that contain errors and failed to load are printed in red (LOAD FAILED), while strategies or hyperopt classes with duplicate names are printed in yellow (DUPLICATE NAME).
|
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
@ -164,34 +127,6 @@ optional arguments:
|
|||||||
--no-color Disable colorization of hyperopt results. May be
|
--no-color Disable colorization of hyperopt results. May be
|
||||||
useful if you are redirecting output to a file.
|
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: `config.json`).
|
|
||||||
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.
|
|
||||||
```
|
|
||||||
```
|
|
||||||
usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|
||||||
[-d PATH] [--userdir PATH]
|
|
||||||
[--hyperopt-path PATH] [-1] [--no-color]
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
|
||||||
Hyperopt Loss functions.
|
|
||||||
-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:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified. Special values are:
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
@ -211,18 +146,16 @@ Common arguments:
|
|||||||
!!! Warning
|
!!! 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.
|
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 and hyperopts directories (within the default userdir).
|
Example: Search default strategies directories (within the default userdir).
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade list-strategies
|
freqtrade list-strategies
|
||||||
freqtrade list-hyperopts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Example: Search strategies and hyperopts directory within the userdir.
|
Example: Search strategies directory within the userdir.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
freqtrade list-strategies --userdir ~/.freqtrade/
|
freqtrade list-strategies --userdir ~/.freqtrade/
|
||||||
freqtrade list-hyperopts --userdir ~/.freqtrade/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Example: Search dedicated strategy path.
|
Example: Search dedicated strategy path.
|
||||||
@ -231,12 +164,6 @@ Example: Search dedicated strategy path.
|
|||||||
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
|
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
|
||||||
```
|
```
|
||||||
|
|
||||||
Example: Search dedicated hyperopt path.
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
freqtrade list-hyperopt --hyperopt-path ~/.freqtrade/hyperopts/
|
|
||||||
```
|
|
||||||
|
|
||||||
## List Exchanges
|
## List Exchanges
|
||||||
|
|
||||||
Use the `list-exchanges` subcommand to see the exchanges available for the bot.
|
Use the `list-exchanges` subcommand to see the exchanges available for the bot.
|
||||||
@ -354,7 +281,7 @@ bitmax True missing opt: fetchMyTrades
|
|||||||
bitmex False Various reasons.
|
bitmex False Various reasons.
|
||||||
bitpanda True
|
bitpanda True
|
||||||
bitso False missing: fetchOHLCV
|
bitso False missing: fetchOHLCV
|
||||||
bitstamp False Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983
|
bitstamp True missing opt: fetchTickers
|
||||||
bitstamp1 False missing: fetchOrder, fetchOHLCV
|
bitstamp1 False missing: fetchOrder, fetchOHLCV
|
||||||
bittrex True
|
bittrex True
|
||||||
bitvavo True
|
bitvavo True
|
||||||
@ -614,6 +541,42 @@ Show whitelist when using a [dynamic pairlist](plugins.md#pairlists).
|
|||||||
freqtrade test-pairlist --config config.json --quote USDT BTC
|
freqtrade test-pairlist --config config.json --quote USDT BTC
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Webserver mode
|
||||||
|
|
||||||
|
!!! Warning "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/config.json` or `config.json` 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
|
## List Hyperopt results
|
||||||
|
|
||||||
You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command.
|
You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command.
|
||||||
@ -702,7 +665,9 @@ You can show the details of any hyperoptimization epoch previously evaluated by
|
|||||||
usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[-d PATH] [--userdir PATH] [--best]
|
[-d PATH] [--userdir PATH] [--best]
|
||||||
[--profitable] [-n INT] [--print-json]
|
[--profitable] [-n INT] [--print-json]
|
||||||
[--hyperopt-filename PATH] [--no-header]
|
[--hyperopt-filename FILENAME] [--no-header]
|
||||||
|
[--disable-param-export]
|
||||||
|
[--breakdown {day,week,month} [{day,week,month} ...]]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
@ -714,6 +679,10 @@ optional arguments:
|
|||||||
Hyperopt result filename.Example: `--hyperopt-
|
Hyperopt result filename.Example: `--hyperopt-
|
||||||
filename=hyperopt_results_2020-09-27_16-20-48.pickle`
|
filename=hyperopt_results_2020-09-27_16-20-48.pickle`
|
||||||
--no-header Do not print epoch details header.
|
--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:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
@ -83,6 +83,7 @@ Possible parameters are:
|
|||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `current_rate`
|
* `current_rate`
|
||||||
|
* `buy_tag`
|
||||||
|
|
||||||
### Webhookbuycancel
|
### Webhookbuycancel
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ Possible parameters are:
|
|||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `current_rate`
|
* `current_rate`
|
||||||
|
* `buy_tag`
|
||||||
|
|
||||||
### Webhookbuyfill
|
### Webhookbuyfill
|
||||||
|
|
||||||
@ -115,6 +117,7 @@ Possible parameters are:
|
|||||||
* `stake_amount`
|
* `stake_amount`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
|
* `buy_tag`
|
||||||
|
|
||||||
### Webhooksell
|
### Webhooksell
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ git clone https://github.com/freqtrade/freqtrade.git
|
|||||||
|
|
||||||
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
|
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
|
||||||
|
|
||||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib‑0.4.20‑cp38‑cp38‑win_amd64.whl` (make sure to use the version matching your python version).
|
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.21-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
|
||||||
|
|
||||||
Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows.
|
Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows.
|
||||||
Other versions must be downloaded from the above link.
|
Other versions must be downloaded from the above link.
|
||||||
|
@ -16,7 +16,6 @@ dependencies:
|
|||||||
- cachetools
|
- cachetools
|
||||||
- requests
|
- requests
|
||||||
- urllib3
|
- urllib3
|
||||||
- wrapt
|
|
||||||
- jsonschema
|
- jsonschema
|
||||||
- TA-Lib
|
- TA-Lib
|
||||||
- tabulate
|
- tabulate
|
||||||
@ -64,7 +63,6 @@ dependencies:
|
|||||||
- py_find_1st
|
- py_find_1st
|
||||||
- tables
|
- tables
|
||||||
- pytest-random-order
|
- pytest-random-order
|
||||||
- flake8-type-annotations
|
|
||||||
- ccxt
|
- ccxt
|
||||||
- flake8-tidy-imports
|
- flake8-tidy-imports
|
||||||
- -e .
|
- -e .
|
||||||
|
@ -22,7 +22,7 @@ if __version__ == 'develop':
|
|||||||
# subprocess.check_output(
|
# subprocess.check_output(
|
||||||
# ['git', 'log', '--format="%h"', '-n 1'],
|
# ['git', 'log', '--format="%h"', '-n 1'],
|
||||||
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
|
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
|
||||||
except Exception:
|
except Exception: # pragma: no cover
|
||||||
# git not available, ignore
|
# git not available, ignore
|
||||||
try:
|
try:
|
||||||
# Try Fallback to freqtrade_commit file (created by CI while building docker image)
|
# Try Fallback to freqtrade_commit file (created by CI while building docker image)
|
||||||
|
@ -8,15 +8,17 @@ Note: Be careful with file-scoped imports in these subfiles.
|
|||||||
"""
|
"""
|
||||||
from freqtrade.commands.arguments import Arguments
|
from freqtrade.commands.arguments import Arguments
|
||||||
from freqtrade.commands.build_config_commands import start_new_config
|
from freqtrade.commands.build_config_commands import start_new_config
|
||||||
from freqtrade.commands.data_commands import (start_convert_data, start_download_data,
|
from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades,
|
||||||
start_list_data)
|
start_download_data, start_list_data)
|
||||||
from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui,
|
from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui,
|
||||||
start_new_hyperopt, start_new_strategy)
|
start_new_strategy)
|
||||||
from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show
|
from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show
|
||||||
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts,
|
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts,
|
||||||
start_list_markets, start_list_strategies,
|
start_list_markets, start_list_strategies,
|
||||||
start_list_timeframes, start_show_trades)
|
start_list_timeframes, start_show_trades)
|
||||||
from freqtrade.commands.optimize_commands import start_backtest_filter, start_backtesting, start_edge, start_hyperopt
|
from freqtrade.commands.optimize_commands import (start_backtest_filter, start_backtesting,
|
||||||
|
start_edge, start_hyperopt)
|
||||||
from freqtrade.commands.pairlist_commands import start_test_pairlist
|
from freqtrade.commands.pairlist_commands import start_test_pairlist
|
||||||
from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit
|
from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit
|
||||||
from freqtrade.commands.trade_commands import start_trading
|
from freqtrade.commands.trade_commands import start_trading
|
||||||
|
from freqtrade.commands.webserver_commands import start_webserver
|
||||||
|
@ -17,12 +17,15 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
|
|||||||
|
|
||||||
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
|
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
|
||||||
|
|
||||||
|
ARGS_WEBSERVER: List[str] = []
|
||||||
|
|
||||||
ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
||||||
"max_open_trades", "stake_amount", "fee", "pairs"]
|
"max_open_trades", "stake_amount", "fee", "pairs"]
|
||||||
|
|
||||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
||||||
"enable_protections", "dry_run_wallet",
|
"enable_protections", "dry_run_wallet", "timeframe_detail",
|
||||||
"strategy_list", "export", "exportfilename"]
|
"strategy_list", "export", "exportfilename",
|
||||||
|
"backtest_breakdown"]
|
||||||
|
|
||||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||||
"position_stacking", "use_max_market_positions",
|
"position_stacking", "use_max_market_positions",
|
||||||
@ -30,7 +33,8 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
|||||||
"epochs", "spaces", "print_all",
|
"epochs", "spaces", "print_all",
|
||||||
"print_colorized", "print_json", "hyperopt_jobs",
|
"print_colorized", "print_json", "hyperopt_jobs",
|
||||||
"hyperopt_random_state", "hyperopt_min_trades",
|
"hyperopt_random_state", "hyperopt_min_trades",
|
||||||
"hyperopt_loss"]
|
"hyperopt_loss", "disableparamexport",
|
||||||
|
"hyperopt_ignore_missing_space"]
|
||||||
|
|
||||||
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
||||||
|
|
||||||
@ -56,16 +60,16 @@ ARGS_BUILD_CONFIG = ["config"]
|
|||||||
|
|
||||||
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
||||||
|
|
||||||
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
|
|
||||||
|
|
||||||
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"]
|
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"]
|
||||||
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
|
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
|
||||||
|
|
||||||
|
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
|
||||||
|
|
||||||
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"]
|
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"]
|
||||||
|
|
||||||
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "timerange",
|
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive",
|
||||||
"download_trades", "exchange", "timeframes", "erase", "dataformat_ohlcv",
|
"timerange", "download_trades", "exchange", "timeframes",
|
||||||
"dataformat_trades"]
|
"erase", "dataformat_ohlcv", "dataformat_trades"]
|
||||||
|
|
||||||
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
||||||
"db_url", "trade_source", "export", "exportfilename",
|
"db_url", "trade_source", "export", "exportfilename",
|
||||||
@ -74,7 +78,7 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
|||||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||||
"trade_source", "timeframe", "plot_auto_open"]
|
"trade_source", "timeframe", "plot_auto_open"]
|
||||||
|
|
||||||
ARGS_INSTALL_UI = ["erase_ui_only"]
|
ARGS_INSTALL_UI = ["erase_ui_only", 'ui_version']
|
||||||
|
|
||||||
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
|
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
|
||||||
|
|
||||||
@ -88,14 +92,15 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
|
|||||||
"hyperoptexportfilename", "export_csv"]
|
"hyperoptexportfilename", "export_csv"]
|
||||||
|
|
||||||
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
||||||
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header"]
|
"print_json", "hyperoptexportfilename", "hyperopt_show_no_header",
|
||||||
|
"disableparamexport", "backtest_breakdown"]
|
||||||
|
|
||||||
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
|
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
|
||||||
"list-markets", "list-pairs", "list-strategies", "list-data",
|
"list-markets", "list-pairs", "list-strategies", "list-data",
|
||||||
"list-hyperopts", "hyperopt-list", "backtest-filter", "hyperopt-show",
|
"hyperopt-list", "hyperopt-show", "backtest-filter",
|
||||||
"plot-dataframe", "plot-profit", "show-trades"]
|
"plot-dataframe", "plot-profit", "show-trades", "trades-to-ohlcv"]
|
||||||
|
|
||||||
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"]
|
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-strategy"]
|
||||||
|
|
||||||
|
|
||||||
class Arguments:
|
class Arguments:
|
||||||
@ -171,14 +176,16 @@ class Arguments:
|
|||||||
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
|
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
|
||||||
self._build_args(optionlist=['version'], parser=self.parser)
|
self._build_args(optionlist=['version'], parser=self.parser)
|
||||||
|
|
||||||
from freqtrade.commands import (start_backtesting, start_backtest_filter, start_convert_data, start_create_userdir,
|
from freqtrade.commands import (
|
||||||
start_download_data, start_edge, start_hyperopt,
|
start_backtesting, start_backtest_filter, start_convert_data, start_convert_trades,
|
||||||
start_hyperopt_list, start_hyperopt_show, start_install_ui,
|
start_create_userdir, start_download_data, start_edge,
|
||||||
start_list_data, start_list_exchanges, start_list_hyperopts,
|
start_hyperopt, start_hyperopt_list, start_hyperopt_show,
|
||||||
start_list_markets, start_list_strategies,
|
start_install_ui, start_list_data, start_list_exchanges,
|
||||||
start_list_timeframes, start_new_config, start_new_hyperopt,
|
start_list_markets, start_list_strategies,
|
||||||
start_new_strategy, start_plot_dataframe, start_plot_profit,
|
start_list_timeframes, start_new_config, start_new_strategy,
|
||||||
start_show_trades, start_test_pairlist, start_trading)
|
start_plot_dataframe, start_plot_profit, start_show_trades,
|
||||||
|
start_test_pairlist, start_trading, start_webserver
|
||||||
|
)
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='command',
|
subparsers = self.parser.add_subparsers(dest='command',
|
||||||
# Use custom message when no subhandler is added
|
# Use custom message when no subhandler is added
|
||||||
@ -205,12 +212,6 @@ class Arguments:
|
|||||||
build_config_cmd.set_defaults(func=start_new_config)
|
build_config_cmd.set_defaults(func=start_new_config)
|
||||||
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
|
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
|
||||||
|
|
||||||
# add new-hyperopt subcommand
|
|
||||||
build_hyperopt_cmd = subparsers.add_parser('new-hyperopt',
|
|
||||||
help="Create new hyperopt")
|
|
||||||
build_hyperopt_cmd.set_defaults(func=start_new_hyperopt)
|
|
||||||
self._build_args(optionlist=ARGS_BUILD_HYPEROPT, parser=build_hyperopt_cmd)
|
|
||||||
|
|
||||||
# add new-strategy subcommand
|
# add new-strategy subcommand
|
||||||
build_strategy_cmd = subparsers.add_parser('new-strategy',
|
build_strategy_cmd = subparsers.add_parser('new-strategy',
|
||||||
help="Create new strategy")
|
help="Create new strategy")
|
||||||
@ -244,6 +245,15 @@ class Arguments:
|
|||||||
convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False))
|
convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False))
|
||||||
self._build_args(optionlist=ARGS_CONVERT_DATA, parser=convert_trade_data_cmd)
|
self._build_args(optionlist=ARGS_CONVERT_DATA, parser=convert_trade_data_cmd)
|
||||||
|
|
||||||
|
# Add trades-to-ohlcv subcommand
|
||||||
|
convert_trade_data_cmd = subparsers.add_parser(
|
||||||
|
'trades-to-ohlcv',
|
||||||
|
help='Convert trade data to OHLCV data.',
|
||||||
|
parents=[_common_parser],
|
||||||
|
)
|
||||||
|
convert_trade_data_cmd.set_defaults(func=start_convert_trades)
|
||||||
|
self._build_args(optionlist=ARGS_CONVERT_TRADES, parser=convert_trade_data_cmd)
|
||||||
|
|
||||||
# Add list-data subcommand
|
# Add list-data subcommand
|
||||||
list_data_cmd = subparsers.add_parser(
|
list_data_cmd = subparsers.add_parser(
|
||||||
'list-data',
|
'list-data',
|
||||||
@ -308,15 +318,6 @@ class Arguments:
|
|||||||
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
|
||||||
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
|
||||||
|
|
||||||
# Add list-hyperopts subcommand
|
|
||||||
list_hyperopts_cmd = subparsers.add_parser(
|
|
||||||
'list-hyperopts',
|
|
||||||
help='Print available hyperopt classes.',
|
|
||||||
parents=[_common_parser],
|
|
||||||
)
|
|
||||||
list_hyperopts_cmd.set_defaults(func=start_list_hyperopts)
|
|
||||||
self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopts_cmd)
|
|
||||||
|
|
||||||
# Add list-markets subcommand
|
# Add list-markets subcommand
|
||||||
list_markets_cmd = subparsers.add_parser(
|
list_markets_cmd = subparsers.add_parser(
|
||||||
'list-markets',
|
'list-markets',
|
||||||
@ -395,3 +396,9 @@ class Arguments:
|
|||||||
)
|
)
|
||||||
plot_profit_cmd.set_defaults(func=start_plot_profit)
|
plot_profit_cmd.set_defaults(func=start_plot_profit)
|
||||||
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
||||||
|
|
||||||
|
# Add webserver subcommand
|
||||||
|
webserver_cmd = subparsers.add_parser('webserver', help='Webserver module.',
|
||||||
|
parents=[_common_parser])
|
||||||
|
webserver_cmd.set_defaults(func=start_webserver)
|
||||||
|
self._build_args(optionlist=ARGS_WEBSERVER, parser=webserver_cmd)
|
||||||
|
@ -61,21 +61,27 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "stake_currency",
|
"name": "stake_currency",
|
||||||
"message": "Please insert your stake currency:",
|
"message": "Please insert your stake currency:",
|
||||||
"default": 'BTC',
|
"default": 'USDT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "stake_amount",
|
"name": "stake_amount",
|
||||||
"message": "Please insert your stake amount:",
|
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "0.01",
|
"default": "100",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "max_open_trades",
|
"name": "max_open_trades",
|
||||||
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "3",
|
"default": "3",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@ -99,6 +105,8 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
"bittrex",
|
"bittrex",
|
||||||
"kraken",
|
"kraken",
|
||||||
"ftx",
|
"ftx",
|
||||||
|
"kucoin",
|
||||||
|
"gateio",
|
||||||
Separator(),
|
Separator(),
|
||||||
"other",
|
"other",
|
||||||
],
|
],
|
||||||
@ -122,6 +130,12 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
"message": "Insert Exchange Secret",
|
"message": "Insert Exchange Secret",
|
||||||
"when": lambda x: not x['dry_run']
|
"when": lambda x: not x['dry_run']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_key_password",
|
||||||
|
"message": "Insert Exchange API Key password",
|
||||||
|
"when": lambda x: not x['dry_run'] and x['exchange_name'] == 'kucoin'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "confirm",
|
"type": "confirm",
|
||||||
"name": "telegram",
|
"name": "telegram",
|
||||||
@ -149,7 +163,8 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "api_server_listen_addr",
|
"name": "api_server_listen_addr",
|
||||||
"message": "Insert Api server Listen Address (best left untouched default!)",
|
"message": ("Insert Api server Listen Address (0.0.0.0 for docker, "
|
||||||
|
"otherwise best left untouched)"),
|
||||||
"default": "127.0.0.1",
|
"default": "127.0.0.1",
|
||||||
"when": lambda x: x['api_server']
|
"when": lambda x: x['api_server']
|
||||||
},
|
},
|
||||||
@ -183,7 +198,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
|||||||
"""
|
"""
|
||||||
Applies selections to the template and writes the result to config_path
|
Applies selections to the template and writes the result to config_path
|
||||||
:param config_path: Path object for new config file. Should not exist yet
|
:param config_path: Path object for new config file. Should not exist yet
|
||||||
:param selecions: Dict containing selections taken by the user.
|
:param selections: Dict containing selections taken by the user.
|
||||||
"""
|
"""
|
||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
try:
|
try:
|
||||||
@ -193,7 +208,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
|||||||
selections['exchange'] = render_template(
|
selections['exchange'] = render_template(
|
||||||
templatefile=f"subtemplates/exchange_{exchange_template}.j2",
|
templatefile=f"subtemplates/exchange_{exchange_template}.j2",
|
||||||
arguments=selections
|
arguments=selections
|
||||||
)
|
)
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
selections['exchange'] = render_template(
|
selections['exchange'] = render_template(
|
||||||
templatefile="subtemplates/exchange_generic.j2",
|
templatefile="subtemplates/exchange_generic.j2",
|
||||||
@ -213,7 +228,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
|||||||
def start_new_config(args: Dict[str, Any]) -> None:
|
def start_new_config(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Create a new strategy from a template
|
Create a new strategy from a template
|
||||||
Asking the user questions to fill out the templateaccordingly.
|
Asking the user questions to fill out the template accordingly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_path = Path(args['config'][0])
|
config_path = Path(args['config'][0])
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Definition of cli arguments used in arguments.py
|
Definition of cli arguments used in arguments.py
|
||||||
"""
|
"""
|
||||||
from argparse import ArgumentTypeError
|
from argparse import SUPPRESS, ArgumentTypeError
|
||||||
|
|
||||||
from freqtrade import __version__, constants
|
from freqtrade import __version__, constants
|
||||||
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN
|
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN
|
||||||
@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
help='Override the value of the `stake_amount` configuration setting.',
|
help='Override the value of the `stake_amount` configuration setting.',
|
||||||
),
|
),
|
||||||
# Backtesting
|
# Backtesting
|
||||||
|
"timeframe_detail": Arg(
|
||||||
|
'--timeframe-detail',
|
||||||
|
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
|
||||||
|
),
|
||||||
"position_stacking": Arg(
|
"position_stacking": Arg(
|
||||||
'--eps', '--enable-position-stacking',
|
'--eps', '--enable-position-stacking',
|
||||||
help='Allow buying the same pair multiple times (position stacking).',
|
help='Allow buying the same pair multiple times (position stacking).',
|
||||||
@ -162,13 +166,14 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
'Please note that ticker-interval needs to be set either in config '
|
'Please note that ticker-interval needs to be set either in config '
|
||||||
'or via command line. When using this together with `--export trades`, '
|
'or via command line. When using this together with `--export trades`, '
|
||||||
'the strategy-name is injected into the filename '
|
'the strategy-name is injected into the filename '
|
||||||
'(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`',
|
'(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
),
|
),
|
||||||
"export": Arg(
|
"export": Arg(
|
||||||
'--export',
|
'--export',
|
||||||
help='Export backtest results, argument are: trades. '
|
help='Export backtest results (default: trades).',
|
||||||
'Example: `--export=trades`',
|
choices=constants.EXPORT_OPTIONS,
|
||||||
|
|
||||||
),
|
),
|
||||||
"exportfilename": Arg(
|
"exportfilename": Arg(
|
||||||
'--export-filename',
|
'--export-filename',
|
||||||
@ -177,12 +182,23 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
|
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
),
|
),
|
||||||
|
"disableparamexport": Arg(
|
||||||
|
'--disable-param-export',
|
||||||
|
help="Disable automatic hyperopt parameter export.",
|
||||||
|
action='store_true',
|
||||||
|
),
|
||||||
"fee": Arg(
|
"fee": Arg(
|
||||||
'--fee',
|
'--fee',
|
||||||
help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
|
help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
|
||||||
type=float,
|
type=float,
|
||||||
metavar='FLOAT',
|
metavar='FLOAT',
|
||||||
),
|
),
|
||||||
|
"backtest_breakdown": Arg(
|
||||||
|
'--breakdown',
|
||||||
|
help='Show backtesting breakdown per [day, week, month].',
|
||||||
|
nargs='+',
|
||||||
|
choices=constants.BACKTEST_BREAKDOWNS
|
||||||
|
),
|
||||||
# Edge
|
# Edge
|
||||||
"stoploss_range": Arg(
|
"stoploss_range": Arg(
|
||||||
'--stoplosses',
|
'--stoplosses',
|
||||||
@ -193,13 +209,13 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
# Hyperopt
|
# Hyperopt
|
||||||
"hyperopt": Arg(
|
"hyperopt": Arg(
|
||||||
'--hyperopt',
|
'--hyperopt',
|
||||||
help='Specify hyperopt class name which will be used by the bot.',
|
help=SUPPRESS,
|
||||||
metavar='NAME',
|
metavar='NAME',
|
||||||
required=False,
|
required=False,
|
||||||
),
|
),
|
||||||
"hyperopt_path": Arg(
|
"hyperopt_path": Arg(
|
||||||
'--hyperopt-path',
|
'--hyperopt-path',
|
||||||
help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.',
|
help='Specify additional lookup path for Hyperopt Loss functions.',
|
||||||
metavar='PATH',
|
metavar='PATH',
|
||||||
),
|
),
|
||||||
"backtest_path": Arg(
|
"backtest_path": Arg(
|
||||||
@ -217,7 +233,7 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
"spaces": Arg(
|
"spaces": Arg(
|
||||||
'--spaces',
|
'--spaces',
|
||||||
help='Specify which parameters to hyperopt. Space-separated list.',
|
help='Specify which parameters to hyperopt. Space-separated list.',
|
||||||
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'default'],
|
choices=['all', 'buy', 'sell', 'roi', 'stoploss', 'trailing', 'protection', 'default'],
|
||||||
nargs='+',
|
nargs='+',
|
||||||
default='default',
|
default='default',
|
||||||
),
|
),
|
||||||
@ -350,6 +366,11 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
type=check_int_positive,
|
type=check_int_positive,
|
||||||
metavar='INT',
|
metavar='INT',
|
||||||
),
|
),
|
||||||
|
"include_inactive": Arg(
|
||||||
|
'--include-inactive-pairs',
|
||||||
|
help='Also download data from inactive pairs.',
|
||||||
|
action='store_true',
|
||||||
|
),
|
||||||
"new_pairs_days": Arg(
|
"new_pairs_days": Arg(
|
||||||
'--new-pairs-days',
|
'--new-pairs-days',
|
||||||
help='Download data of new pairs for given number of days. Default: `%(default)s`.',
|
help='Download data of new pairs for given number of days. Default: `%(default)s`.',
|
||||||
@ -376,12 +397,12 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
),
|
),
|
||||||
"dataformat_ohlcv": Arg(
|
"dataformat_ohlcv": Arg(
|
||||||
'--data-format-ohlcv',
|
'--data-format-ohlcv',
|
||||||
help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).',
|
help='Storage format for downloaded candle (OHLCV) data. (default: `json`).',
|
||||||
choices=constants.AVAILABLE_DATAHANDLERS,
|
choices=constants.AVAILABLE_DATAHANDLERS,
|
||||||
),
|
),
|
||||||
"dataformat_trades": Arg(
|
"dataformat_trades": Arg(
|
||||||
'--data-format-trades',
|
'--data-format-trades',
|
||||||
help='Storage format for downloaded trades data. (default: `%(default)s`).',
|
help='Storage format for downloaded trades data. (default: `jsongz`).',
|
||||||
choices=constants.AVAILABLE_DATAHANDLERS,
|
choices=constants.AVAILABLE_DATAHANDLERS,
|
||||||
),
|
),
|
||||||
"exchange": Arg(
|
"exchange": Arg(
|
||||||
@ -409,6 +430,12 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
default=False,
|
default=False,
|
||||||
),
|
),
|
||||||
|
"ui_version": Arg(
|
||||||
|
'--ui-version',
|
||||||
|
help=('Specify a specific version of FreqUI to install. '
|
||||||
|
'Not specifying this installs the latest version.'),
|
||||||
|
type=str,
|
||||||
|
),
|
||||||
# Templating options
|
# Templating options
|
||||||
"template": Arg(
|
"template": Arg(
|
||||||
'--template',
|
'--template',
|
||||||
@ -547,4 +574,10 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
help='Do not print epoch details header.',
|
help='Do not print epoch details header.',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
),
|
),
|
||||||
|
"hyperopt_ignore_missing_space": Arg(
|
||||||
|
"--ignore-missing-spaces", "--ignore-unparameterized-spaces",
|
||||||
|
help=("Suppress errors for any requested Hyperopt spaces "
|
||||||
|
"that do not contain any parameters."),
|
||||||
|
action="store_true",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_oh
|
|||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.exchange.exchange import market_is_active
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
@ -47,10 +48,13 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
# Init exchange
|
# Init exchange
|
||||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
||||||
# Manual validations of relevant settings
|
markets = [p for p, m in exchange.markets.items() if market_is_active(m)
|
||||||
exchange.validate_pairs(config['pairs'])
|
or config.get('include_inactive')]
|
||||||
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
|
expanded_pairs = expand_pairlist(config['pairs'], markets)
|
||||||
|
|
||||||
|
# Manual validations of relevant settings
|
||||||
|
if not config['exchange'].get('skip_pair_validation', False):
|
||||||
|
exchange.validate_pairs(expanded_pairs)
|
||||||
logger.info(f"About to download pairs: {expanded_pairs}, "
|
logger.info(f"About to download pairs: {expanded_pairs}, "
|
||||||
f"intervals: {config['timeframes']} to {config['datadir']}")
|
f"intervals: {config['timeframes']} to {config['datadir']}")
|
||||||
|
|
||||||
@ -88,6 +92,41 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
f"on exchange {exchange.name}.")
|
f"on exchange {exchange.name}.")
|
||||||
|
|
||||||
|
|
||||||
|
def start_convert_trades(args: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
|
timerange = TimeRange()
|
||||||
|
|
||||||
|
# Remove stake-currency to skip checks which are not relevant for datadownload
|
||||||
|
config['stake_currency'] = ''
|
||||||
|
|
||||||
|
if 'pairs' not in config:
|
||||||
|
raise OperationalException(
|
||||||
|
"Downloading data requires a list of pairs. "
|
||||||
|
"Please check the documentation on how to configure this.")
|
||||||
|
|
||||||
|
# Init exchange
|
||||||
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
||||||
|
# Manual validations of relevant settings
|
||||||
|
if not config['exchange'].get('skip_pair_validation', False):
|
||||||
|
exchange.validate_pairs(config['pairs'])
|
||||||
|
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
|
||||||
|
|
||||||
|
logger.info(f"About to Convert pairs: {expanded_pairs}, "
|
||||||
|
f"intervals: {config['timeframes']} to {config['datadir']}")
|
||||||
|
|
||||||
|
for timeframe in config['timeframes']:
|
||||||
|
exchange.validate_timeframes(timeframe)
|
||||||
|
# Convert downloaded trade data to different timeframes
|
||||||
|
convert_trades_to_ohlcv(
|
||||||
|
pairs=expanded_pairs, timeframes=config['timeframes'],
|
||||||
|
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
||||||
|
data_format_ohlcv=config['dataformat_ohlcv'],
|
||||||
|
data_format_trades=config['dataformat_trades'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
|
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
|
||||||
"""
|
"""
|
||||||
Convert data from one format to another
|
Convert data from one format to another
|
||||||
|
@ -7,7 +7,7 @@ import requests
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
||||||
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
|
from freqtrade.constants import USERPATH_STRATEGIES
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.misc import render_template, render_template_with_fallback
|
from freqtrade.misc import render_template, render_template_with_fallback
|
||||||
@ -38,15 +38,15 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
|
|||||||
indicators = render_template_with_fallback(
|
indicators = render_template_with_fallback(
|
||||||
templatefile=f"subtemplates/indicators_{subtemplate}.j2",
|
templatefile=f"subtemplates/indicators_{subtemplate}.j2",
|
||||||
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2",
|
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2",
|
||||||
)
|
)
|
||||||
buy_trend = render_template_with_fallback(
|
buy_trend = render_template_with_fallback(
|
||||||
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",
|
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2",
|
||||||
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2",
|
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2",
|
||||||
)
|
)
|
||||||
sell_trend = render_template_with_fallback(
|
sell_trend = render_template_with_fallback(
|
||||||
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",
|
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2",
|
||||||
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2",
|
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2",
|
||||||
)
|
)
|
||||||
plot_config = render_template_with_fallback(
|
plot_config = render_template_with_fallback(
|
||||||
templatefile=f"subtemplates/plot_config_{subtemplate}.j2",
|
templatefile=f"subtemplates/plot_config_{subtemplate}.j2",
|
||||||
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2",
|
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2",
|
||||||
@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if "strategy" in args and args["strategy"]:
|
if "strategy" in args and args["strategy"]:
|
||||||
if args["strategy"] == "DefaultStrategy":
|
|
||||||
raise OperationalException("DefaultStrategy is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
||||||
|
|
||||||
@ -89,58 +87,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
raise OperationalException("`new-strategy` requires --strategy to be set.")
|
raise OperationalException("`new-strategy` requires --strategy to be set.")
|
||||||
|
|
||||||
|
|
||||||
def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: str) -> None:
|
|
||||||
"""
|
|
||||||
Deploys a new hyperopt template to hyperopt_path
|
|
||||||
"""
|
|
||||||
fallback = 'full'
|
|
||||||
buy_guards = render_template_with_fallback(
|
|
||||||
templatefile=f"subtemplates/hyperopt_buy_guards_{subtemplate}.j2",
|
|
||||||
templatefallbackfile=f"subtemplates/hyperopt_buy_guards_{fallback}.j2",
|
|
||||||
)
|
|
||||||
sell_guards = render_template_with_fallback(
|
|
||||||
templatefile=f"subtemplates/hyperopt_sell_guards_{subtemplate}.j2",
|
|
||||||
templatefallbackfile=f"subtemplates/hyperopt_sell_guards_{fallback}.j2",
|
|
||||||
)
|
|
||||||
buy_space = render_template_with_fallback(
|
|
||||||
templatefile=f"subtemplates/hyperopt_buy_space_{subtemplate}.j2",
|
|
||||||
templatefallbackfile=f"subtemplates/hyperopt_buy_space_{fallback}.j2",
|
|
||||||
)
|
|
||||||
sell_space = render_template_with_fallback(
|
|
||||||
templatefile=f"subtemplates/hyperopt_sell_space_{subtemplate}.j2",
|
|
||||||
templatefallbackfile=f"subtemplates/hyperopt_sell_space_{fallback}.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
strategy_text = render_template(templatefile='base_hyperopt.py.j2',
|
|
||||||
arguments={"hyperopt": hyperopt_name,
|
|
||||||
"buy_guards": buy_guards,
|
|
||||||
"sell_guards": sell_guards,
|
|
||||||
"buy_space": buy_space,
|
|
||||||
"sell_space": sell_space,
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.info(f"Writing hyperopt to `{hyperopt_path}`.")
|
|
||||||
hyperopt_path.write_text(strategy_text)
|
|
||||||
|
|
||||||
|
|
||||||
def start_new_hyperopt(args: Dict[str, Any]) -> None:
|
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
|
||||||
|
|
||||||
if 'hyperopt' in args and args['hyperopt']:
|
|
||||||
if args['hyperopt'] == 'DefaultHyperopt':
|
|
||||||
raise OperationalException("DefaultHyperopt is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
|
|
||||||
|
|
||||||
if new_path.exists():
|
|
||||||
raise OperationalException(f"`{new_path}` already exists. "
|
|
||||||
"Please choose another Hyperopt Name.")
|
|
||||||
deploy_new_hyperopt(args['hyperopt'], new_path, args['template'])
|
|
||||||
else:
|
|
||||||
raise OperationalException("`new-hyperopt` requires --hyperopt to be set.")
|
|
||||||
|
|
||||||
|
|
||||||
def clean_ui_subdir(directory: Path):
|
def clean_ui_subdir(directory: Path):
|
||||||
if directory.is_dir():
|
if directory.is_dir():
|
||||||
logger.info("Removing UI directory content.")
|
logger.info("Removing UI directory content.")
|
||||||
@ -182,7 +128,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
|
|||||||
f.write(version)
|
f.write(version)
|
||||||
|
|
||||||
|
|
||||||
def get_ui_download_url() -> Tuple[str, str]:
|
def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
|
||||||
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
|
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
|
||||||
# Get base UI Repo path
|
# Get base UI Repo path
|
||||||
|
|
||||||
@ -190,8 +136,16 @@ def get_ui_download_url() -> Tuple[str, str]:
|
|||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
r = resp.json()
|
r = resp.json()
|
||||||
|
|
||||||
latest_version = r[0]['name']
|
if version:
|
||||||
assets = r[0].get('assets', [])
|
tmp = [x for x in r if x['name'] == version]
|
||||||
|
if tmp:
|
||||||
|
latest_version = tmp[0]['name']
|
||||||
|
assets = tmp[0].get('assets', [])
|
||||||
|
else:
|
||||||
|
raise ValueError("UI-Version not found.")
|
||||||
|
else:
|
||||||
|
latest_version = r[0]['name']
|
||||||
|
assets = r[0].get('assets', [])
|
||||||
dl_url = ''
|
dl_url = ''
|
||||||
if assets and len(assets) > 0:
|
if assets and len(assets) > 0:
|
||||||
dl_url = assets[0]['browser_download_url']
|
dl_url = assets[0]['browser_download_url']
|
||||||
@ -210,7 +164,7 @@ def start_install_ui(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
|
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
|
||||||
# First make sure the assets are removed.
|
# First make sure the assets are removed.
|
||||||
dl_url, latest_version = get_ui_download_url()
|
dl_url, latest_version = get_ui_download_url(args.get('ui_version'))
|
||||||
|
|
||||||
curr_version = read_ui_version(dest_folder)
|
curr_version = read_ui_version(dest_folder)
|
||||||
if curr_version == latest_version and not args.get('erase_ui_only'):
|
if curr_version == latest_version and not args.get('erase_ui_only'):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict
|
||||||
|
|
||||||
from colorama import init as colorama_init
|
from colorama import init as colorama_init
|
||||||
|
|
||||||
@ -28,30 +28,12 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
no_details = config.get('hyperopt_list_no_details', False)
|
no_details = config.get('hyperopt_list_no_details', False)
|
||||||
no_header = False
|
no_header = False
|
||||||
|
|
||||||
filteroptions = {
|
|
||||||
'only_best': config.get('hyperopt_list_best', False),
|
|
||||||
'only_profitable': config.get('hyperopt_list_profitable', False),
|
|
||||||
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
|
|
||||||
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
|
|
||||||
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
|
|
||||||
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
|
|
||||||
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
|
|
||||||
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
|
|
||||||
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
|
|
||||||
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None),
|
|
||||||
'filter_min_objective': config.get('hyperopt_list_min_objective', None),
|
|
||||||
'filter_max_objective': config.get('hyperopt_list_max_objective', None),
|
|
||||||
}
|
|
||||||
|
|
||||||
results_file = get_latest_hyperopt_file(
|
results_file = get_latest_hyperopt_file(
|
||||||
config['user_data_dir'] / 'hyperopt_results',
|
config['user_data_dir'] / 'hyperopt_results',
|
||||||
config.get('hyperoptexportfilename'))
|
config.get('hyperoptexportfilename'))
|
||||||
|
|
||||||
# Previous evaluations
|
# Previous evaluations
|
||||||
epochs = HyperoptTools.load_previous_results(results_file)
|
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
|
||||||
total_epochs = len(epochs)
|
|
||||||
|
|
||||||
epochs = hyperopt_filter_epochs(epochs, filteroptions)
|
|
||||||
|
|
||||||
if print_colorized:
|
if print_colorized:
|
||||||
colorama_init(autoreset=True)
|
colorama_init(autoreset=True)
|
||||||
@ -59,7 +41,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
if not export_csv:
|
if not export_csv:
|
||||||
try:
|
try:
|
||||||
print(HyperoptTools.get_result_table(config, epochs, total_epochs,
|
print(HyperoptTools.get_result_table(config, epochs, total_epochs,
|
||||||
not filteroptions['only_best'],
|
not config.get('hyperopt_list_best', False),
|
||||||
print_colorized, 0))
|
print_colorized, 0))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('User interrupted..')
|
print('User interrupted..')
|
||||||
@ -71,7 +53,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if epochs and export_csv:
|
if epochs and export_csv:
|
||||||
HyperoptTools.export_csv_file(
|
HyperoptTools.export_csv_file(
|
||||||
config, epochs, total_epochs, not filteroptions['only_best'], export_csv
|
config, epochs, export_csv
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -91,26 +73,9 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
n = config.get('hyperopt_show_index', -1)
|
n = config.get('hyperopt_show_index', -1)
|
||||||
|
|
||||||
filteroptions = {
|
|
||||||
'only_best': config.get('hyperopt_list_best', False),
|
|
||||||
'only_profitable': config.get('hyperopt_list_profitable', False),
|
|
||||||
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
|
|
||||||
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
|
|
||||||
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
|
|
||||||
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
|
|
||||||
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
|
|
||||||
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
|
|
||||||
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
|
|
||||||
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None),
|
|
||||||
'filter_min_objective': config.get('hyperopt_list_min_objective', None),
|
|
||||||
'filter_max_objective': config.get('hyperopt_list_max_objective', None)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Previous evaluations
|
# Previous evaluations
|
||||||
epochs = HyperoptTools.load_previous_results(results_file)
|
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
|
||||||
total_epochs = len(epochs)
|
|
||||||
|
|
||||||
epochs = hyperopt_filter_epochs(epochs, filteroptions)
|
|
||||||
filtered_epochs = len(epochs)
|
filtered_epochs = len(epochs)
|
||||||
|
|
||||||
if n > filtered_epochs:
|
if n > filtered_epochs:
|
||||||
@ -129,143 +94,11 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
metrics = val['results_metrics']
|
metrics = val['results_metrics']
|
||||||
if 'strategy_name' in metrics:
|
if 'strategy_name' in metrics:
|
||||||
show_backtest_result(metrics['strategy_name'], metrics,
|
strategy_name = metrics['strategy_name']
|
||||||
metrics['stake_currency'])
|
show_backtest_result(strategy_name, metrics,
|
||||||
|
metrics['stake_currency'], config.get('backtest_breakdown', []))
|
||||||
|
|
||||||
|
HyperoptTools.try_export_params(config, strategy_name, val)
|
||||||
|
|
||||||
HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header,
|
HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header,
|
||||||
header_str="Epoch details")
|
header_str="Epoch details")
|
||||||
|
|
||||||
|
|
||||||
def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
|
|
||||||
"""
|
|
||||||
Filter our items from the list of hyperopt results
|
|
||||||
TODO: after 2021.5 remove all "legacy" mode queries.
|
|
||||||
"""
|
|
||||||
if filteroptions['only_best']:
|
|
||||||
epochs = [x for x in epochs if x['is_best']]
|
|
||||||
if filteroptions['only_profitable']:
|
|
||||||
epochs = [x for x in epochs if x['results_metrics'].get(
|
|
||||||
'profit', x['results_metrics'].get('profit_total', 0)) > 0]
|
|
||||||
|
|
||||||
epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions)
|
|
||||||
|
|
||||||
epochs = _hyperopt_filter_epochs_duration(epochs, filteroptions)
|
|
||||||
|
|
||||||
epochs = _hyperopt_filter_epochs_profit(epochs, filteroptions)
|
|
||||||
|
|
||||||
epochs = _hyperopt_filter_epochs_objective(epochs, filteroptions)
|
|
||||||
|
|
||||||
logger.info(f"{len(epochs)} " +
|
|
||||||
("best " if filteroptions['only_best'] else "") +
|
|
||||||
("profitable " if filteroptions['only_profitable'] else "") +
|
|
||||||
"epochs found.")
|
|
||||||
return epochs
|
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int):
|
|
||||||
"""
|
|
||||||
Filter epochs with trade-counts > trades
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'trade_count', x['results_metrics'].get('total_trades', 0)
|
|
||||||
) > trade_count
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List:
|
|
||||||
|
|
||||||
if filteroptions['filter_min_trades'] > 0:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, filteroptions['filter_min_trades'])
|
|
||||||
|
|
||||||
if filteroptions['filter_max_trades'] > 0:
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'trade_count', x['results_metrics'].get('total_trades')
|
|
||||||
) < filteroptions['filter_max_trades']
|
|
||||||
]
|
|
||||||
return epochs
|
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
|
|
||||||
|
|
||||||
def get_duration_value(x):
|
|
||||||
# Duration in minutes ...
|
|
||||||
if 'duration' in x['results_metrics']:
|
|
||||||
return x['results_metrics']['duration']
|
|
||||||
else:
|
|
||||||
# New mode
|
|
||||||
if 'holding_avg_s' in x['results_metrics']:
|
|
||||||
avg = x['results_metrics']['holding_avg_s']
|
|
||||||
return avg // 60
|
|
||||||
raise OperationalException(
|
|
||||||
"Holding-average not available. Please omit the filter on average time, "
|
|
||||||
"or rerun hyperopt with this version")
|
|
||||||
|
|
||||||
if filteroptions['filter_min_avg_time'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if get_duration_value(x) > filteroptions['filter_min_avg_time']
|
|
||||||
]
|
|
||||||
if filteroptions['filter_max_avg_time'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if get_duration_value(x) < filteroptions['filter_max_avg_time']
|
|
||||||
]
|
|
||||||
|
|
||||||
return epochs
|
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
|
|
||||||
|
|
||||||
if filteroptions['filter_min_avg_profit'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100
|
|
||||||
) > filteroptions['filter_min_avg_profit']
|
|
||||||
]
|
|
||||||
if filteroptions['filter_max_avg_profit'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100
|
|
||||||
) < filteroptions['filter_max_avg_profit']
|
|
||||||
]
|
|
||||||
if filteroptions['filter_min_total_profit'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'profit', x['results_metrics'].get('profit_total_abs', 0)
|
|
||||||
) > filteroptions['filter_min_total_profit']
|
|
||||||
]
|
|
||||||
if filteroptions['filter_max_total_profit'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
epochs = [
|
|
||||||
x for x in epochs
|
|
||||||
if x['results_metrics'].get(
|
|
||||||
'profit', x['results_metrics'].get('profit_total_abs', 0)
|
|
||||||
) < filteroptions['filter_max_total_profit']
|
|
||||||
]
|
|
||||||
return epochs
|
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_epochs_objective(epochs: List, filteroptions: dict) -> List:
|
|
||||||
|
|
||||||
if filteroptions['filter_min_objective'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
|
|
||||||
epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']]
|
|
||||||
if filteroptions['filter_max_objective'] is not None:
|
|
||||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
|
||||||
|
|
||||||
epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']]
|
|
||||||
|
|
||||||
return epochs
|
|
||||||
|
@ -10,11 +10,11 @@ from colorama import init as colorama_init
|
|||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
|
from freqtrade.constants import USERPATH_STRATEGIES
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import market_is_active, validate_exchanges
|
from freqtrade.exchange import market_is_active, validate_exchanges
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import parse_db_uri_for_logging, plural
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
|
|
||||||
|
|
||||||
@ -92,25 +92,6 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
|
|||||||
_print_objs_tabular(strategy_objs, config.get('print_colorized', False))
|
_print_objs_tabular(strategy_objs, config.get('print_colorized', False))
|
||||||
|
|
||||||
|
|
||||||
def start_list_hyperopts(args: Dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
Print files with HyperOpt custom classes available in the directory
|
|
||||||
"""
|
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
|
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
|
||||||
|
|
||||||
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS))
|
|
||||||
hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column'])
|
|
||||||
# Sort alphabetically
|
|
||||||
hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name'])
|
|
||||||
|
|
||||||
if args['print_one_column']:
|
|
||||||
print('\n'.join([s['name'] for s in hyperopt_objs]))
|
|
||||||
else:
|
|
||||||
_print_objs_tabular(hyperopt_objs, config.get('print_colorized', False))
|
|
||||||
|
|
||||||
|
|
||||||
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print timeframes available on Exchange
|
Print timeframes available on Exchange
|
||||||
@ -225,7 +206,7 @@ def start_show_trades(args: Dict[str, Any]) -> None:
|
|||||||
if 'db_url' not in config:
|
if 'db_url' not in config:
|
||||||
raise OperationalException("--db-url is required for this command.")
|
raise OperationalException("--db-url is required for this command.")
|
||||||
|
|
||||||
logger.info(f'Using DB: "{config["db_url"]}"')
|
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
||||||
init_db(config['db_url'], clean_open_orders=False)
|
init_db(config['db_url'], clean_open_orders=False)
|
||||||
tfilter = []
|
tfilter = []
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
"""
|
"""
|
||||||
Prepare the configuration for the Hyperopt module
|
Prepare the configuration for the Hyperopt module
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
|
:param method: Bot running mode
|
||||||
:return: Configuration
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
config = setup_utils_configuration(args, method)
|
config = setup_utils_configuration(args, method)
|
||||||
|
15
freqtrade/commands/webserver_commands.py
Normal file
15
freqtrade/commands/webserver_commands.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
|
|
||||||
|
def start_webserver(args: Dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Main entry point for webserver mode
|
||||||
|
"""
|
||||||
|
from freqtrade.configuration import Configuration
|
||||||
|
from freqtrade.rpc.api_server import ApiServer
|
||||||
|
|
||||||
|
# Initialize configuration
|
||||||
|
config = Configuration(args, RunMode.WEBSERVER).get_config()
|
||||||
|
ApiServer(config, standalone=True)
|
19
freqtrade/configuration/PeriodicCache.py
Normal file
19
freqtrade/configuration/PeriodicCache.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from cachetools.ttl import TTLCache
|
||||||
|
|
||||||
|
|
||||||
|
class PeriodicCache(TTLCache):
|
||||||
|
"""
|
||||||
|
Special cache that expires at "straight" times
|
||||||
|
A timer with ttl of 3600 (1h) will expire at every full hour (:00).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, maxsize, ttl, getsizeof=None):
|
||||||
|
def local_timer():
|
||||||
|
ts = datetime.now(timezone.utc).timestamp()
|
||||||
|
offset = (ts % ttl)
|
||||||
|
return ts - offset
|
||||||
|
|
||||||
|
# Init with smlight offset
|
||||||
|
super().__init__(maxsize=maxsize, ttl=ttl-1e-5, timer=local_timer, getsizeof=getsizeof)
|
@ -1,7 +1,8 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
|
||||||
from freqtrade.configuration.check_exchange import check_exchange, remove_credentials
|
from freqtrade.configuration.check_exchange import check_exchange
|
||||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.configuration.config_validation import validate_config_consistency
|
from freqtrade.configuration.config_validation import validate_config_consistency
|
||||||
from freqtrade.configuration.configuration import Configuration
|
from freqtrade.configuration.configuration import Configuration
|
||||||
|
from freqtrade.configuration.PeriodicCache import PeriodicCache
|
||||||
from freqtrade.configuration.timerange import TimeRange
|
from freqtrade.configuration.timerange import TimeRange
|
||||||
|
@ -10,19 +10,6 @@ from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def remove_credentials(config: Dict[str, Any]) -> None:
|
|
||||||
"""
|
|
||||||
Removes exchange keys from the configuration and specifies dry-run
|
|
||||||
Used for backtesting / hyperopt / edge and utils.
|
|
||||||
Modifies the input dict!
|
|
||||||
"""
|
|
||||||
config['exchange']['key'] = ''
|
|
||||||
config['exchange']['secret'] = ''
|
|
||||||
config['exchange']['password'] = ''
|
|
||||||
config['exchange']['uid'] = ''
|
|
||||||
config['dry_run'] = True
|
|
||||||
|
|
||||||
|
|
||||||
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
|
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if the exchange name in the config file is supported by Freqtrade
|
Check if the exchange name in the config file is supported by Freqtrade
|
||||||
@ -51,10 +38,10 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
|
|||||||
|
|
||||||
if not is_exchange_known_ccxt(exchange):
|
if not is_exchange_known_ccxt(exchange):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange "{exchange}" is not known to the ccxt library '
|
f'Exchange "{exchange}" is not known to the ccxt library '
|
||||||
f'and therefore not available for the bot.\n'
|
f'and therefore not available for the bot.\n'
|
||||||
f'The following exchanges are available for Freqtrade: '
|
f'The following exchanges are available for Freqtrade: '
|
||||||
f'{", ".join(available_exchanges())}'
|
f'{", ".join(available_exchanges())}'
|
||||||
)
|
)
|
||||||
|
|
||||||
valid, reason = validate_exchange(exchange)
|
valid, reason = validate_exchange(exchange)
|
||||||
|
@ -3,7 +3,6 @@ from typing import Any, Dict
|
|||||||
|
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
from .check_exchange import remove_credentials
|
|
||||||
from .config_validation import validate_config_consistency
|
from .config_validation import validate_config_consistency
|
||||||
from .configuration import Configuration
|
from .configuration import Configuration
|
||||||
|
|
||||||
@ -15,13 +14,14 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str
|
|||||||
"""
|
"""
|
||||||
Prepare the configuration for utils subcommands
|
Prepare the configuration for utils subcommands
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
|
:param method: Bot running mode
|
||||||
:return: Configuration
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
configuration = Configuration(args, method)
|
configuration = Configuration(args, method)
|
||||||
config = configuration.get_config()
|
config = configuration.get_config()
|
||||||
|
|
||||||
# Ensure we do not use Exchange credentials
|
# Ensure these modes are using Dry-run
|
||||||
remove_credentials(config)
|
config['dry_run'] = True
|
||||||
validate_config_consistency(config)
|
validate_config_consistency(config)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
@ -79,6 +79,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
|
|||||||
_validate_whitelist(conf)
|
_validate_whitelist(conf)
|
||||||
_validate_protections(conf)
|
_validate_protections(conf)
|
||||||
_validate_unlimited_amount(conf)
|
_validate_unlimited_amount(conf)
|
||||||
|
_validate_ask_orderbook(conf)
|
||||||
|
|
||||||
# validate configuration before returning
|
# validate configuration before returning
|
||||||
logger.info('Validating configuration ...')
|
logger.info('Validating configuration ...')
|
||||||
@ -114,7 +115,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
|||||||
if conf.get('stoploss') == 0.0:
|
if conf.get('stoploss') == 0.0:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
||||||
)
|
)
|
||||||
# Skip if trailing stoploss is not activated
|
# Skip if trailing stoploss is not activated
|
||||||
if not conf.get('trailing_stop', False):
|
if not conf.get('trailing_stop', False):
|
||||||
return
|
return
|
||||||
@ -149,7 +150,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
|
|||||||
if not conf.get('edge', {}).get('enabled'):
|
if not conf.get('edge', {}).get('enabled'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not conf.get('ask_strategy', {}).get('use_sell_signal', True):
|
if not conf.get('use_sell_signal', True):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
|
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
|
||||||
)
|
)
|
||||||
@ -179,10 +180,30 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
||||||
|
ask_strategy = conf.get('ask_strategy', {})
|
||||||
|
ob_min = ask_strategy.get('order_book_min')
|
||||||
|
ob_max = ask_strategy.get('order_book_max')
|
||||||
|
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||||
|
if ob_min != ob_max:
|
||||||
|
raise OperationalException(
|
||||||
|
"Using order_book_max != order_book_min in ask_strategy is no longer supported."
|
||||||
|
"Please pick one value and use `order_book_top` in the future."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Move value to order_book_top
|
||||||
|
ask_strategy['order_book_top'] = ob_min
|
||||||
|
logger.warning(
|
||||||
|
"DEPRECATED: "
|
||||||
|
"Please use `order_book_top` instead of `order_book_min` and `order_book_max` "
|
||||||
|
"for your `ask_strategy` configuration."
|
||||||
|
)
|
||||||
|
@ -11,11 +11,12 @@ from freqtrade import constants
|
|||||||
from freqtrade.configuration.check_exchange import check_exchange
|
from freqtrade.configuration.check_exchange import check_exchange
|
||||||
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
|
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
|
||||||
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
|
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
|
||||||
|
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
|
||||||
from freqtrade.configuration.load_config import load_config_file, load_file
|
from freqtrade.configuration.load_config import load_config_file, load_file
|
||||||
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode
|
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.loggers import setup_logging
|
from freqtrade.loggers import setup_logging
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -72,6 +73,11 @@ class Configuration:
|
|||||||
# Merge config options, overwriting old values
|
# Merge config options, overwriting old values
|
||||||
config = deep_merge_dicts(load_config_file(path), config)
|
config = deep_merge_dicts(load_config_file(path), config)
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
env_data = enironment_vars_to_dict()
|
||||||
|
config = deep_merge_dicts(env_data, config)
|
||||||
|
|
||||||
|
config['config_files'] = files
|
||||||
# Normalize config
|
# Normalize config
|
||||||
if 'internals' not in config:
|
if 'internals' not in config:
|
||||||
config['internals'] = {}
|
config['internals'] = {}
|
||||||
@ -144,7 +150,7 @@ class Configuration:
|
|||||||
config['db_url'] = constants.DEFAULT_DB_PROD_URL
|
config['db_url'] = constants.DEFAULT_DB_PROD_URL
|
||||||
logger.info('Dry run is disabled')
|
logger.info('Dry run is disabled')
|
||||||
|
|
||||||
logger.info(f'Using DB: "{config["db_url"]}"')
|
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
||||||
|
|
||||||
def _process_common_options(self, config: Dict[str, Any]) -> None:
|
def _process_common_options(self, config: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
@ -236,6 +242,9 @@ class Configuration:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='timeframe_detail',
|
||||||
|
logstring='Parameter --timeframe-detail detected, '
|
||||||
|
'using {} for intra-candle backtesting ...')
|
||||||
self._args_to_config(config, argname='stake_amount',
|
self._args_to_config(config, argname='stake_amount',
|
||||||
logstring='Parameter --stake-amount detected, '
|
logstring='Parameter --stake-amount detected, '
|
||||||
'overriding stake_amount to: {} ...')
|
'overriding stake_amount to: {} ...')
|
||||||
@ -260,6 +269,12 @@ class Configuration:
|
|||||||
self._args_to_config(config, argname='export',
|
self._args_to_config(config, argname='export',
|
||||||
logstring='Parameter --export detected: {} ...')
|
logstring='Parameter --export detected: {} ...')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='backtest_breakdown',
|
||||||
|
logstring='Parameter --breakdown detected ...')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='disableparamexport',
|
||||||
|
logstring='Parameter --disableparamexport detected: {} ...')
|
||||||
|
|
||||||
# Edge section:
|
# Edge section:
|
||||||
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
||||||
txt_range = eval(self.args["stoploss_range"])
|
txt_range = eval(self.args["stoploss_range"])
|
||||||
@ -358,6 +373,9 @@ class Configuration:
|
|||||||
self._args_to_config(config, argname='hyperopt_show_no_header',
|
self._args_to_config(config, argname='hyperopt_show_no_header',
|
||||||
logstring='Parameter --no-header detected: {}')
|
logstring='Parameter --no-header detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname="hyperopt_ignore_missing_space",
|
||||||
|
logstring="Paramter --ignore-missing-space detected: {}")
|
||||||
|
|
||||||
def _process_plot_options(self, config: Dict[str, Any]) -> None:
|
def _process_plot_options(self, config: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
self._args_to_config(config, argname='pairs',
|
self._args_to_config(config, argname='pairs',
|
||||||
@ -393,6 +411,9 @@ class Configuration:
|
|||||||
self._args_to_config(config, argname='days',
|
self._args_to_config(config, argname='days',
|
||||||
logstring='Detected --days: {}')
|
logstring='Detected --days: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='include_inactive',
|
||||||
|
logstring='Detected --include-inactive-pairs: {}')
|
||||||
|
|
||||||
self._args_to_config(config, argname='download_trades',
|
self._args_to_config(config, argname='download_trades',
|
||||||
logstring='Detected --dl-trades: {}')
|
logstring='Detected --dl-trades: {}')
|
||||||
|
|
||||||
@ -460,7 +481,7 @@ class Configuration:
|
|||||||
pairs_file = Path(self.args["pairs_file"])
|
pairs_file = Path(self.args["pairs_file"])
|
||||||
logger.info(f'Reading pairs file "{pairs_file}".')
|
logger.info(f'Reading pairs file "{pairs_file}".')
|
||||||
# Download pairs from the pairs file if no config is specified
|
# Download pairs from the pairs file if no config is specified
|
||||||
# or if pairs file is specified explicitely
|
# or if pairs file is specified explicitly
|
||||||
if not pairs_file.exists():
|
if not pairs_file.exists():
|
||||||
raise OperationalException(f'No pairs file found with path "{pairs_file}".')
|
raise OperationalException(f'No pairs file found with path "{pairs_file}".')
|
||||||
config['pairs'] = load_file(pairs_file)
|
config['pairs'] = load_file(pairs_file)
|
||||||
|
@ -3,7 +3,7 @@ Functions to handle deprecated settings
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
@ -12,23 +12,24 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def check_conflicting_settings(config: Dict[str, Any],
|
def check_conflicting_settings(config: Dict[str, Any],
|
||||||
section1: str, name1: str,
|
section_old: str, name_old: str,
|
||||||
section2: str, name2: str) -> None:
|
section_new: Optional[str], name_new: str) -> None:
|
||||||
section1_config = config.get(section1, {})
|
section_new_config = config.get(section_new, {}) if section_new else config
|
||||||
section2_config = config.get(section2, {})
|
section_old_config = config.get(section_old, {})
|
||||||
if name1 in section1_config and name2 in section2_config:
|
if name_new in section_new_config and name_old in section_old_config:
|
||||||
|
new_name = f"{section_new}.{name_new}" if section_new else f"{name_new}"
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Conflicting settings `{section1}.{name1}` and `{section2}.{name2}` "
|
f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` "
|
||||||
"(DEPRECATED) detected in the configuration file. "
|
"(DEPRECATED) detected in the configuration file. "
|
||||||
"This deprecated setting will be removed in the next versions of Freqtrade. "
|
"This deprecated setting will be removed in the next versions of Freqtrade. "
|
||||||
f"Please delete it from your configuration and use the `{section1}.{name1}` "
|
f"Please delete it from your configuration and use the `{new_name}` "
|
||||||
"setting instead."
|
"setting instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_removed_setting(config: Dict[str, Any],
|
def process_removed_setting(config: Dict[str, Any],
|
||||||
section1: str, name1: str,
|
section1: str, name1: str,
|
||||||
section2: str, name2: str) -> None:
|
section2: Optional[str], name2: str) -> None:
|
||||||
"""
|
"""
|
||||||
:param section1: Removed section
|
:param section1: Removed section
|
||||||
:param name1: Removed setting name
|
:param name1: Removed setting name
|
||||||
@ -37,27 +38,32 @@ def process_removed_setting(config: Dict[str, Any],
|
|||||||
"""
|
"""
|
||||||
section1_config = config.get(section1, {})
|
section1_config = config.get(section1, {})
|
||||||
if name1 in section1_config:
|
if name1 in section1_config:
|
||||||
|
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. "
|
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
|
||||||
f"Please delete it from your configuration and use the `{section2}.{name2}` "
|
f"Please delete it from your configuration and use the `{section_2}` "
|
||||||
"setting instead."
|
"setting instead."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_deprecated_setting(config: Dict[str, Any],
|
def process_deprecated_setting(config: Dict[str, Any],
|
||||||
section1: str, name1: str,
|
section_old: str, name_old: str,
|
||||||
section2: str, name2: str) -> None:
|
section_new: Optional[str], name_new: str
|
||||||
section2_config = config.get(section2, {})
|
) -> None:
|
||||||
|
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
|
||||||
|
section_old_config = config.get(section_old, {})
|
||||||
|
|
||||||
if name2 in section2_config:
|
if name_old in section_old_config:
|
||||||
|
section_2 = f"{section_new}.{name_new}" if section_new else f"{name_new}"
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
f"The `{section2}.{name2}` setting is deprecated and "
|
f"The `{section_old}.{name_old}` setting is deprecated and "
|
||||||
"will be removed in the next versions of Freqtrade. "
|
"will be removed in the next versions of Freqtrade. "
|
||||||
f"Please use the `{section1}.{name1}` setting in your configuration instead."
|
f"Please use the `{section_2}` setting in your configuration instead."
|
||||||
)
|
)
|
||||||
section1_config = config.get(section1, {})
|
|
||||||
section1_config[name1] = section2_config[name2]
|
section_new_config = config.get(section_new, {}) if section_new else config
|
||||||
|
section_new_config[name_new] = section_old_config[name_old]
|
||||||
|
|
||||||
|
|
||||||
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
||||||
@ -65,15 +71,24 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
|||||||
# Kept for future deprecated / moved settings
|
# Kept for future deprecated / moved settings
|
||||||
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
|
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
|
||||||
# 'experimental', 'use_sell_signal')
|
# 'experimental', 'use_sell_signal')
|
||||||
# process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
|
process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
|
||||||
# 'experimental', 'use_sell_signal')
|
None, 'use_sell_signal')
|
||||||
|
process_deprecated_setting(config, 'ask_strategy', 'sell_profit_only',
|
||||||
|
None, 'sell_profit_only')
|
||||||
|
process_deprecated_setting(config, 'ask_strategy', 'sell_profit_offset',
|
||||||
|
None, 'sell_profit_offset')
|
||||||
|
process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal',
|
||||||
|
None, 'ignore_roi_if_buy_signal')
|
||||||
|
process_deprecated_setting(config, 'ask_strategy', 'ignore_buying_expired_candle_after',
|
||||||
|
None, 'ignore_buying_expired_candle_after')
|
||||||
|
|
||||||
|
# Legacy way - having them in experimental ...
|
||||||
process_removed_setting(config, 'experimental', 'use_sell_signal',
|
process_removed_setting(config, 'experimental', 'use_sell_signal',
|
||||||
'ask_strategy', 'use_sell_signal')
|
None, 'use_sell_signal')
|
||||||
process_removed_setting(config, 'experimental', 'sell_profit_only',
|
process_removed_setting(config, 'experimental', 'sell_profit_only',
|
||||||
'ask_strategy', 'sell_profit_only')
|
None, 'sell_profit_only')
|
||||||
process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
|
process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
|
||||||
'ask_strategy', 'ignore_roi_if_buy_signal')
|
None, 'ignore_roi_if_buy_signal')
|
||||||
|
|
||||||
if (config.get('edge', {}).get('enabled', False)
|
if (config.get('edge', {}).get('enabled', False)
|
||||||
and 'capital_available_percentage' in config.get('edge', {})):
|
and 'capital_available_percentage' in config.get('edge', {})):
|
||||||
@ -93,5 +108,8 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Both 'timeframe' and 'ticker_interval' detected."
|
"Both 'timeframe' and 'ticker_interval' detected."
|
||||||
"Please remove 'ticker_interval' from your configuration to continue operating."
|
"Please remove 'ticker_interval' from your configuration to continue operating."
|
||||||
)
|
)
|
||||||
config['timeframe'] = config['ticker_interval']
|
config['timeframe'] = config['ticker_interval']
|
||||||
|
|
||||||
|
if 'protections' in config:
|
||||||
|
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.")
|
||||||
|
54
freqtrade/configuration/environment_vars.py
Normal file
54
freqtrade/configuration/environment_vars.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from freqtrade.constants import ENV_VAR_PREFIX
|
||||||
|
from freqtrade.misc import deep_merge_dicts
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_var_typed(val):
|
||||||
|
try:
|
||||||
|
return int(val)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
return float(val)
|
||||||
|
except ValueError:
|
||||||
|
if val.lower() in ('t', 'true'):
|
||||||
|
return True
|
||||||
|
elif val.lower() in ('f', 'false'):
|
||||||
|
return False
|
||||||
|
# keep as string
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Environment variables must be prefixed with FREQTRADE.
|
||||||
|
FREQTRADE__{section}__{key}
|
||||||
|
:param env_dict: Dictionary to validate - usually os.environ
|
||||||
|
:param prefix: Prefix to consider (usually FREQTRADE__)
|
||||||
|
:return: Nested dict based on available and relevant variables.
|
||||||
|
"""
|
||||||
|
relevant_vars: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
for env_var, val in sorted(env_dict.items()):
|
||||||
|
if env_var.startswith(prefix):
|
||||||
|
logger.info(f"Loading variable '{env_var}'")
|
||||||
|
key = env_var.replace(prefix, '')
|
||||||
|
for k in reversed(key.split('__')):
|
||||||
|
val = {k.lower(): get_var_typed(val) if type(val) != dict else val}
|
||||||
|
relevant_vars = deep_merge_dicts(val, relevant_vars)
|
||||||
|
|
||||||
|
return relevant_vars
|
||||||
|
|
||||||
|
|
||||||
|
def enironment_vars_to_dict() -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Read environment variables and return a nested dict for relevant variables
|
||||||
|
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
||||||
|
:return: Nested dict based on available and relevant variables.
|
||||||
|
"""
|
||||||
|
return flat_vars_to_nested_dict(os.environ.copy(), ENV_VAR_PREFIX)
|
@ -12,6 +12,7 @@ PROCESS_THROTTLE_SECS = 5 # sec
|
|||||||
HYPEROPT_EPOCH = 100 # epochs
|
HYPEROPT_EPOCH = 100 # epochs
|
||||||
RETRY_TIMEOUT = 30 # sec
|
RETRY_TIMEOUT = 30 # sec
|
||||||
TIMEOUT_UNITS = ['minutes', 'seconds']
|
TIMEOUT_UNITS = ['minutes', 'seconds']
|
||||||
|
EXPORT_OPTIONS = ['none', 'trades']
|
||||||
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
||||||
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
||||||
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||||
@ -23,13 +24,16 @@ ORDERTYPE_POSSIBILITIES = ['limit', 'market']
|
|||||||
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
|
||||||
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||||
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
|
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
|
||||||
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily']
|
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
|
||||||
|
'CalmarHyperOptLoss',
|
||||||
|
'MaxDrawDownHyperOptLoss']
|
||||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
||||||
'AgeFilter', 'PerformanceFilter', 'PrecisionFilter',
|
'AgeFilter', 'OffsetFilter', 'PerformanceFilter',
|
||||||
'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter',
|
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
|
||||||
'SpreadFilter', 'VolatilityFilter']
|
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
|
||||||
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
||||||
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
|
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
|
||||||
|
BACKTEST_BREAKDOWNS = ['day', 'week', 'month']
|
||||||
DRY_RUN_WALLET = 1000
|
DRY_RUN_WALLET = 1000
|
||||||
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
|
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||||
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
|
||||||
@ -39,13 +43,16 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume']
|
|||||||
DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost']
|
DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost']
|
||||||
|
|
||||||
LAST_BT_RESULT_FN = '.last_result.json'
|
LAST_BT_RESULT_FN = '.last_result.json'
|
||||||
|
FTHYPT_FILEVERSION = 'fthypt_fileversion'
|
||||||
|
|
||||||
USERPATH_HYPEROPTS = 'hyperopts'
|
USERPATH_HYPEROPTS = 'hyperopts'
|
||||||
USERPATH_STRATEGIES = 'strategies'
|
USERPATH_STRATEGIES = 'strategies'
|
||||||
USERPATH_NOTEBOOKS = 'notebooks'
|
USERPATH_NOTEBOOKS = 'notebooks'
|
||||||
|
|
||||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||||
|
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||||
|
|
||||||
|
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
|
||||||
|
|
||||||
# Define decimals per coin for outputs
|
# Define decimals per coin for outputs
|
||||||
# Only used for outputs.
|
# Only used for outputs.
|
||||||
@ -60,13 +67,10 @@ DUST_PER_COIN = {
|
|||||||
'ETH': 0.01
|
'ETH': 0.01
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Source files with destination directories within user-directory
|
||||||
# Soure files with destination directories within user-directory
|
|
||||||
USER_DATA_FILES = {
|
USER_DATA_FILES = {
|
||||||
'sample_strategy.py': USERPATH_STRATEGIES,
|
'sample_strategy.py': USERPATH_STRATEGIES,
|
||||||
'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS,
|
|
||||||
'sample_hyperopt_loss.py': USERPATH_HYPEROPTS,
|
'sample_hyperopt_loss.py': USERPATH_HYPEROPTS,
|
||||||
'sample_hyperopt.py': USERPATH_HYPEROPTS,
|
|
||||||
'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS,
|
'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,10 +111,14 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'tradable_balance_ratio': {
|
'tradable_balance_ratio': {
|
||||||
'type': 'number',
|
'type': 'number',
|
||||||
'minimum': 0.1,
|
'minimum': 0.0,
|
||||||
'maximum': 1,
|
'maximum': 1,
|
||||||
'default': 0.99
|
'default': 0.99
|
||||||
},
|
},
|
||||||
|
'available_capital': {
|
||||||
|
'type': 'number',
|
||||||
|
'minimum': 0,
|
||||||
|
},
|
||||||
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
|
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
|
||||||
'last_stake_amount_min_ratio': {
|
'last_stake_amount_min_ratio': {
|
||||||
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
|
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
|
||||||
@ -133,6 +141,15 @@ CONF_SCHEMA = {
|
|||||||
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||||
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
|
||||||
'trailing_only_offset_is_reached': {'type': 'boolean'},
|
'trailing_only_offset_is_reached': {'type': 'boolean'},
|
||||||
|
'use_sell_signal': {'type': 'boolean'},
|
||||||
|
'sell_profit_only': {'type': 'boolean'},
|
||||||
|
'sell_profit_offset': {'type': 'number'},
|
||||||
|
'ignore_roi_if_buy_signal': {'type': 'boolean'},
|
||||||
|
'ignore_buying_expired_candle_after': {'type': 'number'},
|
||||||
|
'backtest_breakdown': {
|
||||||
|
'type': 'array',
|
||||||
|
'items': {'type': 'string', 'enum': BACKTEST_BREAKDOWNS}
|
||||||
|
},
|
||||||
'bot_name': {'type': 'string'},
|
'bot_name': {'type': 'string'},
|
||||||
'unfilledtimeout': {
|
'unfilledtimeout': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@ -153,7 +170,7 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
|
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
|
||||||
'use_order_book': {'type': 'boolean'},
|
'use_order_book': {'type': 'boolean'},
|
||||||
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
|
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
|
||||||
'check_depth_of_market': {
|
'check_depth_of_market': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
'properties': {
|
'properties': {
|
||||||
@ -162,7 +179,7 @@ CONF_SCHEMA = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'required': ['ask_last_balance']
|
'required': ['price_side']
|
||||||
},
|
},
|
||||||
'ask_strategy': {
|
'ask_strategy': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@ -175,13 +192,12 @@ CONF_SCHEMA = {
|
|||||||
'exclusiveMaximum': False,
|
'exclusiveMaximum': False,
|
||||||
},
|
},
|
||||||
'use_order_book': {'type': 'boolean'},
|
'use_order_book': {'type': 'boolean'},
|
||||||
'order_book_min': {'type': 'integer', 'minimum': 1},
|
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
|
||||||
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
|
},
|
||||||
'use_sell_signal': {'type': 'boolean'},
|
'required': ['price_side']
|
||||||
'sell_profit_only': {'type': 'boolean'},
|
},
|
||||||
'sell_profit_offset': {'type': 'number'},
|
'custom_price_max_distance_ratio': {
|
||||||
'ignore_roi_if_buy_signal': {'type': 'boolean'}
|
'type': 'number', 'minimum': 0.0
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'order_types': {
|
'order_types': {
|
||||||
'type': 'object',
|
'type': 'object',
|
||||||
@ -272,9 +288,19 @@ CONF_SCHEMA = {
|
|||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': TELEGRAM_SETTING_OPTIONS,
|
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||||
'default': 'off'
|
'default': 'off'
|
||||||
},
|
},
|
||||||
|
'protection_trigger': {
|
||||||
|
'type': 'string',
|
||||||
|
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||||
|
'default': 'off'
|
||||||
|
},
|
||||||
|
'protection_trigger_global': {
|
||||||
|
'type': 'string',
|
||||||
|
'enum': TELEGRAM_SETTING_OPTIONS,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
'reload': {'type': 'boolean'},
|
||||||
},
|
},
|
||||||
'required': ['enabled', 'token', 'chat_id'],
|
'required': ['enabled', 'token', 'chat_id'],
|
||||||
},
|
},
|
||||||
@ -308,6 +334,8 @@ CONF_SCHEMA = {
|
|||||||
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
|
||||||
},
|
},
|
||||||
'db_url': {'type': 'string'},
|
'db_url': {'type': 'string'},
|
||||||
|
'export': {'type': 'string', 'enum': EXPORT_OPTIONS, 'default': 'trades'},
|
||||||
|
'disableparamexport': {'type': 'boolean'},
|
||||||
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
|
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
|
||||||
'forcebuy_enable': {'type': 'boolean'},
|
'forcebuy_enable': {'type': 'boolean'},
|
||||||
'disable_dataframe_checks': {'type': 'boolean'},
|
'disable_dataframe_checks': {'type': 'boolean'},
|
||||||
@ -322,13 +350,13 @@ CONF_SCHEMA = {
|
|||||||
},
|
},
|
||||||
'dataformat_ohlcv': {
|
'dataformat_ohlcv': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': AVAILABLE_DATAHANDLERS,
|
'enum': AVAILABLE_DATAHANDLERS,
|
||||||
'default': 'json'
|
'default': 'json'
|
||||||
},
|
},
|
||||||
'dataformat_trades': {
|
'dataformat_trades': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': AVAILABLE_DATAHANDLERS,
|
'enum': AVAILABLE_DATAHANDLERS,
|
||||||
'default': 'jsongz'
|
'default': 'jsongz'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'definitions': {
|
'definitions': {
|
||||||
|
@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
|||||||
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
|
||||||
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
|
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
|
||||||
|
|
||||||
# Mid-term format, crated by BacktestResult Named Tuple
|
# Mid-term format, created by BacktestResult Named Tuple
|
||||||
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
|
||||||
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
|
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
|
||||||
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
|
||||||
@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
|||||||
'fee_open', 'fee_close', 'trade_duration',
|
'fee_open', 'fee_close', 'trade_duration',
|
||||||
'profit_ratio', 'profit_abs', 'sell_reason',
|
'profit_ratio', 'profit_abs', 'sell_reason',
|
||||||
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
||||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ]
|
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag']
|
||||||
|
|
||||||
|
|
||||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
||||||
|
@ -49,7 +49,7 @@ def clean_ohlcv_dataframe(data: DataFrame, timeframe: str, pair: str, *,
|
|||||||
fill_missing: bool = True,
|
fill_missing: bool = True,
|
||||||
drop_incomplete: bool = True) -> DataFrame:
|
drop_incomplete: bool = True) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Clense a OHLCV dataframe by
|
Cleanse a OHLCV dataframe by
|
||||||
* Grouping it by date (removes duplicate tics)
|
* Grouping it by date (removes duplicate tics)
|
||||||
* dropping last candles if requested
|
* dropping last candles if requested
|
||||||
* Filling up missing data (if requested)
|
* Filling up missing data (if requested)
|
||||||
@ -242,7 +242,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to:
|
|||||||
:param config: Config dictionary
|
:param config: Config dictionary
|
||||||
:param convert_from: Source format
|
:param convert_from: Source format
|
||||||
:param convert_to: Target format
|
:param convert_to: Target format
|
||||||
:param erase: Erase souce data (does not apply if source and target format are identical)
|
:param erase: Erase source data (does not apply if source and target format are identical)
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history.idatahandler import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
@ -267,7 +267,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to:
|
|||||||
:param config: Config dictionary
|
:param config: Config dictionary
|
||||||
:param convert_from: Source format
|
:param convert_from: Source format
|
||||||
:param convert_to: Target format
|
:param convert_to: Target format
|
||||||
:param erase: Erase souce data (does not apply if source and target format are identical)
|
:param erase: Erase source data (does not apply if source and target format are identical)
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history.idatahandler import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
|
@ -10,11 +10,12 @@ from typing import Any, Dict, List, Optional, Tuple
|
|||||||
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
|
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
|
||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import ExchangeError, OperationalException
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange, timeframe_to_seconds
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -31,6 +32,7 @@ class DataProvider:
|
|||||||
self._pairlists = pairlists
|
self._pairlists = pairlists
|
||||||
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
|
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
|
||||||
self.__slice_index: Optional[int] = None
|
self.__slice_index: Optional[int] = None
|
||||||
|
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
|
||||||
|
|
||||||
def _set_dataframe_max_index(self, limit_index: int):
|
def _set_dataframe_max_index(self, limit_index: int):
|
||||||
"""
|
"""
|
||||||
@ -62,11 +64,22 @@ class DataProvider:
|
|||||||
:param pair: pair to get the data for
|
:param pair: pair to get the data for
|
||||||
:param timeframe: timeframe to get data for
|
:param timeframe: timeframe to get data for
|
||||||
"""
|
"""
|
||||||
return load_pair_history(pair=pair,
|
saved_pair = (pair, str(timeframe))
|
||||||
timeframe=timeframe or self._config['timeframe'],
|
if saved_pair not in self.__cached_pairs_backtesting:
|
||||||
datadir=self._config['datadir'],
|
timerange = TimeRange.parse_timerange(None if self._config.get(
|
||||||
data_format=self._config.get('dataformat_ohlcv', 'json')
|
'timerange') is None else str(self._config.get('timerange')))
|
||||||
)
|
# Move informative start time respecting startup_candle_count
|
||||||
|
timerange.subtract_start(
|
||||||
|
timeframe_to_seconds(str(timeframe)) * self._config.get('startup_candle_count', 0)
|
||||||
|
)
|
||||||
|
self.__cached_pairs_backtesting[saved_pair] = load_pair_history(
|
||||||
|
pair=pair,
|
||||||
|
timeframe=timeframe or self._config['timeframe'],
|
||||||
|
datadir=self._config['datadir'],
|
||||||
|
timerange=timerange,
|
||||||
|
data_format=self._config.get('dataformat_ohlcv', 'json')
|
||||||
|
)
|
||||||
|
return self.__cached_pairs_backtesting[saved_pair].copy()
|
||||||
|
|
||||||
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
|
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
@ -136,6 +149,8 @@ class DataProvider:
|
|||||||
Clear pair dataframe cache.
|
Clear pair dataframe cache.
|
||||||
"""
|
"""
|
||||||
self.__cached_pairs = {}
|
self.__cached_pairs = {}
|
||||||
|
self.__cached_pairs_backtesting = {}
|
||||||
|
self.__slice_index = 0
|
||||||
|
|
||||||
# Exchange functions
|
# Exchange functions
|
||||||
|
|
||||||
|
@ -52,8 +52,8 @@ class HDF5DataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
Store data in hdf5 file.
|
Store data in hdf5 file.
|
||||||
:param pair: Pair - used to generate filename
|
:param pair: Pair - used to generate filename
|
||||||
:timeframe: Timeframe - used to generate filename
|
:param timeframe: Timeframe - used to generate filename
|
||||||
:data: Dataframe containing OHLCV data
|
:param data: Dataframe containing OHLCV data
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
key = self._pair_ohlcv_key(pair, timeframe)
|
key = self._pair_ohlcv_key(pair, timeframe)
|
||||||
|
@ -113,13 +113,15 @@ def refresh_data(datadir: Path,
|
|||||||
:param timeframe: Timeframe (e.g. "5m")
|
:param timeframe: Timeframe (e.g. "5m")
|
||||||
:param pairs: List of pairs to load
|
:param pairs: List of pairs to load
|
||||||
:param exchange: Exchange object
|
:param exchange: Exchange object
|
||||||
|
:param data_format: dataformat to use
|
||||||
:param timerange: Limit data to be loaded to this timerange
|
:param timerange: Limit data to be loaded to this timerange
|
||||||
"""
|
"""
|
||||||
data_handler = get_datahandler(datadir, data_format)
|
data_handler = get_datahandler(datadir, data_format)
|
||||||
for pair in pairs:
|
for idx, pair in enumerate(pairs):
|
||||||
_download_pair_history(pair=pair, timeframe=timeframe,
|
process = f'{idx}/{len(pairs)}'
|
||||||
datadir=datadir, timerange=timerange,
|
_download_pair_history(pair=pair, process=process,
|
||||||
exchange=exchange, data_handler=data_handler)
|
timeframe=timeframe, datadir=datadir,
|
||||||
|
timerange=timerange, exchange=exchange, data_handler=data_handler)
|
||||||
|
|
||||||
|
|
||||||
def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange],
|
def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange],
|
||||||
@ -152,13 +154,14 @@ def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optiona
|
|||||||
return data, start_ms
|
return data, start_ms
|
||||||
|
|
||||||
|
|
||||||
def _download_pair_history(datadir: Path,
|
def _download_pair_history(pair: str, *,
|
||||||
|
datadir: Path,
|
||||||
exchange: Exchange,
|
exchange: Exchange,
|
||||||
pair: str, *,
|
|
||||||
new_pairs_days: int = 30,
|
|
||||||
timeframe: str = '5m',
|
timeframe: str = '5m',
|
||||||
timerange: Optional[TimeRange] = None,
|
process: str = '',
|
||||||
data_handler: IDataHandler = None) -> bool:
|
new_pairs_days: int = 30,
|
||||||
|
data_handler: IDataHandler = None,
|
||||||
|
timerange: Optional[TimeRange] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Download latest candles from the exchange for the pair and timeframe passed in parameters
|
Download latest candles from the exchange for the pair and timeframe passed in parameters
|
||||||
The data is downloaded starting from the last correct data that
|
The data is downloaded starting from the last correct data that
|
||||||
@ -176,7 +179,7 @@ def _download_pair_history(datadir: Path,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(
|
logger.info(
|
||||||
f'Download history data for pair: "{pair}", timeframe: {timeframe} '
|
f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe} '
|
||||||
f'and store in {datadir}.'
|
f'and store in {datadir}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -193,8 +196,9 @@ def _download_pair_history(datadir: Path,
|
|||||||
new_data = exchange.get_historic_ohlcv(pair=pair,
|
new_data = exchange.get_historic_ohlcv(pair=pair,
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
since_ms=since_ms if since_ms else
|
since_ms=since_ms if since_ms else
|
||||||
int(arrow.utcnow().shift(
|
arrow.utcnow().shift(
|
||||||
days=-new_pairs_days).float_timestamp) * 1000
|
days=-new_pairs_days).int_timestamp * 1000,
|
||||||
|
is_new_pair=data.empty
|
||||||
)
|
)
|
||||||
# TODO: Maybe move parsing to exchange class (?)
|
# TODO: Maybe move parsing to exchange class (?)
|
||||||
new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair,
|
new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair,
|
||||||
@ -233,7 +237,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
"""
|
"""
|
||||||
pairs_not_available = []
|
pairs_not_available = []
|
||||||
data_handler = get_datahandler(datadir, data_format)
|
data_handler = get_datahandler(datadir, data_format)
|
||||||
for pair in pairs:
|
for idx, pair in enumerate(pairs, start=1):
|
||||||
if pair not in exchange.markets:
|
if pair not in exchange.markets:
|
||||||
pairs_not_available.append(pair)
|
pairs_not_available.append(pair)
|
||||||
logger.info(f"Skipping pair {pair}...")
|
logger.info(f"Skipping pair {pair}...")
|
||||||
@ -246,10 +250,11 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
f'Deleting existing data for pair {pair}, interval {timeframe}.')
|
f'Deleting existing data for pair {pair}, interval {timeframe}.')
|
||||||
|
|
||||||
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
||||||
_download_pair_history(datadir=datadir, exchange=exchange,
|
process = f'{idx}/{len(pairs)}'
|
||||||
pair=pair, timeframe=str(timeframe),
|
_download_pair_history(pair=pair, process=process,
|
||||||
new_pairs_days=new_pairs_days,
|
datadir=datadir, exchange=exchange,
|
||||||
timerange=timerange, data_handler=data_handler)
|
timerange=timerange, data_handler=data_handler,
|
||||||
|
timeframe=str(timeframe), new_pairs_days=new_pairs_days)
|
||||||
return pairs_not_available
|
return pairs_not_available
|
||||||
|
|
||||||
|
|
||||||
@ -271,7 +276,7 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
until = timerange.stopts * 1000
|
until = timerange.stopts * 1000
|
||||||
else:
|
else:
|
||||||
since = int(arrow.utcnow().shift(days=-new_pairs_days).float_timestamp) * 1000
|
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000
|
||||||
|
|
||||||
trades = data_handler.trades_load(pair)
|
trades = data_handler.trades_load(pair)
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ class IDataHandler(ABC):
|
|||||||
"""
|
"""
|
||||||
Store ohlcv data.
|
Store ohlcv data.
|
||||||
:param pair: Pair - used to generate filename
|
:param pair: Pair - used to generate filename
|
||||||
:timeframe: Timeframe - used to generate filename
|
:param timeframe: Timeframe - used to generate filename
|
||||||
:data: Dataframe containing OHLCV data
|
:param data: Dataframe containing OHLCV data
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -245,8 +245,8 @@ def get_datahandler(datadir: Path, data_format: str = None,
|
|||||||
data_handler: IDataHandler = None) -> IDataHandler:
|
data_handler: IDataHandler = None) -> IDataHandler:
|
||||||
"""
|
"""
|
||||||
:param datadir: Folder to save data
|
:param datadir: Folder to save data
|
||||||
:data_format: dataformat to use
|
:param data_format: dataformat to use
|
||||||
:data_handler: returns this datahandler if it exists or initializes a new one
|
:param data_handler: returns this datahandler if it exists or initializes a new one
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not data_handler:
|
if not data_handler:
|
||||||
|
@ -55,14 +55,14 @@ class JsonDataHandler(IDataHandler):
|
|||||||
format looks as follows:
|
format looks as follows:
|
||||||
[[<date>,<open>,<high>,<low>,<close>]]
|
[[<date>,<open>,<high>,<low>,<close>]]
|
||||||
:param pair: Pair - used to generate filename
|
:param pair: Pair - used to generate filename
|
||||||
:timeframe: Timeframe - used to generate filename
|
:param timeframe: Timeframe - used to generate filename
|
||||||
:data: Dataframe containing OHLCV data
|
:param data: Dataframe containing OHLCV data
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
filename = self._pair_data_filename(self._datadir, pair, timeframe)
|
filename = self._pair_data_filename(self._datadir, pair, timeframe)
|
||||||
_data = data.copy()
|
_data = data.copy()
|
||||||
# Convert date to int
|
# Convert date to int
|
||||||
_data['date'] = _data['date'].astype(np.int64) // 1000 // 1000
|
_data['date'] = _data['date'].view(np.int64) // 1000 // 1000
|
||||||
|
|
||||||
# Reset index, select only appropriate columns and save as json
|
# Reset index, select only appropriate columns and save as json
|
||||||
_data.reset_index(drop=True).loc[:, self._columns].to_json(
|
_data.reset_index(drop=True).loc[:, self._columns].to_json(
|
||||||
|
@ -119,7 +119,7 @@ class Edge:
|
|||||||
)
|
)
|
||||||
# Download informative pairs too
|
# Download informative pairs too
|
||||||
res = defaultdict(list)
|
res = defaultdict(list)
|
||||||
for p, t in self.strategy.informative_pairs():
|
for p, t in self.strategy.gather_informative_pairs():
|
||||||
res[t].append(p)
|
res[t].append(p)
|
||||||
for timeframe, inf_pairs in res.items():
|
for timeframe, inf_pairs in res.items():
|
||||||
timerange_startup = deepcopy(self._timerange)
|
timerange_startup = deepcopy(self._timerange)
|
||||||
@ -151,7 +151,7 @@ class Edge:
|
|||||||
# Fake run-mode to Edge
|
# Fake run-mode to Edge
|
||||||
prior_rm = self.config['runmode']
|
prior_rm = self.config['runmode']
|
||||||
self.config['runmode'] = RunMode.EDGE
|
self.config['runmode'] = RunMode.EDGE
|
||||||
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
|
preprocessed = self.strategy.advise_all_indicators(data)
|
||||||
self.config['runmode'] = prior_rm
|
self.config['runmode'] = prior_rm
|
||||||
|
|
||||||
# Print timeframe
|
# Print timeframe
|
||||||
@ -231,12 +231,12 @@ class Edge:
|
|||||||
'Minimum expectancy and minimum winrate are met only for %s,'
|
'Minimum expectancy and minimum winrate are met only for %s,'
|
||||||
' so other pairs are filtered out.',
|
' so other pairs are filtered out.',
|
||||||
self._final_pairs
|
self._final_pairs
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info(
|
logger.info(
|
||||||
'Edge removed all pairs as no pair with minimum expectancy '
|
'Edge removed all pairs as no pair with minimum expectancy '
|
||||||
'and minimum winrate was found !'
|
'and minimum winrate was found !'
|
||||||
)
|
)
|
||||||
|
|
||||||
return self._final_pairs
|
return self._final_pairs
|
||||||
|
|
||||||
@ -247,7 +247,7 @@ class Edge:
|
|||||||
final = []
|
final = []
|
||||||
for pair, info in self._cached_pairs.items():
|
for pair, info in self._cached_pairs.items():
|
||||||
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
|
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
|
||||||
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
|
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
|
||||||
final.append({
|
final.append({
|
||||||
'Pair': pair,
|
'Pair': pair,
|
||||||
'Winrate': info.winrate,
|
'Winrate': info.winrate,
|
||||||
@ -301,7 +301,7 @@ class Edge:
|
|||||||
def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]:
|
def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
|
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
|
||||||
The calulation will be done per pair and per strategy.
|
The calculation will be done per pair and per strategy.
|
||||||
"""
|
"""
|
||||||
# Removing pairs having less than min_trades_number
|
# Removing pairs having less than min_trades_number
|
||||||
min_trades_number = self.edge_config.get('min_trade_number', 10)
|
min_trades_number = self.edge_config.get('min_trade_number', 10)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
from freqtrade.enums.backteststate import BacktestState
|
||||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
||||||
from freqtrade.enums.selltype import SellType
|
from freqtrade.enums.selltype import SellType
|
||||||
from freqtrade.enums.signaltype import SignalType
|
from freqtrade.enums.signaltype import SignalTagType, SignalType
|
||||||
from freqtrade.enums.state import State
|
from freqtrade.enums.state import State
|
||||||
|
15
freqtrade/enums/backteststate.py
Normal file
15
freqtrade/enums/backteststate.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class BacktestState(Enum):
|
||||||
|
"""
|
||||||
|
Bot application states
|
||||||
|
"""
|
||||||
|
STARTUP = 1
|
||||||
|
DATALOAD = 2
|
||||||
|
ANALYZE = 3
|
||||||
|
CONVERT = 4
|
||||||
|
BACKTEST = 5
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name.lower()}"
|
@ -11,6 +11,8 @@ class RPCMessageType(Enum):
|
|||||||
SELL = 'sell'
|
SELL = 'sell'
|
||||||
SELL_FILL = 'sell_fill'
|
SELL_FILL = 'sell_fill'
|
||||||
SELL_CANCEL = 'sell_cancel'
|
SELL_CANCEL = 'sell_cancel'
|
||||||
|
PROTECTION_TRIGGER = 'protection_trigger'
|
||||||
|
PROTECTION_TRIGGER_GLOBAL = 'protection_trigger_global'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
@ -14,6 +14,7 @@ class RunMode(Enum):
|
|||||||
UTIL_EXCHANGE = "util_exchange"
|
UTIL_EXCHANGE = "util_exchange"
|
||||||
UTIL_NO_EXCHANGE = "util_no_exchange"
|
UTIL_NO_EXCHANGE = "util_no_exchange"
|
||||||
PLOT = "plot"
|
PLOT = "plot"
|
||||||
|
WEBSERVER = "webserver"
|
||||||
OTHER = "other"
|
OTHER = "other"
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,3 +7,10 @@ class SignalType(Enum):
|
|||||||
"""
|
"""
|
||||||
BUY = "buy"
|
BUY = "buy"
|
||||||
SELL = "sell"
|
SELL = "sell"
|
||||||
|
|
||||||
|
|
||||||
|
class SignalTagType(Enum):
|
||||||
|
"""
|
||||||
|
Enum for signal columns
|
||||||
|
"""
|
||||||
|
BUY_TAG = "buy_tag"
|
||||||
|
@ -47,7 +47,7 @@ class InvalidOrderException(ExchangeError):
|
|||||||
class RetryableOrderError(InvalidOrderException):
|
class RetryableOrderError(InvalidOrderException):
|
||||||
"""
|
"""
|
||||||
This is returned when the order is not found.
|
This is returned when the order is not found.
|
||||||
This Error will be repeated with increasing backof (in line with DDosError).
|
This Error will be repeated with increasing backoff (in line with DDosError).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -75,6 +75,6 @@ class DDosProtection(TemporaryError):
|
|||||||
|
|
||||||
class StrategyError(FreqtradeException):
|
class StrategyError(FreqtradeException):
|
||||||
"""
|
"""
|
||||||
Errors with custom user-code deteced.
|
Errors with custom user-code detected.
|
||||||
Usually caused by errors in the strategy.
|
Usually caused by errors in the strategy.
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
# isort: off
|
# isort: off
|
||||||
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
|
from freqtrade.exchange.common import remove_credentials, MAP_EXCHANGE_CHILDCLASS
|
||||||
from freqtrade.exchange.exchange import Exchange
|
from freqtrade.exchange.exchange import Exchange
|
||||||
# isort: on
|
# isort: on
|
||||||
from freqtrade.exchange.bibox import Bibox
|
from freqtrade.exchange.bibox import Bibox
|
||||||
@ -15,6 +15,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
|||||||
timeframe_to_seconds, validate_exchange,
|
timeframe_to_seconds, validate_exchange,
|
||||||
validate_exchanges)
|
validate_exchanges)
|
||||||
from freqtrade.exchange.ftx import Ftx
|
from freqtrade.exchange.ftx import Ftx
|
||||||
|
from freqtrade.exchange.gateio import Gateio
|
||||||
from freqtrade.exchange.hitbtc import Hitbtc
|
from freqtrade.exchange.hitbtc import Hitbtc
|
||||||
from freqtrade.exchange.kraken import Kraken
|
from freqtrade.exchange.kraken import Kraken
|
||||||
from freqtrade.exchange.kucoin import Kucoin
|
from freqtrade.exchange.kucoin import Kucoin
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
""" Binance exchange subclass """
|
""" Binance exchange subclass """
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
from typing import Dict, List
|
||||||
|
|
||||||
|
import arrow
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException,
|
||||||
@ -18,6 +19,7 @@ class Binance(Exchange):
|
|||||||
_ft_has: Dict = {
|
_ft_has: Dict = {
|
||||||
"stoploss_on_exchange": True,
|
"stoploss_on_exchange": True,
|
||||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||||
|
"time_in_force_parameter": "timeInForce",
|
||||||
"ohlcv_candle_limit": 1000,
|
"ohlcv_candle_limit": 1000,
|
||||||
"trades_pagination": "id",
|
"trades_pagination": "id",
|
||||||
"trades_pagination_arg": "fromId",
|
"trades_pagination_arg": "fromId",
|
||||||
@ -68,6 +70,7 @@ class Binance(Exchange):
|
|||||||
amount=amount, price=rate, params=params)
|
amount=amount, price=rate, params=params)
|
||||||
logger.info('stoploss limit order added for %s. '
|
logger.info('stoploss limit order added for %s. '
|
||||||
'stop price: %s. limit: %s', pair, stop_price, rate)
|
'stop price: %s. limit: %s', pair, stop_price, rate)
|
||||||
|
self._log_exchange_response('create_stoploss_order', order)
|
||||||
return order
|
return order
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise InsufficientFundsError(
|
raise InsufficientFundsError(
|
||||||
@ -88,3 +91,20 @@ class Binance(Exchange):
|
|||||||
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
|
async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
|
||||||
|
since_ms: int, is_new_pair: bool
|
||||||
|
) -> List:
|
||||||
|
"""
|
||||||
|
Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
|
||||||
|
Does not work for other exchanges, which don't return the earliest data when called with "0"
|
||||||
|
"""
|
||||||
|
if is_new_pair:
|
||||||
|
x = await self._async_get_candle_history(pair, timeframe, 0)
|
||||||
|
if x and x[2] and x[2][0] and x[2][0][0] > since_ms:
|
||||||
|
# Set starting date to first available candle.
|
||||||
|
since_ms = x[2][0][0]
|
||||||
|
logger.info(f"Candle-data for {pair} available starting with "
|
||||||
|
f"{arrow.get(since_ms // 1000).isoformat()}.")
|
||||||
|
return await super()._async_get_historic_ohlcv(
|
||||||
|
pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair)
|
||||||
|
@ -16,8 +16,6 @@ API_FETCH_ORDER_RETRY_COUNT = 5
|
|||||||
|
|
||||||
BAD_EXCHANGES = {
|
BAD_EXCHANGES = {
|
||||||
"bitmex": "Various reasons.",
|
"bitmex": "Various reasons.",
|
||||||
"bitstamp": "Does not provide history. "
|
|
||||||
"Details in https://github.com/freqtrade/freqtrade/issues/1983",
|
|
||||||
"phemex": "Does not provide history. ",
|
"phemex": "Does not provide history. ",
|
||||||
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
|
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
|
||||||
}
|
}
|
||||||
@ -51,6 +49,19 @@ EXCHANGE_HAS_OPTIONAL = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def remove_credentials(config) -> None:
|
||||||
|
"""
|
||||||
|
Removes exchange keys from the configuration and specifies dry-run
|
||||||
|
Used for backtesting / hyperopt / edge and utils.
|
||||||
|
Modifies the input dict!
|
||||||
|
"""
|
||||||
|
if config.get('dry_run', False):
|
||||||
|
config['exchange']['key'] = ''
|
||||||
|
config['exchange']['secret'] = ''
|
||||||
|
config['exchange']['password'] = ''
|
||||||
|
config['exchange']['uid'] = ''
|
||||||
|
|
||||||
|
|
||||||
def calculate_backoff(retrycount, max_retries):
|
def calculate_backoff(retrycount, max_retries):
|
||||||
"""
|
"""
|
||||||
Calculate backoff
|
Calculate backoff
|
||||||
|
@ -19,15 +19,16 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
|
|||||||
decimal_to_precision)
|
decimal_to_precision)
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
|
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES,
|
||||||
|
ListPairsWithTimeframes)
|
||||||
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
RetryableOrderError, TemporaryError)
|
RetryableOrderError, TemporaryError)
|
||||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES,
|
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES,
|
||||||
EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, retrier,
|
EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED,
|
||||||
retrier_async)
|
remove_credentials, retrier, retrier_async)
|
||||||
from freqtrade.misc import deep_merge_dicts, safe_value_fallback2
|
from freqtrade.misc import chunks, deep_merge_dicts, safe_value_fallback2
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
|
|
||||||
|
|
||||||
@ -53,12 +54,16 @@ class Exchange:
|
|||||||
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
||||||
_params: Dict = {}
|
_params: Dict = {}
|
||||||
|
|
||||||
|
# Additional headers - added to the ccxt object
|
||||||
|
_headers: Dict = {}
|
||||||
|
|
||||||
# Dict to specify which options each exchange implements
|
# Dict to specify which options each exchange implements
|
||||||
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
|
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
|
||||||
# or by specifying them in the configuration.
|
# or by specifying them in the configuration.
|
||||||
_ft_has_default: Dict = {
|
_ft_has_default: Dict = {
|
||||||
"stoploss_on_exchange": False,
|
"stoploss_on_exchange": False,
|
||||||
"order_time_in_force": ["gtc"],
|
"order_time_in_force": ["gtc"],
|
||||||
|
"time_in_force_parameter": "timeInForce",
|
||||||
"ohlcv_params": {},
|
"ohlcv_params": {},
|
||||||
"ohlcv_candle_limit": 500,
|
"ohlcv_candle_limit": 500,
|
||||||
"ohlcv_partial_candle": True,
|
"ohlcv_partial_candle": True,
|
||||||
@ -99,11 +104,13 @@ class Exchange:
|
|||||||
|
|
||||||
# Holds all open sell orders for dry_run
|
# Holds all open sell orders for dry_run
|
||||||
self._dry_run_open_orders: Dict[str, Any] = {}
|
self._dry_run_open_orders: Dict[str, Any] = {}
|
||||||
|
remove_credentials(config)
|
||||||
|
|
||||||
if config['dry_run']:
|
if config['dry_run']:
|
||||||
logger.info('Instance is running with dry_run enabled')
|
logger.info('Instance is running with dry_run enabled')
|
||||||
logger.info(f"Using CCXT {ccxt.__version__}")
|
logger.info(f"Using CCXT {ccxt.__version__}")
|
||||||
exchange_config = config['exchange']
|
exchange_config = config['exchange']
|
||||||
|
self.log_responses = exchange_config.get('log_responses', False)
|
||||||
|
|
||||||
# Deep merge ft_has with default ft_has options
|
# Deep merge ft_has with default ft_has options
|
||||||
self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default))
|
self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default))
|
||||||
@ -167,7 +174,7 @@ class Exchange:
|
|||||||
asyncio.get_event_loop().run_until_complete(self._api_async.close())
|
asyncio.get_event_loop().run_until_complete(self._api_async.close())
|
||||||
|
|
||||||
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
|
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
|
||||||
ccxt_kwargs: dict = None) -> ccxt.Exchange:
|
ccxt_kwargs: Dict = {}) -> ccxt.Exchange:
|
||||||
"""
|
"""
|
||||||
Initialize ccxt with given config and return valid
|
Initialize ccxt with given config and return valid
|
||||||
ccxt instance.
|
ccxt instance.
|
||||||
@ -186,6 +193,10 @@ class Exchange:
|
|||||||
}
|
}
|
||||||
if ccxt_kwargs:
|
if ccxt_kwargs:
|
||||||
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
||||||
|
if self._headers:
|
||||||
|
# Inject static headers after the above output to not confuse users.
|
||||||
|
ccxt_kwargs = deep_merge_dicts({'headers': self._headers}, ccxt_kwargs)
|
||||||
|
if ccxt_kwargs:
|
||||||
ex_config.update(ccxt_kwargs)
|
ex_config.update(ccxt_kwargs)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
@ -226,10 +237,15 @@ class Exchange:
|
|||||||
"""exchange ccxt precisionMode"""
|
"""exchange ccxt precisionMode"""
|
||||||
return self._api.precisionMode
|
return self._api.precisionMode
|
||||||
|
|
||||||
|
def _log_exchange_response(self, endpoint, response) -> None:
|
||||||
|
""" Log exchange responses """
|
||||||
|
if self.log_responses:
|
||||||
|
logger.info(f"API {endpoint}: {response}")
|
||||||
|
|
||||||
def ohlcv_candle_limit(self, timeframe: str) -> int:
|
def ohlcv_candle_limit(self, timeframe: str) -> int:
|
||||||
"""
|
"""
|
||||||
Exchange ohlcv candle limit
|
Exchange ohlcv candle limit
|
||||||
Uses ohlcv_candle_limit_per_timeframe if the exchange has different limts
|
Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits
|
||||||
per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit
|
per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit
|
||||||
:param timeframe: Timeframe to check
|
:param timeframe: Timeframe to check
|
||||||
:return: Candle limit as integer
|
:return: Candle limit as integer
|
||||||
@ -345,9 +361,16 @@ class Exchange:
|
|||||||
def validate_stakecurrency(self, stake_currency: str) -> None:
|
def validate_stakecurrency(self, stake_currency: str) -> None:
|
||||||
"""
|
"""
|
||||||
Checks stake-currency against available currencies on the exchange.
|
Checks stake-currency against available currencies on the exchange.
|
||||||
|
Only runs on startup. If markets have not been loaded, there's been a problem with
|
||||||
|
the connection to the exchange.
|
||||||
:param stake_currency: Stake-currency to validate
|
:param stake_currency: Stake-currency to validate
|
||||||
:raise: OperationalException if stake-currency is not available.
|
:raise: OperationalException if stake-currency is not available.
|
||||||
"""
|
"""
|
||||||
|
if not self._markets:
|
||||||
|
raise OperationalException(
|
||||||
|
'Could not load markets, therefore cannot start. '
|
||||||
|
'Please investigate the above error for more details.'
|
||||||
|
)
|
||||||
quote_currencies = self.get_quote_currencies()
|
quote_currencies = self.get_quote_currencies()
|
||||||
if stake_currency not in quote_currencies:
|
if stake_currency not in quote_currencies:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
@ -381,7 +404,7 @@ class Exchange:
|
|||||||
# its contents depend on the exchange.
|
# its contents depend on the exchange.
|
||||||
# It can also be a string or similar ... so we need to verify that first.
|
# It can also be a string or similar ... so we need to verify that first.
|
||||||
elif (isinstance(self.markets[pair].get('info', None), dict)
|
elif (isinstance(self.markets[pair].get('info', None), dict)
|
||||||
and self.markets[pair].get('info', {}).get('IsRestricted', False)):
|
and self.markets[pair].get('info', {}).get('prohibitedIn', False)):
|
||||||
# Warn users about restricted pairs in whitelist.
|
# Warn users about restricted pairs in whitelist.
|
||||||
# We cannot determine reliably if Users are affected.
|
# We cannot determine reliably if Users are affected.
|
||||||
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
|
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
|
||||||
@ -457,7 +480,7 @@ class Exchange:
|
|||||||
if startup_candles + 5 > candle_limit:
|
if startup_candles + 5 > candle_limit:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f"This strategy requires {startup_candles} candles to start. "
|
f"This strategy requires {startup_candles} candles to start. "
|
||||||
f"{self.name} only provides {candle_limit} for {timeframe}.")
|
f"{self.name} only provides {candle_limit - 5} for {timeframe}.")
|
||||||
|
|
||||||
def exchange_has(self, endpoint: str) -> bool:
|
def exchange_has(self, endpoint: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -469,11 +492,11 @@ class Exchange:
|
|||||||
return endpoint in self._api.has and self._api.has[endpoint]
|
return endpoint in self._api.has and self._api.has[endpoint]
|
||||||
|
|
||||||
def amount_to_precision(self, pair: str, amount: float) -> float:
|
def amount_to_precision(self, pair: str, amount: float) -> float:
|
||||||
'''
|
"""
|
||||||
Returns the amount to buy or sell to a precision the Exchange accepts
|
Returns the amount to buy or sell to a precision the Exchange accepts
|
||||||
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
|
||||||
based on our definitions.
|
based on our definitions.
|
||||||
'''
|
"""
|
||||||
if self.markets[pair]['precision']['amount']:
|
if self.markets[pair]['precision']['amount']:
|
||||||
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
|
amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE,
|
||||||
precision=self.markets[pair]['precision']['amount'],
|
precision=self.markets[pair]['precision']['amount'],
|
||||||
@ -483,14 +506,14 @@ class Exchange:
|
|||||||
return amount
|
return amount
|
||||||
|
|
||||||
def price_to_precision(self, pair: str, price: float) -> float:
|
def price_to_precision(self, pair: str, price: float) -> float:
|
||||||
'''
|
"""
|
||||||
Returns the price rounded up to the precision the Exchange accepts.
|
Returns the price rounded up to the precision the Exchange accepts.
|
||||||
Partial Re-implementation of ccxt internal method decimal_to_precision(),
|
Partial Re-implementation of ccxt internal method decimal_to_precision(),
|
||||||
which does not support rounding up
|
which does not support rounding up
|
||||||
TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and
|
TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and
|
||||||
align with amount_to_precision().
|
align with amount_to_precision().
|
||||||
Rounds up
|
Rounds up
|
||||||
'''
|
"""
|
||||||
if self.markets[pair]['precision']['price']:
|
if self.markets[pair]['precision']['price']:
|
||||||
# price = float(decimal_to_precision(price, rounding_mode=ROUND,
|
# price = float(decimal_to_precision(price, rounding_mode=ROUND,
|
||||||
# precision=self.markets[pair]['precision']['price'],
|
# precision=self.markets[pair]['precision']['price'],
|
||||||
@ -500,7 +523,7 @@ class Exchange:
|
|||||||
precision = self.markets[pair]['precision']['price']
|
precision = self.markets[pair]['precision']['price']
|
||||||
missing = price % precision
|
missing = price % precision
|
||||||
if missing != 0:
|
if missing != 0:
|
||||||
price = price - missing + precision
|
price = round(price - missing + precision, 10)
|
||||||
else:
|
else:
|
||||||
symbol_prec = self.markets[pair]['precision']['price']
|
symbol_prec = self.markets[pair]['precision']['price']
|
||||||
big_price = price * pow(10, symbol_prec)
|
big_price = price * pow(10, symbol_prec)
|
||||||
@ -545,7 +568,7 @@ class Exchange:
|
|||||||
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
|
amount_reserve_percent = 1.0 + self._config.get('amount_reserve_percent',
|
||||||
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
DEFAULT_AMOUNT_RESERVE_PERCENT)
|
||||||
amount_reserve_percent = (
|
amount_reserve_percent = (
|
||||||
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
amount_reserve_percent / (1 - abs(stoploss)) if abs(stoploss) != 1 else 1.5
|
||||||
)
|
)
|
||||||
# it should not be more than 50%
|
# it should not be more than 50%
|
||||||
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
amount_reserve_percent = max(min(amount_reserve_percent, 1.5), 1)
|
||||||
@ -561,7 +584,7 @@ class Exchange:
|
|||||||
rate: float, params: Dict = {}) -> Dict[str, Any]:
|
rate: float, params: Dict = {}) -> Dict[str, Any]:
|
||||||
order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
|
order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
|
||||||
_amount = self.amount_to_precision(pair, amount)
|
_amount = self.amount_to_precision(pair, amount)
|
||||||
dry_order = {
|
dry_order: Dict[str, Any] = {
|
||||||
'id': order_id,
|
'id': order_id,
|
||||||
'symbol': pair,
|
'symbol': pair,
|
||||||
'price': rate,
|
'price': rate,
|
||||||
@ -572,31 +595,110 @@ class Exchange:
|
|||||||
'side': side,
|
'side': side,
|
||||||
'remaining': _amount,
|
'remaining': _amount,
|
||||||
'datetime': arrow.utcnow().isoformat(),
|
'datetime': arrow.utcnow().isoformat(),
|
||||||
'timestamp': int(arrow.utcnow().int_timestamp * 1000),
|
'timestamp': arrow.utcnow().int_timestamp * 1000,
|
||||||
'status': "closed" if ordertype == "market" else "open",
|
'status': "closed" if ordertype == "market" else "open",
|
||||||
'fee': None,
|
'fee': None,
|
||||||
'info': {}
|
'info': {}
|
||||||
}
|
}
|
||||||
self._store_dry_order(dry_order, pair)
|
if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]:
|
||||||
|
dry_order["info"] = {"stopPrice": dry_order["price"]}
|
||||||
|
|
||||||
|
if dry_order["type"] == "market":
|
||||||
|
# Update market order pricing
|
||||||
|
average = self.get_dry_market_fill_price(pair, side, amount, rate)
|
||||||
|
dry_order.update({
|
||||||
|
'average': average,
|
||||||
|
'cost': dry_order['amount'] * average,
|
||||||
|
})
|
||||||
|
dry_order = self.add_dry_order_fee(pair, dry_order)
|
||||||
|
|
||||||
|
dry_order = self.check_dry_limit_order_filled(dry_order)
|
||||||
|
|
||||||
|
self._dry_run_open_orders[dry_order["id"]] = dry_order
|
||||||
# Copy order and close it - so the returned order is open unless it's a market order
|
# Copy order and close it - so the returned order is open unless it's a market order
|
||||||
return dry_order
|
return dry_order
|
||||||
|
|
||||||
def _store_dry_order(self, dry_order: Dict, pair: str) -> None:
|
def add_dry_order_fee(self, pair: str, dry_order: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
closed_order = dry_order.copy()
|
dry_order.update({
|
||||||
if closed_order['type'] in ["market", "limit"]:
|
'fee': {
|
||||||
closed_order.update({
|
'currency': self.get_pair_quote_currency(pair),
|
||||||
'status': 'closed',
|
'cost': dry_order['cost'] * self.get_fee(pair),
|
||||||
'filled': closed_order['amount'],
|
'rate': self.get_fee(pair)
|
||||||
'remaining': 0,
|
}
|
||||||
'fee': {
|
})
|
||||||
'currency': self.get_pair_quote_currency(pair),
|
return dry_order
|
||||||
'cost': dry_order['cost'] * self.get_fee(pair),
|
|
||||||
'rate': self.get_fee(pair)
|
def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float) -> float:
|
||||||
}
|
"""
|
||||||
})
|
Get the market order fill price based on orderbook interpolation
|
||||||
if closed_order["type"] in ["stop_loss_limit", "stop-loss-limit"]:
|
"""
|
||||||
closed_order["info"].update({"stopPrice": closed_order["price"]})
|
if self.exchange_has('fetchL2OrderBook'):
|
||||||
self._dry_run_open_orders[closed_order["id"]] = closed_order
|
ob = self.fetch_l2_order_book(pair, 20)
|
||||||
|
ob_type = 'asks' if side == 'buy' else 'bids'
|
||||||
|
slippage = 0.05
|
||||||
|
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
|
||||||
|
|
||||||
|
remaining_amount = amount
|
||||||
|
filled_amount = 0
|
||||||
|
for book_entry in ob[ob_type]:
|
||||||
|
book_entry_price = book_entry[0]
|
||||||
|
book_entry_coin_volume = book_entry[1]
|
||||||
|
if remaining_amount > 0:
|
||||||
|
if remaining_amount < book_entry_coin_volume:
|
||||||
|
# Orderbook at this slot bigger than remaining amount
|
||||||
|
filled_amount += remaining_amount * book_entry_price
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
filled_amount += book_entry_coin_volume * book_entry_price
|
||||||
|
remaining_amount -= book_entry_coin_volume
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# If remaining_amount wasn't consumed completely (break was not called)
|
||||||
|
filled_amount += remaining_amount * book_entry_price
|
||||||
|
forecast_avg_filled_price = max(filled_amount, 0) / amount
|
||||||
|
# Limit max. slippage to specified value
|
||||||
|
if side == 'buy':
|
||||||
|
forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val)
|
||||||
|
|
||||||
|
else:
|
||||||
|
forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val)
|
||||||
|
|
||||||
|
return self.price_to_precision(pair, forecast_avg_filled_price)
|
||||||
|
|
||||||
|
return rate
|
||||||
|
|
||||||
|
def _is_dry_limit_order_filled(self, pair: str, side: str, limit: float) -> bool:
|
||||||
|
if not self.exchange_has('fetchL2OrderBook'):
|
||||||
|
return True
|
||||||
|
ob = self.fetch_l2_order_book(pair, 1)
|
||||||
|
if side == 'buy':
|
||||||
|
price = ob['asks'][0][0]
|
||||||
|
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}")
|
||||||
|
if limit >= price:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
price = ob['bids'][0][0]
|
||||||
|
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}")
|
||||||
|
if limit <= price:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_dry_limit_order_filled(self, order: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Check dry-run limit order fill and update fee (if it filled).
|
||||||
|
"""
|
||||||
|
if order['status'] != "closed" and order['type'] in ["limit"]:
|
||||||
|
pair = order['symbol']
|
||||||
|
if self._is_dry_limit_order_filled(pair, order['side'], order['price']):
|
||||||
|
order.update({
|
||||||
|
'status': 'closed',
|
||||||
|
'filled': order['amount'],
|
||||||
|
'remaining': 0,
|
||||||
|
})
|
||||||
|
self.add_dry_order_fee(pair, order)
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
def fetch_dry_run_order(self, order_id) -> Dict[str, Any]:
|
def fetch_dry_run_order(self, order_id) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@ -605,6 +707,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
order = self._dry_run_open_orders[order_id]
|
order = self._dry_run_open_orders[order_id]
|
||||||
|
order = self.check_dry_limit_order_filled(order)
|
||||||
return order
|
return order
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
# Gracefully handle errors with dry-run orders.
|
# Gracefully handle errors with dry-run orders.
|
||||||
@ -614,7 +717,17 @@ class Exchange:
|
|||||||
# Order handling
|
# Order handling
|
||||||
|
|
||||||
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||||
rate: float, params: Dict = {}) -> Dict:
|
rate: float, time_in_force: str = 'gtc') -> Dict:
|
||||||
|
|
||||||
|
if self._config['dry_run']:
|
||||||
|
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
|
||||||
|
return dry_order
|
||||||
|
|
||||||
|
params = self._params.copy()
|
||||||
|
if time_in_force != 'gtc' and ordertype != 'market':
|
||||||
|
param = self._ft_has.get('time_in_force_parameter', '')
|
||||||
|
params.update({param: time_in_force})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Set the precision for amount and price(rate) as accepted by the exchange
|
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||||
amount = self.amount_to_precision(pair, amount)
|
amount = self.amount_to_precision(pair, amount)
|
||||||
@ -622,8 +735,10 @@ class Exchange:
|
|||||||
or self._api.options.get("createMarketBuyOrderRequiresPrice", False))
|
or self._api.options.get("createMarketBuyOrderRequiresPrice", False))
|
||||||
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
|
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
|
||||||
|
|
||||||
return self._api.create_order(pair, ordertype, side,
|
order = self._api.create_order(pair, ordertype, side,
|
||||||
amount, rate_for_order, params)
|
amount, rate_for_order, params)
|
||||||
|
self._log_exchange_response('create_order', order)
|
||||||
|
return order
|
||||||
|
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise InsufficientFundsError(
|
raise InsufficientFundsError(
|
||||||
@ -643,32 +758,6 @@ class Exchange:
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
def buy(self, pair: str, ordertype: str, amount: float,
|
|
||||||
rate: float, time_in_force: str) -> Dict:
|
|
||||||
|
|
||||||
if self._config['dry_run']:
|
|
||||||
dry_order = self.create_dry_run_order(pair, ordertype, "buy", amount, rate)
|
|
||||||
return dry_order
|
|
||||||
|
|
||||||
params = self._params.copy()
|
|
||||||
if time_in_force != 'gtc' and ordertype != 'market':
|
|
||||||
params.update({'timeInForce': time_in_force})
|
|
||||||
|
|
||||||
return self.create_order(pair, ordertype, 'buy', amount, rate, params)
|
|
||||||
|
|
||||||
def sell(self, pair: str, ordertype: str, amount: float,
|
|
||||||
rate: float, time_in_force: str = 'gtc') -> Dict:
|
|
||||||
|
|
||||||
if self._config['dry_run']:
|
|
||||||
dry_order = self.create_dry_run_order(pair, ordertype, "sell", amount, rate)
|
|
||||||
return dry_order
|
|
||||||
|
|
||||||
params = self._params.copy()
|
|
||||||
if time_in_force != 'gtc' and ordertype != 'market':
|
|
||||||
params.update({'timeInForce': time_in_force})
|
|
||||||
|
|
||||||
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
|
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
@ -694,7 +783,9 @@ class Exchange:
|
|||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
return self.fetch_dry_run_order(order_id)
|
return self.fetch_dry_run_order(order_id)
|
||||||
try:
|
try:
|
||||||
return self._api.fetch_order(order_id, pair)
|
order = self._api.fetch_order(order_id, pair)
|
||||||
|
self._log_exchange_response('fetch_order', order)
|
||||||
|
return order
|
||||||
except ccxt.OrderNotFound as e:
|
except ccxt.OrderNotFound as e:
|
||||||
raise RetryableOrderError(
|
raise RetryableOrderError(
|
||||||
f'Order not found (pair: {pair} id: {order_id}). Message: {e}') from e
|
f'Order not found (pair: {pair} id: {order_id}). Message: {e}') from e
|
||||||
@ -717,6 +808,8 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Simple wrapper calling either fetch_order or fetch_stoploss_order depending on
|
Simple wrapper calling either fetch_order or fetch_stoploss_order depending on
|
||||||
the stoploss_order parameter
|
the stoploss_order parameter
|
||||||
|
:param order_id: OrderId to fetch order
|
||||||
|
:param pair: Pair corresponding to order_id
|
||||||
:param stoploss_order: If true, uses fetch_stoploss_order, otherwise fetch_order.
|
:param stoploss_order: If true, uses fetch_stoploss_order, otherwise fetch_order.
|
||||||
"""
|
"""
|
||||||
if stoploss_order:
|
if stoploss_order:
|
||||||
@ -729,7 +822,7 @@ class Exchange:
|
|||||||
:param order: Order dict as returned from fetch_order()
|
:param order: Order dict as returned from fetch_order()
|
||||||
:return: True if order has been cancelled without being filled, False otherwise.
|
:return: True if order has been cancelled without being filled, False otherwise.
|
||||||
"""
|
"""
|
||||||
return (order.get('status') in ('closed', 'canceled', 'cancelled')
|
return (order.get('status') in NON_OPEN_EXCHANGE_STATES
|
||||||
and order.get('filled') == 0.0)
|
and order.get('filled') == 0.0)
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
@ -744,7 +837,9 @@ class Exchange:
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._api.cancel_order(order_id, pair)
|
order = self._api.cancel_order(order_id, pair)
|
||||||
|
self._log_exchange_response('cancel_order', order)
|
||||||
|
return order
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Could not cancel order. Message: {e}') from e
|
f'Could not cancel order. Message: {e}') from e
|
||||||
@ -916,99 +1011,64 @@ class Exchange:
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1,
|
def get_rate(self, pair: str, refresh: bool, side: str) -> float:
|
||||||
order_book_min: int = 1):
|
|
||||||
"""
|
"""
|
||||||
Helper generator to query orderbook in loop (used for early sell-order placing)
|
Calculates bid/ask target
|
||||||
"""
|
bid rate - between current ask price and last price
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_max)
|
ask rate - either using ticker bid or first bid based on orderbook
|
||||||
for i in range(order_book_min, order_book_max + 1):
|
or remain static in any other case since it's not updating.
|
||||||
yield order_book[side][i - 1][0]
|
|
||||||
|
|
||||||
def get_buy_rate(self, pair: str, refresh: bool) -> float:
|
|
||||||
"""
|
|
||||||
Calculates bid target between current ask price and last price
|
|
||||||
:param pair: Pair to get rate for
|
:param pair: Pair to get rate for
|
||||||
:param refresh: allow cached data
|
:param refresh: allow cached data
|
||||||
|
:param side: "buy" or "sell"
|
||||||
:return: float: Price
|
:return: float: Price
|
||||||
:raises PricingError if orderbook price could not be determined.
|
:raises PricingError if orderbook price could not be determined.
|
||||||
"""
|
"""
|
||||||
|
cache_rate: TTLCache = self._buy_rate_cache if side == "buy" else self._sell_rate_cache
|
||||||
|
[strat_name, name] = ['bid_strategy', 'Buy'] if side == "buy" else ['ask_strategy', 'Sell']
|
||||||
|
|
||||||
if not refresh:
|
if not refresh:
|
||||||
rate = self._buy_rate_cache.get(pair)
|
rate = cache_rate.get(pair)
|
||||||
# Check if cache has been invalidated
|
# Check if cache has been invalidated
|
||||||
if rate:
|
if rate:
|
||||||
logger.debug(f"Using cached buy rate for {pair}.")
|
logger.debug(f"Using cached {side} rate for {pair}.")
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
bid_strategy = self._config.get('bid_strategy', {})
|
conf_strategy = self._config.get(strat_name, {})
|
||||||
if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False):
|
|
||||||
|
|
||||||
order_book_top = bid_strategy.get('order_book_top', 1)
|
if conf_strategy.get('use_order_book', False) and ('use_order_book' in conf_strategy):
|
||||||
|
|
||||||
|
order_book_top = conf_strategy.get('order_book_top', 1)
|
||||||
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
order_book = self.fetch_l2_order_book(pair, order_book_top)
|
||||||
logger.debug('order_book %s', order_book)
|
logger.debug('order_book %s', order_book)
|
||||||
# top 1 = index 0
|
# top 1 = index 0
|
||||||
try:
|
try:
|
||||||
rate_from_l2 = order_book[f"{bid_strategy['price_side']}s"][order_book_top - 1][0]
|
rate = order_book[f"{conf_strategy['price_side']}s"][order_book_top - 1][0]
|
||||||
except (IndexError, KeyError) as e:
|
except (IndexError, KeyError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Buy Price from orderbook could not be determined."
|
f"{name} Price at location {order_book_top} from orderbook could not be "
|
||||||
f"Orderbook: {order_book}"
|
f"determined. Orderbook: {order_book}"
|
||||||
)
|
)
|
||||||
raise PricingError from e
|
raise PricingError from e
|
||||||
logger.info(f"Buy price from orderbook {bid_strategy['price_side'].capitalize()} side "
|
price_side = {conf_strategy['price_side'].capitalize()}
|
||||||
f"- top {order_book_top} order book buy rate {rate_from_l2:.8f}")
|
logger.debug(f"{name} price from orderbook {price_side}"
|
||||||
used_rate = rate_from_l2
|
f"side - top {order_book_top} order book {side} rate {rate:.8f}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price")
|
logger.debug(f"Using Last {conf_strategy['price_side'].capitalize()} / Last Price")
|
||||||
ticker = self.fetch_ticker(pair)
|
ticker = self.fetch_ticker(pair)
|
||||||
ticker_rate = ticker[bid_strategy['price_side']]
|
ticker_rate = ticker[conf_strategy['price_side']]
|
||||||
if ticker['last'] and ticker_rate > ticker['last']:
|
if ticker['last'] and ticker_rate:
|
||||||
balance = bid_strategy['ask_last_balance']
|
if side == 'buy' and ticker_rate > ticker['last']:
|
||||||
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
balance = conf_strategy.get('ask_last_balance', 0.0)
|
||||||
used_rate = ticker_rate
|
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
|
||||||
|
elif side == 'sell' and ticker_rate < ticker['last']:
|
||||||
self._buy_rate_cache[pair] = used_rate
|
balance = conf_strategy.get('bid_last_balance', 0.0)
|
||||||
|
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
||||||
return used_rate
|
|
||||||
|
|
||||||
def get_sell_rate(self, pair: str, refresh: bool) -> float:
|
|
||||||
"""
|
|
||||||
Get sell rate - either using ticker bid or first bid based on orderbook
|
|
||||||
or remain static in any other case since it's not updating.
|
|
||||||
:param pair: Pair to get rate for
|
|
||||||
:param refresh: allow cached data
|
|
||||||
:return: Bid rate
|
|
||||||
:raises PricingError if price could not be determined.
|
|
||||||
"""
|
|
||||||
if not refresh:
|
|
||||||
rate = self._sell_rate_cache.get(pair)
|
|
||||||
# Check if cache has been invalidated
|
|
||||||
if rate:
|
|
||||||
logger.debug(f"Using cached sell rate for {pair}.")
|
|
||||||
return rate
|
|
||||||
|
|
||||||
ask_strategy = self._config.get('ask_strategy', {})
|
|
||||||
if ask_strategy.get('use_order_book', False):
|
|
||||||
# This code is only used for notifications, selling uses the generator directly
|
|
||||||
logger.info(
|
|
||||||
f"Getting price from order book {ask_strategy['price_side'].capitalize()} side."
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
rate = next(self._order_book_gen(pair, f"{ask_strategy['price_side']}s"))
|
|
||||||
except (IndexError, KeyError) as e:
|
|
||||||
logger.warning("Sell Price at location from orderbook could not be determined.")
|
|
||||||
raise PricingError from e
|
|
||||||
else:
|
|
||||||
ticker = self.fetch_ticker(pair)
|
|
||||||
ticker_rate = ticker[ask_strategy['price_side']]
|
|
||||||
if ticker['last'] and ticker_rate < ticker['last']:
|
|
||||||
balance = ask_strategy.get('bid_last_balance', 0.0)
|
|
||||||
ticker_rate = ticker_rate - balance * (ticker_rate - ticker['last'])
|
|
||||||
rate = ticker_rate
|
rate = ticker_rate
|
||||||
|
|
||||||
if rate is None:
|
if rate is None:
|
||||||
raise PricingError(f"Sell-Rate for {pair} was empty.")
|
raise PricingError(f"{name}-Rate for {pair} was empty.")
|
||||||
self._sell_rate_cache[pair] = rate
|
cache_rate[pair] = rate
|
||||||
|
|
||||||
return rate
|
return rate
|
||||||
|
|
||||||
# Fee handling
|
# Fee handling
|
||||||
@ -1042,6 +1102,7 @@ class Exchange:
|
|||||||
pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000))
|
pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000))
|
||||||
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
|
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
|
||||||
|
|
||||||
|
self._log_exchange_response('get_trades_for_order', matched_trades)
|
||||||
return matched_trades
|
return matched_trades
|
||||||
except ccxt.DDoSProtection as e:
|
except ccxt.DDoSProtection as e:
|
||||||
raise DDosProtection(e) from e
|
raise DDosProtection(e) from e
|
||||||
@ -1134,7 +1195,7 @@ class Exchange:
|
|||||||
# Historic data
|
# Historic data
|
||||||
|
|
||||||
def get_historic_ohlcv(self, pair: str, timeframe: str,
|
def get_historic_ohlcv(self, pair: str, timeframe: str,
|
||||||
since_ms: int) -> List:
|
since_ms: int, is_new_pair: bool = False) -> List:
|
||||||
"""
|
"""
|
||||||
Get candle history using asyncio and returns the list of candles.
|
Get candle history using asyncio and returns the list of candles.
|
||||||
Handles all async work for this.
|
Handles all async work for this.
|
||||||
@ -1146,7 +1207,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe,
|
self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe,
|
||||||
since_ms=since_ms))
|
since_ms=since_ms, is_new_pair=is_new_pair))
|
||||||
|
|
||||||
def get_historic_ohlcv_as_df(self, pair: str, timeframe: str,
|
def get_historic_ohlcv_as_df(self, pair: str, timeframe: str,
|
||||||
since_ms: int) -> DataFrame:
|
since_ms: int) -> DataFrame:
|
||||||
@ -1161,11 +1222,12 @@ class Exchange:
|
|||||||
return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True,
|
return ohlcv_to_dataframe(ticks, timeframe, pair=pair, fill_missing=True,
|
||||||
drop_incomplete=self._ohlcv_partial_candle)
|
drop_incomplete=self._ohlcv_partial_candle)
|
||||||
|
|
||||||
async def _async_get_historic_ohlcv(self, pair: str,
|
async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
|
||||||
timeframe: str,
|
since_ms: int, is_new_pair: bool
|
||||||
since_ms: int) -> List:
|
) -> List:
|
||||||
"""
|
"""
|
||||||
Download historic ohlcv
|
Download historic ohlcv
|
||||||
|
:param is_new_pair: used by binance subclass to allow "fast" new pair downloading
|
||||||
"""
|
"""
|
||||||
|
|
||||||
one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe)
|
one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe)
|
||||||
@ -1178,21 +1240,22 @@ class Exchange:
|
|||||||
pair, timeframe, since) for since in
|
pair, timeframe, since) for since in
|
||||||
range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)]
|
range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)]
|
||||||
|
|
||||||
results = await asyncio.gather(*input_coroutines, return_exceptions=True)
|
|
||||||
|
|
||||||
# Combine gathered results
|
|
||||||
data: List = []
|
data: List = []
|
||||||
for res in results:
|
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
|
||||||
if isinstance(res, Exception):
|
for input_coro in chunks(input_coroutines, 100):
|
||||||
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
|
||||||
continue
|
results = await asyncio.gather(*input_coro, return_exceptions=True)
|
||||||
# Deconstruct tuple if it's not an exception
|
for res in results:
|
||||||
p, _, new_data = res
|
if isinstance(res, Exception):
|
||||||
if p == pair:
|
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
|
||||||
data.extend(new_data)
|
continue
|
||||||
|
# Deconstruct tuple if it's not an exception
|
||||||
|
p, _, new_data = res
|
||||||
|
if p == pair:
|
||||||
|
data.extend(new_data)
|
||||||
# Sort data again after extending the result - above calls return in "async order"
|
# Sort data again after extending the result - above calls return in "async order"
|
||||||
data = sorted(data, key=lambda x: x[0])
|
data = sorted(data, key=lambda x: x[0])
|
||||||
logger.info("Downloaded data for %s with length %s.", pair, len(data))
|
logger.info(f"Downloaded data for {pair} with length {len(data)}.")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *,
|
def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *,
|
||||||
@ -1210,7 +1273,7 @@ class Exchange:
|
|||||||
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
|
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
|
||||||
|
|
||||||
input_coroutines = []
|
input_coroutines = []
|
||||||
|
cached_pairs = []
|
||||||
# Gather coroutines to run
|
# Gather coroutines to run
|
||||||
for pair, timeframe in set(pair_list):
|
for pair, timeframe in set(pair_list):
|
||||||
if (((pair, timeframe) not in self._klines)
|
if (((pair, timeframe) not in self._klines)
|
||||||
@ -1222,6 +1285,7 @@ class Exchange:
|
|||||||
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
|
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
|
||||||
pair, timeframe
|
pair, timeframe
|
||||||
)
|
)
|
||||||
|
cached_pairs.append((pair, timeframe))
|
||||||
|
|
||||||
results = asyncio.get_event_loop().run_until_complete(
|
results = asyncio.get_event_loop().run_until_complete(
|
||||||
asyncio.gather(*input_coroutines, return_exceptions=True))
|
asyncio.gather(*input_coroutines, return_exceptions=True))
|
||||||
@ -1239,11 +1303,15 @@ class Exchange:
|
|||||||
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
|
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
|
||||||
# keeping parsed dataframe in cache
|
# keeping parsed dataframe in cache
|
||||||
ohlcv_df = ohlcv_to_dataframe(
|
ohlcv_df = ohlcv_to_dataframe(
|
||||||
ticks, timeframe, pair=pair, fill_missing=True,
|
ticks, timeframe, pair=pair, fill_missing=True,
|
||||||
drop_incomplete=self._ohlcv_partial_candle)
|
drop_incomplete=self._ohlcv_partial_candle)
|
||||||
results_df[(pair, timeframe)] = ohlcv_df
|
results_df[(pair, timeframe)] = ohlcv_df
|
||||||
if cache:
|
if cache:
|
||||||
self._klines[(pair, timeframe)] = ohlcv_df
|
self._klines[(pair, timeframe)] = ohlcv_df
|
||||||
|
# Return cached klines
|
||||||
|
for pair, timeframe in cached_pairs:
|
||||||
|
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)
|
||||||
|
|
||||||
return results_df
|
return results_df
|
||||||
|
|
||||||
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
|
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
|
||||||
@ -1454,7 +1522,7 @@ class Exchange:
|
|||||||
:returns List of trade data
|
:returns List of trade data
|
||||||
"""
|
"""
|
||||||
if not self.exchange_has("fetchTrades"):
|
if not self.exchange_has("fetchTrades"):
|
||||||
raise OperationalException("This exchange does not suport downloading Trades.")
|
raise OperationalException("This exchange does not support downloading Trades.")
|
||||||
|
|
||||||
return asyncio.get_event_loop().run_until_complete(
|
return asyncio.get_event_loop().run_until_complete(
|
||||||
self._async_get_trade_history(pair=pair, since=since,
|
self._async_get_trade_history(pair=pair, since=since,
|
||||||
|
@ -69,6 +69,7 @@ class Ftx(Exchange):
|
|||||||
|
|
||||||
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
|
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
|
||||||
amount=amount, params=params)
|
amount=amount, params=params)
|
||||||
|
self._log_exchange_response('create_stoploss_order', order)
|
||||||
logger.info('stoploss order added for %s. '
|
logger.info('stoploss order added for %s. '
|
||||||
'stop price: %s.', pair, stop_price)
|
'stop price: %s.', pair, stop_price)
|
||||||
return order
|
return order
|
||||||
@ -99,12 +100,14 @@ class Ftx(Exchange):
|
|||||||
orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})
|
orders = self._api.fetch_orders(pair, None, params={'type': 'stop'})
|
||||||
|
|
||||||
order = [order for order in orders if order['id'] == order_id]
|
order = [order for order in orders if order['id'] == order_id]
|
||||||
|
self._log_exchange_response('fetch_stoploss_order', order)
|
||||||
if len(order) == 1:
|
if len(order) == 1:
|
||||||
if order[0].get('status') == 'closed':
|
if order[0].get('status') == 'closed':
|
||||||
# Trigger order was triggered ...
|
# Trigger order was triggered ...
|
||||||
real_order_id = order[0].get('info', {}).get('orderId')
|
real_order_id = order[0].get('info', {}).get('orderId')
|
||||||
|
|
||||||
order1 = self._api.fetch_order(real_order_id, pair)
|
order1 = self._api.fetch_order(real_order_id, pair)
|
||||||
|
self._log_exchange_response('fetch_stoploss_order1', order1)
|
||||||
# Fake type to stop - as this was really a stop order.
|
# Fake type to stop - as this was really a stop order.
|
||||||
order1['id_stop'] = order1['id']
|
order1['id_stop'] = order1['id']
|
||||||
order1['id'] = order_id
|
order1['id'] = order_id
|
||||||
@ -131,7 +134,9 @@ class Ftx(Exchange):
|
|||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
return self._api.cancel_order(order_id, pair, params={'type': 'stop'})
|
order = self._api.cancel_order(order_id, pair, params={'type': 'stop'})
|
||||||
|
self._log_exchange_response('cancel_stoploss_order', order)
|
||||||
|
return order
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Could not cancel order. Message: {e}') from e
|
f'Could not cancel order. Message: {e}') from e
|
||||||
|
33
freqtrade/exchange/gateio.py
Normal file
33
freqtrade/exchange/gateio.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
""" Gate.io exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Gateio(Exchange):
|
||||||
|
"""
|
||||||
|
Gate.io exchange class. Contains adjustments needed for Freqtrade to work
|
||||||
|
with this exchange.
|
||||||
|
|
||||||
|
Please note that this exchange is not included in the list of exchanges
|
||||||
|
officially supported by the Freqtrade development team. So some features
|
||||||
|
may still not work as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_ft_has: Dict = {
|
||||||
|
"ohlcv_candle_limit": 1000,
|
||||||
|
}
|
||||||
|
|
||||||
|
_headers = {'X-Gate-Channel-Id': 'freqtrade'}
|
||||||
|
|
||||||
|
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||||
|
super().validate_ordertypes(order_types)
|
||||||
|
|
||||||
|
if any(v == 'market' for k, v in order_types.items()):
|
||||||
|
raise OperationalException(
|
||||||
|
f'Exchange {self.name} does not support market orders.')
|
@ -49,7 +49,7 @@ class Kraken(Exchange):
|
|||||||
orders = self._api.fetch_open_orders()
|
orders = self._api.fetch_open_orders()
|
||||||
order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1],
|
order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1],
|
||||||
x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"],
|
x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"],
|
||||||
# Don't remove the below comment, this can be important for debuggung
|
# Don't remove the below comment, this can be important for debugging
|
||||||
# x["side"], x["amount"],
|
# x["side"], x["amount"],
|
||||||
) for x in orders]
|
) for x in orders]
|
||||||
for bal in balances:
|
for bal in balances:
|
||||||
@ -103,6 +103,7 @@ class Kraken(Exchange):
|
|||||||
|
|
||||||
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
|
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
|
||||||
amount=amount, price=stop_price, params=params)
|
amount=amount, price=stop_price, params=params)
|
||||||
|
self._log_exchange_response('create_stoploss_order', order)
|
||||||
logger.info('stoploss order added for %s. '
|
logger.info('stoploss order added for %s. '
|
||||||
'stop price: %s.', pair, stop_price)
|
'stop price: %s.', pair, stop_price)
|
||||||
return order
|
return order
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user