Merge branch 'develop' into pr/eatrisno/4308
This commit is contained in:
commit
d32508aa75
@ -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 []
|
||||
|
@ -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
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.py eol=lf
|
||||
*.sh eol=lf
|
||||
*.ps1 eol=crlf
|
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
6
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal 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
|
41
.github/workflows/ci.yml
vendored
41
.github/workflows/ci.yml
vendored
@ -75,7 +75,7 @@ jobs:
|
||||
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
|
||||
run: |
|
||||
# Allow failure for coveralls
|
||||
coveralls -v || true
|
||||
coveralls || true
|
||||
|
||||
- name: Backtesting
|
||||
run: |
|
||||
@ -102,7 +102,7 @@ jobs:
|
||||
mypy freqtrade scripts
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@v1.8.0
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
@ -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
|
||||
@ -194,7 +195,7 @@ jobs:
|
||||
mypy freqtrade scripts
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@v1.8.0
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
@ -257,7 +258,7 @@ jobs:
|
||||
mypy freqtrade scripts
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@v1.8.0
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
@ -288,7 +289,7 @@ jobs:
|
||||
mkdocs build
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@v1.8.0
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
@ -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: homoluctus/slatify@v1.8.0
|
||||
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
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*'
|
||||
@ -364,13 +374,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,16 +392,16 @@ 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
|
||||
uses: homoluctus/slatify@v1.8.0
|
||||
uses: lazy-actions/slatify@v3.0.0
|
||||
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
|
@ -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=
|
||||
|
@ -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-l9d9iqgl-9cVBIeBkCBa8j6upSmd_NA) 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), 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.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
30
Dockerfile
30
Dockerfile
@ -1,20 +1,29 @@
|
||||
FROM python:3.9.1-slim-buster as base
|
||||
FROM python:3.9.5-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 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
|
||||
|
||||
|
27
README.md
27
README.md
@ -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)
|
||||
@ -22,12 +22,21 @@ expect.
|
||||
We strongly recommend you to have coding and Python knowledge. Do not
|
||||
hesitate to read the source code and understand the mechanism of this bot.
|
||||
|
||||
## Exchange marketplaces supported
|
||||
## Supported Exchange marketplaces
|
||||
|
||||
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||
|
||||
- [X] [Bittrex](https://bittrex.com/)
|
||||
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists))
|
||||
- [X] [Kraken](https://kraken.com/)
|
||||
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||
- [X] [FTX](https://ftx.com)
|
||||
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||
|
||||
### Community tested
|
||||
|
||||
Exchanges confirmed working by the community:
|
||||
|
||||
- [X] [Bitvavo](https://bitvavo.com/)
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -114,7 +123,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.
|
||||
@ -136,16 +145,16 @@ The project is currently setup in two main branches:
|
||||
|
||||
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).
|
||||
Please check out our [discord server](https://discord.gg/p7nuUNVfP7).
|
||||
|
||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-l9d9iqgl-9cVBIeBkCBa8j6upSmd_NA).
|
||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
|
||||
|
||||
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
|
||||
|
||||
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.
|
||||
|
||||
@ -154,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.
|
||||
|
||||
@ -169,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-k9o2v5ut-jX8Mc4CwNM8CDc2Dyg96YA). 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) 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.
|
||||
|
||||
**Important:** Always create your PR against the `develop` branch, not `stable`.
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.20-cp37-cp37m-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.20-cp37-cp37m-win_amd64.whl
Normal file
Binary file not shown.
BIN
build_helpers/TA_Lib-0.4.20-cp38-cp38-win_amd64.whl
Normal file
BIN
build_helpers/TA_Lib-0.4.20-cp38-cp38-win_amd64.whl
Normal file
Binary file not shown.
@ -8,10 +8,13 @@ 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 \
|
||||
&& make -j$(nproc) \
|
||||
&& which sudo && sudo make install || make install \
|
||||
&& cd ..
|
||||
else
|
||||
echo "TA-lib already installed, skipping installation"
|
||||
fi
|
||||
# && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
|
||||
|
@ -1,16 +1,15 @@
|
||||
# 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.20-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.20-cp38-cp38-win_amd64.whl
|
||||
}
|
||||
|
||||
pip install -r requirements-dev.txt
|
||||
|
@ -1,21 +1,48 @@
|
||||
#!/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_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"
|
||||
# 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"
|
||||
# Pull last build to avoid rebuilding the whole image
|
||||
# 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 ${IMAGE_NAME}:$TAG
|
||||
@ -24,11 +51,6 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${TAG} -t fre
|
||||
|
||||
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
|
||||
|
||||
@ -37,21 +59,29 @@ if [ $? -ne 0 ]; then
|
||||
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
|
||||
|
||||
# Create multiarch image
|
||||
# Make sure that all images contained here are pushed to github first.
|
||||
# Otherwise installation might fail.
|
||||
|
||||
docker manifest create freqtradeorg/freqtrade:${TAG} ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:${TAG_PI}
|
||||
docker manifest push freqtradeorg/freqtrade:${TAG}
|
||||
|
||||
# Tag as latest for develop builds
|
||||
if [ "${TAG}" = "develop" ]; then
|
||||
docker manifest create freqtradeorg/freqtrade:latest ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:${TAG_PI}
|
||||
docker manifest push freqtradeorg/freqtrade:latest
|
||||
fi
|
||||
|
||||
|
||||
docker images
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "failed pushing repo"
|
||||
echo "failed building image"
|
||||
return 1
|
||||
fi
|
@ -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
|
@ -41,13 +41,13 @@
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
"ETC/BTC",
|
||||
"DASH/BTC",
|
||||
"ZEC/BTC",
|
||||
"RVN/BTC",
|
||||
"CRO/BTC",
|
||||
"XLM/BTC",
|
||||
"XRP/BTC",
|
||||
"TRX/BTC",
|
||||
"ADA/BTC",
|
||||
"XMR/BTC"
|
||||
"DOT/BTC"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
"DOGE/BTC"
|
||||
|
99
config_ftx.json.example
Normal file
99
config_ftx.json.example
Normal file
@ -0,0 +1,99 @@
|
||||
{
|
||||
"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": false,
|
||||
"order_book_top": 1,
|
||||
"check_depth_of_market": {
|
||||
"enabled": false,
|
||||
"bids_to_ask_delta": 1
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"exchange": {
|
||||
"name": "ftx",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 50
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
@ -23,7 +23,8 @@
|
||||
"stoploss": -0.10,
|
||||
"unfilledtimeout": {
|
||||
"buy": 10,
|
||||
"sell": 30
|
||||
"sell": 30,
|
||||
"unit": "minutes"
|
||||
},
|
||||
"bid_strategy": {
|
||||
"price_side": "bid",
|
||||
@ -49,6 +50,8 @@
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"emergencysell": "market",
|
||||
"forcesell": "market",
|
||||
"forcebuy": "market",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": false,
|
||||
"stoploss_on_exchange_interval": 60
|
||||
@ -111,7 +114,7 @@
|
||||
"password": "",
|
||||
"ccxt_config": {"enableRateLimit": true},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": false,
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 500,
|
||||
"aiohttp_trust_env": false
|
||||
},
|
||||
@ -161,7 +164,18 @@
|
||||
"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"
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ 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:
|
||||
|
58
docker/Dockerfile.aarch64
Normal file
58
docker/Dockerfile.aarch64
Normal file
@ -0,0 +1,58 @@
|
||||
FROM --platform=linux/arm64/v8 python:3.9.4-slim-buster as base
|
||||
|
||||
# Setup env
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONFAULTHANDLER 1
|
||||
ENV PATH=/home/ftuser/.local/bin:$PATH
|
||||
ENV FT_APP_ENV="docker"
|
||||
|
||||
# Prepare environment
|
||||
RUN mkdir /freqtrade \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
|
||||
&& apt-get clean \
|
||||
&& useradd -u 1000 -G sudo -U -m ftuser \
|
||||
&& chown ftuser:ftuser /freqtrade \
|
||||
# Allow sudoers
|
||||
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
|
||||
|
||||
WORKDIR /freqtrade
|
||||
|
||||
# Install dependencies
|
||||
FROM base as python-deps
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip
|
||||
|
||||
# Install TA-lib
|
||||
COPY build_helpers/* /tmp/
|
||||
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
|
||||
ENV LD_LIBRARY_PATH /usr/local/lib
|
||||
|
||||
# Install dependencies
|
||||
COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/
|
||||
USER ftuser
|
||||
RUN pip install --user --no-cache-dir numpy \
|
||||
&& pip install --user --no-cache-dir -r requirements-hyperopt.txt
|
||||
|
||||
# Copy dependencies to runtime-image
|
||||
FROM base as runtime-image
|
||||
COPY --from=python-deps /usr/local/lib /usr/local/lib
|
||||
ENV LD_LIBRARY_PATH /usr/local/lib
|
||||
|
||||
COPY --from=python-deps --chown=ftuser:ftuser /home/ftuser/.local /home/ftuser/.local
|
||||
|
||||
USER ftuser
|
||||
# Install and execute
|
||||
COPY --chown=ftuser:ftuser . /freqtrade/
|
||||
|
||||
RUN pip install -e . --user --no-cache-dir --no-build-isolation\
|
||||
&& mkdir /freqtrade/user_data/ \
|
||||
&& freqtrade install-ui
|
||||
|
||||
ENTRYPOINT ["freqtrade"]
|
||||
# Default to trade mode
|
||||
CMD [ "trade" ]
|
@ -1,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,11 +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 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
10
docker/Dockerfile.custom
Normal 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
|
@ -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 []
|
||||
|
@ -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 []
|
||||
|
@ -4,4 +4,4 @@ FROM freqtradeorg/freqtrade:${sourceimage}
|
||||
# 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
|
||||
|
@ -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
|
@ -4,34 +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 hyperop 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 ...
|
||||
```
|
||||
|
||||
## 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.
|
||||
@ -40,6 +12,11 @@ For the sample below, you then need to add the command line parameter `--hyperop
|
||||
A sample of this can be found below, which is identical to the Default Hyperopt loss implementation. A full sample can be found in [userdata/hyperopts](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_loss.py).
|
||||
|
||||
``` python
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.optimize.hyperopt import IHyperOptLoss
|
||||
|
||||
TARGET_TRADES = 600
|
||||
@ -54,6 +31,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
|
||||
@staticmethod
|
||||
def hyperopt_loss_function(results: DataFrame, trade_count: int,
|
||||
min_date: datetime, max_date: datetime,
|
||||
config: Dict, processed: Dict[str, DataFrame],
|
||||
*args, **kwargs) -> float:
|
||||
"""
|
||||
Objective function, returns smaller number for better results
|
||||
@ -81,6 +59,8 @@ Currently, the arguments are:
|
||||
* `trade_count`: Amount of trades (identical to `len(results)`)
|
||||
* `min_date`: Start date of the timerange used
|
||||
* `min_date`: End date of the timerange used
|
||||
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
|
||||
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
|
||||
|
||||
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.
|
||||
|
||||
@ -89,3 +69,315 @@ This function needs to return a floating point number (`float`). Smaller numbers
|
||||
|
||||
!!! Note
|
||||
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface later.
|
||||
|
||||
## 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(self):
|
||||
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
|
||||
```
|
||||
|
||||
## 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]`).
|
||||
|
||||
---
|
||||
|
||||
## Legacy Hyperopt
|
||||
|
||||
This Section explains the configuration of an explicit Hyperopt file (separate to the strategy).
|
||||
|
||||
!!! Warning "Deprecated / legacy mode"
|
||||
Since the 2021.4 release you no longer have to write a separate hyperopt class, but all strategies can be hyperopted.
|
||||
Please read the [main hyperopt page](hyperopt.md) for more details.
|
||||
|
||||
### Prepare hyperopt file
|
||||
|
||||
Configuring an explicit hyperopt file is similar to writing your own strategy, and many tasks will be similar.
|
||||
|
||||
!!! Tip "About this page"
|
||||
For this page, we will be using a fictional strategy called `AwesomeStrategy` - which will be optimized using the `AwesomeHyperopt` class.
|
||||
|
||||
#### Create a Custom Hyperopt File
|
||||
|
||||
The simplest way to get started is to use the following command, which will create a new hyperopt file from a template, which will be located under `user_data/hyperopts/AwesomeHyperopt.py`.
|
||||
|
||||
Let assume you want a hyperopt file `AwesomeHyperopt.py`:
|
||||
|
||||
``` bash
|
||||
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
|
||||
```
|
||||
|
||||
#### Legacy Hyperopt checklist
|
||||
|
||||
Checklist on all tasks / possibilities in hyperopt
|
||||
|
||||
Depending on the space you want to optimize, only some of the below are required:
|
||||
|
||||
* fill `buy_strategy_generator` - for buy signal optimization
|
||||
* fill `indicator_space` - for buy signal optimization
|
||||
* fill `sell_strategy_generator` - for sell signal optimization
|
||||
* fill `sell_indicator_space` - for sell signal optimization
|
||||
|
||||
!!! Note
|
||||
`populate_indicators` needs to create all indicators any of thee spaces may use, otherwise hyperopt will not work.
|
||||
|
||||
Optional in hyperopt - can also be loaded from a strategy (recommended):
|
||||
|
||||
* `populate_indicators` - fallback to create indicators
|
||||
* `populate_buy_trend` - fallback if not optimizing for buy space. should come from strategy
|
||||
* `populate_sell_trend` - fallback if not optimizing for sell space. should come from strategy
|
||||
|
||||
!!! Note
|
||||
You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods.
|
||||
Assuming the optional methods are not in your hyperopt file, please use `--strategy AweSomeStrategy` which contains these methods so hyperopt can use these methods instead.
|
||||
|
||||
Rarely you may also need to override:
|
||||
|
||||
* `roi_space` - for custom ROI optimization (if you need the ranges for the ROI parameters in the optimization hyperspace that differ from default)
|
||||
* `generate_roi_table` - for custom ROI optimization (if you need the ranges for the values in the ROI table that differ from default or the number of entries (steps) in the ROI table which differs from the default 4 steps)
|
||||
* `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default)
|
||||
* `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stop parameters in the optimization hyperspace that differ from default)
|
||||
|
||||
#### Defining a buy signal optimization
|
||||
|
||||
Let's say you are curious: should you use MACD crossings or lower Bollinger
|
||||
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
|
||||
help with those buy decisions. If you decide to use RSI or ADX, which values
|
||||
should I use for them? So let's use hyperparameter optimization to solve this
|
||||
mystery.
|
||||
|
||||
We will start by defining a search space:
|
||||
|
||||
```python
|
||||
def indicator_space() -> List[Dimension]:
|
||||
"""
|
||||
Define your Hyperopt space for searching strategy parameters
|
||||
"""
|
||||
return [
|
||||
Integer(20, 40, name='adx-value'),
|
||||
Integer(20, 40, name='rsi-value'),
|
||||
Categorical([True, False], name='adx-enabled'),
|
||||
Categorical([True, False], name='rsi-enabled'),
|
||||
Categorical(['bb_lower', 'macd_cross_signal'], name='trigger')
|
||||
]
|
||||
```
|
||||
|
||||
Above definition says: I have five parameters I want you to randomly combine
|
||||
to find the best combination. Two of them are integer values (`adx-value` and `rsi-value`) and I want you test in the range of values 20 to 40.
|
||||
Then we have three category variables. First two are either `True` or `False`.
|
||||
We use these to either enable or disable the ADX and RSI guards.
|
||||
The last one we call `trigger` and use it to decide which buy trigger we want to use.
|
||||
|
||||
So let's write the buy strategy generator using these values:
|
||||
|
||||
```python
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
# GUARDS AND TRENDS
|
||||
if 'adx-enabled' in params and params['adx-enabled']:
|
||||
conditions.append(dataframe['adx'] > params['adx-value'])
|
||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
|
||||
# TRIGGERS
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
|
||||
# Check that volume is not 0
|
||||
conditions.append(dataframe['volume'] > 0)
|
||||
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'buy'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_buy_trend
|
||||
```
|
||||
|
||||
Hyperopt will now call `populate_buy_trend()` many times (`epochs`) with different value combinations.
|
||||
It will use the given historical data and make buys based on the buy signals generated with the above function.
|
||||
Based on the results, hyperopt will tell you which parameter combination produced the best results (based on the configured [loss function](#loss-functions)).
|
||||
|
||||
!!! Note
|
||||
The above setup expects to find ADX, RSI and Bollinger Bands in the populated indicators.
|
||||
When you want to test an indicator that isn't used by the bot currently, remember to
|
||||
add it to the `populate_indicators()` method in your strategy or hyperopt file.
|
||||
|
||||
#### Sell optimization
|
||||
|
||||
Similar to the buy-signal above, sell-signals can also be optimized.
|
||||
Place the corresponding settings into the following methods
|
||||
|
||||
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
|
||||
* Within `sell_strategy_generator()` - populate the nested method `populate_sell_trend()` to apply the parameters.
|
||||
|
||||
The configuration and rules are the same than for buy signals.
|
||||
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
|
||||
|
||||
### Execute Hyperopt
|
||||
|
||||
Once you have updated your hyperopt configuration you can run it.
|
||||
Because hyperopt tries a lot of combinations to find the best parameters it will take time to get a good result. More time usually results in better results.
|
||||
|
||||
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
|
||||
|
||||
```bash
|
||||
freqtrade hyperopt --config config.json --hyperopt <hyperoptname> --hyperopt-loss <hyperoptlossname> --strategy <strategyname> -e 500 --spaces all
|
||||
```
|
||||
|
||||
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
||||
|
||||
The `-e` option will set how many evaluations hyperopt will do. Since hyperopt uses Bayesian search, running too many epochs at once may not produce greater results. Experience has shown that best results are usually not improving much after 500-1000 epochs.
|
||||
Doing multiple runs (executions) with a few 1000 epochs and different random state will most likely produce different results.
|
||||
|
||||
The `--spaces all` option determines that all possible parameters should be optimized. Possibilities are listed below.
|
||||
|
||||
!!! Note
|
||||
Hyperopt will store hyperopt results with the timestamp of the hyperopt start time.
|
||||
Reading commands (`hyperopt-list`, `hyperopt-show`) can use `--hyperopt-filename <filename>` to read and display older hyperopt results.
|
||||
You can find a list of filenames with `ls -l user_data/hyperopt_results/`.
|
||||
|
||||
#### Running Hyperopt using methods from a strategy
|
||||
|
||||
Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided.
|
||||
|
||||
```bash
|
||||
freqtrade hyperopt --hyperopt AwesomeHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy AwesomeStrategy
|
||||
```
|
||||
|
||||
### Understand the Hyperopt Result
|
||||
|
||||
Once Hyperopt is completed you can use the result to create a new strategy.
|
||||
Given the following result from hyperopt:
|
||||
|
||||
```
|
||||
Best result:
|
||||
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
|
||||
Buy hyperspace params:
|
||||
{ 'adx-value': 44,
|
||||
'rsi-value': 29,
|
||||
'adx-enabled': False,
|
||||
'rsi-enabled': True,
|
||||
'trigger': 'bb_lower'}
|
||||
```
|
||||
|
||||
You should understand this result like:
|
||||
|
||||
* The buy trigger that worked best was `bb_lower`.
|
||||
* You should not use ADX because `adx-enabled: False`)
|
||||
* You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
|
||||
|
||||
You have to look inside your strategy file into `buy_strategy_generator()`
|
||||
method, what those values match to.
|
||||
|
||||
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
|
||||
|
||||
```python
|
||||
(dataframe['rsi'] < 29.0)
|
||||
```
|
||||
|
||||
Translating your whole hyperopt result as the new buy-signal would then look like:
|
||||
|
||||
```python
|
||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < 29.0) & # rsi-value
|
||||
dataframe['close'] < dataframe['bb_lowerband'] # trigger
|
||||
),
|
||||
'buy'] = 1
|
||||
return dataframe
|
||||
```
|
||||
|
||||
### Validate backtesting results
|
||||
|
||||
Once the optimized parameters and conditions have been implemented into your strategy, you should backtest the strategy to make sure everything is working as expected.
|
||||
|
||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||
|
||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||
|
||||
### Sharing methods with your strategy
|
||||
|
||||
Hyperopt classes provide access to the Strategy via the `strategy` class attribute.
|
||||
This can be a great way to reduce code duplication if used correctly, but will also complicate usage for inexperienced users.
|
||||
|
||||
``` python
|
||||
from pandas import DataFrame
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
|
||||
buy_params = {
|
||||
'rsi-value': 30,
|
||||
'adx-value': 35,
|
||||
}
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return self.buy_strategy_generator(self.buy_params, dataframe, metadata)
|
||||
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
qtpylib.crossed_above(dataframe['rsi'], params['rsi-value']) &
|
||||
dataframe['adx'] > params['adx-value']) &
|
||||
dataframe['volume'] > 0
|
||||
)
|
||||
, 'buy'] = 1
|
||||
return dataframe
|
||||
|
||||
class MyAwesomeHyperOpt(IHyperOpt):
|
||||
...
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Call strategy's buy strategy generator
|
||||
return self.StrategyClass.buy_strategy_generator(params, dataframe, metadata)
|
||||
|
||||
return populate_buy_trend
|
||||
```
|
||||
|
3
docs/assets/ccxt-logo.svg
Normal file
3
docs/assets/ccxt-logo.svg
Normal 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 |
44
docs/assets/freqtrade_poweredby.svg
Normal file
44
docs/assets/freqtrade_poweredby.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
BIN
docs/assets/telegram_forcebuy.png
Normal file
BIN
docs/assets/telegram_forcebuy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -15,15 +15,16 @@ 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]
|
||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||
[--export EXPORT] [--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}
|
||||
@ -37,6 +38,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).
|
||||
@ -48,6 +52,9 @@ optional arguments:
|
||||
Enable protections for backtesting.Will slow
|
||||
backtesting down by a considerable amount, but will
|
||||
include configured protections
|
||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||
Starting balance, used for backtesting / hyperopt and
|
||||
dry-runs.
|
||||
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
||||
Provide a space-separated list of strategies to
|
||||
backtest. Please note that ticker-interval needs to be
|
||||
@ -91,8 +98,7 @@ Strategy arguments:
|
||||
## Test your strategy with Backtesting
|
||||
|
||||
Now you have good Buy and Sell strategies and some historic data, you want to test it against
|
||||
real data. This is what we call
|
||||
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||
|
||||
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default.
|
||||
If no data is available for the exchange / pair / timeframe combination, backtesting will ask you to download them first using `freqtrade download-data`.
|
||||
@ -100,6 +106,8 @@ For details on downloading, please refer to the [Data Downloading](data-download
|
||||
|
||||
The result of backtesting will confirm if your bot has better odds of making a profit than a loss.
|
||||
|
||||
All profit calculations include fees, and freqtrade will use the exchange's default fees for the calculation.
|
||||
|
||||
!!! 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.
|
||||
@ -107,38 +115,56 @@ The result of backtesting will confirm if your bot has better odds of making a p
|
||||
|
||||
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist.
|
||||
|
||||
### Run a backtesting against the currencies listed in your config file
|
||||
### Starting balance
|
||||
|
||||
#### With 5 min candle (OHLCV) data (per default)
|
||||
Backtesting will require a starting balance, which can be provided as `--dry-run-wallet <balance>` or `--starting-balance <balance>` command line argument, or via `dry_run_wallet` configuration setting.
|
||||
This amount must be higher than `stake_amount`, otherwise the bot will not be able to simulate any trade.
|
||||
|
||||
### Dynamic stake amount
|
||||
|
||||
Backtesting supports [dynamic stake amount](configuration.md#dynamic-stake-amount) by configuring `stake_amount` as `"unlimited"`, which will split the starting balance into `max_open_trades` pieces.
|
||||
Profits from early trades will result in subsequent higher stake amounts, resulting in compounding of profits over the backtesting period.
|
||||
|
||||
### Example backtesting commands
|
||||
|
||||
With 5 min candle (OHLCV) data (per default)
|
||||
|
||||
```bash
|
||||
freqtrade backtesting
|
||||
freqtrade backtesting --strategy AwesomeStrategy
|
||||
```
|
||||
|
||||
#### With 1 min candle (OHLCV) data
|
||||
Where `--strategy AwesomeStrategy` / `-s AwesomeStrategy` refers to the class name of the strategy, which is within a python file in the `user_data/strategies` directory.
|
||||
|
||||
---
|
||||
|
||||
With 1 min candle (OHLCV) data
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --timeframe 1m
|
||||
freqtrade backtesting --strategy AwesomeStrategy --timeframe 1m
|
||||
```
|
||||
|
||||
#### Using a different on-disk historical candle (OHLCV) data source
|
||||
---
|
||||
|
||||
Providing a custom starting balance of 1000 (in stake currency)
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --strategy AwesomeStrategy --dry-run-wallet 1000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Using a different on-disk historical candle (OHLCV) data source
|
||||
|
||||
Assume you downloaded the history data from the Bittrex exchange and kept it in the `user_data/data/bittrex-20180101` directory.
|
||||
You can then use this data for backtesting as follows:
|
||||
|
||||
```bash
|
||||
freqtrade --datadir user_data/data/bittrex-20180101 backtesting
|
||||
freqtrade backtesting --strategy AwesomeStrategy --datadir user_data/data/bittrex-20180101
|
||||
```
|
||||
|
||||
#### With a (custom) strategy file
|
||||
---
|
||||
|
||||
```bash
|
||||
freqtrade backtesting -s SampleStrategy
|
||||
```
|
||||
|
||||
Where `-s SampleStrategy` refers to the class name within the strategy file `sample_strategy.py` found in the `freqtrade/user_data/strategies` directory.
|
||||
|
||||
#### Comparing multiple Strategies
|
||||
Comparing multiple Strategies
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --timeframe 5m
|
||||
@ -146,23 +172,29 @@ freqtrade backtesting --strategy-list SampleStrategy1 AwesomeStrategy --timefram
|
||||
|
||||
Where `SampleStrategy1` and `AwesomeStrategy` refer to class names of strategies.
|
||||
|
||||
#### Exporting trades to file
|
||||
---
|
||||
|
||||
Exporting trades to file
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --export trades --config config.json --strategy SampleStrategy
|
||||
freqtrade backtesting --strategy backtesting --export trades --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.
|
||||
|
||||
#### Exporting trades to file specifying a custom filename
|
||||
---
|
||||
|
||||
Exporting trades to file specifying a custom filename
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --export trades --export-filename=backtest_samplestrategy.json
|
||||
freqtrade backtesting --strategy backtesting --export trades --export-filename=backtest_samplestrategy.json
|
||||
```
|
||||
|
||||
Please also read about the [strategy startup period](strategy-customization.md#strategy-startup-period).
|
||||
|
||||
#### Supplying custom fee value
|
||||
---
|
||||
|
||||
Supplying custom fee value
|
||||
|
||||
Sometimes your account has certain fee rebates (fee reductions starting with a certain account size or monthly volume), which are not visible to ccxt.
|
||||
To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting.
|
||||
@ -177,26 +209,26 @@ freqtrade backtesting --fee 0.001
|
||||
!!! Note
|
||||
Only supply this option (or the corresponding configuration parameter) if you want to experiment with different fee values. By default, Backtesting fetches the default fee from the exchange pair/market info.
|
||||
|
||||
#### Running backtest with smaller testset by using timerange
|
||||
---
|
||||
|
||||
Use the `--timerange` argument to change how much of the testset you want to use.
|
||||
Running backtest with smaller test-set by using timerange
|
||||
|
||||
Use the `--timerange` argument to change how much of the test-set you want to use.
|
||||
|
||||
For example, running backtesting with the `--timerange=20190501-` option will use all available data starting with May 1st, 2019 from your inputdata.
|
||||
For example, running backtesting with the `--timerange=20190501-` option will use all available data starting with May 1st, 2019 from your input data.
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --timerange=20190501-
|
||||
```
|
||||
|
||||
You can also specify particular dates or a range span indexed by start and stop.
|
||||
You can also specify particular date ranges.
|
||||
|
||||
The full timerange specification:
|
||||
|
||||
- Use tickframes till 2018/01/31: `--timerange=-20180131`
|
||||
- Use tickframes since 2018/01/31: `--timerange=20180131-`
|
||||
- Use tickframes since 2018/01/31 till 2018/03/01 : `--timerange=20180131-20180301`
|
||||
- Use tickframes between POSIX timestamps 1527595200 1527618600:
|
||||
`--timerange=1527595200-1527618600`
|
||||
- Use data until 2018/01/31: `--timerange=-20180131`
|
||||
- Use data since 2018/01/31: `--timerange=20180131-`
|
||||
- Use data since 2018/01/31 till 2018/03/01 : `--timerange=20180131-20180301`
|
||||
- Use data between POSIX / epoch timestamps 1527595200 1527618600: `--timerange=1527595200-1527618600`
|
||||
|
||||
## Understand the backtesting result
|
||||
|
||||
@ -205,29 +237,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 |
|
||||
@ -235,11 +267,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 |
|
||||
|-----------------------+---------------------|
|
||||
@ -248,19 +280,32 @@ A backtesting result will look like that:
|
||||
| Max open trades | 3 |
|
||||
| | |
|
||||
| Total trades | 429 |
|
||||
| Total Profit % | 152.41% |
|
||||
| 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 |
|
||||
| | |
|
||||
| Best Pair | LSK/BTC 26.26% |
|
||||
| Worst Pair | ZEC/BTC -10.18% |
|
||||
| Best Trade | LSK/BTC 4.25% |
|
||||
| Worst Trade | ZEC/BTC -10.25% |
|
||||
| Best day | 25.27% |
|
||||
| Worst day | -30.67% |
|
||||
| Best day | 0.00076 BTC |
|
||||
| Worst day | -0.00036 BTC |
|
||||
| Days win/draw/lose | 12 / 82 / 25 |
|
||||
| Avg. Duration Winners | 4:23:00 |
|
||||
| Avg. Duration Loser | 6:55:00 |
|
||||
| Zero Duration Trades | 4.6% (20) |
|
||||
| Rejected Buy signals | 3089 |
|
||||
| | |
|
||||
| Max Drawdown | 50.63% |
|
||||
| Min balance | 0.00945123 BTC |
|
||||
| Max balance | 0.01846651 BTC |
|
||||
| Drawdown | 50.63% |
|
||||
| Drawdown | 0.0015 BTC |
|
||||
| Drawdown high | 0.0013 BTC |
|
||||
| Drawdown low | -0.0002 BTC |
|
||||
| Drawdown Start | 2019-02-15 14:10:00 |
|
||||
| Drawdown End | 2019-04-11 18:15:00 |
|
||||
| Market change | -5.88% |
|
||||
@ -275,15 +320,15 @@ 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
|
||||
earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
|
||||
|
||||
The column `avg profit %` shows the average profit for all trades made while the column `cum profit %` sums up all the profits/losses.
|
||||
The column `tot profit %` shows instead the total profit % in relation to allocated capital (`max_open_trades * stake_amount`).
|
||||
In the above results we have `max_open_trades=2` and `stake_amount=0.005` in config so `tot_profit %` will be `(76.20/100) * (0.005 * 2) =~ 0.00762792 BTC`.
|
||||
The column `Avg Profit %` shows the average profit for all trades made while the column `Cum Profit %` sums up all the profits/losses.
|
||||
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
||||
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
||||
|
||||
Your strategy performance is influenced by your buy strategy, your sell strategy, and also by the `minimal_roi` and `stop_loss` you have set.
|
||||
|
||||
@ -324,19 +369,32 @@ It contains some useful key metrics about performance of your strategy on backte
|
||||
| Max open trades | 3 |
|
||||
| | |
|
||||
| Total trades | 429 |
|
||||
| Total Profit % | 152.41% |
|
||||
| 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 |
|
||||
| | |
|
||||
| Best Pair | LSK/BTC 26.26% |
|
||||
| Worst Pair | ZEC/BTC -10.18% |
|
||||
| Best Trade | LSK/BTC 4.25% |
|
||||
| Worst Trade | ZEC/BTC -10.25% |
|
||||
| Best day | 25.27% |
|
||||
| Worst day | -30.67% |
|
||||
| Best day | 0.00076 BTC |
|
||||
| Worst day | -0.00036 BTC |
|
||||
| Days win/draw/lose | 12 / 82 / 25 |
|
||||
| Avg. Duration Winners | 4:23:00 |
|
||||
| Avg. Duration Loser | 6:55:00 |
|
||||
| Zero Duration Trades | 4.6% (20) |
|
||||
| Rejected Buy signals | 3089 |
|
||||
| | |
|
||||
| Max Drawdown | 50.63% |
|
||||
| Min balance | 0.00945123 BTC |
|
||||
| Max balance | 0.01846651 BTC |
|
||||
| Drawdown | 50.63% |
|
||||
| Drawdown | 0.0015 BTC |
|
||||
| Drawdown high | 0.0013 BTC |
|
||||
| Drawdown low | -0.0002 BTC |
|
||||
| Drawdown Start | 2019-02-15 14:10:00 |
|
||||
| Drawdown End | 2019-04-11 18:15:00 |
|
||||
| Market change | -5.88% |
|
||||
@ -347,13 +405,23 @@ 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 Profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table.
|
||||
- `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 %`.
|
||||
- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade
|
||||
- `Best Trade` / `Worst Trade`: Biggest single winning trade and biggest single losing trade.
|
||||
- `Best day` / `Worst day`: Best and worst day based on daily profit.
|
||||
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
|
||||
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
|
||||
- `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
|
||||
- `Zero Duration Trades`: A number of trades that completed within same candle as they opened and had `trailing_stop_loss` sell reason. A significant amount of such trades may indicate that strategy is exploiting trailing stoploss behavior in backtesting and produces unrealistic results.
|
||||
- `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached.
|
||||
- `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.
|
||||
|
||||
@ -362,6 +430,7 @@ It contains some useful key metrics about performance of your strategy on backte
|
||||
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
|
||||
@ -409,15 +478,14 @@ 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
|
||||
|
||||
Great, your strategy is profitable. What if the bot can give your the
|
||||
optimal parameters to use for your strategy?
|
||||
Great, your strategy is profitable. What if the bot can give your the optimal parameters to use for your strategy?
|
||||
Your next step is to learn [how to find optimal parameters with Hyperopt](hyperopt.md)
|
||||
|
@ -4,14 +4,14 @@ This page provides you some basic concepts on how Freqtrade works and operates.
|
||||
|
||||
## Freqtrade terminology
|
||||
|
||||
* Strategy: Your trading strategy, telling the bot what to do.
|
||||
* Trade: Open position.
|
||||
* Open Order: Order which is currently placed on the exchange, and is not yet complete.
|
||||
* Pair: Tradable pair, usually in the format of Quote/Base (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.
|
||||
* Market order: Guaranteed to fill, may move price depending on the order size.
|
||||
* **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).
|
||||
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
||||
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
||||
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
||||
* **Market order**: Guaranteed to fill, may move price depending on the order size.
|
||||
|
||||
## Fee handling
|
||||
|
||||
@ -53,6 +53,7 @@ This loop will be repeated again and again until the bot is stopped.
|
||||
* 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)
|
||||
* Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy)
|
||||
* Loops per candle simulating entry and exit points.
|
||||
* Generate backtest report output
|
||||
|
||||
|
@ -56,6 +56,7 @@ optional arguments:
|
||||
usage: freqtrade trade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||
[--db-url PATH] [--sd-notify] [--dry-run]
|
||||
[--dry-run-wallet DRY_RUN_WALLET]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@ -66,6 +67,9 @@ optional arguments:
|
||||
--sd-notify Notify systemd service manager.
|
||||
--dry-run Enforce dry-run for trading (removes Exchange secrets
|
||||
and simulates trades).
|
||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||
Starting balance, used for backtesting / hyperopt and
|
||||
dry-runs.
|
||||
|
||||
Common arguments:
|
||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||
|
@ -11,7 +11,16 @@ Per default, the bot loads the configuration from the `config.json` file, locate
|
||||
|
||||
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.
|
||||
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`).
|
||||
|
||||
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.
|
||||
@ -40,8 +49,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
| 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.
|
||||
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <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). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Positive float or `"unlimited"`.
|
||||
| `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`.
|
||||
| `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)
|
||||
@ -49,7 +58,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
|
||||
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
|
||||
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
||||
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||
| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||
@ -58,15 +67,18 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md#trailing-stop-loss-custom-positive-loss). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float
|
||||
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](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
|
||||
| `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
|
||||
| `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 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. 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
|
||||
@ -97,6 +109,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||
| `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
|
||||
| `telegram.balance_dust_level` | Dust-level (in stake currency) - currencies with a balance below this will not be shown by `/balance`. <br> **Datatype:** float
|
||||
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
||||
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||
@ -141,8 +154,6 @@ Values set in the configuration file always overwrite values set in the strategy
|
||||
* `process_only_new_candles`
|
||||
* `order_types`
|
||||
* `order_time_in_force`
|
||||
* `stake_currency`
|
||||
* `stake_amount`
|
||||
* `unfilledtimeout`
|
||||
* `disable_dataframe_checks`
|
||||
* `protections`
|
||||
@ -156,6 +167,23 @@ Values set in the configuration file always overwrite values set in the strategy
|
||||
|
||||
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.
|
||||
|
||||
#### Minimum trade stake
|
||||
|
||||
The minimum stake amount will depend by exchange and pair, and is usually listed in the exchange support pages.
|
||||
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.4$.
|
||||
|
||||
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 in 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.
|
||||
|
||||
#### Available 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.
|
||||
@ -218,11 +246,14 @@ To allow the bot to trade all the available `stake_currency` in your account (mi
|
||||
"tradable_balance_ratio": 0.99,
|
||||
```
|
||||
|
||||
!!! Note
|
||||
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).
|
||||
!!! 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.
|
||||
|
||||
!!! Note "When using Dry-Run Mode"
|
||||
When using `"stake_amount" : "unlimited",` in combination with Dry-Run, 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 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.
|
||||
|
||||
--8<-- "includes/pricing.md"
|
||||
|
||||
### Understand minimal_roi
|
||||
|
||||
@ -273,9 +304,12 @@ For example, if your strategy is using a 1h timeframe, and you only want to buy
|
||||
},
|
||||
```
|
||||
|
||||
!!! 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`) to order-types (`market`, `limit`, ...) as well as configures stoploss to be on the exchange and defines stoploss on exchange update interval in seconds.
|
||||
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.
|
||||
|
||||
This allows to buy using limit orders, sell using
|
||||
limit-orders, and create stoplosses using market orders. It also allows to set the
|
||||
@ -287,7 +321,7 @@ the buy order is fulfilled.
|
||||
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.
|
||||
|
||||
For information on (`emergencysell`,`stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
|
||||
For information on (`emergencysell`,`forcesell`, `forcebuy`, `stoploss_on_exchange`,`stoploss_on_exchange_interval`,`stoploss_on_exchange_limit_ratio`) please see stop loss documentation [stop loss on exchange](stoploss.md)
|
||||
|
||||
Syntax for Strategy:
|
||||
|
||||
@ -296,6 +330,8 @@ order_types = {
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"emergencysell": "market",
|
||||
"forcebuy": "market",
|
||||
"forcesell": "market",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": False,
|
||||
"stoploss_on_exchange_interval": 60,
|
||||
@ -310,6 +346,8 @@ Configuration:
|
||||
"buy": "limit",
|
||||
"sell": "limit",
|
||||
"emergencysell": "market",
|
||||
"forcebuy": "market",
|
||||
"forcesell": "market",
|
||||
"stoploss": "market",
|
||||
"stoploss_on_exchange": false,
|
||||
"stoploss_on_exchange_interval": 60
|
||||
@ -368,8 +406,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.
|
||||
This is an ongoing work. For now it is supported only for binance.
|
||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values.
|
||||
|
||||
### Exchange configuration
|
||||
|
||||
@ -410,26 +448,6 @@ This configuration enables binance, as well as rate limiting to avoid bans from
|
||||
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.
|
||||
|
||||
### What values can be used for fiat_display_currency?
|
||||
|
||||
The `fiat_display_currency` configuration parameter sets the base currency to use for the
|
||||
@ -449,8 +467,6 @@ The valid values are:
|
||||
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
|
||||
```
|
||||
|
||||
--8<-- "includes/pricing.md"
|
||||
|
||||
## Using Dry-run mode
|
||||
|
||||
We recommend starting the bot in the Dry-run mode to see how your bot will
|
||||
@ -515,16 +531,27 @@ API Keys are usually only required for live trading (trading for real money, bot
|
||||
**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 to use 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.
|
||||
|
@ -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).
|
||||
|
@ -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-l9d9iqgl-9cVBIeBkCBa8j6upSmd_NA) 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) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) where you can ask questions.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Using Freqtrade with Docker
|
||||
|
||||
This page explains how to run the bot with Docker. It is not meant to work out of the box. You'll still need to read through the documentation and understand how to properly configure it.
|
||||
|
||||
## Install Docker
|
||||
|
||||
Start by downloading and installing Docker CE for your platform:
|
||||
@ -8,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.
|
||||
@ -20,7 +22,7 @@ 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
|
||||
@ -46,6 +48,8 @@ Create a new directory and place the [docker-compose file](https://github.com/fr
|
||||
# Download the docker-compose file from the repository
|
||||
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
||||
|
||||
# Edit the compose file to use an image named `*_pi` (stable_pi or develop_pi)
|
||||
|
||||
# Pull the freqtrade image
|
||||
docker-compose pull
|
||||
|
||||
@ -63,6 +67,40 @@ Create a new directory and place the [docker-compose file](https://github.com/fr
|
||||
# image: freqtradeorg/freqtrade:develop_pi
|
||||
```
|
||||
|
||||
=== "ARM 64 Systenms (Mac M1, Raspberry Pi 4, Jetson Nano)"
|
||||
In case of a Mac M1, make sure that your docker installation is running in native mode
|
||||
Arm64 images are not yet provided via Docker Hub and need to be build locally first.
|
||||
Depending on the device, this may take a few minutes (Apple M1) or multiple hours (Raspberry Pi)
|
||||
|
||||
``` bash
|
||||
# Clone Freqtrade repository
|
||||
git clone https://github.com/freqtrade/freqtrade.git
|
||||
cd freqtrade
|
||||
# Optionally switch to the stable version
|
||||
git checkout stable
|
||||
|
||||
# Modify your docker-compose file to enable building and change the image name
|
||||
# (see the Note Box below for necessary changes)
|
||||
|
||||
# Build image
|
||||
docker-compose build
|
||||
|
||||
# Create user directory structure
|
||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
||||
|
||||
# Create configuration - Requires answering interactive questions
|
||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||
```
|
||||
|
||||
!!! Note "Change your docker Image"
|
||||
You have to change the docker image in the docker-compose file for your arm64 build to work properly.
|
||||
``` yml
|
||||
image: freqtradeorg/freqtrade:custom_arm64
|
||||
build:
|
||||
context: .
|
||||
dockerfile: "./docker/Dockerfile.aarch64"
|
||||
```
|
||||
|
||||
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
||||
The last 2 steps in the snippet create the directory with `user_data`, as well as (interactively) the default configuration based on your selections.
|
||||
|
||||
@ -75,7 +113,7 @@ The last 2 steps in the snippet create the directory with `user_data`, as well a
|
||||
|
||||
1. The configuration is now available as `user_data/config.json`
|
||||
2. Copy a custom strategy to the directory `user_data/strategies/`
|
||||
3. add the Strategy' class name to the `docker-compose.yml` file
|
||||
3. Add the Strategy' class name to the `docker-compose.yml` file
|
||||
|
||||
The `SampleStrategy` is run by default.
|
||||
|
||||
@ -90,6 +128,9 @@ Once this is done, you're ready to launch the bot in trading mode (Dry-run or Li
|
||||
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.
|
||||
|
||||
#### Monitoring the bot
|
||||
|
||||
You can check for running instances with `docker-compose ps`.
|
||||
@ -151,8 +192,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.
|
||||
|
||||
|
15
docs/edge.md
15
docs/edge.md
@ -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"
|
||||
|
@ -7,18 +7,17 @@ This page combines common gotchas and informations which are exchange-specific a
|
||||
!!! 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
|
||||
|
||||
@ -40,12 +39,23 @@ Due to the heavy rate-limiting applied by Kraken, the following configuration se
|
||||
},
|
||||
```
|
||||
|
||||
!!! Warning "Downloading data from kraken"
|
||||
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.
|
||||
|
||||
### Restricted markets
|
||||
|
||||
Bittrex split its exchange into US and International versions.
|
||||
@ -92,8 +102,22 @@ To use subaccounts with FTX, you need to edit the configuration and add the foll
|
||||
}
|
||||
```
|
||||
|
||||
!!! Note
|
||||
Older versions of freqtrade may require this key to be added to `"ccxt_async_config"` as well.
|
||||
## Kucoin
|
||||
|
||||
Kucoin requries a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||
|
||||
```json
|
||||
"exchange": {
|
||||
"name": "kucoin",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"password": "your_exchange_api_key_password",
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
@ -117,3 +141,23 @@ 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.
|
||||
|
21
docs/faq.md
21
docs/faq.md
@ -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).
|
||||
@ -38,12 +52,11 @@ you can't say much from few trades.
|
||||
|
||||
### I’d like to make changes to the config. Can I do that without having to kill the bot?
|
||||
|
||||
Yes. You can edit your config, use the `/stop` command in Telegram, followed by `/reload_config` and the bot will run with the new config.
|
||||
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
|
||||
|
||||
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).
|
||||
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).
|
||||
|
||||
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
|
||||
|
||||
@ -143,7 +156,7 @@ freqtrade hyperopt --hyperopt SampleHyperopt --hyperopt-loss SharpeHyperOptLossD
|
||||
|
||||
### 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-l9d9iqgl-9cVBIeBkCBa8j6upSmd_NA) - 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 [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) - or the Freqtrade [discord community](https://discord.gg/p7nuUNVfP7). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
|
||||
|
||||
* If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers:
|
||||
|
||||
|
529
docs/hyperopt.md
529
docs/hyperopt.md
@ -1,19 +1,22 @@
|
||||
# Hyperopt
|
||||
|
||||
This page explains how to tune your strategy by finding the optimal
|
||||
parameters, a process called hyperparameter optimization. The bot uses several
|
||||
algorithms included in the `scikit-optimize` package to accomplish this. The
|
||||
search will burn all your CPU cores, make your laptop sound like a fighter jet
|
||||
and still take a long time.
|
||||
parameters, a process called hyperparameter optimization. The bot uses algorithms included in the `scikit-optimize` package to accomplish this.
|
||||
The search will burn all your CPU cores, make your laptop sound like a fighter jet and still take a long time.
|
||||
|
||||
In general, the search for best parameters starts with a few random combinations (see [below](#reproducible-results) for more details) and then uses Bayesian search with a ML regressor algorithm (currently ExtraTreesRegressor) to quickly find a combination of parameters in the search hyperspace that minimizes the value of the [loss function](#loss-functions).
|
||||
|
||||
Hyperopt requires historic data to be available, just as backtesting does.
|
||||
Hyperopt requires historic data to be available, just as backtesting does (hyperopt runs backtesting many times with different parameters).
|
||||
To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation.
|
||||
|
||||
!!! Bug
|
||||
Hyperopt can crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133)
|
||||
|
||||
!!! Note
|
||||
Since 2021.4 release you no longer have to write a separate hyperopt class, but can configure the parameters directly in the strategy.
|
||||
The legacy method is still supported, but it is no longer the recommended way of setting up hyperopt.
|
||||
The legacy documentation is available at [Legacy Hyperopt](advanced-hyperopt.md#legacy-hyperopt).
|
||||
|
||||
## Install hyperopt dependencies
|
||||
|
||||
Since Hyperopt dependencies are not needed to run the bot itself, are heavy, can not be easily built on some platforms (like Raspberry PI), they are not installed by default. Before you run Hyperopt, you need to install the corresponding dependencies, as described in this section below.
|
||||
@ -34,7 +37,6 @@ pip install -r requirements-hyperopt.txt
|
||||
|
||||
## Hyperopt command reference
|
||||
|
||||
|
||||
```
|
||||
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||
@ -42,8 +44,10 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||
[--data-format-ohlcv {json,jsongz,hdf5}]
|
||||
[--max-open-trades INT]
|
||||
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
||||
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
|
||||
[--dmmp] [--enable-protections] [-e INT]
|
||||
[-p PAIRS [PAIRS ...]] [--hyperopt NAME]
|
||||
[--hyperopt-path PATH] [--eps] [--dmmp]
|
||||
[--enable-protections]
|
||||
[--dry-run-wallet DRY_RUN_WALLET] [-e INT]
|
||||
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]]
|
||||
[--print-all] [--no-color] [--print-json] [-j JOBS]
|
||||
[--random-state INT] [--min-trades INT]
|
||||
@ -52,8 +56,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d 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}
|
||||
@ -67,6 +70,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.
|
||||
--hyperopt NAME Specify hyperopt class name which will be used by the
|
||||
bot.
|
||||
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
||||
@ -82,6 +88,9 @@ optional arguments:
|
||||
Enable protections for backtesting.Will slow
|
||||
backtesting down by a considerable amount, but will
|
||||
include configured protections
|
||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||
Starting balance, used for backtesting / hyperopt and
|
||||
dry-runs.
|
||||
-e INT, --epochs INT Specify number of epochs (default: 100).
|
||||
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
|
||||
Specify which parameters to hyperopt. Space-separated
|
||||
@ -100,7 +109,8 @@ optional arguments:
|
||||
reproducible hyperopt results.
|
||||
--min-trades INT Set minimal desired number of trades for evaluations
|
||||
in the hyperopt optimization path (default: 1).
|
||||
--hyperopt-loss NAME Specify the class name of the hyperopt loss function
|
||||
--hyperopt-loss NAME, --hyperoptloss NAME
|
||||
Specify the class name of the hyperopt loss function
|
||||
class (IHyperOptLoss). Different functions can
|
||||
generate completely different results, since the
|
||||
target for optimization is different. Built-in
|
||||
@ -133,47 +143,19 @@ Strategy arguments:
|
||||
|
||||
```
|
||||
|
||||
## Prepare Hyperopting
|
||||
|
||||
Before we start digging into Hyperopt, we recommend you to take a look at
|
||||
the sample hyperopt file located in [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt.py).
|
||||
|
||||
Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar.
|
||||
|
||||
!!! Tip "About this page"
|
||||
For this page, we will be using a fictional strategy called `AwesomeStrategy` - which will be optimized using the `AwesomeHyperopt` class.
|
||||
|
||||
The simplest way to get started is to use the following, command, which will create a new hyperopt file from a template, which will be located under `user_data/hyperopts/AwesomeHyperopt.py`.
|
||||
|
||||
``` bash
|
||||
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
|
||||
```
|
||||
|
||||
### Hyperopt checklist
|
||||
|
||||
Checklist on all tasks / possibilities in hyperopt
|
||||
|
||||
Depending on the space you want to optimize, only some of the below are required:
|
||||
|
||||
* fill `buy_strategy_generator` - for buy signal optimization
|
||||
* fill `indicator_space` - for buy signal optimization
|
||||
* fill `sell_strategy_generator` - for sell signal optimization
|
||||
* fill `sell_indicator_space` - for sell signal optimization
|
||||
* define parameters with `space='buy'` - for buy signal optimization
|
||||
* define parameters with `space='sell'` - for sell signal optimization
|
||||
|
||||
!!! Note
|
||||
`populate_indicators` needs to create all indicators any of thee spaces may use, otherwise hyperopt will not work.
|
||||
`populate_indicators` needs to create all indicators any of the spaces may use, otherwise hyperopt will not work.
|
||||
|
||||
Optional in hyperopt - can also be loaded from a strategy (recommended):
|
||||
|
||||
* `populate_indicators` - fallback to create indicators
|
||||
* `populate_buy_trend` - fallback if not optimizing for buy space. should come from strategy
|
||||
* `populate_sell_trend` - fallback if not optimizing for sell space. should come from strategy
|
||||
|
||||
!!! Note
|
||||
You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods.
|
||||
Assuming the optional methods are not in your hyperopt file, please use `--strategy AweSomeStrategy` which contains these methods so hyperopt can use these methods instead.
|
||||
|
||||
Rarely you may also need to override:
|
||||
Rarely you may also need to create a [nested class](advanced-hyperopt.md#overriding-pre-defined-spaces) named `HyperOpt` and implement
|
||||
|
||||
* `roi_space` - for custom ROI optimization (if you need the ranges for the ROI parameters in the optimization hyperspace that differ from default)
|
||||
* `generate_roi_table` - for custom ROI optimization (if you need the ranges for the values in the ROI table that differ from default or the number of entries (steps) in the ROI table which differs from the default 4 steps)
|
||||
@ -181,31 +163,30 @@ Rarely you may also need to override:
|
||||
* `trailing_space` - for custom trailing stop optimization (if you need the ranges for the trailing stop parameters in the optimization hyperspace that differ from default)
|
||||
|
||||
!!! Tip "Quickly optimize ROI, stoploss and trailing stoploss"
|
||||
You can quickly optimize the spaces `roi`, `stoploss` and `trailing` without changing anything (i.e. without creation of a "complete" Hyperopt class with dimensions, parameters, triggers and guards, as described in this document) from the default hyperopt template by relying on your strategy to do most of the calculations.
|
||||
You can quickly optimize the spaces `roi`, `stoploss` and `trailing` without changing anything in your strategy.
|
||||
|
||||
```python
|
||||
``` bash
|
||||
# Have a working strategy at hand.
|
||||
freqtrade new-hyperopt --hyperopt EmptyHyperopt
|
||||
|
||||
freqtrade hyperopt --hyperopt EmptyHyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss trailing --strategy MyWorkingStrategy --config config.json -e 100
|
||||
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss trailing --strategy MyWorkingStrategy --config config.json -e 100
|
||||
```
|
||||
|
||||
### Create a Custom Hyperopt File
|
||||
### Hyperopt execution logic
|
||||
|
||||
Let assume you want a hyperopt file `AwesomeHyperopt.py`:
|
||||
Hyperopt will first load your data into memory and will then run `populate_indicators()` once per Pair to generate all indicators.
|
||||
|
||||
``` bash
|
||||
freqtrade new-hyperopt --hyperopt AwesomeHyperopt
|
||||
```
|
||||
Hyperopt will then spawn into different processes (number of processors, or `-j <n>`), and run backtesting over and over again, changing the parameters that are part of the `--spaces` defined.
|
||||
|
||||
This command will create a new hyperopt file from a template, allowing you to get started quickly.
|
||||
For every new set of parameters, freqtrade will run first `populate_buy_trend()` followed by `populate_sell_trend()`, and then run the regular backtesting process to simulate trades.
|
||||
|
||||
After backtesting, the results are passed into the [loss function](#loss-functions), which will evaluate if this result was better or worse than previous results.
|
||||
Based on the loss function result, hyperopt will determine the next set of parameters to try in the next round of backtesting.
|
||||
|
||||
### Configure your Guards and Triggers
|
||||
|
||||
There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
|
||||
There are two places you need to change in your strategy file to add a new buy hyperopt for testing:
|
||||
|
||||
* Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
|
||||
* Within `buy_strategy_generator()` - populate the nested `populate_buy_trend()` to apply the parameters.
|
||||
* Define the parameters at the class level hyperopt shall be optimizing.
|
||||
* Within `populate_buy_trend()` - use defined parameter values instead of raw constants.
|
||||
|
||||
There you have two different types of indicators: 1. `guards` and 2. `triggers`.
|
||||
|
||||
@ -217,81 +198,90 @@ There you have two different types of indicators: 1. `guards` and 2. `triggers`.
|
||||
However, this guide will make this distinction to make it clear that signals should not be "sticking".
|
||||
Sticking signals are signals that are active for multiple candles. This can lead into buying a signal late (right before the signal disappears - which means that the chance of success is a lot lower than right at the beginning).
|
||||
|
||||
Hyper-optimization will, for each epoch round, pick one trigger and possibly
|
||||
multiple guards. The constructed strategy will be something like "*buy exactly when close price touches lower Bollinger band, BUT only if
|
||||
ADX > 10*".
|
||||
|
||||
If you have updated the buy strategy, i.e. changed the contents of `populate_buy_trend()` method, you have to update the `guards` and `triggers` your hyperopt must use correspondingly.
|
||||
Hyper-optimization will, for each epoch round, pick one trigger and possibly multiple guards.
|
||||
|
||||
#### Sell optimization
|
||||
|
||||
Similar to the buy-signal above, sell-signals can also be optimized.
|
||||
Place the corresponding settings into the following methods
|
||||
|
||||
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
|
||||
* Within `sell_strategy_generator()` - populate the nested method `populate_sell_trend()` to apply the parameters.
|
||||
* Define the parameters at the class level hyperopt shall be optimizing, either naming them `sell_*`, or by explicitly defining `space='sell'`.
|
||||
* Within `populate_sell_trend()` - use defined parameter values instead of raw constants.
|
||||
|
||||
The configuration and rules are the same than for buy signals.
|
||||
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
|
||||
|
||||
#### Using timeframe as a part of the Strategy
|
||||
|
||||
The Strategy class exposes the timeframe value as the `self.timeframe` attribute.
|
||||
The same value is available as class-attribute `HyperoptName.timeframe`.
|
||||
In the case of the linked sample-value this would be `AwesomeHyperopt.timeframe`.
|
||||
|
||||
## Solving a Mystery
|
||||
|
||||
Let's say you are curious: should you use MACD crossings or lower Bollinger
|
||||
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
|
||||
help with those buy decisions. If you decide to use RSI or ADX, which values
|
||||
should I use for them? So let's use hyperparameter optimization to solve this
|
||||
mystery.
|
||||
Let's say you are curious: should you use MACD crossings or lower Bollinger Bands to trigger your buys.
|
||||
And you also wonder should you use RSI or ADX to help with those buy decisions.
|
||||
If you decide to use RSI or ADX, which values should I use for them?
|
||||
|
||||
We will start by defining a search space:
|
||||
So let's use hyperparameter optimization to solve this mystery.
|
||||
|
||||
```python
|
||||
def indicator_space() -> List[Dimension]:
|
||||
### Defining indicators to be used
|
||||
|
||||
We start by calculating the indicators our strategy is going to use.
|
||||
|
||||
``` python
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""
|
||||
Define your Hyperopt space for searching strategy parameters
|
||||
Generate all indicators used by the strategy
|
||||
"""
|
||||
return [
|
||||
Integer(20, 40, name='adx-value'),
|
||||
Integer(20, 40, name='rsi-value'),
|
||||
Categorical([True, False], name='adx-enabled'),
|
||||
Categorical([True, False], name='rsi-enabled'),
|
||||
Categorical(['bb_lower', 'macd_cross_signal'], name='trigger')
|
||||
]
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
dataframe['macdhist'] = macd['macdhist']
|
||||
|
||||
bollinger = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
|
||||
dataframe['bb_lowerband'] = bollinger['lowerband']
|
||||
dataframe['bb_middleband'] = bollinger['middleband']
|
||||
dataframe['bb_upperband'] = bollinger['upperband']
|
||||
return dataframe
|
||||
```
|
||||
|
||||
Above definition says: I have five parameters I want you to randomly combine
|
||||
to find the best combination. Two of them are integer values (`adx-value`
|
||||
and `rsi-value`) and I want you test in the range of values 20 to 40.
|
||||
### Hyperoptable parameters
|
||||
|
||||
We continue to define hyperoptable parameters:
|
||||
|
||||
```python
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
buy_adx = DecimalParameter(20, 40, decimals=1, default=30.1, space="buy")
|
||||
buy_rsi = IntParameter(20, 40, default=30, space="buy")
|
||||
buy_adx_enabled = CategoricalParameter([True, False], default=True, space="buy")
|
||||
buy_rsi_enabled = CategoricalParameter([True, False], default=False, space="buy")
|
||||
buy_trigger = CategoricalParameter(["bb_lower", "macd_cross_signal"], default="bb_lower", space="buy")
|
||||
```
|
||||
|
||||
The above definition says: I have five parameters I want to randomly combine to find the best combination.
|
||||
`buy_rsi` is an integer parameter, which will be tested between 20 and 40. This space has a size of 20.
|
||||
`buy_adx` is a decimal parameter, which will be evaluated between 20 and 40 with 1 decimal place (so values are 20.1, 20.2, ...). This space has a size of 200.
|
||||
Then we have three category variables. First two are either `True` or `False`.
|
||||
We use these to either enable or disable the ADX and RSI guards. The last
|
||||
one we call `trigger` and use it to decide which buy trigger we want to use.
|
||||
We use these to either enable or disable the ADX and RSI guards.
|
||||
The last one we call `trigger` and use it to decide which buy trigger we want to use.
|
||||
|
||||
!!! Note "Parameter space assignment"
|
||||
Parameters must either be assigned to a variable named `buy_*` or `sell_*` - or contain `space='buy'` | `space='sell'` to be assigned to a space correctly.
|
||||
If no parameter is available for a space, you'll receive the error that no space was found when running hyperopt.
|
||||
|
||||
So let's write the buy strategy using these values:
|
||||
|
||||
```python
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Define the buy strategy parameters to be used by Hyperopt.
|
||||
"""
|
||||
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
# GUARDS AND TRENDS
|
||||
if 'adx-enabled' in params and params['adx-enabled']:
|
||||
conditions.append(dataframe['adx'] > params['adx-value'])
|
||||
if 'rsi-enabled' in params and params['rsi-enabled']:
|
||||
conditions.append(dataframe['rsi'] < params['rsi-value'])
|
||||
if self.buy_adx_enabled.value:
|
||||
conditions.append(dataframe['adx'] > self.buy_adx.value)
|
||||
if self.buy_rsi_enabled.value:
|
||||
conditions.append(dataframe['rsi'] < self.buy_rsi.value)
|
||||
|
||||
# TRIGGERS
|
||||
if 'trigger' in params:
|
||||
if params['trigger'] == 'bb_lower':
|
||||
if self.buy_trigger.value == 'bb_lower':
|
||||
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
|
||||
if params['trigger'] == 'macd_cross_signal':
|
||||
if self.buy_trigger.value == 'macd_cross_signal':
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe['macd'], dataframe['macdsignal']
|
||||
))
|
||||
@ -305,12 +295,10 @@ So let's write the buy strategy using these values:
|
||||
'buy'] = 1
|
||||
|
||||
return dataframe
|
||||
|
||||
return populate_buy_trend
|
||||
```
|
||||
|
||||
Hyperopt will now call `populate_buy_trend()` many times (`epochs`) with different value combinations.
|
||||
It will use the given historical data and make buys based on the buy signals generated with the above function.
|
||||
It will use the given historical data and simulate buys based on the buy signals generated with the above function.
|
||||
Based on the results, hyperopt will tell you which parameter combination produced the best results (based on the configured [loss function](#loss-functions)).
|
||||
|
||||
!!! Note
|
||||
@ -318,6 +306,108 @@ Based on the results, hyperopt will tell you which parameter combination produce
|
||||
When you want to test an indicator that isn't used by the bot currently, remember to
|
||||
add it to the `populate_indicators()` method in your strategy or hyperopt file.
|
||||
|
||||
## Parameter types
|
||||
|
||||
There are four parameter types each suited for different purposes.
|
||||
|
||||
* `IntParameter` - defines an integral parameter with upper and lower boundaries of search space.
|
||||
* `DecimalParameter` - defines a floating point parameter with a limited number of decimals (default 3). Should be preferred instead of `RealParameter` in most cases.
|
||||
* `RealParameter` - defines a floating point parameter with upper and lower boundaries and no precision limit. Rarely used as it creates a space with a near infinite number of possibilities.
|
||||
* `CategoricalParameter` - defines a parameter with a predetermined number of choices.
|
||||
|
||||
!!! Tip "Disabling parameter optimization"
|
||||
Each parameter takes two boolean parameters:
|
||||
* `load` - when set to `False` it will not load values configured in `buy_params` and `sell_params`.
|
||||
* `optimize` - when set to `False` parameter will not be included in optimization process.
|
||||
Use these parameters to quickly prototype various ideas.
|
||||
|
||||
!!! Warning
|
||||
Hyperoptable parameters cannot be used in `populate_indicators` - as hyperopt does not recalculate indicators for each epoch, so the starting value would be used in this case.
|
||||
|
||||
### Optimizing an indicator parameter
|
||||
|
||||
Assuming you have a simple strategy in mind - a EMA cross strategy (2 Moving averages crossing) - and you'd like to find the ideal parameters for this strategy.
|
||||
|
||||
``` python
|
||||
from pandas import DataFrame
|
||||
from functools import reduce
|
||||
|
||||
import talib.abstract as ta
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
stoploss = -0.05
|
||||
timeframe = '15m'
|
||||
# Define the parameter spaces
|
||||
buy_ema_short = IntParameter(3, 50, default=5)
|
||||
buy_ema_long = IntParameter(15, 200, default=50)
|
||||
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
"""Generate all indicators used by the strategy"""
|
||||
|
||||
# Calculate all ema_short values
|
||||
for val in self.buy_ema_short.range:
|
||||
dataframe[f'ema_short_{val}'] = ta.EMA(dataframe, timeperiod=val)
|
||||
|
||||
# Calculate all ema_long values
|
||||
for val in self.buy_ema_long.range:
|
||||
dataframe[f'ema_long_{val}'] = ta.EMA(dataframe, timeperiod=val)
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe[f'ema_short_{self.buy_ema_short.value}'], dataframe[f'ema_long_{self.buy_ema_long.value}']
|
||||
))
|
||||
|
||||
# Check that volume is not 0
|
||||
conditions.append(dataframe['volume'] > 0)
|
||||
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'buy'] = 1
|
||||
return dataframe
|
||||
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
conditions = []
|
||||
conditions.append(qtpylib.crossed_above(
|
||||
dataframe[f'ema_long_{self.buy_ema_long.value}'], dataframe[f'ema_short_{self.buy_ema_short.value}']
|
||||
))
|
||||
|
||||
# Check that volume is not 0
|
||||
conditions.append(dataframe['volume'] > 0)
|
||||
|
||||
if conditions:
|
||||
dataframe.loc[
|
||||
reduce(lambda x, y: x & y, conditions),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
```
|
||||
|
||||
Breaking it down:
|
||||
|
||||
Using `self.buy_ema_short.range` will return a range object containing all entries between the Parameters low and high value.
|
||||
In this case (`IntParameter(3, 50, default=5)`), the loop would run for all numbers between 3 and 50 (`[3, 4, 5, ... 49, 50]`).
|
||||
By using this in a loop, hyperopt will generate 48 new columns (`['buy_ema_3', 'buy_ema_4', ... , 'buy_ema_50']`).
|
||||
|
||||
Hyperopt itself will then use the selected value to create the buy and sell signals
|
||||
|
||||
While this strategy is most likely too simple to provide consistent profit, it should serve as an example how optimize indicator parameters.
|
||||
|
||||
!!! Note
|
||||
`self.buy_ema_short.range` will act differently between hyperopt and other modes. For hyperopt, the above example may generate 48 new columns, however for all other modes (backtesting, dry/live), it will only generate the column for the selected value. You should therefore avoid using the resulting column with explicit values (values other than `self.buy_ema_short.value`).
|
||||
|
||||
??? Hint "Performance tip"
|
||||
By doing the calculation of all possible indicators in `populate_indicators()`, the calculation of the indicator happens only once for every parameter.
|
||||
While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values).
|
||||
You should however try to use space ranges as small as possible. Every new column will require more memory, and every possibility hyperopt can try will increase the search space.
|
||||
|
||||
## Loss-functions
|
||||
|
||||
Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results.
|
||||
@ -339,16 +429,14 @@ Creation of a custom loss function is covered in the [Advanced Hyperopt](advance
|
||||
## Execute Hyperopt
|
||||
|
||||
Once you have updated your hyperopt configuration you can run it.
|
||||
Because hyperopt tries a lot of combinations to find the best parameters it will take time to get a good result. More time usually results in better results.
|
||||
Because hyperopt tries a lot of combinations to find the best parameters it will take time to get a good result.
|
||||
|
||||
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
|
||||
|
||||
```bash
|
||||
freqtrade hyperopt --config config.json --hyperopt <hyperoptname> --hyperopt-loss <hyperoptlossname> --strategy <strategyname> -e 500 --spaces all
|
||||
freqtrade hyperopt --config config.json --hyperopt-loss <hyperoptlossname> --strategy <strategyname> -e 500 --spaces all
|
||||
```
|
||||
|
||||
Use `<hyperoptname>` as the name of the custom hyperopt used.
|
||||
|
||||
The `-e` option will set how many evaluations hyperopt will do. Since hyperopt uses Bayesian search, running too many epochs at once may not produce greater results. Experience has shown that best results are usually not improving much after 500-1000 epochs.
|
||||
Doing multiple runs (executions) with a few 1000 epochs and different random state will most likely produce different results.
|
||||
|
||||
@ -362,24 +450,17 @@ The `--spaces all` option determines that all possible parameters should be opti
|
||||
### Execute Hyperopt with different historical data source
|
||||
|
||||
If you would like to hyperopt parameters using an alternate historical data set that
|
||||
you have on-disk, use the `--datadir PATH` option. By default, hyperopt
|
||||
uses data from directory `user_data/data`.
|
||||
you have on-disk, use the `--datadir PATH` option. By default, hyperopt uses data from directory `user_data/data`.
|
||||
|
||||
### Running Hyperopt with a smaller test-set
|
||||
|
||||
Use the `--timerange` argument to change how much of the test-set you want to use.
|
||||
For example, to use one month of data, pass the following parameter to the hyperopt call:
|
||||
For example, to use one month of data, pass `--timerange 20210101-20210201` (from january 2021 - february 2021) to the hyperopt call.
|
||||
|
||||
Full command:
|
||||
|
||||
```bash
|
||||
freqtrade hyperopt --hyperopt <hyperoptname> --strategy <strategyname> --timerange 20180401-20180501
|
||||
```
|
||||
|
||||
### Running Hyperopt using methods from a strategy
|
||||
|
||||
Hyperopt can reuse `populate_indicators`, `populate_buy_trend`, `populate_sell_trend` from your strategy, assuming these methods are **not** in your custom hyperopt file, and a strategy is provided.
|
||||
|
||||
```bash
|
||||
freqtrade hyperopt --hyperopt AwesomeHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy AwesomeStrategy
|
||||
freqtrade hyperopt --hyperopt <hyperoptname> --strategy <strategyname> --timerange 20210101-20210201
|
||||
```
|
||||
|
||||
### Running Hyperopt with Smaller Search Space
|
||||
@ -402,90 +483,48 @@ Legal values are:
|
||||
|
||||
The default Hyperopt Search Space, used when no `--space` command line option is specified, does not include the `trailing` hyperspace. We recommend you to run optimization for the `trailing` hyperspace separately, when the best parameters for other hyperspaces were found, validated and pasted into your custom strategy.
|
||||
|
||||
### Position stacking and disabling max market positions
|
||||
|
||||
In some situations, you may need to run Hyperopt (and Backtesting) with the
|
||||
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
|
||||
|
||||
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
|
||||
open trade is allowed for every traded pair. The total number of trades open for all pairs
|
||||
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
|
||||
some potential trades to be hidden (or masked) by previously open trades.
|
||||
|
||||
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
|
||||
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
|
||||
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
|
||||
number).
|
||||
|
||||
!!! Note
|
||||
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
|
||||
|
||||
You can also enable position stacking in the configuration file by explicitly setting
|
||||
`"position_stacking"=true`.
|
||||
|
||||
### Reproducible results
|
||||
|
||||
The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with an asterisk character (`*`) in the first column in the Hyperopt output.
|
||||
|
||||
The initial state for generation of these random values (random state) is controlled by the value of the `--random-state` command line option. You can set it to some arbitrary value of your choice to obtain reproducible results.
|
||||
|
||||
If you have not set this value explicitly in the command line options, Hyperopt seeds the random state with some random value for you. The random state value for each Hyperopt run is shown in the log, so you can copy and paste it into the `--random-state` command line option to repeat the set of the initial random epochs used.
|
||||
|
||||
If you have not changed anything in the command line options, configuration, timerange, Strategy and Hyperopt classes, historical data and the Loss Function -- you should obtain same hyper-optimization results with same random state value used.
|
||||
|
||||
## Understand the Hyperopt Result
|
||||
|
||||
Once Hyperopt is completed you can use the result to create a new strategy.
|
||||
Once Hyperopt is completed you can use the result to update your strategy.
|
||||
Given the following result from hyperopt:
|
||||
|
||||
```
|
||||
Best result:
|
||||
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
|
||||
Buy hyperspace params:
|
||||
{ 'adx-value': 44,
|
||||
'rsi-value': 29,
|
||||
'adx-enabled': False,
|
||||
'rsi-enabled': True,
|
||||
'trigger': 'bb_lower'}
|
||||
# Buy hyperspace params:
|
||||
buy_params = {
|
||||
'buy_adx': 44,
|
||||
'buy_rsi': 29,
|
||||
'buy_adx_enabled': False,
|
||||
'buy_rsi_enabled': True,
|
||||
'buy_trigger': 'bb_lower'
|
||||
}
|
||||
```
|
||||
|
||||
You should understand this result like:
|
||||
|
||||
- The buy trigger that worked best was `bb_lower`.
|
||||
- You should not use ADX because `adx-enabled: False`)
|
||||
- You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
|
||||
* The buy trigger that worked best was `bb_lower`.
|
||||
* You should not use ADX because `'buy_adx_enabled': False`.
|
||||
* You should **consider** using the RSI indicator (`'buy_rsi_enabled': True`) and the best value is `29.0` (`'buy_rsi': 29.0`)
|
||||
|
||||
You have to look inside your strategy file into `buy_strategy_generator()`
|
||||
method, what those values match to.
|
||||
Your strategy class can immediately take advantage of these results. Simply copy hyperopt results block and paste them at class level, replacing old parameters (if any). New parameters will automatically be loaded next time strategy is executed.
|
||||
|
||||
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
|
||||
Transferring your whole hyperopt result to your strategy would then look like:
|
||||
|
||||
```python
|
||||
(dataframe['rsi'] < 29.0)
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
# Buy hyperspace params:
|
||||
buy_params = {
|
||||
'buy_adx': 44,
|
||||
'buy_rsi': 29,
|
||||
'buy_adx_enabled': False,
|
||||
'buy_rsi_enabled': True,
|
||||
'buy_trigger': 'bb_lower'
|
||||
}
|
||||
```
|
||||
|
||||
Translating your whole hyperopt result as the new buy-signal would then look like:
|
||||
|
||||
```python
|
||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < 29.0) & # rsi-value
|
||||
dataframe['close'] < dataframe['bb_lowerband'] # trigger
|
||||
),
|
||||
'buy'] = 1
|
||||
return dataframe
|
||||
```
|
||||
|
||||
By default, hyperopt prints colorized results -- epochs with positive profit are printed in the green color. This highlighting helps you find epochs that can be interesting for later analysis. Epochs with zero total profit or with negative profits (losses) are printed in the normal color. If you do not need colorization of results (for instance, when you are redirecting hyperopt output to a file) you can switch colorization off by specifying the `--no-color` option in the command line.
|
||||
|
||||
You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option.
|
||||
|
||||
!!! Note "Windows and color output"
|
||||
Windows does not support color-output natively, therefore it is automatically disabled. To have color-output for hyperopt running under windows, please consider using WSL.
|
||||
|
||||
### Understand Hyperopt ROI results
|
||||
|
||||
If you are optimizing ROI (i.e. if optimization search-space contains 'all', 'default' or 'roi'), your result will look as follows and include a ROI table:
|
||||
@ -493,13 +532,15 @@ If you are optimizing ROI (i.e. if optimization search-space contains 'all', 'de
|
||||
```
|
||||
Best result:
|
||||
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
|
||||
ROI table:
|
||||
{ 0: 0.10674,
|
||||
# ROI table:
|
||||
minimal_roi = {
|
||||
0: 0.10674,
|
||||
21: 0.09158,
|
||||
78: 0.03634,
|
||||
118: 0}
|
||||
118: 0
|
||||
}
|
||||
```
|
||||
|
||||
In order to use this best ROI table found by Hyperopt in backtesting and for live trades/dry-run, copy-paste it as the value of the `minimal_roi` attribute of your custom strategy:
|
||||
@ -519,13 +560,13 @@ As stated in the comment, you can also use it as the value of the `minimal_roi`
|
||||
|
||||
#### Default ROI Search Space
|
||||
|
||||
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point):
|
||||
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the timeframe used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 3 digits after the decimal point):
|
||||
|
||||
| # step | 1m | | 5m | | 1h | | 1d | |
|
||||
| ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- |
|
||||
| 1 | 0 | 0.01161...0.11992 | 0 | 0.03...0.31 | 0 | 0.06883...0.71124 | 0 | 0.12178...1.25835 |
|
||||
| 2 | 2...8 | 0.00774...0.04255 | 10...40 | 0.02...0.11 | 120...480 | 0.04589...0.25238 | 2880...11520 | 0.08118...0.44651 |
|
||||
| 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 |
|
||||
| ------ | ------ | ------------- | -------- | ----------- | ---------- | ------------- | ------------ | ------------- |
|
||||
| 1 | 0 | 0.011...0.119 | 0 | 0.03...0.31 | 0 | 0.068...0.711 | 0 | 0.121...1.258 |
|
||||
| 2 | 2...8 | 0.007...0.042 | 10...40 | 0.02...0.11 | 120...480 | 0.045...0.252 | 2880...11520 | 0.081...0.446 |
|
||||
| 3 | 4...20 | 0.003...0.015 | 20...100 | 0.01...0.04 | 240...1200 | 0.022...0.091 | 5760...28800 | 0.040...0.162 |
|
||||
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
|
||||
|
||||
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
|
||||
@ -536,6 +577,9 @@ Override the `roi_space()` method if you need components of the ROI tables to va
|
||||
|
||||
A sample for these methods can be found in [sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
||||
|
||||
!!! Note "Reduced search space"
|
||||
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
||||
|
||||
### Understand Hyperopt Stoploss results
|
||||
|
||||
If you are optimizing stoploss values (i.e. if optimization search-space contains 'all', 'default' or 'stoploss'), your result will look as follows and include stoploss:
|
||||
@ -543,15 +587,18 @@ If you are optimizing stoploss values (i.e. if optimization search-space contain
|
||||
```
|
||||
Best result:
|
||||
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722%). Avg duration 180.4 mins. Objective: 1.94367
|
||||
|
||||
Buy hyperspace params:
|
||||
{ 'adx-value': 44,
|
||||
'rsi-value': 29,
|
||||
'adx-enabled': False,
|
||||
'rsi-enabled': True,
|
||||
'trigger': 'bb_lower'}
|
||||
Stoploss: -0.27996
|
||||
# Buy hyperspace params:
|
||||
buy_params = {
|
||||
'buy_adx': 44,
|
||||
'buy_rsi': 29,
|
||||
'buy_adx_enabled': False,
|
||||
'buy_rsi_enabled': True,
|
||||
'buy_trigger': 'bb_lower'
|
||||
}
|
||||
|
||||
stoploss: -0.27996
|
||||
```
|
||||
|
||||
In order to use this best stoploss value found by Hyperopt in backtesting and for live trades/dry-run, copy-paste it as the value of the `stoploss` attribute of your custom strategy:
|
||||
@ -572,6 +619,9 @@ If you have the `stoploss_space()` method in your custom hyperopt file, remove i
|
||||
|
||||
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
||||
|
||||
!!! Note "Reduced search space"
|
||||
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
||||
|
||||
### Understand Hyperopt Trailing Stop results
|
||||
|
||||
If you are optimizing trailing stop values (i.e. if optimization search-space contains 'all' or 'trailing'), your result will look as follows and include trailing stop parameters:
|
||||
@ -579,13 +629,13 @@ If you are optimizing trailing stop values (i.e. if optimization search-space co
|
||||
```
|
||||
Best result:
|
||||
|
||||
45/100: 606 trades. Avg profit 1.04%. Total profit 0.31555614 BTC ( 630.48Σ%). Avg duration 150.3 mins. Objective: -1.10161
|
||||
45/100: 606 trades. Avg profit 1.04%. Total profit 0.31555614 BTC ( 630.48%). Avg duration 150.3 mins. Objective: -1.10161
|
||||
|
||||
Trailing stop:
|
||||
{ 'trailing_only_offset_is_reached': True,
|
||||
'trailing_stop': True,
|
||||
'trailing_stop_positive': 0.02001,
|
||||
'trailing_stop_positive_offset': 0.06038}
|
||||
# Trailing stop:
|
||||
trailing_stop = True
|
||||
trailing_stop_positive = 0.02001
|
||||
trailing_stop_positive_offset = 0.06038
|
||||
trailing_only_offset_is_reached = True
|
||||
```
|
||||
|
||||
In order to use these best trailing stop parameters found by Hyperopt in backtesting and for live trades/dry-run, copy-paste them as the values of the corresponding attributes of your custom strategy:
|
||||
@ -607,6 +657,59 @@ If you are optimizing trailing stop values, Freqtrade creates the 'trailing' opt
|
||||
|
||||
Override the `trailing_space()` method and define the desired range in it if you need values of the trailing stop parameters to vary in other ranges during hyperoptimization. A sample for this method can be found in [user_data/hyperopts/sample_hyperopt_advanced.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py).
|
||||
|
||||
!!! Note "Reduced search space"
|
||||
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
||||
|
||||
### Reproducible results
|
||||
|
||||
The search for optimal parameters starts with a few (currently 30) random combinations in the hyperspace of parameters, random Hyperopt epochs. These random epochs are marked with an asterisk character (`*`) in the first column in the Hyperopt output.
|
||||
|
||||
The initial state for generation of these random values (random state) is controlled by the value of the `--random-state` command line option. You can set it to some arbitrary value of your choice to obtain reproducible results.
|
||||
|
||||
If you have not set this value explicitly in the command line options, Hyperopt seeds the random state with some random value for you. The random state value for each Hyperopt run is shown in the log, so you can copy and paste it into the `--random-state` command line option to repeat the set of the initial random epochs used.
|
||||
|
||||
If you have not changed anything in the command line options, configuration, timerange, Strategy and Hyperopt classes, historical data and the Loss Function -- you should obtain same hyper-optimization results with same random state value used.
|
||||
|
||||
## Output formatting
|
||||
|
||||
By default, hyperopt prints colorized results -- epochs with positive profit are printed in the green color. This highlighting helps you find epochs that can be interesting for later analysis. Epochs with zero total profit or with negative profits (losses) are printed in the normal color. If you do not need colorization of results (for instance, when you are redirecting hyperopt output to a file) you can switch colorization off by specifying the `--no-color` option in the command line.
|
||||
|
||||
You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option.
|
||||
|
||||
!!! Note "Windows and color output"
|
||||
Windows does not support color-output natively, therefore it is automatically disabled. To have color-output for hyperopt running under windows, please consider using WSL.
|
||||
|
||||
## Position stacking and disabling max market positions
|
||||
|
||||
In some situations, you may need to run Hyperopt (and Backtesting) with the
|
||||
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
|
||||
|
||||
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
|
||||
open trade is allowed for every traded pair. The total number of trades open for all pairs
|
||||
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
|
||||
some potential trades to be hidden (or masked) by previously open trades.
|
||||
|
||||
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
|
||||
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
|
||||
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
|
||||
number).
|
||||
|
||||
!!! Note
|
||||
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
|
||||
|
||||
You can also enable position stacking in the configuration file by explicitly setting
|
||||
`"position_stacking"=true`.
|
||||
|
||||
## Out of Memory errors
|
||||
|
||||
As hyperopt consumes a lot of memory (the complete data needs to be in memory once per parallel backtesting process), it's likely that you run into "out of memory" errors.
|
||||
To combat these, you have multiple options:
|
||||
|
||||
* reduce the amount of pairs
|
||||
* reduce the timerange used (`--timerange <timerange>`)
|
||||
* reduce the number of parallel processes (`-j <n>`)
|
||||
* Increase the memory of your machine
|
||||
|
||||
## Show details of Hyperopt results
|
||||
|
||||
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
@ -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.
|
||||
|
||||
@ -29,6 +29,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
|
||||
* [`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.
|
||||
@ -59,6 +60,8 @@ When used in the chain of Pairlist Handlers in a non-leading position (after Sta
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
@ -89,6 +92,7 @@ This filter allows freqtrade to ignore pairs until they have been listed for at
|
||||
#### PerformanceFilter
|
||||
|
||||
Sorts pairs by past trade performance, as follows:
|
||||
|
||||
1. Positive performance.
|
||||
2. No closed trades yet.
|
||||
3. Negative performance.
|
||||
@ -108,6 +112,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 +121,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.
|
||||
|
||||
@ -164,9 +174,32 @@ 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.
|
||||
|
||||
#### 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 +210,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 +222,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}
|
||||
],
|
||||
```
|
||||
|
@ -103,6 +103,10 @@ A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting
|
||||
|
||||
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.
|
||||
|
@ -40,7 +40,9 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
|
||||
|
||||
#### Stoploss Guard
|
||||
|
||||
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`), and determines if the amount of trades that resulted in stoploss are above `trade_limit` - in which case trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
|
||||
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
|
||||
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
|
||||
|
||||
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
|
||||
|
||||
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.
|
||||
|
@ -1,16 +1,13 @@
|
||||
# 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)
|
||||
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a class="github-button" href="https://github.com/freqtrade/freqtrade" data-icon="octicon-star" data-size="large" aria-label="Star freqtrade/freqtrade on GitHub">Star</a>
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a class="github-button" href="https://github.com/freqtrade/freqtrade/fork" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork freqtrade/freqtrade on GitHub">Fork</a>
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a class="github-button" href="https://github.com/freqtrade/freqtrade/archive/stable.zip" data-icon="octicon-cloud-download" data-size="large" aria-label="Download freqtrade/freqtrade on GitHub">Download</a>
|
||||
<!-- Place this tag where you want the button to render. -->
|
||||
<a class="github-button" href="https://github.com/freqtrade" data-size="large" aria-label="Follow @freqtrade on GitHub">Follow @freqtrade</a>
|
||||
|
||||
## Introduction
|
||||
|
||||
@ -35,6 +32,22 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python
|
||||
- Control/Monitor: Use Telegram or a REST API (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.).
|
||||
- Analyse: Further analysis can be performed on either Backtesting data or Freqtrade trading history (SQL database), including automated standard plots, and methods to load the data into [interactive environments](data-analysis.md).
|
||||
|
||||
## Supported exchange marketplaces
|
||||
|
||||
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] [Bittrex](https://bittrex.com/)
|
||||
- [X] [FTX](https://ftx.com)
|
||||
- [X] [Kraken](https://kraken.com/)
|
||||
- [ ] [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/)
|
||||
|
||||
## Requirements
|
||||
|
||||
### Hardware requirements
|
||||
@ -63,9 +76,9 @@ Alternatively
|
||||
|
||||
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).
|
||||
Please check out our [discord server](https://discord.gg/p7nuUNVfP7).
|
||||
|
||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-l9d9iqgl-9cVBIeBkCBa8j6upSmd_NA).
|
||||
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
|
||||
|
||||
## Ready to try?
|
||||
|
||||
|
@ -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-pandas git
|
||||
```
|
||||
|
||||
=== "RaspberryPi/Raspbian"
|
||||
@ -269,7 +269,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
|
||||
|
||||
|
@ -6,22 +6,22 @@ This file was automatically generated - do not edit
|
||||
{% set site_url = site_url ~ "/index.html" %}
|
||||
{% endif %}
|
||||
<header class="md-header" data-md-component="header">
|
||||
<nav class="md-header-nav md-grid" aria-label="{{ lang.t('header.title') }}">
|
||||
<a href="{{ site_url }}" title="{{ config.site_name | e }}" class="md-header-nav__button md-logo"
|
||||
<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-nav__button md-icon" for="__drawer">
|
||||
<label class="md-header__button md-icon" for="__drawer">
|
||||
{% include ".icons/material/menu" ~ ".svg" %}
|
||||
</label>
|
||||
<div class="md-header-nav__title" data-md-component="header-title">
|
||||
<div class="md-header-nav__ellipsis">
|
||||
<div class="md-header-nav__topic">
|
||||
<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-nav__topic">
|
||||
<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 }}
|
||||
@ -32,14 +32,35 @@ This file was automatically generated - do not edit
|
||||
</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-nav__button md-icon" for="__search">
|
||||
<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-nav__source">
|
||||
<div class="md-header__source">
|
||||
{% include "partials/source.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -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
|
||||
@ -188,7 +194,7 @@ Sample configuration with inline comments explaining the process:
|
||||
'senkou_a': {
|
||||
'color': 'green', #optional
|
||||
'fill_to': 'senkou_b',
|
||||
'fill_label': 'Ichimoku Cloud' #optional,
|
||||
'fill_label': 'Ichimoku Cloud', #optional
|
||||
'fill_color': 'rgba(255,76,46,0.2)', #optional
|
||||
},
|
||||
# plot senkou_b, too. Not only the area to it.
|
||||
@ -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.
|
||||
|
@ -1,3 +1,4 @@
|
||||
mkdocs-material==6.2.8
|
||||
mkdocs==1.2
|
||||
mkdocs-material==7.1.7
|
||||
mdx_truly_sane_lists==1.2
|
||||
pymdown-extensions==8.1.1
|
||||
pymdown-extensions==8.2
|
||||
|
@ -71,7 +71,10 @@ 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!",
|
||||
//...
|
||||
},
|
||||
```
|
||||
|
||||
@ -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,13 +130,15 @@ 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.
|
||||
| `status` | Lists all open trades.
|
||||
| `count` | Displays number of trades used and available.
|
||||
| `locks` | Displays currently locked pairs.
|
||||
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
|
||||
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
|
||||
| `forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
|
||||
| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
|
||||
@ -180,7 +188,12 @@ 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.
|
||||
|
||||
:param lock_id: ID for the lock to delete
|
||||
|
||||
delete_trade
|
||||
Delete trade from the database.
|
||||
@ -202,10 +215,13 @@ forcesell
|
||||
|
||||
:param tradeid: Id of the trade (can be received via status command)
|
||||
|
||||
locks
|
||||
Return current 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>.
|
||||
@ -225,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.
|
||||
|
||||
@ -261,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
|
||||
|
@ -6,6 +6,10 @@ With some configuration, freqtrade (in combination with ccxt) provides access to
|
||||
This document is an overview to configure Freqtrade to be used with sandboxes.
|
||||
This can be useful to developers and trader alike.
|
||||
|
||||
!!! Warning
|
||||
Sandboxes usually have very low volume, and either a very wide spread, or no orders available at all.
|
||||
Therefore, sandboxes will usually not do a good job of showing you how a strategy would work in real trading.
|
||||
|
||||
## Exchanges known to have a sandbox / testnet
|
||||
|
||||
* [binance](https://testnet.binance.vision/)
|
||||
|
@ -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`
|
||||
|
||||
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>`
|
||||
|
@ -51,6 +51,14 @@ The bot cannot do these every 5 seconds (at each iteration), otherwise it would
|
||||
So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute).
|
||||
This same logic will reapply a stoploss order on the exchange should you cancel it accidentally.
|
||||
|
||||
### forcesell
|
||||
|
||||
`forcesell` is an optional value, which defaults to the same value as `sell` and is used when sending a `/forcesell` command from Telegram or from the Rest API.
|
||||
|
||||
### forcebuy
|
||||
|
||||
`forcebuy` is an optional value, which defaults to the same value as `buy` and is used when sending a `/forcebuy` command from Telegram or from the Rest API.
|
||||
|
||||
### emergencysell
|
||||
|
||||
`emergencysell` is an optional value, which defaults to `market` and is used when creating stop loss on exchange orders fails.
|
||||
|
@ -11,14 +11,118 @@ If you're just getting started, please be familiar with the methods described in
|
||||
!!! Tip
|
||||
You can get a strategy template containing all below methods by running `freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced`
|
||||
|
||||
## Storing information
|
||||
|
||||
Storing information can be accomplished by creating a new dictionary within the strategy class.
|
||||
|
||||
The name of the variable can be chosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables.
|
||||
|
||||
```python
|
||||
class AwesomeStrategy(IStrategy):
|
||||
# Create custom dictionary
|
||||
custom_info = {}
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Check if the entry already exists
|
||||
if not metadata["pair"] in self.custom_info:
|
||||
# Create empty entry for this pair
|
||||
self.custom_info[metadata["pair"]] = {}
|
||||
|
||||
if "crosstime" in self.custom_info[metadata["pair"]]:
|
||||
self.custom_info[metadata["pair"]]["crosstime"] += 1
|
||||
else:
|
||||
self.custom_info[metadata["pair"]]["crosstime"] = 1
|
||||
```
|
||||
|
||||
!!! 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.
|
||||
|
||||
!!! Note
|
||||
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
|
||||
|
||||
## Dataframe access
|
||||
|
||||
You may access dataframe in various strategy functions by querying it from dataprovider.
|
||||
|
||||
``` python
|
||||
from freqtrade.exchange import timeframe_to_prev_date
|
||||
|
||||
class AwesomeStrategy(IStrategy):
|
||||
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
|
||||
rate: float, time_in_force: str, sell_reason: str,
|
||||
current_time: 'datetime', **kwargs) -> bool:
|
||||
# Obtain pair dataframe.
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
|
||||
# Obtain last available candle. Do not use current_time to look up latest candle, because
|
||||
# current_time points to curret incomplete candle whose data is not available.
|
||||
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 "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 stoplosses though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange.
|
||||
|
||||
!!! Note
|
||||
Returning a `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters.
|
||||
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
||||
@ -51,7 +155,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
|
||||
```
|
||||
@ -87,9 +191,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
|
||||
```
|
||||
@ -148,18 +252,25 @@ 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()`.
|
||||
|
||||
#### 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):
|
||||
|
||||
@ -170,23 +281,57 @@ 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.7 + 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
|
||||
|
||||
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
|
||||
|
||||
``` python
|
||||
class AwesomeStrategy(IStrategy):
|
||||
|
||||
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:
|
||||
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
last_candle = dataframe.iloc[-1].squeeze()
|
||||
|
||||
# Use parabolic sar as absolute stoploss price
|
||||
stoploss_price = last_candle['sar']
|
||||
|
||||
# Convert absolute price to percentage relative to current_rate
|
||||
if stoploss_price < current_rate:
|
||||
return (stoploss_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 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.
|
||||
@ -199,7 +344,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):
|
||||
@ -213,21 +358,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
|
||||
```
|
||||
@ -412,7 +557,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.
|
||||
|
||||
|
@ -159,7 +159,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 +193,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`:
|
||||
|
||||
@ -300,38 +300,7 @@ The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `p
|
||||
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
|
||||
|
||||
The Metadata-dict should not be modified and does not persist information across multiple calls.
|
||||
Instead, have a look at the section [Storing information](#Storing-information)
|
||||
|
||||
### Storing information
|
||||
|
||||
Storing information can be accomplished by creating a new dictionary within the strategy class.
|
||||
|
||||
The name of the variable can be chosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables.
|
||||
|
||||
```python
|
||||
class AwesomeStrategy(IStrategy):
|
||||
# Create custom dictionary
|
||||
cust_info = {}
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
# Check if the entry already exists
|
||||
if not metadata["pair"] in self._cust_info:
|
||||
# Create empty entry for this pair
|
||||
self._cust_info[metadata["pair"]] = {}
|
||||
|
||||
if "crosstime" in self.cust_info[metadata["pair"]:
|
||||
self.cust_info[metadata["pair"]]["crosstime"] += 1
|
||||
else:
|
||||
self.cust_info[metadata["pair"]]["crosstime"] = 1
|
||||
```
|
||||
|
||||
!!! 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.
|
||||
|
||||
!!! Note
|
||||
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
|
||||
|
||||
***
|
||||
Instead, have a look at the section [Storing information](strategy-advanced.md#Storing-information)
|
||||
|
||||
## Additional data (informative_pairs)
|
||||
|
||||
@ -444,6 +413,7 @@ It can also be used in specific callbacks to get the signal that caused the acti
|
||||
``` python
|
||||
# fetch current dataframe
|
||||
if self.dp:
|
||||
if self.dp.runmode.value in ('live', 'dry_run'):
|
||||
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
|
||||
timeframe=self.timeframe)
|
||||
```
|
||||
@ -462,8 +432,28 @@ if self.dp:
|
||||
dataframe['best_ask'] = ob['asks'][0][0]
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used.
|
||||
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.
|
||||
|
||||
### *ticker(pair)*
|
||||
|
||||
@ -613,6 +603,43 @@ 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.
|
||||
|
||||
|
||||
## Additional data (Wallets)
|
||||
|
||||
The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.
|
||||
@ -704,7 +731,7 @@ To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
|
||||
Locked pairs will always be rounded up to the next candle. So assuming a `5m` timeframe, a lock with `until` set to 10:18 will lock the pair until the candle from 10:15-10:20 will be finished.
|
||||
|
||||
!!! Warning
|
||||
Locking pairs is not available during backtesting.
|
||||
Manually locking pairs is not available during backtesting, only locks via Protections are allowed.
|
||||
|
||||
#### Pair locking example
|
||||
|
||||
|
@ -24,7 +24,7 @@ config["strategy"] = "SampleStrategy"
|
||||
# Location of the data
|
||||
data_location = Path(config['user_data_dir'], 'data', 'binance')
|
||||
# Pair to analyze - Only use one pair here
|
||||
pair = "BTC_USDT"
|
||||
pair = "BTC/USDT"
|
||||
```
|
||||
|
||||
|
||||
@ -34,7 +34,9 @@ from freqtrade.data.history import load_pair_history
|
||||
|
||||
candles = load_pair_history(datadir=data_location,
|
||||
timeframe=config["timeframe"],
|
||||
pair=pair)
|
||||
pair=pair,
|
||||
data_format = "hdf5",
|
||||
)
|
||||
|
||||
# Confirm success
|
||||
print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}")
|
||||
@ -193,4 +195,18 @@ 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.
|
||||
|
@ -80,13 +80,32 @@ Example configuration showing the different settings:
|
||||
"warning": "on",
|
||||
"startup": "off",
|
||||
"buy": "silent",
|
||||
"sell": "on",
|
||||
"buy_cancel": "silent",
|
||||
"sell_cancel": "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",
|
||||
"buy_fill": "off",
|
||||
"sell_fill": "off"
|
||||
},
|
||||
"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.
|
||||
|
||||
|
||||
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
|
||||
|
||||
## Create a custom keyboard (command shortcut buttons)
|
||||
|
||||
Telegram allows us to create a custom keyboard with buttons for commands.
|
||||
@ -143,7 +162,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.
|
||||
| `/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)
|
||||
@ -239,10 +259,14 @@ Return a summary of your profit/loss and performance.
|
||||
|
||||
> **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)
|
||||
@ -251,11 +275,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
|
||||
|
199
docs/utils.md
199
docs/utils.md
@ -253,18 +253,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]
|
||||
|
@ -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": "",
|
||||
@ -40,6 +50,21 @@ Sample configuration (tested using IFTTT).
|
||||
|
||||
The url in `webhook.url` should point to the correct url for your webhook. If you're using [IFTTT](https://ifttt.com) (as shown in the sample above) please insert our event and key to the url.
|
||||
|
||||
You can set the POST body format to Form-Encoded (default) or JSON-Encoded. Use `"format": "form"` or `"format": "json"` respectively. Example configuration for Mattermost Cloud integration:
|
||||
|
||||
```json
|
||||
"webhook": {
|
||||
"enabled": true,
|
||||
"url": "https://<YOURSUBDOMAIN>.cloud.mattermost.com/hooks/<YOURHOOK>",
|
||||
"format": "json",
|
||||
"webhookstatus": {
|
||||
"text": "Status: {status}"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
The result would be POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel.
|
||||
|
||||
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
|
||||
|
||||
### Webhookbuy
|
||||
@ -76,6 +101,21 @@ Possible parameters are:
|
||||
* `order_type`
|
||||
* `current_rate`
|
||||
|
||||
### 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`
|
||||
|
||||
### Webhooksell
|
||||
|
||||
The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format.
|
||||
@ -88,6 +128,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`
|
||||
|
@ -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_Lib‑0.4.19‑cp38‑cp38‑win_amd64.whl` (make sure to use the version matching your python version)
|
||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib‑0.4.20‑cp38‑cp38‑win_amd64.whl` (make sure to use the version matching your python version).
|
||||
|
||||
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.
|
||||
|
@ -4,7 +4,7 @@ channels:
|
||||
# - defaults
|
||||
dependencies:
|
||||
# 1/4 req main
|
||||
- python>=3.7
|
||||
- python>=3.7,<3.9
|
||||
- numpy
|
||||
- pandas
|
||||
- pip
|
||||
|
@ -14,18 +14,18 @@ ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_dat
|
||||
|
||||
ARGS_STRATEGY = ["strategy", "strategy_path"]
|
||||
|
||||
ARGS_TRADE = ["db_url", "sd_notify", "dry_run"]
|
||||
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
|
||||
|
||||
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",
|
||||
"enable_protections", "dry_run_wallet",
|
||||
"strategy_list", "export", "exportfilename"]
|
||||
|
||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||
"position_stacking", "use_max_market_positions",
|
||||
"enable_protections",
|
||||
"enable_protections", "dry_run_wallet",
|
||||
"epochs", "spaces", "print_all",
|
||||
"print_colorized", "print_json", "hyperopt_jobs",
|
||||
"hyperopt_random_state", "hyperopt_min_trades",
|
||||
@ -60,15 +60,16 @@ ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
|
||||
|
||||
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"]
|
||||
|
||||
|
@ -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
|
||||
@ -93,10 +95,10 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
"message": "Select exchange",
|
||||
"choices": [
|
||||
"binance",
|
||||
"binanceje",
|
||||
"binanceus",
|
||||
"bittrex",
|
||||
"kraken",
|
||||
"ftx",
|
||||
Separator(),
|
||||
"other",
|
||||
],
|
||||
@ -138,6 +140,32 @@ 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 (best left untouched default!)",
|
||||
"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 +173,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
|
||||
|
||||
|
||||
@ -173,6 +204,9 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
||||
arguments=selections)
|
||||
|
||||
logger.info(f"Writing config to `{config_path}`.")
|
||||
logger.info(
|
||||
"Please make sure to check the configuration contents and adjust settings to your needs.")
|
||||
|
||||
config_path.write_text(config_text)
|
||||
|
||||
|
||||
@ -183,6 +217,7 @@ def start_new_config(args: Dict[str, Any]) -> None:
|
||||
"""
|
||||
|
||||
config_path = Path(args['config'][0])
|
||||
chown_user_directory(config_path.parent)
|
||||
if config_path.exists():
|
||||
overwrite = ask_user_overwrite(config_path)
|
||||
if overwrite:
|
||||
|
@ -110,10 +110,15 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
help='Enforce dry-run for trading (removes Exchange secrets and simulates trades).',
|
||||
action='store_true',
|
||||
),
|
||||
"dry_run_wallet": Arg(
|
||||
'--dry-run-wallet', '--starting-balance',
|
||||
help='Starting balance, used for backtesting / hyperopt and dry-runs.',
|
||||
type=float,
|
||||
),
|
||||
# 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',
|
||||
@ -128,7 +133,6 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
"stake_amount": Arg(
|
||||
'--stake-amount',
|
||||
help='Override the value of the `stake_amount` configuration setting.',
|
||||
type=float,
|
||||
),
|
||||
# Backtesting
|
||||
"position_stacking": Arg(
|
||||
@ -191,6 +195,7 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
'--hyperopt',
|
||||
help='Specify hyperopt class name which will be used by the bot.',
|
||||
metavar='NAME',
|
||||
required=False,
|
||||
),
|
||||
"hyperopt_path": Arg(
|
||||
'--hyperopt-path',
|
||||
@ -262,7 +267,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: '
|
||||
@ -325,7 +330,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
|
||||
@ -340,6 +345,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 '
|
||||
@ -422,6 +433,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.',
|
||||
|
@ -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__)
|
||||
@ -62,8 +62,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 +75,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 ...")
|
||||
|
@ -8,9 +8,9 @@ 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.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__)
|
||||
|
@ -6,8 +6,9 @@ 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)
|
||||
|
||||
@ -47,7 +48,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||
config.get('hyperoptexportfilename'))
|
||||
|
||||
# Previous evaluations
|
||||
epochs = Hyperopt.load_previous_results(results_file)
|
||||
epochs = HyperoptTools.load_previous_results(results_file)
|
||||
total_epochs = len(epochs)
|
||||
|
||||
epochs = hyperopt_filter_epochs(epochs, filteroptions)
|
||||
@ -57,18 +58,19 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||
|
||||
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 filteroptions['only_best'],
|
||||
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(
|
||||
HyperoptTools.export_csv_file(
|
||||
config, epochs, total_epochs, not filteroptions['only_best'], export_csv
|
||||
)
|
||||
|
||||
@ -77,7 +79,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)
|
||||
|
||||
@ -105,7 +107,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
||||
}
|
||||
|
||||
# Previous evaluations
|
||||
epochs = Hyperopt.load_previous_results(results_file)
|
||||
epochs = HyperoptTools.load_previous_results(results_file)
|
||||
total_epochs = len(epochs)
|
||||
|
||||
epochs = hyperopt_filter_epochs(epochs, filteroptions)
|
||||
@ -124,18 +126,26 @@ 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:
|
||||
show_backtest_result(metrics['strategy_name'], metrics,
|
||||
metrics['stake_currency'])
|
||||
|
||||
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
|
||||
TODO: after 2021.5 remove all "legacy" mode queries.
|
||||
"""
|
||||
if filteroptions['only_best']:
|
||||
epochs = [x for x in epochs if x['is_best']]
|
||||
if filteroptions['only_profitable']:
|
||||
epochs = [x for x in epochs if x['results_metrics']['profit'] > 0]
|
||||
epochs = [x for x in epochs if x['results_metrics'].get(
|
||||
'profit', x['results_metrics'].get('profit_total', 0)) > 0]
|
||||
|
||||
epochs = _hyperopt_filter_epochs_trade_count(epochs, filteroptions)
|
||||
|
||||
@ -152,34 +162,59 @@ def hyperopt_filter_epochs(epochs: List, filteroptions: dict) -> List:
|
||||
return epochs
|
||||
|
||||
|
||||
def _hyperopt_filter_epochs_trade(epochs: List, trade_count: int):
|
||||
"""
|
||||
Filter epochs with trade-counts > trades
|
||||
"""
|
||||
return [
|
||||
x for x in epochs
|
||||
if x['results_metrics'].get(
|
||||
'trade_count', x['results_metrics'].get('total_trades', 0)
|
||||
) > trade_count
|
||||
]
|
||||
|
||||
|
||||
def _hyperopt_filter_epochs_trade_count(epochs: List, filteroptions: dict) -> List:
|
||||
|
||||
if filteroptions['filter_min_trades'] > 0:
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades']
|
||||
]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, filteroptions['filter_min_trades'])
|
||||
|
||||
if filteroptions['filter_max_trades'] > 0:
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
|
||||
if x['results_metrics'].get(
|
||||
'trade_count', x['results_metrics'].get('total_trades')
|
||||
) < filteroptions['filter_max_trades']
|
||||
]
|
||||
return epochs
|
||||
|
||||
|
||||
def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
|
||||
|
||||
def get_duration_value(x):
|
||||
# Duration in minutes ...
|
||||
if 'duration' in x['results_metrics']:
|
||||
return x['results_metrics']['duration']
|
||||
else:
|
||||
# New mode
|
||||
if 'holding_avg_s' in x['results_metrics']:
|
||||
avg = x['results_metrics']['holding_avg_s']
|
||||
return avg // 60
|
||||
raise OperationalException(
|
||||
"Holding-average not available. Please omit the filter on average time, "
|
||||
"or rerun hyperopt with this version")
|
||||
|
||||
if filteroptions['filter_min_avg_time'] is not None:
|
||||
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']
|
||||
if get_duration_value(x) > 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 = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']
|
||||
if get_duration_value(x) < filteroptions['filter_max_avg_time']
|
||||
]
|
||||
|
||||
return epochs
|
||||
@ -188,28 +223,36 @@ def _hyperopt_filter_epochs_duration(epochs: List, filteroptions: dict) -> List:
|
||||
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 = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['avg_profit'] > filteroptions['filter_min_avg_profit']
|
||||
if x['results_metrics'].get(
|
||||
'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100
|
||||
) > filteroptions['filter_min_avg_profit']
|
||||
]
|
||||
if filteroptions['filter_max_avg_profit'] is not None:
|
||||
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['avg_profit'] < filteroptions['filter_max_avg_profit']
|
||||
if x['results_metrics'].get(
|
||||
'avg_profit', x['results_metrics'].get('profit_mean', 0) * 100
|
||||
) < filteroptions['filter_max_avg_profit']
|
||||
]
|
||||
if filteroptions['filter_min_total_profit'] is not None:
|
||||
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']
|
||||
if x['results_metrics'].get(
|
||||
'profit', x['results_metrics'].get('profit_total_abs', 0)
|
||||
) > filteroptions['filter_min_total_profit']
|
||||
]
|
||||
if filteroptions['filter_max_total_profit'] is not None:
|
||||
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
epochs = [
|
||||
x for x in epochs
|
||||
if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
|
||||
if x['results_metrics'].get(
|
||||
'profit', x['results_metrics'].get('profit_total_abs', 0)
|
||||
) < filteroptions['filter_max_total_profit']
|
||||
]
|
||||
return epochs
|
||||
|
||||
@ -217,11 +260,11 @@ def _hyperopt_filter_epochs_profit(epochs: List, filteroptions: dict) -> List:
|
||||
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 = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
|
||||
epochs = [x for x in epochs if x['loss'] < filteroptions['filter_min_objective']]
|
||||
if filteroptions['filter_max_objective'] is not None:
|
||||
epochs = [x for x in epochs if x['results_metrics']['trade_count'] > 0]
|
||||
epochs = _hyperopt_filter_epochs_trade(epochs, 0)
|
||||
|
||||
epochs = [x for x in epochs if x['loss'] > filteroptions['filter_max_objective']]
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import csv
|
||||
import logging
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
@ -12,11 +11,11 @@ from tabulate import tabulate
|
||||
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.constants import USERPATH_HYPEROPTS, 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.exchange import market_is_active, validate_exchanges
|
||||
from freqtrade.misc import 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]))
|
||||
@ -99,7 +113,7 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None:
|
||||
|
||||
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 +153,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 +191,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())}.")
|
||||
|
@ -3,8 +3,9 @@ from typing import Any, Dict
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.configuration import setup_utils_configuration
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.misc import round_coin_value
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -22,11 +23,13 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
||||
RunMode.BACKTEST: 'backtesting',
|
||||
RunMode.HYPEROPT: 'hyperoptimization',
|
||||
}
|
||||
if (method in no_unlimited_runmodes.keys() and
|
||||
config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT):
|
||||
raise DependencyException(
|
||||
f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" '
|
||||
f'for {no_unlimited_runmodes[method]}')
|
||||
if method in no_unlimited_runmodes.keys():
|
||||
if (config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT
|
||||
and config['stake_amount'] > config['dry_run_wallet']):
|
||||
wallet = round_coin_value(config['dry_run_wallet'], config['stake_currency'])
|
||||
stake = round_coin_value(config['stake_amount'], config['stake_currency'])
|
||||
raise OperationalException(f"Starting balance ({wallet}) "
|
||||
f"is smaller than stake_amount {stake}.")
|
||||
|
||||
return config
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -1,10 +1,10 @@
|
||||
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__)
|
||||
@ -57,9 +57,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 '
|
||||
|
@ -1,7 +1,7 @@
|
||||
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
|
||||
|
@ -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__)
|
||||
@ -47,6 +47,8 @@ def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]:
|
||||
conf_schema = deepcopy(constants.CONF_SCHEMA)
|
||||
if conf.get('runmode', RunMode.OTHER) in (RunMode.DRY_RUN, RunMode.LIVE):
|
||||
conf_schema['required'] = constants.SCHEMA_TRADE_REQUIRED
|
||||
elif conf.get('runmode', RunMode.OTHER) in (RunMode.BACKTEST, RunMode.HYPEROPT):
|
||||
conf_schema['required'] = constants.SCHEMA_BACKTEST_REQUIRED
|
||||
else:
|
||||
conf_schema['required'] = constants.SCHEMA_MINIMAL_REQUIRED
|
||||
try:
|
||||
@ -72,6 +74,7 @@ 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)
|
||||
@ -93,6 +96,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:
|
||||
@ -133,11 +149,6 @@ 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):
|
||||
raise OperationalException(
|
||||
"Edge requires `use_sell_signal` to be True, otherwise no sells will happen."
|
||||
|
@ -11,11 +11,11 @@ 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.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
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -75,8 +75,6 @@ class Configuration:
|
||||
# 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 +106,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))
|
||||
|
||||
@ -214,9 +214,6 @@ class Configuration:
|
||||
self._args_to_config(
|
||||
config, argname='enable_protections',
|
||||
logstring='Parameter --enable-protections detected, enabling Protections. ...')
|
||||
# Setting max_open_trades to infinite if -1
|
||||
if config.get('max_open_trades') == -1:
|
||||
config['max_open_trades'] = float('inf')
|
||||
|
||||
if 'use_max_market_positions' in self.args and not self.args["use_max_market_positions"]:
|
||||
config.update({'use_max_market_positions': False})
|
||||
@ -228,11 +225,23 @@ class Configuration:
|
||||
'overriding max_open_trades to: %s ...', config.get('max_open_trades'))
|
||||
elif config['runmode'] in NON_UTIL_MODES:
|
||||
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
|
||||
# Setting max_open_trades to infinite if -1
|
||||
if config.get('max_open_trades') == -1:
|
||||
config['max_open_trades'] = float('inf')
|
||||
|
||||
if self.args.get('stake_amount', None):
|
||||
# Convert explicitly to float to support CLI argument for both unlimited and value
|
||||
try:
|
||||
self.args['stake_amount'] = float(self.args['stake_amount'])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self._args_to_config(config, argname='stake_amount',
|
||||
logstring='Parameter --stake-amount detected, '
|
||||
'overriding stake_amount to: {} ...')
|
||||
|
||||
self._args_to_config(config, argname='dry_run_wallet',
|
||||
logstring='Parameter --dry-run-wallet detected, '
|
||||
'overriding dry_run_wallet to: {} ...')
|
||||
self._args_to_config(config, argname='fee',
|
||||
logstring='Parameter --fee detected, '
|
||||
'setting fee to: {} ...')
|
||||
@ -366,6 +375,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: {}')
|
||||
|
||||
@ -390,6 +402,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',
|
||||
@ -436,6 +453,7 @@ class Configuration:
|
||||
"""
|
||||
|
||||
if "pairs" in config:
|
||||
config['exchange']['pair_whitelist'] = config['pairs']
|
||||
return
|
||||
|
||||
if "pairs_file" in self.args and self.args["pairs_file"]:
|
||||
@ -445,8 +463,7 @@ class Configuration:
|
||||
# or if pairs file is specified explicitely
|
||||
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
|
||||
|
||||
@ -457,7 +474,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()
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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}"')
|
||||
|
@ -11,6 +11,7 @@ DEFAULT_EXCHANGE = 'bittrex'
|
||||
PROCESS_THROTTLE_SECS = 5 # sec
|
||||
HYPEROPT_EPOCH = 100 # epochs
|
||||
RETRY_TIMEOUT = 30 # sec
|
||||
TIMEOUT_UNITS = ['minutes', 'seconds']
|
||||
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
|
||||
DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite'
|
||||
UNLIMITED_STAKE_AMOUNT = 'unlimited'
|
||||
@ -26,7 +27,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
|
||||
'AgeFilter', 'PerformanceFilter', 'PrecisionFilter',
|
||||
'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter',
|
||||
'SpreadFilter']
|
||||
'SpreadFilter', 'VolatilityFilter']
|
||||
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
||||
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
|
||||
DRY_RUN_WALLET = 1000
|
||||
@ -54,6 +55,11 @@ DECIMALS_PER_COIN = {
|
||||
'ETH': 5,
|
||||
}
|
||||
|
||||
DUST_PER_COIN = {
|
||||
'BTC': 0.0001,
|
||||
'ETH': 0.01
|
||||
}
|
||||
|
||||
|
||||
# Soure files with destination directories within user-directory
|
||||
USER_DATA_FILES = {
|
||||
@ -91,6 +97,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': {
|
||||
@ -131,7 +138,8 @@ CONF_SCHEMA = {
|
||||
'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': {
|
||||
@ -160,12 +168,18 @@ CONF_SCHEMA = {
|
||||
'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},
|
||||
'sell_profit_offset': {'type': 'number'},
|
||||
'ignore_roi_if_buy_signal': {'type': 'boolean'}
|
||||
}
|
||||
},
|
||||
@ -174,6 +188,8 @@ CONF_SCHEMA = {
|
||||
'properties': {
|
||||
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forcesell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'forcebuy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'emergencysell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
|
||||
'stoploss_on_exchange': {'type': 'boolean'},
|
||||
@ -230,20 +246,37 @@ CONF_SCHEMA = {
|
||||
'enabled': {'type': 'boolean'},
|
||||
'token': {'type': 'string'},
|
||||
'chat_id': {'type': 'string'},
|
||||
'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'
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
'required': ['enabled', 'token', 'chat_id']
|
||||
'required': ['enabled', 'token', 'chat_id'],
|
||||
},
|
||||
'webhook': {
|
||||
'type': 'object',
|
||||
@ -370,6 +403,16 @@ SCHEMA_TRADE_REQUIRED = [
|
||||
'dataformat_trades',
|
||||
]
|
||||
|
||||
SCHEMA_BACKTEST_REQUIRED = [
|
||||
'exchange',
|
||||
'max_open_trades',
|
||||
'stake_currency',
|
||||
'stake_amount',
|
||||
'dry_run_wallet',
|
||||
'dataformat_ohlcv',
|
||||
'dataformat_trades',
|
||||
]
|
||||
|
||||
SCHEMA_MINIMAL_REQUIRED = [
|
||||
'exchange',
|
||||
'dry_run',
|
||||
|
@ -10,7 +10,7 @@ import pandas as pd
|
||||
|
||||
from freqtrade.constants import LAST_BT_RESULT_FN
|
||||
from freqtrade.misc import json_load
|
||||
from freqtrade.persistence import Trade, init_db
|
||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -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)
|
||||
@ -224,7 +226,7 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str,
|
||||
return df_final[df_final['open_trades'] > max_open_trades]
|
||||
|
||||
|
||||
def trade_list_to_dataframe(trades: List[Trade]) -> pd.DataFrame:
|
||||
def trade_list_to_dataframe(trades: List[LocalTrade]) -> pd.DataFrame:
|
||||
"""
|
||||
Convert list of Trade objects to pandas Dataframe
|
||||
:param trades: List of trade objects
|
||||
@ -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
|
||||
@ -360,13 +362,14 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
|
||||
|
||||
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
|
||||
value_col: str = 'profit_ratio'
|
||||
) -> Tuple[float, pd.Timestamp, pd.Timestamp]:
|
||||
) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]:
|
||||
"""
|
||||
Calculate max drawdown and the corresponding close dates
|
||||
:param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
|
||||
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
|
||||
:param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio')
|
||||
:return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time
|
||||
:return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown,
|
||||
high and low time and high and low value.
|
||||
:raise: ValueError if trade-dataframe was found empty.
|
||||
"""
|
||||
if len(trades) == 0:
|
||||
@ -382,4 +385,26 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
|
||||
raise ValueError("No losing trade, therefore no drawdown.")
|
||||
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
||||
low_date = profit_results.loc[idxmin, date_col]
|
||||
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date
|
||||
high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin]
|
||||
['high_value'].idxmax(), 'cumulative']
|
||||
low_val = max_drawdown_df.loc[idxmin, 'cumulative']
|
||||
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val
|
||||
|
||||
|
||||
def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]:
|
||||
"""
|
||||
Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane
|
||||
:param trades: DataFrame containing trades (requires columns close_date and profit_percent)
|
||||
:param starting_balance: Add starting balance to results, to show the wallets high / low points
|
||||
:return: Tuple (float, float) with cumsum of profit_abs
|
||||
:raise: ValueError if trade-dataframe was found empty.
|
||||
"""
|
||||
if len(trades) == 0:
|
||||
raise ValueError("Trade dataframe empty.")
|
||||
|
||||
csum_df = pd.DataFrame()
|
||||
csum_df['sum'] = trades['profit_abs'].cumsum()
|
||||
csum_min = csum_df['sum'].min() + starting_balance
|
||||
csum_max = csum_df['sum'].max() + starting_balance
|
||||
|
||||
return csum_min, csum_max
|
||||
|
@ -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
|
||||
|
@ -12,21 +12,32 @@ from pandas import DataFrame
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
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,40 +56,6 @@ 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
|
||||
@ -111,47 +88,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 +127,89 @@ 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 = {}
|
||||
|
||||
# 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)
|
||||
|
@ -89,7 +89,7 @@ class HDF5DataHandler(IDataHandler):
|
||||
if timerange.starttype == 'date':
|
||||
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
|
||||
if timerange.stoptype == 'date':
|
||||
where.append(f"date < Timestamp({timerange.stopts * 1e9})")
|
||||
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
|
||||
|
||||
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)
|
||||
|
||||
|
@ -155,6 +155,7 @@ def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optiona
|
||||
def _download_pair_history(datadir: Path,
|
||||
exchange: Exchange,
|
||||
pair: str, *,
|
||||
new_pairs_days: int = 30,
|
||||
timeframe: str = '5m',
|
||||
timerange: Optional[TimeRange] = None,
|
||||
data_handler: IDataHandler = None) -> bool:
|
||||
@ -193,7 +194,7 @@ def _download_pair_history(datadir: Path,
|
||||
timeframe=timeframe,
|
||||
since_ms=since_ms if since_ms else
|
||||
int(arrow.utcnow().shift(
|
||||
days=-30).float_timestamp) * 1000
|
||||
days=-new_pairs_days).float_timestamp) * 1000
|
||||
)
|
||||
# TODO: Maybe move parsing to exchange class (?)
|
||||
new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair,
|
||||
@ -223,7 +224,8 @@ def _download_pair_history(datadir: Path,
|
||||
|
||||
def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes: List[str],
|
||||
datadir: Path, timerange: Optional[TimeRange] = None,
|
||||
erase: bool = False, data_format: str = None) -> List[str]:
|
||||
new_pairs_days: int = 30, erase: bool = False,
|
||||
data_format: str = None) -> List[str]:
|
||||
"""
|
||||
Refresh stored ohlcv data for backtesting and hyperopt operations.
|
||||
Used by freqtrade download-data subcommand.
|
||||
@ -246,12 +248,14 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
||||
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
|
||||
_download_pair_history(datadir=datadir, exchange=exchange,
|
||||
pair=pair, timeframe=str(timeframe),
|
||||
new_pairs_days=new_pairs_days,
|
||||
timerange=timerange, data_handler=data_handler)
|
||||
return pairs_not_available
|
||||
|
||||
|
||||
def _download_trades_history(exchange: Exchange,
|
||||
pair: str, *,
|
||||
new_pairs_days: int = 30,
|
||||
timerange: Optional[TimeRange] = None,
|
||||
data_handler: IDataHandler
|
||||
) -> bool:
|
||||
@ -261,9 +265,13 @@ def _download_trades_history(exchange: Exchange,
|
||||
"""
|
||||
try:
|
||||
|
||||
since = timerange.startts * 1000 if \
|
||||
(timerange and timerange.starttype == 'date') else int(arrow.utcnow().shift(
|
||||
days=-30).float_timestamp) * 1000
|
||||
until = None
|
||||
if (timerange and timerange.starttype == 'date'):
|
||||
since = timerange.startts * 1000
|
||||
if timerange.stoptype == 'date':
|
||||
until = timerange.stopts * 1000
|
||||
else:
|
||||
since = int(arrow.utcnow().shift(days=-new_pairs_days).float_timestamp) * 1000
|
||||
|
||||
trades = data_handler.trades_load(pair)
|
||||
|
||||
@ -291,6 +299,7 @@ def _download_trades_history(exchange: Exchange,
|
||||
# Default since_ms to 30 days if nothing is given
|
||||
new_trades = exchange.get_historic_trades(pair=pair,
|
||||
since=since,
|
||||
until=until,
|
||||
from_id=from_id,
|
||||
)
|
||||
trades.extend(new_trades[1])
|
||||
@ -311,8 +320,8 @@ def _download_trades_history(exchange: Exchange,
|
||||
|
||||
|
||||
def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: Path,
|
||||
timerange: TimeRange, erase: bool = False,
|
||||
data_format: str = 'jsongz') -> List[str]:
|
||||
timerange: TimeRange, new_pairs_days: int = 30,
|
||||
erase: bool = False, data_format: str = 'jsongz') -> List[str]:
|
||||
"""
|
||||
Refresh stored trades data for backtesting and hyperopt operations.
|
||||
Used by freqtrade download-data subcommand.
|
||||
@ -333,6 +342,7 @@ def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir:
|
||||
logger.info(f'Downloading trades for pair {pair}.')
|
||||
_download_trades_history(exchange=exchange,
|
||||
pair=pair,
|
||||
new_pairs_days=new_pairs_days,
|
||||
timerange=timerange,
|
||||
data_handler=data_handler)
|
||||
return pairs_not_available
|
||||
@ -362,7 +372,7 @@ def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str],
|
||||
logger.exception(f'Could not convert {pair} to OHLCV.')
|
||||
|
||||
|
||||
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
|
||||
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[datetime, datetime]:
|
||||
"""
|
||||
Get the maximum common timerange for the given backtest data.
|
||||
|
||||
@ -370,7 +380,7 @@ def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]
|
||||
:return: tuple containing min_date, max_date
|
||||
"""
|
||||
timeranges = [
|
||||
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
|
||||
(frame['date'].min().to_pydatetime(), frame['date'].max().to_pydatetime())
|
||||
for frame in data.values()
|
||||
]
|
||||
return (min(timeranges, key=operator.itemgetter(0))[0],
|
||||
|
@ -1,6 +1,8 @@
|
||||
# pragma pylint: disable=W0603
|
||||
""" Edge positioning package """
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from typing import Any, Dict, List, NamedTuple
|
||||
|
||||
import arrow
|
||||
@ -11,9 +13,11 @@ from pandas import DataFrame
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT
|
||||
from freqtrade.data.history import get_timerange, load_data, refresh_data
|
||||
from freqtrade.enums import RunMode, SellType
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.exchange.exchange import timeframe_to_seconds
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -45,7 +49,7 @@ class Edge:
|
||||
|
||||
self.config = config
|
||||
self.exchange = exchange
|
||||
self.strategy = strategy
|
||||
self.strategy: IStrategy = strategy
|
||||
|
||||
self.edge_config = self.config.get('edge', {})
|
||||
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
|
||||
@ -81,12 +85,16 @@ class Edge:
|
||||
if config.get('fee'):
|
||||
self.fee = config['fee']
|
||||
else:
|
||||
try:
|
||||
self.fee = self.exchange.get_fee(symbol=expand_pairlist(
|
||||
self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0])
|
||||
except IndexError:
|
||||
self.fee = None
|
||||
|
||||
def calculate(self, pairs: List[str]) -> bool:
|
||||
if self.fee is None and pairs:
|
||||
self.fee = self.exchange.get_fee(pairs[0])
|
||||
|
||||
def calculate(self) -> bool:
|
||||
pairs = expand_pairlist(self.config['exchange']['pair_whitelist'],
|
||||
list(self.exchange.markets))
|
||||
heartbeat = self.edge_config.get('process_throttle_secs')
|
||||
|
||||
if (self._last_updated > 0) and (
|
||||
@ -98,12 +106,32 @@ class Edge:
|
||||
logger.info('Using local backtesting data (using whitelist in given config) ...')
|
||||
|
||||
if self._refresh_pairs:
|
||||
timerange_startup = deepcopy(self._timerange)
|
||||
timerange_startup.subtract_start(timeframe_to_seconds(
|
||||
self.strategy.timeframe) * self.strategy.startup_candle_count)
|
||||
refresh_data(
|
||||
datadir=self.config['datadir'],
|
||||
pairs=pairs,
|
||||
exchange=self.exchange,
|
||||
timeframe=self.strategy.timeframe,
|
||||
timerange=self._timerange,
|
||||
timerange=timerange_startup,
|
||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||
)
|
||||
# Download informative pairs too
|
||||
res = defaultdict(list)
|
||||
for p, t in self.strategy.informative_pairs():
|
||||
res[t].append(p)
|
||||
for timeframe, inf_pairs in res.items():
|
||||
timerange_startup = deepcopy(self._timerange)
|
||||
timerange_startup.subtract_start(timeframe_to_seconds(
|
||||
timeframe) * self.strategy.startup_candle_count)
|
||||
refresh_data(
|
||||
datadir=self.config['datadir'],
|
||||
pairs=inf_pairs,
|
||||
exchange=self.exchange,
|
||||
timeframe=timeframe,
|
||||
timerange=timerange_startup,
|
||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||
)
|
||||
|
||||
data = load_data(
|
||||
@ -120,8 +148,11 @@ class Edge:
|
||||
self._cached_pairs = {}
|
||||
logger.critical("No data found. Edge is stopped ...")
|
||||
return False
|
||||
|
||||
# Fake run-mode to Edge
|
||||
prior_rm = self.config['runmode']
|
||||
self.config['runmode'] = RunMode.EDGE
|
||||
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
|
||||
self.config['runmode'] = prior_rm
|
||||
|
||||
# Print timeframe
|
||||
min_date, max_date = get_timerange(preprocessed)
|
||||
@ -178,7 +209,7 @@ class Edge:
|
||||
if pair in self._cached_pairs:
|
||||
return self._cached_pairs[pair].stoploss
|
||||
else:
|
||||
logger.warning('tried to access stoploss of a non-existing pair, '
|
||||
logger.warning(f'Tried to access stoploss of non-existing pair {pair}, '
|
||||
'strategy stoploss is returned instead.')
|
||||
return self.strategy.stoploss
|
||||
|
||||
@ -209,7 +240,7 @@ class Edge:
|
||||
|
||||
return self._final_pairs
|
||||
|
||||
def accepted_pairs(self) -> list:
|
||||
def accepted_pairs(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
return a list of accepted pairs along with their winrate, expectancy and stoploss
|
||||
"""
|
||||
|
6
freqtrade/enums/__init__.py
Normal file
6
freqtrade/enums/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
# flake8: noqa: F401
|
||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
||||
from freqtrade.enums.selltype import SellType
|
||||
from freqtrade.enums.signaltype import SignalType
|
||||
from freqtrade.enums.state import State
|
19
freqtrade/enums/rpcmessagetype.py
Normal file
19
freqtrade/enums/rpcmessagetype.py
Normal file
@ -0,0 +1,19 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class RPCMessageType(Enum):
|
||||
STATUS = 'status'
|
||||
WARNING = 'warning'
|
||||
STARTUP = 'startup'
|
||||
BUY = 'buy'
|
||||
BUY_FILL = 'buy_fill'
|
||||
BUY_CANCEL = 'buy_cancel'
|
||||
SELL = 'sell'
|
||||
SELL_FILL = 'sell_fill'
|
||||
SELL_CANCEL = 'sell_cancel'
|
||||
|
||||
def __repr__(self):
|
||||
return self.value
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
@ -1,23 +1,6 @@
|
||||
# pragma pylint: disable=too-few-public-methods
|
||||
|
||||
"""
|
||||
Bot state constant
|
||||
"""
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class State(Enum):
|
||||
"""
|
||||
Bot application states
|
||||
"""
|
||||
RUNNING = 1
|
||||
STOPPED = 2
|
||||
RELOAD_CONFIG = 3
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name.lower()}"
|
||||
|
||||
|
||||
class RunMode(Enum):
|
||||
"""
|
||||
Bot running mode (backtest, hyperopt, ...)
|
20
freqtrade/enums/selltype.py
Normal file
20
freqtrade/enums/selltype.py
Normal file
@ -0,0 +1,20 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class SellType(Enum):
|
||||
"""
|
||||
Enum to distinguish between sell reasons
|
||||
"""
|
||||
ROI = "roi"
|
||||
STOP_LOSS = "stop_loss"
|
||||
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
|
||||
TRAILING_STOP_LOSS = "trailing_stop_loss"
|
||||
SELL_SIGNAL = "sell_signal"
|
||||
FORCE_SELL = "force_sell"
|
||||
EMERGENCY_SELL = "emergency_sell"
|
||||
CUSTOM_SELL = "custom_sell"
|
||||
NONE = ""
|
||||
|
||||
def __str__(self):
|
||||
# explicitly convert to String to help with exporting data.
|
||||
return self.value
|
9
freqtrade/enums/signaltype.py
Normal file
9
freqtrade/enums/signaltype.py
Normal file
@ -0,0 +1,9 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class SignalType(Enum):
|
||||
"""
|
||||
Enum to distinguish between buy and sell signals
|
||||
"""
|
||||
BUY = "buy"
|
||||
SELL = "sell"
|
13
freqtrade/enums/state.py
Normal file
13
freqtrade/enums/state.py
Normal file
@ -0,0 +1,13 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class State(Enum):
|
||||
"""
|
||||
Bot application states
|
||||
"""
|
||||
RUNNING = 1
|
||||
STOPPED = 2
|
||||
RELOAD_CONFIG = 3
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name.lower()}"
|
@ -7,11 +7,14 @@ from freqtrade.exchange.bibox import Bibox
|
||||
from freqtrade.exchange.binance import Binance
|
||||
from freqtrade.exchange.bittrex import Bittrex
|
||||
from freqtrade.exchange.bybit import Bybit
|
||||
from freqtrade.exchange.coinbasepro import Coinbasepro
|
||||
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
|
||||
get_exchange_bad_reason, is_exchange_bad,
|
||||
is_exchange_known_ccxt, is_exchange_officially_supported,
|
||||
market_is_active, timeframe_to_minutes, timeframe_to_msecs,
|
||||
timeframe_to_next_date, timeframe_to_prev_date,
|
||||
timeframe_to_seconds)
|
||||
timeframe_to_seconds, validate_exchange,
|
||||
validate_exchanges)
|
||||
from freqtrade.exchange.ftx import Ftx
|
||||
from freqtrade.exchange.hitbtc import Hitbtc
|
||||
from freqtrade.exchange.kraken import Kraken
|
||||
from freqtrade.exchange.kucoin import Kucoin
|
||||
|
@ -52,7 +52,7 @@ class Binance(Exchange):
|
||||
'In stoploss limit order, stop price should be more than limit price')
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.dry_run_order(
|
||||
dry_order = self.create_dry_run_order(
|
||||
pair, ordertype, "sell", amount, stop_price)
|
||||
return dry_order
|
||||
|
||||
|
@ -12,12 +12,14 @@ class Bittrex(Exchange):
|
||||
"""
|
||||
Bittrex exchange class. Contains adjustments needed for Freqtrade to work
|
||||
with this exchange.
|
||||
|
||||
Please note that this exchange is not included in the list of exchanges
|
||||
officially supported by the Freqtrade development team. So some features
|
||||
may still not work as expected.
|
||||
"""
|
||||
|
||||
_ft_has: Dict = {
|
||||
"ohlcv_candle_limit_per_timeframe": {
|
||||
'1m': 1440,
|
||||
'5m': 288,
|
||||
'1h': 744,
|
||||
'1d': 365,
|
||||
},
|
||||
"l2_limit_range": [1, 25, 500],
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ class Bybit(Exchange):
|
||||
may still not work as expected.
|
||||
"""
|
||||
|
||||
# fetchCurrencies API point requires authentication for Bybit,
|
||||
_ft_has: Dict = {
|
||||
"ohlcv_candle_limit": 200,
|
||||
}
|
||||
|
23
freqtrade/exchange/coinbasepro.py
Normal file
23
freqtrade/exchange/coinbasepro.py
Normal file
@ -0,0 +1,23 @@
|
||||
""" CoinbasePro exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from freqtrade.exchange import Exchange
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Coinbasepro(Exchange):
|
||||
"""
|
||||
CoinbasePro exchange class. Contains adjustments needed for Freqtrade to work
|
||||
with this exchange.
|
||||
|
||||
Please note that this exchange is not included in the list of exchanges
|
||||
officially supported by the Freqtrade development team. So some features
|
||||
may still not work as expected.
|
||||
"""
|
||||
|
||||
_ft_has: Dict = {
|
||||
"ohlcv_candle_limit": 300,
|
||||
}
|
@ -18,78 +18,8 @@ BAD_EXCHANGES = {
|
||||
"bitmex": "Various reasons.",
|
||||
"bitstamp": "Does not provide history. "
|
||||
"Details in https://github.com/freqtrade/freqtrade/issues/1983",
|
||||
"hitbtc": "This API cannot be used with Freqtrade. "
|
||||
"Use `hitbtc2` exchange id to access this exchange.",
|
||||
"phemex": "Does not provide history. ",
|
||||
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
|
||||
**dict.fromkeys([
|
||||
'adara',
|
||||
'anxpro',
|
||||
'bigone',
|
||||
'coinbase',
|
||||
'coinexchange',
|
||||
'coinmarketcap',
|
||||
'lykke',
|
||||
'xbtce',
|
||||
], "Does not provide timeframes. ccxt fetchOHLCV: False"),
|
||||
**dict.fromkeys([
|
||||
'bcex',
|
||||
'bit2c',
|
||||
'bitbay',
|
||||
'bitflyer',
|
||||
'bitforex',
|
||||
'bithumb',
|
||||
'bitso',
|
||||
'bitstamp1',
|
||||
'bl3p',
|
||||
'braziliex',
|
||||
'btcbox',
|
||||
'btcchina',
|
||||
'btctradeim',
|
||||
'btctradeua',
|
||||
'bxinth',
|
||||
'chilebit',
|
||||
'coincheck',
|
||||
'coinegg',
|
||||
'coinfalcon',
|
||||
'coinfloor',
|
||||
'coingi',
|
||||
'coinmate',
|
||||
'coinone',
|
||||
'coinspot',
|
||||
'coolcoin',
|
||||
'crypton',
|
||||
'deribit',
|
||||
'exmo',
|
||||
'exx',
|
||||
'flowbtc',
|
||||
'foxbit',
|
||||
'fybse',
|
||||
# 'hitbtc',
|
||||
'ice3x',
|
||||
'independentreserve',
|
||||
'indodax',
|
||||
'itbit',
|
||||
'lakebtc',
|
||||
'latoken',
|
||||
'liquid',
|
||||
'livecoin',
|
||||
'luno',
|
||||
'mixcoins',
|
||||
'negociecoins',
|
||||
'nova',
|
||||
'paymium',
|
||||
'southxchange',
|
||||
'stronghold',
|
||||
'surbitcoin',
|
||||
'therock',
|
||||
'tidex',
|
||||
'vaultoro',
|
||||
'vbtc',
|
||||
'virwox',
|
||||
'yobit',
|
||||
'zaif',
|
||||
], "Does not provide timeframes. ccxt fetchOHLCV: emulated"),
|
||||
}
|
||||
|
||||
MAP_EXCHANGE_CHILDCLASS = {
|
||||
@ -98,6 +28,29 @@ MAP_EXCHANGE_CHILDCLASS = {
|
||||
}
|
||||
|
||||
|
||||
EXCHANGE_HAS_REQUIRED = [
|
||||
# Required / private
|
||||
'fetchOrder',
|
||||
'cancelOrder',
|
||||
'createOrder',
|
||||
# 'createLimitOrder', 'createMarketOrder',
|
||||
'fetchBalance',
|
||||
|
||||
# Public endpoints
|
||||
'loadMarkets',
|
||||
'fetchOHLCV',
|
||||
]
|
||||
|
||||
EXCHANGE_HAS_OPTIONAL = [
|
||||
# Private
|
||||
'fetchMyTrades', # Trades for order - fee detection
|
||||
# Public
|
||||
'fetchOrderBook', 'fetchL2OrderBook', 'fetchTicker', # OR for pricing
|
||||
'fetchTickers', # For volumepairlist?
|
||||
'fetchTrades', # Downloading trades data
|
||||
]
|
||||
|
||||
|
||||
def calculate_backoff(retrycount, max_retries):
|
||||
"""
|
||||
Calculate backoff
|
||||
@ -140,7 +93,7 @@ def retrier(_func=None, retries=API_RETRY_COUNT):
|
||||
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
||||
count -= 1
|
||||
kwargs.update({'count': count})
|
||||
if isinstance(ex, DDosProtection) or isinstance(ex, RetryableOrderError):
|
||||
if isinstance(ex, (DDosProtection, RetryableOrderError)):
|
||||
# increasing backoff
|
||||
backoff_delay = calculate_backoff(count + 1, retries)
|
||||
logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user