mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-02 18:13:04 +00:00
Merge pull request #12182 from freqtrade/new_release
New release 2025.8
This commit is contained in:
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -29,6 +29,10 @@ updates:
|
||||
mkdocs:
|
||||
patterns:
|
||||
- "mkdocs*"
|
||||
scipy:
|
||||
patterns:
|
||||
- "scipy"
|
||||
- "scipy-stubs"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment:
|
||||
name: develop
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
91
.github/workflows/ci.yml
vendored
91
.github/workflows/ci.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
with:
|
||||
activate-environment: true
|
||||
enable-cache: true
|
||||
@@ -47,24 +47,9 @@ jobs:
|
||||
cache-suffix: "${{ matrix.python-version }}"
|
||||
prune-cache: false
|
||||
|
||||
- name: Cache_dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: ~/dependencies/
|
||||
key: ${{ runner.os }}-dependencies
|
||||
|
||||
- name: TA binary *nix
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
|
||||
|
||||
- name: Installation - *nix
|
||||
run: |
|
||||
uv pip install --upgrade wheel
|
||||
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
|
||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||
uv pip install -r requirements-dev.txt
|
||||
uv pip install -e ft_client/
|
||||
uv pip install -e .
|
||||
@@ -163,7 +148,7 @@ jobs:
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -174,7 +159,7 @@ jobs:
|
||||
check-latest: true
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
with:
|
||||
activate-environment: true
|
||||
enable-cache: true
|
||||
@@ -183,18 +168,6 @@ jobs:
|
||||
cache-suffix: "${{ matrix.python-version }}"
|
||||
prune-cache: false
|
||||
|
||||
- name: Cache_dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: ~/dependencies/
|
||||
key: ${{ matrix.os }}-dependencies
|
||||
|
||||
- name: TA binary *nix
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
|
||||
|
||||
- name: Installation - macOS (Brew)
|
||||
run: |
|
||||
# brew update
|
||||
@@ -222,9 +195,6 @@ jobs:
|
||||
- name: Installation (python)
|
||||
run: |
|
||||
uv pip install wheel
|
||||
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
|
||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||
uv pip install -r requirements-dev.txt
|
||||
uv pip install -e ft_client/
|
||||
uv pip install -e .
|
||||
@@ -287,11 +257,11 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ windows-latest ]
|
||||
os: [ "windows-2022", "windows-2025" ]
|
||||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -301,7 +271,7 @@ jobs:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
with:
|
||||
activate-environment: true
|
||||
enable-cache: true
|
||||
@@ -315,7 +285,9 @@ jobs:
|
||||
function uvpipFunction { uv pip $args }
|
||||
Set-Alias -name pip -value uvpipFunction
|
||||
|
||||
./build_helpers/install_windows.ps1
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -e .
|
||||
|
||||
- name: Tests
|
||||
run: |
|
||||
@@ -379,7 +351,7 @@ jobs:
|
||||
mypy-version-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -396,7 +368,7 @@ jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -408,7 +380,7 @@ jobs:
|
||||
docs-check:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -439,7 +411,7 @@ jobs:
|
||||
# Run pytest with "live" checks
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -449,7 +421,7 @@ jobs:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
with:
|
||||
activate-environment: true
|
||||
enable-cache: true
|
||||
@@ -458,25 +430,9 @@ jobs:
|
||||
cache-suffix: "3.12"
|
||||
prune-cache: false
|
||||
|
||||
- name: Cache_dependencies
|
||||
uses: actions/cache@v4
|
||||
id: cache
|
||||
with:
|
||||
path: ~/dependencies/
|
||||
key: ${{ runner.os }}-dependencies
|
||||
|
||||
|
||||
- name: TA binary *nix
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
|
||||
|
||||
- name: Installation - *nix
|
||||
run: |
|
||||
uv pip install --upgrade wheel
|
||||
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
|
||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||
uv pip install -r requirements-dev.txt
|
||||
uv pip install -e ft_client/
|
||||
uv pip install -e .
|
||||
@@ -508,15 +464,16 @@ jobs:
|
||||
|
||||
- name: Check user permission
|
||||
id: check
|
||||
uses: scherermichael-oss/action-has-permission@136e061bfe093832d87f090dd768e14e27a740d3 # 1.0.6
|
||||
continue-on-error: true
|
||||
uses: prince-chrismc/check-actor-permissions-action@d504e74ba31658f4cdf4fcfeb509d4c09736d88e # v3.0.2
|
||||
with:
|
||||
required-permission: write
|
||||
permission: "write"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Discord notification
|
||||
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
|
||||
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
if: steps.check.outputs.permitted == 'true' && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||
with:
|
||||
severity: info
|
||||
details: Test Completed!
|
||||
@@ -528,7 +485,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -575,12 +532,12 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download artifact 📦
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: freqtrade*-build
|
||||
path: dist
|
||||
@@ -604,12 +561,12 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download artifact 📦
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: freqtrade*-build
|
||||
path: dist
|
||||
|
||||
2
.github/workflows/deploy-docs.yml
vendored
2
.github/workflows/deploy-docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
name: Deploy Docs through mike
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: true
|
||||
|
||||
|
||||
4
.github/workflows/devcontainer-build.yml
vendored
4
.github/workflows/devcontainer-build.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
7
.github/workflows/docker-build.yml
vendored
7
.github/workflows/docker-build.yml
vendored
@@ -22,11 +22,12 @@ permissions:
|
||||
|
||||
jobs:
|
||||
deploy-docker:
|
||||
name: "Deploy Docker x64 and armv7l"
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository == 'freqtrade/freqtrade'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -74,7 +75,7 @@ jobs:
|
||||
build_helpers/publish_docker_multi.sh
|
||||
|
||||
deploy-arm:
|
||||
name: "Deploy Docker"
|
||||
name: "Deploy Docker ARM64"
|
||||
permissions:
|
||||
packages: write
|
||||
needs: [ deploy-docker ]
|
||||
@@ -83,7 +84,7 @@ jobs:
|
||||
if: github.repository == 'freqtrade/freqtrade'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/docker-update-readme.yml
vendored
2
.github/workflows/docker-update-readme.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
2
.github/workflows/pre-commit-update.yml
vendored
2
.github/workflows/pre-commit-update.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
auto-update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
@@ -21,9 +21,9 @@ jobs:
|
||||
# actions: read # only needed for private repos
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1
|
||||
uses: zizmorcore/zizmor-action@5ca5fc7a4779c5263a3ffa0e1f693009994446d1 # v0.1.2
|
||||
|
||||
@@ -21,18 +21,18 @@ repos:
|
||||
# stages: [push]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: "v1.17.0"
|
||||
rev: "v1.17.1"
|
||||
hooks:
|
||||
- id: mypy
|
||||
exclude: build_helpers
|
||||
additional_dependencies:
|
||||
- types-cachetools==6.1.0.20250717
|
||||
- types-filelock==3.2.7
|
||||
- types-requests==2.32.4.20250611
|
||||
- types-requests==2.32.4.20250809
|
||||
- types-tabulate==0.9.0.20241207
|
||||
- types-python-dateutil==2.9.0.20250708
|
||||
- scipy-stubs==1.16.0.2
|
||||
- SQLAlchemy==2.0.41
|
||||
- types-python-dateutil==2.9.0.20250822
|
||||
- scipy-stubs==1.16.1.1
|
||||
- SQLAlchemy==2.0.43
|
||||
# stages: [push]
|
||||
|
||||
- repo: https://github.com/pycqa/isort
|
||||
@@ -44,13 +44,13 @@ repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.12.5'
|
||||
rev: 'v0.12.10'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
exclude: |
|
||||
@@ -83,6 +83,6 @@ repos:
|
||||
|
||||
# Ensure github actions remain safe
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.11.0
|
||||
rev: v1.12.1
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM python:3.13.5-slim-bookworm AS base
|
||||
FROM python:3.13.7-slim-bookworm AS base
|
||||
|
||||
# Setup env
|
||||
ENV LANG=C.UTF-8
|
||||
@@ -27,11 +27,6 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& pip install --upgrade pip wheel
|
||||
|
||||
# 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
|
||||
@@ -49,7 +44,7 @@ USER ftuser
|
||||
# Install and execute
|
||||
COPY --chown=ftuser:ftuser . /freqtrade/
|
||||
|
||||
RUN pip install -e . --user --no-cache-dir --no-build-isolation \
|
||||
RUN pip install -e . --user --no-cache-dir \
|
||||
&& mkdir /freqtrade/user_data/ \
|
||||
&& freqtrade install-ui
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[](https://doi.org/10.21105/joss.04864)
|
||||
[](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
||||
[](https://www.freqtrade.io)
|
||||
[](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
||||
|
||||
Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram or webUI. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning.
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
if [ -z "$1" ]; then
|
||||
INSTALL_LOC=/usr/local
|
||||
else
|
||||
INSTALL_LOC=${1}
|
||||
fi
|
||||
echo "Installing to ${INSTALL_LOC}"
|
||||
if [ -n "$2" ] || [ ! -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 \
|
||||
&& echo "Downloading gcc config.guess and config.sub" \
|
||||
&& curl -s 'https://raw.githubusercontent.com/gcc-mirror/gcc/master/config.guess' -o config.guess \
|
||||
&& curl -s 'https://raw.githubusercontent.com/gcc-mirror/gcc/master/config.sub' -o config.sub \
|
||||
&& ./configure --prefix=${INSTALL_LOC}/ \
|
||||
&& make
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed building ta-lib."
|
||||
cd .. && rm -rf ./ta-lib/
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$2" ]; then
|
||||
which sudo && sudo make install || make install
|
||||
if [ -x "$(command -v apt-get)" ]; then
|
||||
echo "Updating library path using ldconfig"
|
||||
sudo ldconfig
|
||||
fi
|
||||
else
|
||||
# Don't install with sudo
|
||||
make install
|
||||
fi
|
||||
|
||||
cd .. && rm -rf ./ta-lib/
|
||||
else
|
||||
echo "TA-lib already installed, skipping installation"
|
||||
fi
|
||||
@@ -1,10 +0,0 @@
|
||||
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
|
||||
|
||||
python -m pip install --upgrade pip
|
||||
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
|
||||
|
||||
pip install -U wheel "numpy<3.0"
|
||||
pip install --only-binary ta-lib --find-links=build_helpers\ "ta-lib<0.6.0"
|
||||
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -e .
|
||||
@@ -180,6 +180,16 @@
|
||||
"description": "Offset for profit exit. \nUsually specified in the strategy and missing in the configuration.",
|
||||
"type": "number"
|
||||
},
|
||||
"recursive_strategy_search": {
|
||||
"description": "Enable recursive strategy search.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"user_data_dir": {
|
||||
"description": "Path to the user data directory."
|
||||
},
|
||||
"datadir": {
|
||||
"description": "Path to the data directory."
|
||||
},
|
||||
"fee": {
|
||||
"description": "Trading fee percentage. Can help to simulate slippage in backtesting",
|
||||
"type": "number",
|
||||
@@ -562,6 +572,7 @@
|
||||
"pairlists": {
|
||||
"description": "Configuration for pairlists.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build_helpers/ta_lib-0.6.5-cp311-cp311-manylinux_2_31_armv7l.whl
Normal file
BIN
build_helpers/ta_lib-0.6.5-cp311-cp311-manylinux_2_31_armv7l.whl
Normal file
Binary file not shown.
@@ -34,7 +34,7 @@ COPY build_helpers/* /tmp/
|
||||
# Install dependencies
|
||||
COPY --chown=ftuser:ftuser requirements.txt /freqtrade/
|
||||
USER ftuser
|
||||
RUN pip install --user --no-cache-dir "numpy<3.0" \
|
||||
RUN pip install --user --prefer-binary --no-cache-dir "numpy<3.0" build \
|
||||
&& pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib \
|
||||
&& pip install --user --no-cache-dir -r requirements.txt
|
||||
|
||||
@@ -49,7 +49,7 @@ USER ftuser
|
||||
# Install and execute
|
||||
COPY --chown=ftuser:ftuser . /freqtrade/
|
||||
|
||||
RUN pip install -e . --user --no-cache-dir --no-build-isolation\
|
||||
RUN pip install -e . --user --no-cache-dir \
|
||||
&& mkdir /freqtrade/user_data/ \
|
||||
&& freqtrade install-ui
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM freqtradeorg/freqtrade:develop
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements-dev.txt /freqtrade/
|
||||
|
||||
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 []
|
||||
@@ -46,29 +46,32 @@ ranging from the simplest (0) to the most detailed per pair, per buy and per sel
|
||||
|
||||
More options are available by running with the `-h` option.
|
||||
|
||||
### Using export-filename
|
||||
### Using backtest-filename
|
||||
|
||||
Normally, `backtesting-analysis` uses the latest backtest results, but if you wanted to go
|
||||
back to a previous backtest output, you need to supply the `--export-filename` option.
|
||||
You can supply the same parameter to `backtest-analysis` with the name of the final backtest
|
||||
output file. This allows you to keep historical versions of backtest results and re-analyse
|
||||
them at a later date:
|
||||
By default, `backtesting-analysis` processes the most recent backtest results in the `user_data/backtest_results` directory.
|
||||
If you want to analyze results from an earlier backtest, use the `--backtest-filename` option to specify the desired file. This lets you revisit and re-analyze historical backtest outputs at any time by providing the filename of the relevant backtest result:
|
||||
|
||||
``` bash
|
||||
freqtrade backtesting -c <config.json> --timeframe <tf> --strategy <strategy_name> --timerange=<timerange> --export=signals --export-filename=/tmp/mystrat_backtest.json
|
||||
freqtrade backtesting-analysis -c <config.json> --timeframe <tf> --strategy <strategy_name> --timerange <timerange> --export signals --backtest-filename backtest-result-2025-03-05_20-38-34.zip
|
||||
```
|
||||
|
||||
You should see some output similar to below in the logs with the name of the timestamped
|
||||
filename that was exported:
|
||||
|
||||
```
|
||||
2022-06-14 16:28:32,698 - freqtrade.misc - INFO - dumping json to "/tmp/mystrat_backtest-2022-06-14_16-28-32.json"
|
||||
2022-06-14 16:28:32,698 - freqtrade.misc - INFO - dumping json to "mystrat_backtest-2022-06-14_16-28-32.json"
|
||||
```
|
||||
|
||||
You can then use that filename in `backtesting-analysis`:
|
||||
|
||||
```
|
||||
freqtrade backtesting-analysis -c <config.json> --export-filename=/tmp/mystrat_backtest-2022-06-14_16-28-32.json
|
||||
freqtrade backtesting-analysis -c <config.json> --backtest-filename=mystrat_backtest-2022-06-14_16-28-32.json
|
||||
```
|
||||
|
||||
To use a result from a different results directory, you can use `--backtest-directory` to specify the directory
|
||||
|
||||
``` bash
|
||||
freqtrade backtesting-analysis -c <config.json> --backtest-directory custom_results/ --backtest-filename mystrat_backtest-2022-06-14_16-28-32.json
|
||||
```
|
||||
|
||||
### Tuning the buy tags and sell tags to display
|
||||
|
||||
@@ -105,12 +105,14 @@ Only use this if you're sure you'll not want to plot or analyze your results fur
|
||||
|
||||
---
|
||||
|
||||
Exporting trades to file specifying a custom filename
|
||||
Exporting trades to file specifying a custom directory
|
||||
|
||||
```bash
|
||||
freqtrade backtesting --strategy backtesting --export trades --export-filename=backtest_samplestrategy.json
|
||||
freqtrade backtesting --strategy backtesting --export trades --backtest-directory=user_data/custom-backtest-results
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Please also read about the [strategy startup period](strategy-customization.md#strategy-startup-period).
|
||||
|
||||
---
|
||||
@@ -158,117 +160,136 @@ The most important in the backtesting is to understand the result.
|
||||
A backtesting result will look like that:
|
||||
|
||||
```
|
||||
================================================ BACKTESTING REPORT =================================================
|
||||
| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|
||||
|----------+--------+----------------+------------------+----------------+--------------+--------------------------|
|
||||
| ADA/BTC | 35 | -0.11 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
|
||||
| ARK/BTC | 11 | -0.41 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
|
||||
| BTS/BTC | 32 | 0.31 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
|
||||
| DASH/BTC | 13 | -0.08 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
|
||||
| ENG/BTC | 18 | 1.36 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
|
||||
| EOS/BTC | 36 | 0.08 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
|
||||
| ETC/BTC | 26 | 0.37 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
|
||||
| ETH/BTC | 33 | 0.30 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
|
||||
| IOTA/BTC | 32 | 0.03 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
|
||||
| LSK/BTC | 15 | 1.75 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
|
||||
| LTC/BTC | 32 | -0.04 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
|
||||
| NANO/BTC | 17 | 1.26 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
|
||||
| NEO/BTC | 23 | 0.82 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
|
||||
| REQ/BTC | 9 | 1.17 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
|
||||
| XLM/BTC | 16 | 1.22 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
|
||||
| XMR/BTC | 23 | -0.18 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
|
||||
| XRP/BTC | 35 | 0.66 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||
| ZEC/BTC | 22 | -0.46 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
||||
| TOTAL | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||
============================================= LEFT OPEN TRADES REPORT =============================================
|
||||
| Pair | Trades | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||
|----------+---------+----------------+------------------+----------------+----------------+---------------------|
|
||||
| ADA/BTC | 1 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
||||
| LTC/BTC | 1 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
||||
| TOTAL | 2 | 0.78 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
||||
==================== EXIT REASON STATS ====================
|
||||
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||
|--------------------+---------+-------+--------+---------|
|
||||
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||
| stop_loss | 166 | 0 | 0 | 166 |
|
||||
| exit_signal | 56 | 36 | 0 | 20 |
|
||||
| force_exit | 2 | 0 | 0 | 2 |
|
||||
BACKTESTING REPORT
|
||||
┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ LTC/USDT:USDT │ 16 │ 1.0 │ 56.176 │ 5.62 │ 16:16:00 │ 16 0 0 100 │
|
||||
│ ETC/USDT:USDT │ 12 │ 0.72 │ 30.936 │ 3.09 │ 9:55:00 │ 11 0 1 91.7 │
|
||||
│ ETH/USDT:USDT │ 8 │ 0.66 │ 17.864 │ 1.79 │ 1 day, 13:55:00 │ 7 0 1 87.5 │
|
||||
│ XLM/USDT:USDT │ 10 │ 0.31 │ 11.054 │ 1.11 │ 12:08:00 │ 9 0 1 90.0 │
|
||||
│ BTC/USDT:USDT │ 8 │ 0.21 │ 7.289 │ 0.73 │ 3 days, 1:24:00 │ 6 0 2 75.0 │
|
||||
│ XRP/USDT:USDT │ 9 │ -0.14 │ -7.261 │ -0.73 │ 21:18:00 │ 8 0 1 88.9 │
|
||||
│ DOT/USDT:USDT │ 6 │ -0.4 │ -9.187 │ -0.92 │ 5:35:00 │ 4 0 2 66.7 │
|
||||
│ ADA/USDT:USDT │ 8 │ -1.76 │ -52.098 │ -5.21 │ 11:38:00 │ 6 0 2 75.0 │
|
||||
│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
└───────────────┴────────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘
|
||||
LEFT OPEN TRADES REPORT
|
||||
┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ BTC/USDT:USDT │ 1 │ -4.14 │ -9.930 │ -0.99 │ 17 days, 8:00:00 │ 0 0 1 0 │
|
||||
│ ETC/USDT:USDT │ 1 │ -4.24 │ -15.365 │ -1.54 │ 10:40:00 │ 0 0 1 0 │
|
||||
│ DOT/USDT:USDT │ 1 │ -5.29 │ -19.125 │ -1.91 │ 11:30:00 │ 0 0 1 0 │
|
||||
│ TOTAL │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │
|
||||
└───────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┘
|
||||
ENTER TAG STATS
|
||||
┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Enter Tag ┃ Entries ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ OTHER │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
└───────────┴─────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┘
|
||||
EXIT REASON STATS
|
||||
┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Exit Reason ┃ Exits ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ roi │ 67 │ 1.05 │ 242.179 │ 24.22 │ 15:49:00 │ 67 0 0 100 │
|
||||
│ exit_signal │ 4 │ -2.23 │ -31.217 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │
|
||||
│ force_exit │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │
|
||||
│ stop_loss │ 3 │ -10.14 │ -111.768 │ -11.18 │ 1 day, 3:05:00 │ 0 0 3 0 │
|
||||
│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
└─────────────┴───────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘
|
||||
MIXED TAG STATS
|
||||
┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Enter Tag ┃ Exit Reason ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ │ roi │ 67 │ 1.05 │ 242.179 │ 24.22 │ 15:49:00 │ 67 0 0 100 │
|
||||
│ │ exit_signal │ 4 │ -2.23 │ -31.217 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │
|
||||
│ │ force_exit │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │
|
||||
│ │ stop_loss │ 3 │ -10.14 │ -111.768 │ -11.18 │ 1 day, 3:05:00 │ 0 0 3 0 │
|
||||
│ TOTAL │ │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
└───────────┴─────────────┴────────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘
|
||||
SUMMARY METRICS
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Metric ┃ Value ┃
|
||||
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ Backtesting from │ 2025-07-01 00:00:00 │
|
||||
│ Backtesting to │ 2025-08-01 00:00:00 │
|
||||
│ Trading Mode │ Isolated Futures │
|
||||
│ Max open trades │ 3 │
|
||||
│ │ │
|
||||
│ Total/Daily Avg Trades │ 77 / 2.48 │
|
||||
│ Starting balance │ 1000 USDT │
|
||||
│ Final balance │ 1054.774 USDT │
|
||||
│ Absolute profit │ 54.774 USDT │
|
||||
│ Total profit % │ 5.48% │
|
||||
│ CAGR % │ 87.36% │
|
||||
│ Sortino │ 2.48 │
|
||||
│ Sharpe │ 3.75 │
|
||||
│ Calmar │ 40.99 │
|
||||
│ SQN │ 0.69 │
|
||||
│ Profit factor │ 1.29 │
|
||||
│ Expectancy (Ratio) │ 0.71 (0.04) │
|
||||
│ Avg. daily profit │ 1.767 USDT │
|
||||
│ Avg. stake amount │ 345.016 USDT │
|
||||
│ Total trade volume │ 53316.954 USDT │
|
||||
│ │ │
|
||||
│ Long / Short trades │ 67 / 10 │
|
||||
│ Long / Short profit % │ 8.94% / -3.47% │
|
||||
│ Long / Short profit USDT │ 89.425 / -34.651 │
|
||||
│ │ │
|
||||
│ Best Pair │ LTC/USDT:USDT 5.62% │
|
||||
│ Worst Pair │ ADA/USDT:USDT -5.21% │
|
||||
│ Best trade │ ETC/USDT:USDT 2.00% │
|
||||
│ Worst trade │ ADA/USDT:USDT -10.17% │
|
||||
│ Best day │ 26.91 USDT │
|
||||
│ Worst day │ -47.741 USDT │
|
||||
│ Days win/draw/lose │ 20 / 6 / 5 │
|
||||
│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │
|
||||
│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │
|
||||
│ Max Consecutive Wins / Loss │ 36 / 3 │
|
||||
│ Rejected Entry signals │ 258 │
|
||||
│ Entry/Exit Timeouts │ 0 / 0 │
|
||||
│ │ │
|
||||
│ Min balance │ 1003.168 USDT │
|
||||
│ Max balance │ 1149.421 USDT │
|
||||
│ Max % of account underwater │ 8.23% │
|
||||
│ Absolute drawdown │ 94.647 USDT (8.23%) │
|
||||
│ Drawdown duration │ 9 days 08:50:00 │
|
||||
│ Profit at drawdown start │ 149.421 USDT │
|
||||
│ Profit at drawdown end │ 54.774 USDT │
|
||||
│ Drawdown start │ 2025-07-22 15:10:00 │
|
||||
│ Drawdown end │ 2025-08-01 00:00:00 │
|
||||
│ Market change │ 30.51% │
|
||||
└───────────────────────────────┴─────────────────────────────────┘
|
||||
|
||||
================== SUMMARY METRICS ==================
|
||||
| Metric | Value |
|
||||
|-----------------------------+---------------------|
|
||||
| Backtesting from | 2019-01-01 00:00:00 |
|
||||
| Backtesting to | 2019-05-01 00:00:00 |
|
||||
| Trading Mode | Spot |
|
||||
| Max open trades | 3 |
|
||||
| | |
|
||||
| Total/Daily Avg Trades | 429 / 3.575 |
|
||||
| Starting balance | 0.01000000 BTC |
|
||||
| Final balance | 0.01762792 BTC |
|
||||
| Absolute profit | 0.00762792 BTC |
|
||||
| Total profit % | 76.2% |
|
||||
| CAGR % | 460.87% |
|
||||
| Sortino | 1.88 |
|
||||
| Sharpe | 2.97 |
|
||||
| Calmar | 6.29 |
|
||||
| SQN | 2.45 |
|
||||
| Profit factor | 1.11 |
|
||||
| Expectancy (Ratio) | -0.15 (-0.05) |
|
||||
| Avg. stake amount | 0.001 BTC |
|
||||
| Total trade volume | 0.429 BTC |
|
||||
| | |
|
||||
| Long / Short | 352 / 77 |
|
||||
| Total profit Long % | 1250.58% |
|
||||
| Total profit Short % | -15.02% |
|
||||
| Absolute profit Long | 0.00838792 BTC |
|
||||
| Absolute profit Short | -0.00076 BTC |
|
||||
| | |
|
||||
| Best Pair | LSK/BTC 26.26% |
|
||||
| Worst Pair | ZEC/BTC -10.18% |
|
||||
| Best Trade | LSK/BTC 4.25% |
|
||||
| Worst Trade | ZEC/BTC -10.25% |
|
||||
| Best day | 0.00076 BTC |
|
||||
| Worst day | -0.00036 BTC |
|
||||
| Days win/draw/lose | 12 / 82 / 25 |
|
||||
| Avg. Duration Winners | 4:23:00 |
|
||||
| Avg. Duration Loser | 6:55:00 |
|
||||
| Max Consecutive Wins / Loss | 3 / 4 |
|
||||
| Rejected Entry signals | 3089 |
|
||||
| Entry/Exit Timeouts | 0 / 0 |
|
||||
| Canceled Trade Entries | 34 |
|
||||
| Canceled Entry Orders | 123 |
|
||||
| Replaced Entry Orders | 89 |
|
||||
| | |
|
||||
| Min balance | 0.00945123 BTC |
|
||||
| Max balance | 0.01846651 BTC |
|
||||
| Max % of account underwater | 25.19% |
|
||||
| Absolute Drawdown (Account) | 13.33% |
|
||||
| Drawdown | 0.0015 BTC |
|
||||
| Drawdown high | 0.0013 BTC |
|
||||
| Drawdown low | -0.0002 BTC |
|
||||
| Drawdown Start | 2019-02-15 14:10:00 |
|
||||
| Drawdown End | 2019-04-11 18:15:00 |
|
||||
| Market change | -5.88% |
|
||||
=====================================================
|
||||
Backtested 2025-07-01 00:00:00 -> 2025-08-01 00:00:00 | Max open trades : 3
|
||||
STRATEGY SUMMARY
|
||||
┏━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃
|
||||
┡━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ SampleStrategy │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ 94.647 USDT 8.23% │
|
||||
└────────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┴────────────────────┘
|
||||
```
|
||||
|
||||
### Backtesting report table
|
||||
|
||||
The 1st table contains all trades the bot made, including "left open trades".
|
||||
The first table contains all trades the bot made, including "left open trades".
|
||||
|
||||
The last line will give you the overall performance of your strategy,
|
||||
here:
|
||||
|
||||
```
|
||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||
│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │
|
||||
```
|
||||
|
||||
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 bot has made `77` trades for an average duration of `22:12:00`, with a performance of `5.48%` (profit), that means it has earned a total of `54.774 USDT` starting with a capital of 1000 USDT.
|
||||
|
||||
The column `Avg Profit %` shows the average profit for all trades made.
|
||||
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
||||
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
||||
|
||||
In the above results, we have a starting balance of 1000 USDT and an absolute profit of 54.774 USDT - so the `Tot Profit %` will be `(54.774 / 1000) * 100 ~= 5.48%`.
|
||||
|
||||
Your strategy performance is influenced by your entry strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set.
|
||||
|
||||
@@ -284,86 +305,83 @@ On the other hand, if you set a too high `minimal_roi` like `"0": 0.55`
|
||||
(55%), there is almost no chance that the bot will ever reach this profit.
|
||||
Hence, keep in mind that your performance is an integral mix of all different elements of the strategy, your configuration, and the crypto-currency pairs you have set up.
|
||||
|
||||
### Exit reasons table
|
||||
|
||||
The 2nd table contains a recap of exit reasons.
|
||||
This table can tell you which area needs some additional work (e.g. all or many of the `exit_signal` trades are losses, so you should work on improving the exit signal, or consider disabling it).
|
||||
|
||||
### Left open trades table
|
||||
|
||||
The 3rd table contains all trades the bot had to `force_exit` at the end of the backtesting period to present you the full picture.
|
||||
The second table contains all trades the bot had to `force_exit` at the end of the backtesting period to present you the full picture.
|
||||
This is necessary to simulate realistic behavior, since the backtest period has to end at some point, while realistically, you could leave the bot running forever.
|
||||
These trades are also included in the first table, but are also shown separately in this table for clarity.
|
||||
|
||||
### Enter tag stats table
|
||||
|
||||
The third table provides a breakdown of trades by their entry tags (e.g., `enter_long`, `enter_short`), showing the number of entries, average profit percentage, total profit in the stake currency, total profit percentage, average duration, and the number of wins, draws, and losses for each tag.
|
||||
|
||||
### Exit reason stats table
|
||||
|
||||
The fourth table contains a recap of exit reasons (e.g., `exit_signal`, `roi`, `stop_loss`, `force_exit`). This table can tell you which area needs additional work (e.g., if many `exit_signal` trades are losses, you should work on improving the exit signal or consider disabling it).
|
||||
|
||||
### Mixed tag stats table
|
||||
|
||||
The fifth table combines entry tags and exit reasons, providing a detailed view of how different entry tags performed with specific exit reasons. This can help identify which combinations of entry and exit strategies are most effective.
|
||||
|
||||
### Summary metrics
|
||||
|
||||
The last element of the backtest report is the summary metrics table.
|
||||
It contains some useful key metrics about performance of your strategy on backtesting data.
|
||||
It contains key metrics about the performance of your strategy on backtesting data.
|
||||
|
||||
```
|
||||
================== SUMMARY METRICS ==================
|
||||
| Metric | Value |
|
||||
|-----------------------------+---------------------|
|
||||
| Backtesting from | 2019-01-01 00:00:00 |
|
||||
| Backtesting to | 2019-05-01 00:00:00 |
|
||||
| Trading Mode | Spot |
|
||||
| Max open trades | 3 |
|
||||
| | |
|
||||
| Total/Daily Avg Trades | 429 / 3.575 |
|
||||
| Starting balance | 0.01000000 BTC |
|
||||
| Final balance | 0.01762792 BTC |
|
||||
| Absolute profit | 0.00762792 BTC |
|
||||
| Total profit % | 76.2% |
|
||||
| CAGR % | 460.87% |
|
||||
| Sortino | 1.88 |
|
||||
| Sharpe | 2.97 |
|
||||
| Calmar | 6.29 |
|
||||
| SQN | 2.45 |
|
||||
| Profit factor | 1.11 |
|
||||
| Expectancy (Ratio) | -0.15 (-0.05) |
|
||||
| Avg. daily profit | 0.0001 BTC |
|
||||
| Avg. stake amount | 0.001 BTC |
|
||||
| Total trade volume | 0.429 BTC |
|
||||
| | |
|
||||
| Long / Short | 352 / 77 |
|
||||
| Total profit Long % | 1250.58% |
|
||||
| Total profit Short % | -15.02% |
|
||||
| Absolute profit Long | 0.00838792 BTC |
|
||||
| Absolute profit Short | -0.00076 BTC |
|
||||
| | |
|
||||
| Best Pair | LSK/BTC 26.26% |
|
||||
| Worst Pair | ZEC/BTC -10.18% |
|
||||
| Best Trade | LSK/BTC 4.25% |
|
||||
| Worst Trade | ZEC/BTC -10.25% |
|
||||
| Best day | 0.00076 BTC |
|
||||
| Worst day | -0.00036 BTC |
|
||||
| Days win/draw/lose | 12 / 82 / 25 |
|
||||
| Avg. Duration Winners | 4:23:00 |
|
||||
| Avg. Duration Loser | 6:55:00 |
|
||||
| Max Consecutive Wins / Loss | 3 / 4 |
|
||||
| Rejected Entry signals | 3089 |
|
||||
| Entry/Exit Timeouts | 0 / 0 |
|
||||
| Canceled Trade Entries | 34 |
|
||||
| Canceled Entry Orders | 123 |
|
||||
| Replaced Entry Orders | 89 |
|
||||
| | |
|
||||
| Min balance | 0.00945123 BTC |
|
||||
| Max balance | 0.01846651 BTC |
|
||||
| Max % of account underwater | 25.19% |
|
||||
| Absolute Drawdown (Account) | 13.33% |
|
||||
| Drawdown | 0.0015 BTC |
|
||||
| Drawdown high | 0.0013 BTC |
|
||||
| Drawdown low | -0.0002 BTC |
|
||||
| Drawdown Start | 2019-02-15 14:10:00 |
|
||||
| Drawdown End | 2019-04-11 18:15:00 |
|
||||
| Market change | -5.88% |
|
||||
=====================================================
|
||||
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Metric ┃ Value ┃
|
||||
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ Backtesting from │ 2025-07-01 00:00:00 │
|
||||
│ Backtesting to │ 2025-08-01 00:00:00 │
|
||||
│ Trading Mode │ Isolated Futures │
|
||||
│ Max open trades │ 3 │
|
||||
│ │ │
|
||||
│ Total/Daily Avg Trades │ 72 / 2.32 │
|
||||
│ Starting balance │ 1000 USDT │
|
||||
│ Final balance │ 1106.734 USDT │
|
||||
│ Absolute profit │ 106.734 USDT │
|
||||
│ Total profit % │ 10.67% │
|
||||
│ CAGR % │ 230.04% │
|
||||
│ Sortino │ 4.99 │
|
||||
│ Sharpe │ 8.00 │
|
||||
│ Calmar │ 77.76 │
|
||||
│ SQN │ 1.52 │
|
||||
│ Profit factor │ 1.79 │
|
||||
│ Expectancy (Ratio) │ 1.48 (0.07) │
|
||||
│ Avg. daily profit │ 3.443 USDT │
|
||||
│ Avg. stake amount │ 363.133 USDT │
|
||||
│ Total trade volume │ 52466.174 USDT │
|
||||
│ │ │
|
||||
│ Best Pair │ LTC/USDT:USDT 4.48% │
|
||||
│ Worst Pair │ ADA/USDT:USDT -1.78% │
|
||||
│ Best trade │ ETC/USDT:USDT 2.00% │
|
||||
│ Worst trade │ ADA/USDT:USDT -10.17% │
|
||||
│ Best day │ 23.535 USDT │
|
||||
│ Worst day │ -49.813 USDT │
|
||||
│ Days win/draw/lose │ 21 / 6 / 4 │
|
||||
│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:30 │
|
||||
│ Min/Max/Avg. Duration Losers │ 0d 12:00 / 17d 08:00 / 3d 23:28 │
|
||||
│ Max Consecutive Wins / Loss │ 58 / 4 │
|
||||
│ Rejected Entry signals │ 254 │
|
||||
│ Entry/Exit Timeouts │ 0 / 0 │
|
||||
│ │ │
|
||||
│ Min balance │ 1003.168 USDT │
|
||||
│ Max balance │ 1209 USDT │
|
||||
│ Max % of account underwater │ 8.46% │
|
||||
│ Absolute drawdown │ 102.266 USDT (8.46%) │
|
||||
│ Drawdown duration │ 9 days 08:50:00 │
|
||||
│ Profit at drawdown start │ 209 USDT │
|
||||
│ Profit at drawdown end │ 106.734 USDT │
|
||||
│ Drawdown start │ 2025-07-22 15:10:00 │
|
||||
│ Drawdown end │ 2025-08-01 00:00:00 │
|
||||
│ Market change │ 30.51% │
|
||||
└───────────────────────────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
- `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).
|
||||
- `Trading Mode`: Spot or Futures trading.
|
||||
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
|
||||
- `Total/Daily Avg Trades`: Identical to the total trades of the backtest output table / Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
|
||||
- `Starting balance`: Start balance - as given by dry-run-wallet (config or command line).
|
||||
- `Final balance`: Final balance - starting balance + absolute profit.
|
||||
@@ -374,58 +392,71 @@ It contains some useful key metrics about performance of your strategy on backte
|
||||
- `Sharpe`: Annualized Sharpe ratio.
|
||||
- `Calmar`: Annualized Calmar ratio.
|
||||
- `SQN`: System Quality Number (SQN) - by Van Tharp.
|
||||
- `Profit factor`: profit / loss.
|
||||
- `Profit factor`: Sum of the profits of all winning trades divided by the sum of the losses of all losing trades.
|
||||
- `Expectancy (Ratio)`: Expectancy ratio, which is the average profit or loss per trade. A negative expectancy ratio means that your strategy is not profitable.
|
||||
- `Avg. daily profit`: Average profit per day, calculated as `(Total Profit / Backtest Days)`.
|
||||
- `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 (based on absolute profit), and it's corresponding `Tot Profit %`.
|
||||
- `Best Trade` / `Worst Trade`: Biggest single winning trade and biggest single losing trade.
|
||||
- `Long / Short trades`: Split long/short trade counts (only shown when short trades were made).
|
||||
- `Long / Short profit %`: Profit percentage for long and short trades (only shown when short trades were made).
|
||||
- `Long / Short profit USDT`: Profit in stake currency for long and short trades (only shown when short trades were made).
|
||||
- `Best Pair` / `Worst Pair`: Best and worst performing pair (based on total profit percentage), and its corresponding `Tot Profit %`.
|
||||
- `Best trade` / `Worst trade`: Biggest single winning trade and biggest single losing trade.
|
||||
- `Best day` / `Worst day`: Best and worst day based on daily profit.
|
||||
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
|
||||
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
|
||||
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trades).
|
||||
- `Min/Max/Avg. Duration Winners`: Minimum, maximum, and average durations for winning trades.
|
||||
- `Min/Max/Avg. Duration Losers`: Minimum, maximum, and average durations for losing trades.
|
||||
- `Max Consecutive Wins / Loss`: Maximum consecutive wins/losses in a row.
|
||||
- `Rejected Entry signals`: Trade entry signals that could not be acted upon due to `max_open_trades` being reached.
|
||||
- `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used).
|
||||
- `Canceled Trade Entries`: Number of trades that have been canceled by user request via `adjust_entry_price`.
|
||||
- `Canceled Entry Orders`: Number of entry orders that have been canceled by user request via `adjust_entry_price`.
|
||||
- `Replaced Entry Orders`: Number of entry orders that have been replaced by user request via `adjust_entry_price`.
|
||||
- `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period.
|
||||
- `Max % of account underwater`: Maximum percentage your account has decreased from the top since the simulation started.
|
||||
Calculated as the maximum of `(Max Balance - Current Balance) / (Max Balance)`.
|
||||
- `Absolute Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
|
||||
- `Drawdown`: Maximum, absolute drawdown experienced. Difference between Drawdown High and Subsequent Low point.
|
||||
- `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost.
|
||||
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
||||
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
|
||||
- `Long / Short`: Split long/short values (Only shown when short trades were made).
|
||||
- `Total profit Long %` / `Absolute profit Long`: Profit long trades only (Only shown when short trades were made).
|
||||
- `Total profit Short %` / `Absolute profit Short`: Profit short trades only (Only shown when short trades were made).
|
||||
- `Max % of account underwater`: Maximum percentage your account has decreased from the top since the simulation started. Calculated as the maximum of `(Max Balance - Current Balance) / (Max Balance)`.
|
||||
- `Absolute drawdown`: Maximum absolute drawdown experienced, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`..
|
||||
- `Drawdown duration`: Duration of the largest drawdown period.
|
||||
- `Profit at drawdown start` / `Profit at drawdown end`: Profit at the beginning and end of the largest drawdown period.
|
||||
- `Drawdown start` / `Drawdown end`: Start and end datetime for the largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
||||
- `Market change`: Change of the market during the backtest period. Calculated as the average of all pairs' changes from the first to the last candle using the "close" column.
|
||||
|
||||
### Daily / Weekly / Monthly breakdown
|
||||
### Daily / Weekly / Monthly / Yearly breakdown
|
||||
|
||||
You can get an overview over daily / weekly or monthly results by using the `--breakdown <>` switch.
|
||||
You can get an overview over daily, weekly, monthly, or yearly results by using the `--breakdown <>` switch.
|
||||
|
||||
To visualize daily and weekly breakdowns, you can use the following:
|
||||
To visualize monthly and yearly breakdowns, you can use the following:
|
||||
|
||||
``` bash
|
||||
freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day week
|
||||
freqtrade backtesting --strategy MyAwesomeStrategy --breakdown month year
|
||||
```
|
||||
|
||||
``` output
|
||||
======================== DAY BREAKDOWN =========================
|
||||
| Day | Tot Profit USDT | Wins | Draws | Losses |
|
||||
|------------+-------------------+--------+---------+----------|
|
||||
| 03/07/2021 | 200.0 | 2 | 0 | 0 |
|
||||
| 04/07/2021 | -50.31 | 0 | 0 | 2 |
|
||||
| 05/07/2021 | 220.611 | 3 | 2 | 0 |
|
||||
| 06/07/2021 | 150.974 | 3 | 0 | 2 |
|
||||
| 07/07/2021 | -70.193 | 1 | 0 | 2 |
|
||||
| 08/07/2021 | 212.413 | 2 | 0 | 3 |
|
||||
|
||||
MONTH BREAKDOWN
|
||||
┏━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Month ┃ Trades ┃ Tot Profit USDT ┃ Profit Factor ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ 31/01/2020 │ 12 │ 44.451 │ 7.28 │ 10 0 2 83.3 │
|
||||
│ 29/02/2020 │ 30 │ 45.41 │ 2.36 │ 17 0 13 56.7 │
|
||||
│ 31/03/2020 │ 35 │ 142.024 │ 2.42 │ 14 0 21 40.0 │
|
||||
│ 30/04/2020 │ 67 │ -23.692 │ 0.81 │ 24 0 43 35.8 │
|
||||
...
|
||||
...
|
||||
│ 30/04/2025 │ 203 │ -63.43 │ 0.81 │ 73 0 130 36.0 │
|
||||
│ 31/05/2025 │ 142 │ 104.675 │ 1.28 │ 59 0 83 41.5 │
|
||||
│ 30/06/2025 │ 177 │ -1.014 │ 1.0 │ 85 0 92 48.0 │
|
||||
│ 31/07/2025 │ 155 │ 232.762 │ 1.6 │ 63 0 92 40.6 │
|
||||
└────────────┴────────┴─────────────────┴───────────────┴────────────────────────┘
|
||||
YEAR BREAKDOWN
|
||||
┏━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ Year ┃ Trades ┃ Tot Profit USDT ┃ Profit Factor ┃ Win Draw Loss Win% ┃
|
||||
┡━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
|
||||
│ 31/12/2020 │ 896 │ 868.889 │ 1.46 │ 351 0 545 39.2 │
|
||||
│ 31/12/2021 │ 1778 │ 4487.163 │ 1.93 │ 745 0 1033 41.9 │
|
||||
│ 31/12/2022 │ 1736 │ 938.27 │ 1.27 │ 698 0 1038 40.2 │
|
||||
│ 31/12/2023 │ 1712 │ 1677.126 │ 1.68 │ 670 0 1042 39.1 │
|
||||
│ 31/12/2024 │ 1609 │ 3198.424 │ 2.22 │ 773 0 836 48.0 │
|
||||
│ 31/12/2025 │ 1042 │ 716.174 │ 1.33 │ 420 0 622 40.3 │
|
||||
└────────────┴────────┴─────────────────┴───────────────┴────────────────────────┘
|
||||
```
|
||||
|
||||
The output will show a table containing the realized absolute Profit (in stake currency) for the given timeperiod, as well as wins, draws and losses that materialized (closed) on this day. Below that there will be a second table for the summarized values of weeks indicated by the date of the closing Sunday. The same would apply to a monthly breakdown indicated by the last day of the month.
|
||||
The output will display tables containing the realized absolute profit (in stake currency) for the selected period, along with additional statistics such as number of trades, profit factor, and distribution of wins, draws, and losses that materialized (closed) on this period.
|
||||
|
||||
### Backtest result caching
|
||||
|
||||
@@ -449,10 +480,10 @@ For this mode - `--notes "<notes>"` can be used to add notes to the backtest res
|
||||
The output file freqtrade produces is a zip file containing the following files:
|
||||
|
||||
- The backtest report in json format
|
||||
- the market change data in feather format
|
||||
- a copy of the strategy file
|
||||
- a copy of the strategy parameters (if a parameter file was used)
|
||||
- a sanitized copy of the config file
|
||||
- The market change data in feather format
|
||||
- A copy of the strategy file
|
||||
- A copy of the strategy parameters (if a parameter file was used)
|
||||
- A sanitized copy of the config file
|
||||
|
||||
This will ensure results are reproducible - under the assumption that the same data is available.
|
||||
|
||||
@@ -470,7 +501,7 @@ Since backtesting lacks some detailed information about what happens within a ca
|
||||
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
|
||||
- ROI
|
||||
- Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
|
||||
- Exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
|
||||
- Exits are never "below the candle", so a ROI of 2% may result in an exit at 2.4% if low was at 2.4% profit
|
||||
- ROI entries which came into effect on the triggering candle (e.g. `120: 0.02` for 1h candles, from `60: 0.05`) will use the candle's open as exit rate
|
||||
- Force-exits caused by `<N>=-1` ROI entries use low as exit value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
|
||||
- Stoploss exits happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
|
||||
@@ -536,7 +567,7 @@ freqtrade backtesting --strategy AwesomeStrategy --timeframe 1h --timeframe-deta
|
||||
|
||||
This will load 1h data (the main timeframe) as well as 5m data (detail timeframe) for the selected timerange.
|
||||
The strategy will be analyzed with the 1h timeframe.
|
||||
Candles where activity may take place (there's an active signal, the pair is in a trade) are evaluated at the 5m timeframe.
|
||||
Candles where activity may take place (there's an active signal, the pair is in a trade) are evaluated at the 5m timeframe.
|
||||
This will allow for a more accurate simulation of intra-candle movements - and can lead to different results, especially on higher timeframes.
|
||||
|
||||
Entries will generally still happen at the main candle's open, however freed trade slots may be freed earlier (if the exit signal is triggered on the 5m candle), which can then be used for a new trade of a different pair.
|
||||
@@ -599,5 +630,5 @@ Detailed output for all strategies one after the other will be available, so mak
|
||||
|
||||
## 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 you the optimal parameters to use for your strategy?
|
||||
Your next step is to learn [how to find optimal parameters with Hyperopt](hyperopt.md)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
usage: freqtrade backtesting-analysis [-h] [-v] [--no-color] [--logfile FILE]
|
||||
[-V] [-c PATH] [-d PATH]
|
||||
[--userdir PATH]
|
||||
[--export-filename PATH]
|
||||
[--backtest-filename PATH]
|
||||
[--backtest-directory PATH]
|
||||
[--analysis-groups {0,1,2,3,4,5} [{0,1,2,3,4,5} ...]]
|
||||
[--enter-reason-list ENTER_REASON_LIST [ENTER_REASON_LIST ...]]
|
||||
[--exit-reason-list EXIT_REASON_LIST [EXIT_REASON_LIST ...]]
|
||||
@@ -14,10 +15,15 @@ usage: freqtrade backtesting-analysis [-h] [-v] [--no-color] [--logfile FILE]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--backtest-directory PATH, --export-directory PATH
|
||||
Directory to use for backtest results. Example:
|
||||
`--export-directory=user_data/backtest_results/`.
|
||||
--analysis-groups {0,1,2,3,4,5} [{0,1,2,3,4,5} ...]
|
||||
grouping output - 0: simple wins/losses by enter tag,
|
||||
1: by enter_tag, 2: by enter_tag and exit_tag, 3: by
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
```
|
||||
usage: freqtrade backtesting-show [-h] [-v] [--no-color] [--logfile FILE] [-V]
|
||||
[-c PATH] [-d PATH] [--userdir PATH]
|
||||
[--export-filename PATH] [--show-pair-list]
|
||||
[--backtest-filename PATH]
|
||||
[--backtest-directory PATH]
|
||||
[--show-pair-list]
|
||||
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--backtest-directory PATH, --export-directory PATH
|
||||
Directory to use for backtest results. Example:
|
||||
`--export-directory=user_data/backtest_results/`.
|
||||
--show-pair-list Show backtesting pairlist sorted by profit.
|
||||
--breakdown {day,week,month,year} [{day,week,month,year} ...]
|
||||
Show backtesting breakdown per [day, week, month,
|
||||
|
||||
@@ -14,7 +14,8 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V]
|
||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||
[--export {none,trades,signals}]
|
||||
[--export-filename PATH]
|
||||
[--backtest-filename PATH]
|
||||
[--backtest-directory PATH]
|
||||
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
|
||||
[--cache {none,day,week,month}]
|
||||
[--freqai-backtest-live-models] [--notes TEXT]
|
||||
@@ -61,10 +62,15 @@ options:
|
||||
becomes `backtest-data-SampleStrategy.json`
|
||||
--export {none,trades,signals}
|
||||
Export backtest results (default: trades).
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--backtest-directory PATH, --export-directory PATH
|
||||
Directory to use for backtest results. Example:
|
||||
`--export-directory=user_data/backtest_results/`.
|
||||
--breakdown {day,week,month,year} [{day,week,month,year} ...]
|
||||
Show backtesting breakdown per [day, week, month,
|
||||
year].
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
```
|
||||
usage: freqtrade list-exchanges [-h] [-v] [--no-color] [--logfile FILE] [-V]
|
||||
[-c PATH] [-d PATH] [--userdir PATH] [-1] [-a]
|
||||
[--trading-mode {spot,margin,futures}]
|
||||
[--dex-exchanges]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-1, --one-column Print output in one column.
|
||||
-a, --all Print all exchanges known to the ccxt library.
|
||||
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
|
||||
Select Trading mode
|
||||
--dex-exchanges Print only DEX exchanges.
|
||||
|
||||
Common arguments:
|
||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||
|
||||
@@ -15,7 +15,8 @@ usage: freqtrade lookahead-analysis [-h] [-v] [--no-color] [--logfile FILE]
|
||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||
[--export {none,trades,signals}]
|
||||
[--export-filename PATH]
|
||||
[--backtest-filename PATH]
|
||||
[--backtest-directory PATH]
|
||||
[--freqai-backtest-live-models]
|
||||
[--minimum-trade-amount INT]
|
||||
[--targeted-trade-amount INT]
|
||||
@@ -60,10 +61,15 @@ options:
|
||||
becomes `backtest-data-SampleStrategy.json`
|
||||
--export {none,trades,signals}
|
||||
Export backtest results (default: trades).
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--backtest-directory PATH, --export-directory PATH
|
||||
Directory to use for backtest results. Example:
|
||||
`--export-directory=user_data/backtest_results/`.
|
||||
--freqai-backtest-live-models
|
||||
Run backtest with ready models.
|
||||
--minimum-trade-amount INT
|
||||
|
||||
@@ -10,7 +10,7 @@ usage: freqtrade plot-dataframe [-h] [-v] [--no-color] [--logfile FILE] [-V]
|
||||
[--plot-limit INT] [--db-url PATH]
|
||||
[--trade-source {DB,file}]
|
||||
[--export {none,trades,signals}]
|
||||
[--export-filename PATH]
|
||||
[--backtest-filename PATH]
|
||||
[--timerange TIMERANGE] [-i TIMEFRAME]
|
||||
[--no-trades]
|
||||
|
||||
@@ -38,10 +38,12 @@ options:
|
||||
(backtest file)) Default: file
|
||||
--export {none,trades,signals}
|
||||
Export backtest results (default: trades).
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--timerange TIMERANGE
|
||||
Specify what timerange of data to use.
|
||||
-i TIMEFRAME, --timeframe TIMEFRAME
|
||||
|
||||
@@ -6,7 +6,7 @@ usage: freqtrade plot-profit [-h] [-v] [--no-color] [--logfile FILE] [-V]
|
||||
[--freqaimodel NAME] [--freqaimodel-path PATH]
|
||||
[-p PAIRS [PAIRS ...]] [--timerange TIMERANGE]
|
||||
[--export {none,trades,signals}]
|
||||
[--export-filename PATH] [--db-url PATH]
|
||||
[--backtest-filename PATH] [--db-url PATH]
|
||||
[--trade-source {DB,file}] [-i TIMEFRAME]
|
||||
[--auto-open]
|
||||
|
||||
@@ -19,10 +19,12 @@ options:
|
||||
Specify what timerange of data to use.
|
||||
--export {none,trades,signals}
|
||||
Export backtest results (default: trades).
|
||||
--export-filename PATH, --backtest-filename PATH
|
||||
Use this filename for backtest results.Requires
|
||||
`--export` to be set as well. Example: `--export-filen
|
||||
ame=user_data/backtest_results/backtest_today.json`
|
||||
--backtest-filename PATH, --export-filename PATH
|
||||
Use this filename for backtest results.Example:
|
||||
`--backtest-
|
||||
filename=backtest_results_2020-09-27_16-20-48.json`.
|
||||
Assumes either `user_data/backtest_results/` or
|
||||
`--export-directory` as base directory.
|
||||
--db-url PATH Override trades database URL, this is useful in custom
|
||||
deployments (default: `sqlite:///tradesv3.sqlite` for
|
||||
Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for
|
||||
|
||||
@@ -566,8 +566,8 @@ Configuration:
|
||||
|
||||
### Understand order_time_in_force
|
||||
|
||||
The `order_time_in_force` configuration parameter defines the policy by which the order
|
||||
is executed on the exchange. Three commonly used time in force are:
|
||||
The `order_time_in_force` configuration parameter defines the policy by which the order is executed on the exchange.
|
||||
Commonly used time in force are:
|
||||
|
||||
**GTC (Good Till Canceled):**
|
||||
|
||||
@@ -589,11 +589,13 @@ is automatically cancelled by the exchange.
|
||||
Post only order. The order is either placed as a maker order, or it is canceled.
|
||||
This means the order must be placed on orderbook for at least time in an unfilled state.
|
||||
|
||||
Please check the [Exchange documentation](exchanges.md) for supported time in force values for your exchange.
|
||||
|
||||
#### time_in_force config
|
||||
|
||||
The `order_time_in_force` parameter contains a dict with entry and exit time in force policy values.
|
||||
This can be set in the configuration file or in the strategy.
|
||||
Values set in the configuration file overwrites values set in the strategy.
|
||||
Values set in the configuration file overwrite values from in the strategy, following the regular [precedence rules](#configuration-option-prevalence).
|
||||
|
||||
The possible values are: `GTC` (default), `FOK` or `IOC`.
|
||||
|
||||
@@ -605,9 +607,9 @@ The possible values are: `GTC` (default), `FOK` or `IOC`.
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
This is ongoing work. For now, it is supported only for binance, gate and kucoin.
|
||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
|
||||
|
||||
|
||||
### Fiat conversion
|
||||
|
||||
Freqtrade uses the Coingecko API to convert the coin value to it's corresponding fiat value for the Telegram reports.
|
||||
|
||||
@@ -408,6 +408,22 @@ jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace freqtrade/tem
|
||||
jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown freqtrade/templates/strategy_analysis_example.ipynb --stdout > docs/strategy_analysis_example.md
|
||||
```
|
||||
|
||||
## Backtest documentation results
|
||||
|
||||
To generate backtest outputs, please use the following commands:
|
||||
|
||||
``` bash
|
||||
# Assume a dedicated user directory for this output
|
||||
freqtrade create-userdir --userdir user_data_bttest/
|
||||
# set can_short = True
|
||||
sed -i "s/can_short: bool = False/can_short: bool = True/" user_data_bttest/strategies/sample_strategy.py
|
||||
|
||||
freqtrade download-data --timerange 20250625-20250801 --config tests/testdata/config.tests.usdt.json --userdir user_data_bttest/ -t 5m
|
||||
|
||||
freqtrade backtesting --config tests/testdata/config.tests.usdt.json -s SampleStrategy --userdir user_data_bttest/ --cache none --timerange 20250701-20250801
|
||||
```
|
||||
|
||||
|
||||
## Continuous integration
|
||||
|
||||
This documents some decisions taken for the CI Pipeline.
|
||||
@@ -418,7 +434,6 @@ This documents some decisions taken for the CI Pipeline.
|
||||
* Docker images contain a file, `/freqtrade/freqtrade_commit` containing the commit this image is based of.
|
||||
* Full docker image rebuilds are run once a week via schedule.
|
||||
* Deployments run on ubuntu.
|
||||
* ta-lib binaries are contained in the build_helpers directory to avoid fails related to external unavailability.
|
||||
* All tests must pass for a PR to be merged to `stable` or `develop`.
|
||||
|
||||
## Creating a release
|
||||
|
||||
@@ -227,7 +227,7 @@ Kucoin requires a passphrase for each api key, you will therefore need to add th
|
||||
}
|
||||
```
|
||||
|
||||
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel) and "IOC" (immediate-or-cancel) settings.
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Kucoin supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||
@@ -271,7 +271,9 @@ Using the wrong exchange will result in the error "OKX Error 50119: API key does
|
||||
## Gate.io
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Gate.io supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange..
|
||||
Gate.io supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
||||
|
||||
Gate.io supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), and "IOC" (immediate-or-cancel) settings.
|
||||
|
||||
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
|
||||
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
|
||||
@@ -286,9 +288,15 @@ Without these permissions, the bot will not start correctly and show errors like
|
||||
|
||||
## Bybit
|
||||
|
||||
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
||||
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
||||
|
||||
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors.
|
||||
Bybit supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
|
||||
|
||||
Futures trading on bybit is currently supported for isolated futures mode.
|
||||
|
||||
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
|
||||
|
||||
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
|
||||
|
||||
@@ -305,11 +313,6 @@ We do strongly recommend to limit all API keys to the IP you're going to use it
|
||||
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
|
||||
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
|
||||
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
||||
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
||||
|
||||
## Bitmart
|
||||
|
||||
Bitmart requires the API key Memo (the name you give the API key) to go along with the exchange key and secret.
|
||||
@@ -328,6 +331,26 @@ It's therefore required to pass the UID as well.
|
||||
!!! Warning "Necessary Verification"
|
||||
Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification.
|
||||
|
||||
## Bitget
|
||||
|
||||
Bitget requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
|
||||
|
||||
```json
|
||||
"exchange": {
|
||||
"name": "bitget",
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"password": "your_exchange_api_key_password",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Bitget supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Bitget supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used.
|
||||
|
||||
## Hyperliquid
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
|
||||
@@ -389,6 +389,8 @@ The `refresh_period` setting defines the interval (in seconds) at which the mark
|
||||
The `categories` setting specifies the [coingecko categories](https://www.coingecko.com/en/categories) from which to select coins from. The default is an empty list `[]`, meaning no category filtering is applied.
|
||||
If an incorrect category string is chosen, the plugin will print the available categories from CoinGecko and fail. The category should be the ID of the category, for example, for `https://www.coingecko.com/en/categories/layer-1`, the category ID would be `layer-1`. You can pass multiple categories such as `["layer-1", "meme-token"]` to select from several categories.
|
||||
|
||||
Coins like 1000PEPE/USDT or KPEPE/USDT:USDT are detected on a best effort basis, with the prefixes `1000` and `K` being used to identify them.
|
||||
|
||||
!!! Warning "Many categories"
|
||||
Each added category corresponds to one API call to CoinGecko. The more categories you add, the longer the pairlist generation will take, potentially causing rate limit issues.
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
[](https://github.com/freqtrade/freqtrade/actions/)
|
||||
[](https://doi.org/10.21105/joss.04864)
|
||||
[](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
||||
[](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
||||
|
||||
<!-- GitHub action buttons -->
|
||||
[:octicons-star-16: Star](https://github.com/freqtrade/freqtrade){ .md-button .md-button--sm }
|
||||
|
||||
@@ -46,7 +46,6 @@ These requirements apply to both [Script Installation](#script-installation) and
|
||||
* [pip](https://pip.pypa.io/en/stable/installing/)
|
||||
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
|
||||
* [TA-Lib](https://ta-lib.github.io/ta-lib-python/) (install instructions [below](#install-ta-lib))
|
||||
|
||||
### Install code
|
||||
|
||||
@@ -201,35 +200,6 @@ This option will hard reset your branch (only if you are on either `stable` or `
|
||||
|
||||
Make sure you fulfill the [Requirements](#requirements) and have downloaded the [Freqtrade repository](#freqtrade-repository).
|
||||
|
||||
### Install TA-Lib
|
||||
|
||||
#### TA-Lib script installation
|
||||
|
||||
```bash
|
||||
sudo ./build_helpers/install_ta-lib.sh
|
||||
```
|
||||
|
||||
!!! Note
|
||||
This will use the ta-lib tar.gz included in this repository.
|
||||
|
||||
##### TA-Lib manual installation
|
||||
|
||||
[Official installation guide](https://ta-lib.github.io/ta-lib-python/install.html)
|
||||
|
||||
```bash
|
||||
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
|
||||
tar xvzf 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
|
||||
./configure --prefix=/usr/local
|
||||
make
|
||||
sudo make install
|
||||
# On debian based systems (debian, ubuntu, ...) - updating ldconfig might be necessary.
|
||||
sudo ldconfig
|
||||
cd ..
|
||||
rm -rf ./ta-lib*
|
||||
```
|
||||
|
||||
### Setup Python virtual environment (virtualenv)
|
||||
|
||||
You will run freqtrade in separated `virtual environment`
|
||||
@@ -332,16 +302,6 @@ python3 -m pip install -r requirements.txt
|
||||
python3 -m pip install -e .
|
||||
```
|
||||
|
||||
Patch conda libta-lib (Linux only)
|
||||
|
||||
```bash
|
||||
# Ensure that the environment is active!
|
||||
conda activate freqtrade
|
||||
|
||||
cd build_helpers
|
||||
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
|
||||
```
|
||||
|
||||
[You are now ready](#you-are-ready) to run the bot.
|
||||
|
||||
### Important shortcuts
|
||||
|
||||
@@ -38,7 +38,7 @@ Many strategies, without the programmer knowing, have fallen prey to lookahead b
|
||||
This typically makes the strategy backtest look profitable, sometimes to extremes, but this is not realistic as the strategy is "cheating" by looking at data it would not have in dry or live modes.
|
||||
|
||||
The reason why strategies can "cheat" is because the freqtrade backtesting process populates the full dataframe including all candle timestamps at the outset.
|
||||
If the programmer is not careful or oblivious how things work internally
|
||||
If the programmer is not careful or oblivious how things work internally
|
||||
(which sometimes can be really hard to find out) then the strategy will look into the future.
|
||||
|
||||
This command is made to try to verify the validity in the form of the aforementioned lookahead bias.
|
||||
@@ -50,8 +50,7 @@ After this initial backtest runs, it will look if the `minimum-trade-amount` is
|
||||
If this happens, use a wider timerange to get more trades for the analysis, or use a timerange where more trades occur.
|
||||
|
||||
After setting the baseline it will then do additional backtest runs for every entry and exit separately.
|
||||
When these verification backtests complete, it will compare the indicators at the signal candles (both entry or exit)
|
||||
and report the bias.
|
||||
When these verification backtests complete, it will compare both dataframes (baseline and sliced) for any difference in columns' value and report the bias.
|
||||
After all signals have been verified or falsified a result table will be generated for the user to see.
|
||||
|
||||
### How to find and remove bias? How can I salvage a biased strategy?
|
||||
@@ -98,8 +97,8 @@ If the strategy has many different signals / signal types, it's up to you to sel
|
||||
This would lead to a false-negative, i.e. the strategy will be reported as non-biased.
|
||||
- `lookahead-analysis` has access to the same backtesting options and this can introduce problems.
|
||||
Please don't use any options like enabling position stacking as this will distort the number of checked signals.
|
||||
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` slots,
|
||||
If you decide to do so, then make doubly sure that you won't ever run out of `max_open_trades` slots,
|
||||
and that you have enough capital in the backtest wallet configuration.
|
||||
- In the results table, the `biased_indicators` column
|
||||
- In the results table, the `biased_indicators` column
|
||||
will falsely flag FreqAI target indicators defined in `set_freqai_targets()` as biased.
|
||||
**These are not biased and can safely be ignored.**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
markdown==3.8.2
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.6.16
|
||||
mkdocs-material==9.6.18
|
||||
mdx_truly_sane_lists==1.3
|
||||
pymdown-extensions==10.16
|
||||
pymdown-extensions==10.16.1
|
||||
jinja2==3.1.6
|
||||
mike==2.1.3
|
||||
|
||||
@@ -31,6 +31,7 @@ The Order-type will be ignored if only one mode is available.
|
||||
| Binance | limit |
|
||||
| Binance Futures | market, limit |
|
||||
| Bingx | market, limit |
|
||||
| Bitget | market, limit |
|
||||
| HTX | limit |
|
||||
| kraken | market, limit |
|
||||
| Gate | limit |
|
||||
|
||||
@@ -47,3 +47,8 @@
|
||||
border-color: #afb8c1;
|
||||
box-shadow: inset 0 1px 0 rgba(175, 184, 193, 0.2);
|
||||
}
|
||||
|
||||
.md-grid {
|
||||
/* default is max-width: 61rem; */
|
||||
max-width: 75rem;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,3 @@ freqtrade install-ui
|
||||
|
||||
Update-problems usually come missing dependencies (you didn't follow the above instructions) - or from updated dependencies, which fail to install (for example TA-lib).
|
||||
Please refer to the corresponding installation sections (common problems linked below)
|
||||
|
||||
Common problems and their solutions:
|
||||
|
||||
* [ta-lib update on windows](windows_installation.md#install-ta-lib)
|
||||
|
||||
@@ -38,30 +38,6 @@ cd freqtrade
|
||||
!!! Hint
|
||||
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information.
|
||||
|
||||
### Install ta-lib
|
||||
|
||||
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
|
||||
|
||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.11, 3.12 and 3.13) and for 64bit Windows.
|
||||
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
|
||||
|
||||
Other versions must be downloaded from the above link.
|
||||
|
||||
``` powershell
|
||||
cd \path\freqtrade
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate.ps1
|
||||
# optionally install ta-lib from wheel
|
||||
# Eventually adjust the below filename to match the downloaded wheel
|
||||
pip install --find-links build_helpers\ TA-Lib -U
|
||||
pip install -r requirements.txt
|
||||
pip install -e .
|
||||
freqtrade
|
||||
```
|
||||
|
||||
!!! Note "Use Powershell"
|
||||
The above installation script assumes you're using powershell on a 64bit windows.
|
||||
Commands for the legacy CMD windows console may differ.
|
||||
|
||||
### Error during installation on Windows
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Freqtrade bot"""
|
||||
|
||||
__version__ = "2025.7"
|
||||
__version__ = "2025.8"
|
||||
|
||||
if "dev" in __version__:
|
||||
from pathlib import Path
|
||||
|
||||
@@ -17,7 +17,7 @@ def start_analysis_entries_exits(args: dict[str, Any]) -> None:
|
||||
from freqtrade.data.entryexitanalysis import process_entry_exit_reasons
|
||||
|
||||
# Initialize configuration
|
||||
config = setup_utils_configuration(args, RunMode.BACKTEST)
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||
|
||||
logger.info("Starting freqtrade in analysis mode")
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ ARGS_BACKTEST = [
|
||||
"strategy_list",
|
||||
"export",
|
||||
"exportfilename",
|
||||
"exportdirectory",
|
||||
"backtest_breakdown",
|
||||
"backtest_cache",
|
||||
"freqai_backtest_live_models",
|
||||
@@ -94,9 +95,14 @@ ARGS_LIST_FREQAIMODELS = ["freqaimodel_path", "print_one_column"]
|
||||
|
||||
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column"]
|
||||
|
||||
ARGS_BACKTEST_SHOW = ["exportfilename", "backtest_show_pair_list", "backtest_breakdown"]
|
||||
ARGS_BACKTEST_SHOW = [
|
||||
"exportfilename",
|
||||
"exportdirectory",
|
||||
"backtest_show_pair_list",
|
||||
"backtest_breakdown",
|
||||
]
|
||||
|
||||
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
||||
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all", "trading_mode", "dex_exchanges"]
|
||||
|
||||
ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
|
||||
|
||||
@@ -233,6 +239,7 @@ ARGS_HYPEROPT_SHOW = [
|
||||
|
||||
ARGS_ANALYZE_ENTRIES_EXITS = [
|
||||
"exportfilename",
|
||||
"exportdirectory",
|
||||
"analysis_groups",
|
||||
"enter_reason_list",
|
||||
"exit_reason_list",
|
||||
|
||||
@@ -199,22 +199,29 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
"(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`",
|
||||
nargs="+",
|
||||
),
|
||||
"export": Arg(
|
||||
"--export",
|
||||
help="Export backtest results (default: trades).",
|
||||
choices=constants.EXPORT_OPTIONS,
|
||||
),
|
||||
"backtest_notes": Arg(
|
||||
"--notes",
|
||||
help="Add notes to the backtest results.",
|
||||
metavar="TEXT",
|
||||
),
|
||||
"export": Arg(
|
||||
"--export",
|
||||
help="Export backtest results (default: trades).",
|
||||
choices=constants.EXPORT_OPTIONS,
|
||||
),
|
||||
"exportdirectory": Arg(
|
||||
"--backtest-directory",
|
||||
"--export-directory",
|
||||
help="Directory to use for backtest results. "
|
||||
"Example: `--export-directory=user_data/backtest_results/`. ",
|
||||
metavar="PATH",
|
||||
),
|
||||
"exportfilename": Arg(
|
||||
"--export-filename",
|
||||
"--backtest-filename",
|
||||
"--export-filename",
|
||||
help="Use this filename for backtest results."
|
||||
"Requires `--export` to be set as well. "
|
||||
"Example: `--export-filename=user_data/backtest_results/backtest_today.json`",
|
||||
"Example: `--backtest-filename=backtest_results_2020-09-27_16-20-48.json`. "
|
||||
"Assumes either `user_data/backtest_results/` or `--export-directory` as base directory.",
|
||||
metavar="PATH",
|
||||
),
|
||||
"disableparamexport": Arg(
|
||||
@@ -369,6 +376,11 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
help="Print all exchanges known to the ccxt library.",
|
||||
action="store_true",
|
||||
),
|
||||
"dex_exchanges": Arg(
|
||||
"--dex-exchanges",
|
||||
help="Print only DEX exchanges.",
|
||||
action="store_true",
|
||||
),
|
||||
# List pairs / markets
|
||||
"list_pairs_all": Arg(
|
||||
"-a",
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
|
||||
from freqtrade.enums import CandleType, RunMode, TradingMode
|
||||
from freqtrade.exceptions import ConfigurationError
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -134,7 +134,8 @@ def start_list_data(args: dict[str, Any]) -> None:
|
||||
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
|
||||
)
|
||||
if args["pairs"]:
|
||||
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
|
||||
pl = expand_pairlist(args["pairs"], [p[0] for p in paircombs], keep_invalid=True)
|
||||
paircombs = [comb for comb in paircombs if comb[0] in pl]
|
||||
title = f"Found {len(paircombs)} pair / timeframe combinations."
|
||||
if not config.get("show_timerange"):
|
||||
groupedpair = defaultdict(list)
|
||||
@@ -197,7 +198,8 @@ def start_list_trades_data(args: dict[str, Any]) -> None:
|
||||
)
|
||||
|
||||
if args["pairs"]:
|
||||
paircombs = [comb for comb in paircombs if comb in args["pairs"]]
|
||||
pl = expand_pairlist(args["pairs"], [p for p in paircombs], keep_invalid=True)
|
||||
paircombs = [comb for comb in paircombs if comb in pl]
|
||||
|
||||
title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), 'pair')}."
|
||||
if not config.get("show_timerange"):
|
||||
|
||||
@@ -46,7 +46,18 @@ def start_list_exchanges(args: dict[str, Any]) -> None:
|
||||
table.add_column("Markets")
|
||||
table.add_column("Reason")
|
||||
|
||||
trading_mode = args.get("trading_mode", None)
|
||||
dex_only = args.get("dex_exchanges", False)
|
||||
|
||||
for exchange in available_exchanges:
|
||||
if trading_mode and not any(
|
||||
a["trading_mode"] == trading_mode for a in exchange["trade_modes"]
|
||||
):
|
||||
# If trading_mode is specified, only show exchanges that support it
|
||||
continue
|
||||
if dex_only and not exchange.get("dex", False):
|
||||
# If dex_only is specified, only show DEX exchanges
|
||||
continue
|
||||
name = Text(exchange["name"])
|
||||
if exchange["supported"]:
|
||||
name.append(" (Supported)", style="italic")
|
||||
@@ -135,6 +146,9 @@ def start_list_strategies(args: dict[str, Any]) -> None:
|
||||
strategy_objs = StrategyResolver.search_all_objects(
|
||||
config, not args["print_one_column"], config.get("recursive_strategy_search", False)
|
||||
)
|
||||
if not strategy_objs:
|
||||
logger.warning("No strategies found.")
|
||||
return
|
||||
# Sort alphabetically
|
||||
strategy_objs = sorted(strategy_objs, key=lambda x: x["name"])
|
||||
for obj in strategy_objs:
|
||||
|
||||
@@ -72,7 +72,7 @@ def start_backtesting_show(args: dict[str, Any]) -> None:
|
||||
from freqtrade.data.btanalysis import load_backtest_stats
|
||||
from freqtrade.optimize.optimize_reports import show_backtest_results, show_sorted_pairlist
|
||||
|
||||
results = load_backtest_stats(config["exportfilename"])
|
||||
results = load_backtest_stats(config["exportdirectory"], config["exportfilename"])
|
||||
|
||||
show_backtest_results(config, results)
|
||||
show_sorted_pairlist(config, results)
|
||||
|
||||
@@ -157,6 +157,16 @@ CONF_SCHEMA = {
|
||||
"description": f"Offset for profit exit. {__IN_STRATEGY}",
|
||||
"type": "number",
|
||||
},
|
||||
"recursive_strategy_search": {
|
||||
"description": "Enable recursive strategy search.",
|
||||
"type": "boolean",
|
||||
},
|
||||
"user_data_dir": {
|
||||
"description": "Path to the user data directory.",
|
||||
},
|
||||
"datadir": {
|
||||
"description": "Path to the data directory.",
|
||||
},
|
||||
"fee": {
|
||||
"description": "Trading fee percentage. Can help to simulate slippage in backtesting",
|
||||
"type": "number",
|
||||
@@ -443,6 +453,7 @@ CONF_SCHEMA = {
|
||||
"pairlists": {
|
||||
"description": "Configuration for pairlists.",
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1371,6 +1382,7 @@ SCHEMA_TRADE_REQUIRED = [
|
||||
"entry_pricing",
|
||||
"stoploss",
|
||||
"minimal_roi",
|
||||
"pairlists",
|
||||
"internals",
|
||||
"dataformat_ohlcv",
|
||||
"dataformat_trades",
|
||||
@@ -1380,6 +1392,7 @@ SCHEMA_BACKTEST_REQUIRED = [
|
||||
"exchange",
|
||||
"stake_currency",
|
||||
"stake_amount",
|
||||
"pairlists",
|
||||
"dry_run_wallet",
|
||||
"dataformat_ohlcv",
|
||||
"dataformat_trades",
|
||||
|
||||
@@ -66,7 +66,8 @@ def validate_config_schema(conf: dict[str, Any], preliminary: bool = False) -> d
|
||||
return conf
|
||||
except ValidationError as e:
|
||||
logger.critical(f"Invalid configuration. Reason: {e}")
|
||||
raise ValidationError(best_match(Draft4Validator(conf_schema).iter_errors(conf)).message)
|
||||
result = best_match(FreqtradeValidator(conf_schema).iter_errors(conf))
|
||||
raise ConfigurationError(result.message)
|
||||
|
||||
|
||||
def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = False) -> None:
|
||||
|
||||
@@ -84,9 +84,6 @@ class Configuration:
|
||||
if "internals" not in config:
|
||||
config["internals"] = {}
|
||||
|
||||
if "pairlists" not in config:
|
||||
config["pairlists"] = []
|
||||
|
||||
# Keep a copy of the original configuration file
|
||||
config["original_config"] = deepcopy(config)
|
||||
|
||||
@@ -212,13 +209,28 @@ class Configuration:
|
||||
config.update({"datadir": create_datadir(config, self.args.get("datadir"))})
|
||||
logger.info("Using data directory: %s ...", config.get("datadir"))
|
||||
|
||||
self._args_to_config(
|
||||
config, argname="exportdirectory", logstring="Using {} as backtest directory ..."
|
||||
)
|
||||
|
||||
if self.args.get("exportfilename"):
|
||||
self._args_to_config(
|
||||
config, argname="exportfilename", logstring="Storing backtest results to {} ..."
|
||||
)
|
||||
config["exportfilename"] = Path(config["exportfilename"])
|
||||
else:
|
||||
config["exportfilename"] = config["user_data_dir"] / "backtest_results"
|
||||
if config.get("exportdirectory") and Path(config["exportdirectory"]).is_dir():
|
||||
logger.warning(
|
||||
"DEPRECATED: Using `--export-filename` with directories is deprecated, "
|
||||
"use `--backtest-directory` instead."
|
||||
)
|
||||
if config.get("exportdirectory") is None:
|
||||
# Fallback - assign export-directory directly.
|
||||
config["exportdirectory"] = config["exportfilename"]
|
||||
if not config.get("exportdirectory"):
|
||||
config["exportdirectory"] = config["user_data_dir"] / "backtest_results"
|
||||
if not config.get("exportfilename"):
|
||||
config["exportfilename"] = None
|
||||
config["exportdirectory"] = Path(config["exportdirectory"])
|
||||
|
||||
if self.args.get("show_sensitive"):
|
||||
logger.warning(
|
||||
|
||||
@@ -16,10 +16,7 @@ from .bt_fileutils import (
|
||||
load_backtest_data,
|
||||
load_backtest_metadata,
|
||||
load_backtest_stats,
|
||||
load_exit_signal_candles,
|
||||
load_file_from_zip,
|
||||
load_rejected_signals,
|
||||
load_signal_candles,
|
||||
load_trades,
|
||||
load_trades_from_db,
|
||||
trade_list_to_dataframe,
|
||||
|
||||
@@ -155,33 +155,55 @@ def load_backtest_metadata(filename: Path | str) -> dict[str, Any]:
|
||||
raise OperationalException("Unexpected error while loading backtest metadata.") from e
|
||||
|
||||
|
||||
def load_backtest_stats(filename: Path | str) -> BacktestResultType:
|
||||
def _normalize_filename(file_or_directory: Path | str, filename: Path | str | None) -> Path:
|
||||
"""
|
||||
Normalize the filename by ensuring it is a Path object.
|
||||
:param file_or_directory: The directory or file to normalize.
|
||||
:param filename: The filename to normalize.
|
||||
:return: A Path object representing the normalized filename.
|
||||
"""
|
||||
if isinstance(file_or_directory, str):
|
||||
file_or_directory = Path(file_or_directory)
|
||||
if file_or_directory.is_dir():
|
||||
if not filename:
|
||||
filename = get_latest_backtest_filename(file_or_directory)
|
||||
if Path(filename).is_file():
|
||||
fn = Path(filename)
|
||||
else:
|
||||
fn = file_or_directory / filename
|
||||
else:
|
||||
fn = file_or_directory
|
||||
return fn
|
||||
|
||||
|
||||
def load_backtest_stats(
|
||||
file_or_directory: Path | str, filename: Path | str | None = None
|
||||
) -> BacktestResultType:
|
||||
"""
|
||||
Load backtest statistics file.
|
||||
:param filename: pathlib.Path object, or string pointing to the file.
|
||||
:param file_or_directory: pathlib.Path object, or string pointing to the directory,
|
||||
or absolute/relative path to the backtest results file.
|
||||
:param filename: Optional filename to load from (if different from the main filename).
|
||||
Only valid when loading from a directory.
|
||||
:return: a dictionary containing the resulting file.
|
||||
"""
|
||||
if isinstance(filename, str):
|
||||
filename = Path(filename)
|
||||
if filename.is_dir():
|
||||
filename = filename / get_latest_backtest_filename(filename)
|
||||
if not filename.is_file():
|
||||
raise ValueError(f"File {filename} does not exist.")
|
||||
logger.info(f"Loading backtest result from {filename}")
|
||||
fn = _normalize_filename(file_or_directory, filename)
|
||||
|
||||
if filename.suffix == ".zip":
|
||||
if not fn.is_file():
|
||||
raise ValueError(f"File or directory {fn} does not exist.")
|
||||
logger.info(f"Loading backtest result from {fn}")
|
||||
|
||||
if fn.suffix == ".zip":
|
||||
data = json_load(
|
||||
StringIO(
|
||||
load_file_from_zip(filename, filename.with_suffix(".json").name).decode("utf-8")
|
||||
)
|
||||
StringIO(load_file_from_zip(fn, fn.with_suffix(".json").name).decode("utf-8"))
|
||||
)
|
||||
else:
|
||||
with filename.open() as file:
|
||||
with fn.open() as file:
|
||||
data = json_load(file)
|
||||
|
||||
# Legacy list format does not contain metadata.
|
||||
if isinstance(data, dict):
|
||||
data["metadata"] = load_backtest_metadata(filename)
|
||||
data["metadata"] = load_backtest_metadata(fn)
|
||||
return data
|
||||
|
||||
|
||||
@@ -362,16 +384,21 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
|
||||
return df
|
||||
|
||||
|
||||
def load_backtest_data(filename: Path | str, strategy: str | None = None) -> pd.DataFrame:
|
||||
def load_backtest_data(
|
||||
file_or_directory: Path | str, strategy: str | None = None, filename: Path | str | None = None
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
Load backtest data file.
|
||||
:param filename: pathlib.Path object, or string pointing to a file or directory
|
||||
Load backtest data file, returns a dataframe with the individual trades.
|
||||
:param file_or_directory: pathlib.Path object, or string pointing to the directory,
|
||||
or absolute/relative path to the backtest results file.
|
||||
:param strategy: Strategy to load - mainly relevant for multi-strategy backtests
|
||||
Can also serve as protection to load the correct result.
|
||||
:param filename: Optional filename to load from (if different from the main filename).
|
||||
Only valid when loading from a directory.
|
||||
:return: a dataframe with the analysis results
|
||||
:raise: ValueError if loading goes wrong.
|
||||
"""
|
||||
data = load_backtest_stats(filename)
|
||||
data = load_backtest_stats(file_or_directory, filename)
|
||||
if not isinstance(data, list):
|
||||
# new, nested format
|
||||
if "strategy" not in data:
|
||||
@@ -430,20 +457,23 @@ def load_file_from_zip(zip_path: Path, filename: str) -> bytes:
|
||||
raise ValueError(f"Bad zip file: {zip_path}.") from None
|
||||
|
||||
|
||||
def load_backtest_analysis_data(backtest_dir: Path, name: str):
|
||||
def load_backtest_analysis_data(
|
||||
file_or_directory: Path,
|
||||
name: Literal["signals", "rejected", "exited"],
|
||||
filename: Path | str | None = None,
|
||||
):
|
||||
"""
|
||||
Load backtest analysis data either from a pickle file or from within a zip file
|
||||
:param backtest_dir: Directory containing backtest results
|
||||
:param file_or_directory: pathlib.Path object, or string pointing to the directory,
|
||||
or absolute/relative path to the backtest results file.
|
||||
:param name: Name of the analysis data to load (signals, rejected, exited)
|
||||
:param filename: Optional filename to load from (if different from the main filename).
|
||||
Only valid when loading from a directory.
|
||||
:return: Analysis data
|
||||
"""
|
||||
import joblib
|
||||
|
||||
if backtest_dir.is_dir():
|
||||
lbf = Path(get_latest_backtest_filename(backtest_dir))
|
||||
zip_path = backtest_dir / lbf
|
||||
else:
|
||||
zip_path = backtest_dir
|
||||
zip_path = _normalize_filename(file_or_directory, filename)
|
||||
|
||||
if zip_path.suffix == ".zip":
|
||||
# Load from zip file
|
||||
@@ -458,10 +488,10 @@ def load_backtest_analysis_data(backtest_dir: Path, name: str):
|
||||
|
||||
else:
|
||||
# Load from separate pickle file
|
||||
if backtest_dir.is_dir():
|
||||
scpf = Path(backtest_dir, f"{zip_path.stem}_{name}.pkl")
|
||||
if file_or_directory.is_dir():
|
||||
scpf = Path(file_or_directory, f"{zip_path.stem}_{name}.pkl")
|
||||
else:
|
||||
scpf = Path(backtest_dir.parent / f"{backtest_dir.stem}_{name}.pkl")
|
||||
scpf = Path(file_or_directory.parent / f"{file_or_directory.stem}_{name}.pkl")
|
||||
|
||||
try:
|
||||
with scpf.open("rb") as scp:
|
||||
@@ -473,27 +503,6 @@ def load_backtest_analysis_data(backtest_dir: Path, name: str):
|
||||
return None
|
||||
|
||||
|
||||
def load_rejected_signals(backtest_dir: Path):
|
||||
"""
|
||||
Load rejected signals from backtest directory
|
||||
"""
|
||||
return load_backtest_analysis_data(backtest_dir, "rejected")
|
||||
|
||||
|
||||
def load_signal_candles(backtest_dir: Path):
|
||||
"""
|
||||
Load signal candles from backtest directory
|
||||
"""
|
||||
return load_backtest_analysis_data(backtest_dir, "signals")
|
||||
|
||||
|
||||
def load_exit_signal_candles(backtest_dir: Path) -> dict[str, dict[str, pd.DataFrame]]:
|
||||
"""
|
||||
Load exit signal candles from backtest directory
|
||||
"""
|
||||
return load_backtest_analysis_data(backtest_dir, "exited")
|
||||
|
||||
|
||||
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
|
||||
"""
|
||||
Convert list of Trade objects to pandas Dataframe
|
||||
|
||||
@@ -11,7 +11,7 @@ def get_tick_size_over_time(candles: DataFrame) -> Series:
|
||||
# count the number of significant digits for the open and close prices
|
||||
for col in ["open", "high", "low", "close"]:
|
||||
candles[f"{col}_count"] = (
|
||||
candles[col].round(14).astype(str).str.extract(r"\.(\d*[1-9])")[0].str.len()
|
||||
candles[col].round(14).apply("{:.15f}".format).str.extract(r"\.(\d*[1-9])")[0].str.len()
|
||||
)
|
||||
candles["max_count"] = candles[["open_count", "close_count", "high_count", "low_count"]].max(
|
||||
axis=1
|
||||
|
||||
@@ -7,11 +7,9 @@ from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import Config
|
||||
from freqtrade.data.btanalysis import (
|
||||
BT_DATA_COLUMNS,
|
||||
load_backtest_analysis_data,
|
||||
load_backtest_data,
|
||||
load_backtest_stats,
|
||||
load_exit_signal_candles,
|
||||
load_rejected_signals,
|
||||
load_signal_candles,
|
||||
)
|
||||
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||
from freqtrade.util import print_df_rich_table
|
||||
@@ -332,7 +330,7 @@ def process_entry_exit_reasons(config: Config):
|
||||
do_rejected = config.get("analysis_rejected", False)
|
||||
to_csv = config.get("analysis_to_csv", False)
|
||||
csv_path = Path(
|
||||
config.get("analysis_csv_path", config["exportfilename"]), # type: ignore[arg-type]
|
||||
config.get("analysis_csv_path", config["exportdirectory"]), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
if entry_only is True and exit_only is True:
|
||||
@@ -346,20 +344,30 @@ def process_entry_exit_reasons(config: Config):
|
||||
None if config.get("timerange") is None else str(config.get("timerange"))
|
||||
)
|
||||
try:
|
||||
backtest_stats = load_backtest_stats(config["exportfilename"])
|
||||
backtest_stats = load_backtest_stats(
|
||||
config["exportdirectory"], config["exportfilename"]
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ConfigurationError(e) from e
|
||||
|
||||
for strategy_name, results in backtest_stats["strategy"].items():
|
||||
trades = load_backtest_data(config["exportfilename"], strategy_name)
|
||||
trades = load_backtest_data(
|
||||
config["exportdirectory"], strategy_name, config["exportfilename"]
|
||||
)
|
||||
|
||||
if trades is not None and not trades.empty:
|
||||
signal_candles = load_signal_candles(config["exportfilename"])
|
||||
exit_signals = load_exit_signal_candles(config["exportfilename"])
|
||||
signal_candles = load_backtest_analysis_data(
|
||||
config["exportdirectory"], "signals", config["exportfilename"]
|
||||
)
|
||||
exit_signals = load_backtest_analysis_data(
|
||||
config["exportdirectory"], "exited", config["exportfilename"]
|
||||
)
|
||||
|
||||
rej_df = None
|
||||
if do_rejected:
|
||||
rejected_signals_dict = load_rejected_signals(config["exportfilename"])
|
||||
rejected_signals_dict = load_backtest_analysis_data(
|
||||
config["exportdirectory"], "rejected", config["exportfilename"]
|
||||
)
|
||||
rej_df = prepare_results(
|
||||
rejected_signals_dict,
|
||||
strategy_name,
|
||||
|
||||
@@ -97,7 +97,7 @@ def load_data(
|
||||
"""
|
||||
result: dict[str, DataFrame] = {}
|
||||
if startup_candles > 0 and timerange:
|
||||
logger.info(f"Using indicator startup period: {startup_candles} ...")
|
||||
logger.debug(f"Using indicator startup period: {startup_candles} ...")
|
||||
|
||||
data_handler = get_datahandler(datadir, data_format)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from freqtrade.exchange.exchange import Exchange
|
||||
# isort: on
|
||||
from freqtrade.exchange.binance import Binance
|
||||
from freqtrade.exchange.bingx import Bingx
|
||||
from freqtrade.exchange.bitget import Bitget
|
||||
from freqtrade.exchange.bitmart import Bitmart
|
||||
from freqtrade.exchange.bitpanda import Bitpanda
|
||||
from freqtrade.exchange.bitvavo import Bitvavo
|
||||
@@ -45,4 +46,4 @@ from freqtrade.exchange.kucoin import Kucoin
|
||||
from freqtrade.exchange.lbank import Lbank
|
||||
from freqtrade.exchange.luno import Luno
|
||||
from freqtrade.exchange.modetrade import Modetrade
|
||||
from freqtrade.exchange.okx import Okx
|
||||
from freqtrade.exchange.okx import MyOkx, Okx
|
||||
|
||||
@@ -45,7 +45,6 @@ class Binance(Exchange):
|
||||
"funding_fee_candle_limit": 1000,
|
||||
"stoploss_order_types": {"limit": "stop", "market": "stop_market"},
|
||||
"stoploss_blocks_assets": False, # Stoploss orders do not block assets
|
||||
"order_time_in_force": ["GTC", "FOK", "IOC"],
|
||||
"tickers_have_price": False,
|
||||
"floor_leverage": True,
|
||||
"fetch_orders_limit_minutes": 7 * 1440, # "fetch_orders" is limited to 7 days
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
128
freqtrade/exchange/bitget.py
Normal file
128
freqtrade/exchange/bitget.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import ccxt
|
||||
|
||||
from freqtrade.enums import CandleType
|
||||
from freqtrade.exceptions import (
|
||||
DDosProtection,
|
||||
OperationalException,
|
||||
RetryableOrderError,
|
||||
TemporaryError,
|
||||
)
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange.common import API_RETRY_COUNT, retrier
|
||||
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
|
||||
from freqtrade.util.datetime_helpers import dt_now, dt_ts
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Bitget(Exchange):
|
||||
"""
|
||||
Bitget 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: FtHas = {
|
||||
"stoploss_on_exchange": True,
|
||||
"stop_price_param": "stopPrice",
|
||||
"stop_price_prop": "stopPrice",
|
||||
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
||||
"ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones.
|
||||
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
|
||||
}
|
||||
_ft_has_futures: FtHas = {
|
||||
"mark_ohlcv_timeframe": "4h",
|
||||
}
|
||||
|
||||
def ohlcv_candle_limit(
|
||||
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
|
||||
) -> int:
|
||||
"""
|
||||
Exchange ohlcv candle limit
|
||||
bitget has the following behaviour:
|
||||
* 1000 candles for up-to-date data
|
||||
* 200 candles for historic data (prior to a certain date)
|
||||
:param timeframe: Timeframe to check
|
||||
:param candle_type: Candle-type
|
||||
:param since_ms: Starting timestamp
|
||||
:return: Candle limit as integer
|
||||
"""
|
||||
timeframe_map = self._api.options["fetchOHLCV"]["maxRecentDaysPerTimeframe"]
|
||||
days = timeframe_map.get(timeframe, 30)
|
||||
|
||||
if candle_type in (CandleType.FUTURES, CandleType.SPOT, CandleType.MARK) and (
|
||||
not since_ms or dt_ts(dt_now() - timedelta(days=days)) < since_ms
|
||||
):
|
||||
return 1000
|
||||
|
||||
return super().ohlcv_candle_limit(timeframe, candle_type, since_ms)
|
||||
|
||||
def _convert_stop_order(self, pair: str, order_id: str, order: CcxtOrder) -> CcxtOrder:
|
||||
if order.get("status", "open") == "closed":
|
||||
# Use orderID as cliendOrderId filter to fetch the regular followup order.
|
||||
# Could be done with "fetch_order" - but clientOid as filter doesn't seem to work
|
||||
# https://www.bitget.com/api-doc/spot/trade/Get-Order-Info
|
||||
|
||||
for method in (
|
||||
self._api.fetch_canceled_and_closed_orders,
|
||||
self._api.fetch_open_orders,
|
||||
):
|
||||
orders = method(pair)
|
||||
orders_f = [order for order in orders if order["clientOrderId"] == order_id]
|
||||
if orders_f:
|
||||
order_reg = orders_f[0]
|
||||
self._log_exchange_response("fetch_stoploss_order1", order_reg)
|
||||
order_reg["id_stop"] = order_reg["id"]
|
||||
order_reg["id"] = order_id
|
||||
order_reg["type"] = "stoploss"
|
||||
order_reg["status_stop"] = "triggered"
|
||||
return order_reg
|
||||
order = self._order_contracts_to_amount(order)
|
||||
order["type"] = "stoploss"
|
||||
return order
|
||||
|
||||
def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder:
|
||||
params2 = {
|
||||
"stop": True,
|
||||
}
|
||||
for method in (
|
||||
self._api.fetch_open_orders,
|
||||
self._api.fetch_canceled_and_closed_orders,
|
||||
):
|
||||
try:
|
||||
orders = method(pair, params=params2)
|
||||
orders_f = [order for order in orders if order["id"] == order_id]
|
||||
if orders_f:
|
||||
order = orders_f[0]
|
||||
self._log_exchange_response("get_stop_order_fallback", order)
|
||||
return self._convert_stop_order(pair, order_id, order)
|
||||
except (ccxt.OrderNotFound, ccxt.InvalidOrder):
|
||||
pass
|
||||
except ccxt.DDoSProtection as e:
|
||||
raise DDosProtection(e) from e
|
||||
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f"Could not get order due to {e.__class__.__name__}. Message: {e}"
|
||||
) from e
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).")
|
||||
|
||||
@retrier(retries=API_RETRY_COUNT)
|
||||
def fetch_stoploss_order(
|
||||
self, order_id: str, pair: str, params: dict | None = None
|
||||
) -> CcxtOrder:
|
||||
if self._config["dry_run"]:
|
||||
return self.fetch_dry_run_order(order_id)
|
||||
|
||||
return self._fetch_stop_order_fallback(order_id, pair)
|
||||
|
||||
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
|
||||
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
|
||||
@@ -46,10 +46,9 @@ BAD_EXCHANGES = {
|
||||
|
||||
MAP_EXCHANGE_CHILDCLASS = {
|
||||
"binanceus": "binance",
|
||||
"binanceje": "binance",
|
||||
"binanceusdm": "binance",
|
||||
"okex": "okx",
|
||||
"myokx": "okx",
|
||||
"okxus": "okx",
|
||||
"gateio": "gate",
|
||||
"huboi": "htx",
|
||||
}
|
||||
@@ -64,6 +63,7 @@ SUPPORTED_EXCHANGES = [
|
||||
"hyperliquid",
|
||||
"kraken",
|
||||
"okx",
|
||||
"myokx",
|
||||
]
|
||||
|
||||
# either the main, or replacement methods (array) is required
|
||||
|
||||
@@ -213,9 +213,9 @@ def amount_to_precision(
|
||||
amount = float(
|
||||
decimal_to_precision(
|
||||
amount,
|
||||
rounding_mode=TRUNCATE,
|
||||
precision=precision,
|
||||
counting_mode=precisionMode,
|
||||
TRUNCATE, # rounding_mode
|
||||
precision, # numPrecisionDigits
|
||||
precisionMode, # counting_mode
|
||||
)
|
||||
)
|
||||
|
||||
@@ -311,11 +311,11 @@ def price_to_precision(
|
||||
return float(
|
||||
decimal_to_precision(
|
||||
price,
|
||||
rounding_mode=rounding_mode,
|
||||
precision=int(price_precision)
|
||||
rounding_mode, # rounding mode
|
||||
int(price_precision)
|
||||
if precisionMode != TICK_SIZE
|
||||
else price_precision,
|
||||
counting_mode=precisionMode,
|
||||
else price_precision, # numPrecisionDigits
|
||||
precisionMode, # counting_mode
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -287,3 +287,14 @@ class Okx(Exchange):
|
||||
orders_open = self._api.fetch_open_orders(pair, since=since_ms)
|
||||
orders.extend(orders_open)
|
||||
return orders
|
||||
|
||||
|
||||
class MyOkx(Okx):
|
||||
"""
|
||||
MyOkx exchange class.
|
||||
Minimal adjustment to disable futures trading for the EU subsidiary of Okx
|
||||
"""
|
||||
|
||||
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||
(TradingMode.SPOT, MarginMode.NONE),
|
||||
]
|
||||
|
||||
@@ -493,7 +493,7 @@ class FreqaiDataDrawer:
|
||||
|
||||
dk.data["data_path"] = str(dk.data_path)
|
||||
dk.data["model_filename"] = str(dk.model_filename)
|
||||
dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns)
|
||||
dk.data["training_features_list"] = dk.training_features_list
|
||||
dk.data["label_list"] = dk.label_list
|
||||
|
||||
with (save_path / f"{dk.model_filename}_{METADATA}.json").open("w") as fp:
|
||||
|
||||
@@ -514,12 +514,7 @@ class IFreqaiModel(ABC):
|
||||
current coin/bot loop
|
||||
"""
|
||||
|
||||
if "training_features_list_raw" in dk.data:
|
||||
feature_list = dk.data["training_features_list_raw"]
|
||||
else:
|
||||
feature_list = dk.data["training_features_list"]
|
||||
|
||||
if dk.training_features_list != feature_list:
|
||||
if dk.training_features_list != dk.data["training_features_list"]:
|
||||
raise OperationalException(
|
||||
"Trying to access pretrained model with `identifier` "
|
||||
"but found different features furnished by current strategy. "
|
||||
|
||||
@@ -13,8 +13,8 @@ from freqtrade.loggers.set_log_levels import (
|
||||
reduce_verbosity_for_bias_tester,
|
||||
restore_verbosity_for_bias_tester,
|
||||
)
|
||||
from freqtrade.optimize.analysis.base_analysis import BaseAnalysis, VarHolder
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.optimize.base_analysis import BaseAnalysis, VarHolder
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -70,34 +70,29 @@ class LookaheadAnalysis(BaseAnalysis):
|
||||
cut_df: DataFrame = cut_vars.indicators[current_pair]
|
||||
full_df: DataFrame = full_vars.indicators[current_pair]
|
||||
|
||||
# cut longer dataframe to length of the shorter
|
||||
full_df_cut = full_df[(full_df.date == cut_vars.compared_dt)].reset_index(drop=True)
|
||||
cut_df_cut = cut_df[(cut_df.date == cut_vars.compared_dt)].reset_index(drop=True)
|
||||
# trim full_df to the same index and length as cut_df
|
||||
cut_full_df = full_df.loc[cut_df.index]
|
||||
compare_df = cut_full_df.compare(cut_df)
|
||||
|
||||
# check if dataframes are not empty
|
||||
if full_df_cut.shape[0] != 0 and cut_df_cut.shape[0] != 0:
|
||||
# compare dataframes
|
||||
compare_df = full_df_cut.compare(cut_df_cut)
|
||||
if compare_df.shape[0] > 0:
|
||||
for col_name in compare_df:
|
||||
col_idx = compare_df.columns.get_loc(col_name)
|
||||
compare_df_row = compare_df.iloc[0]
|
||||
# compare_df now comprises tuples with [1] having either 'self' or 'other'
|
||||
if "other" in col_name[1]:
|
||||
continue
|
||||
self_value = compare_df_row.iloc[col_idx]
|
||||
other_value = compare_df_row.iloc[col_idx + 1]
|
||||
|
||||
if compare_df.shape[0] > 0:
|
||||
for col_name, values in compare_df.items():
|
||||
col_idx = compare_df.columns.get_loc(col_name)
|
||||
compare_df_row = compare_df.iloc[0]
|
||||
# compare_df now comprises tuples with [1] having either 'self' or 'other'
|
||||
if "other" in col_name[1]:
|
||||
continue
|
||||
self_value = compare_df_row.iloc[col_idx]
|
||||
other_value = compare_df_row.iloc[col_idx + 1]
|
||||
|
||||
# output differences
|
||||
if self_value != other_value:
|
||||
if not self.current_analysis.false_indicators.__contains__(col_name[0]):
|
||||
self.current_analysis.false_indicators.append(col_name[0])
|
||||
logger.info(
|
||||
f"=> found look ahead bias in indicator "
|
||||
f"{col_name[0]}. "
|
||||
f"{str(self_value)} != {str(other_value)}"
|
||||
)
|
||||
# output differences
|
||||
if self_value != other_value:
|
||||
if not self.current_analysis.false_indicators.__contains__(col_name[0]):
|
||||
self.current_analysis.false_indicators.append(col_name[0])
|
||||
logger.info(
|
||||
f"=> found look ahead bias in column "
|
||||
f"{col_name[0]}. "
|
||||
f"{str(self_value)} != {str(other_value)}"
|
||||
)
|
||||
|
||||
def prepare_data(self, varholder: VarHolder, pairs_to_load: list[DataFrame]):
|
||||
if "freqai" in self.local_config and "identifier" in self.local_config["freqai"]:
|
||||
@@ -132,7 +127,13 @@ class LookaheadAnalysis(BaseAnalysis):
|
||||
varholder.data, varholder.timerange = backtesting.load_bt_data()
|
||||
varholder.timeframe = backtesting.timeframe
|
||||
|
||||
varholder.indicators = backtesting.strategy.advise_all_indicators(varholder.data)
|
||||
temp_indicators = backtesting.strategy.advise_all_indicators(varholder.data)
|
||||
filled_indicators = dict()
|
||||
for pair, dataframe in temp_indicators.items():
|
||||
filled_indicators[pair] = backtesting.strategy.ft_advise_signals(
|
||||
dataframe, {"pair": pair}
|
||||
)
|
||||
varholder.indicators = filled_indicators
|
||||
varholder.result = self.get_result(backtesting, varholder.indicators)
|
||||
|
||||
def fill_entry_and_exit_varHolders(self, result_row):
|
||||
@@ -171,23 +172,23 @@ class LookaheadAnalysis(BaseAnalysis):
|
||||
self.fill_entry_and_exit_varHolders(result_row)
|
||||
|
||||
# this will trigger a logger-message
|
||||
buy_or_sell_biased: bool = False
|
||||
entry_or_exit_biased: bool = False
|
||||
|
||||
# register if buy signal is broken
|
||||
if not self.report_signal(
|
||||
self.entry_varHolders[idx].result, "open_date", self.entry_varHolders[idx].compared_dt
|
||||
):
|
||||
self.current_analysis.false_entry_signals += 1
|
||||
buy_or_sell_biased = True
|
||||
entry_or_exit_biased = True
|
||||
|
||||
# register if buy or sell signal is broken
|
||||
if not self.report_signal(
|
||||
self.exit_varHolders[idx].result, "close_date", self.exit_varHolders[idx].compared_dt
|
||||
):
|
||||
self.current_analysis.false_exit_signals += 1
|
||||
buy_or_sell_biased = True
|
||||
entry_or_exit_biased = True
|
||||
|
||||
if buy_or_sell_biased:
|
||||
if entry_or_exit_biased:
|
||||
logger.info(
|
||||
f"found lookahead-bias in trade "
|
||||
f"pair: {result_row['pair']}, "
|
||||
|
||||
@@ -13,9 +13,8 @@ from freqtrade.loggers.set_log_levels import (
|
||||
reduce_verbosity_for_bias_tester,
|
||||
restore_verbosity_for_bias_tester,
|
||||
)
|
||||
from freqtrade.optimize.analysis.base_analysis import BaseAnalysis, VarHolder
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.optimize.base_analysis import BaseAnalysis, VarHolder
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -33,13 +32,6 @@ class RecursiveAnalysis(BaseAnalysis):
|
||||
|
||||
super().__init__(config, strategy_obj)
|
||||
|
||||
strat = StrategyResolver.load_strategy(config)
|
||||
self._strat_scc = strat.startup_candle_count
|
||||
|
||||
if self._strat_scc not in self._startup_candle:
|
||||
self._startup_candle.append(self._strat_scc)
|
||||
self._startup_candle.sort()
|
||||
|
||||
self.partial_varHolder_array: list[VarHolder] = []
|
||||
self.partial_varHolder_lookahead_array: list[VarHolder] = []
|
||||
|
||||
@@ -146,8 +138,16 @@ class RecursiveAnalysis(BaseAnalysis):
|
||||
|
||||
backtesting = Backtesting(prepare_data_config, self.exchange)
|
||||
self.exchange = backtesting.exchange
|
||||
self.local_config["candle_type_def"] = prepare_data_config["candle_type_def"]
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
|
||||
strat = backtesting.strategy
|
||||
self._strat_scc = strat.startup_candle_count
|
||||
|
||||
if self._strat_scc not in self._startup_candle:
|
||||
self._startup_candle.append(self._strat_scc)
|
||||
self._startup_candle.sort()
|
||||
|
||||
varholder.data, varholder.timerange = backtesting.load_bt_data()
|
||||
varholder.timeframe = backtesting.timeframe
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class RecursiveAnalysisSubFunctions:
|
||||
@staticmethod
|
||||
def text_table_recursive_analysis_instances(recursive_instances: list[RecursiveAnalysis]):
|
||||
startups = recursive_instances[0]._startup_candle
|
||||
strat_scc = recursive_instances[0]._strat_scc
|
||||
strat_scc = getattr(recursive_instances[0], "_strat_scc", 0) or 0
|
||||
headers = ["Indicators"]
|
||||
for candle in startups:
|
||||
if candle == strat_scc:
|
||||
|
||||
@@ -124,7 +124,7 @@ class Backtesting:
|
||||
self.trade_id_counter: int = 0
|
||||
self.order_id_counter: int = 0
|
||||
|
||||
config["dry_run"] = True
|
||||
self.config["dry_run"] = True
|
||||
self.price_pair_prec: dict[str, Series] = {}
|
||||
self.run_ids: dict[str, str] = {}
|
||||
self.strategylist: list[IStrategy] = []
|
||||
@@ -137,6 +137,7 @@ class Backtesting:
|
||||
self.rejected_dict: dict[str, list] = {}
|
||||
|
||||
self._exchange_name = self.config["exchange"]["name"]
|
||||
self.__initial_backtest = exchange is None
|
||||
if not exchange:
|
||||
exchange = ExchangeResolver.load_exchange(self.config, load_leverage_tiers=True)
|
||||
self.exchange = exchange
|
||||
@@ -179,20 +180,7 @@ class Backtesting:
|
||||
|
||||
if len(self.pairlists.whitelist) == 0:
|
||||
raise OperationalException("No pair in whitelist.")
|
||||
|
||||
if config.get("fee", None) is not None:
|
||||
self.fee = config["fee"]
|
||||
logger.info(f"Using fee {self.fee:.4%} from config.")
|
||||
else:
|
||||
fees = [
|
||||
self.exchange.get_fee(
|
||||
symbol=self.pairlists.whitelist[0],
|
||||
taker_or_maker=mt, # type: ignore
|
||||
)
|
||||
for mt in ("taker", "maker")
|
||||
]
|
||||
self.fee = max(fee for fee in fees if fee is not None)
|
||||
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
|
||||
self.set_fee()
|
||||
self.precision_mode = self.exchange.precisionMode
|
||||
self.precision_mode_price = self.exchange.precision_mode_price
|
||||
|
||||
@@ -217,8 +205,8 @@ class Backtesting:
|
||||
# This value should NOT be written to startup_candle_count
|
||||
self.required_startup = self.dataprovider.get_required_startup(self.timeframe)
|
||||
|
||||
self.trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT)
|
||||
self.margin_mode: MarginMode = config.get("margin_mode", MarginMode.ISOLATED)
|
||||
self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT)
|
||||
self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.ISOLATED)
|
||||
# strategies which define "can_short=True" will fail to load in Spot mode.
|
||||
self._can_short = self.trading_mode != TradingMode.SPOT
|
||||
self._position_stacking: bool = self.config.get("position_stacking", False)
|
||||
@@ -238,6 +226,30 @@ class Backtesting:
|
||||
"PrecisionFilter not allowed for backtesting multiple strategies."
|
||||
)
|
||||
|
||||
def log_once(self, msg: str) -> None:
|
||||
"""
|
||||
Partial reimplementation of log_once from the Login mixin.
|
||||
only used by recursive, as __initial_backtest is false in all other cases.
|
||||
|
||||
"""
|
||||
if self.__initial_backtest:
|
||||
logger.info(msg)
|
||||
|
||||
def set_fee(self):
|
||||
if self.config.get("fee", None) is not None:
|
||||
self.fee = self.config["fee"]
|
||||
self.log_once(f"Using fee {self.fee:.4%} from config.")
|
||||
else:
|
||||
fees = [
|
||||
self.exchange.get_fee(
|
||||
symbol=self.pairlists.whitelist[0],
|
||||
taker_or_maker=mt,
|
||||
)
|
||||
for mt in ("taker", "maker")
|
||||
]
|
||||
self.fee = max(fee for fee in fees if fee is not None)
|
||||
self.log_once(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
LoggingMixin.show_output = True
|
||||
@@ -1649,7 +1661,7 @@ class Backtesting:
|
||||
pair_detail = self.get_detail_data(pair, row)
|
||||
if pair_detail is not None:
|
||||
pair_detail_cache[pair] = pair_detail
|
||||
row = pair_detail_cache[pair][idx]
|
||||
row = pair_detail_cache[pair][idx]
|
||||
|
||||
is_last_row = current_time_det == end_date
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ def text_table_strategy(strategy_results, stake_currency: str, title: str):
|
||||
|
||||
|
||||
def text_table_add_metrics(strat_results: dict) -> None:
|
||||
stake = strat_results["stake_currency"]
|
||||
if len(strat_results["trades"]) > 0:
|
||||
best_trade = max(strat_results["trades"], key=lambda x: x["profit_ratio"])
|
||||
worst_trade = min(strat_results["trades"], key=lambda x: x["profit_ratio"])
|
||||
@@ -202,23 +203,19 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
[
|
||||
("", ""), # Empty line to improve readability
|
||||
(
|
||||
"Long / Short",
|
||||
"Long / Short trades",
|
||||
f"{strat_results.get('trade_count_long', 'total_trades')} / "
|
||||
f"{strat_results.get('trade_count_short', 0)}",
|
||||
),
|
||||
("Total profit Long %", f"{strat_results['profit_total_long']:.2%}"),
|
||||
("Total profit Short %", f"{strat_results['profit_total_short']:.2%}"),
|
||||
(
|
||||
"Absolute profit Long",
|
||||
fmt_coin(
|
||||
strat_results["profit_total_long_abs"], strat_results["stake_currency"]
|
||||
),
|
||||
"Long / Short profit %",
|
||||
f"{strat_results['profit_total_long']:.2%} / "
|
||||
f"{strat_results['profit_total_short']:.2%}",
|
||||
),
|
||||
(
|
||||
"Absolute profit Short",
|
||||
fmt_coin(
|
||||
strat_results["profit_total_short_abs"], strat_results["stake_currency"]
|
||||
),
|
||||
f"Long / Short profit {stake}",
|
||||
f"{strat_results['profit_total_long_abs']:.{decimals_per_coin(stake)}f} / "
|
||||
f"{strat_results['profit_total_short_abs']:.{decimals_per_coin(stake)}f}",
|
||||
),
|
||||
]
|
||||
if strat_results.get("trade_count_short", 0) > 0
|
||||
@@ -231,27 +228,34 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
drawdown_metrics.append(
|
||||
("Max % of account underwater", f"{strat_results['max_relative_drawdown']:.2%}")
|
||||
)
|
||||
drawdown_account = (
|
||||
strat_results["max_drawdown_account"]
|
||||
if "max_drawdown_account" in strat_results
|
||||
else strat_results["max_drawdown"]
|
||||
)
|
||||
drawdown_metrics.extend(
|
||||
[
|
||||
(
|
||||
("Absolute Drawdown (Account)", f"{strat_results['max_drawdown_account']:.2%}")
|
||||
if "max_drawdown_account" in strat_results
|
||||
else ("Drawdown", f"{strat_results['max_drawdown']:.2%}")
|
||||
"Absolute drawdown",
|
||||
f"{fmt_coin(strat_results['max_drawdown_abs'], stake)} "
|
||||
f"({drawdown_account:.2%})",
|
||||
),
|
||||
(
|
||||
"Absolute Drawdown",
|
||||
fmt_coin(strat_results["max_drawdown_abs"], strat_results["stake_currency"]),
|
||||
"Drawdown duration",
|
||||
strat_results["drawdown_duration"]
|
||||
if "drawdown_duration" in strat_results
|
||||
else "N/A",
|
||||
),
|
||||
(
|
||||
"Drawdown high",
|
||||
fmt_coin(strat_results["max_drawdown_high"], strat_results["stake_currency"]),
|
||||
"Profit at drawdown start",
|
||||
fmt_coin(strat_results["max_drawdown_high"], stake),
|
||||
),
|
||||
(
|
||||
"Drawdown low",
|
||||
fmt_coin(strat_results["max_drawdown_low"], strat_results["stake_currency"]),
|
||||
"Profit at drawdown end",
|
||||
fmt_coin(strat_results["max_drawdown_low"], stake),
|
||||
),
|
||||
("Drawdown Start", strat_results["drawdown_start"]),
|
||||
("Drawdown End", strat_results["drawdown_end"]),
|
||||
("Drawdown start", strat_results["drawdown_start"]),
|
||||
("Drawdown end", strat_results["drawdown_end"]),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -299,15 +303,15 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
),
|
||||
(
|
||||
"Starting balance",
|
||||
fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["starting_balance"], stake),
|
||||
),
|
||||
(
|
||||
"Final balance",
|
||||
fmt_coin(strat_results["final_balance"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["final_balance"], stake),
|
||||
),
|
||||
(
|
||||
"Absolute profit ",
|
||||
fmt_coin(strat_results["profit_total_abs"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["profit_total_abs"], stake),
|
||||
),
|
||||
("Total profit %", f"{strat_results['profit_total']:.2%}"),
|
||||
("CAGR %", f"{strat_results['cagr']:.2%}" if "cagr" in strat_results else "N/A"),
|
||||
@@ -335,16 +339,16 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
"Avg. daily profit",
|
||||
fmt_coin(
|
||||
(strat_results["profit_total_abs"] / strat_results["backtest_days"]),
|
||||
strat_results["stake_currency"],
|
||||
stake,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Avg. stake amount",
|
||||
fmt_coin(strat_results["avg_stake_amount"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["avg_stake_amount"], stake),
|
||||
),
|
||||
(
|
||||
"Total trade volume",
|
||||
fmt_coin(strat_results["total_volume"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["total_volume"], stake),
|
||||
),
|
||||
*short_metrics,
|
||||
("", ""), # Empty line to improve readability
|
||||
@@ -362,11 +366,11 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
("Worst trade", f"{worst_trade['pair']} {worst_trade['profit_ratio']:.2%}"),
|
||||
(
|
||||
"Best day",
|
||||
fmt_coin(strat_results["backtest_best_day_abs"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["backtest_best_day_abs"], stake),
|
||||
),
|
||||
(
|
||||
"Worst day",
|
||||
fmt_coin(strat_results["backtest_worst_day_abs"], strat_results["stake_currency"]),
|
||||
fmt_coin(strat_results["backtest_worst_day_abs"], stake),
|
||||
),
|
||||
(
|
||||
"Days win/draw/lose",
|
||||
@@ -404,17 +408,17 @@ def text_table_add_metrics(strat_results: dict) -> None:
|
||||
),
|
||||
*entry_adjustment_metrics,
|
||||
("", ""), # Empty line to improve readability
|
||||
("Min balance", fmt_coin(strat_results["csum_min"], strat_results["stake_currency"])),
|
||||
("Max balance", fmt_coin(strat_results["csum_max"], strat_results["stake_currency"])),
|
||||
("Min balance", fmt_coin(strat_results["csum_min"], stake)),
|
||||
("Max balance", fmt_coin(strat_results["csum_max"], stake)),
|
||||
*drawdown_metrics,
|
||||
("Market change", f"{strat_results['market_change']:.2%}"),
|
||||
]
|
||||
print_rich_table(metrics, ["Metric", "Value"], summary="SUMMARY METRICS", justify="left")
|
||||
|
||||
else:
|
||||
start_balance = fmt_coin(strat_results["starting_balance"], strat_results["stake_currency"])
|
||||
start_balance = fmt_coin(strat_results["starting_balance"], stake)
|
||||
stake_amount = (
|
||||
fmt_coin(strat_results["stake_amount"], strat_results["stake_currency"])
|
||||
fmt_coin(strat_results["stake_amount"], stake)
|
||||
if strat_results["stake_amount"] != UNLIMITED_STAKE_AMOUNT
|
||||
else "unlimited"
|
||||
)
|
||||
|
||||
@@ -64,7 +64,7 @@ def store_backtest_results(
|
||||
:param market_change_data: Dataframe containing market change data
|
||||
:param analysis_results: Dictionary containing analysis results
|
||||
"""
|
||||
recordfilename: Path = config["exportfilename"]
|
||||
recordfilename: Path = config["exportdirectory"]
|
||||
zip_filename = _generate_filename(recordfilename, dtappendix, ".zip")
|
||||
base_filename = _generate_filename(recordfilename, dtappendix, "")
|
||||
json_filename = _generate_filename(recordfilename, dtappendix, ".json")
|
||||
|
||||
@@ -598,6 +598,8 @@ def generate_strategy_stats(
|
||||
"timerange": config.get("timerange", ""),
|
||||
"enable_protections": config.get("enable_protections", False),
|
||||
"strategy_name": strategy,
|
||||
"freqaimodel": config.get("freqaimodel", None),
|
||||
"freqai_identifier": config.get("freqai", {}).get("identifier", None),
|
||||
# Parameters relevant for backtesting
|
||||
"stoploss": config["stoploss"],
|
||||
"trailing_stop": config.get("trailing_stop", False),
|
||||
@@ -625,6 +627,7 @@ def generate_strategy_stats(
|
||||
underwater = calculate_max_drawdown(
|
||||
results, value_col="profit_abs", starting_balance=start_balance, relative=True
|
||||
)
|
||||
drawdown_duration = drawdown.low_date - drawdown.high_date
|
||||
|
||||
strat_stats.update(
|
||||
{
|
||||
@@ -635,6 +638,8 @@ def generate_strategy_stats(
|
||||
"drawdown_start_ts": drawdown.high_date.timestamp() * 1000,
|
||||
"drawdown_end": drawdown.low_date.strftime(DATETIME_PRINT_FORMAT),
|
||||
"drawdown_end_ts": drawdown.low_date.timestamp() * 1000,
|
||||
"drawdown_duration": drawdown_duration,
|
||||
"drawdown_duration_s": drawdown_duration.total_seconds(),
|
||||
"max_drawdown_low": drawdown.low_value,
|
||||
"max_drawdown_high": drawdown.high_value,
|
||||
}
|
||||
|
||||
@@ -1187,10 +1187,13 @@ class LocalTrade:
|
||||
"""
|
||||
close_trade_value = self.calc_close_trade_value(rate, amount)
|
||||
|
||||
if amount is None or open_rate is None:
|
||||
if (amount is None) and (open_rate is None):
|
||||
open_trade_value = self.open_trade_value
|
||||
else:
|
||||
open_trade_value = self._calc_open_trade_value(amount, open_rate)
|
||||
# Fall back to trade.amount and self.open_rate if necessary
|
||||
open_trade_value = self._calc_open_trade_value(
|
||||
amount or self.amount, open_rate or self.open_rate
|
||||
)
|
||||
|
||||
if open_trade_value == 0.0:
|
||||
return 0.0
|
||||
|
||||
@@ -31,8 +31,9 @@ class AgeFilter(IPairList):
|
||||
|
||||
self._min_days_listed = self._pairlistconfig.get("min_days_listed", 10)
|
||||
self._max_days_listed = self._pairlistconfig.get("max_days_listed")
|
||||
self._def_candletype = self._config["candle_type_def"]
|
||||
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"])
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._def_candletype)
|
||||
if self._min_days_listed < 1:
|
||||
raise OperationalException("AgeFilter requires min_days_listed to be >= 1")
|
||||
if self._min_days_listed > candle_limit:
|
||||
@@ -100,7 +101,7 @@ class AgeFilter(IPairList):
|
||||
:return: new allowlist
|
||||
"""
|
||||
needed_pairs: ListPairsWithTimeframes = [
|
||||
(p, "1d", self._config["candle_type_def"])
|
||||
(p, "1d", self._def_candletype)
|
||||
for p in pairlist
|
||||
if p not in self._symbolsChecked and p not in self._symbolsCheckFailed
|
||||
]
|
||||
@@ -116,8 +117,8 @@ class AgeFilter(IPairList):
|
||||
if self._enabled:
|
||||
for p in deepcopy(pairlist):
|
||||
daily_candles = (
|
||||
candles[(p, "1d", self._config["candle_type_def"])]
|
||||
if (p, "1d", self._config["candle_type_def"]) in candles
|
||||
candles[(p, "1d", self._def_candletype)]
|
||||
if (p, "1d", self._def_candletype) in candles
|
||||
else None
|
||||
)
|
||||
if not self._validate_pair_loc(p, daily_candles):
|
||||
|
||||
@@ -37,7 +37,6 @@ class MarketCapPairList(IPairList):
|
||||
self._refresh_period = self._pairlistconfig.get("refresh_period", 86400)
|
||||
self._categories = self._pairlistconfig.get("categories", [])
|
||||
self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
|
||||
self._def_candletype = self._config["candle_type_def"]
|
||||
|
||||
_coingecko_config = self._config.get("coingecko", {})
|
||||
|
||||
@@ -118,6 +117,16 @@ class MarketCapPairList(IPairList):
|
||||
},
|
||||
}
|
||||
|
||||
def get_markets_exchange(self):
|
||||
markets = [
|
||||
k
|
||||
for k in self._exchange.get_markets(
|
||||
quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
|
||||
).keys()
|
||||
]
|
||||
|
||||
return markets
|
||||
|
||||
def gen_pairlist(self, tickers: Tickers) -> list[str]:
|
||||
"""
|
||||
Generate the pairlist
|
||||
@@ -133,12 +142,8 @@ class MarketCapPairList(IPairList):
|
||||
else:
|
||||
# Use fresh pairlist
|
||||
# Check if pair quote currency equals to the stake currency.
|
||||
_pairlist = [
|
||||
k
|
||||
for k in self._exchange.get_markets(
|
||||
quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
|
||||
).keys()
|
||||
]
|
||||
_pairlist = self.get_markets_exchange()
|
||||
|
||||
# No point in testing for blacklisted pairs...
|
||||
_pairlist = self.verify_blacklist(_pairlist, logger.info)
|
||||
|
||||
@@ -147,6 +152,31 @@ class MarketCapPairList(IPairList):
|
||||
|
||||
return pairlist
|
||||
|
||||
# Prefixes to test to discover coins like 1000PEPE/USDDT:USDT or KPEPE/USDC (hyperliquid)
|
||||
prefixes = ("1000", "K")
|
||||
|
||||
def resolve_marketcap_pair(
|
||||
self,
|
||||
pair: str,
|
||||
pairlist: list[str],
|
||||
markets: list[str],
|
||||
filtered_pairlist: list[str],
|
||||
) -> str | None:
|
||||
if pair in filtered_pairlist:
|
||||
return None
|
||||
|
||||
if pair in pairlist:
|
||||
return pair
|
||||
|
||||
if pair not in markets:
|
||||
for prefix in self.prefixes:
|
||||
test_prefix = f"{prefix}{pair}"
|
||||
|
||||
if test_prefix in pairlist:
|
||||
return test_prefix
|
||||
|
||||
return None
|
||||
|
||||
def filter_pairlist(self, pairlist: list[str], tickers: dict) -> list[str]:
|
||||
"""
|
||||
Filters and sorts pairlist and returns the whitelist again.
|
||||
@@ -189,21 +219,25 @@ class MarketCapPairList(IPairList):
|
||||
self._marketcap_cache["marketcap"] = marketcap_list
|
||||
|
||||
if marketcap_list:
|
||||
filtered_pairlist = []
|
||||
filtered_pairlist: list[str] = []
|
||||
|
||||
market = self._config["trading_mode"]
|
||||
market = self._exchange._config["trading_mode"]
|
||||
pair_format = f"{self._stake_currency.upper()}"
|
||||
if market == "futures":
|
||||
pair_format += f":{self._stake_currency.upper()}"
|
||||
|
||||
top_marketcap = marketcap_list[: self._max_rank :]
|
||||
markets = self.get_markets_exchange()
|
||||
|
||||
for mc_pair in top_marketcap:
|
||||
test_pair = f"{mc_pair.upper()}/{pair_format}"
|
||||
if test_pair in pairlist and test_pair not in filtered_pairlist:
|
||||
filtered_pairlist.append(test_pair)
|
||||
if len(filtered_pairlist) == self._number_assets:
|
||||
break
|
||||
pair = f"{mc_pair.upper()}/{pair_format}"
|
||||
resolved = self.resolve_marketcap_pair(pair, pairlist, markets, filtered_pairlist)
|
||||
|
||||
if resolved:
|
||||
filtered_pairlist.append(resolved)
|
||||
|
||||
if len(filtered_pairlist) == self._number_assets:
|
||||
break
|
||||
|
||||
if len(filtered_pairlist) > 0:
|
||||
return filtered_pairlist
|
||||
|
||||
@@ -91,7 +91,7 @@ class PercentChangePairList(IPairList):
|
||||
)
|
||||
|
||||
candle_limit = self._exchange.ohlcv_candle_limit(
|
||||
self._lookback_timeframe, self._config["candle_type_def"]
|
||||
self._lookback_timeframe, self._def_candletype
|
||||
)
|
||||
|
||||
if self._lookback_period > candle_limit:
|
||||
|
||||
@@ -40,7 +40,7 @@ class VolatilityFilter(IPairList):
|
||||
|
||||
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
|
||||
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"])
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._def_candletype)
|
||||
if self._days < 1:
|
||||
raise OperationalException("VolatilityFilter requires lookback_days to be >= 1")
|
||||
if self._days > candle_limit:
|
||||
|
||||
@@ -89,7 +89,7 @@ class VolumePairList(IPairList):
|
||||
raise OperationalException(f"key {self._sort_key} not in {SORT_VALUES}")
|
||||
|
||||
candle_limit = self._exchange.ohlcv_candle_limit(
|
||||
self._lookback_timeframe, self._config["candle_type_def"]
|
||||
self._lookback_timeframe, self._def_candletype
|
||||
)
|
||||
if self._lookback_period < 0:
|
||||
raise OperationalException("VolumeFilter requires lookback_period to be >= 0")
|
||||
|
||||
@@ -34,7 +34,7 @@ class RangeStabilityFilter(IPairList):
|
||||
|
||||
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
|
||||
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._config["candle_type_def"])
|
||||
candle_limit = self._exchange.ohlcv_candle_limit("1d", self._def_candletype)
|
||||
if self._days < 1:
|
||||
raise OperationalException("RangeStabilityFilter requires lookback_days to be >= 1")
|
||||
if self._days > candle_limit:
|
||||
|
||||
@@ -54,6 +54,7 @@ def __run_pairlist(job_id: str, config_loc: Config):
|
||||
|
||||
with FtNoDBContext():
|
||||
exchange = get_exchange(config_loc)
|
||||
config_loc["candle_type_def"] = exchange._config["candle_type_def"]
|
||||
pairlists = PairListManager(exchange, config_loc)
|
||||
pairlists.refresh_pairlist()
|
||||
ApiBG.jobs[job_id]["result"] = {
|
||||
|
||||
@@ -342,8 +342,22 @@ class Telegram(RPCHandler):
|
||||
self._loop.run_until_complete(self._startup_telegram())
|
||||
|
||||
async def _startup_telegram(self) -> None:
|
||||
await self._app.initialize()
|
||||
await self._app.start()
|
||||
retries = 3
|
||||
attempt = 0
|
||||
while attempt < retries:
|
||||
try:
|
||||
await self._app.initialize()
|
||||
await self._app.start()
|
||||
break
|
||||
except Exception as ex:
|
||||
logger.error(
|
||||
"Error starting Telegram bot (attempt %d/%d): %s", attempt + 1, retries, ex
|
||||
)
|
||||
attempt += 1
|
||||
if attempt == retries:
|
||||
logger.warning("Telegram init failed.")
|
||||
return
|
||||
await asyncio.sleep(2)
|
||||
if self._app.updater:
|
||||
await self._app.updater.start_polling(
|
||||
bootstrap_retries=-1,
|
||||
|
||||
@@ -225,7 +225,6 @@ class RealParameter(NumericParameter):
|
||||
|
||||
class DecimalParameter(NumericParameter):
|
||||
default: float
|
||||
value: float
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -259,6 +258,14 @@ class DecimalParameter(NumericParameter):
|
||||
low=low, high=high, default=default, space=space, optimize=optimize, load=load, **kwargs
|
||||
)
|
||||
|
||||
@property
|
||||
def value(self) -> float:
|
||||
return self._value
|
||||
|
||||
@value.setter
|
||||
def value(self, new_value: float):
|
||||
self._value = round(new_value, self._decimals)
|
||||
|
||||
def get_space(self, name: str) -> "SKDecimal":
|
||||
"""
|
||||
Create optimization space.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from freqtrade_client.ft_rest_client import FtRestClient
|
||||
|
||||
|
||||
__version__ = "2025.7"
|
||||
__version__ = "2025.8"
|
||||
|
||||
if "dev" in __version__:
|
||||
from pathlib import Path
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Requirements for freqtrade client library
|
||||
requests==2.32.4
|
||||
requests==2.32.5
|
||||
python-rapidjson==1.21
|
||||
|
||||
@@ -40,7 +40,7 @@ dependencies = [
|
||||
"jsonschema",
|
||||
"numpy>2.0,<3.0",
|
||||
"pandas>=2.2.0,<3.0",
|
||||
"TA-Lib<0.6",
|
||||
"TA-Lib<0.7",
|
||||
"ft-pandas-ta",
|
||||
"technical",
|
||||
"tabulate",
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
-r requirements-freqai-rl.txt
|
||||
-r docs/requirements-docs.txt
|
||||
|
||||
ruff==0.12.5
|
||||
mypy==1.17.0
|
||||
pre-commit==4.2.0
|
||||
ruff==0.12.10
|
||||
mypy==1.17.1
|
||||
pre-commit==4.3.0
|
||||
pytest==8.4.1
|
||||
pytest-asyncio==1.1.0
|
||||
pytest-cov==6.2.1
|
||||
@@ -18,15 +18,15 @@ pytest-timeout==2.4.0
|
||||
pytest-xdist==3.8.0
|
||||
isort==6.0.1
|
||||
# For datetime mocking
|
||||
time-machine==2.16.0
|
||||
time-machine==2.19.0
|
||||
|
||||
# Convert jupyter notebooks to markdown documents
|
||||
nbconvert==7.16.6
|
||||
|
||||
# mypy types
|
||||
scipy-stubs==1.16.0.2 # keep in sync with `scipy` in `requirements-hyperopt.txt`
|
||||
scipy-stubs==1.16.1.1 # keep in sync with `scipy` in `requirements-hyperopt.txt`
|
||||
types-cachetools==6.1.0.20250717
|
||||
types-filelock==3.2.7
|
||||
types-requests==2.32.4.20250611
|
||||
types-requests==2.32.4.20250809
|
||||
types-tabulate==0.9.0.20241207
|
||||
types-python-dateutil==2.9.0.20250708
|
||||
types-python-dateutil==2.9.0.20250822
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-r requirements-freqai.txt
|
||||
|
||||
# Required for freqai-rl
|
||||
torch==2.7.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||
torch==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||
gymnasium==0.29.1
|
||||
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
|
||||
stable_baselines3==2.7.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
# Required for freqai
|
||||
scikit-learn==1.7.1
|
||||
joblib==1.5.1
|
||||
joblib==1.5.2
|
||||
catboost==1.2.8; 'arm' not in platform_machine
|
||||
lightgbm==4.6.0
|
||||
xgboost==3.0.2
|
||||
xgboost==3.0.4
|
||||
tensorboard==2.20.0
|
||||
datasieve==0.1.9
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
# Required for hyperopt
|
||||
scipy==1.16.1
|
||||
scikit-learn==1.7.1
|
||||
filelock==3.18.0
|
||||
optuna==4.4.0
|
||||
filelock==3.19.1
|
||||
optuna==4.5.0
|
||||
cmaes==0.12.0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Include all requirements to run the bot.
|
||||
-r requirements.txt
|
||||
|
||||
plotly==6.2.0
|
||||
plotly==6.3.0
|
||||
|
||||
@@ -1,36 +1,40 @@
|
||||
numpy==2.3.2
|
||||
pandas==2.3.1
|
||||
numpy==2.3.2; platform_machine != 'armv7l'
|
||||
numpy==2.2.4; platform_machine == 'armv7l'
|
||||
pandas==2.3.2; platform_machine != 'armv7l'
|
||||
pandas==2.2.3; platform_machine == 'armv7l'
|
||||
bottleneck==1.5.0
|
||||
numexpr==2.11.0
|
||||
# Indicator libraries
|
||||
ft-pandas-ta==0.3.15
|
||||
ta-lib==0.5.5
|
||||
technical==1.5.2
|
||||
ta-lib==0.6.5
|
||||
technical==1.5.3
|
||||
|
||||
ccxt==4.4.96
|
||||
cryptography==45.0.5
|
||||
aiohttp==3.12.14
|
||||
SQLAlchemy==2.0.41
|
||||
ccxt==4.5.2
|
||||
cryptography==45.0.6
|
||||
aiohttp==3.12.15
|
||||
SQLAlchemy==2.0.43
|
||||
python-telegram-bot==22.3
|
||||
# can't be hard-pinned due to telegram-bot pinning httpx with ~
|
||||
httpx>=0.24.1
|
||||
humanize==4.12.3
|
||||
cachetools==6.1.0
|
||||
requests==2.32.4
|
||||
requests==2.32.5
|
||||
urllib3==2.5.0
|
||||
certifi==2025.7.14
|
||||
jsonschema==4.25.0
|
||||
certifi==2025.8.3
|
||||
jsonschema==4.25.1
|
||||
tabulate==0.9.0
|
||||
pycoingecko==3.2.0
|
||||
jinja2==3.1.6
|
||||
joblib==1.5.1
|
||||
joblib==1.5.2
|
||||
rich==14.1.0
|
||||
pyarrow==21.0.0; platform_machine != 'armv7l'
|
||||
pyarrow==21.0.0; platform_machine != 'armv7l' and platform_machine != "aarch64"
|
||||
# TODO: downgrade for aarch64 until https://github.com/apache/arrow/issues/47229 is resolved
|
||||
pyarrow==20.0.0; platform_machine == "aarch64"
|
||||
|
||||
# Load ticker files 30% faster
|
||||
python-rapidjson==1.21
|
||||
# Properly format api responses
|
||||
orjson==3.11.1
|
||||
orjson==3.11.2
|
||||
|
||||
# Notify systemd
|
||||
sdnotify==0.3.2
|
||||
|
||||
10
setup.ps1
10
setup.ps1
@@ -228,16 +228,6 @@ function Main {
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path "$VenvDir\Lib\site-packages\talib")) {
|
||||
# Install TA-Lib using the virtual environment's pip
|
||||
Write-Log "Installing TA-Lib using virtual environment's pip..."
|
||||
python -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib 2>&1 | Out-File $LogFilePath -Append
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Log "Failed to install TA-Lib." -Level 'ERROR'
|
||||
Exit-Script -exitCode 1
|
||||
}
|
||||
}
|
||||
|
||||
# Present options for requirement files
|
||||
$SelectedIndices = Get-UserSelection -prompt "Select which requirement files to install:" -options $RequirementFiles -defaultChoice 'A'
|
||||
|
||||
|
||||
22
setup.sh
22
setup.sh
@@ -91,7 +91,6 @@ function updateenv() {
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
install_talib
|
||||
|
||||
${PYTHON} -m pip install --upgrade -r ${REQUIREMENTS} ${REQUIREMENTS_HYPEROPT} ${REQUIREMENTS_PLOT} ${REQUIREMENTS_FREQAI} ${REQUIREMENTS_FREQAI_RL}
|
||||
if [ $? -ne 0 ]; then
|
||||
@@ -118,25 +117,6 @@ function updateenv() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Install tab lib
|
||||
function install_talib() {
|
||||
if [ -f /usr/local/lib/libta_lib.a ] || [ -f /usr/local/lib/libta_lib.so ] || [ -f /usr/lib/libta_lib.so ]; then
|
||||
echo "ta-lib already installed, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
cd build_helpers && ./install_ta-lib.sh
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Quitting. Please fix the above error before continuing."
|
||||
cd ..
|
||||
exit 1
|
||||
fi;
|
||||
|
||||
cd ..
|
||||
}
|
||||
|
||||
|
||||
# Install bot MacOS
|
||||
function install_macos() {
|
||||
if [ ! -x "$(command -v brew)" ]
|
||||
@@ -257,7 +237,7 @@ function install() {
|
||||
install_redhat
|
||||
else
|
||||
echo "This script does not support your OS."
|
||||
echo "If you have Python version 3.11 - 3.13, pip, virtualenv, ta-lib you can continue."
|
||||
echo "If you have Python version 3.11 - 3.13, pip, virtualenv installed you can continue."
|
||||
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
@@ -133,6 +133,8 @@ def test_list_exchanges(capsys):
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^binance$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bybit$", captured.out, re.MULTILINE)
|
||||
# An exchange not supporting futures
|
||||
assert re.search(r"^kraken$", captured.out, re.MULTILINE)
|
||||
|
||||
# Test with --all
|
||||
args = [
|
||||
@@ -160,6 +162,32 @@ def test_list_exchanges(capsys):
|
||||
assert re.search(r"^bingx$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
|
||||
|
||||
# Only dex
|
||||
args = [
|
||||
"list-exchanges",
|
||||
"--dex",
|
||||
]
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"Exchanges available for Freqtrade.*", captured.out)
|
||||
assert not re.search(r".*binance.*", captured.out)
|
||||
assert not re.search(r".*bingx.*", captured.out)
|
||||
assert re.search(r".*hyperliquid.*", captured.out)
|
||||
|
||||
# Only futures
|
||||
args = [
|
||||
"list-exchanges",
|
||||
"--trading-mode",
|
||||
"futures",
|
||||
]
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"Exchanges available for Freqtrade.*", captured.out)
|
||||
assert re.search(r".*binance.*", captured.out)
|
||||
assert not re.search(r".*kraken.*", captured.out)
|
||||
|
||||
|
||||
def test_list_timeframes(mocker, capsys):
|
||||
api_mock = MagicMock()
|
||||
@@ -1748,6 +1776,27 @@ def test_start_list_data(testdatadir, capsys):
|
||||
captured.out,
|
||||
)
|
||||
|
||||
# Test with regex
|
||||
args = [
|
||||
"list-data",
|
||||
"--pairs",
|
||||
"XMR/.*",
|
||||
"--datadir",
|
||||
str(testdatadir),
|
||||
"--show-timerange",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs["config"] = None
|
||||
start_list_data(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "Found 1 pair / timeframe combinations." in captured.out
|
||||
assert re.search(r".*Pair.*Timeframe.*Type.*From .* To .* Candles .*\n", captured.out)
|
||||
assert "UNITTEST/BTC" not in captured.out
|
||||
assert re.search(
|
||||
r"\n.* XMR/USDT .* 5m .* spot .* 2019-10-11 00:00:00 .* 2019-10-13 11:19:00 .* 2469 |\n",
|
||||
captured.out,
|
||||
)
|
||||
|
||||
|
||||
def test_start_list_trades_data(testdatadir, capsys):
|
||||
args = [
|
||||
@@ -1834,8 +1883,10 @@ def test_backtesting_show(mocker, testdatadir, capsys):
|
||||
sbr = mocker.patch("freqtrade.optimize.optimize_reports.show_backtest_results")
|
||||
args = [
|
||||
"backtesting-show",
|
||||
"--export-directory",
|
||||
f"{testdatadir / 'backtest_results'}",
|
||||
"--export-filename",
|
||||
f"{testdatadir / 'backtest_results/backtest-result.json'}",
|
||||
"backtest-result.json",
|
||||
"--show-pair-list",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
|
||||
@@ -521,7 +521,11 @@ def patch_torch_initlogs(mocker) -> None:
|
||||
mocked_module = types.ModuleType(module_name)
|
||||
sys.modules[module_name] = mocked_module
|
||||
else:
|
||||
mocker.patch("torch._logging._init_logs")
|
||||
try:
|
||||
mocker.patch("torch._logging._init_logs")
|
||||
except ModuleNotFoundError:
|
||||
# Allow running limited tests to run without freqAI dependencies
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user