Merge branch 'develop' into pr/rextea/4606

This commit is contained in:
Matthias 2021-10-17 16:29:19 +02:00
commit 47bba331c1
287 changed files with 17159 additions and 9644 deletions

View File

@ -1,20 +1,21 @@
FROM freqtradeorg/freqtrade:develop
USER root
# Install dependencies
COPY requirements-dev.txt /freqtrade/
RUN apt-get update \
&& apt-get -y install git mercurial sudo vim \
&& apt-get -y install git mercurial sudo vim build-essential \
&& apt-get clean \
&& pip install autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir \
&& useradd -u 1000 -U -m ftuser \
&& mkdir -p /home/ftuser/.vscode-server /home/ftuser/.vscode-server-insiders /home/ftuser/commandhistory \
&& echo "export PROMPT_COMMAND='history -a'" >> /home/ftuser/.bashrc \
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/ftuser/.bashrc \
&& mv /root/.local /home/ftuser/.local/ \
&& chown ftuser:ftuser -R /home/ftuser/.local/ \
&& chown ftuser: -R /home/ftuser/
USER ftuser
RUN pip install --user autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []

View File

@ -1,11 +1,20 @@
{
"name": "freqtrade Develop",
"dockerComposeFile": [
"docker-compose.yml"
"build": {
"dockerfile": "Dockerfile",
"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/",
@ -25,20 +34,6 @@
"ms-python.vscode-pylance",
"davidanson.vscode-markdownlint",
"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"
}

View File

@ -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:

View File

@ -1,9 +1,9 @@
.git
.gitignore
Dockerfile
Dockerfile.armhf
.dockerignore
config.json*
*.sqlite
docker/
.coveragerc
.eggs
.github
@ -13,4 +13,13 @@ CONTRIBUTING.md
MANIFEST.in
README.md
freqtrade.service
freqtrade.egg-info
config.json*
*.sqlite
user_data
*.log
.vscode
.mypy_cache
.ipynb_checkpoints

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
*.py eol=lf
*.sh eol=lf
*.ps1 eol=crlf

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,6 @@
---
blank_issues_enabled: false
contact_links:
- name: Discord Server
url: https://discord.gg/p7nuUNVfP7
about: Ask a question or get community support from our Discord server

View File

@ -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)
## Summary
Explain in one sentence the goal of this PR
Solve the issue: #___
## Quick changelog
- <change log #1>
- <change log #2>
- <change log 1>
- <change log 1>
## What's new?
*Explain in details what this PR solve or improve. You can include visuals.*

View File

@ -75,19 +75,19 @@ jobs:
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
run: |
# Allow failure for coveralls
coveralls -v || true
coveralls || true
- name: Backtesting
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
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
run: |
@ -148,6 +148,7 @@ jobs:
- name: Installation - macOS
run: |
brew update
brew install hdf5 c-blosc
python -m pip install --upgrade pip
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
@ -171,15 +172,15 @@ jobs:
- name: Backtesting
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
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
run: |
@ -238,15 +239,15 @@ jobs:
- name: Backtesting
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config_bittrex.json.example config.json
cp config_examples/config_bittrex.example.json config.json
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
run: |
@ -300,7 +301,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Cleanup previous runs on this branch
uses: rokroskar/workflow-run-cleanup-action@v0.2.2
uses: rokroskar/workflow-run-cleanup-action@v0.3.3
if: "!startsWith(github.ref, 'refs/tags/') && github.ref != 'refs/heads/stable' && github.repository == 'freqtrade/freqtrade'"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
@ -310,9 +311,18 @@ jobs:
needs: [ build_linux, build_macos, build_windows, docs_check ]
runs-on: ubuntu-20.04
steps:
- name: Check user permission
id: check
uses: scherermichael-oss/action-has-permission@1.0.6
with:
required-permission: write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI*'
@ -324,6 +334,7 @@ jobs:
runs-on: ubuntu-20.04
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v2
@ -364,13 +375,6 @@ jobs:
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Build and test and push docker image
env:
IMAGE_NAME: freqtradeorg/freqtrade
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
run: |
build_helpers/publish_docker.sh
# We need docker experimental to pull the ARM image.
- name: Switch docker to experimental
run: |
@ -389,12 +393,12 @@ jobs:
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Build Raspberry docker image
- name: Build and test and push docker images
env:
IMAGE_NAME: freqtradeorg/freqtrade
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}_pi
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
run: |
build_helpers/publish_docker_pi.sh
build_helpers/publish_docker_multi.sh
- name: Slack Notification
@ -408,3 +412,31 @@ jobs:
channel: '#notifications'
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
View File

@ -95,3 +95,8 @@ target/
#exceptions
!*.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

View File

@ -26,14 +26,14 @@ jobs:
# - coveralls || true
name: pytest
- script:
- cp config_bittrex.json.example config.json
- cp config_examples/config_bittrex.example.json config.json
- freqtrade create-userdir --userdir user_data
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
name: backtest
- script:
- cp config_bittrex.json.example config.json
- cp config_examples/config_bittrex.example.json config.json
- 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
- script: flake8
name: flake8
@ -46,12 +46,6 @@ jobs:
- script: mypy freqtrade scripts
name: mypy
# - stage: docker
# if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron))
# script:
# - build_helpers/publish_docker.sh
# name: "Build and test and push docker image"
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=

View File

@ -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.
- 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/MA9v74M), 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

View File

@ -1,20 +1,29 @@
FROM python:3.9.2-slim-buster as base
FROM python:3.9.7-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=/root/.local/bin:$PATH
ENV PATH=/home/ftuser/.local/bin:$PATH
ENV FT_APP_ENV="docker"
# Prepare environment
RUN mkdir /freqtrade
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 -s /bin/bash 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 curl build-essential libssl-dev git \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& pip install --upgrade pip
@ -24,7 +33,8 @@ RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies
COPY requirements.txt requirements-hyperopt.txt /freqtrade/
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
@ -33,13 +43,13 @@ 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 /root/.local /root/.local
COPY --from=python-deps --chown=ftuser:ftuser /home/ftuser/.local /home/ftuser/.local
USER ftuser
# Install and execute
COPY . /freqtrade/
RUN pip install -e . --no-cache-dir \
COPY --chown=ftuser:ftuser . /freqtrade/
RUN pip install -e . --user --no-cache-dir --no-build-isolation \
&& mkdir /freqtrade/user_data/ \
&& freqtrade install-ui

View File

@ -1,4 +1,4 @@
# Freqtrade
# ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade_poweredby.svg)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
@ -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.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist))
- [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] [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)_
### 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:
- [X] [Bitvavo](https://bitvavo.com/)
- [X] [Kucoin](https://www.kucoin.com/)
## 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] **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] **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] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
@ -69,7 +71,7 @@ cd freqtrade
./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
@ -77,22 +79,22 @@ For any other type of installation please refer to [Installation doc](https://ww
```
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
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.
create-userdir Create user-data directory.
new-config Create new config
new-hyperopt Create new hyperopt
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
list-data List downloaded data.
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
@ -106,8 +108,10 @@ positional arguments:
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
optional arguments:
-h, --help show this help message and exit
@ -123,7 +127,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
- `/stop`: Stops the trader.
- `/stopbuy`: Stop entering new trades.
- `/status <trade_id>|[table]`: Lists all or specific open trades.
- `/profit`: Lists cumulative profit from all finished trades
- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days.
- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`).
- `/performance`: Show performance of each finished trade grouped by pair
- `/balance`: Show account balance per currency.
@ -141,20 +145,16 @@ The project is currently setup in two main branches:
## 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.
Please check out our [discord server](https://discord.gg/MA9v74M).
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
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).
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
If you discover a bug in the bot, please
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new/choose) and
ensure you follow the template guide so that our team can assist you as
quickly as possible.
@ -163,7 +163,7 @@ quickly as possible.
Have you a great idea to improve the bot you want to share? Please,
first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
If it hasn't been requested, please
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
[create a new request](https://github.com/freqtrade/freqtrade/issues/new/choose)
and ensure you follow the template guide so that it does not get lost
in the bug reports.
@ -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?
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/MA9v74M) 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`.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,10 +8,21 @@ if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib \
&& sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
&& 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 \
&& ./configure --prefix=${INSTALL_LOC}/ \
&& make \
&& which sudo && sudo make install || make install \
&& cd ..
&& make
if [ $? -ne 0 ]; then
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
echo "TA-lib already installed, skipping installation"
fi

View File

@ -1,16 +1,18 @@
# Downloads don't work automatically, since the URL is regenerated via javascript.
# Downloaded from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib
# Invoke-WebRequest -Uri "https://download.lfd.uci.edu/pythonlibs/xxxxxxx/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -OutFile "TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl"
python -m pip install --upgrade pip
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.7') {
pip install build_helpers\TA_Lib-0.4.19-cp37-cp37m-win_amd64.whl
pip install build_helpers\TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
}
if ($pyv -eq '3.8') {
pip install build_helpers\TA_Lib-0.4.19-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

View File

@ -1,59 +0,0 @@
#!/bin/sh
# Replace / with _ to create a valid tag
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
TAG_PLOT=${TAG}_plot
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"
docker build -t freqtrade:${TAG} .
else
echo "event ${GITHUB_EVENT_NAME}: building with cache"
# Pull last build to avoid rebuilding the whole image
docker pull ${IMAGE_NAME}:${TAG}
docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} .
fi
# Tag image for upload and next build step
docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG
docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
docker tag freqtrade:$TAG_PLOT ${IMAGE_NAME}:$TAG_PLOT
if [ $? -ne 0 ]; then
echo "failed building image"
return 1
fi
# 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
if [ $? -ne 0 ]; then
echo "failed running backtest"
return 1
fi
if [ $? -ne 0 ]; then
echo "failed tagging image"
return 1
fi
# Tag as latest for develop builds
if [ "${TAG}" = "develop" ]; then
docker tag freqtrade:$TAG ${IMAGE_NAME}:latest
fi
# Show all available images
docker images
docker push ${IMAGE_NAME}
docker push ${IMAGE_NAME}:$TAG_PLOT
docker push ${IMAGE_NAME}:$TAG
if [ $? -ne 0 ]; then
echo "failed pushing repo"
return 1
fi

View 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"

View File

@ -0,0 +1,75 @@
#!/bin/sh
# The below assumes a correctly setup docker buildx environment
# Replace / with _ to create a valid tag
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
TAG_PLOT=${TAG}_plot
TAG_PI="${TAG}_pi"
PI_PLATFORM="linux/arm/v7"
echo "Running for ${TAG}"
CACHE_IMAGE=freqtradeorg/freqtrade_cache
CACHE_TAG=${CACHE_IMAGE}:${TAG_PI}_cache
# 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} .
# Build PI image
docker buildx build \
--cache-to=type=registry,ref=${CACHE_TAG} \
-f docker/Dockerfile.armhf \
--platform ${PI_PLATFORM} \
-t ${IMAGE_NAME}:${TAG_PI} --push .
else
echo "event ${GITHUB_EVENT_NAME}: building with cache"
# Build regular image
docker pull ${IMAGE_NAME}:${TAG}
docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} .
# Pull last build to avoid rebuilding the whole image
# docker pull --platform ${PI_PLATFORM} ${IMAGE_NAME}:${TAG}
docker buildx build \
--cache-from=type=registry,ref=${CACHE_TAG} \
--cache-to=type=registry,ref=${CACHE_TAG} \
-f docker/Dockerfile.armhf \
--platform ${PI_PLATFORM} \
-t ${IMAGE_NAME}:${TAG_PI} --push .
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 ${CACHE_IMAGE}:$TAG
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 ${CACHE_IMAGE}:$TAG_PLOT
# Run backtest
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
echo "failed running backtest"
return 1
fi
docker images
docker push ${CACHE_IMAGE}
docker push ${CACHE_IMAGE}:$TAG_PLOT
docker push ${CACHE_IMAGE}:$TAG
docker images
if [ $? -ne 0 ]; then
echo "failed building image"
return 1
fi

View File

@ -1,36 +0,0 @@
#!/bin/sh
# The below assumes a correctly setup docker buildx environment
# Replace / with _ to create a valid tag
TAG=$(echo "${BRANCH_NAME}" | sed -e "s/\//_/g")
PI_PLATFORM="linux/arm/v7"
echo "Running for ${TAG}"
CACHE_TAG=freqtradeorg/freqtrade_cache:${TAG}_cache
# 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"
docker buildx build \
--cache-to=type=registry,ref=${CACHE_TAG} \
-f Dockerfile.armhf \
--platform ${PI_PLATFORM} \
-t ${IMAGE_NAME}:${TAG} --push .
else
echo "event ${GITHUB_EVENT_NAME}: building with cache"
# Pull last build to avoid rebuilding the whole image
# docker pull --platform ${PI_PLATFORM} ${IMAGE_NAME}:${TAG}
docker buildx build \
--cache-from=type=registry,ref=${CACHE_TAG} \
--cache-to=type=registry,ref=${CACHE_TAG} \
-f Dockerfile.armhf \
--platform ${PI_PLATFORM} \
-t ${IMAGE_NAME}:${TAG} --push .
fi
if [ $? -ne 0 ]; then
echo "failed building image"
return 1
fi

View File

@ -13,7 +13,7 @@
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"use_order_book": true,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
@ -21,21 +21,15 @@
}
},
"ask_strategy": {
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_config": {},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 200
},
"pair_whitelist": [
"ALGO/BTC",

View File

@ -12,7 +12,7 @@
"sell": 30
},
"bid_strategy": {
"use_order_book": false,
"use_order_book": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
@ -21,12 +21,8 @@
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "bittrex",

View File

@ -0,0 +1,92 @@
{
"max_open_trades": 3,
"stake_currency": "USD",
"stake_amount": 50,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"timeframe": "5m",
"dry_run": true,
"cancel_open_orders_on_exit": false,
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": true,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy": {
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "ftx",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"BTC/USD",
"ETH/USD",
"BNB/USD",
"USDT/USD",
"LTC/USD",
"SRM/USD",
"SXP/USD",
"XRP/USD",
"DOGE/USD",
"1INCH/USD",
"CHZ/USD",
"MATIC/USD",
"LINK/USD",
"OXY/USD",
"SUSHI/USD"
],
"pair_blacklist": [
"FTT/USD"
]
},
"pairlists": [
{"method": "StaticPairList"}
],
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "freqtrader",
"password": "SuperSecurePassword"
},
"bot_name": "freqtrade",
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}
}

View File

@ -14,6 +14,10 @@
"trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"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": {
"40": 0.0,
"30": 0.01,
@ -23,11 +27,12 @@
"stoploss": -0.10,
"unfilledtimeout": {
"buy": 10,
"sell": 30
"sell": 30,
"unit": "minutes"
},
"bid_strategy": {
"price_side": "bid",
"use_order_book": false,
"use_order_book": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
@ -37,13 +42,8 @@
},
"ask_strategy":{
"price_side": "ask",
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": false
"use_order_book": true,
"order_book_top": 1
},
"order_types": {
"buy": "limit",
@ -78,45 +78,14 @@
"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": {
"name": "binance",
"sandbox": false,
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"password": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"rateLimit": 500,
"aiohttp_trust_env": false
},
"ccxt_config": {},
"ccxt_async_config": {},
"pair_whitelist": [
"ALGO/BTC",
"ATOM/BTC",
@ -163,10 +132,25 @@
"warning": "on",
"startup": "on",
"buy": "on",
"sell": "on",
"buy_fill": "on",
"sell": {
"roi": "off",
"emergency_sell": "off",
"force_sell": "off",
"sell_signal": "off",
"trailing_stop_loss": "off",
"stop_loss": "off",
"stoploss_on_exchange": "off",
"custom_sell": "off"
},
"sell_fill": "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": {
"enabled": false,
@ -188,7 +172,7 @@
"heartbeat_interval": 60
},
"disable_dataframe_checks": false,
"strategy": "DefaultStrategy",
"strategy": "SampleStrategy",
"strategy_path": "user_data/strategies/",
"dataformat_ohlcv": "json",
"dataformat_trades": "jsongz"

View File

@ -12,7 +12,7 @@
"sell": 30
},
"bid_strategy": {
"use_order_book": false,
"use_order_book": true,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
@ -21,21 +21,15 @@
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
"use_order_book": true,
"order_book_top": 1
},
"exchange": {
"name": "kraken",
"key": "your_exchange_key",
"secret": "your_exchange_key",
"ccxt_config": {"enableRateLimit": true},
"ccxt_config": {},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000
},
"pair_whitelist": [
"ADA/EUR",

View File

@ -9,16 +9,16 @@ services:
# Build step - only needed when additional dependencies are needed
# build:
# context: .
# dockerfile: "./docker/Dockerfile.technical"
# dockerfile: "./docker/Dockerfile.custom"
restart: unless-stopped
container_name: freqtrade
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
# Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation
# before enabling this.
# ports:
# - "127.0.0.1:8080:8080"
ports:
- "127.0.0.1:8080:8080"
# Default command used when running `docker compose up`
command: >
trade

View File

@ -1,23 +1,29 @@
FROM --platform=linux/arm/v7 python:3.7.9-slim-buster as base
FROM python:3.7.10-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=/root/.local/bin:$PATH
ENV PATH=/home/ftuser/.local/bin:$PATH
ENV FT_APP_ENV="docker"
# Prepare environment
RUN mkdir /freqtrade
WORKDIR /freqtrade
RUN mkdir /freqtrade \
&& apt-get update \
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-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
RUN apt-get update \
&& apt-get -y install libatlas3-base curl sqlite3 \
&& apt-get clean
WORKDIR /freqtrade
# Install dependencies
FROM base as python-deps
RUN apt-get -y install build-essential libssl-dev libffi-dev libgfortran5 \
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& pip install --upgrade pip \
&& echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > /etc/pip.conf
@ -28,7 +34,8 @@ RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies
COPY requirements.txt /freqtrade/
COPY --chown=ftuser:ftuser requirements.txt /freqtrade/
USER ftuser
RUN pip install --user --no-cache-dir numpy \
&& pip install --user --no-cache-dir -r requirements.txt
@ -37,13 +44,14 @@ 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 /root/.local /root/.local
COPY --from=python-deps --chown=ftuser:ftuser /home/ftuser/.local /home/ftuser/.local
USER ftuser
# Install and execute
COPY . /freqtrade/
RUN apt-get install -y libhdf5-serial-dev \
&& apt-get clean \
&& pip install -e . --no-cache-dir \
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"]

10
docker/Dockerfile.custom Normal file
View File

@ -0,0 +1,10 @@
FROM freqtradeorg/freqtrade:develop
# Switch user to root if you must install something from apt
# Don't forget to switch the user back below!
# USER root
# The below dependency - pyti - serves as an example. Please use whatever you need!
RUN pip install --user pyti
# USER ftuser

View File

@ -3,8 +3,8 @@ FROM freqtradeorg/freqtrade:develop
# Install dependencies
COPY requirements-dev.txt /freqtrade/
RUN pip install numpy --no-cache-dir \
&& pip install -r requirements-dev.txt --no-cache-dir
RUN pip install numpy --user --no-cache-dir \
&& pip install -r requirements-dev.txt --user --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []

View File

@ -1,7 +1,7 @@
FROM freqtradeorg/freqtrade:develop_plot
RUN pip install jupyterlab --no-cache-dir
RUN pip install jupyterlab --user --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []

View File

@ -1,7 +1,8 @@
ARG sourceimage=develop
FROM freqtradeorg/freqtrade:${sourceimage}
ARG sourceimage=freqtradeorg/freqtrade
ARG sourcetag=develop
FROM ${sourceimage}:${sourcetag}
# Install dependencies
COPY requirements-plot.txt /freqtrade/
RUN pip install -r requirements-plot.txt --no-cache-dir
RUN pip install -r requirements-plot.txt --user --no-cache-dir

View File

@ -1,6 +0,0 @@
FROM freqtradeorg/freqtrade:develop
RUN apt-get update \
&& apt-get -y install git \
&& apt-get clean \
&& pip install git+https://github.com/freqtrade/technical

View File

@ -4,79 +4,6 @@ This page explains some advanced Hyperopt topics that may require higher
coding skills and Python knowledge than creation of an ordinal hyperoptimization
class.
## Derived hyperopt classes
Custom hyperopt classes can be derived in the same way [it can be done for strategies](strategy-customization.md#derived-strategies).
Applying to hyperoptimization, as an example, you may override how dimensions are defined in your optimization hyperspace:
```python
class MyAwesomeHyperOpt(IHyperOpt):
...
# Uses default stoploss dimension
class MyAwesomeHyperOpt2(MyAwesomeHyperOpt):
@staticmethod
def stoploss_space() -> List[Dimension]:
# Override boundaries for stoploss
return [
Real(-0.33, -0.01, name='stoploss'),
]
```
and then quickly switch between hyperopt classes, running optimization process with hyperopt class you need in each particular case:
```
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --strategy MyAwesomeStrategy ...
or
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt2 --hyperopt-loss SharpeHyperOptLossDaily --strategy MyAwesomeStrategy ...
```
## 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
```
## Creating and using a custom loss function
To use a custom loss function class, make sure that the function `hyperopt_loss_function` is defined in your custom hyperopt loss class.
@ -105,6 +32,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for better results
@ -126,7 +54,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
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`):
`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)`)
@ -134,11 +62,92 @@ Currently, the arguments are:
* `min_date`: End date of the timerange used
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
* `backtest_stats`: Backtesting statistics using the same format as the backtesting file "strategy" substructure. Available fields can be seen in `generate_strategy_stats()` in `optimize_reports.py`.
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
!!! Note
This function is called once per 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 "`*args` and `**kwargs`"
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface in the future.
## Overriding pre-defined spaces
To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_space`, `trailing_space`), define a nested class called Hyperopt and define the required spaces as follows:
```python
class MyAwesomeStrategy(IStrategy):
class HyperOpt:
# Define a custom stoploss space.
def stoploss_space():
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
# Define custom ROI space
def roi_space() -> List[Dimension]:
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
SKDecimal(0.01, 0.04, decimals=3, name='roi_p1'),
SKDecimal(0.01, 0.07, decimals=3, name='roi_p2'),
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
]
```
!!! Note
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface later.
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
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
* `Categorical` - Pick from a list of categories (e.g. `Categorical(['a', 'b', 'c'], name="cat")`)
* `Integer` - Pick from a range of whole numbers (e.g. `Integer(1, 10, name='rsi')`)
* `SKDecimal` - Pick from a range of decimal numbers with limited precision (e.g. `SKDecimal(0.1, 0.5, decimals=3, name='adx')`). *Available only with freqtrade*.
* `Real` - Pick from a range of decimal numbers with full precision (e.g. `Real(0.1, 0.5, name='adx')`
You can import all of these from `freqtrade.optimize.space`, although `Categorical`, `Integer` and `Real` are only aliases for their corresponding scikit-optimize Spaces. `SKDecimal` is provided by freqtrade for faster optimizations.
``` python
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal, Real # noqa
```
!!! Hint "SKDecimal vs. Real"
We recommend to use `SKDecimal` instead of the `Real` space in almost all cases. While the Real space provides full accuracy (up to ~16 decimal places) - this precision is rarely needed, and leads to unnecessary long hyperopt times.
Assuming the definition of a rather small space (`SKDecimal(0.10, 0.15, decimals=2, name='xxx')`) - SKDecimal will have 5 possibilities (`[0.10, 0.11, 0.12, 0.13, 0.14, 0.15]`).
A corresponding real space `Real(0.10, 0.15 name='xxx')` on the other hand has an almost unlimited number of possibilities (`[0.10, 0.010000000001, 0.010000000002, ... 0.014999999999, 0.01500000000]`).

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 90 90" width="100" height="100"><defs><path d="M0 90L0 0L90 0L90 90L0 90ZM50 60L60 60L60 80L70 80L70 60L80 60L80 50L50 50L50 60ZM30 80L40 80L40 70L30 70L30 80ZM30 60L20 60L20 70L10 70L10 80L20 80L20 70L30 70L30 60L40 60L40 50L30 50L30 60ZM10 60L20 60L20 50L10 50L10 60ZM10 40L40 40L40 30L20 30L20 20L40 20L40 10L10 10L10 40ZM50 40L80 40L80 30L60 30L60 20L80 20L80 10L50 10L50 40Z" id="c6g67PWSoP"></path></defs><g><g><g><use xlink:href="#c6g67PWSoP" opacity="1" fill="#000000" fill-opacity="1"></use></g></g></g></svg>

After

Width:  |  Height:  |  Size: 818 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -15,16 +15,17 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--eps] [--dmmp] [--enable-protections]
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
[--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
[--export {none,trades}] [--export-filename PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
@ -38,6 +39,9 @@ optional arguments:
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
@ -52,6 +56,9 @@ optional arguments:
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
Starting balance, used for backtesting / hyperopt and
dry-runs.
--timeframe-detail TIMEFRAME_DETAIL
Specify detail timeframe for backtesting (`1m`, `5m`,
`30m`, `1h`, `1d`).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to
backtest. Please note that ticker-interval needs to be
@ -59,9 +66,9 @@ optional arguments:
this together with `--export trades`, the strategy-
name is injected into the filename (so `backtest-
data.json` becomes `backtest-data-
DefaultStrategy.json`
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
SampleStrategy.json`
--export {none,trades}
Export backtest results (default: trades).
--export-filename PATH
Save backtest results to the file with this filename.
Requires `--export` to be set as well. Example:
@ -98,7 +105,7 @@ Strategy arguments:
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).
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`.
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
@ -108,11 +115,16 @@ All profit calculations include fees, and freqtrade will use the exchange's defa
!!! 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.
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.
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
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.
@ -172,13 +184,13 @@ Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies
---
Exporting trades to file
Prevent exporting trades to file
```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.
---
@ -235,29 +247,29 @@ The most important in the backtesting is to understand the result.
A backtesting result will look like that:
```
========================================================= BACKTESTING REPORT ========================================================
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 0 | 21 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 0 | 8 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 0 | 14 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 0 | 7 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 0 | 10 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 0 | 20 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 0 | 15 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 0 | 17 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 0 | 18 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 0 | 9 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 0 | 21 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 0 | 7 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 0 | 13 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 0 | 5 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 0 | 9 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 0 | 11 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 0 | 23 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 0 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
========================================================= SELL REASON STATS =========================================================
========================================================= BACKTESTING REPORT ==========================================================
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
========================================================= SELL REASON STATS ==========================================================
| Sell Reason | Sells | Wins | Draws | Losses |
|:-------------------|--------:|------:|-------:|--------:|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
@ -265,11 +277,11 @@ A backtesting result will look like that:
| sell_signal | 56 | 36 | 0 | 20 |
| force_sell | 2 | 0 | 0 | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 | 0 |
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
=============== SUMMARY METRICS ===============
| Metric | Value |
|-----------------------+---------------------|
@ -277,7 +289,7 @@ A backtesting result will look like that:
| Backtesting to | 2019-05-01 00:00:00 |
| Max open trades | 3 |
| | |
| Total trades | 429 |
| Total/Daily Avg Trades| 429 / 3.575 |
| Starting balance | 0.01000000 BTC |
| Final balance | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
@ -295,6 +307,7 @@ A backtesting result will look like that:
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 |
| Rejected Buy signals | 3089 |
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
@ -316,7 +329,7 @@ The last line will give you the overall performance of your strategy,
here:
```
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
```
The bot has made `429` trades for an average duration of `4:12:00`, with a performance of `76.20%` (profit), that means it has
@ -364,12 +377,11 @@ It contains some useful key metrics about performance of your strategy on backte
| Backtesting to | 2019-05-01 00:00:00 |
| Max open trades | 3 |
| | |
| Total trades | 429 |
| Total/Daily Avg Trades| 429 / 3.575 |
| Starting balance | 0.01000000 BTC |
| Final balance | 0.01762792 BTC |
| Absolute profit | 0.00762792 BTC |
| Total profit % | 76.2% |
| Trades per day | 3.575 |
| Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 BTC |
| | |
@ -382,6 +394,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 |
| Rejected Buy signals | 3089 |
| | |
| Min balance | 0.00945123 BTC |
| Max balance | 0.01846651 BTC |
@ -398,12 +411,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).
- `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).
- `Final balance`: Final balance - starting balance + absolute profit.
- `Absolute profit`: Profit made in stake currency.
- `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital Starting capital) / Starting capital`.
- `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.
- `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 %`.
@ -411,17 +423,24 @@ 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.
- `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.
- `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.
- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
- `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost.
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
### Assumptions made by backtesting
### 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:
- Buys happen at open-price
- All orders are filled at the requested price (no slippage, no unfilled orders)
- Sell-signal sells happen at open-price of the consecutive candle
- Sell-signal is favored over Stoploss, because sell-signals are assumed to trigger on candle's open
- ROI
@ -432,6 +451,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
- Low happens before high for stoploss, protecting capital first
- Trailing stoploss
- Trailing Stoploss is only adjusted if it's below the candle's low (otherwise it would be triggered)
- High happens first - adjusting stoploss
- 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
@ -446,10 +466,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.
### Further backtest-result analysis
### Improved backtest accuracy
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.
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
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
@ -469,11 +509,11 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
```
=========================================================== STRATEGY SUMMARY ===========================================================
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 |
=========================================================== STRATEGY SUMMARY =========================================================================
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
```
## Next step

View File

@ -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.
* **Trade**: Open position.
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
* **Pair**: Tradable pair, usually in the format of 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"`, ...).
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
* **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_sell_timeout()` strategy callback for open sell orders.
* Verifies existing positions and eventually places sell orders.
* Considers stoploss, ROI and sell-signal.
* Determine sell-price based on `ask_strategy` configuration setting.
* Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`.
* 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.
* Check if trade-slots are still available (if `max_open_trades` is reached).
* 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.
This loop will be repeated again and again until the bot is stopped.
@ -52,8 +53,10 @@ This loop will be repeated again and again until the bot is stopped.
* Load historic data for configured pairlist.
* Calls `bot_loop_start()` once.
* 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).
* 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
!!! Note

View File

@ -12,22 +12,22 @@ This page explains the different parameters of the bot and how to run it.
```
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
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.
create-userdir Create user-data directory.
new-config Create new config
new-hyperopt Create new hyperopt
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
list-data List downloaded data.
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
@ -41,8 +41,10 @@ positional arguments:
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
optional arguments:
-h, --help show this help message and exit

View File

@ -5,44 +5,75 @@ By default, these settings are configured via the configuration file (see below)
## The Freqtrade configuration file
The bot uses a set of configuration parameters during its operation that all together conform 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.
You can specify a different configuration file used by the bot with the `-c/--config` command line option.
In some advanced use cases, multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
You can specify 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 default configuration file is not created we recommend you to use `freqtrade new-config --config config.json` to generate a basic configuration file.
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 the JSON format.
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 syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines.
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.
!!! Tip "Use multiple configuration files to keep secrets secret"
You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself.
``` bash
freqtrade trade --config user_data/config.json --config user_data/config-private.json <...>
```
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`).
## Configuration parameters
The table below will list all configuration parameters available.
Freqtrade can also load many options via command line (CLI) arguments (check out the commands `--help` output for details).
The prevelance for all Options is as follows:
The prevalence for all Options is as follows:
- CLI arguments override any other option
- Configuration files are used in sequence (last file wins), and override Strategy configurations.
- 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.
- [Environment Variables](#environment-variables)
- Configuration files are used in sequence (the last file wins) and override Strategy configurations.
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#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.
| 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_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`.
| `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
| `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.
@ -59,25 +90,27 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `fee` | Fee used during backtesting / dry-runs. Should normally not be configured, which has freqtrade fall back to the exchange default fee. Set as ratio (e.g. 0.001 = 0.1%). Fee is applied twice for each trade, once when buying, once when selling. <br> **Datatype:** Float (as ratio)
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.buy` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.sell` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `minutes`.* <br> **Datatype:** String
| `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.** Set 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.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.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.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.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_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
| `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
| `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
| `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)
| `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
| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** 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
| `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
| `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)
| `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
| `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
| `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.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
@ -91,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.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.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.
| `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
| `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.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
@ -129,7 +163,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
### 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.
* `minimal_roi`
@ -145,35 +179,67 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force`
* `unfilledtimeout`
* `disable_dataframe_checks`
* `protections`
* `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy)
* `sell_profit_offset` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy)
* `ignore_buying_expired_candle_after` (ask_strategy)
* `use_sell_signal`
* `sell_profit_only`
* `sell_profit_offset`
* `ignore_roi_if_buy_signal`
* `ignore_buying_expired_candle_after`
### 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.
#### Available balance
#### Minimum trade stake
The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages.
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$.
The minimum stake amount to buy this pair is, 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.
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
With a reserve of 5%, the minimum stake amount would be ~12.6$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14$ (`12.6 / (1 - 0.1)`).
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
!!! Warning
Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange.
#### 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.
Freqtrade will reserve 1% for eventual fees when entering a trade and will therefore not touch that by default.
You can configure the "untouched" amount by using the `tradable_balance_ratio` setting.
For example, if you have 10 ETH available in your wallet on the exchange and `tradable_balance_ratio=0.5` (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers this as 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
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
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:
@ -201,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
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.
@ -219,18 +285,18 @@ To allow the bot to trade all the available `stake_currency` in your account (mi
```
!!! 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"
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.
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.
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.
--8<-- "includes/pricing.md"
### Understand minimal_roi
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
in minutes and the value is the minimum ROI as ratio.
in minutes and the value is the minimum ROI as a ratio.
See the example below:
```json
@ -245,7 +311,7 @@ See the example below:
Most of the strategy files already include the optimal `minimal_roi` value.
This parameter can be set in either Strategy or Configuration file. If you use it in the configuration file, it will override the
`minimal_roi` value from the strategy file.
If it is not set in either Strategy or Configuration, a default of 1000% `{"0": 10}` is used, and minimal roi is disabled unless your trade generates 1000% profit.
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"
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.
@ -264,18 +330,21 @@ 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.
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:
``` json
"ask_strategy":{
{
//...
"ignore_buying_expired_candle_after": 300,
"price_side": "bid",
// ...
},
}
```
!!! Note
This setting resets with each new candle, so it will not prevent sticking-signals from executing on the 2nd or 3rd candle they're active. Best use a "trigger" selector for buy signals, which are only active for one candle.
### Understand order_types
The `order_types` configuration parameter maps actions (`buy`, `sell`, `stoploss`, `emergencysell`, `forcesell`, `forcebuy`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
@ -288,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.
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)
@ -339,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.
!!! 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
@ -349,12 +418,12 @@ is executed on the exchange. Three commonly used time in force are:
**GTC (Good Till Canceled):**
This is most of the time the default time in force. It means the order will remain
on exchange till it is 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.
**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):**
@ -375,67 +444,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
```
!!! Warning
This is an ongoing work. For now it is supported only for binance and only for buy orders.
Please don't change the default value unless you know what you are doing.
### 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.
#### Advanced Freqtrade Exchange configuration
Advanced options can be configured using the `_ft_has_params` setting, which will override Defaults and exchange-specific behaviours.
Available options are listed in the exchange-class as `_ft_has_default`.
For example, to test the order type `FOK` with Kraken, and modify candle limit to 200 (so you only get 200 candles per API call):
```json
"exchange": {
"name": "kraken",
"_ft_has_params": {
"order_time_in_force": ["gtc", "fok"],
"ohlcv_candle_limit": 200
}
```
!!! Warning
Please make sure to fully understand the impacts of these settings before modifying them.
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 for your particular exchange.
### What values can be used for fiat_display_currency?
@ -448,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"
```
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:
@ -459,7 +469,7 @@ The valid values are:
## Using Dry-run mode
We recommend starting the bot in the Dry-run mode to see how your bot will
behave and what is the performance of your strategy. In the Dry-run mode the
behave and what is the performance of your strategy. In the Dry-run mode, the
bot does not engage your money. It only runs a live simulation without
creating trades on the exchange.
@ -485,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.
!!! 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
* API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in dry-run mode.
* Wallets (`/balance`) are simulated based on `dry_run_wallet`.
* Orders are simulated, and will not be posted to the exchange.
* 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.
* Open orders (not trades, which are stored in the database) are reset on bot restart.
## Switch to production mode
In production mode, the bot will engage your money. Be careful, since a wrong
strategy can lose all your money. Be aware of what you are doing when
you run it in production mode.
In production mode, the bot will engage your money. Be careful, since a wrong strategy can lose all your money.
Be aware of what you are doing when you run it in production mode.
When switching to Production mode, please make sure to use a different / fresh database to avoid dry-run trades messing with your exchange money and eventually tainting your statistics.
### Setup your exchange account
You will need to create API Keys (usually you get `key` and `secret`, some exchanges require an additional `password`) from the Exchange website and you'll need to insert this into the appropriate fields in the configuration or when asked by the `freqtrade new-config` command.
API Keys are usually only required for live trading (trading for real money, bot running in "production mode", executing real orders on the exchange) and are not required for the bot running in dry-run (trade simulation) mode. When you 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
@ -517,24 +529,35 @@ API Keys are usually only required for live trading (trading for real money, bot
"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
"exchange": {
{
"exchange": {
"name": "bittrex",
"key": "af8ddd35195e9dc500b9a6f799f6f5c93d89193b",
"secret": "08a9dc6db3d7b53e1acebd9275677f4b0a04f1a5",
...
//"password": "", // Optional, not needed by all exchanges)
// ...
}
//...
}
```
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"
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.
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.
**NEVER** share your private configuration file or your exchange keys with anyone!
### Using proxy with Freqtrade
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
"ccxt_async_config": {

View File

@ -11,8 +11,9 @@ 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.
!!! 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, use `--days xx` with a number slightly higher than the missing number of days. Freqtrade will keep the available data and only download the missing data.
Be careful though: If the number is too small (which would result in a few missing days), the whole dataset will be removed and only xx days will be downloaded.
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 are updating existing data after inserting new pairs that you have no data for, use `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
If you use `--days xx` parameter alone - data for specified number of days will be downloaded for _all_ pairs. Be careful, if specified number of days is smaller than gap between now and last downloaded candle - freqtrade will delete all existing data to avoid gaps in candle data.
### Usage
@ -20,8 +21,9 @@ You can use a relative timerange (`--days 20`) or an absolute starting point (`-
usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] [--pairs-file FILE]
[--days INT] [--timerange TIMERANGE]
[--dl-trades] [--exchange EXCHANGE]
[--days INT] [--new-pairs-days INT]
[--timerange TIMERANGE] [--dl-trades]
[--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} ...]]
[--erase]
[--data-format-ohlcv {json,jsongz,hdf5}]
@ -30,10 +32,12 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
Limit command to these pairs. Pairs are space-
separated.
--pairs-file FILE File containing a list of pairs to download.
--days INT Download data for given number of days.
--new-pairs-days INT Download data of new pairs for given number of days.
Default: `None`.
--timerange TIMERANGE
Specify what timerange of data to use.
--dl-trades Download trades instead of OHLCV data. The bot will
@ -48,10 +52,10 @@ optional arguments:
exchange/pairs/timeframes.
--data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data.
(default: `json`).
(default: `None`).
--data-format-trades {json,jsongz,hdf5}
Storage format for downloaded trades data. (default:
`jsongz`).
`None`).
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@ -200,6 +204,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
```
### 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
You can get a list of downloaded data using the `list-data` sub-command.
@ -267,7 +326,7 @@ 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:
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

View File

@ -33,3 +33,13 @@ The old section of configuration parameters (`"pairlist"`) has been deprecated i
### 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.
### 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.

View File

@ -2,7 +2,7 @@
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/MA9v74M) 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
@ -240,11 +240,18 @@ The `IProtection` parent class provides a helper method for this in `calculate_l
!!! Note
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.
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).
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
Check if the new exchange supports Stoploss on Exchange orders through their API.

View File

@ -10,11 +10,11 @@ Start by downloading and installing Docker CE for your platform:
* [Windows](https://docs.docker.com/docker-for-windows/install/)
* [Linux](https://docs.docker.com/install/)
To simplify running freqtrade, please install [`docker-compose`](https://docs.docker.com/compose/install/) should be installed and available to follow the below [docker quick start guide](#docker-quick-start).
To simplify running freqtrade, [`docker-compose`](https://docs.docker.com/compose/install/) should be installed and available to follow the below [docker quick start guide](#docker-quick-start).
## Freqtrade with docker-compose
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) ready for usage.
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/stable/docker-compose.yml) ready for usage.
!!! Note
- The following section assumes that `docker` and `docker-compose` are installed and available to the logged in user.
@ -22,48 +22,23 @@ Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.co
### Docker quick start
Create a new directory and place the [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/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
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
``` 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
# Pull the freqtrade image
docker-compose pull
# Pull the freqtrade image
docker-compose pull
# Create user directory structure
docker-compose run --rm freqtrade create-userdir --userdir user_data
# 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
```
=== "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
# 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
```
# Create configuration - Requires answering interactive questions
docker-compose run --rm freqtrade new-config --config user_data/config.json
```
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.
@ -81,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.
!!! 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.
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).
@ -95,6 +70,18 @@ docker-compose up -d
!!! 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.
#### 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
You can check for running instances with `docker-compose ps`.
@ -131,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>`.
!!! 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`"
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
@ -156,8 +148,8 @@ Head over to the [Backtesting Documentation](backtesting.md) to learn more.
### Additional dependencies with docker-compose
If your strategy requires dependencies not included in the default image (like [technical](https://github.com/freqtrade/technical)) - it will be necessary to build the image on your host.
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.technical) for an example).
If your strategy requires dependencies not included in the default image - it will be necessary to build the image on your host.
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.custom](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.custom) for an example).
You'll then also need to modify the `docker-compose.yml` file and uncomment the build step, as well as rename the image to avoid naming collisions.
@ -168,9 +160,9 @@ You'll then also need to modify the `docker-compose.yml` file and uncomment the
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.
You can then use these commands as follows:
@ -181,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.
## Data analysis using docker compose
### Data analysis using docker compose
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
You can run this server using the following command:
@ -198,3 +190,22 @@ Since part of this image is built on your machine, it is recommended to rebuild
``` bash
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.

View File

@ -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.
!!! Warning
`Edge positioning` is not compatible with dynamic (volume-based) whitelist.
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
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
@ -215,16 +215,20 @@ Let's say the stake currency is **ETH** and there is $10$ **ETH** on the wallet.
usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
[--fee FLOAT] [-p PAIRS [PAIRS ...]]
[--stoplosses STOPLOSS_RANGE]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data.
(default: `None`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
@ -233,6 +237,9 @@ optional arguments:
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--stoplosses STOPLOSS_RANGE
Defines a range of stoploss values against which edge
will assess the strategy. The format is "min,max,step"

View File

@ -2,23 +2,74 @@
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 supports [time_in_force](configuration.md#understand-order_time_in_force).
!!! 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.
### Blacklists
### Binance Blacklist
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB order unsellable as the expected amount is not there anymore.
Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore.
### Binance sites
Binance has been split into 3, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized.
Binance has been split into 2, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized.
* [binance.com](https://www.binance.com/) - International users. Use exchange id: `binance`.
* [binance.us](https://www.binance.us/) - US based users. Use exchange id: `binanceus`.
* [binance.je](https://www.binance.je/) - Binance Jersey, trading fiat currencies. Use exchange id: `binanceje`.
## Kraken
@ -44,12 +95,25 @@ Due to the heavy rate-limiting applied by Kraken, the following configuration se
Downloading kraken data will require significantly more memory (RAM) than any other exchange, as the trades-data needs to be converted into candles on your machine.
It will also take a long time, as freqtrade will need to download every single trade that happened on the exchange for the pair / timerange combination, therefore please be patient.
!!! Warning "rateLimit tuning"
Please pay attention that rateLimit configuration entry holds delay in milliseconds between requests, NOT requests\sec rate.
So, in order to mitigate Kraken API "Rate limit exceeded" exception, this configuration should be increased, NOT decreased.
## Bittrex
### Order types
Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faq.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy).
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.
### 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
Bittrex split its exchange into US and International versions.
@ -71,8 +135,9 @@ You can get a list of restricted markets by using the following snippet:
``` python
import ccxt
ct = ccxt.bittrex()
_ = ct.load_markets()
res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']]
lm = ct.load_markets()
res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']]
print(res)
```
@ -96,6 +161,27 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll
}
```
## Kucoin
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
"exchange": {
"name": "kucoin",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"password": "your_exchange_api_key_password",
// ...
}
```
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force).
### Kucoin Blacklists
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues.
Accounts having KCS accounts use this to pay for fees - if your first trade happens to be on `KCS`, further trades will consume this position and make the initial KCS trade unsellable as the expected amount is not there anymore.
## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
@ -118,3 +204,25 @@ Whether your exchange returns incomplete candles or not can be checked using [th
Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle.
However, if it is based on the need for the latest price for your strategy - then this requirement can be acquired using the [data provider](strategy-customization.md#possible-options-for-dataprovider) from within the strategy.
### Advanced Freqtrade Exchange configuration
Advanced options can be configured using the `_ft_has_params` setting, which will override Defaults and exchange-specific behavior.
Available options are listed in the exchange-class as `_ft_has_default`.
For example, to test the order type `FOK` with Kraken, and modify candle limit to 200 (so you only get 200 candles per API call):
```json
"exchange": {
"name": "kraken",
"_ft_has_params": {
"order_time_in_force": ["gtc", "fok"],
"ohlcv_candle_limit": 200
}
//...
}
```
!!! Warning
Please make sure to fully understand the impacts of these settings before modifying them.

View File

@ -1,5 +1,19 @@
# Freqtrade FAQ
## Supported Markets
Freqtrade supports spot trading only.
### Can I open short positions?
No, Freqtrade does not support trading with margin / leverage, and cannot open short positions.
In some cases, your exchange may provide leveraged spot tokens which can be traded with Freqtrade eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD, etc...
### Can I trade options or futures?
No, options and futures trading are not supported.
## Beginner Tips & Tricks
* When you work with your strategy & hyperopt file you should use a proper code editor like VSCode or PyCharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely pointed out by Freqtrade during startup).
@ -40,9 +54,11 @@ 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.
### I want to improve the bot with a new strategy
### I want to use incomplete candles
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).
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?
@ -68,11 +84,11 @@ Currently known to happen for US Bittrex users.
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 = {
@ -124,6 +140,22 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us
## 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?
Per default Hyperopt called without the `-e`/`--epochs` command line option will only
@ -137,12 +169,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.
```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?
* 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/X89cVG). 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:

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade.
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler.
@ -23,12 +23,14 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list)
* [`AgeFilter`](#agefilter)
* [`OffsetFilter`](#offsetfilter)
* [`PerformanceFilter`](#performancefilter)
* [`PrecisionFilter`](#precisionfilter)
* [`PriceFilter`](#pricefilter)
* [`ShuffleFilter`](#shufflefilter)
* [`SpreadFilter`](#spreadfilter)
* [`RangeStabilityFilter`](#rangestabilityfilter)
* [`VolatilityFilter`](#volatilityfilter)
!!! Tip "Testing pairlists"
Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) utility sub-command to test your configuration quickly.
@ -56,21 +58,87 @@ This option must be configured along with `exchange.skip_pair_validation` in the
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 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.
`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.
```json
"pairlists": [{
"pairlists": [
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"min_value": 0,
"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
@ -78,24 +146,70 @@ The `refresh_period` setting allows to define the period (in seconds), at which
#### 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
in the first few days while the pair goes through its price-discovery period. Bots can often
be caught out buying before the pair has finished dropping in price.
This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days.
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
Sorts pairs by past trade performance, as follows:
1. Positive performance.
2. No closed trades yet.
3. Negative performance.
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.
#### PrecisionFilter
@ -108,6 +222,7 @@ The `PriceFilter` allows filtering of pairs by price. Currently the following pr
* `min_price`
* `max_price`
* `max_value`
* `low_price_ratio`
The `min_price` setting removes pairs where the price is below the specified price. This is useful if you wish to avoid trading very low-priced pairs.
@ -116,6 +231,11 @@ This option is disabled by default, and will only apply if set to > 0.
The `max_price` setting removes pairs where the price is above the specified price. This is useful if you wish to trade only low-priced pairs.
This option is disabled by default, and will only apply if set to > 0.
The `max_value` setting removes pairs where the minimum value change is above a specified value.
This is useful when an exchange has unbalanced limits. For example, if step-size = 1 (so you can only buy 1, or 2, or 3, but not 1.1 Coins) - and the price is pretty high (like 20\$) as the coin has risen sharply since the last limit adaption.
As a result of the above, you can only buy for 20\$, or 40\$ - but not for 25\$.
On exchanges that deduct fees from the receiving currency (e.g. FTX) - this can result in high value coins / amounts that are unsellable as the amount is slightly below the limit.
The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio.
This option is disabled by default, and will only apply if set to > 0.
@ -145,10 +265,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio
#### 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:
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
"pairlists": [
@ -156,6 +276,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
"method": "RangeStabilityFilter",
"lookback_days": 10,
"min_rate_of_change": 0.01,
"max_rate_of_change": 0.99,
"refresh_period": 1440
}
]
@ -163,10 +284,34 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
!!! Tip
This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit.
Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time.
#### VolatilityFilter
Volatility is the degree of historical variation of a pairs over time, is is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)).
This filter removes pairs if the average volatility over a `lookback_days` days is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
This filter can be used to narrow down your pairs to a certain volatility or avoid very volatile pairs.
In the below example:
If the volatility over the last 10 days is not in the range of 0.05-0.50, remove the pair from the whitelist. The filter is applied every 24h.
```json
"pairlists": [
{
"method": "VolatilityFilter",
"lookback_days": 10,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 86400
}
]
```
### Full example of Pairlist Handlers
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies both [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#price-filter), filtering all assets where 1 price unit is > 1%. Then the `SpreadFilter` is applied and pairs are finally shuffled with the random seed set to some predefined value.
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#pricefilter), filtering all assets where 1 price unit is > 1%. Then the [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) is applied and pairs are finally shuffled with the random seed set to some predefined value.
```json
"exchange": {
@ -177,7 +322,7 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets,
{
"method": "VolumePairList",
"number_assets": 20,
"sort_key": "quoteVolume",
"sort_key": "quoteVolume"
},
{"method": "AgeFilter", "min_days_listed": 10},
{"method": "PrecisionFilter"},
@ -189,6 +334,13 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets,
"min_rate_of_change": 0.01,
"refresh_period": 1440
},
{
"method": "VolatilityFilter",
"lookback_days": 10,
"min_volatility": 0.05,
"max_volatility": 0.50,
"refresh_period": 86400
},
{"method": "ShuffleFilter", "seed": 42}
],
```

View File

@ -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
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
@ -82,27 +82,18 @@ In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot
#### 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
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.
1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
#### Sell price without Orderbook enabled
When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price.
When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price.
### Market order pricing
When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection.

View File

@ -1,14 +1,13 @@
## Protections
!!! 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.
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
!!! Note
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
To align your protection with your strategy, you can define protections in the strategy.
!!! Tip
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"
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
* [`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.
```json
"protections": [
``` python
@property
def protections(self):
return [
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 4,
"only_per_pair": false
"only_per_pair": False
}
],
]
```
!!! Note
@ -69,8 +74,10 @@ 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.
```json
"protections": [
``` python
@property
def protections(self):
return [
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
@ -78,7 +85,7 @@ The below sample stops trading for 12 candles if max-drawdown is > 20% consideri
"stop_duration_candles": 12,
"max_allowed_drawdown": 0.2
},
],
]
```
#### Low Profit Pairs
@ -88,8 +95,10 @@ 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.
```json
"protections": [
``` python
@property
def protections(self):
return [
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
@ -97,7 +106,7 @@ The below example will stop trading a pair for 60 minutes if the pair does not h
"stop_duration": 60,
"required_profit": 0.02
}
],
]
```
#### 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".
```json
"protections": [
``` python
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 2
}
],
]
```
!!! Note
@ -132,52 +143,15 @@ 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 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
from freqtrade.strategy import IStrategy
class AwesomeStrategy(IStrategy)
timeframe = '1h'
protections = [
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5

View File

@ -1,4 +1,5 @@
# Freqtrade
![freqtrade](assets/freqtrade_poweredby.svg)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
@ -35,17 +36,19 @@ 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.
- [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] [FTX](https://ftx.com)
- [X] [Kraken](https://kraken.com/)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
- [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)_
### Community tested
Exchanges confirmed working by the community:
- [X] [Bitvavo](https://bitvavo.com/)
- [X] [Kucoin](https://www.kucoin.com/)
## Requirements
@ -71,13 +74,9 @@ Alternatively
## 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.
Please check out our [discord server](https://discord.gg/MA9v74M).
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
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).
## Ready to try?

View File

@ -60,7 +60,7 @@ OS Specific steps are listed first, the [Common](#common) section below is neces
sudo apt-get update
# install packages
sudo apt install -y python3-pip python3-venv python3-pandas python3-pip git
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git
```
=== "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.
??? 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
@ -203,6 +210,8 @@ sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
./configure --prefix=/usr/local
make
sudo make install
# On debian based systems (debian, ubuntu, ...) - updating ldconfig might be necessary.
sudo ldconfig
cd ..
rm -rf ./ta-lib*
```
@ -269,7 +278,7 @@ git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
```
#### Freqtrade instal: Conda Environment
#### Freqtrade install: Conda Environment
Prepare conda-freqtrade environment, using file `environment.yml`, which exist in main freqtrade directory

68
docs/overrides/main.html Normal file
View 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 %}

View File

@ -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>

View File

@ -37,7 +37,7 @@ usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
Limit command to these pairs. Pairs are space-
separated.
--indicators1 INDICATORS1 [INDICATORS1 ...]
Set indicators from your strategy you want in the
@ -66,8 +66,7 @@ optional arguments:
--timerange TIMERANGE
Specify what timerange of data to use.
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--no-trades Skip using trades from backtesting file and DB.
Common arguments:
@ -91,6 +90,7 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
Example:
@ -170,9 +170,15 @@ Additional features when using plot_config include:
* Specify additional subplots
* Specify indicator pairs to fill area in between
The sample plot configuration below specifies fixed colors for the indicators. Otherwise consecutive plots may produce different colorschemes each time, making comparisons difficult.
The sample plot configuration below specifies fixed colors for the indicators. Otherwise, consecutive plots may produce different color schemes each time, making comparisons difficult.
It also allows multiple subplots to display both MACD and RSI at the same time.
Plot type can be configured using `type` key. Possible types are:
* `scatter` corresponding to `plotly.graph_objects.Scatter` class (default).
* `bar` corresponding to `plotly.graph_objects.Bar` class.
Extra parameters to `plotly.graph_objects.*` constructor can be specified in `plotly` dict.
Sample configuration with inline comments explaining the process:
``` python
@ -198,7 +204,8 @@ Sample configuration with inline comments explaining the process:
# Create subplot MACD
"MACD": {
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'}
'macdsignal': {'color': 'orange'},
'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}}
},
# Additional subplot RSI
"RSI": {
@ -213,6 +220,9 @@ Sample configuration with inline comments explaining the process:
The above configuration assumes that `ema10`, `ema50`, `senkou_a`, `senkou_b`,
`macd`, `macdsignal`, `macdhist` and `rsi` are columns in the DataFrame created by the strategy.
!!! Warning
`plotly` arguments are only supported with plotly library and will not work with freq-ui.
## Plot profit
![plot-profit](assets/plot-profit.png)
@ -245,7 +255,7 @@ usage: freqtrade plot-profit [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
Limit command to these pairs. Pairs are space-
separated.
--timerange TIMERANGE
Specify what timerange of data to use.
@ -264,8 +274,8 @@ optional arguments:
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--auto-open Automatically open generated plot.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@ -288,6 +298,7 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
The `-p/--pairs` argument, can be used to limit the pairs that are considered for this calculation.

View File

@ -1,3 +1,4 @@
mkdocs-material==7.0.6
mkdocs==1.2.2
mkdocs-material==7.3.2
mdx_truly_sane_lists==1.2
pymdown-extensions==8.1.1
pymdown-extensions==9.0

View File

@ -71,11 +71,14 @@ If you run your bot using docker, you'll need to have the bot listen to incoming
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080
"listen_port": 8080,
"username": "Freqtrader",
"password": "SuperSecret1!",
//...
},
```
Uncomment the following from your docker-compose file:
Make sure that the following 2 lines are available in your docker-compose file:
```yml
ports:
@ -106,7 +109,10 @@ By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be use
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080
"listen_port": 8080,
"username": "Freqtrader",
"password": "SuperSecret1!",
//...
}
}
```
@ -124,7 +130,8 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `stop` | Stops the trader.
| `stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `reload_config` | Reloads the configuration file.
| `trades` | List last trades.
| `trades` | List last trades. Limited to 500 trades per call.
| `trade/<tradeid>` | Get specific trade.
| `delete_trade <trade_id>` | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.
| `show_config` | Shows part of the current configuration with relevant settings to operation.
| `logs` | Shows last log messages.
@ -181,7 +188,7 @@ count
Return the amount of open trades.
daily
Return the amount of open trades.
Return the profits for each day, and amount of trades.
delete_lock
Delete (disable) lock from the database.
@ -214,7 +221,7 @@ locks
logs
Show latest logs.
:param limit: Limits log messages to the last <limit> logs. No limit to get all the trades.
:param limit: Limits log messages to the last <limit> logs. No limit to get the entire log.
pair_candles
Return live dataframe for <pair><timeframe>.
@ -234,6 +241,9 @@ pair_history
performance
Return the performance of the different coins.
ping
simple ping
plot_config
Return plot configuration if the strategy defines one.
@ -270,17 +280,22 @@ strategy
:param strategy: Strategy class name
trades
Return trades history.
trade
Return specific trade
:param limit: Limits trades to the X last trades. No limit to get all the trades.
:param trade_id: Specify which trade to get.
trades
Return trades history, sorted by id
:param limit: Limits trades to the X last trades. Max 500 trades.
:param offset: Offset by this amount of trades.
version
Return the version of the bot.
whitelist
Show the current whitelist.
```
### OpenAPI interface

View File

@ -19,7 +19,7 @@ The freqtrade docker image does contain sqlite3, so you can edit the database wi
``` bash
docker-compose exec freqtrade /bin/bash
sqlite3 <databasefile>.sqlite
sqlite3 <database-file>.sqlite
```
## Open the DB
@ -99,3 +99,32 @@ DELETE FROM trades WHERE id = 31;
!!! Warning
This will remove this trade from the database. Please make sure you got the correct id and **NEVER** run this query without the `where` clause.
## Use a different database system
!!! Warning
By using one of the below database systems, you acknowledge that you know how to manage such a system. Freqtrade will not provide any support with setup or maintenance (or backups) of the below database systems.
### PostgreSQL
Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems.
Installation:
`pip install psycopg2-binary`
Usage:
`... --db-url postgresql+psycopg2://<username>:<password>@localhost:5432/<database>`
Freqtrade will automatically create the tables necessary upon startup.
If you're running different instances of Freqtrade, you must either setup one database per Instance or use different users / schemas for your connections.
### MariaDB / MySQL
Freqtrade supports MariaDB by using SQLAlchemy, which supports multiple different database systems.
Installation:
`pip install pymysql`
Usage:
`... --db-url mysql+pymysql://<username>:<password>@localhost:3306/<database>`

View File

@ -40,43 +40,119 @@ class AwesomeStrategy(IStrategy):
!!! Note
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
***
## Dataframe access
### Storing custom information using DatetimeIndex from `dataframe`
You may access dataframe in various strategy functions by querying it from dataprovider.
Imagine you need to store an indicator like `ATR` or `RSI` into `custom_info`. To use this in a meaningful way, you will not only need the raw data of the indicator, but probably also need to keep the right timestamps.
``` python
from freqtrade.exchange import timeframe_to_prev_date
```python
import talib.abstract as ta
class AwesomeStrategy(IStrategy):
# Create custom dictionary
custom_info = {}
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str,
current_time: 'datetime', **kwargs) -> bool:
# Obtain pair dataframe.
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# using "ATR" here as example
dataframe['atr'] = ta.ATR(dataframe)
if self.dp.runmode.value in ('backtest', 'hyperopt'):
# add indicator mapped to correct DatetimeIndex to custom_info
self.custom_info[metadata['pair']] = dataframe[['date', 'atr']].copy().set_index('date')
return dataframe
# Obtain last available candle. Do not use current_time to look up latest candle, because
# current_time points to current incomplete candle whose data is not available.
last_candle = dataframe.iloc[-1].squeeze()
# <...>
# In dry/live runs trade open date will not match candle open date therefore it must be
# rounded.
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
# Look up trade candle.
trade_candle = dataframe.loc[dataframe['date'] == trade_date]
# trade_candle may be empty for trades that just opened as it is still incomplete.
if not trade_candle.empty:
trade_candle = trade_candle.squeeze()
# <...>
```
!!! Warning
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
!!! Warning "Using .iloc[-1]"
You can use `.iloc[-1]` here because `get_analyzed_dataframe()` only returns candles that backtesting is allowed to see.
This will not work in `populate_*` methods, so make sure to not use `.iloc[]` in that area.
Also, this will only work starting with version 2021.5.
***
## Custom sell signal
It is possible to define custom sell signals, indicating that specified position should be sold. This is very useful when we need to customize sell conditions for each individual trade, or if you need the trade profit to take the sell decision.
For example you could implement a 1:2 risk-reward ROI with `custom_sell()`.
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
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
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.
An example of how we can use different indicators depending on the current profit and also sell trades that were open longer than one day:
``` python
class AwesomeStrategy(IStrategy):
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()
# Above 20% profit, sell when rsi < 80
if current_profit > 0.2:
if last_candle['rsi'] < 80:
return 'rsi_below_80'
# Between 2% and 10%, sell if EMA-long above EMA-short
if 0.02 < current_profit < 0.1:
if last_candle['emalong'] > last_candle['emashort']:
return 'ema_long_below_80'
# Sell any positions at a loss if they are held for more than one day.
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
return 'unclog'
```
See [Dataframe access](#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.
See `custom_stoploss` examples below on how to access the saved dataframe columns
## Custom stoploss
A stoploss can only ever move upwards - so if you set it to an absolute profit of 2%, you can never move it below this price.
Also, 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.
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
The method must return a stoploss value (float / number) with a relative ratio below the current price.
E.g. `current_profit = 0.05` (5% profit) - stoploss returns `0.02` - then you "locked in" a profit of 3% (`0.05 - 0.02 = 0.03`).
The method must return a stoploss value (float / number) as a percentage of the current price.
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price.
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
@ -109,7 +185,7 @@ class AwesomeStrategy(IStrategy):
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the currentrate
:return float: New stoploss value, relative to the current rate
"""
return -0.04
```
@ -145,9 +221,9 @@ class AwesomeStrategy(IStrategy):
current_rate: float, current_profit: float, **kwargs) -> float:
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date:
if current_time - timedelta(minutes=120) > trade.open_date_utc:
return -0.05
elif current_time - timedelta(minutes=60) > trade.open_date:
elif current_time - timedelta(minutes=60) > trade.open_date_utc:
return -0.10
return 1
```
@ -197,7 +273,7 @@ class AwesomeStrategy(IStrategy):
current_rate: float, current_profit: float, **kwargs) -> float:
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
desired_stoploss = current_profit / 2
@ -206,18 +282,31 @@ class AwesomeStrategy(IStrategy):
return max(min(desired_stoploss, 0.05), 0.025)
```
#### Absolute stoploss
#### Calculating stoploss relative to open price
The below example sets absolute profit levels based on the current profit.
Stoploss values returned from `custom_stoploss()` always specify a percentage relative to `current_rate`. In order to set a stoploss relative to the *open* price, we need to use `current_profit` 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_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
Instead of continuously trailing behind the current price, this example sets fixed stoploss price levels based on the current profit.
* Use the regular stoploss until 20% profit is reached
* Once profit is > 40%, stoploss will be at 25%, locking in at least 25% of the profit.
* Once profit is > 25% - stoploss will be 15%.
* Once profit is > 20% - stoploss will be set to 7%.
* Once profit is > 20% - set stoploss to 7% above open price.
* Once profit is > 25% - set stoploss to 15% above open price.
* Once profit is > 40% - set stoploss to 25% above open price.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open
class AwesomeStrategy(IStrategy):
@ -228,72 +317,106 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# Calculate as `-desired_stop_from_open + current_profit` to get the distance between current_profit and initial price
# evaluate highest to lowest, so that highest possible stop is used
if current_profit > 0.40:
return (-0.25 + current_profit)
if current_profit > 0.25:
return (-0.15 + current_profit)
if current_profit > 0.20:
return (-0.07 + current_profit)
return stoploss_from_open(0.25, current_profit)
elif current_profit > 0.25:
return stoploss_from_open(0.15, current_profit)
elif current_profit > 0.20:
return stoploss_from_open(0.07, current_profit)
# return maximum stoploss value, keeping current stoploss price unchanged
return 1
```
#### Custom stoploss using an indicator from dataframe example
Imagine you want to use `custom_stoploss()` to use a trailing indicator like e.g. "ATR"
See: "Storing custom information using DatetimeIndex from `dataframe`" example above) on how to store the indicator into `custom_info`
!!! Warning
only use .iat[-1] in live mode, not in backtesting/hyperopt
otherwise you will look into the future
see [Common mistakes when developing strategies](strategy-customization.md#common-mistakes-when-developing-strategies) for more info.
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
``` python
from freqtrade.persistence import Trade
from freqtrade.state import RunMode
class AwesomeStrategy(IStrategy):
# ... populate_* methods
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# <...>
dataframe['sar'] = ta.SAR(dataframe)
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
result = 1
if self.custom_info and pair in self.custom_info and trade:
# using current_time directly (like below) will only work in backtesting.
# so check "runmode" to make sure that it's only used in backtesting/hyperopt
if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'):
relative_sl = self.custom_info[pair].loc[current_time]['atr]
# in live / dry-run, it'll be really the current time
else:
# but we can just use the last entry from an already analyzed dataframe instead
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
# WARNING
# only use .iat[-1] in live mode, not in backtesting/hyperopt
# otherwise you will look into the future
# see: https://www.freqtrade.io/en/latest/strategy-customization/#common-mistakes-when-developing-strategies
relative_sl = dataframe['atr'].iat[-1]
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
if (relative_sl is not None):
# new stoploss relative to current_rate
new_stoploss = (current_rate-relative_sl)/current_rate
# turn into relative negative offset required by `custom_stoploss` return implementation
result = new_stoploss - 1
# Use parabolic sar as absolute stoploss price
stoploss_price = last_candle['sar']
return result
# Convert absolute price to percentage relative to current_rate
if stoploss_price < current_rate:
return (stoploss_price / current_rate) - 1
# return maximum stoploss value, keeping current stoploss price unchanged
return 1
```
See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks.
---
## 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
Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if a order did time out or not.
However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if an order did time out or not.
!!! Note
Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances.
@ -306,7 +429,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to
The function must return either `True` (cancel order) or `False` (keep order alive).
``` python
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
@ -320,21 +443,21 @@ class AwesomeStrategy(IStrategy):
}
def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5):
if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5):
return True
elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3):
elif trade.open_rate > 10 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=3):
return True
elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24):
elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24):
return True
return False
def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date < datetime.utcnow() - timedelta(minutes=5):
if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5):
return True
elif trade.open_rate > 10 and trade.open_date < datetime.utcnow() - timedelta(minutes=3):
elif trade.open_rate > 10 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=3):
return True
elif trade.open_rate < 1 and trade.open_date < datetime.utcnow() - timedelta(hours=24):
elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24):
return True
return False
```
@ -416,7 +539,7 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
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.
Timing for this function is critical, so avoid doing heavy computations or
@ -431,6 +554,7 @@ class AwesomeStrategy(IStrategy):
: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 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.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@ -452,7 +576,8 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, 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.
Timing for this function is critical, so avoid doing heavy computations or
@ -470,6 +595,7 @@ class AwesomeStrategy(IStrategy):
:param sell_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'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.
:return bool: When True is returned, then the sell-order is placed on the exchange.
False aborts the process
@ -483,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
@ -519,7 +678,7 @@ Both attributes and methods may be overridden, altering behavior of the original
## Embedding Strategies
Freqtrade provides you with with an easy way to embed the strategy into your configuration file.
Freqtrade provides you with an easy way to embed the strategy into your configuration file.
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
in your chosen config file.
@ -542,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!
## 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)
```

View File

@ -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).
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
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.
@ -159,7 +169,7 @@ Edit the method `populate_buy_trend()` in your strategy file to update your buy
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
This will method will also define a new column, `"buy"`, which needs to contain 1 for buys, and 0 for "no action".
This method will also define a new column, `"buy"`, which needs to contain 1 for buys, and 0 for "no action".
Sample from `user_data/strategies/sample_strategy.py`:
@ -193,7 +203,7 @@ Please note that the sell-signal is only used if `use_sell_signal` is set to tru
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
This will method will also define a new column, `"sell"`, which needs to contain 1 for sells, and 0 for "no action".
This method will also define a new column, `"sell"`, which needs to contain 1 for sells, and 0 for "no action".
Sample from `user_data/strategies/sample_strategy.py`:
@ -422,10 +432,6 @@ if self.dp:
Returns an empty dataframe if the requested pair was not cached.
This should not happen when using whitelisted pairs.
!!! Warning "Warning about backtesting"
This method will return an empty dataframe during backtesting.
### *orderbook(pair, maximum)*
``` python
@ -436,6 +442,26 @@ if self.dp:
dataframe['best_ask'] = ob['asks'][0][0]
```
The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will look as follows:
``` js
{
'bids': [
[ price, amount ], // [ float, float ]
[ price, amount ],
...
],
'asks': [
[ price, amount ],
[ price, amount ],
//...
],
//...
}
```
Therefore, using `ob['bids'][0][0]` as demonstrated above will result in using the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position.
!!! Warning "Warning about backtesting"
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return uptodate values.
@ -587,6 +613,204 @@ All columns of the informative dataframe will be available on the returning data
***
### *stoploss_from_open()*
Stoploss values returned from `custom_stoploss` must specify a percentage relative to `current_rate`, but sometimes you may want to specify a stoploss relative to the open price instead. `stoploss_from_open()` is a helper function to calculate a stoploss value that can be returned from `custom_stoploss` which will be equivalent to the desired percentage above the open price.
??? Example "Returning a stoploss relative to the open price from the custom stoploss function"
Say the open price was $100, and `current_price` is $121 (`current_profit` will be `0.21`).
If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_open
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit)
return 1
```
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)
```
### *@informative()*
``` 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.
"""
```
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.
??? 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 (Wallets)
The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.
@ -728,6 +952,8 @@ Printing more than a few rows is also possible (simply use `print(dataframe)` i
## Common mistakes when developing strategies
### Peeking into the future while backtesting
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.
This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods, since they all use data which is not available during dry/live runs, so these strategies will perform well during backtesting, but will fail / perform badly in real conditions.

View File

@ -130,6 +130,44 @@ trades = load_backtest_data(backtest_dir)
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
In case you did already some trading and want to analyze your performance
@ -190,9 +228,23 @@ graph = generate_candlestick_graph(pair=pair,
# Show graph inline
# graph.show()
# Render graph in a seperate window
# Render graph in a separate window
graph.show(renderer="browser")
```
## Plot average profit per trade as distribution graph
```python
import plotly.figure_factory as ff
hist_data = [trades.profit_ratio]
group_labels = ['profit_ratio'] # name of the dataset
fig = ff.create_distplot(hist_data, group_labels,bin_size=0.01)
fig.show()
```
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.

View File

@ -11,3 +11,18 @@
.rst-versions .rst-other-versions {
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; }
}

View File

@ -80,15 +80,36 @@ Example configuration showing the different settings:
"warning": "on",
"startup": "off",
"buy": "silent",
"sell": "on",
"sell": {
"roi": "silent",
"emergency_sell": "on",
"force_sell": "on",
"sell_signal": "silent",
"trailing_stop_loss": "on",
"stop_loss": "on",
"stoploss_on_exchange": "on",
"custom_sell": "silent"
},
"buy_cancel": "silent",
"sell_cancel": "on"
"sell_cancel": "on",
"buy_fill": "off",
"sell_fill": "off",
"protection_trigger": "off",
"protection_trigger_global": "on"
},
"reload": true,
"balance_dust_level": 0.01
},
},
```
`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.
`*_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.
`reload` allows you to disable reload-buttons on selected messages.
## Create a custom keyboard (command shortcut buttons)
@ -146,8 +167,8 @@ official commands. You can ask at any moment for help with `/help`.
| `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
| `/count` | Displays number of trades used and available
| `/locks` | Show currently locked pairs.
| `/unlock <pair | lock_id>` | Remove the lock for this pair (or for this lock id).
| `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
| `/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 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)
@ -227,10 +248,10 @@ current max
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
> ∙ `0.00485701 BTC (258.45%)`
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (143.43%)`
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
@ -239,14 +260,22 @@ Return a summary of your profit/loss and performance.
> **Avg. Duration:** `2:33:45`
> **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>
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
### /forcebuy <pair>
### /forcebuy <pair> [rate]
> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
Omitting the pair will open a query asking for the pair to buy (based on the current whitelist).
![Telegram force-buy screenshot](assets/telegram_forcebuy.png)
Note that for this to work, `forcebuy_enable` needs to be set to true.
[More details](configuration.md#understand-forcebuy_enable)
@ -255,11 +284,11 @@ Note that for this to work, `forcebuy_enable` needs to be set to true.
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ...
### /balance

View File

@ -26,9 +26,7 @@ optional arguments:
├── data
├── hyperopt_results
├── hyperopts
│   ├── sample_hyperopt_advanced.py
│   ├── sample_hyperopt_loss.py
│   └── sample_hyperopt.py
├── notebooks
│   └── strategy_analysis_example.ipynb
├── plot
@ -111,46 +109,11 @@ Using the advanced template (populates all optional functions and methods)
freqtrade new-strategy --strategy AwesomeStrategy --template advanced
```
## Create new hyperopt
## List Strategies
Creates a new hyperopt from a template similar to SampleHyperopt.
The file will be named inline with your class name, and will not overwrite existing files.
Use the `list-strategies` subcommand to see all strategies in one particular directory.
Results will be located in `user_data/hyperopts/<classname>.py`.
``` 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).
This subcommand is useful for finding problems in your environment with loading strategies: modules with strategies that contain errors and failed to load are printed in red (LOAD FAILED), while strategies with duplicate names are printed in yellow (DUPLICATE NAME).
```
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
@ -164,34 +127,6 @@ optional arguments:
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default: `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:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
@ -211,18 +146,16 @@ Common arguments:
!!! Warning
Using these commands will try to load all python files from a directory. This can be a security risk if untrusted files reside in this directory, since all module-level code is executed.
Example: Search default strategies and hyperopts directories (within the default userdir).
Example: Search default strategies directories (within the default userdir).
``` bash
freqtrade list-strategies
freqtrade list-hyperopts
```
Example: Search strategies and hyperopts directory within the userdir.
Example: Search strategies directory within the userdir.
``` bash
freqtrade list-strategies --userdir ~/.freqtrade/
freqtrade list-hyperopts --userdir ~/.freqtrade/
```
Example: Search dedicated strategy path.
@ -231,12 +164,6 @@ Example: Search dedicated strategy path.
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
```
Example: Search dedicated hyperopt path.
``` bash
freqtrade list-hyperopt --hyperopt-path ~/.freqtrade/hyperopts/
```
## List Exchanges
Use the `list-exchanges` subcommand to see the exchanges available for the bot.
@ -253,18 +180,211 @@ optional arguments:
* Example: see exchanges available for the bot:
```
$ freqtrade list-exchanges
Exchanges available for Freqtrade: _1btcxe, acx, allcoin, bequant, bibox, binance, binanceje, binanceus, bitbank, bitfinex, bitfinex2, bitkk, bitlish, bitmart, bittrex, bitz, bleutrade, btcalpha, btcmarkets, btcturk, buda, cex, cobinhood, coinbaseprime, coinbasepro, coinex, cointiger, coss, crex24, digifinex, dsx, dx, ethfinex, fcoin, fcoinjp, gateio, gdax, gemini, hitbtc2, huobipro, huobiru, idex, kkex, kraken, kucoin, kucoin2, kuna, lbank, mandala, mercado, oceanex, okcoincny, okcoinusd, okex, okex3, poloniex, rightbtc, theocean, tidebit, upbit, zb
Exchanges available for Freqtrade:
Exchange name Valid reason
--------------- ------- --------------------------------------------
aax True
ascendex True missing opt: fetchMyTrades
bequant True
bibox True
bigone True
binance True
binanceus True
bitbank True missing opt: fetchTickers
bitcoincom True
bitfinex True
bitforex True missing opt: fetchMyTrades, fetchTickers
bitget True
bithumb True missing opt: fetchMyTrades
bitkk True missing opt: fetchMyTrades
bitmart True
bitmax True missing opt: fetchMyTrades
bitpanda True
bittrex True
bitvavo True
bitz True missing opt: fetchMyTrades
btcalpha True missing opt: fetchTicker, fetchTickers
btcmarkets True missing opt: fetchTickers
buda True missing opt: fetchMyTrades, fetchTickers
bw True missing opt: fetchMyTrades, fetchL2OrderBook
bybit True
bytetrade True
cdax True
cex True missing opt: fetchMyTrades
coinbaseprime True missing opt: fetchTickers
coinbasepro True missing opt: fetchTickers
coinex True
crex24 True
deribit True
digifinex True
equos True missing opt: fetchTicker, fetchTickers
eterbase True
fcoin True missing opt: fetchMyTrades, fetchTickers
fcoinjp True missing opt: fetchMyTrades, fetchTickers
ftx True
gateio True
gemini True
gopax True
hbtc True
hitbtc True
huobijp True
huobipro True
idex True
kraken True
kucoin True
lbank True missing opt: fetchMyTrades
mercado True missing opt: fetchTickers
ndax True missing opt: fetchTickers
novadax True
okcoin True
okex True
probit True
qtrade True
stex True
timex True
upbit True missing opt: fetchMyTrades
vcc True
zb True missing opt: fetchMyTrades
```
!!! Note "missing opt exchanges"
Values with "missing opt:" might need special configuration (e.g. using orderbook if `fetchTickers` is missing) - but should in theory work (although we cannot guarantee they will).
* Example: see all exchanges supported by the ccxt library (including 'bad' ones, i.e. those that are known to not work with Freqtrade):
```
$ freqtrade list-exchanges -a
All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpro, bcex, bequant, bibox, bigone, binance, binanceje, binanceus, bit2c, bitbank, bitbay, bitfinex, bitfinex2, bitflyer, bitforex, bithumb, bitkk, bitlish, bitmart, bitmex, bitso, bitstamp, bitstamp1, bittrex, bitz, bl3p, bleutrade, braziliex, btcalpha, btcbox, btcchina, btcmarkets, btctradeim, btctradeua, btcturk, buda, bxinth, cex, chilebit, cobinhood, coinbase, coinbaseprime, coinbasepro, coincheck, coinegg, coinex, coinexchange, coinfalcon, coinfloor, coingi, coinmarketcap, coinmate, coinone, coinspot, cointiger, coolcoin, coss, crex24, crypton, deribit, digifinex, dsx, dx, ethfinex, exmo, exx, fcoin, fcoinjp, flowbtc, foxbit, fybse, gateio, gdax, gemini, hitbtc, hitbtc2, huobipro, huobiru, ice3x, idex, independentreserve, indodax, itbit, kkex, kraken, kucoin, kucoin2, kuna, lakebtc, latoken, lbank, liquid, livecoin, luno, lykke, mandala, mercado, mixcoins, negociecoins, nova, oceanex, okcoincny, okcoinusd, okex, okex3, paymium, poloniex, rightbtc, southxchange, stronghold, surbitcoin, theocean, therock, tidebit, tidex, upbit, vaultoro, vbtc, virwox, xbtce, yobit, zaif, zb
All exchanges supported by the ccxt library:
Exchange name Valid reason
------------------ ------- ---------------------------------------------------------------------------------------
aax True
aofex False missing: fetchOrder
ascendex True missing opt: fetchMyTrades
bequant True
bibox True
bigone True
binance True
binanceus True
bit2c False missing: fetchOrder, fetchOHLCV
bitbank True missing opt: fetchTickers
bitbay False missing: fetchOrder
bitcoincom True
bitfinex True
bitfinex2 False missing: fetchOrder
bitflyer False missing: fetchOrder, fetchOHLCV
bitforex True missing opt: fetchMyTrades, fetchTickers
bitget True
bithumb True missing opt: fetchMyTrades
bitkk True missing opt: fetchMyTrades
bitmart True
bitmax True missing opt: fetchMyTrades
bitmex False Various reasons.
bitpanda True
bitso False missing: fetchOHLCV
bitstamp False Does not provide history. Details in https://github.com/freqtrade/freqtrade/issues/1983
bitstamp1 False missing: fetchOrder, fetchOHLCV
bittrex True
bitvavo True
bitz True missing opt: fetchMyTrades
bl3p False missing: fetchOrder, fetchOHLCV
bleutrade False missing: fetchOrder
braziliex False missing: fetchOHLCV
btcalpha True missing opt: fetchTicker, fetchTickers
btcbox False missing: fetchOHLCV
btcmarkets True missing opt: fetchTickers
btctradeua False missing: fetchOrder, fetchOHLCV
btcturk False missing: fetchOrder
buda True missing opt: fetchMyTrades, fetchTickers
bw True missing opt: fetchMyTrades, fetchL2OrderBook
bybit True
bytetrade True
cdax True
cex True missing opt: fetchMyTrades
chilebit False missing: fetchOrder, fetchOHLCV
coinbase False missing: fetchOrder, cancelOrder, createOrder, fetchOHLCV
coinbaseprime True missing opt: fetchTickers
coinbasepro True missing opt: fetchTickers
coincheck False missing: fetchOrder, fetchOHLCV
coinegg False missing: fetchOHLCV
coinex True
coinfalcon False missing: fetchOHLCV
coinfloor False missing: fetchOrder, fetchOHLCV
coingi False missing: fetchOrder, fetchOHLCV
coinmarketcap False missing: fetchOrder, cancelOrder, createOrder, fetchBalance, fetchOHLCV
coinmate False missing: fetchOHLCV
coinone False missing: fetchOHLCV
coinspot False missing: fetchOrder, cancelOrder, fetchOHLCV
crex24 True
currencycom False missing: fetchOrder
delta False missing: fetchOrder
deribit True
digifinex True
equos True missing opt: fetchTicker, fetchTickers
eterbase True
exmo False missing: fetchOrder
exx False missing: fetchOHLCV
fcoin True missing opt: fetchMyTrades, fetchTickers
fcoinjp True missing opt: fetchMyTrades, fetchTickers
flowbtc False missing: fetchOrder, fetchOHLCV
foxbit False missing: fetchOrder, fetchOHLCV
ftx True
gateio True
gemini True
gopax True
hbtc True
hitbtc True
hollaex False missing: fetchOrder
huobijp True
huobipro True
idex True
independentreserve False missing: fetchOHLCV
indodax False missing: fetchOHLCV
itbit False missing: fetchOHLCV
kraken True
kucoin True
kuna False missing: fetchOHLCV
lakebtc False missing: fetchOrder, fetchOHLCV
latoken False missing: fetchOrder, fetchOHLCV
lbank True missing opt: fetchMyTrades
liquid False missing: fetchOHLCV
luno False missing: fetchOHLCV
lykke False missing: fetchOHLCV
mercado True missing opt: fetchTickers
mixcoins False missing: fetchOrder, fetchOHLCV
ndax True missing opt: fetchTickers
novadax True
oceanex False missing: fetchOHLCV
okcoin True
okex True
paymium False missing: fetchOrder, fetchOHLCV
phemex False Does not provide history.
poloniex False missing: fetchOrder
probit True
qtrade True
rightbtc False missing: fetchOrder
ripio False missing: fetchOHLCV
southxchange False missing: fetchOrder, fetchOHLCV
stex True
surbitcoin False missing: fetchOrder, fetchOHLCV
therock False missing: fetchOHLCV
tidebit False missing: fetchOrder
tidex False missing: fetchOHLCV
timex True
upbit True missing opt: fetchMyTrades
vbtc False missing: fetchOrder, fetchOHLCV
vcc True
wavesexchange False missing: fetchOrder
whitebit False missing: fetchOrder, cancelOrder, createOrder, fetchBalance
xbtce False missing: fetchOrder, fetchOHLCV
xena False missing: fetchOrder
yobit False missing: fetchOHLCV
zaif False missing: fetchOrder, fetchOHLCV
zb True missing opt: fetchMyTrades
```
## List Timeframes
Use the `list-timeframes` subcommand to see the list of timeframes (ticker intervals) available for the exchange.
Use the `list-timeframes` subcommand to see the list of timeframes available for the exchange.
```
usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1]
@ -421,6 +541,42 @@ Show whitelist when using a [dynamic pairlist](plugins.md#pairlists).
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
You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command.
@ -509,7 +665,8 @@ You can show the details of any hyperoptimization epoch previously evaluated by
usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--best]
[--profitable] [-n INT] [--print-json]
[--hyperopt-filename PATH] [--no-header]
[--hyperopt-filename FILENAME] [--no-header]
[--disable-param-export]
optional arguments:
-h, --help show this help message and exit
@ -521,6 +678,8 @@ optional arguments:
Hyperopt result filename.Example: `--hyperopt-
filename=hyperopt_results_2020-09-27_16-20-48.pickle`
--no-header Do not print epoch details header.
--disable-param-export
Disable automatic hyperopt parameter export.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).

View File

@ -19,6 +19,11 @@ Sample configuration (tested using IFTTT).
"value1": "Cancelling Open Buy Order for {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
},
"webhookbuyfill": {
"value1": "Buy Order for {pair} filled",
"value2": "at {open_rate:8f}",
"value3": ""
},
"webhooksell": {
"value1": "Selling {pair}",
@ -30,6 +35,11 @@ Sample configuration (tested using IFTTT).
"value2": "limit {limit:8f}",
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
},
"webhooksellfill": {
"value1": "Sell Order for {pair} filled",
"value2": "at {close_rate:8f}.",
"value3": ""
},
"webhookstatus": {
"value1": "Status: {status}",
"value2": "",
@ -73,6 +83,7 @@ Possible parameters are:
* `fiat_currency`
* `order_type`
* `current_rate`
* `buy_tag`
### Webhookbuycancel
@ -90,6 +101,23 @@ Possible parameters are:
* `fiat_currency`
* `order_type`
* `current_rate`
* `buy_tag`
### Webhookbuyfill
The fields in `webhook.webhookbuyfill` are filled when the bot filled a buy order. Parameters are filled using string.format.
Possible parameters are:
* `trade_id`
* `exchange`
* `pair`
* `open_rate`
* `amount`
* `open_date`
* `stake_amount`
* `stake_currency`
* `fiat_currency`
* `buy_tag`
### Webhooksell
@ -103,6 +131,27 @@ Possible parameters are:
* `limit`
* `amount`
* `open_rate`
* `profit_amount`
* `profit_ratio`
* `stake_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`
* `open_date`
* `close_date`
### Webhooksellfill
The fields in `webhook.webhooksellfill` are filled when the bot fills a sell order (closes a Trae). Parameters are filled using string.format.
Possible parameters are:
* `trade_id`
* `exchange`
* `pair`
* `gain`
* `close_rate`
* `amount`
* `open_rate`
* `current_rate`
* `profit_amount`
* `profit_ratio`

View File

@ -1,3 +1,5 @@
# Windows installation
We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure).
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
@ -21,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).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.19cp38cp38win_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.
Other versions must be downloaded from the above link.

View File

@ -4,7 +4,7 @@ channels:
# - defaults
dependencies:
# 1/4 req main
- python>=3.7
- python>=3.7,<3.9
- numpy
- pandas
- pip

View File

@ -22,7 +22,7 @@ if __version__ == 'develop':
# subprocess.check_output(
# ['git', 'log', '--format="%h"', '-n 1'],
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
except Exception:
except Exception: # pragma: no cover
# git not available, ignore
try:
# Try Fallback to freqtrade_commit file (created by CI while building docker image)

View File

@ -8,15 +8,16 @@ Note: Be careful with file-scoped imports in these subfiles.
"""
from freqtrade.commands.arguments import Arguments
from freqtrade.commands.build_config_commands import start_new_config
from freqtrade.commands.data_commands import (start_convert_data, start_download_data,
start_list_data)
from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades,
start_download_data, start_list_data)
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.list_commands import (start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies,
start_list_timeframes, start_show_trades)
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets,
start_list_strategies, start_list_timeframes,
start_show_trades)
from freqtrade.commands.optimize_commands import start_backtesting, start_edge, start_hyperopt
from freqtrade.commands.pairlist_commands import start_test_pairlist
from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit
from freqtrade.commands.trade_commands import start_trading
from freqtrade.commands.webserver_commands import start_webserver

View File

@ -16,11 +16,13 @@ ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
ARGS_WEBSERVER: List[str] = []
ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
"max_open_trades", "stake_amount", "fee"]
"max_open_trades", "stake_amount", "fee", "pairs"]
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", "show_days"]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
@ -29,7 +31,8 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"epochs", "spaces", "print_all",
"print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades",
"hyperopt_loss"]
"hyperopt_loss", "disableparamexport",
"hyperopt_ignore_missing_space"]
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
@ -53,24 +56,25 @@ ARGS_BUILD_CONFIG = ["config"]
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_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "timerange", "download_trades", "exchange",
"timeframes", "erase", "dataformat_ohlcv", "dataformat_trades"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "timerange",
"download_trades", "exchange", "timeframes", "erase", "dataformat_ohlcv",
"dataformat_trades"]
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
"db_url", "trade_source", "export", "exportfilename",
"timerange", "timeframe", "no_trades"]
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "timeframe"]
"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"]
@ -84,14 +88,15 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
"hyperoptexportfilename", "export_csv"]
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"]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
"list-markets", "list-pairs", "list-strategies", "list-data",
"list-hyperopts", "hyperopt-list", "hyperopt-show",
"plot-dataframe", "plot-profit", "show-trades"]
"hyperopt-list", "hyperopt-show",
"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:
@ -167,14 +172,14 @@ class Arguments:
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
self._build_args(optionlist=['version'], parser=self.parser)
from freqtrade.commands import (start_backtesting, start_convert_data, start_create_userdir,
start_download_data, start_edge, start_hyperopt,
start_hyperopt_list, start_hyperopt_show, start_install_ui,
start_list_data, start_list_exchanges, start_list_hyperopts,
from freqtrade.commands import (start_backtesting, start_convert_data, start_convert_trades,
start_create_userdir, start_download_data, start_edge,
start_hyperopt, start_hyperopt_list, start_hyperopt_show,
start_install_ui, start_list_data, start_list_exchanges,
start_list_markets, start_list_strategies,
start_list_timeframes, start_new_config, start_new_hyperopt,
start_new_strategy, start_plot_dataframe, start_plot_profit,
start_show_trades, start_test_pairlist, start_trading)
start_list_timeframes, start_new_config, start_new_strategy,
start_plot_dataframe, start_plot_profit, start_show_trades,
start_test_pairlist, start_trading, start_webserver)
subparsers = self.parser.add_subparsers(dest='command',
# Use custom message when no subhandler is added
@ -201,12 +206,6 @@ class Arguments:
build_config_cmd.set_defaults(func=start_new_config)
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
build_strategy_cmd = subparsers.add_parser('new-strategy',
help="Create new strategy")
@ -240,6 +239,15 @@ class Arguments:
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)
# 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
list_data_cmd = subparsers.add_parser(
'list-data',
@ -295,15 +303,6 @@ class Arguments:
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
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
list_markets_cmd = subparsers.add_parser(
'list-markets',
@ -382,3 +381,9 @@ class Arguments:
)
plot_profit_cmd.set_defaults(func=start_plot_profit)
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)

View File

@ -1,9 +1,11 @@
import logging
import secrets
from pathlib import Path
from typing import Any, Dict, List
from questionary import Separator, prompt
from freqtrade.configuration.directory_operations import chown_user_directory
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
@ -59,21 +61,27 @@ def ask_user_config() -> Dict[str, Any]:
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": 'BTC',
"default": 'USDT',
},
{
"type": "text",
"name": "stake_amount",
"message": "Please insert your stake amount:",
"default": "0.01",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "100",
"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",
"name": "max_open_trades",
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
"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",
@ -97,6 +105,8 @@ def ask_user_config() -> Dict[str, Any]:
"bittrex",
"kraken",
"ftx",
"kucoin",
"gateio",
Separator(),
"other",
],
@ -120,6 +130,12 @@ def ask_user_config() -> Dict[str, Any]:
"message": "Insert Exchange Secret",
"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",
"name": "telegram",
@ -138,6 +154,33 @@ def ask_user_config() -> Dict[str, Any]:
"message": "Insert Telegram chat id",
"when": lambda x: x['telegram']
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": ("Insert Api server Listen Address (0.0.0.0 for docker, "
"otherwise best left untouched)"),
"default": "127.0.0.1",
"when": lambda x: x['api_server']
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x['api_server']
},
{
"type": "text",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x['api_server']
},
]
answers = prompt(questions)
@ -145,6 +188,9 @@ def ask_user_config() -> Dict[str, Any]:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Force JWT token to be a random string
answers['api_server_jwt_key'] = secrets.token_hex()
return answers
@ -152,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
: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
try:
@ -182,10 +228,11 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
def start_new_config(args: Dict[str, Any]) -> None:
"""
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])
chown_user_directory(config_path.parent)
if config_path.exists():
overwrite = ask_user_overwrite(config_path)
if overwrite:

View File

@ -1,7 +1,7 @@
"""
Definition of cli arguments used in arguments.py
"""
from argparse import ArgumentTypeError
from argparse import SUPPRESS, ArgumentTypeError
from freqtrade import __version__, constants
from freqtrade.constants import HYPEROPT_LOSS_BUILTIN
@ -118,7 +118,7 @@ AVAILABLE_CLI_OPTIONS = {
# Optimize common
"timeframe": Arg(
'-i', '--timeframe', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"timerange": Arg(
'--timerange',
@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
help='Override the value of the `stake_amount` configuration setting.',
),
# Backtesting
"timeframe_detail": Arg(
'--timeframe-detail',
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"position_stacking": Arg(
'--eps', '--enable-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 '
'or via command line. When using this together with `--export trades`, '
'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='+',
),
"export": Arg(
'--export',
help='Export backtest results, argument are: trades. '
'Example: `--export=trades`',
help='Export backtest results (default: trades).',
choices=constants.EXPORT_OPTIONS,
),
"exportfilename": Arg(
'--export-filename',
@ -177,6 +182,11 @@ AVAILABLE_CLI_OPTIONS = {
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
metavar='PATH',
),
"disableparamexport": Arg(
'--disable-param-export',
help="Disable automatic hyperopt parameter export.",
action='store_true',
),
"fee": Arg(
'--fee',
help='Specify fee ratio. Will be applied twice (on trade entry and exit).',
@ -199,12 +209,13 @@ AVAILABLE_CLI_OPTIONS = {
# Hyperopt
"hyperopt": Arg(
'--hyperopt',
help='Specify hyperopt class name which will be used by the bot.',
help=SUPPRESS,
metavar='NAME',
required=False,
),
"hyperopt_path": Arg(
'--hyperopt-path',
help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.',
help='Specify additional lookup path for Hyperopt Loss functions.',
metavar='PATH',
),
"epochs": Arg(
@ -217,7 +228,7 @@ AVAILABLE_CLI_OPTIONS = {
"spaces": Arg(
'--spaces',
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='+',
default='default',
),
@ -272,7 +283,7 @@ AVAILABLE_CLI_OPTIONS = {
default=1,
),
"hyperopt_loss": Arg(
'--hyperopt-loss',
'--hyperopt-loss', '--hyperoptloss',
help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). '
'Different functions can generate completely different results, '
'since the target for optimization is different. Built-in Hyperopt-loss-functions are: '
@ -335,7 +346,7 @@ AVAILABLE_CLI_OPTIONS = {
# Script options
"pairs": Arg(
'-p', '--pairs',
help='Show profits for only these pairs. Pairs are space-separated.',
help='Limit command to these pairs. Pairs are space-separated.',
nargs='+',
),
# Download data
@ -350,6 +361,12 @@ AVAILABLE_CLI_OPTIONS = {
type=check_int_positive,
metavar='INT',
),
"new_pairs_days": Arg(
'--new-pairs-days',
help='Download data of new pairs for given number of days. Default: `%(default)s`.',
type=check_int_positive,
metavar='INT',
),
"download_trades": Arg(
'--dl-trades',
help='Download trades instead of OHLCV data. The bot will resample trades to the '
@ -370,12 +387,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"dataformat_ohlcv": Arg(
'--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,
),
"dataformat_trades": Arg(
'--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,
),
"exchange": Arg(
@ -403,6 +420,12 @@ AVAILABLE_CLI_OPTIONS = {
action='store_true',
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
"template": Arg(
'--template',
@ -432,6 +455,11 @@ AVAILABLE_CLI_OPTIONS = {
metavar='INT',
default=750,
),
"plot_auto_open": Arg(
'--auto-open',
help='Automatically open generated plot.',
action='store_true',
),
"no_trades": Arg(
'--no-trades',
help='Skip using trades from backtesting file and DB.',
@ -536,4 +564,10 @@ AVAILABLE_CLI_OPTIONS = {
help='Do not print epoch details header.',
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",
),
}

View File

@ -8,11 +8,11 @@ from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data,
refresh_backtest_trades_data)
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -48,6 +48,7 @@ def start_download_data(args: Dict[str, Any]) -> None:
# 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))
@ -62,8 +63,8 @@ def start_download_data(args: Dict[str, Any]) -> None:
if config.get('download_trades'):
pairs_not_available = refresh_backtest_trades_data(
exchange, pairs=expanded_pairs, datadir=config['datadir'],
timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_trades'])
timerange=timerange, new_pairs_days=config['new_pairs_days'],
erase=bool(config.get('erase')), data_format=config['dataformat_trades'])
# Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv(
@ -75,8 +76,9 @@ def start_download_data(args: Dict[str, Any]) -> None:
else:
pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_ohlcv'])
datadir=config['datadir'], timerange=timerange,
new_pairs_days=config['new_pairs_days'],
erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'])
except KeyboardInterrupt:
sys.exit("SIGINT received, aborting ...")
@ -87,6 +89,41 @@ def start_download_data(args: Dict[str, Any]) -> None:
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:
"""
Convert data from one format to another

View File

@ -7,10 +7,10 @@ import requests
from freqtrade.configuration import setup_utils_configuration
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.exceptions import OperationalException
from freqtrade.misc import render_template, render_template_with_fallback
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
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')
@ -89,58 +87,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
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):
if directory.is_dir():
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)
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/'
# Get base UI Repo path
@ -190,6 +136,14 @@ def get_ui_download_url() -> Tuple[str, str]:
resp.raise_for_status()
r = resp.json()
if version:
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 = ''
@ -210,7 +164,7 @@ def start_install_ui(args: Dict[str, Any]) -> None:
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
# 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)
if curr_version == latest_version and not args.get('erase_ui_only'):

View File

@ -1,13 +1,14 @@
import logging
from operator import itemgetter
from typing import Any, Dict, List
from typing import Any, Dict
from colorama import init as colorama_init
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
from freqtrade.optimize.optimize_reports import show_backtest_result
logger = logging.getLogger(__name__)
@ -17,7 +18,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
"""
List hyperopt epochs previously evaluated
"""
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@ -27,49 +28,32 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
no_details = config.get('hyperopt_list_no_details', 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(
config['user_data_dir'] / 'hyperopt_results',
config.get('hyperoptexportfilename'))
# Previous evaluations
epochs = Hyperopt.load_previous_results(results_file)
total_epochs = len(epochs)
epochs = hyperopt_filter_epochs(epochs, filteroptions)
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
if print_colorized:
colorama_init(autoreset=True)
if not export_csv:
try:
print(Hyperopt.get_result_table(config, epochs, total_epochs,
not filteroptions['only_best'], print_colorized, 0))
print(HyperoptTools.get_result_table(config, epochs, total_epochs,
not config.get('hyperopt_list_best', False),
print_colorized, 0))
except KeyboardInterrupt:
print('User interrupted..')
if epochs and not no_details:
sorted_epochs = sorted(epochs, key=itemgetter('loss'))
results = sorted_epochs[0]
Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header)
HyperoptTools.show_epoch_details(results, total_epochs, print_json, no_header)
if epochs and export_csv:
Hyperopt.export_csv_file(
config, epochs, total_epochs, not filteroptions['only_best'], export_csv
HyperoptTools.export_csv_file(
config, epochs, export_csv
)
@ -77,7 +61,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
"""
Show details of a hyperopt epoch previously evaluated
"""
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@ -89,26 +73,9 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
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
epochs = Hyperopt.load_previous_results(results_file)
total_epochs = len(epochs)
epochs, total_epochs = HyperoptTools.load_filtered_results(results_file, config)
epochs = hyperopt_filter_epochs(epochs, filteroptions)
filtered_epochs = len(epochs)
if n > filtered_epochs:
@ -124,105 +91,14 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
if epochs:
val = epochs[n]
Hyperopt.print_epoch_details(val, total_epochs, print_json, no_header,
metrics = val['results_metrics']
if 'strategy_name' in metrics:
strategy_name = metrics['strategy_name']
show_backtest_result(strategy_name, metrics,
metrics['stake_currency'])
HyperoptTools.try_export_params(config, strategy_name, val)
HyperoptTools.show_epoch_details(val, total_epochs, print_json, no_header,
header_str="Epoch details")
def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
"""
Filter our items from the list of hyperopt results
"""
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']['profit'] > 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_count(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_trades'] > 0:
epochs = [
x for x in epochs
if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades']
]
if filteroptions['filter_max_trades'] > 0:
epochs = [
x for x in epochs
if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
]
return epochs
def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
if filteroptions['filter_min_avg_time'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']
]
if filteroptions['filter_max_avg_time'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['duration'] < 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 = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit']
]
if filteroptions['filter_max_avg_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['avg_profit'] < filteroptions['filter_max_avg_profit']
]
if filteroptions['filter_min_total_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']
]
if filteroptions['filter_max_total_profit'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [
x for x in epochs
if x['results_metrics']['profit'] < 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 = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']]
if filteroptions['filter_max_objective'] is not None:
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']]
return epochs

View File

@ -1,7 +1,6 @@
import csv
import logging
import sys
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict, List
@ -11,12 +10,12 @@ from colorama import init as colorama_init
from tabulate import tabulate
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.exceptions import OperationalException
from freqtrade.exchange import available_exchanges, ccxt_exchanges, market_is_active
from freqtrade.misc import plural
from freqtrade.exchange import market_is_active, validate_exchanges
from freqtrade.misc import parse_db_uri_for_logging, plural
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -28,14 +27,18 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
exchanges = ccxt_exchanges() if args['list_exchanges_all'] else available_exchanges()
exchanges = validate_exchanges(args['list_exchanges_all'])
if args['print_one_column']:
print('\n'.join(exchanges))
print('\n'.join([e[0] for e in exchanges]))
else:
if args['list_exchanges_all']:
print(f"All exchanges supported by the ccxt library: {', '.join(exchanges)}")
print("All exchanges supported by the ccxt library:")
else:
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
print("Exchanges available for Freqtrade:")
exchanges = [e for e in exchanges if e[1] is not False]
print(tabulate(exchanges, headers=['Exchange name', 'Valid', 'reason']))
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
@ -50,15 +53,21 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
reset = ''
names = [s['name'] for s in objs]
objss_to_print = [{
objs_to_print = [{
'name': s['name'] if s['name'] else "--",
'location': s['location'].name,
'status': (red + "LOAD FAILED" + reset if s['class'] is None
else "OK" if names.count(s['name']) == 1
else yellow + "DUPLICATE NAME" + reset)
} for s in objs]
print(tabulate(objss_to_print, headers='keys', tablefmt='psql', stralign='right'))
for idx, s in enumerate(objs):
if 'hyperoptable' in s:
objs_to_print[idx].update({
'hyperoptable': "Yes" if s['hyperoptable']['count'] > 0 else "No",
'buy-Params': len(s['hyperoptable'].get('buy', [])),
'sell-Params': len(s['hyperoptable'].get('sell', [])),
})
print(tabulate(objs_to_print, headers='keys', tablefmt='psql', stralign='right'))
def start_list_strategies(args: Dict[str, Any]) -> None:
@ -71,6 +80,11 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically
strategy_objs = sorted(strategy_objs, key=lambda x: x['name'])
for obj in strategy_objs:
if obj['class']:
obj['hyperoptable'] = obj['class'].detect_all_parameters()
else:
obj['hyperoptable'] = {'count': 0}
if args['print_one_column']:
print('\n'.join([s['name'] for s in strategy_objs]))
@ -78,28 +92,9 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
_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:
"""
Print ticker intervals (timeframes) available on Exchange
Print timeframes available on Exchange
"""
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use timeframe set in the config
@ -139,7 +134,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
pairs_only=pairs_only,
active_only=active_only)
# Sort the pairs/markets by symbol
pairs = OrderedDict(sorted(pairs.items()))
pairs = dict(sorted(pairs.items()))
except Exception as e:
raise OperationalException(f"Cannot get markets. Reason: {e}") from e
@ -177,7 +172,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
# human-readable formats.
print()
if len(pairs):
if pairs:
if args.get('print_list', False):
# print data as a list, with human-readable summary
print(f"{summary_str}: {', '.join(pairs.keys())}.")
@ -211,7 +206,7 @@ def start_show_trades(args: Dict[str, Any]) -> None:
if 'db_url' not in config:
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)
tfilter = []

View File

@ -3,9 +3,9 @@ from typing import Any, Dict
from freqtrade import constants
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.misc import round_coin_value
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -15,6 +15,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
"""
Prepare the configuration for the Hyperopt module
:param args: Cli args from Arguments()
:param method: Bot running mode
:return: Configuration
"""
config = setup_utils_configuration(args, method)

View File

@ -4,8 +4,8 @@ from typing import Any, Dict
import rapidjson
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -31,7 +31,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
results[curr] = pairlists.whitelist
for curr, pairlist in results.items():
if not args.get('print_one_column', False):
if not args.get('print_one_column', False) and not args.get('list_pairs_print_json', False):
print(f"Pairs for {curr}: ")
if args.get('print_one_column', False):

View File

@ -1,8 +1,8 @@
from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
def validate_plot_args(args: Dict[str, Any]) -> None:

View 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)

View 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)

View File

@ -1,7 +1,8 @@
# 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_validation import validate_config_consistency
from freqtrade.configuration.configuration import Configuration
from freqtrade.configuration.PeriodicCache import PeriodicCache
from freqtrade.configuration.timerange import TimeRange

View File

@ -1,28 +1,15 @@
import logging
from typing import Any, Dict
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason, is_exchange_bad,
is_exchange_known_ccxt, is_exchange_officially_supported)
from freqtrade.state import RunMode
from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
is_exchange_officially_supported, validate_exchange)
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:
"""
Check if the exchange name in the config file is supported by Freqtrade
@ -57,9 +44,13 @@ def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
f'{", ".join(available_exchanges())}'
)
if check_for_bad and is_exchange_bad(exchange):
raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. '
f'Reason: {get_exchange_bad_reason(exchange)}')
valid, reason = validate_exchange(exchange)
if not valid:
if check_for_bad:
raise OperationalException(f'Exchange "{exchange}" will not work with Freqtrade. '
f'Reason: {reason}')
else:
logger.warning(f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}')
if is_exchange_officially_supported(exchange):
logger.info(f'Exchange "{exchange}" is officially supported '

View File

@ -1,9 +1,8 @@
import logging
from typing import Any, Dict
from freqtrade.state import RunMode
from freqtrade.enums import RunMode
from .check_exchange import remove_credentials
from .config_validation import validate_config_consistency
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
:param args: Cli args from Arguments()
:param method: Bot running mode
:return: Configuration
"""
configuration = Configuration(args, method)
config = configuration.get_config()
# Ensure we do not use Exchange credentials
remove_credentials(config)
# Ensure these modes are using Dry-run
config['dry_run'] = True
validate_config_consistency(config)
return config

View File

@ -6,8 +6,8 @@ from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import constants
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -74,10 +74,12 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
# validating trailing stoploss
_validate_trailing_stoploss(conf)
_validate_price_config(conf)
_validate_edge(conf)
_validate_whitelist(conf)
_validate_protections(conf)
_validate_unlimited_amount(conf)
_validate_ask_orderbook(conf)
# validate configuration before returning
logger.info('Validating configuration ...')
@ -95,6 +97,19 @@ def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
raise OperationalException("`max_open_trades` and `stake_amount` cannot both be unlimited.")
def _validate_price_config(conf: Dict[str, Any]) -> None:
"""
When using market orders, price sides must be using the "other" side of the price
"""
if (conf.get('order_types', {}).get('buy') == 'market'
and conf.get('bid_strategy', {}).get('price_side') != 'ask'):
raise OperationalException('Market buy orders require bid_strategy.price_side = "ask".')
if (conf.get('order_types', {}).get('sell') == 'market'
and conf.get('ask_strategy', {}).get('price_side') != 'bid'):
raise OperationalException('Market sell orders require ask_strategy.price_side = "bid".')
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
if conf.get('stoploss') == 0.0:
@ -135,12 +150,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
if not conf.get('edge', {}).get('enabled'):
return
if conf.get('pairlist', {}).get('method') == 'VolumePairList':
raise OperationalException(
"Edge and VolumePairList are incompatible, "
"Edge will override whatever pairs VolumePairlist selects."
)
if not conf.get('ask_strategy', {}).get('use_sell_signal', True):
if not conf.get('use_sell_signal', True):
raise OperationalException(
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
)
@ -177,3 +187,23 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
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."
)

View File

@ -11,11 +11,12 @@ from freqtrade import constants
from freqtrade.configuration.check_exchange import check_exchange
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
from freqtrade.configuration.load_config import load_config_file
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
from freqtrade.configuration.load_config import load_config_file, load_file
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging
from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.state import NON_UTIL_MODES, TRADING_MODES, RunMode
from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging
logger = logging.getLogger(__name__)
@ -72,11 +73,14 @@ class Configuration:
# Merge config options, overwriting old values
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
if 'internals' not in config:
config['internals'] = {}
# TODO: This can be deleted along with removal of deprecated
# experimental settings
if 'ask_strategy' not in config:
config['ask_strategy'] = {}
@ -108,6 +112,8 @@ class Configuration:
self._process_plot_options(config)
self._process_data_options(config)
# Check if the exchange set by the user is supported
check_exchange(config, config.get('experimental', {}).get('block_bad_exchanges', True))
@ -144,7 +150,7 @@ class Configuration:
config['db_url'] = constants.DEFAULT_DB_PROD_URL
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:
@ -236,6 +242,9 @@ class Configuration:
except ValueError:
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',
logstring='Parameter --stake-amount detected, '
'overriding stake_amount to: {} ...')
@ -263,6 +272,9 @@ class Configuration:
self._args_to_config(config, argname='show_days',
logstring='Parameter --show-days detected ...')
self._args_to_config(config, argname='disableparamexport',
logstring='Parameter --disableparamexport detected: {} ...')
# Edge section:
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
txt_range = eval(self.args["stoploss_range"])
@ -361,6 +373,9 @@ class Configuration:
self._args_to_config(config, argname='hyperopt_show_no_header',
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:
self._args_to_config(config, argname='pairs',
@ -378,6 +393,9 @@ class Configuration:
self._args_to_config(config, argname='plot_limit',
logstring='Limiting plot to: {}')
self._args_to_config(config, argname='plot_auto_open',
logstring='Parameter --auto-open detected.')
self._args_to_config(config, argname='trade_source',
logstring='Using trades from: {}')
@ -402,6 +420,11 @@ class Configuration:
self._args_to_config(config, argname='dataformat_trades',
logstring='Using "{}" to store trades data.')
def _process_data_options(self, config: Dict[str, Any]) -> None:
self._args_to_config(config, argname='new_pairs_days',
logstring='Detected --new-pairs-days: {}')
def _process_runmode(self, config: Dict[str, Any]) -> None:
self._args_to_config(config, argname='dry_run',
@ -448,17 +471,17 @@ class Configuration:
"""
if "pairs" in config:
config['exchange']['pair_whitelist'] = config['pairs']
return
if "pairs_file" in self.args and self.args["pairs_file"]:
pairs_file = Path(self.args["pairs_file"])
logger.info(f'Reading pairs file "{pairs_file}".')
# 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():
raise OperationalException(f'No pairs file found with path "{pairs_file}".')
with pairs_file.open('r') as f:
config['pairs'] = json_load(f)
config['pairs'] = load_file(pairs_file)
config['pairs'].sort()
return
@ -469,7 +492,6 @@ class Configuration:
# Fall back to /dl_path/pairs.json
pairs_file = config['datadir'] / 'pairs.json'
if pairs_file.exists():
with pairs_file.open('r') as f:
config['pairs'] = json_load(f)
config['pairs'] = load_file(pairs_file)
if 'pairs' in config:
config['pairs'].sort()

View File

@ -3,7 +3,7 @@ Functions to handle deprecated settings
"""
import logging
from typing import Any, Dict
from typing import Any, Dict, Optional
from freqtrade.exceptions import OperationalException
@ -12,23 +12,24 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str) -> None:
section1_config = config.get(section1, {})
section2_config = config.get(section2, {})
if name1 in section1_config and name2 in section2_config:
section_old: str, name_old: str,
section_new: Optional[str], name_new: str) -> None:
section_new_config = config.get(section_new, {}) if section_new else config
section_old_config = config.get(section_old, {})
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(
f"Conflicting settings `{section1}.{name1}` and `{section2}.{name2}` "
f"Conflicting settings `{new_name}` and `{section_old}.{name_old}` "
"(DEPRECATED) detected in the configuration file. "
"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."
)
def process_removed_setting(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str) -> None:
section2: Optional[str], name2: str) -> None:
"""
:param section1: Removed section
:param name1: Removed setting name
@ -37,27 +38,32 @@ def process_removed_setting(config: Dict[str, Any],
"""
section1_config = config.get(section1, {})
if name1 in section1_config:
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
raise OperationalException(
f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. "
f"Please delete it from your configuration and use the `{section2}.{name2}` "
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
f"Please delete it from your configuration and use the `{section_2}` "
"setting instead."
)
def process_deprecated_setting(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str) -> None:
section2_config = config.get(section2, {})
section_old: str, name_old: str,
section_new: Optional[str], name_new: str
) -> 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(
"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. "
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:
@ -65,15 +71,24 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
# Kept for future deprecated / moved settings
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
# 'experimental', 'use_sell_signal')
# process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
# 'experimental', 'use_sell_signal')
process_deprecated_setting(config, 'ask_strategy', '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',
'ask_strategy', 'use_sell_signal')
None, 'use_sell_signal')
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',
'ask_strategy', 'ignore_roi_if_buy_signal')
None, 'ignore_roi_if_buy_signal')
if (config.get('edge', {}).get('enabled', False)
and 'capital_available_percentage' in config.get('edge', {})):
@ -95,3 +110,6 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
"Please remove 'ticker_interval' from your configuration to continue operating."
)
config['timeframe'] = config['ticker_interval']
if 'protections' in config:
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.")

View File

@ -24,6 +24,21 @@ def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> Pat
return folder
def chown_user_directory(directory: Path) -> None:
"""
Use Sudo to change permissions of the home-directory if necessary
Only applies when running in docker!
"""
import os
if os.environ.get('FT_APP_ENV') == 'docker':
try:
import subprocess
subprocess.check_output(
['sudo', 'chown', '-R', 'ftuser:', str(directory.resolve())])
except Exception:
logger.warning(f"Could not chown {directory}")
def create_userdata_dir(directory: str, create_dir: bool = False) -> Path:
"""
Create userdata directory structure.
@ -37,6 +52,7 @@ def create_userdata_dir(directory: str, create_dir: bool = False) -> Path:
sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "logs",
"notebooks", "plot", "strategies", ]
folder = Path(directory)
chown_user_directory(folder)
if not folder.is_dir():
if create_dir:
folder.mkdir(parents=True)
@ -72,6 +88,5 @@ def copy_sample_files(directory: Path, overwrite: bool = False) -> None:
if not overwrite:
logger.warning(f"File `{targetfile}` exists already, not deploying sample file.")
continue
else:
logger.warning(f"File `{targetfile}` exists already, overwriting.")
shutil.copy(str(sourcedir / source), str(targetfile))

View 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)

View File

@ -38,6 +38,15 @@ def log_config_error_range(path: str, errmsg: str) -> str:
return ''
def load_file(path: Path) -> Dict[str, Any]:
try:
with path.open('r') as file:
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
except FileNotFoundError:
raise OperationalException(f'File "{path}" not found!')
return config
def load_config_file(path: str) -> Dict[str, Any]:
"""
Loads a config file from the given path

View File

@ -3,10 +3,13 @@ This module contains the argument manager class
"""
import logging
import re
from datetime import datetime
from typing import Optional
import arrow
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
@ -41,7 +44,7 @@ class TimeRange:
self.startts = self.startts - seconds
def adjust_start_if_necessary(self, timeframe_secs: int, startup_candles: int,
min_date: arrow.Arrow) -> None:
min_date: datetime) -> None:
"""
Adjust startts by <startup_candles> candles.
Applies only if no startup-candles have been available.
@ -52,11 +55,11 @@ class TimeRange:
:return: None (Modifies the object in place)
"""
if (not self.starttype or (startup_candles
and min_date.int_timestamp >= self.startts)):
and min_date.timestamp() >= self.startts)):
# If no startts was defined, or backtest-data starts at the defined backtest-date
logger.warning("Moving start-date by %s candles to account for startup time.",
startup_candles)
self.startts = (min_date.int_timestamp + timeframe_secs * startup_candles)
self.startts = int(min_date.timestamp() + timeframe_secs * startup_candles)
self.starttype = 'date'
@staticmethod
@ -103,5 +106,8 @@ class TimeRange:
stop = int(stops) // 1000
else:
stop = int(stops)
if start > stop > 0:
raise OperationalException(
f'Start date is after stop date for timerange "{text}"')
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)
raise OperationalException(f'Incorrect syntax for timerange "{text}"')

View File

@ -11,6 +11,8 @@ DEFAULT_EXCHANGE = 'bittrex'
PROCESS_THROTTLE_SECS = 5 # sec
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
TIMEOUT_UNITS = ['minutes', 'seconds']
EXPORT_OPTIONS = ['none', 'trades']
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
@ -22,11 +24,12 @@ ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily']
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
'MaxDrawDownHyperOptLoss']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'AgeFilter', 'PerformanceFilter', 'PrecisionFilter',
'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter',
'SpreadFilter']
'AgeFilter', 'OffsetFilter', 'PerformanceFilter',
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
DRY_RUN_WALLET = 1000
@ -38,12 +41,16 @@ DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume']
DEFAULT_TRADES_COLUMNS = ['timestamp', 'id', 'type', 'side', 'price', 'amount', 'cost']
LAST_BT_RESULT_FN = '.last_result.json'
FTHYPT_FILEVERSION = 'fthypt_fileversion'
USERPATH_HYPEROPTS = 'hyperopts'
USERPATH_STRATEGIES = 'strategies'
USERPATH_NOTEBOOKS = 'notebooks'
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
ENV_VAR_PREFIX = 'FREQTRADE__'
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
# Define decimals per coin for outputs
@ -60,12 +67,10 @@ DUST_PER_COIN = {
}
# Soure files with destination directories within user-directory
# Source files with destination directories within user-directory
USER_DATA_FILES = {
'sample_strategy.py': USERPATH_STRATEGIES,
'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS,
'sample_hyperopt_loss.py': USERPATH_HYPEROPTS,
'sample_hyperopt.py': USERPATH_HYPEROPTS,
'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS,
}
@ -96,6 +101,7 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'max_open_trades': {'type': ['integer', 'number'], 'minimum': -1},
'new_pairs_days': {'type': 'integer', 'default': 30},
'timeframe': {'type': 'string'},
'stake_currency': {'type': 'string'},
'stake_amount': {
@ -105,10 +111,14 @@ CONF_SCHEMA = {
},
'tradable_balance_ratio': {
'type': 'number',
'minimum': 0.1,
'minimum': 0.0,
'maximum': 1,
'default': 0.99
},
'available_capital': {
'type': 'number',
'minimum': 0,
},
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
'last_stake_amount_min_ratio': {
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
@ -131,12 +141,18 @@ CONF_SCHEMA = {
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'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'},
'bot_name': {'type': 'string'},
'unfilledtimeout': {
'type': 'object',
'properties': {
'buy': {'type': 'number', 'minimum': 1},
'sell': {'type': 'number', 'minimum': 1}
'sell': {'type': 'number', 'minimum': 1},
'unit': {'type': 'string', 'enum': TIMEOUT_UNITS, 'default': 'minutes'}
}
},
'bid_strategy': {
@ -150,7 +166,7 @@ CONF_SCHEMA = {
},
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
'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': {
'type': 'object',
'properties': {
@ -159,20 +175,25 @@ CONF_SCHEMA = {
}
},
},
'required': ['ask_last_balance']
'required': ['price_side']
},
'ask_strategy': {
'type': 'object',
'properties': {
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
'bid_last_balance': {
'type': 'number',
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False,
},
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'integer', 'minimum': 1},
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'},
'sell_profit_offset': {'type': 'number', 'minimum': 0.0},
'ignore_roi_if_buy_signal': {'type': 'boolean'}
}
'order_book_top': {'type': 'integer', 'minimum': 1, 'maximum': 50, },
},
'required': ['price_side']
},
'custom_price_max_distance_ratio': {
'type': 'number', 'minimum': 0.0
},
'order_types': {
'type': 'object',
@ -240,17 +261,43 @@ CONF_SCHEMA = {
'balance_dust_level': {'type': 'number', 'minimum': 0.0},
'notification_settings': {
'type': 'object',
'default': {},
'properties': {
'status': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'buy': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'sell': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'buy_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'sell_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}
'buy_fill': {'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'off'
},
'sell': {
'type': ['string', 'object'],
'additionalProperties': {
'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS
}
},
'sell_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'sell_fill': {
'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS,
'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'],
},
'webhook': {
@ -283,6 +330,8 @@ CONF_SCHEMA = {
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
},
'db_url': {'type': 'string'},
'export': {'type': 'string', 'enum': EXPORT_OPTIONS, 'default': 'trades'},
'disableparamexport': {'type': 'boolean'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'forcebuy_enable': {'type': 'boolean'},
'disable_dataframe_checks': {'type': 'boolean'},

View File

@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"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',
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
'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',
'profit_ratio', 'profit_abs', 'sell_reason',
'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:
@ -156,6 +156,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
data = data['strategy'][strategy]['trades']
df = pd.DataFrame(data)
if not df.empty:
df['open_date'] = pd.to_datetime(df['open_date'],
utc=True,
infer_datetime_format=True
@ -167,7 +168,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
else:
# old format - only with lists.
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD)
if not df.empty:
df['open_date'] = pd.to_datetime(df['open_date'],
unit='s',
utc=True,
@ -180,6 +181,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
)
# Create compatibility with new format
df['profit_abs'] = df['close_rate'] - df['open_rate']
if not df.empty:
if 'profit_ratio' not in df.columns:
df['profit_ratio'] = df['profit_percent']
df = df.sort_values("open_date").reset_index(drop=True)
@ -337,7 +339,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
"""
Adds a column `col_name` with the cumulative profit for the given trades array.
:param df: DataFrame with date index
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
:param trades: DataFrame containing trades (requires columns close_date and profit_abs)
:param col_name: Column name that will be assigned the results
:param timeframe: Timeframe used during the operations
:return: Returns df with one additional column, col_name, containing the cumulative profit.
@ -349,8 +351,8 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
timeframe_minutes = timeframe_to_minutes(timeframe)
# Resample to timeframe to make sure trades match candles
_trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date'
)[['profit_ratio']].sum()
df.loc[:, col_name] = _trades_sum['profit_ratio'].cumsum()
)[['profit_abs']].sum()
df.loc[:, col_name] = _trades_sum['profit_abs'].cumsum()
# Set first value to 0
df.loc[df.iloc[0].name, col_name] = 0
# FFill to get continuous

View File

@ -49,7 +49,7 @@ def clean_ohlcv_dataframe(data: DataFrame, timeframe: str, pair: str, *,
fill_missing: bool = True,
drop_incomplete: bool = True) -> DataFrame:
"""
Clense a OHLCV dataframe by
Cleanse a OHLCV dataframe by
* Grouping it by date (removes duplicate tics)
* dropping last candles if requested
* Filling up missing data (if requested)
@ -110,19 +110,32 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
df.reset_index(inplace=True)
len_before = len(dataframe)
len_after = len(df)
pct_missing = (len_after - len_before) / len_before if len_before > 0 else 0
if len_before != len_after:
logger.info(f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}")
message = (f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}"
f" - {round(pct_missing * 100, 2)}%")
if pct_missing > 0.01:
logger.info(message)
else:
# Don't be verbose if only a small amount is missing
logger.debug(message)
return df
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date') -> DataFrame:
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date',
startup_candles: int = 0) -> DataFrame:
"""
Trim dataframe based on given timerange
:param df: Dataframe to trim
:param timerange: timerange (use start and end date if available)
:param: df_date_col: Column in the dataframe to use as Date column
:param df_date_col: Column in the dataframe to use as Date column
:param startup_candles: When not 0, is used instead the timerange start date
:return: trimmed dataframe
"""
if startup_candles:
# Trim candles instead of timeframe in case of given startup_candle count
df = df.iloc[startup_candles:, :]
else:
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
df = df.loc[df[df_date_col] >= start, :]
@ -132,6 +145,27 @@ def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date') -> DataF
return df
def trim_dataframes(preprocessed: Dict[str, DataFrame], timerange,
startup_candles: int) -> Dict[str, DataFrame]:
"""
Trim startup period from analyzed dataframes
:param preprocessed: Dict of pair: dataframe
:param timerange: timerange (use start and end date if available)
:param startup_candles: Startup-candles that should be removed
:return: Dict of trimmed dataframes
"""
processed: Dict[str, DataFrame] = {}
for pair, df in preprocessed.items():
trimed_df = trim_dataframe(df, timerange, startup_candles=startup_candles)
if not trimed_df.empty:
processed[pair] = trimed_df
else:
logger.warning(f'{pair} has no data left after adjusting for startup candles, '
f'skipping.')
return processed
def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:
"""
TODO: This should get a dedicated test
@ -208,7 +242,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to:
:param config: Config dictionary
:param convert_from: Source 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
src = get_datahandler(config['datadir'], convert_from)
@ -233,7 +267,7 @@ def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to:
:param config: Config dictionary
:param convert_from: Source 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
src = get_datahandler(config['datadir'], convert_from)

View File

@ -10,23 +10,36 @@ from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
from freqtrade.data.history import load_pair_history
from freqtrade.enums import RunMode
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.state import RunMode
from freqtrade.exchange import Exchange, timeframe_to_seconds
logger = logging.getLogger(__name__)
NO_EXCHANGE_EXCEPTION = 'Exchange is not available to DataProvider.'
MAX_DATAFRAME_CANDLES = 1000
class DataProvider:
def __init__(self, config: dict, exchange: Exchange, pairlists=None) -> None:
def __init__(self, config: dict, exchange: Optional[Exchange], pairlists=None) -> None:
self._config = config
self._exchange = exchange
self._pairlists = pairlists
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
self.__slice_index: Optional[int] = None
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
def _set_dataframe_max_index(self, limit_index: int):
"""
Limit analyzed dataframe to max specified index.
:param limit_index: dataframe index.
"""
self.__slice_index = limit_index
def _set_cached_df(self, pair: str, timeframe: str, dataframe: DataFrame) -> None:
"""
@ -45,51 +58,28 @@ class DataProvider:
"""
self._pairlists = pairlists
def refresh(self,
pairlist: ListPairsWithTimeframes,
helping_pairs: ListPairsWithTimeframes = None) -> None:
"""
Refresh data, called with each cycle
"""
if helping_pairs:
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
else:
self._exchange.refresh_latest_ohlcv(pairlist)
@property
def available_pairs(self) -> ListPairsWithTimeframes:
"""
Return a list of tuples containing (pair, timeframe) for which data is currently cached.
Should be whitelist + open trades.
"""
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
Please use the `available_pairs` method to verify which pairs are currently cached.
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param copy: copy dataframe before returning if True.
Use False only for read-only operations (where the dataframe is not modified)
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
return self._exchange.klines((pair, timeframe or self._config['timeframe']),
copy=copy)
else:
return DataFrame()
def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame:
"""
Get stored historical candle (OHLCV) data
:param pair: pair to get the data for
:param timeframe: timeframe to get data for
"""
return load_pair_history(pair=pair,
saved_pair = (pair, str(timeframe))
if saved_pair not in self.__cached_pairs_backtesting:
timerange = TimeRange.parse_timerange(None if self._config.get(
'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:
"""
@ -111,47 +101,27 @@ class DataProvider:
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]:
"""
Retrieve the analyzed dataframe. Returns the full dataframe in trade mode (live / dry),
and the last 1000 candles (up to the time evaluated at this moment) in all other modes.
:param pair: pair to get the data for
:param timeframe: timeframe to get data for
:return: Tuple of (Analyzed Dataframe, lastrefreshed) for the requested pair / timeframe
combination.
Returns empty dataframe and Epoch 0 (1970-01-01) if no dataframe was cached.
"""
if (pair, timeframe) in self.__cached_pairs:
return self.__cached_pairs[(pair, timeframe)]
pair_key = (pair, timeframe)
if pair_key in self.__cached_pairs:
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
df, date = self.__cached_pairs[pair_key]
else:
df, date = self.__cached_pairs[pair_key]
if self.__slice_index is not None:
max_index = self.__slice_index
df = df.iloc[max(0, max_index - MAX_DATAFRAME_CANDLES):max_index]
return df, date
else:
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
def market(self, pair: str) -> Optional[Dict[str, Any]]:
"""
Return market data for the pair
:param pair: Pair to get the data for
:return: Market data dict from ccxt or None if market info is not available for the pair
"""
return self._exchange.markets.get(pair)
def ticker(self, pair: str):
"""
Return last ticker data from exchange
:param pair: Pair to get the data for
:return: Ticker dict from exchange or empty dict if ticker is not available for the pair
"""
try:
return self._exchange.fetch_ticker(pair)
except ExchangeError:
return {}
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
"""
Fetch latest l2 orderbook data
Warning: Does a network request - so use with common sense.
:param pair: pair to get the data for
:param maximum: Maximum number of orderbook entries to query
:return: dict including bids/asks with a total of `maximum` entries.
"""
return self._exchange.fetch_l2_order_book(pair, maximum)
@property
def runmode(self) -> RunMode:
"""
@ -170,6 +140,91 @@ class DataProvider:
"""
if self._pairlists:
return self._pairlists.whitelist
return self._pairlists.whitelist.copy()
else:
raise OperationalException("Dataprovider was not initialized with a pairlist provider.")
def clear_cache(self):
"""
Clear pair dataframe cache.
"""
self.__cached_pairs = {}
self.__cached_pairs_backtesting = {}
self.__slice_index = 0
# Exchange functions
def refresh(self,
pairlist: ListPairsWithTimeframes,
helping_pairs: ListPairsWithTimeframes = None) -> None:
"""
Refresh data, called with each cycle
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
if helping_pairs:
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
else:
self._exchange.refresh_latest_ohlcv(pairlist)
@property
def available_pairs(self) -> ListPairsWithTimeframes:
"""
Return a list of tuples containing (pair, timeframe) for which data is currently cached.
Should be whitelist + open trades.
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
Please use the `available_pairs` method to verify which pairs are currently cached.
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param copy: copy dataframe before returning if True.
Use False only for read-only operations (where the dataframe is not modified)
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
return self._exchange.klines((pair, timeframe or self._config['timeframe']),
copy=copy)
else:
return DataFrame()
def market(self, pair: str) -> Optional[Dict[str, Any]]:
"""
Return market data for the pair
:param pair: Pair to get the data for
:return: Market data dict from ccxt or None if market info is not available for the pair
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
return self._exchange.markets.get(pair)
def ticker(self, pair: str):
"""
Return last ticker data from exchange
:param pair: Pair to get the data for
:return: Ticker dict from exchange or empty dict if ticker is not available for the pair
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
try:
return self._exchange.fetch_ticker(pair)
except ExchangeError:
return {}
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
"""
Fetch latest l2 orderbook data
Warning: Does a network request - so use with common sense.
:param pair: pair to get the data for
:param maximum: Maximum number of orderbook entries to query
:return: dict including bids/asks with a total of `maximum` entries.
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
return self._exchange.fetch_l2_order_book(pair, maximum)

Some files were not shown because too many files have changed in this diff Show More