diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..3fa04cb6f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [xmatthias] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 44ff606b4..4d1f28a0d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,9 +5,17 @@ updates: schedule: interval: daily open-pull-requests-limit: 10 + - package-ecosystem: pip directory: "/" schedule: interval: weekly open-pull-requests-limit: 10 target-branch: develop + +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + target-branch: develop diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 228a60389..216a53bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,9 @@ name: Freqtrade CI on: push: branches: - - master - stable - develop + - ci/* tags: release: types: [published] @@ -20,13 +20,13 @@ jobs: strategy: matrix: os: [ ubuntu-18.04, ubuntu-20.04 ] - python-version: [3.7, 3.8, 3.9] + python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -39,7 +39,7 @@ jobs: - name: pip cache (linux) uses: actions/cache@v2 - if: startsWith(matrix.os, 'ubuntu') + if: runner.os == 'Linux' with: path: ~/.cache/pip key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip @@ -50,8 +50,9 @@ jobs: cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd .. - name: Installation - *nix + if: runner.os == 'Linux' run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip 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 @@ -69,7 +70,7 @@ jobs: if: matrix.python-version == '3.9' - name: Coveralls - if: (startsWith(matrix.os, 'ubuntu-20') && matrix.python-version == '3.8') + if: (runner.os == 'Linux' && matrix.python-version == '3.8') env: # Coveralls token. Not used as secret due to github not providing secrets to forked repositories COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu @@ -101,29 +102,26 @@ jobs: run: | mypy freqtrade scripts - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade CI ${{ matrix.os }}*' - mention: 'here' - mention_if: 'failure' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} + severity: error + details: Freqtrade CI failed on ${{ matrix.os }} + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} build_macos: runs-on: ${{ matrix.os }} strategy: matrix: os: [ macos-latest ] - python-version: [3.7, 3.8, 3.9] + python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -136,7 +134,7 @@ jobs: - name: pip cache (macOS) uses: actions/cache@v2 - if: startsWith(matrix.os, 'macOS') + if: runner.os == 'macOS' with: path: ~/Library/Caches/pip key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip @@ -147,10 +145,11 @@ jobs: cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd .. - name: Installation - macOS + if: runner.os == 'macOS' run: | brew update brew install hdf5 c-blosc - python -m pip install --upgrade pip + python -m pip install --upgrade pip 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 @@ -162,7 +161,7 @@ jobs: pytest --random-order --cov=freqtrade --cov-config=.coveragerc - name: Coveralls - if: (startsWith(matrix.os, 'ubuntu-20') && matrix.python-version == '3.8') + if: (runner.os == 'Linux' && matrix.python-version == '3.8') env: # Coveralls token. Not used as secret due to github not providing secrets to forked repositories COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu @@ -194,17 +193,13 @@ jobs: run: | mypy freqtrade scripts - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade CI ${{ matrix.os }}*' - mention: 'here' - mention_if: 'failure' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} - + severity: info + details: Test Succeeded! + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} build_windows: @@ -212,19 +207,18 @@ jobs: strategy: matrix: os: [ windows-latest ] - python-version: [3.7, 3.8] + python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Pip cache (Windows) uses: actions/cache@preview - if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache key: ${{ matrix.os }}-${{ matrix.python-version }}-pip @@ -257,28 +251,25 @@ jobs: run: | mypy freqtrade scripts - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade CI windows*' - mention: 'here' - mention_if: 'failure' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} + severity: error + details: Test Failed + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} docs_check: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Documentation syntax run: | ./tests/test_docs.sh - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 @@ -288,14 +279,13 @@ jobs: pip install mkdocs mkdocs build - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade Docs*' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} + severity: error + details: Freqtrade doc test failed! + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} cleanup-prior-runs: runs-on: ubuntu-20.04 @@ -306,7 +296,7 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - # Notify on slack only once - when CI completes (and after deploy) in case it's successfull + # Notify only once - when CI completes (and after deploy) in case it's successfull notify-complete: needs: [ build_linux, build_macos, build_windows, docs_check ] runs-on: ubuntu-20.04 @@ -320,14 +310,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade CI*' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} + severity: info + details: Test Completed! + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} deploy: needs: [ build_linux, build_macos, build_windows, docs_check ] @@ -336,10 +325,10 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 @@ -385,7 +374,7 @@ jobs: - name: Set up Docker Buildx id: buildx - uses: crazy-max/ghaction-docker-buildx@v1 + uses: crazy-max/ghaction-docker-buildx@v3.3.1 with: buildx-version: latest qemu-version: latest @@ -400,17 +389,13 @@ jobs: run: | build_helpers/publish_docker_multi.sh - - - name: Slack Notification - uses: lazy-actions/slatify@v3.0.0 + - name: Discord notification + uses: rjstone/discord-webhook-notify@v1 if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) with: - type: ${{ job.status }} - job_name: '*Freqtrade CI Deploy*' - mention: 'here' - mention_if: 'failure' - channel: '#notifications' - url: ${{ secrets.SLACK_WEBHOOK }} + severity: info + details: Deploy Succeeded! + webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} deploy_arm: @@ -420,7 +405,7 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Extract branch name shell: bash diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 95e69be2a..ebb773ad7 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -8,9 +8,9 @@ jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v2.1.0 + uses: peter-evans/dockerhub-description@v2.4.3 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.gitignore b/.gitignore index 16df71194..97f77f779 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Freqtrade rules config*.json *.sqlite +*.sqlite-shm +*.sqlite-wal logfile.txt user_data/* !user_data/strategy/sample_strategy.py @@ -10,6 +12,9 @@ freqtrade-plot.html freqtrade-profit-plot.html freqtrade/rpc/api_server/ui/* +# Macos related +.DS_Store + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 15c174bfe..000000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -os: -- linux -dist: bionic -language: python -python: -- 3.8 -services: - - docker -env: - global: - - IMAGE_NAME=freqtradeorg/freqtrade -install: -- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies; cd .. -- export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH -- export TA_LIBRARY_PATH=${HOME}/dependencies/lib -- export TA_INCLUDE_PATH=${HOME}/dependencies/include -- pip install -r requirements-dev.txt -- pip install -e . -jobs: - - include: - - stage: tests - script: - - pytest --random-order --cov=freqtrade --cov-config=.coveragerc - # Allow failure for coveralls - # - coveralls || true - name: pytest - - script: - - cp config_examples/config_bittrex.example.json config.json - - freqtrade create-userdir --userdir user_data - - freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy - name: backtest - - script: - - cp config_examples/config_bittrex.example.json config.json - - freqtrade create-userdir --userdir user_data - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily - name: hyperopt - - script: flake8 - name: flake8 - - script: - # Test Documentation boxes - - # !!! : is not allowed! - # !!! "title" - Title needs to be quoted! - - grep -Er '^!{3}\s\S+:|^!{3}\s\S+\s[^"]' docs/*; test $? -ne 0 - name: doc syntax - - script: mypy freqtrade scripts - name: mypy - -notifications: - slack: - secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= -cache: - pip: True - directories: - - $HOME/dependencies diff --git a/Dockerfile b/Dockerfile index f7e26efe3..8f5b85698 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9.7-slim-buster as base +FROM python:3.9.9-slim-bullseye as base # Setup env ENV LANG C.UTF-8 diff --git a/README.md b/README.md index 9882bce02..efa334a27 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,14 @@ [![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io) [![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](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. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning. +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. ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png) +## Sponsored promotion + +[![tokenbot-promo](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/TokenBot-Freqtrade-banner.png)](https://tokenbot.com/?utm_source=github&utm_medium=freqtrade&utm_campaign=algodevs) + ## Disclaimer This software is for educational purposes only. Do not risk money which @@ -26,12 +30,13 @@ hesitate to read the source code and understand the mechanism of this bot. Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) +- [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) -- [X] [FTX](https://ftx.com) +- [X] [FTX](https://ftx.com/#a=2258149) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) -- [X] [OKEX](https://www.okex.com/) +- [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested @@ -49,7 +54,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr ## Features -- [x] **Based on Python 3.7+**: For botting on any operating system - Windows, macOS and Linux. +- [x] **Based on Python 3.8+**: For botting on any operating system - Windows, macOS and Linux. - [x] **Persistence**: Persistence is achieved through sqlite. - [x] **Dry-run**: Run the bot without paying money. - [x] **Backtesting**: Run a simulation of your buy/sell strategy. @@ -57,22 +62,16 @@ Please find the complete documentation on the [freqtrade website](https://www.fr - [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/). - [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists. - [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid. +- [x] **Builtin WebUI**: Builtin web UI to manage your bot. - [x] **Manageable via Telegram**: Manage the bot with Telegram. -- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat. -- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss. +- [x] **Display profit/loss in fiat**: Display your profit/loss in fiat currency. - [x] **Performance status report**: Provide a performance status of your current trades. ## Quick start -Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot. +Please refer to the [Docker Quickstart documentation](https://www.freqtrade.io/en/stable/docker_quickstart/) on how to get started quickly. -```bash -git clone -b develop https://github.com/freqtrade/freqtrade.git -cd freqtrade -./setup.sh --install -``` - -For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/). +For further (native) installation methods, please refer to the [Installation documentation page](https://www.freqtrade.io/en/stable/installation/). ## Basic Usage @@ -197,7 +196,7 @@ To run this bot we recommend you a cloud instance with a minimum of: ### Software requirements -- [Python 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/) +- [Python >= 3.8](http://docs.python-guide.org/en/latest/starting/installation/) - [pip](https://pip.pypa.io/en/stable/installing/) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) diff --git a/build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl b/build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl deleted file mode 100644 index bccfd090f..000000000 Binary files a/build_helpers/TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl deleted file mode 100644 index 67b41bf99..000000000 Binary files a/build_helpers/TA_Lib-0.4.21-cp38-cp38-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl deleted file mode 100644 index da9d74558..000000000 Binary files a/build_helpers/TA_Lib-0.4.21-cp39-cp39-win_amd64.whl and /dev/null differ diff --git a/build_helpers/TA_Lib-0.4.24-cp310-cp310-win_amd64.whl b/build_helpers/TA_Lib-0.4.24-cp310-cp310-win_amd64.whl new file mode 100644 index 000000000..9a96b7894 Binary files /dev/null and b/build_helpers/TA_Lib-0.4.24-cp310-cp310-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.24-cp38-cp38-win_amd64.whl b/build_helpers/TA_Lib-0.4.24-cp38-cp38-win_amd64.whl new file mode 100644 index 000000000..f6c66375b Binary files /dev/null and b/build_helpers/TA_Lib-0.4.24-cp38-cp38-win_amd64.whl differ diff --git a/build_helpers/TA_Lib-0.4.24-cp39-cp39-win_amd64.whl b/build_helpers/TA_Lib-0.4.24-cp39-cp39-win_amd64.whl new file mode 100644 index 000000000..84d3e60ab Binary files /dev/null and b/build_helpers/TA_Lib-0.4.24-cp39-cp39-win_amd64.whl differ diff --git a/build_helpers/install_windows.ps1 b/build_helpers/install_windows.ps1 index ec38ea212..4caefa340 100644 --- a/build_helpers/install_windows.ps1 +++ b/build_helpers/install_windows.ps1 @@ -1,19 +1,18 @@ # Downloads don't work automatically, since the URL is regenerated via javascript. # Downloaded from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib -python -m pip install --upgrade pip +python -m pip install --upgrade pip wheel $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" -if ($pyv -eq '3.7') { - pip install build_helpers\TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl -} if ($pyv -eq '3.8') { - pip install build_helpers\TA_Lib-0.4.21-cp38-cp38-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.24-cp38-cp38-win_amd64.whl } if ($pyv -eq '3.9') { - pip install build_helpers\TA_Lib-0.4.21-cp39-cp39-win_amd64.whl + pip install build_helpers\TA_Lib-0.4.24-cp39-cp39-win_amd64.whl +} +if ($pyv -eq '3.10') { + pip install build_helpers\TA_Lib-0.4.24-cp310-cp310-win_amd64.whl } - pip install -r requirements-dev.txt pip install -e . diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index d59ff96cb..c6faf506c 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -9,7 +9,9 @@ "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, - "sell": 30 + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" }, "bid_strategy": { "ask_last_balance": 0.0, diff --git a/config_examples/config_bittrex.example.json b/config_examples/config_bittrex.example.json index 4352d8822..9fe99c835 100644 --- a/config_examples/config_bittrex.example.json +++ b/config_examples/config_bittrex.example.json @@ -9,7 +9,9 @@ "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, - "sell": 30 + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" }, "bid_strategy": { "use_order_book": true, diff --git a/config_examples/config_ftx.example.json b/config_examples/config_ftx.example.json index 4d9633cc0..4f7c2af54 100644 --- a/config_examples/config_ftx.example.json +++ b/config_examples/config_ftx.example.json @@ -9,7 +9,9 @@ "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, - "sell": 30 + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" }, "bid_strategy": { "ask_last_balance": 0.0, diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 228a08a02..7931476b4 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -8,6 +8,7 @@ "amend_last_stake_amount": false, "last_stake_amount_min_ratio": 0.5, "dry_run": true, + "dry_run_wallet": 1000, "cancel_open_orders_on_exit": false, "timeframe": "5m", "trailing_stop": false, @@ -18,6 +19,7 @@ "sell_profit_only": false, "sell_profit_offset": 0.0, "ignore_roi_if_buy_signal": false, + "ignore_buying_expired_candle_after": 300, "minimal_roi": { "40": 0.0, "30": 0.01, @@ -27,7 +29,7 @@ "stoploss": -0.10, "unfilledtimeout": { "buy": 10, - "sell": 30, + "sell": 10, "exit_timeout_count": 0, "unit": "minutes" }, @@ -54,7 +56,8 @@ "forcebuy": "market", "stoploss": "market", "stoploss_on_exchange": false, - "stoploss_on_exchange_interval": 60 + "stoploss_on_exchange_interval": 60, + "stoploss_on_exchange_limit_ratio": 0.99 }, "order_time_in_force": { "buy": "gtc", @@ -85,6 +88,7 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "password": "", + "log_responses": false, "ccxt_config": {}, "ccxt_async_config": {}, "pair_whitelist": [ diff --git a/config_examples/config_kraken.example.json b/config_examples/config_kraken.example.json index 32def895c..5ac3a9255 100644 --- a/config_examples/config_kraken.example.json +++ b/config_examples/config_kraken.example.json @@ -9,7 +9,9 @@ "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, - "sell": 30 + "sell": 10, + "exit_timeout_count": 0, + "unit": "minutes" }, "bid_strategy": { "use_order_book": true, diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index f9827774e..16f2aebcd 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -1,4 +1,4 @@ -FROM python:3.7.10-slim-buster as base +FROM python:3.9.9-slim-bullseye as base # Setup env ENV LANG C.UTF-8 diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index f5a52ff49..9dbb86b2d 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -13,7 +13,7 @@ A sample of this can be found below, which is identical to the Default Hyperopt ``` python from datetime import datetime -from typing import Dict +from typing import Any, Dict from pandas import DataFrame @@ -105,7 +105,7 @@ You can define your own estimator for Hyperopt by implementing `generate_estimat ```python class MyAwesomeStrategy(IStrategy): class HyperOpt: - def generate_estimator(): + def generate_estimator(dimensions: List['Dimension'], **kwargs): return "RF" ``` @@ -119,13 +119,34 @@ Example for `ExtraTreesRegressor` ("ET") with additional parameters: ```python class MyAwesomeStrategy(IStrategy): class HyperOpt: - def generate_estimator(): + def generate_estimator(dimensions: List['Dimension'], **kwargs): from skopt.learning import ExtraTreesRegressor # Corresponds to "ET" - but allows additional parameters. return ExtraTreesRegressor(n_estimators=100) ``` +The `dimensions` parameter is the list of `skopt.space.Dimension` objects corresponding to the parameters to be optimized. It can be used to create isotropic kernels for the `skopt.learning.GaussianProcessRegressor` estimator. Here's an example: + +```python +class MyAwesomeStrategy(IStrategy): + class HyperOpt: + def generate_estimator(dimensions: List['Dimension'], **kwargs): + from skopt.utils import cook_estimator + from skopt.learning.gaussian_process.kernels import (Matern, ConstantKernel) + kernel_bounds = (0.0001, 10000) + kernel = ( + ConstantKernel(1.0, kernel_bounds) * + Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=2.5) + ) + kernel += ( + ConstantKernel(1.0, kernel_bounds) * + Matern(length_scale=np.ones(len(dimensions)), length_scale_bounds=[kernel_bounds for d in dimensions], nu=1.5) + ) + + return cook_estimator("GP", space=dimensions, kernel=kernel, n_restarts_optimizer=2) +``` + !!! Note While custom estimators can be provided, it's up to you as User to do research on possible parameters and analyze / understand which ones should be used. If you're unsure about this, best use one of the Defaults (`"ET"` has proven to be the most versatile) without further parameters. diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 02b0307e5..93a2025ed 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -176,12 +176,15 @@ Log messages are send to `syslog` with the `user` facility. So you can see them On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better. For `rsyslog` the messages from the bot can be redirected into a separate dedicated log file. To achieve this, add + ``` if $programname startswith "freqtrade" then -/var/log/freqtrade.log ``` + to one of the rsyslog configuration files, for example at the end of the `/etc/rsyslog.d/50-default.conf`. For `syslog` (`rsyslog`), the reduction mode can be switched on. This will reduce the number of repeating messages. For instance, multiple bot Heartbeat messages will be reduced to a single message when nothing else happens with the bot. To achieve this, set in `/etc/rsyslog.conf`: + ``` # Filter duplicated messages $RepeatedMsgReduction on diff --git a/docs/assets/TokenBot-Freqtrade-banner.png b/docs/assets/TokenBot-Freqtrade-banner.png new file mode 100644 index 000000000..6087056b6 Binary files /dev/null and b/docs/assets/TokenBot-Freqtrade-banner.png differ diff --git a/docs/assets/plot-profit.png b/docs/assets/plot-profit.png index 88d69a2d4..e9fe6c341 100644 Binary files a/docs/assets/plot-profit.png and b/docs/assets/plot-profit.png differ diff --git a/docs/assets/windows_install.png b/docs/assets/windows_install.png new file mode 100644 index 000000000..530c3047f Binary files /dev/null and b/docs/assets/windows_install.png differ diff --git a/docs/backtesting.md b/docs/backtesting.md index 49a94b05e..e7846b1f8 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -22,6 +22,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] [--export {none,trades}] [--export-filename PATH] [--breakdown {day,week,month} [{day,week,month} ...]] + [--cache {none,day,week,month}] optional arguments: -h, --help show this help message and exit @@ -76,6 +77,9 @@ optional arguments: _today.json` --breakdown {day,week,month} [{day,week,month} ...] Show backtesting breakdown per [day, week, month]. + --cache {none,day,week,month} + Load a cached backtest result no older than specified + age (default: day). Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -115,7 +119,7 @@ The result of backtesting will confirm if your bot has better odds of making a p All profit calculations include fees, and freqtrade will use the exchange's default fees for the calculation. !!! Warning "Using dynamic pairlists for backtesting" - Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist. + Using dynamic pairlists is possible (not all of the handlers are allowed to be used in backtest mode), however it relies on the current market conditions - which will not reflect the historic status of the pairlist. Also, when using pairlists other than StaticPairlist, reproducibility of backtesting-results cannot be guaranteed. Please read the [pairlists documentation](plugins.md#pairlists) for more information. @@ -309,10 +313,11 @@ A backtesting result will look like that: | Avg. Duration Winners | 4:23:00 | | Avg. Duration Loser | 6:55:00 | | Rejected Buy signals | 3089 | +| Entry/Exit Timeouts | 0 / 0 | | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | -| Drawdown | 50.63% | +| Drawdown (Account) | 13.33% | | Drawdown | 0.0015 BTC | | Drawdown high | 0.0013 BTC | | Drawdown low | -0.0002 BTC | @@ -396,10 +401,11 @@ It contains some useful key metrics about performance of your strategy on backte | Avg. Duration Winners | 4:23:00 | | Avg. Duration Loser | 6:55:00 | | Rejected Buy signals | 3089 | +| Entry/Exit Timeouts | 0 / 0 | | | | | Min balance | 0.00945123 BTC | | Max balance | 0.01846651 BTC | -| Drawdown | 50.63% | +| Drawdown (Account) | 13.33% | | Drawdown | 0.0015 BTC | | Drawdown high | 0.0013 BTC | | Drawdown low | -0.0002 BTC | @@ -425,8 +431,10 @@ It contains some useful key metrics about performance of your strategy on backte - `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade). - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Rejected Buy signals`: Buy signals that could not be acted upon due to max_open_trades being reached. +- `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used). - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. -- `Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). +- `Drawdown (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. @@ -456,6 +464,14 @@ freqtrade backtesting --strategy MyAwesomeStrategy --breakdown day month 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. +### Backtest result caching + +To save time, by default backtest will reuse a cached result from within the last day when the backtested strategy and config match that of a previous backtest. To force a new backtest despite existing result for an identical run specify `--cache none` parameter. + +!!! Warning + Caching is automatically disabled for open-ended timeranges (`--timerange 20210101-`), as freqtrade cannot ensure reliably that the underlying data didn't change. It can also use cached results where it shouldn't if the original backtest had missing data at the end, which was fixed by downloading more data. + In this instance, please use `--cache none` once to force a fresh backtest. + ### Further backtest-result analysis To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file). @@ -484,8 +500,8 @@ Since backtesting lacks some detailed information about what happens within a ca - ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies - Sell-reason does not explain if a trade was positive or negative, just what triggered the sell (this can look odd if negative ROI values are used) - Evaluation sequence (if multiple signals happen on the same candle) - - ROI (if not stoploss) - Sell-signal + - ROI (if not stoploss) - Stoploss Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode. diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 80443a0bf..8c6303063 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -38,6 +38,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`. * Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback. * Before a sell order is placed, `confirm_trade_exit()` strategy callback is called. +* Check position adjustments for open trades if enabled by calling `adjust_trade_position()` and place additional order if required. * Check if trade-slots are still available (if `max_open_trades` is reached). * Verifies buy signal trying to enter new positions. * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. @@ -56,7 +57,12 @@ This loop will be repeated again and again until the bot is stopped. * Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair). * Loops per candle simulating entry and exit points. * Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy). + * Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle). + * Determine stake size by calling the `custom_stake_amount()` callback. + * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested. * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. + * For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). + * Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_buy_timeout()` / `check_sell_timeout()` strategy callbacks. * Generate backtest report output !!! Note diff --git a/docs/configuration.md b/docs/configuration.md index 6c810fba2..d702fe8f9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -126,14 +126,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `exchange.key` | API key to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `exchange.uid` | API uid to use for the exchange. Only required when you are in production mode and for exchanges that use uid for API requests.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Supports regex pairs as `.*/BTC`. Not used by VolumePairList. [More information](plugins.md#pairlists-and-pairlist-handlers).
**Datatype:** List | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting. [More information](plugins.md#pairlists-and-pairlist-handlers).
**Datatype:** List -| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict +| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for additional ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation). Please avoid adding exchange secrets here (use the dedicated fields instead), as they may be contained in logs.
**Datatype:** Dict | `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict | `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
**Datatype:** Dict | `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded.
*Defaults to `60` minutes.*
**Datatype:** Positive Integer | `exchange.skip_pair_validation` | Skip pairlist validation on startup.
*Defaults to `false`
**Datatype:** Boolean | `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.
*Defaults to `false`
**Datatype:** Boolean +| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".
*Defaults to `None`
**Datatype:** float | `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.
*Defaults to `false`
**Datatype:** Boolean | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
*Defaults to `true`.*
**Datatype:** Boolean @@ -170,6 +172,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String +| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 ### Parameters in the strategy @@ -194,6 +198,8 @@ Values set in the configuration file always overwrite values set in the strategy * `sell_profit_offset` * `ignore_roi_if_buy_signal` * `ignore_buying_expired_candle_after` +* `position_adjustment_enable` +* `max_entry_position_adjustment` ### Configuring amount per trade @@ -202,9 +208,8 @@ There are several methods to configure how much of the stake currency the bot wi #### Minimum trade stake The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages. -Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$. -The minimum stake amount to buy this pair is, therefore, `20 * 0.6 ~= 12`. +Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`. This exchange has also a limit on USD - where all orders must be > 10$ - which however does not apply in this case. To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%). @@ -301,6 +306,15 @@ To allow the bot to trade all the available `stake_currency` in your account (mi When using `"stake_amount" : "unlimited",` in combination with Dry-Run, Backtesting or Hyperopt, the balance will be simulated starting with a stake of `dry_run_wallet` which will evolve. It is therefore important to set `dry_run_wallet` to a sensible value (like 0.05 or 0.01 for BTC and 1000 or 100 for USDT, for example), otherwise, it may simulate trades with 100 BTC (or more) or 0.05 USDT (or less) at once - which may not correspond to your real available balance or is less than the exchange minimal limit for the order amount for the stake currency. +#### Dynamic stake amount with position adjustment + +When you want to use position adjustment with unlimited stakes, you must also implement `custom_stake_amount` to a return a value depending on your strategy. +Typical value would be in the range of 25% - 50% of the proposed stakes, but depends highly on your strategy and how much you wish to leave into the wallet as position adjustment buffer. + +For example if your position adjustment assumes it can do 2 additional buys with the same stake amounts then your buffer should be 66.6667% of the initially proposed unlimited stake amount. + +Or another example if your position adjustment assumes it can do 1 additional buy with 3x the original stake amount then `custom_stake_amount` should return 25% of proposed stake amount and leave 75% for possible later position adjustments. + --8<-- "includes/pricing.md" ### Understand minimal_roi diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 17da98935..9a79ee5ed 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -1,6 +1,6 @@ -# Analyzing bot data with Jupyter notebooks +# Analyzing bot data with Jupyter notebooks -You can analyze the results of backtests and trading history easily using Jupyter notebooks. Sample notebooks are located at `user_data/notebooks/` after initializing the user directory with `freqtrade create-userdir --userdir user_data`. +You can analyze the results of backtests and trading history easily using Jupyter notebooks. Sample notebooks are located at `user_data/notebooks/` after initializing the user directory with `freqtrade create-userdir --userdir user_data`. ## Quick start with docker @@ -41,32 +41,35 @@ ipython kernel install --user --name=freqtrade !!! Warning Some tasks don't work especially well in notebooks. For example, anything using asynchronous execution is a problem for Jupyter. Also, freqtrade's primary entry point is the shell cli, so using pure python in a notebook bypasses arguments that provide required objects and parameters to helper functions. You may need to set those values or create expected objects manually. -## Recommended workflow +## Recommended workflow -| Task | Tool | - --- | --- -Bot operations | CLI +| Task | Tool | + --- | --- +Bot operations | CLI Repetitive tasks | Shell scripts -Data analysis & visualization | Notebook +Data analysis & visualization | Notebook 1. Use the CLI to + * download historical data * run a backtest * run with real-time data - * export results + * export results 1. Collect these actions in shell scripts + * save complicated commands with arguments - * execute multi-step operations + * execute multi-step operations * automate testing strategies and preparing data for analysis 1. Use a notebook to + * visualize data - * munge and plot to generate insights + * mangle and plot to generate insights -## Example utility snippets +## Example utility snippets -### Change directory to root +### Change directory to root Jupyter notebooks execute from the notebook directory. The following snippet searches for the project root, so relative paths remain consistent. diff --git a/docs/deprecated.md b/docs/deprecated.md index d86a7ac7a..eaf85bfbf 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -15,8 +15,8 @@ This command line option was deprecated in 2019.7-dev (develop branch) and remov ### The **--dynamic-whitelist** command line option -This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) -and in freqtrade 2019.7. +This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) and in freqtrade 2019.7. +Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead. ### the `--live` command line option diff --git a/docs/developer.md b/docs/developer.md index b69a70aa3..ee4bac5c2 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -324,9 +324,8 @@ jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown freqtrade This documents some decisions taken for the CI Pipeline. * CI runs on all OS variants, Linux (ubuntu), macOS and Windows. -* Docker images are build for the branches `stable` and `develop`. +* Docker images are build for the branches `stable` and `develop`, and are built as multiarch builds, supporting multiple platforms via the same tag. * Docker images containing Plot dependencies are also available as `stable_plot` and `develop_plot`. -* Raspberry PI Docker images are postfixed with `_pi` - so tags will be `:stable_pi` and `develop_pi`. * 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. diff --git a/docs/docker_quickstart.md b/docs/docker_quickstart.md index 95df37811..84c1d596a 100644 --- a/docs/docker_quickstart.md +++ b/docs/docker_quickstart.md @@ -126,6 +126,12 @@ All freqtrade arguments will be available by running `docker-compose run --rm fr !!! Note "`docker-compose run --rm`" Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command). +??? Note "Using docker without docker-compose" + "`docker-compose run --rm`" will require a compose file to be provided. + Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead. + For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`. + This can be useful for fetching exchange information to add to your `config.json` without affecting your running containers. + #### Example: Download data with docker-compose Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host. diff --git a/docs/exchanges.md b/docs/exchanges.md index 3883e0b1d..c2368170d 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -57,7 +57,7 @@ This configuration enables kraken, as well as rate-limiting to avoid bans from t Binance supports [time_in_force](configuration.md#understand-order_time_in_force). !!! Tip "Stoploss on Exchange" - Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. + Binance 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.. ### Binance Blacklist @@ -177,18 +177,27 @@ 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). +!!! 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. + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used. + ### Kucoin Blacklists For Kucoin, please add `"KCS/"` to your blacklist to avoid issues. Accounts having KCS accounts use this to pay for fees - if your first trade happens to be on `KCS`, further trades will consume this position and make the initial KCS trade unsellable as the expected amount is not there anymore. -## OKEX +## Huobi -OKEX 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: +!!! Tip "Stoploss on Exchange" + Huobi supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. + +## OKX (former OKEX) + +OKX 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": "okex", + "name": "okx", "key": "your_exchange_key", "secret": "your_exchange_secret", "password": "your_exchange_api_key_password", @@ -197,7 +206,15 @@ OKEX requires a passphrase for each api key, you will therefore need to add this ``` !!! Warning - OKEX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode. + OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode. + +## 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 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. ## All exchanges diff --git a/docs/faq.md b/docs/faq.md index 8957507dd..27bc077ec 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -188,12 +188,12 @@ There is however nothing preventing you from using GPU-enabled indicators within Per default Hyperopt called without the `-e`/`--epochs` command line option will only run 100 epochs, means 100 evaluations of your triggers, guards, ... Too few to find a great result (unless if you are very lucky), so you probably -have to run it for 10.000 or more. But it will take an eternity to +have to run it for 10000 or more. But it will take an eternity to compute. Since hyperopt uses Bayesian search, running for too many epochs may not produce greater results. -It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. +It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. ```bash freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000 @@ -217,9 +217,9 @@ already 8\*10^9\*10 evaluations. A roughly total of 80 billion evaluations. Did you run 100 000 evaluations? Congrats, you've done roughly 1 / 100 000 th of the search space, assuming that the bot never tests the same parameters more than once. -* The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 10.0000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. +* The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 100000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. -Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. +Example: 4% profit 650 times vs 0,3% profit a trade 10000 times in a year. If we assume you set the --timerange to 365 days. Example: `freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601` diff --git a/docs/hyperopt.md b/docs/hyperopt.md index b7b6cb772..19d8cd692 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -116,7 +116,7 @@ optional arguments: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily, - CalmarHyperOptLoss, MaxDrawDownHyperOptLoss + CalmarHyperOptLoss, MaxDrawDownHyperOptLoss, ProfitDrawDownHyperOptLoss --disable-param-export Disable automatic hyperopt parameter export. --ignore-missing-spaces, --ignore-unparameterized-spaces @@ -508,6 +508,46 @@ class MyAwesomeStrategy(IStrategy): You will then obviously also change potential interesting entries to parameters to allow hyper-optimization. +### Optimizing `max_entry_position_adjustment` + +While `max_entry_position_adjustment` is not a separate space, it can still be used in hyperopt by using the property approach shown above. + +``` python +from pandas import DataFrame +from functools import reduce + +import talib.abstract as ta + +from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, + IStrategy, IntParameter) +import freqtrade.vendor.qtpylib.indicators as qtpylib + +class MyAwesomeStrategy(IStrategy): + stoploss = -0.05 + timeframe = '15m' + + # Define the parameter spaces + max_epa = CategoricalParameter([-1, 0, 1, 3, 5, 10], default=1, space="buy", optimize=True) + + @property + def max_entry_position_adjustment(self): + return self.max_epa.value + + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # ... +``` + +??? Tip "Using `IntParameter`" + You can also use the `IntParameter` for this optimization, but you must explicitly return an integer: + ``` python + max_epa = IntParameter(-1, 10, default=1, space="buy", optimize=True) + + @property + def max_entry_position_adjustment(self): + return int(self.max_epa.value) + ``` + ## Loss-functions Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results. @@ -525,6 +565,7 @@ Currently, the following loss functions are builtin: * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. * `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. +* `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes. Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation. diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md index 4f56f8e98..cec5ceb19 100644 --- a/docs/includes/pairlists.md +++ b/docs/includes/pairlists.md @@ -196,7 +196,7 @@ Trade count is used as a tie breaker. You can use the `minutes` parameter to only consider performance of the past X minutes (rolling window). Not defining this parameter (or setting it to 0) will use all-time performance. -The optional `min_profit` parameter defines the minimum profit a pair must have to be considered. +The optional `min_profit` (as ratio -> a setting of `0.01` corresponds to 1%) parameter defines the minimum profit a pair must have to be considered. Pairs below this level will be filtered out. Using this parameter without `minutes` is highly discouraged, as it can lead to an empty pairlist without a way to recover. @@ -206,7 +206,7 @@ Using this parameter without `minutes` is highly discouraged, as it can lead to { "method": "PerformanceFilter", "minutes": 1440, // rolling 24h - "min_profit": 0.01 + "min_profit": 0.01 // minimal profit 1% } ], ``` @@ -220,6 +220,9 @@ As this Filter uses past performance of the bot, it'll have some startup-period Filters low-value coins which would not allow setting stoplosses. +!!! Warning "Backtesting" + `PrecisionFilter` does not support backtesting mode using multiple strategies. + #### PriceFilter The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported: @@ -243,7 +246,7 @@ On exchanges that deduct fees from the receiving currency (e.g. FTX) - this can The `low_price_ratio` setting removes pairs where a raise of 1 price unit (pip) is above the `low_price_ratio` ratio. This option is disabled by default, and will only apply if set to > 0. -For `PriceFiler` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. +For `PriceFilter` at least one of its `min_price`, `max_price` or `low_price_ratio` settings must be applied. Calculation example: @@ -257,7 +260,7 @@ Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 - Shuffles (randomizes) pairs in the pairlist. It can be used for preventing the bot from trading some of the pairs more frequently then others when you want all pairs be treated with the same priority. !!! Tip - You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order. + You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order. ShuffleFilter will automatically detect runmodes and apply the `seed` only for backtesting modes - if a `seed` value is set. #### SpreadFilter @@ -292,7 +295,7 @@ If the trading range over the last 10 days is <1% or >99%, remove the pair from #### VolatilityFilter -Volatility is the degree of historical variation of a pairs over time, is is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)). +Volatility is the degree of historical variation of a pairs over time, it is measured by the standard deviation of logarithmic daily returns. Returns are assumed to be normally distributed, although actual distribution might be different. In a normal distribution, 68% of observations fall within one standard deviation and 95% of observations fall within two standard deviations. Assuming a volatility of 0.05 means that the expected returns for 20 out of 30 days is expected to be less than 5% (one standard deviation). Volatility is a positive ratio of the expected deviation of return and can be greater than 1.00. Please refer to the wikipedia definition of [`volatility`](https://en.wikipedia.org/wiki/Volatility_(finance)). This filter removes pairs if the average volatility over a `lookback_days` days is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`. diff --git a/docs/index.md b/docs/index.md index 292955346..2aa80c240 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ ## Introduction -Freqtrade is a crypto-currency algorithmic trading software developed in python (3.7+) and supported on Windows, macOS and Linux. +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. !!! Danger "DISCLAIMER" This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. @@ -20,6 +20,12 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python We strongly recommend you to have basic coding skills and Python knowledge. Do not hesitate to read the source code and understand the mechanisms of this bot, algorithms and techniques implemented in it. +![freqtrade screenshot](assets/freqtrade-screenshot.png) + +## Sponsored promotion + +[![tokenbot-promo](assets/TokenBot-Freqtrade-banner.png)](https://tokenbot.com/?utm_source=github&utm_medium=freqtrade&utm_campaign=algodevs) + ## Features - Develop your Strategy: Write your strategy in python, using [pandas](https://pandas.pydata.org/). Example strategies to inspire you are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). @@ -29,19 +35,20 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python - Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade. - Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode). - Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital. -- Control/Monitor: Use Telegram or a REST API (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.). +- Control/Monitor: Use Telegram or a WebUI (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.). - Analyse: Further analysis can be performed on either Backtesting data or Freqtrade trading history (SQL database), including automated standard plots, and methods to load the data into [interactive environments](data-analysis.md). ## Supported exchange marketplaces Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#binance-blacklist)) +- [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) -- [X] [FTX](https://ftx.com) +- [X] [FTX](https://ftx.com/#a=2258149) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) -- [X] [OKEX](https://www.okex.com/) +- [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others through ccxt](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested @@ -67,7 +74,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of: Alternatively -- Python 3.7+ +- Python 3.8+ - pip (pip3) - git - TA-Lib diff --git a/docs/installation.md b/docs/installation.md index ee7ffe55d..92aa59498 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -24,7 +24,7 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable). !!! Note - Python3.7 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. + Python3.8 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository. Also, python headers (`python-dev` / `python-devel`) must be available for the installation to complete successfully. !!! Warning "Up-to-date clock" @@ -36,9 +36,13 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito These requirements apply to both [Script Installation](#script-installation) and [Manual Installation](#manual-installation). +!!! Note "ARM64 systems" + If you are running an ARM64 system (like a MacOS M1 or an Oracle VM), please use [docker](docker_quickstart.md) to run freqtrade. + While native installation is possible with some manual effort, this is not supported at the moment. + ### Install guide -* [Python >= 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/) +* [Python >= 3.8.x](http://docs.python-guide.org/en/latest/starting/installation/) * [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) @@ -50,7 +54,7 @@ We've included/collected install instructions for Ubuntu, MacOS, and Windows. Th OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems. !!! Note - Python3.7 or higher and the corresponding pip are assumed to be available. + Python3.8 or higher and the corresponding pip are assumed to be available. === "Debian/Ubuntu" #### Install necessary dependencies @@ -65,7 +69,7 @@ OS Specific steps are listed first, the [Common](#common) section below is neces === "RaspberryPi/Raspbian" The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/). - This image comes with python3.7 preinstalled, making it easy to get freqtrade up and running. + This image comes with python3.9 preinstalled, making it easy to get freqtrade up and running. Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. @@ -165,7 +169,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr ** --install ** With this option, the script will install the bot and most dependencies: -You will need to have git and python3.7+ installed beforehand for this to work. +You will need to have git and python3.8+ installed beforehand for this to work. * Mandatory software as: `ta-lib` * Setup your virtualenv under `.env/` @@ -416,16 +420,3 @@ open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10 ``` If this file is inexistent, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details. - -### MacOS installation error with python 3.9 - -When using python 3.9 on macOS, it's currently necessary to install some os-level modules to allow dependencies to compile. -The errors you'll see happen during installation and are related to the installation of `tables` or `blosc`. - -You can install the necessary libraries with the following command: - -```bash -brew install hdf5 c-blosc -``` - -After this, please run the installation (script) again. diff --git a/docs/plotting.md b/docs/plotting.md index 9fae38504..ccfbb12cb 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -164,16 +164,17 @@ The resulting plot will have the following elements: An advanced plot configuration can be specified in the strategy in the `plot_config` parameter. -Additional features when using plot_config include: +Additional features when using `plot_config` include: * Specify colors per indicator * Specify additional subplots -* Specify indicator pairs to fill area in between +* Specify indicator pairs to fill area in between The sample plot configuration below specifies fixed colors for the indicators. Otherwise, consecutive plots may produce different color schemes each time, making comparisons difficult. It also allows multiple subplots to display both MACD and RSI at the same time. Plot type can be configured using `type` key. Possible types are: + * `scatter` corresponding to `plotly.graph_objects.Scatter` class (default). * `bar` corresponding to `plotly.graph_objects.Bar` class. @@ -182,40 +183,89 @@ Extra parameters to `plotly.graph_objects.*` constructor can be specified in `pl Sample configuration with inline comments explaining the process: ``` python - plot_config = { - 'main_plot': { - # Configuration for main plot indicators. - # Specifies `ema10` to be red, and `ema50` to be a shade of gray - 'ema10': {'color': 'red'}, - 'ema50': {'color': '#CCCCCC'}, - # By omitting color, a random color is selected. - 'sar': {}, - # fill area between senkou_a and senkou_b - 'senkou_a': { - 'color': 'green', #optional - 'fill_to': 'senkou_b', - 'fill_label': 'Ichimoku Cloud', #optional - 'fill_color': 'rgba(255,76,46,0.2)', #optional - }, - # plot senkou_b, too. Not only the area to it. - 'senkou_b': {} +@property +def plot_config(self): + """ + There are a lot of solutions how to build the return dictionary. + The only important point is the return value. + Example: + plot_config = {'main_plot': {}, 'subplots': {}} + + """ + plot_config = {} + plot_config['main_plot'] = { + # Configuration for main plot indicators. + # Assumes 2 parameters, emashort and emalong to be specified. + f'ema_{self.emashort.value}': {'color': 'red'}, + f'ema_{self.emalong.value}': {'color': '#CCCCCC'}, + # By omitting color, a random color is selected. + 'sar': {}, + # fill area between senkou_a and senkou_b + 'senkou_a': { + 'color': 'green', #optional + 'fill_to': 'senkou_b', + 'fill_label': 'Ichimoku Cloud', #optional + 'fill_color': 'rgba(255,76,46,0.2)', #optional }, - 'subplots': { - # Create subplot MACD - "MACD": { - 'macd': {'color': 'blue', 'fill_to': 'macdhist'}, - 'macdsignal': {'color': 'orange'}, - 'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}} - }, - # Additional subplot RSI - "RSI": { - 'rsi': {'color': 'red'} - } + # plot senkou_b, too. Not only the area to it. + 'senkou_b': {} + } + plot_config['subplots'] = { + # Create subplot MACD + "MACD": { + 'macd': {'color': 'blue', 'fill_to': 'macdhist'}, + 'macdsignal': {'color': 'orange'}, + 'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}} + }, + # Additional subplot RSI + "RSI": { + 'rsi': {'color': 'red'} } } + return plot_config ``` +??? Note "As attribute (former method)" + Assigning plot_config is also possible as Attribute (this used to be the default way). + This has the disadvantage that strategy parameters are not available, preventing certain configurations from working. + + ``` python + plot_config = { + 'main_plot': { + # Configuration for main plot indicators. + # Specifies `ema10` to be red, and `ema50` to be a shade of gray + 'ema10': {'color': 'red'}, + 'ema50': {'color': '#CCCCCC'}, + # By omitting color, a random color is selected. + 'sar': {}, + # fill area between senkou_a and senkou_b + 'senkou_a': { + 'color': 'green', #optional + 'fill_to': 'senkou_b', + 'fill_label': 'Ichimoku Cloud', #optional + 'fill_color': 'rgba(255,76,46,0.2)', #optional + }, + # plot senkou_b, too. Not only the area to it. + 'senkou_b': {} + }, + 'subplots': { + # Create subplot MACD + "MACD": { + 'macd': {'color': 'blue', 'fill_to': 'macdhist'}, + 'macdsignal': {'color': 'orange'}, + 'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}} + }, + # Additional subplot RSI + "RSI": { + 'rsi': {'color': 'red'} + } + } + } + + ``` + + !!! Note The above configuration assumes that `ema10`, `ema50`, `senkou_a`, `senkou_b`, `macd`, `macdsignal`, `macdhist` and `rsi` are columns in the DataFrame created by the strategy. @@ -223,6 +273,9 @@ Sample configuration with inline comments explaining the process: !!! Warning `plotly` arguments are only supported with plotly library and will not work with freq-ui. +!!! Note "Trade position adjustments" + If `position_adjustment_enable` / `adjust_trade_position()` is used, the trade initial buy price is averaged over multiple orders and the trade start price will most likely appear outside the candle range. + ## Plot profit ![plot-profit](assets/plot-profit.png) @@ -233,6 +286,8 @@ The `plot-profit` subcommand shows an interactive graph with three plots: * The summarized profit made by backtesting. Note that this is not the real-world profit, but more of an estimate. * Profit for each individual pair. +* Parallelism of trades. +* Underwater (Periods of drawdown). The first graph is good to get a grip of how the overall market progresses. @@ -242,6 +297,8 @@ This graph will also highlight the start (and end) of the Max drawdown period. The third graph can be useful to spot outliers, events in pairs that cause profit spikes. +The forth graph can help you analyze trade parallelism, showing how often max_open_trades have been maxed out. + Possible options for the `freqtrade plot-profit` subcommand: ``` @@ -261,8 +318,8 @@ optional arguments: Specify what timerange of data to use. --export EXPORT Export backtest results, argument are: trades. Example: `--export=trades` - --export-filename PATH - Save backtest results to the file with this filename. + --export-filename PATH, --backtest-filename PATH + Use backtest results from this filename. Requires `--export` to be set as well. Example: `--export-filename=user_data/backtest_results/backtest _today.json` diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 772919436..0ca0e4b63 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==7.3.6 +mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.1 +pymdown-extensions==9.2 diff --git a/docs/rest-api.md b/docs/rest-api.md index 7299e0282..8c2599cbc 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -38,6 +38,11 @@ Sample configuration: !!! Danger "Security warning" By default, the configuration listens on localhost only (so it's not reachable from other systems). We strongly recommend to not expose this API to the internet and choose a strong, unique password, since others will potentially be able to control your bot. +??? Note "API/UI Access on a remote servers" + If you're running on a VPS, you should consider using either a ssh tunnel, or setup a VPN (openVPN, wireguard) to connect to your bot. + This will ensure that freqUI is not directly exposed to the internet, which is not recommended for security reasons (freqUI does not support https out of the box). + Setup of these tools is not part of this tutorial, however many good tutorials can be found on the internet. + You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` in a browser to check if the API is running correctly. This should return the response: diff --git a/docs/stoploss.md b/docs/stoploss.md index 4f8ac9e94..62081b540 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -2,6 +2,7 @@ The `stoploss` configuration parameter is loss as ratio that should trigger a sale. For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. +Stoploss calculations do include fees, so a stoploss of -10% is placed exactly 10% below the entry point. Most of the strategy files already include the optimal `stoploss` value. @@ -23,14 +24,14 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit) and FTX (stop limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. ### stoploss_on_exchange and stoploss_on_exchange_limit_ratio Enable or Disable stop loss on exchange. -If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfully. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. +If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order fills. This will protect you against sudden crashes in market, as the order execution happens purely within the exchange, and has no potential network overhead. If `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price. `stoploss` defines the stop-price where the limit order is placed - and limit should be slightly below this. diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 47d7ee6ae..3793abacf 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -77,43 +77,6 @@ class AwesomeStrategy(IStrategy): *** -## Custom sell signal - -It is possible to define custom sell signals, indicating that specified position should be sold. This is very useful when we need to customize sell conditions for each individual trade, or if you need the trade profit to take the sell decision. - -For example you could implement a 1:2 risk-reward ROI with `custom_sell()`. - -Using custom_sell() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. - -!!! Note - Returning a `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. - -An example of how we can use different indicators depending on the current profit and also sell trades that were open longer than one day: - -``` python -class AwesomeStrategy(IStrategy): - def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, - current_profit: float, **kwargs): - dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - last_candle = dataframe.iloc[-1].squeeze() - - # Above 20% profit, sell when rsi < 80 - if current_profit > 0.2: - if last_candle['rsi'] < 80: - return 'rsi_below_80' - - # Between 2% and 10%, sell if EMA-long above EMA-short - if 0.02 < current_profit < 0.1: - if last_candle['emalong'] > last_candle['emashort']: - return 'ema_long_below_80' - - # Sell any positions at a loss if they are held for more than one day. - if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1: - return 'unclog' -``` - -See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. - ## Buy Tag When your strategy has multiple buy signals, you can name the signal that triggered. @@ -164,505 +127,20 @@ The provided exit-tag is then used as sell-reason - and shown as such in backtes !!! Note `sell_reason` is limited to 100 characters, remaining data will be truncated. -## Bot loop start callback +## Strategy version -A simple callback which is called once at the start of every bot throttling iteration. -This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc. +You can implement custom strategy versioning by using the "version" method, and returning the version you would like this strategy to have. ``` python -import requests - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - def bot_loop_start(self, **kwargs) -> None: - """ - Called at the start of the bot iteration (one loop). - Might be used to perform pair-independent tasks - (e.g. gather some remote resource for comparison) - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - """ - if self.config['runmode'].value in ('live', 'dry_run'): - # Assign this to the class by using self.* - # can then be used by populate_* methods - self.remote_data = requests.get('https://some_remote_source.example.com') - -``` - -## Custom stoploss - -The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss. - -The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object. -The method must return a stoploss value (float / number) as a percentage of the current price. -E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD. - -The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price. - -To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method: - -``` python -# additional imports required -from datetime import datetime -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - """ - Custom stoploss logic, returning the new distance relative to current_rate (as ratio). - e.g. returning -0.05 would create a stoploss 5% below current_rate. - The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss. - - For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ - - When not implemented by a strategy, returns the initial stoploss value - Only called when use_custom_stoploss is set to True. - - :param pair: Pair that's currently analyzed - :param trade: trade object. - :param current_time: datetime object, containing the current datetime - :param current_rate: Rate, calculated based on pricing settings in ask_strategy. - :param current_profit: Current profit (as ratio), calculated based on current_rate. - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return float: New stoploss value, relative to the current rate - """ - return -0.04 -``` - -Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchange-freqtrade)). - -!!! Note "Use of dates" - All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support. - -!!! Tip "Trailing stoploss" - It's recommended to disable `trailing_stop` when using custom stoploss values. Both can work in tandem, but you might encounter the trailing stop to move the price higher while your custom function would not want this, causing conflicting behavior. - -### Custom stoploss examples - -The next section will show some examples on what's possible with the custom stoploss function. -Of course, many more things are possible, and all examples can be combined at will. - -#### Time based trailing stop - -Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss. - -``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - # Make sure you have the longest interval first - these conditions are evaluated from top to bottom. - if current_time - timedelta(minutes=120) > trade.open_date_utc: - return -0.05 - elif current_time - timedelta(minutes=60) > trade.open_date_utc: - return -0.10 - return 1 -``` - -#### Different stoploss per pair - -Use a different stoploss depending on the pair. -In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs. - -``` python -from datetime import datetime -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - if pair in ('ETH/BTC', 'XRP/BTC'): - return -0.10 - elif pair in ('LTC/BTC'): - return -0.05 - return -0.15 -``` - -#### Trailing stoploss with positive offset - -Use the initial stoploss until the profit is above 4%, then use a trailing stoploss of 50% of the current profit with a minimum of 2.5% and a maximum of 5%. - -Please note that the stoploss can only increase, values lower than the current stoploss are ignored. - -``` python -from datetime import datetime, timedelta -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - if current_profit < 0.04: - return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss - - # After reaching the desired offset, allow the stoploss to trail by half the profit - desired_stoploss = current_profit / 2 - - # Use a minimum of 2.5% and a maximum of 5% - return max(min(desired_stoploss, 0.05), 0.025) -``` - -#### Calculating stoploss relative to open price - -Stoploss values returned from `custom_stoploss()` always specify a percentage relative to `current_rate`. In order to set a stoploss relative to the *open* price, we need to use `current_profit` to calculate what percentage relative to the `current_rate` will give you the same result as if the percentage was specified from the open price. - -The helper function [`stoploss_from_open()`](strategy-customization.md#stoploss_from_open) can be used to convert from an open price relative stop, to a current price relative stop which can be returned from `custom_stoploss()`. - -### Calculating stoploss percentage from absolute price - -Stoploss values returned from `custom_stoploss()` always specify a percentage relative to `current_rate`. In order to set a stoploss at specified absolute price level, we need to use `stop_rate` to calculate what percentage relative to the `current_rate` will give you the same result as if the percentage was specified from the open price. - -The helper function [`stoploss_from_absolute()`](strategy-customization.md#stoploss_from_absolute) can be used to convert from an absolute price, to a current price relative stop which can be returned from `custom_stoploss()`. - -#### Stepped stoploss - -Instead of continuously trailing behind the current price, this example sets fixed stoploss price levels based on the current profit. - -* Use the regular stoploss until 20% profit is reached -* Once profit is > 20% - set stoploss to 7% above open price. -* Once profit is > 25% - set stoploss to 15% above open price. -* Once profit is > 40% - set stoploss to 25% above open price. - -``` python -from datetime import datetime -from freqtrade.persistence import Trade -from freqtrade.strategy import stoploss_from_open - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - # evaluate highest to lowest, so that highest possible stop is used - if current_profit > 0.40: - return stoploss_from_open(0.25, current_profit) - elif current_profit > 0.25: - return stoploss_from_open(0.15, current_profit) - elif current_profit > 0.20: - return stoploss_from_open(0.07, current_profit) - - # return maximum stoploss value, keeping current stoploss price unchanged - return 1 -``` - -#### Custom stoploss using an indicator from dataframe example - -Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss. - -``` python -class AwesomeStrategy(IStrategy): - - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - # <...> - dataframe['sar'] = ta.SAR(dataframe) - - use_custom_stoploss = True - - def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, - current_rate: float, current_profit: float, **kwargs) -> float: - - dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - last_candle = dataframe.iloc[-1].squeeze() - - # Use parabolic sar as absolute stoploss price - stoploss_price = last_candle['sar'] - - # Convert absolute price to percentage relative to current_rate - if stoploss_price < current_rate: - return (stoploss_price / current_rate) - 1 - - # return maximum stoploss value, keeping current stoploss price unchanged - return 1 -``` - -See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks. - ---- - -## Custom order price rules - -By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy. - -You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and `custom_exit_price()` for exits. - -!!! Note - If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. - -### Custom order entry and exit price example - -``` python -from datetime import datetime, timedelta, timezone -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - def custom_entry_price(self, pair: str, current_time: datetime, - proposed_rate, **kwargs) -> float: - - dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, - timeframe=self.timeframe) - new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] - - return new_entryprice - - def custom_exit_price(self, pair: str, trade: Trade, - current_time: datetime, proposed_rate: float, - current_profit: float, **kwargs) -> float: - - dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, - timeframe=self.timeframe) - new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] - - return new_exitprice - -``` - -!!! Warning - Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. - -!!! Example - If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98. - -!!! Warning "No backtesting support" - Custom entry-prices are currently not supported during backtesting. - -## Custom order timeout rules - -Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section. - -However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if an order did time out or not. - -!!! Note - Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. - -### Custom order timeout example - -A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. -It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. - -The function must return either `True` (cancel order) or `False` (keep order alive). - -``` python -from datetime import datetime, timedelta, timezone -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. - unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 - } - - def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: - if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): - return True - elif trade.open_rate > 10 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=3): - return True - elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24): - return True - return False - - - def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: - if trade.open_rate > 100 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=5): - return True - elif trade.open_rate > 10 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(minutes=3): - return True - elif trade.open_rate < 1 and trade.open_date_utc < datetime.now(timezone.utc) - timedelta(hours=24): - return True - return False +def version(self) -> str: + """ + Returns version of the strategy. + """ + return "1.1" ``` !!! Note - For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. - -### Custom order timeout example (using additional data) - -``` python -from datetime import datetime -from freqtrade.persistence import Trade - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. - unfilledtimeout = { - 'buy': 60 * 25, - 'sell': 60 * 25 - } - - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: - ob = self.dp.orderbook(pair, 1) - current_price = ob['bids'][0][0] - # Cancel buy order if price is more than 2% above the order. - if current_price > order['price'] * 1.02: - return True - return False - - - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: - ob = self.dp.orderbook(pair, 1) - current_price = ob['asks'][0][0] - # Cancel sell order if price is more than 2% below the order. - if current_price < order['price'] * 0.98: - return True - return False -``` - ---- - -## Bot order confirmation - -### Trade entry (buy order) confirmation - -`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect). - -``` python -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time: datetime, **kwargs) -> bool: - """ - Called right before placing a buy order. - Timing for this function is critical, so avoid doing heavy computations or - network requests in this method. - - For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ - - When not implemented by a strategy, returns True (always confirming). - - :param pair: Pair that's about to be bought. - :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in target (quote) currency that's going to be traded. - :param rate: Rate that's going to be used when using limit orders - :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). - :param current_time: datetime object, containing the current datetime - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the buy-order is placed on the exchange. - False aborts the process - """ - return True - -``` - -### Trade exit (sell order) confirmation - -`confirm_trade_exit()` can be used to abort a trade exit (sell) at the latest second (maybe because the price is not what we expect). - -``` python -from freqtrade.persistence import Trade - - -class AwesomeStrategy(IStrategy): - - # ... populate_* methods - - def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, - rate: float, time_in_force: str, sell_reason: str, - current_time: datetime, **kwargs) -> bool: - """ - Called right before placing a regular sell order. - Timing for this function is critical, so avoid doing heavy computations or - network requests in this method. - - For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ - - When not implemented by a strategy, returns True (always confirming). - - :param pair: Pair that's about to be sold. - :param order_type: Order type (as configured in order_types). usually limit or market. - :param amount: Amount in quote currency. - :param rate: Rate that's going to be used when using limit orders - :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). - :param sell_reason: Sell reason. - Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', - 'sell_signal', 'force_sell', 'emergency_sell'] - :param current_time: datetime object, containing the current datetime - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the sell-order is placed on the exchange. - False aborts the process - """ - if sell_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0: - # Reject force-sells with negative profit - # This is just a sample, please adjust to your needs - # (this does not necessarily make sense, assuming you know when you're force-selling) - return False - return True - -``` - -### Stake size management - -It is possible to manage your risk by reducing or increasing stake amount when placing a new trade. - -```python -class AwesomeStrategy(IStrategy): - def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, - proposed_stake: float, min_stake: float, max_stake: float, - **kwargs) -> float: - - dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - current_candle = dataframe.iloc[-1].squeeze() - - if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']: - if self.config['stake_amount'] == 'unlimited': - # Use entire available wallet during favorable conditions when in compounding mode. - return max_stake - else: - # Compound profits during favorable conditions instead of using a static stake. - return self.wallets.get_total_stake_amount() / self.config['max_open_trades'] - - # Use default stake amount. - return proposed_stake -``` - -Freqtrade will fall back to the `proposed_stake` value should your code raise an exception. The exception itself will be logged. - -!!! Tip - You do not _have_ to ensure that `min_stake <= returned_value <= max_stake`. Trades will succeed as the returned value will be clamped to supported range and this acton will be logged. - -!!! Tip - Returning `0` or `None` will prevent trades from being placed. - ---- + You should make sure to implement proper version control (like a git repository) alongside this, as freqtrade will not keep historic versions of your strategy, so it's up to the user to be able to eventually roll back to a prior version of the strategy. ## Derived strategies @@ -744,9 +222,9 @@ should be rewritten to ```python frames = [dataframe] for val in self.buy_ema_short.range: - frames.append({ + frames.append(DataFrame({ f'ema_short_{val}': ta.EMA(dataframe, timeperiod=val) - }) + })) # Append columns to existing dataframe merged_frame = pd.concat(frames, axis=1) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md new file mode 100644 index 000000000..555352d21 --- /dev/null +++ b/docs/strategy-callbacks.md @@ -0,0 +1,684 @@ +# Strategy Callbacks + +While the main strategy functions (`populate_indicators()`, `populate_buy_trend()`, `populate_sell_trend()`) should be used in a vectorized way, and are only called [once during backtesting](bot-basics.md#backtesting-hyperopt-execution-logic), callbacks are called "whenever needed". + +As such, you should avoid doing heavy calculations in callbacks to avoid delays during operations. +Depending on the callback used, they may be called when entering / exiting a trade, or throughout the duration of a trade. + +Currently available callbacks: + +* [`bot_loop_start()`](#bot-loop-start) +* [`custom_stake_amount()`](#custom-stake-size) +* [`custom_sell()`](#custom-sell-signal) +* [`custom_stoploss()`](#custom-stoploss) +* [`custom_entry_price()` and `custom_exit_price()`](#custom-order-price-rules) +* [`check_buy_timeout()` and `check_sell_timeout()](#custom-order-timeout-rules) +* [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) +* [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) +* [`adjust_trade_position()`](#adjust-trade-position) + +!!! Tip "Callback calling sequence" + You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) + +## Bot loop start + +A simple callback which is called once at the start of every bot throttling iteration (roughly every 5 seconds, unless configured differently). +This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc. + +``` python +import requests + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def bot_loop_start(self, **kwargs) -> None: + """ + Called at the start of the bot iteration (one loop). + Might be used to perform pair-independent tasks + (e.g. gather some remote resource for comparison) + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + if self.config['runmode'].value in ('live', 'dry_run'): + # Assign this to the class by using self.* + # can then be used by populate_* methods + self.remote_data = requests.get('https://some_remote_source.example.com') + +``` + +## Custom Stake size + +Called before entering a trade, makes it possible to manage your position size when placing a new trade. + +```python +class AwesomeStrategy(IStrategy): + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + entry_tag: Optional[str], **kwargs) -> float: + + dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) + current_candle = dataframe.iloc[-1].squeeze() + + if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']: + if self.config['stake_amount'] == 'unlimited': + # Use entire available wallet during favorable conditions when in compounding mode. + return max_stake + else: + # Compound profits during favorable conditions instead of using a static stake. + return self.wallets.get_total_stake_amount() / self.config['max_open_trades'] + + # Use default stake amount. + return proposed_stake +``` + +Freqtrade will fall back to the `proposed_stake` value should your code raise an exception. The exception itself will be logged. + +!!! Tip + You do not _have_ to ensure that `min_stake <= returned_value <= max_stake`. Trades will succeed as the returned value will be clamped to supported range and this action will be logged. + +!!! Tip + Returning `0` or `None` will prevent trades from being placed. + +## Custom sell signal + +Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. + +Allows to define custom sell signals, indicating that specified position should be sold. This is very useful when we need to customize sell conditions for each individual trade, or if you need trade data to make an exit decision. + +For example you could implement a 1:2 risk-reward ROI with `custom_sell()`. + +Using custom_sell() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. + +!!! Note + Returning a (none-empty) `string` or `True` from this method is equal to setting sell signal on a candle at specified time. This method is not called when sell signal is set already, or if sell signals are disabled (`use_sell_signal=False` or `sell_profit_only=True` while profit is below `sell_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. + +An example of how we can use different indicators depending on the current profit and also sell trades that were open longer than one day: + +``` python +class AwesomeStrategy(IStrategy): + def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, + current_profit: float, **kwargs): + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + + # Above 20% profit, sell when rsi < 80 + if current_profit > 0.2: + if last_candle['rsi'] < 80: + return 'rsi_below_80' + + # Between 2% and 10%, sell if EMA-long above EMA-short + if 0.02 < current_profit < 0.1: + if last_candle['emalong'] > last_candle['emashort']: + return 'ema_long_below_80' + + # Sell any positions at a loss if they are held for more than one day. + if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1: + return 'unclog' +``` + +See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks. + +## Custom stoploss + +Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. +The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object. + +The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade). + +The method must return a stoploss value (float / number) as a percentage of the current price. +E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD. + +The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price. + +To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method: + +``` python +# additional imports required +from datetime import datetime +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + """ + Custom stoploss logic, returning the new distance relative to current_rate (as ratio). + e.g. returning -0.05 would create a stoploss 5% below current_rate. + The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns the initial stoploss value + Only called when use_custom_stoploss is set to True. + + :param pair: Pair that's currently analyzed + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: New stoploss value, relative to the current rate + """ + return -0.04 +``` + +Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchange-freqtrade)). + +!!! Note "Use of dates" + All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support. + +!!! Tip "Trailing stoploss" + It's recommended to disable `trailing_stop` when using custom stoploss values. Both can work in tandem, but you might encounter the trailing stop to move the price higher while your custom function would not want this, causing conflicting behavior. + +### Custom stoploss examples + +The next section will show some examples on what's possible with the custom stoploss function. +Of course, many more things are possible, and all examples can be combined at will. + +#### Time based trailing stop + +Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss. + +``` python +from datetime import datetime, timedelta +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + + # Make sure you have the longest interval first - these conditions are evaluated from top to bottom. + if current_time - timedelta(minutes=120) > trade.open_date_utc: + return -0.05 + elif current_time - timedelta(minutes=60) > trade.open_date_utc: + return -0.10 + return 1 +``` + +#### Different stoploss per pair + +Use a different stoploss depending on the pair. +In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs. + +``` python +from datetime import datetime +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + + if pair in ('ETH/BTC', 'XRP/BTC'): + return -0.10 + elif pair in ('LTC/BTC'): + return -0.05 + return -0.15 +``` + +#### Trailing stoploss with positive offset + +Use the initial stoploss until the profit is above 4%, then use a trailing stoploss of 50% of the current profit with a minimum of 2.5% and a maximum of 5%. + +Please note that the stoploss can only increase, values lower than the current stoploss are ignored. + +``` python +from datetime import datetime, timedelta +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + + if current_profit < 0.04: + return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss + + # After reaching the desired offset, allow the stoploss to trail by half the profit + desired_stoploss = current_profit / 2 + + # Use a minimum of 2.5% and a maximum of 5% + return max(min(desired_stoploss, 0.05), 0.025) +``` + +#### Stepped stoploss + +Instead of continuously trailing behind the current price, this example sets fixed stoploss price levels based on the current profit. + +* Use the regular stoploss until 20% profit is reached +* Once profit is > 20% - set stoploss to 7% above open price. +* Once profit is > 25% - set stoploss to 15% above open price. +* Once profit is > 40% - set stoploss to 25% above open price. + +``` python +from datetime import datetime +from freqtrade.persistence import Trade +from freqtrade.strategy import stoploss_from_open + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + + # evaluate highest to lowest, so that highest possible stop is used + if current_profit > 0.40: + return stoploss_from_open(0.25, current_profit) + elif current_profit > 0.25: + return stoploss_from_open(0.15, current_profit) + elif current_profit > 0.20: + return stoploss_from_open(0.07, current_profit) + + # return maximum stoploss value, keeping current stoploss price unchanged + return 1 +``` + +#### Custom stoploss using an indicator from dataframe example + +Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss. + +``` python +class AwesomeStrategy(IStrategy): + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + # <...> + dataframe['sar'] = ta.SAR(dataframe) + + use_custom_stoploss = True + + def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, + current_rate: float, current_profit: float, **kwargs) -> float: + + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + last_candle = dataframe.iloc[-1].squeeze() + + # Use parabolic sar as absolute stoploss price + stoploss_price = last_candle['sar'] + + # Convert absolute price to percentage relative to current_rate + if stoploss_price < current_rate: + return (stoploss_price / current_rate) - 1 + + # return maximum stoploss value, keeping current stoploss price unchanged + return 1 +``` + +See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks. + +### Common helpers for stoploss calculations + +#### Stoploss relative to open price + +Stoploss values returned from `custom_stoploss()` always specify a percentage relative to `current_rate`. In order to set a stoploss relative to the *open* price, we need to use `current_profit` to calculate what percentage relative to the `current_rate` will give you the same result as if the percentage was specified from the open price. + +The helper function [`stoploss_from_open()`](strategy-customization.md#stoploss_from_open) can be used to convert from an open price relative stop, to a current price relative stop which can be returned from `custom_stoploss()`. + +#### Stoploss percentage from absolute price + +Stoploss values returned from `custom_stoploss()` always specify a percentage relative to `current_rate`. In order to set a stoploss at specified absolute price level, we need to use `stop_rate` to calculate what percentage relative to the `current_rate` will give you the same result as if the percentage was specified from the open price. + +The helper function [`stoploss_from_absolute()`](strategy-customization.md#stoploss_from_absolute) can be used to convert from an absolute price, to a current price relative stop which can be returned from `custom_stoploss()`. + +--- + +## Custom order price rules + +By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy. + +You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and `custom_exit_price()` for exits. + +Each of these methods are called right before placing an order on the exchange. + +!!! Note + If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration. + +### Custom order entry and exit price example + +``` python +from datetime import datetime, timedelta, timezone +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + entry_tag: Optional[str], **kwargs) -> float: + + dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, + timeframe=self.timeframe) + new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] + + return new_entryprice + + def custom_exit_price(self, pair: str, trade: Trade, + current_time: datetime, proposed_rate: float, + current_profit: float, **kwargs) -> float: + + dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, + timeframe=self.timeframe) + new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] + + return new_exitprice + +``` + +!!! Warning + Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. + **Example**: + If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98, which is 2% below the current (proposed) rate. + +!!! Warning "Backtesting" + Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range. + Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle. + `custom_exit_price()` is only called for sells of type Sell_signal and Custom sell. All other sell-types will use regular backtesting prices. + +## Custom order timeout rules + +Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section. + +However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if an order did time out or not. + +!!! Note + Backtesting fills orders if their price falls within the candle's low/high range. + The below callbacks will be called once per (detail) candle for orders that don't fill immediately (which use custom pricing). + +### Custom order timeout example + +Called for every open order until that order is either filled or cancelled. +`check_buy_timeout()` is called for trade entries, while `check_sell_timeout()` is called for trade exit orders. + +A simple example, which applies different unfilled-timeouts depending on the price of the asset can be seen below. +It applies a tight timeout for higher priced assets, while allowing more time to fill on cheap coins. + +The function must return either `True` (cancel order) or `False` (keep order alive). + +``` python +from datetime import datetime, timedelta +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. + unfilledtimeout = { + 'buy': 60 * 25, + 'sell': 60 * 25 + } + + def check_buy_timeout(self, pair: str, trade: 'Trade', order: dict, + current_time: datetime, **kwargs) -> bool: + if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): + return True + elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): + return True + elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24): + return True + return False + + + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): + return True + elif trade.open_rate > 10 and trade.open_date_utc < current_time - timedelta(minutes=3): + return True + elif trade.open_rate < 1 and trade.open_date_utc < current_time - timedelta(hours=24): + return True + return False +``` + +!!! Note + For the above example, `unfilledtimeout` must be set to something bigger than 24h, otherwise that type of timeout will apply first. + +### Custom order timeout example (using additional data) + +``` python +from datetime import datetime +from freqtrade.persistence import Trade + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + # Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours. + unfilledtimeout = { + 'buy': 60 * 25, + 'sell': 60 * 25 + } + + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + ob = self.dp.orderbook(pair, 1) + current_price = ob['bids'][0][0] + # Cancel buy order if price is more than 2% above the order. + if current_price > order['price'] * 1.02: + return True + return False + + + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: + ob = self.dp.orderbook(pair, 1) + current_price = ob['asks'][0][0] + # Cancel sell order if price is more than 2% below the order. + if current_price < order['price'] * 0.98: + return True + return False +``` + +--- + +## Bot order confirmation + +Confirm trade entry / exits. +This are the last methods that will be called before an order is placed. + +### Trade entry (buy order) confirmation + +`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect). + +``` python +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, + time_in_force: str, current_time: datetime, entry_tag: Optional[str], + **kwargs) -> bool: + """ + Called right before placing a buy order. + Timing for this function is critical, so avoid doing heavy computations or + network requests in this method. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns True (always confirming). + + :param pair: Pair that's about to be bought. + :param order_type: Order type (as configured in order_types). usually limit or market. + :param amount: Amount in target (quote) currency that's going to be traded. + :param rate: Rate that's going to be used when using limit orders + :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). + :param current_time: datetime object, containing the current datetime + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the buy-order is placed on the exchange. + False aborts the process + """ + return True + +``` + +### Trade exit (sell order) confirmation + +`confirm_trade_exit()` can be used to abort a trade exit (sell) at the latest second (maybe because the price is not what we expect). + +``` python +from freqtrade.persistence import Trade + + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, + rate: float, time_in_force: str, sell_reason: str, + current_time: datetime, **kwargs) -> bool: + """ + Called right before placing a regular sell order. + Timing for this function is critical, so avoid doing heavy computations or + network requests in this method. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns True (always confirming). + + :param pair: Pair that's about to be sold. + :param order_type: Order type (as configured in order_types). usually limit or market. + :param amount: Amount in quote currency. + :param rate: Rate that's going to be used when using limit orders + :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). + :param sell_reason: Sell reason. + Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', + 'sell_signal', 'force_sell', 'emergency_sell'] + :param current_time: datetime object, containing the current datetime + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return bool: When True is returned, then the sell-order is placed on the exchange. + False aborts the process + """ + if sell_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0: + # Reject force-sells with negative profit + # This is just a sample, please adjust to your needs + # (this does not necessarily make sense, assuming you know when you're force-selling) + return False + return True + +``` + +## Adjust trade position + +The `position_adjustment_enable` strategy property enables the usage of `adjust_trade_position()` callback in the strategy. +For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled. +`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging). + +`max_entry_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys. + +The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional buy order should be made (position is increased). +If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored. +Additional orders also result in additional fees and those orders don't count towards `max_open_trades`. + +This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`. +`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. + +!!! Note "About stake size" + Using fixed stake size means it will be the amount used for the first order, just like without position adjustment. + If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that. + Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order. + +!!! Warning + Stoploss is still calculated from the initial opening price, not averaged price. + +!!! Warning "/stopbuy" + While `/stopbuy` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades. + +!!! Warning "Backtesting" + During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so performance will be affected. + +``` python +from freqtrade.persistence import Trade + + +class DigDeeperStrategy(IStrategy): + + position_adjustment_enable = True + + # Attempts to handle large drops with DCA. High stoploss is required. + stoploss = -0.30 + + # ... populate_* methods + + # Example specific variables + max_entry_position_adjustment = 3 + # This number is explained a bit further down + max_dca_multiplier = 5.5 + + # This is called when placing the initial order (opening trade) + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + entry_tag: Optional[str], **kwargs) -> float: + + # We need to leave most of the funds for possible further DCA orders + # This also applies to fixed stakes + return proposed_stake / self.max_dca_multiplier + + def adjust_trade_position(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, min_stake: float, + max_stake: float, **kwargs): + """ + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. + + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Current buy rate. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param min_stake: Minimal stake size allowed by exchange. + :param max_stake: Balance available for trading. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade + """ + + if current_profit > -0.05: + return None + + # Obtain pair dataframe (just to show how to access it) + dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe) + # Only buy when not actively falling price. + last_candle = dataframe.iloc[-1].squeeze() + previous_candle = dataframe.iloc[-2].squeeze() + if last_candle['close'] < previous_candle['close']: + return None + + filled_buys = trade.select_filled_orders('buy') + count_of_buys = trade.nr_of_successful_buys + # Allow up to 3 additional increasingly larger buys (4 in total) + # Initial buy is 1x + # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% + # If that falls down to -5% again, we buy 1.5x more + # If that falls once again down to -5%, we buy 1.75x more + # Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake. + # That is why max_dca_multiplier is 5.5 + # Hope you have a deep wallet! + try: + # This returns first order stake size + stake_amount = filled_buys[0].cost + # This then calculates current safety order size + stake_amount = stake_amount * (1 + (count_of_buys * 0.25)) + return stake_amount + except Exception as exception: + return None + + return None + +``` diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index d54bae710..e6eff2416 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -317,20 +317,14 @@ class AwesomeStrategy(IStrategy): Setting a stoploss is highly recommended to protect your capital from strong moves against you. -Sample: +Sample of setting a 10% stoploss: ``` python stoploss = -0.10 ``` -This would signify a stoploss of -10%. - For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). -If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order_types dictionary, so your stoploss is on the exchange and cannot be missed due to network problems, high load or other reasons. - -For more information on order_types please look [here](configuration.md#understand-order_types). - ### Timeframe (formerly ticker interval) This is the set of candles the bot should download and use for the analysis. @@ -346,7 +340,7 @@ The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `p Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`. The Metadata-dict should not be modified and does not persist information across multiple calls. -Instead, have a look at the section [Storing information](strategy-advanced.md#Storing-information) +Instead, have a look at the [Storing information](strategy-advanced.md#Storing-information) section. ## Strategy file loading @@ -844,7 +838,7 @@ In some situations it may be confusing to deal with stops relative to current ra from datetime import datetime from freqtrade.persistence import Trade - from freqtrade.strategy import IStrategy, stoploss_from_open + from freqtrade.strategy import IStrategy, stoploss_from_absolute class AwesomeStrategy(IStrategy): @@ -1016,6 +1010,10 @@ The following lists some common patterns which should be avoided to prevent frus - don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling().mean()` instead - don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead. +### Colliding signals + +When buy and sell signals collide (both `'buy'` and `'sell'` are 1), freqtrade will do nothing and ignore the entry (buy) signal. This will avoid trades that buy, and sell immediately. Obviously, this can potentially lead to missed entries. + ## Further strategy ideas To get additional Ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk. diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index dd7e07824..90d8d8800 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -50,7 +50,9 @@ candles.head() ```python # Load strategy using values set above from freqtrade.resolvers import StrategyResolver +from freqtrade.data.dataprovider import DataProvider strategy = StrategyResolver.load_strategy(config) +strategy.dp = DataProvider(config, None, None) # Generate buy/sell signals using strategy df = strategy.analyze_ticker(candles, {'pair': pair}) @@ -228,7 +230,7 @@ graph = generate_candlestick_graph(pair=pair, # Show graph inline # graph.show() -# Render graph in a separate window +# Render graph in a seperate window graph.show(renderer="browser") ``` diff --git a/docs/utils.md b/docs/utils.md index 4a032db26..a28a0f456 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -59,7 +59,7 @@ $ freqtrade new-config --config config_binance.json ? Do you want to enable Dry-run (simulated trades)? Yes ? Please insert your stake currency: BTC ? Please insert your stake amount: 0.05 -? Please insert max_open_trades (Integer or 'unlimited'): 3 +? Please insert max_open_trades (Integer or -1 for unlimited open trades): 3 ? Please insert your desired timeframe (e.g. 5m): 5m ? Please insert your display Currency (for reporting): USD ? Select exchange binance @@ -517,20 +517,25 @@ Requires a configuration with specified `pairlists` attribute. Can be used to generate static pairlists to be used during backtesting / hyperopt. ``` -usage: freqtrade test-pairlist [-h] [-c PATH] +usage: freqtrade test-pairlist [-h] [-v] [-c PATH] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] - [-1] [--print-json] + [-1] [--print-json] [--exchange EXCHANGE] optional arguments: -h, --help show this help message and exit + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. --quote QUOTE_CURRENCY [QUOTE_CURRENCY ...] Specify quote currency(-ies). Space-separated list. -1, --one-column Print output in one column. --print-json Print list of pairs or market symbols in JSON format. + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + ``` ### Examples diff --git a/docs/webhook-config.md b/docs/webhook-config.md index ec944cb50..c93f9aac8 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -50,7 +50,7 @@ Sample configuration (tested using IFTTT). The url in `webhook.url` should point to the correct url for your webhook. If you're using [IFTTT](https://ifttt.com) (as shown in the sample above) please insert your event and key to the url. -You can set the POST body format to Form-Encoded (default) or JSON-Encoded. Use `"format": "form"` or `"format": "json"` respectively. Example configuration for Mattermost Cloud integration: +You can set the POST body format to Form-Encoded (default), JSON-Encoded, or raw data. Use `"format": "form"`, `"format": "json"`, or `"format": "raw"` respectively. Example configuration for Mattermost Cloud integration: ```json "webhook": { @@ -63,7 +63,36 @@ You can set the POST body format to Form-Encoded (default) or JSON-Encoded. Use }, ``` -The result would be POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel. +The result would be a POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel. + +When using the Form-Encoded or JSON-Encoded configuration you can configure any number of payload values, and both the key and value will be ouput in the POST request. However, when using the raw data format you can only configure one value and it **must** be named `"data"`. In this instance the data key will not be output in the POST request, only the value. For example: + +```json + "webhook": { + "enabled": true, + "url": "https://", + "format": "raw", + "webhookstatus": { + "data": "Status: {status}" + } + }, +``` + +The result would be a POST request with e.g. `Status: running` body and `Content-Type: text/plain` header. + +Optional parameters are available to enable automatic retries for webhook messages. The `webhook.retries` parameter can be set for the maximum number of retries the webhook request should attempt if it is unsuccessful (i.e. HTTP response status is not 200). By default this is set to `0` which is disabled. An additional `webhook.retry_delay` parameter can be set to specify the time in seconds between retry attempts. By default this is set to `0.1` (i.e. 100ms). Note that increasing the number of retries or retry delay may slow down the trader if there are connectivity issues with the webhook. Example configuration for retries: + +```json + "webhook": { + "enabled": true, + "url": "https://", + "retries": 3, + "retry_delay": 0.2, + "webhookstatus": { + "status": "Status: {status}" + } + }, +``` Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called. @@ -75,11 +104,13 @@ Possible parameters are: * `trade_id` * `exchange` * `pair` -* `limit` +* ~~`limit` # Deprecated - should no longer be used.~~ +* `open_rate` * `amount` * `open_date` * `stake_amount` * `stake_currency` +* `base_currency` * `fiat_currency` * `order_type` * `current_rate` @@ -98,6 +129,7 @@ Possible parameters are: * `open_date` * `stake_amount` * `stake_currency` +* `base_currency` * `fiat_currency` * `order_type` * `current_rate` @@ -116,7 +148,10 @@ Possible parameters are: * `open_date` * `stake_amount` * `stake_currency` +* `base_currency` * `fiat_currency` +* `order_type` +* `current_rate` * `buy_tag` ### Webhooksell @@ -134,6 +169,7 @@ Possible parameters are: * `profit_amount` * `profit_ratio` * `stake_currency` +* `base_currency` * `fiat_currency` * `sell_reason` * `order_type` @@ -156,6 +192,7 @@ Possible parameters are: * `profit_amount` * `profit_ratio` * `stake_currency` +* `base_currency` * `fiat_currency` * `sell_reason` * `order_type` @@ -178,6 +215,7 @@ Possible parameters are: * `profit_amount` * `profit_ratio` * `stake_currency` +* `base_currency` * `fiat_currency` * `sell_reason` * `order_type` diff --git a/docs/windows_installation.md b/docs/windows_installation.md index 2db0ae913..242c994c4 100644 --- a/docs/windows_installation.md +++ b/docs/windows_installation.md @@ -23,9 +23,9 @@ git clone https://github.com/freqtrade/freqtrade.git Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows). -As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.21-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). +As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.24-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). -Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows. +Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows. Other versions must be downloaded from the above link. ``` powershell @@ -54,6 +54,8 @@ error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. -The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building C code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker compose](docker_quickstart.md) first. +You can download the Visual C++ build tools from [here](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and install "Desktop development with C++" in it's default configuration. Unfortunately, this is a heavy download / dependency so you might want to consider WSL2 or [docker compose](docker_quickstart.md) first. + +![Windows installation](assets/windows_install.png) --- diff --git a/environment.yml b/environment.yml index 84ab5ff6f..50af602e5 100644 --- a/environment.yml +++ b/environment.yml @@ -4,7 +4,7 @@ channels: # - defaults dependencies: # 1/4 req main - - python>=3.7,<3.9 + - python>=3.8,<=3.10 - numpy - pandas - pip @@ -25,9 +25,12 @@ dependencies: - fastapi - uvicorn - pyjwt + - aiofiles + - psutil - colorama - questionary - prompt-toolkit + - python-dateutil # ============================ diff --git a/freqtrade/__main__.py b/freqtrade/__main__.py index ab4c7a110..fc45bdf61 100644 --- a/freqtrade/__main__.py +++ b/freqtrade/__main__.py @@ -3,7 +3,7 @@ __main__.py for Freqtrade To launch Freqtrade as a module -> python -m freqtrade (with Python >= 3.7) +> python -m freqtrade (with Python >= 3.8) """ from freqtrade import main diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 032f7dd51..28f7d7148 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -24,7 +24,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", "strategy_list", "export", "exportfilename", - "backtest_breakdown"] + "backtest_breakdown", "backtest_cache"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "position_stacking", "use_max_market_positions", @@ -51,7 +51,7 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", - "list_pairs_print_json"] + "list_pairs_print_json", "exchange"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] @@ -75,7 +75,7 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "timerange", "timeframe", "no_trades"] ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", - "trade_source", "timeframe", "plot_auto_open"] + "trade_source", "timeframe", "plot_auto_open", ] ARGS_INSTALL_UI = ["erase_ui_only", 'ui_version'] diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index b0ca1a1bf..ca55dbbc4 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -76,17 +76,14 @@ def ask_user_config() -> Dict[str, Any]: { "type": "text", "name": "max_open_trades", - "message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):", + "message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):", "default": "3", - "validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val), - "filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"' - if val == UNLIMITED_STAKE_AMOUNT - else val + "validate": lambda val: validate_is_int(val) }, { "type": "select", "name": "timeframe_in_config", - "message": "Tim", + "message": "Time", "choices": ["Have the strategy define timeframe.", "Override in configuration."] }, { @@ -111,11 +108,12 @@ def ask_user_config() -> Dict[str, Any]: "binance", "binanceus", "bittrex", - "kraken", "ftx", - "kucoin", "gateio", - "okex", + "huobi", + "kraken", + "kucoin", + "okx", Separator(), "other", ], @@ -143,7 +141,7 @@ def ask_user_config() -> Dict[str, Any]: "type": "password", "name": "exchange_key_password", "message": "Insert Exchange API Key password", - "when": lambda x: not x['dry_run'] and x['exchange_name'] in ('kucoin', 'okex') + "when": lambda x: not x['dry_run'] and x['exchange_name'] in ('kucoin', 'okx') }, { "type": "confirm", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 6aa4ed363..11fcc6b81 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -182,11 +182,12 @@ AVAILABLE_CLI_OPTIONS = { ), "exportfilename": Arg( - '--export-filename', - help='Save backtest results to the file with this filename. ' - 'Requires `--export` to be set as well. ' - 'Example: `--export-filename=user_data/backtest_results/backtest_today.json`', - metavar='PATH', + "--export-filename", + "--backtest-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`", + metavar="PATH", ), "disableparamexport": Arg( '--disable-param-export', @@ -205,6 +206,12 @@ AVAILABLE_CLI_OPTIONS = { nargs='+', choices=constants.BACKTEST_BREAKDOWNS ), + "backtest_cache": Arg( + '--cache', + help='Load a cached backtest result no older than specified age (default: %(default)s).', + default=constants.BACKTEST_CACHE_DEFAULT, + choices=constants.BACKTEST_CACHE_AGE, + ), # Edge "stoploss_range": Arg( '--stoplosses', diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index f230b696c..1bfd384fc 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -25,12 +25,16 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[ RunMode.HYPEROPT: 'hyperoptimization', } if method in no_unlimited_runmodes.keys(): + wallet_size = config['dry_run_wallet'] * config['tradable_balance_ratio'] + # tradable_balance_ratio if (config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT - and config['stake_amount'] > config['dry_run_wallet']): - wallet = round_coin_value(config['dry_run_wallet'], config['stake_currency']) + and config['stake_amount'] > wallet_size): + wallet = round_coin_value(wallet_size, config['stake_currency']) stake = round_coin_value(config['stake_amount'], config['stake_currency']) - raise OperationalException(f"Starting balance ({wallet}) " - f"is smaller than stake_amount {stake}.") + raise OperationalException( + f"Starting balance ({wallet}) is smaller than stake_amount {stake}. " + f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`." + ) return config diff --git a/freqtrade/configuration/PeriodicCache.py b/freqtrade/configuration/PeriodicCache.py index 25c0c47f3..64fff668e 100644 --- a/freqtrade/configuration/PeriodicCache.py +++ b/freqtrade/configuration/PeriodicCache.py @@ -1,6 +1,6 @@ from datetime import datetime, timezone -from cachetools.ttl import TTLCache +from cachetools import TTLCache class PeriodicCache(TTLCache): diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index f5a674878..1ba17a04d 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -276,6 +276,9 @@ class Configuration: self._args_to_config(config, argname='backtest_breakdown', logstring='Parameter --breakdown detected ...') + self._args_to_config(config, argname='backtest_cache', + logstring='Parameter --cache={} detected ...') + self._args_to_config(config, argname='disableparamexport', logstring='Parameter --disableparamexport detected: {} ...') @@ -428,7 +431,6 @@ class Configuration: logstring='Using "{}" to store trades data.') def _process_data_options(self, config: Dict[str, Any]) -> None: - self._args_to_config(config, argname='new_pairs_days', logstring='Detected --new-pairs-days: {}') diff --git a/freqtrade/constants.py b/freqtrade/constants.py index e775e39fc..200917d43 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -26,7 +26,7 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', 'CalmarHyperOptLoss', - 'MaxDrawDownHyperOptLoss'] + 'MaxDrawDownHyperOptLoss', 'ProfitDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', @@ -34,6 +34,8 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] +BACKTEST_CACHE_AGE = ['none', 'day', 'week', 'month'] +BACKTEST_CACHE_DEFAULT = 'day' DRY_RUN_WALLET = 1000 DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons @@ -50,6 +52,8 @@ USERPATH_STRATEGIES = 'strategies' USERPATH_NOTEBOOKS = 'notebooks' TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] +WEBHOOK_FORMAT_OPTIONS = ['form', 'json', 'raw'] + ENV_VAR_PREFIX = 'FREQTRADE__' NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired') @@ -136,7 +140,7 @@ CONF_SCHEMA = { 'minProperties': 1 }, 'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5}, - 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, + 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True, 'minimum': -1}, 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, @@ -312,10 +316,16 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'enabled': {'type': 'boolean'}, + 'url': {'type': 'string'}, + 'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'}, + 'retries': {'type': 'integer', 'minimum': 0}, + 'retry_delay': {'type': 'number', 'minimum': 0}, 'webhookbuy': {'type': 'object'}, 'webhookbuycancel': {'type': 'object'}, + 'webhookbuyfill': {'type': 'object'}, 'webhooksell': {'type': 'object'}, 'webhooksellcancel': {'type': 'object'}, + 'webhooksellfill': {'type': 'object'}, 'webhookstatus': {'type': 'object'}, }, }, @@ -361,7 +371,9 @@ CONF_SCHEMA = { 'type': 'string', 'enum': AVAILABLE_DATAHANDLERS, 'default': 'jsongz' - } + }, + 'position_adjustment_enable': {'type': 'boolean'}, + 'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, }, 'definitions': { 'exchange': { @@ -387,6 +399,7 @@ CONF_SCHEMA = { }, 'uniqueItems': True }, + 'unknown_fee_rate': {'type': 'number'}, 'outdated_offset': {'type': 'integer', 'minimum': 1}, 'markets_refresh_interval': {'type': 'integer'}, 'ccxt_config': {'type': 'object'}, @@ -427,7 +440,6 @@ SCHEMA_TRADE_REQUIRED = [ 'dry_run_wallet', 'ask_strategy', 'bid_strategy', - 'unfilledtimeout', 'stoploss', 'minimal_roi', 'internals', diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 7d97661c4..16926923b 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -2,6 +2,8 @@ Helpers when analyzing backtest data """ import logging +from copy import copy +from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union @@ -9,21 +11,13 @@ import numpy as np import pandas as pd from freqtrade.constants import LAST_BT_RESULT_FN -from freqtrade.misc import json_load +from freqtrade.exceptions import OperationalException +from freqtrade.misc import get_backtest_metadata_filename, json_load from freqtrade.persistence import LocalTrade, Trade, init_db logger = logging.getLogger(__name__) -# Old format - maybe remove? -BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index", - "trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"] - -# Mid-term format, created by BacktestResult Named Tuple -BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration', - 'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open', - 'fee_close', 'amount', 'profit_abs', 'profit_ratio'] - # Newest format BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'open_rate', 'close_rate', @@ -106,10 +100,30 @@ def get_latest_hyperopt_file(directory: Union[Path, str], predef_filename: str = if isinstance(directory, str): directory = Path(directory) if predef_filename: + if Path(predef_filename).is_absolute(): + raise OperationalException( + "--hyperopt-filename expects only the filename, not an absolute path.") return directory / predef_filename return directory / get_latest_hyperopt_filename(directory) +def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]: + """ + Read metadata dictionary from backtest results file without reading and deserializing entire + file. + :param filename: path to backtest results file. + :return: metadata dict or None if metadata is not present. + """ + filename = get_backtest_metadata_filename(filename) + try: + with filename.open() as fp: + return json_load(fp) + except FileNotFoundError: + return {} + except Exception as e: + raise OperationalException('Unexpected error while loading backtest metadata.') from e + + def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: """ Load backtest statistics file. @@ -126,9 +140,80 @@ def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: with filename.open() as file: data = json_load(file) + # Legacy list format does not contain metadata. + if isinstance(data, dict): + data['metadata'] = load_backtest_metadata(filename) + return data +def _load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): + bt_data = load_backtest_stats(filename) + for k in ('metadata', 'strategy'): + results[k][strategy_name] = bt_data[k][strategy_name] + comparison = bt_data['strategy_comparison'] + for i in range(len(comparison)): + if comparison[i]['key'] == strategy_name: + results['strategy_comparison'].append(comparison[i]) + break + + +def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str], + min_backtest_date: datetime = None) -> Dict[str, Any]: + """ + Find existing backtest stats that match specified run IDs and load them. + :param dirname: pathlib.Path object, or string pointing to the file. + :param run_ids: {strategy_name: id_string} dictionary. + :param min_backtest_date: do not load a backtest older than specified date. + :return: results dict. + """ + # Copy so we can modify this dict without affecting parent scope. + run_ids = copy(run_ids) + dirname = Path(dirname) + results: Dict[str, Any] = { + 'metadata': {}, + 'strategy': {}, + 'strategy_comparison': [], + } + + # Weird glob expression here avoids including .meta.json files. + for filename in reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))): + metadata = load_backtest_metadata(filename) + if not metadata: + # Files are sorted from newest to oldest. When file without metadata is encountered it + # is safe to assume older files will also not have any metadata. + break + + for strategy_name, run_id in list(run_ids.items()): + strategy_metadata = metadata.get(strategy_name, None) + if not strategy_metadata: + # This strategy is not present in analyzed backtest. + continue + + if min_backtest_date is not None: + try: + backtest_date = strategy_metadata['backtest_start_time'] + except KeyError: + # TODO: this can be removed starting from feb 2022 + # The metadata-file without start_time was only available in develop + # and was never included in an official release. + # Older metadata format without backtest time, too old to consider. + return results + backtest_date = datetime.fromtimestamp(backtest_date, tz=timezone.utc) + if backtest_date < min_backtest_date: + # Do not use a cached result for this strategy as first result is too old. + del run_ids[strategy_name] + continue + + if strategy_metadata['run_id'] == run_id: + del run_ids[strategy_name] + _load_and_merge_backtest_result(strategy_name, filename, results) + + if len(run_ids) == 0: + break + return results + + def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame: """ Load backtest data file. @@ -167,23 +252,9 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non ) else: # old format - only with lists. - df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD) - if not df.empty: - df['open_date'] = pd.to_datetime(df['open_date'], - unit='s', - utc=True, - infer_datetime_format=True - ) - df['close_date'] = pd.to_datetime(df['close_date'], - unit='s', - utc=True, - infer_datetime_format=True - ) - # Create compatibility with new format - df['profit_abs'] = df['close_rate'] - df['open_rate'] + raise OperationalException( + "Backtest-results with only trades data are no longer supported.") if not df.empty: - if 'profit_ratio' not in df.columns: - df['profit_ratio'] = df['profit_percent'] df = df.sort_values("open_date").reset_index(drop=True) return df @@ -325,6 +396,7 @@ def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame], :param column: Column in the original dataframes to use :return: DataFrame with the column renamed to the dict key, and a column named mean, containing the mean of all pairs. + :raise: ValueError if no data is provided. """ df_comb = pd.concat([data[pair].set_index('date').rename( {column: pair}, axis=1)[pair] for pair in data], axis=1) @@ -360,9 +432,19 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, return df -def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio' - ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float]: +def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str + ) -> pd.DataFrame: + max_drawdown_df = pd.DataFrame() + max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() + max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() + max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + max_drawdown_df['date'] = profit_results.loc[:, date_col] + return max_drawdown_df + + +def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_ratio' + ): """ Calculate max drawdown and the corresponding close dates :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) @@ -375,10 +457,29 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = pd.DataFrame() - max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() - max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() - max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + + return max_drawdown_df + + +def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_abs', starting_balance: float = 0 + ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: + """ + Calculate max drawdown and the corresponding close dates + :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) + :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') + :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') + :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. + :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown) + with absolute max drawdown, high and low time and high and low value, + and the relative account drawdown + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + profit_results = trades.sort_values(date_col).reset_index(drop=True) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) idxmin = max_drawdown_df['drawdown'].idxmin() if idxmin == 0: @@ -388,7 +489,18 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] ['high_value'].idxmax(), 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative'] - return abs(min(max_drawdown_df['drawdown'])), high_date, low_date, high_val, low_val + max_drawdown_rel = 0.0 + if high_val + starting_balance != 0: + max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance) + + return ( + abs(min(max_drawdown_df['drawdown'])), + high_date, + low_date, + high_val, + low_val, + max_drawdown_rel + ) def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index dd60530aa..49fac99ea 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -6,7 +6,6 @@ from typing import List, Optional import numpy as np import pandas as pd -from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, ListPairsWithTimeframes, TradeList) @@ -61,10 +60,10 @@ class HDF5DataHandler(IDataHandler): filename = self._pair_data_filename(self._datadir, pair, timeframe) - ds = pd.HDFStore(filename, mode='a', complevel=9, complib='blosc') - ds.put(key, _data.loc[:, self._columns], format='table', data_columns=['date']) - - ds.close() + _data.loc[:, self._columns].to_hdf( + filename, key, mode='a', complevel=9, complib='blosc', + format='table', data_columns=['date'] + ) def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange] = None) -> pd.DataFrame: @@ -99,19 +98,6 @@ class HDF5DataHandler(IDataHandler): 'low': 'float', 'close': 'float', 'volume': 'float'}) return pairdata - def ohlcv_purge(self, pair: str, timeframe: str) -> bool: - """ - Remove data for this pair - :param pair: Delete data for this pair. - :param timeframe: Timeframe (e.g. "5m") - :return: True when deleted, false if file did not exist. - """ - filename = self._pair_data_filename(self._datadir, pair, timeframe) - if filename.exists(): - filename.unlink() - return True - return False - def ohlcv_append(self, pair: str, timeframe: str, data: pd.DataFrame) -> None: """ Append data to existing data structures @@ -142,11 +128,11 @@ class HDF5DataHandler(IDataHandler): """ key = self._pair_trades_key(pair) - ds = pd.HDFStore(self._pair_trades_filename(self._datadir, pair), - mode='a', complevel=9, complib='blosc') - ds.put(key, pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS), - format='table', data_columns=['timestamp']) - ds.close() + pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS).to_hdf( + self._pair_trades_filename(self._datadir, pair), key, + mode='a', complevel=9, complib='blosc', + format='table', data_columns=['timestamp'] + ) def trades_append(self, pair: str, data: TradeList): """ @@ -180,17 +166,9 @@ class HDF5DataHandler(IDataHandler): trades[['id', 'type']] = trades[['id', 'type']].replace({np.nan: None}) return trades.values.tolist() - def trades_purge(self, pair: str) -> bool: - """ - Remove data for this pair - :param pair: Delete data for this pair. - :return: True when deleted, false if file did not exist. - """ - filename = self._pair_trades_filename(self._datadir, pair) - if filename.exists(): - filename.unlink() - return True - return False + @classmethod + def _get_file_extension(cls): + return "h5" @classmethod def _pair_ohlcv_key(cls, pair: str, timeframe: str) -> str: @@ -199,15 +177,3 @@ class HDF5DataHandler(IDataHandler): @classmethod def _pair_trades_key(cls, pair: str) -> str: return f"{pair}/trades" - - @classmethod - def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path: - pair_s = misc.pair_to_filename(pair) - filename = datadir.joinpath(f'{pair_s}-{timeframe}.h5') - return filename - - @classmethod - def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path: - pair_s = misc.pair_to_filename(pair) - filename = datadir.joinpath(f'{pair_s}-trades.h5') - return filename diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index e6b8db322..7cdee8b71 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple import arrow -from pandas import DataFrame +from pandas import DataFrame, concat from freqtrade.configuration import TimeRange from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS @@ -208,7 +208,7 @@ def _download_pair_history(pair: str, *, else: # Run cleaning again to ensure there were no duplicate candles # Especially between existing and new data. - data = clean_ohlcv_dataframe(data.append(new_dataframe), timeframe, pair, + data = clean_ohlcv_dataframe(concat([data, new_dataframe], axis=0), timeframe, pair, fill_missing=False, drop_incomplete=False) logger.debug("New Start: %s", diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index 05052b2d7..cb02f98e3 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -12,6 +12,7 @@ from typing import List, Optional, Type from pandas import DataFrame +from freqtrade import misc from freqtrade.configuration import TimeRange from freqtrade.constants import ListPairsWithTimeframes, TradeList from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe @@ -26,6 +27,13 @@ class IDataHandler(ABC): def __init__(self, datadir: Path) -> None: self._datadir = datadir + @classmethod + def _get_file_extension(cls) -> str: + """ + Get file extension for this particular datahandler + """ + raise NotImplementedError() + @abstractclassmethod def ohlcv_get_available_data(cls, datadir: Path) -> ListPairsWithTimeframes: """ @@ -70,7 +78,6 @@ class IDataHandler(ABC): :return: DataFrame with ohlcv data, or empty DataFrame """ - @abstractmethod def ohlcv_purge(self, pair: str, timeframe: str) -> bool: """ Remove data for this pair @@ -78,6 +85,11 @@ class IDataHandler(ABC): :param timeframe: Timeframe (e.g. "5m") :return: True when deleted, false if file did not exist. """ + filename = self._pair_data_filename(self._datadir, pair, timeframe) + if filename.exists(): + filename.unlink() + return True + return False @abstractmethod def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None: @@ -123,13 +135,17 @@ class IDataHandler(ABC): :return: List of trades """ - @abstractmethod def trades_purge(self, pair: str) -> bool: """ Remove data for this pair :param pair: Delete data for this pair. :return: True when deleted, false if file did not exist. """ + filename = self._pair_trades_filename(self._datadir, pair) + if filename.exists(): + filename.unlink() + return True + return False def trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> TradeList: """ @@ -141,6 +157,18 @@ class IDataHandler(ABC): """ return trades_remove_duplicates(self._trades_load(pair, timerange=timerange)) + @classmethod + def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path: + pair_s = misc.pair_to_filename(pair) + filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}') + return filename + + @classmethod + def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path: + pair_s = misc.pair_to_filename(pair) + filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}') + return filename + def ohlcv_load(self, pair, timeframe: str, timerange: Optional[TimeRange] = None, fill_missing: bool = True, @@ -173,7 +201,7 @@ class IDataHandler(ABC): enddate = pairdf.iloc[-1]['date'] if timerange_startup: - self._validate_pairdata(pair, pairdf, timerange_startup) + self._validate_pairdata(pair, pairdf, timeframe, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup) if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): return pairdf @@ -200,7 +228,7 @@ class IDataHandler(ABC): return True return False - def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange): + def _validate_pairdata(self, pair, pairdata: DataFrame, timeframe: str, timerange: TimeRange): """ Validates pairdata for missing data at start end end and logs warnings. :param pairdata: Dataframe to validate @@ -210,12 +238,12 @@ class IDataHandler(ABC): if timerange.starttype == 'date': start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) if pairdata.iloc[0]['date'] > start: - logger.warning(f"Missing data at start for pair {pair}, " + logger.warning(f"Missing data at start for pair {pair} at {timeframe}, " f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}") if timerange.stoptype == 'date': stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) if pairdata.iloc[-1]['date'] < stop: - logger.warning(f"Missing data at end for pair {pair}, " + logger.warning(f"Missing data at end for pair {pair} at {timeframe}, " f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}") diff --git a/freqtrade/data/history/jsondatahandler.py b/freqtrade/data/history/jsondatahandler.py index 24d6e814b..ccefc8356 100644 --- a/freqtrade/data/history/jsondatahandler.py +++ b/freqtrade/data/history/jsondatahandler.py @@ -174,34 +174,10 @@ class JsonDataHandler(IDataHandler): pass return tradesdata - def trades_purge(self, pair: str) -> bool: - """ - Remove data for this pair - :param pair: Delete data for this pair. - :return: True when deleted, false if file did not exist. - """ - filename = self._pair_trades_filename(self._datadir, pair) - if filename.exists(): - filename.unlink() - return True - return False - - @classmethod - def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path: - pair_s = misc.pair_to_filename(pair) - filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}') - return filename - @classmethod def _get_file_extension(cls): return "json.gz" if cls._use_zip else "json" - @classmethod - def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path: - pair_s = misc.pair_to_filename(pair) - filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}') - return filename - class JsonGzDataHandler(JsonDataHandler): diff --git a/freqtrade/enums/__init__.py b/freqtrade/enums/__init__.py index d803baf31..eab483db3 100644 --- a/freqtrade/enums/__init__.py +++ b/freqtrade/enums/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa: F401 from freqtrade.enums.backteststate import BacktestState +from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.selltype import SellType diff --git a/freqtrade/enums/ordertypevalue.py b/freqtrade/enums/ordertypevalue.py new file mode 100644 index 000000000..9bb716171 --- /dev/null +++ b/freqtrade/enums/ordertypevalue.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class OrderTypeValues(str, Enum): + limit = 'limit' + market = 'market' diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 614e8ad68..2b9ed47ea 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange # isort: on from freqtrade.exchange.bibox import Bibox from freqtrade.exchange.binance import Binance +from freqtrade.exchange.bitpanda import Bitpanda from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.bybit import Bybit from freqtrade.exchange.coinbasepro import Coinbasepro @@ -17,6 +18,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges, from freqtrade.exchange.ftx import Ftx from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc +from freqtrade.exchange.huobi import Huobi from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin -from freqtrade.exchange.okex import Okex +from freqtrade.exchange.okx import Okx diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 4ba30b626..37ead6dd8 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -3,12 +3,8 @@ import logging from typing import Dict, List, Tuple import arrow -import ccxt -from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, - OperationalException, TemporaryError) from freqtrade.exchange import Exchange -from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -18,6 +14,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "stop_loss_limit"}, "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", "ohlcv_candle_limit": 1000, @@ -33,65 +30,6 @@ class Binance(Exchange): """ return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) - @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: - """ - creates a stoploss limit order. - this stoploss-limit is binance-specific. - It may work with a limited number of other exchanges, but this has not been tested yet. - """ - # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - rate = stop_price * limit_price_pct - - ordertype = "stop_loss_limit" - - stop_price = self.price_to_precision(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._config['dry_run']: - dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) - return dry_order - - try: - params = self._params.copy() - params.update({'stopPrice': stop_price}) - - amount = self.amount_to_precision(pair, amount) - - rate = self.price_to_precision(pair, rate) - - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', - amount=amount, price=rate, params=params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - self._log_exchange_response('create_stoploss_order', order) - return order - except ccxt.InsufficientFunds as e: - raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - # Errors: - # `binance Order would trigger immediately.` - raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/bitpanda.py b/freqtrade/exchange/bitpanda.py new file mode 100644 index 000000000..4cac35ce8 --- /dev/null +++ b/freqtrade/exchange/bitpanda.py @@ -0,0 +1,37 @@ +""" Bitpanda exchange subclass """ +import logging +from datetime import datetime, timezone +from typing import Dict, List, Optional + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Bitpanda(Exchange): + """ + Bitpanda exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + """ + + def get_trades_for_order(self, order_id: str, pair: str, since: datetime, + params: Optional[Dict] = None) -> List: + """ + Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. + The "since" argument passed in is coming from the database and is in UTC, + as timezone-native datetime object. + From the python documentation: + > Naive datetime instances are assumed to represent local time + Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the + transformation from local timezone to UTC. + This works for timezones UTC+ since then the result will contain trades from a few hours + instead of from the last 5 seconds, however fails for UTC- timezones, + since we're then asking for trades with a "since" argument in the future. + + :param order_id order_id: Order-id as given when creating the order + :param pair: Pair the order is for + :param since: datetime object of the order creation time. Assumes object is in UTC. + """ + params = {'to': int(datetime.now(timezone.utc).timestamp() * 1000)} + return super().get_trades_for_order(order_id, pair, since, params) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index a4c827e07..fad905b04 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -4,9 +4,20 @@ import time from functools import wraps from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError +from freqtrade.mixins import LoggingMixin logger = logging.getLogger(__name__) +__logging_mixin = None + + +def _get_logging_mixin(): + # Logging-mixin to cache kucoin responses + # Only to be used in retrier + global __logging_mixin + if not __logging_mixin: + __logging_mixin = LoggingMixin(logger) + return __logging_mixin # Maximum default retry count. @@ -16,13 +27,15 @@ API_FETCH_ORDER_RETRY_COUNT = 5 BAD_EXCHANGES = { "bitmex": "Various reasons.", - "phemex": "Does not provide history. ", + "phemex": "Does not provide history.", + "probit": "Requires additional, regular calls to `signIn()`.", "poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.", } MAP_EXCHANGE_CHILDCLASS = { 'binanceus': 'binance', 'binanceje': 'binance', + 'okex': 'okx', } @@ -72,28 +85,33 @@ def calculate_backoff(retrycount, max_retries): def retrier_async(f): async def wrapper(*args, **kwargs): count = kwargs.pop('count', API_RETRY_COUNT) + kucoin = args[0].name == "Kucoin" # Check if the exchange is KuCoin. try: return await f(*args, **kwargs) except TemporaryError as ex: - logger.warning('%s() returned exception: "%s"', f.__name__, ex) + msg = f'{f.__name__}() returned exception: "{ex}". ' if count > 0: - logger.warning('retrying %s() still for %s times', f.__name__, count) + msg += f'Retrying still for {count} times.' count -= 1 - kwargs.update({'count': count}) + kwargs['count'] = count if isinstance(ex, DDosProtection): - if "kucoin" in str(ex) and "429000" in str(ex): + if kucoin and "429000" in str(ex): # Temporary fix for 429000 error on kucoin # see https://github.com/freqtrade/freqtrade/issues/5700 for details. - logger.warning( + _get_logging_mixin().log_once( f"Kucoin 429 error, avoid triggering DDosProtection backoff delay. " - f"{count} tries left before giving up") + f"{count} tries left before giving up", logmethod=logger.warning) + # Reset msg to avoid logging too many times. + msg = '' else: backoff_delay = calculate_backoff(count + 1, API_RETRY_COUNT) logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}") await asyncio.sleep(backoff_delay) + if msg: + logger.warning(msg) return await wrapper(*args, **kwargs) else: - logger.warning('Giving up retrying: %s()', f.__name__) + logger.warning(msg + 'Giving up.') raise ex return wrapper @@ -106,9 +124,9 @@ def retrier(_func=None, retries=API_RETRY_COUNT): try: return f(*args, **kwargs) except (TemporaryError, RetryableOrderError) as ex: - logger.warning('%s() returned exception: "%s"', f.__name__, ex) + msg = f'{f.__name__}() returned exception: "{ex}". ' if count > 0: - logger.warning('retrying %s() still for %s times', f.__name__, count) + logger.warning(msg + f'Retrying still for {count} times.') count -= 1 kwargs.update({'count': count}) if isinstance(ex, (DDosProtection, RetryableOrderError)): @@ -118,7 +136,7 @@ def retrier(_func=None, retries=API_RETRY_COUNT): time.sleep(backoff_delay) return wrapper(*args, **kwargs) else: - logger.warning('Giving up retrying: %s()', f.__name__) + logger.warning(msg + 'Giving up.') raise ex return wrapper # Support both @retrier and @retrier(retries=2) syntax diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 19ad4e4b6..a502ad034 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -9,7 +9,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone from math import ceil -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Coroutine, Dict, List, Optional, Tuple import arrow import ccxt @@ -67,6 +67,8 @@ class Exchange: "ohlcv_params": {}, "ohlcv_candle_limit": 500, "ohlcv_partial_candle": True, + # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency + "ohlcv_volume_currency": "base", # "base" or "quote" "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", "l2_limit_range": None, @@ -83,6 +85,8 @@ class Exchange: self._api: ccxt.Exchange = None self._api_async: ccxt_async.Exchange = None self._markets: Dict = {} + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) self._config.update(config) @@ -170,8 +174,10 @@ class Exchange: def close(self): logger.debug("Exchange object destroyed, closing async loop") - if self._api_async and inspect.iscoroutinefunction(self._api_async.close): - asyncio.get_event_loop().run_until_complete(self._api_async.close()) + if (self._api_async and inspect.iscoroutinefunction(self._api_async.close) + and self._api_async.session): + logger.info("Closing async ccxt session.") + self.loop.run_until_complete(self._api_async.close()) def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt, ccxt_kwargs: Dict = {}) -> ccxt.Exchange: @@ -326,7 +332,7 @@ class Exchange: def _load_async_markets(self, reload: bool = False) -> None: try: if self._api_async: - asyncio.get_event_loop().run_until_complete( + self.loop.run_until_complete( self._api_async.load_markets(reload=reload)) except (asyncio.TimeoutError, ccxt.BaseError) as e: @@ -370,7 +376,7 @@ class Exchange: raise OperationalException( 'Could not load markets, therefore cannot start. ' 'Please investigate the above error for more details.' - ) + ) quote_currencies = self.get_quote_currencies() if stake_currency not in quote_currencies: raise OperationalException( @@ -594,7 +600,8 @@ class Exchange: # Dry-run methods def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Dict[str, Any]: + rate: float, params: Dict = {}, + stop_loss: bool = False) -> Dict[str, Any]: order_id = f'dry_run_{side}_{datetime.now().timestamp()}' _amount = self.amount_to_precision(pair, amount) dry_order: Dict[str, Any] = { @@ -606,21 +613,26 @@ class Exchange: 'cost': _amount * rate, 'type': ordertype, 'side': side, + 'filled': 0, 'remaining': _amount, - 'datetime': arrow.utcnow().isoformat(), + 'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'timestamp': arrow.utcnow().int_timestamp * 1000, - 'status': "closed" if ordertype == "market" else "open", + 'status': "closed" if ordertype == "market" and not stop_loss else "open", 'fee': None, 'info': {} } - if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: + if stop_loss: dry_order["info"] = {"stopPrice": dry_order["price"]} + dry_order["stopPrice"] = dry_order["price"] + # Workaround to avoid filling stoploss orders immediately + dry_order["ft_order_type"] = "stoploss" - if dry_order["type"] == "market": + if dry_order["type"] == "market" and not dry_order.get("ft_order_type"): # Update market order pricing average = self.get_dry_market_fill_price(pair, side, amount, rate) dry_order.update({ 'average': average, + 'filled': _amount, 'cost': dry_order['amount'] * average, }) dry_order = self.add_dry_order_fee(pair, dry_order) @@ -652,7 +664,8 @@ class Exchange: max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage)) remaining_amount = amount - filled_amount = 0 + filled_amount = 0.0 + book_entry_price = 0.0 for book_entry in ob[ob_type]: book_entry_price = book_entry[0] book_entry_coin_volume = book_entry[1] @@ -685,23 +698,29 @@ class Exchange: if not self.exchange_has('fetchL2OrderBook'): return True ob = self.fetch_l2_order_book(pair, 1) - if side == 'buy': - price = ob['asks'][0][0] - logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}") - if limit >= price: - return True - else: - price = ob['bids'][0][0] - logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}") - if limit <= price: - return True + try: + if side == 'buy': + price = ob['asks'][0][0] + logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}") + if limit >= price: + return True + else: + price = ob['bids'][0][0] + logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}") + if limit <= price: + return True + except IndexError: + # Ignore empty orderbooks when filling - can be filled with the next iteration. + pass return False def check_dry_limit_order_filled(self, order: Dict[str, Any]) -> Dict[str, Any]: """ Check dry-run limit order fill and update fee (if it filled). """ - if order['status'] != "closed" and order['type'] in ["limit"]: + if (order['status'] != "closed" + and order['type'] in ["limit"] + and not order.get('ft_order_type')): pair = order['symbol'] if self._is_dry_limit_order_filled(pair, order['side'], order['price']): order.update({ @@ -778,25 +797,96 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: + params = self._params.copy() + # Verify if stopPrice works for your exchange! + params.update({'stopPrice': stop_price}) + return params + + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss order. + requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market + to the corresponding exchange type. + The precise ordertype is determined by the order_types dict or exchange default. - Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each - exchange's subclass. + The exception below should never raise, since we disallow starting the bot in validate_ordertypes() - Note: Changes to this interface need to be applied to all sub-classes too. - """ - raise OperationalException(f"stoploss is not implemented for {self.name}.") + This may work with a limited number of other exchanges, but correct working + needs to be tested individually. + WARNING: setting `stoploss_on_exchange` to True will NOT auto-enable stoploss on exchange. + `stoploss_adjust` must still be implemented for this to work. + """ + if not self._ft_has['stoploss_on_exchange']: + raise OperationalException(f"stoploss is not implemented for {self.name}.") + + user_order_type = order_types.get('stoploss', 'market') + if user_order_type in self._ft_has["stoploss_order_types"].keys(): + ordertype = self._ft_has["stoploss_order_types"][user_order_type] + else: + # Otherwise pick only one available + ordertype = list(self._ft_has["stoploss_order_types"].values())[0] + user_order_type = list(self._ft_has["stoploss_order_types"].keys())[0] + + stop_price_norm = self.price_to_precision(pair, stop_price) + rate = None + if user_order_type == 'limit': + # Limit price threshold: As limit price should always be below stop-price + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + rate = stop_price * limit_price_pct + + # Ensure rate is less than stop price + if stop_price_norm <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + rate = self.price_to_precision(pair, rate) + + if self._config['dry_run']: + dry_order = self.create_dry_run_order( + pair, ordertype, "sell", amount, stop_price_norm, stop_loss=True) + return dry_order + + try: + params = self._get_stop_params(ordertype=ordertype, stop_price=stop_price_norm) + + amount = self.amount_to_precision(pair, amount) + + order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + amount=amount, price=rate, params=params) + logger.info(f"stoploss {user_order_type} order added for {pair}. " + f"stop price: {stop_price}. limit: {rate}") + self._log_exchange_response('create_stoploss_order', order) + return order + except ccxt.InsufficientFunds as e: + raise InsufficientFundsError( + f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + # Errors: + # `Order would trigger immediately.` + raise InvalidOrderException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f"Could not place stoploss order due to {e.__class__.__name__}. " + f"Message: {e}") from e + except ccxt.BaseError as e: + raise OperationalException(e) from e @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) - def fetch_order(self, order_id: str, pair: str) -> Dict: + def fetch_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: return self.fetch_dry_run_order(order_id) try: - order = self._api.fetch_order(order_id, pair) + order = self._api.fetch_order(order_id, pair, params=params) self._log_exchange_response('fetch_order', order) return order except ccxt.OrderNotFound as e: @@ -839,7 +929,7 @@ class Exchange: and order.get('filled') == 0.0) @retrier - def cancel_order(self, order_id: str, pair: str) -> Dict: + def cancel_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: try: order = self.fetch_dry_run_order(order_id) @@ -850,7 +940,7 @@ class Exchange: return {} try: - order = self._api.cancel_order(order_id, pair) + order = self._api.cancel_order(order_id, pair, params=params) self._log_exchange_response('cancel_order', order) return order except ccxt.InvalidOrder as e: @@ -940,7 +1030,7 @@ class Exchange: raise OperationalException(e) from e @retrier - def get_tickers(self, cached: bool = False) -> Dict: + def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict: """ :param cached: Allow cached result :return: fetch_tickers result @@ -950,7 +1040,7 @@ class Exchange: if tickers: return tickers try: - tickers = self._api.fetch_tickers() + tickers = self._api.fetch_tickers(symbols) self._fetch_tickers_cache['fetch_tickers'] = tickers return tickers except ccxt.NotSupported as e: @@ -1087,7 +1177,8 @@ class Exchange: # Fee handling @retrier - def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: + def get_trades_for_order(self, order_id: str, pair: str, since: datetime, + params: Optional[Dict] = None) -> List: """ Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. The "since" argument passed in is coming from the database and is in UTC, @@ -1111,8 +1202,10 @@ class Exchange: try: # Allow 5s offset to catch slight time offsets (discovered in #1185) # since needs to be int in milliseconds + _params = params if params else {} my_trades = self._api.fetch_my_trades( - pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000)) + pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000), + params=_params) matched_trades = [trade for trade in my_trades if trade['order'] == order_id] self._log_exchange_response('get_trades_for_order', matched_trades) @@ -1190,9 +1283,11 @@ class Exchange: tick = self.fetch_ticker(comb) fee_to_quote_rate = safe_value_fallback2(tick, tick, 'last', 'ask') - return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) except ExchangeError: - return None + fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None) + if not fee_to_quote_rate: + return None + return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8) def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]: """ @@ -1218,7 +1313,7 @@ class Exchange: :param since_ms: Timestamp in milliseconds to get history from :return: List with candle (OHLCV) data """ - pair, timeframe, data = asyncio.get_event_loop().run_until_complete( + pair, timeframe, data = self.loop.run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair)) logger.info(f"Downloaded data for {pair} with length {len(data)}.") @@ -1263,7 +1358,7 @@ class Exchange: results = await asyncio.gather(*input_coro, return_exceptions=True) for res in results: if isinstance(res, Exception): - logger.warning("Async code raised an exception: %s", res.__class__.__name__) + logger.warning(f"Async code raised an exception: {repr(res)}") if raise_: raise continue @@ -1276,6 +1371,22 @@ class Exchange: data = sorted(data, key=lambda x: x[0]) return pair, timeframe, data + def _build_coroutine(self, pair: str, timeframe: str, since_ms: Optional[int]) -> Coroutine: + if not since_ms and self.required_candle_call_count > 1: + # Multiple calls for one pair - to get more history + one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) + move_to = one_call * self.required_candle_call_count + now = timeframe_to_next_date(timeframe) + since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) + + if since_ms: + return self._async_get_historic_ohlcv( + pair, timeframe, since_ms=since_ms, raise_=True) + else: + # One call ... "regular" refresh + return self._async_get_candle_history( + pair, timeframe, since_ms=since_ms) + def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *, since_ms: Optional[int] = None, cache: bool = True ) -> Dict[Tuple[str, str], DataFrame]: @@ -1294,22 +1405,15 @@ class Exchange: cached_pairs = [] # Gather coroutines to run for pair, timeframe in set(pair_list): - if ((pair, timeframe) not in self._klines + if timeframe not in self.timeframes: + logger.warning( + f"Cannot download ({pair}, {timeframe}) combination as this timeframe is " + f"not available on {self.name}. Available timeframes are " + f"{', '.join(self.timeframes)}.") + continue + if ((pair, timeframe) not in self._klines or not cache or self._now_is_time_to_refresh(pair, timeframe)): - if not since_ms and self.required_candle_call_count > 1: - # Multiple calls for one pair - to get more history - one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) - move_to = one_call * self.required_candle_call_count - now = timeframe_to_next_date(timeframe) - since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) - - if since_ms: - input_coroutines.append(self._async_get_historic_ohlcv( - pair, timeframe, since_ms=since_ms, raise_=True)) - else: - # One call ... "regular" refresh - input_coroutines.append(self._async_get_candle_history( - pair, timeframe, since_ms=since_ms)) + input_coroutines.append(self._build_coroutine(pair, timeframe, since_ms)) else: logger.debug( "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", @@ -1317,27 +1421,32 @@ class Exchange: ) cached_pairs.append((pair, timeframe)) - results = asyncio.get_event_loop().run_until_complete( - asyncio.gather(*input_coroutines, return_exceptions=True)) - results_df = {} - # handle caching - for res in results: - if isinstance(res, Exception): - logger.warning("Async code raised an exception: %s", res.__class__.__name__) - continue - # Deconstruct tuple (has 3 elements) - pair, timeframe, ticks = res - # keeping last candle time as last refreshed time of the pair - if ticks: - self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 - # keeping parsed dataframe in cache - ohlcv_df = ohlcv_to_dataframe( - ticks, timeframe, pair=pair, fill_missing=True, - drop_incomplete=self._ohlcv_partial_candle) - results_df[(pair, timeframe)] = ohlcv_df - if cache: - self._klines[(pair, timeframe)] = ohlcv_df + # Chunk requests into batches of 100 to avoid overwelming ccxt Throttling + for input_coro in chunks(input_coroutines, 100): + async def gather_stuff(): + return await asyncio.gather(*input_coro, return_exceptions=True) + + results = self.loop.run_until_complete(gather_stuff()) + + # handle caching + for res in results: + if isinstance(res, Exception): + logger.warning(f"Async code raised an exception: {repr(res)}") + continue + # Deconstruct tuple (has 3 elements) + pair, timeframe, ticks = res + # keeping last candle time as last refreshed time of the pair + if ticks: + self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 + # keeping parsed dataframe in cache + ohlcv_df = ohlcv_to_dataframe( + ticks, timeframe, pair=pair, fill_missing=True, + drop_incomplete=self._ohlcv_partial_candle) + results_df[(pair, timeframe)] = ohlcv_df + if cache: + self._klines[(pair, timeframe)] = ohlcv_df + # Return cached klines for pair, timeframe in cached_pairs: results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False) @@ -1554,7 +1663,7 @@ class Exchange: if not self.exchange_has("fetchTrades"): raise OperationalException("This exchange does not support downloading Trades.") - return asyncio.get_event_loop().run_until_complete( + return self.loop.run_until_complete( self._async_get_trade_history(pair=pair, since=since, until=until, from_id=from_id)) @@ -1564,7 +1673,7 @@ def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = Non def is_exchange_officially_supported(exchange_name: str) -> bool: - return exchange_name in ['bittrex', 'binance', 'kraken', 'ftx', 'gateio', 'okex'] + return exchange_name in ['binance', 'bittrex', 'ftx', 'gateio', 'huobi', 'kraken', 'okx'] def ccxt_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]: diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index 6cd549d60..a346216b3 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -19,6 +19,7 @@ class Ftx(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, "ohlcv_candle_limit": 1500, + "ohlcv_volume_currency": "quote", } def market_is_tradable(self, market: Dict[str, Any]) -> bool: @@ -55,7 +56,7 @@ class Ftx(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, "sell", amount, stop_price, stop_loss=True) return dry_order try: @@ -105,15 +106,18 @@ class Ftx(Exchange): if order[0].get('status') == 'closed': # Trigger order was triggered ... real_order_id = order[0].get('info', {}).get('orderId') + # OrderId may be None for stoploss-market orders + # But contains "average" in these cases. + if real_order_id: + order1 = self._api.fetch_order(real_order_id, pair) + self._log_exchange_response('fetch_stoploss_order1', order1) + # Fake type to stop - as this was really a stop order. + order1['id_stop'] = order1['id'] + order1['id'] = order_id + order1['type'] = 'stop' + order1['status_stop'] = 'triggered' + return order1 - order1 = self._api.fetch_order(real_order_id, pair) - self._log_exchange_response('fetch_stoploss_order1', order1) - # Fake type to stop - as this was really a stop order. - order1['id_stop'] = order1['id'] - order1['id'] = order_id - order1['type'] = 'stop' - order1['status_stop'] = 'triggered' - return order1 return order[0] else: raise InvalidOrderException(f"Could not get stoploss order for id {order_id}") diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 018248a99..bfe996e86 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -21,6 +21,9 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, + "ohlcv_volume_currency": "quote", + "stoploss_order_types": {"limit": "limit"}, + "stoploss_on_exchange": True, } _headers = {'X-Gate-Channel-Id': 'freqtrade'} @@ -30,4 +33,25 @@ class Gateio(Exchange): if any(v == 'market' for k, v in order_types.items()): raise OperationalException( - f'Exchange {self.name} does not support market orders.') + f'Exchange {self.name} does not support market orders.') + + def fetch_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.fetch_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) + + def cancel_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.cancel_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return stop_loss > float(order['stopPrice']) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py new file mode 100644 index 000000000..d07e13497 --- /dev/null +++ b/freqtrade/exchange/huobi.py @@ -0,0 +1,39 @@ +""" Huobi exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Huobi(Exchange): + """ + Huobi exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + """ + + _ft_has: Dict = { + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "stop-limit"}, + "ohlcv_candle_limit": 1000, + "l2_limit_range": [5, 10, 20], + "l2_limit_range_required": False, + } + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop' and stop_loss > float(order['stopPrice']) + + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: + + params = self._params.copy() + params.update({ + "stopPrice": stop_price, + "operator": "lte", + }) + return params diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 1b069aa6c..8cec2500e 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -1,6 +1,6 @@ """ Kraken exchange subclass """ import logging -from typing import Any, Dict +from typing import Any, Dict, List import ccxt @@ -33,6 +33,12 @@ class Kraken(Exchange): return (parent_check and market.get('darkpool', False) is False) + def get_tickers(self, symbols: List[str] = None, cached: bool = False) -> Dict: + # Only fetch tickers for current stake currency + # Otherwise the request for kraken becomes too large. + symbols = list(self.get_markets(quote_currencies=[self._config['stake_currency']])) + return super().get_tickers(symbols=symbols, cached=cached) + @retrier def get_balances(self) -> dict: if self._config['dry_run']: @@ -80,6 +86,8 @@ class Kraken(Exchange): """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. + TODO: investigate if this can be combined with generic implementation + (careful, prices are reversed) """ params = self._params.copy() @@ -95,7 +103,7 @@ class Kraken(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, "sell", amount, stop_price, stop_loss=True) return dry_order try: diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 2884669a6..e55f49cce 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -19,8 +19,26 @@ class Kucoin(Exchange): """ _ft_has: Dict = { + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "limit", "market": "market"}, "l2_limit_range": [20, 100], "l2_limit_range_required": False, "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", } + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['info'].get('stop') is not None and stop_loss > float(order['stopPrice']) + + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: + + params = self._params.copy() + params.update({ + 'stopPrice': stop_price, + 'stop': 'loss' + }) + return params diff --git a/freqtrade/exchange/okex.py b/freqtrade/exchange/okx.py similarity index 73% rename from freqtrade/exchange/okex.py rename to freqtrade/exchange/okx.py index ec31be3a3..8e2cccf46 100644 --- a/freqtrade/exchange/okex.py +++ b/freqtrade/exchange/okx.py @@ -7,12 +7,12 @@ from freqtrade.exchange import Exchange logger = logging.getLogger(__name__) -class Okex(Exchange): - """Okex exchange class. +class Okx(Exchange): + """Okx exchange class. Contains adjustments needed for Freqtrade to work with this exchange. """ _ft_has: Dict = { - "ohlcv_candle_limit": 100, + "ohlcv_candle_limit": 300, } diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index db0453cd7..16864f814 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -7,16 +7,14 @@ import traceback from datetime import datetime, timezone from math import isclose from threading import Lock -from typing import Any, Dict, List, Optional - -import arrow +from typing import Any, Dict, List, Optional, Tuple from freqtrade import __version__, constants from freqtrade.configuration import validate_config_consistency from freqtrade.data.converter import order_book_to_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.edge import Edge -from freqtrade.enums import RPCMessageType, SellType, State +from freqtrade.enums import RPCMessageType, RunMode, SellType, State from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, InvalidOrderException, PricingError) from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds @@ -102,6 +100,8 @@ class FreqtradeBot(LoggingMixin): self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications @@ -126,6 +126,7 @@ class FreqtradeBot(LoggingMixin): self.rpc.cleanup() cleanup_db() + self.exchange.close() def startup(self) -> None: """ @@ -178,11 +179,17 @@ class FreqtradeBot(LoggingMixin): # First process current opened trades (positions) self.exit_positions(trades) + # Check if we need to adjust our current positions before attempting to buy new trades. + if self.strategy.position_adjustment_enable: + with self._exit_lock: + self.process_open_trade_positions() + # Then looking for buy opportunities if self.get_free_open_trades(): self.enter_positions() Trade.commit() + self.last_process = datetime.now(timezone.utc) def process_stopped(self) -> None: """ @@ -278,39 +285,19 @@ class FreqtradeBot(LoggingMixin): if order: logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.") self.update_trade_state(trade, order.order_id, - stoploss_order=order.ft_order_side == 'stoploss') + stoploss_order=order.ft_order_side == 'stoploss', + send_msg=False) trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() for trade in trades: if trade.is_open and not trade.fee_updated('buy'): order = trade.select_order('buy', False) - if order: + open_order = trade.select_order('buy', True) + if order and open_order is None: logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") - self.update_trade_state(trade, order.order_id) + self.update_trade_state(trade, order.order_id, send_msg=False) def handle_insufficient_funds(self, trade: Trade): - """ - Determine if we ever opened a sell order for this trade. - If not, try update buy fees - otherwise "refind" the open order we obviously lost. - """ - sell_order = trade.select_order('sell', None) - if sell_order: - self.refind_lost_order(trade) - else: - self.reupdate_enter_order_fees(trade) - - def reupdate_enter_order_fees(self, trade: Trade): - """ - Get buy order from database, and try to reupdate. - Handles trades where the initial fee-update did not work. - """ - logger.info(f"Trying to reupdate buy fees for {trade}") - order = trade.select_order('buy', False) - if order: - logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") - self.update_trade_state(trade, order.order_id) - - def refind_lost_order(self, trade): """ Try refinding a lost trade. Only used when InsufficientFunds appears on sell orders (stoploss or sell). @@ -323,9 +310,6 @@ class FreqtradeBot(LoggingMixin): if not order.ft_is_open: logger.debug(f"Order {order} is no longer open.") continue - if order.ft_order_side == 'buy': - # Skip buy side - this is handled by reupdate_buy_order_fees - continue try: fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair, order.ft_order_side == 'stoploss') @@ -337,6 +321,9 @@ class FreqtradeBot(LoggingMixin): if fo and fo['status'] == 'open': # Assume this as the open order trade.open_order_id = order.order_id + elif order.ft_order_side == 'buy': + if fo and fo['status'] == 'open': + trade.open_order_id = order.order_id if fo: logger.info(f"Found {order} for trade {trade}.") self.update_trade_state(trade, order.order_id, fo, @@ -442,6 +429,59 @@ class FreqtradeBot(LoggingMixin): else: return False +# +# BUY / increase positions / DCA logic and methods +# + def process_open_trade_positions(self): + """ + Tries to execute additional buy or sell orders for open trades (positions) + """ + # Walk through each pair and check if it needs changes + for trade in Trade.get_open_trades(): + # If there is any open orders, wait for them to finish. + if trade.open_order_id is None: + try: + self.check_and_call_adjust_trade_position(trade) + except DependencyException as exception: + logger.warning( + f"Unable to adjust position of trade for {trade.pair}: {exception}") + + def check_and_call_adjust_trade_position(self, trade: Trade): + """ + Check the implemented trading strategy for adjustment command. + If the strategy triggers the adjustment, a new order gets issued. + Once that completes, the existing trade is modified to match new data. + """ + if self.strategy.max_entry_position_adjustment > -1: + count_of_buys = trade.nr_of_successful_buys + if count_of_buys > self.strategy.max_entry_position_adjustment: + logger.debug(f"Max adjustment entries for {trade.pair} has been reached.") + return + else: + logger.debug("Max adjustment entries is set to unlimited.") + current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") + current_profit = trade.calc_profit_ratio(current_rate) + + min_stake_amount = self.exchange.get_min_pair_stake_amount(trade.pair, + current_rate, + self.strategy.stoploss) + max_stake_amount = self.wallets.get_available_stake_amount() + logger.debug(f"Calling adjust_trade_position for pair {trade.pair}") + stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, + default_retval=None)( + trade=trade, current_time=datetime.now(timezone.utc), current_rate=current_rate, + current_profit=current_profit, min_stake=min_stake_amount, max_stake=max_stake_amount) + + if stake_amount is not None and stake_amount > 0.0: + # We should increase our position + self.execute_entry(trade.pair, stake_amount, trade=trade) + + if stake_amount is not None and stake_amount < 0.0: + # We should decrease our position + # TODO: Selling part of the trade not implemented yet. + logger.error(f"Unable to decrease trade position / sell partially" + f" for pair {trade.pair}, feature not implemented.") + def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: """ Checks depth of market before executing a buy @@ -466,67 +506,49 @@ class FreqtradeBot(LoggingMixin): logger.info(f"Bids to asks delta for {pair} does not satisfy condition.") return False - def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, - forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool: + def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, *, + ordertype: Optional[str] = None, buy_tag: Optional[str] = None, + trade: Optional[Trade] = None) -> bool: """ Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY :param stake_amount: amount of stake-currency for the pair :return: True if a buy order is created, false if it fails. """ - time_in_force = self.strategy.order_time_in_force['buy'] - if price: - enter_limit_requested = price - else: - # Calculate price - proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") - custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, - default_retval=proposed_enter_rate)( - pair=pair, current_time=datetime.now(timezone.utc), - proposed_rate=proposed_enter_rate) + pos_adjust = trade is not None - enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) - - if not enter_limit_requested: - raise PricingError('Could not determine buy price.') - - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, - self.strategy.stoploss) - - if not self.edge: - max_stake_amount = self.wallets.get_available_stake_amount() - stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, - default_retval=stake_amount)( - pair=pair, current_time=datetime.now(timezone.utc), - current_rate=enter_limit_requested, proposed_stake=stake_amount, - min_stake=min_stake_amount, max_stake=max_stake_amount) - stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) + enter_limit_requested, stake_amount = self.get_valid_enter_price_and_stake( + pair, price, stake_amount, buy_tag, trade) if not stake_amount: return False - logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " - f"{stake_amount} ...") + if pos_adjust: + logger.info(f"Position adjust: about to create a new order for {pair} with stake: " + f"{stake_amount} for {trade}") + else: + logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: " + f"{stake_amount} ...") amount = stake_amount / enter_limit_requested - order_type = self.strategy.order_types['buy'] - if forcebuy: - # Forcebuy can define a different ordertype - order_type = self.strategy.order_types.get('forcebuy', order_type) + order_type = ordertype or self.strategy.order_types['buy'] + time_in_force = self.strategy.order_time_in_force['buy'] - if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( + if not pos_adjust and not strategy_safe_wrapper( + self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested, - time_in_force=time_in_force, current_time=datetime.now(timezone.utc)): + time_in_force=time_in_force, current_time=datetime.now(timezone.utc), + entry_tag=buy_tag): logger.info(f"User requested abortion of buying {pair}") return False - amount = self.exchange.amount_to_precision(pair, amount) order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", amount=amount, rate=enter_limit_requested, time_in_force=time_in_force) order_obj = Order.parse_from_ccxt_object(order, pair, 'buy') order_id = order['id'] order_status = order.get('status', None) + logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.") # we assume the order is executed at the price requested enter_limit_filled_price = enter_limit_requested @@ -562,57 +584,125 @@ class FreqtradeBot(LoggingMixin): # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') - trade = Trade( - pair=pair, - stake_amount=stake_amount, - amount=amount, - is_open=True, - amount_requested=amount_requested, - fee_open=fee, - fee_close=fee, - open_rate=enter_limit_filled_price, - open_rate_requested=enter_limit_requested, - open_date=datetime.utcnow(), - exchange=self.exchange.id, - open_order_id=order_id, - strategy=self.strategy.get_strategy_name(), - buy_tag=buy_tag, - timeframe=timeframe_to_minutes(self.config['timeframe']) - ) + # This is a new trade + if trade is None: + trade = Trade( + pair=pair, + stake_amount=stake_amount, + amount=amount, + is_open=True, + amount_requested=amount_requested, + fee_open=fee, + fee_close=fee, + open_rate=enter_limit_filled_price, + open_rate_requested=enter_limit_requested, + open_date=datetime.utcnow(), + exchange=self.exchange.id, + open_order_id=order_id, + fee_open_currency=None, + strategy=self.strategy.get_strategy_name(), + buy_tag=buy_tag, + timeframe=timeframe_to_minutes(self.config['timeframe']) + ) + else: + # This is additional buy, we reset fee_open_currency so timeout checking can work + trade.is_open = True + trade.fee_open_currency = None + trade.open_rate_requested = enter_limit_requested + trade.open_order_id = order_id + trade.orders.append(order_obj) - - # Update fees if order is closed - if order_status == 'closed': - self.update_trade_state(trade, order_id, order) - + trade.recalc_trade_from_orders() Trade.query.session.add(trade) Trade.commit() # Updating wallets self.wallets.update() - self._notify_enter(trade, order_type) + self._notify_enter(trade, order, order_type) + + if pos_adjust: + if order_status == 'closed': + logger.info(f"DCA order closed, trade should be up to date: {trade}") + trade = self.cancel_stoploss_on_exchange(trade) + else: + logger.info(f"DCA order {order_status}, will wait for resolution: {trade}") + + # Update fees if order is closed + if order_status == 'closed': + self.update_trade_state(trade, order_id, order) return True - def _notify_enter(self, trade: Trade, order_type: str) -> None: + def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade: + # First cancelling stoploss on exchange ... + if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: + try: + logger.info(f"Canceling stoploss on exchange for {trade}") + co = self.exchange.cancel_stoploss_order_with_result( + trade.stoploss_order_id, trade.pair, trade.amount) + trade.update_order(co) + except InvalidOrderException: + logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") + return trade + + def get_valid_enter_price_and_stake( + self, pair: str, price: Optional[float], stake_amount: float, + entry_tag: Optional[str], + trade: Optional[Trade]) -> Tuple[float, float]: + if price: + enter_limit_requested = price + else: + # Calculate price + proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=proposed_enter_rate)( + pair=pair, current_time=datetime.now(timezone.utc), + proposed_rate=proposed_enter_rate, entry_tag=entry_tag) + + enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate) + if not enter_limit_requested: + raise PricingError('Could not determine buy price.') + min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested, + self.strategy.stoploss) + if not self.edge and trade is None: + max_stake_amount = self.wallets.get_available_stake_amount() + stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, + default_retval=stake_amount)( + pair=pair, current_time=datetime.now(timezone.utc), + current_rate=enter_limit_requested, proposed_stake=stake_amount, + min_stake=min_stake_amount, max_stake=max_stake_amount, entry_tag=entry_tag) + stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) + return enter_limit_requested, stake_amount + + def _notify_enter(self, trade: Trade, order: Dict, order_type: Optional[str] = None, + fill: bool = False) -> None: """ Sends rpc notification when a buy occurred. """ + open_rate = safe_value_fallback(order, 'average', 'price') + if open_rate is None: + open_rate = trade.open_rate + + current_rate = trade.open_rate_requested + if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE): + current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy") + msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY, + 'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate, + 'limit': open_rate, # Deprecated (?) + 'open_rate': open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'fiat_currency': self.config.get('fiat_display_currency', None), - 'amount': trade.amount, + 'amount': safe_value_fallback(order, 'filled', 'amount') or trade.amount, 'open_date': trade.open_date or datetime.utcnow(), - 'current_rate': trade.open_rate_requested, + 'current_rate': current_rate, } # Send the message @@ -644,22 +734,6 @@ class FreqtradeBot(LoggingMixin): # Send the message self.rpc.send_msg(msg) - def _notify_enter_fill(self, trade: Trade) -> None: - msg = { - 'trade_id': trade.id, - 'type': RPCMessageType.BUY_FILL, - 'buy_tag': trade.buy_tag, - 'exchange': self.exchange.name.capitalize(), - 'pair': trade.pair, - 'open_rate': trade.open_rate, - 'stake_amount': trade.stake_amount, - 'stake_currency': self.config['stake_currency'], - 'fiat_currency': self.config.get('fiat_display_currency', None), - 'amount': trade.amount, - 'open_date': trade.open_date, - } - self.rpc.send_msg(msg) - # # SELL / exit positions / close trades logic and methods # @@ -682,7 +756,7 @@ class FreqtradeBot(LoggingMixin): trades_closed += 1 except DependencyException as exception: - logger.warning('Unable to sell trade %s: %s', trade.pair, exception) + logger.warning(f'Unable to sell trade {trade.pair}: {exception}') # Updating wallets if any trade occurred if trades_closed: @@ -799,11 +873,15 @@ class FreqtradeBot(LoggingMixin): stop_price = trade.open_rate * (1 + stoploss) if self.create_stoploss_order(trade=trade, stop_price=stop_price): + # The above will return False if the placement failed and the trade was force-sold. + # in which case the trade will be closed - which we must check below. trade.stoploss_last_update = datetime.utcnow() return False # If stoploss order is canceled for some reason we add it - if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'): + if (trade.is_open + and stoploss_order + and stoploss_order['status'] in ('canceled', 'cancelled')): if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss): return False else: @@ -813,7 +891,7 @@ class FreqtradeBot(LoggingMixin): # Finally we check if stoploss on exchange should be moved up because of trailing. # Triggered Orders are now real orders - so don't replace stoploss anymore if ( - stoploss_order + trade.is_open and stoploss_order and stoploss_order.get('status_stop') != 'triggered' and (self.config.get('trailing_stop', False) or self.config.get('use_custom_stoploss', False)) @@ -825,7 +903,7 @@ class FreqtradeBot(LoggingMixin): return False - def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: Dict) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange @@ -833,7 +911,9 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order): + stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stop_loss) + + if self.exchange.stoploss_adjust(stoploss_norm, order): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: @@ -868,24 +948,10 @@ class FreqtradeBot(LoggingMixin): logger.info( f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. ' f'Tag: {exit_tag if exit_tag is not None else "None"}') - self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag) + self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag=exit_tag) return True return False - def _check_timed_out(self, side: str, order: dict) -> bool: - """ - Check if timeout is active, and if the order is still open and timed out - """ - timeout = self.config.get('unfilledtimeout', {}).get(side) - ordertime = arrow.get(order['datetime']).datetime - if timeout is not None: - timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') - timeout_kwargs = {timeout_unit: -timeout} - timeout_threshold = arrow.utcnow().shift(**timeout_kwargs).datetime - return (order['status'] == 'open' and order['side'] == side - and ordertime < timeout_threshold) - return False - def check_handle_timedout(self) -> None: """ Check if any orders are timed out and cancel if necessary @@ -904,30 +970,32 @@ class FreqtradeBot(LoggingMixin): fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order) + order_obj = trade.select_order_by_order_id(trade.open_order_id) + if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('buy', order) - or strategy_safe_wrapper(self.strategy.check_buy_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): + or (order_obj and self.strategy.ft_check_timed_out( + 'buy', trade, order_obj, datetime.now(timezone.utc)) + ))): self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and ( fully_cancelled - or self._check_timed_out('sell', order) - or strategy_safe_wrapper(self.strategy.check_sell_timeout, - default_retval=False)(pair=trade.pair, - trade=trade, - order=order))): - self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) + or (order_obj and self.strategy.ft_check_timed_out( + 'sell', trade, order_obj, datetime.now(timezone.utc)) + ))): + canceled = self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT']) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) - if max_timeouts > 0 and canceled_count >= max_timeouts: + if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: logger.warning(f'Emergencyselling trade {trade}, as the sell order ' f'timed out {max_timeouts} times.') - self.execute_trade_exit(trade, order.get('price'), sell_reason=SellCheckTuple( - sell_type=SellType.EMERGENCY_SELL)) + try: + self.execute_trade_exit( + trade, order.get('price'), + sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL)) + except DependencyException as exception: + logger.warning(f'Unable to emergency sell trade {trade.pair}: {exception}') def cancel_all_open_orders(self) -> None: """ @@ -958,12 +1026,12 @@ class FreqtradeBot(LoggingMixin): # Cancelled orders may have the status of 'canceled' or 'closed' if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES: - filled_val = order.get('filled', 0.0) or 0.0 + filled_val: float = order.get('filled', 0.0) or 0.0 filled_stake = filled_val * trade.open_rate minstake = self.exchange.get_min_pair_stake_amount( trade.pair, trade.open_rate, self.strategy.stoploss) - if filled_val > 0 and filled_stake < minstake: + if filled_val > 0 and minstake and filled_stake < minstake: logger.warning( f"Order {trade.open_order_id} for {trade.pair} not cancelled, " f"as the filled amount of {filled_val} would result in an unsellable trade.") @@ -987,10 +1055,16 @@ class FreqtradeBot(LoggingMixin): filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled') if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC): logger.info('Buy order fully cancelled. Removing %s from database.', trade) - # if trade is not partially completed, just delete the trade - trade.delete() - was_trade_fully_canceled = True - reason += f", {constants.CANCEL_REASON['FULLY_CANCELLED']}" + # if trade is not partially completed and it's the only order, just delete the trade + if len(trade.orders) <= 1: + trade.delete() + was_trade_fully_canceled = True + reason += f", {constants.CANCEL_REASON['FULLY_CANCELLED']}" + else: + # FIXME TODO: This could possibly reworked to not duplicate the code 15 lines below. + self.update_trade_state(trade, trade.open_order_id, corder) + trade.open_order_id = None + logger.info('Partial buy order timeout for %s.', trade) else: # if trade is partially complete, edit the stake details for the trade # and close the order @@ -1010,11 +1084,12 @@ class FreqtradeBot(LoggingMixin): reason=reason) return was_trade_fully_canceled - def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str: + def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> bool: """ Sell cancel - cancel order and update trade - :return: Reason for cancel + :return: True if exit order was cancelled, false otherwise """ + cancelled = False # if trade is not partially completed, just cancel the order if order['remaining'] == order['amount'] or order.get('filled') == 0.0: if not self.exchange.check_order_canceled_empty(order): @@ -1025,7 +1100,7 @@ class FreqtradeBot(LoggingMixin): trade.update_order(co) except InvalidOrderException: logger.exception(f"Could not cancel sell order {trade.open_order_id}") - return 'error cancelling order' + return False logger.info('Sell order %s for %s.', reason, trade) else: reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] @@ -1039,9 +1114,12 @@ class FreqtradeBot(LoggingMixin): trade.close_date = None trade.is_open = True trade.open_order_id = None + trade.sell_reason = None + cancelled = True else: # TODO: figure out how to handle partially complete sell orders reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] + cancelled = False self.wallets.update() self._notify_exit_cancel( @@ -1049,7 +1127,7 @@ class FreqtradeBot(LoggingMixin): order_type=self.strategy.order_types['sell'], reason=reason ) - return reason + return cancelled def _safe_exit_amount(self, pair: str, amount: float) -> float: """ @@ -1081,7 +1159,10 @@ class FreqtradeBot(LoggingMixin): trade: Trade, limit: float, sell_reason: SellCheckTuple, - exit_tag: Optional[str] = None) -> bool: + *, + exit_tag: Optional[str] = None, + ordertype: Optional[str] = None, + ) -> bool: """ Executes a trade exit for the given trade and limit :param trade: Trade instance @@ -1095,8 +1176,8 @@ class FreqtradeBot(LoggingMixin): # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if self.config['dry_run'] and sell_type == 'stoploss' \ - and self.strategy.order_types['stoploss_on_exchange']: + if (self.config['dry_run'] and sell_type == 'stoploss' + and self.strategy.order_types['stoploss_on_exchange']): limit = trade.stop_loss # set custom_exit_price if available @@ -1111,22 +1192,12 @@ class FreqtradeBot(LoggingMixin): limit = self.get_valid_price(custom_exit_price, proposed_limit_rate) # First cancelling stoploss on exchange ... - if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id: - try: - co = self.exchange.cancel_stoploss_order_with_result(trade.stoploss_order_id, - trade.pair, trade.amount) - trade.update_order(co) - except InvalidOrderException: - logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") + trade = self.cancel_stoploss_on_exchange(trade) - order_type = self.strategy.order_types[sell_type] + order_type = ordertype or self.strategy.order_types[sell_type] if sell_reason.sell_type == SellType.EMERGENCY_SELL: # Emergency sells (default to market!) order_type = self.strategy.order_types.get("emergencysell", "market") - if sell_reason.sell_type == SellType.FORCE_SELL: - # Force sells (default to the sell_type defined in the strategy, - # but we allow this value to be changed) - order_type = self.strategy.order_types.get("forcesell", order_type) amount = self._safe_exit_amount(trade.pair, trade.amount) time_in_force = self.strategy.order_time_in_force['sell'] @@ -1158,16 +1229,16 @@ class FreqtradeBot(LoggingMixin): trade.sell_order_status = '' trade.close_rate_requested = limit trade.sell_reason = exit_tag or sell_reason.sell_reason - # In case of market sell orders the order can be closed immediately - if order.get('status', 'unknown') in ('closed', 'expired'): - self.update_trade_state(trade, trade.open_order_id, order) - Trade.commit() # Lock pair for one candle to prevent immediate re-buys self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') self._notify_exit(trade, order_type) + # In case of market sell orders the order can be closed immediately + if order.get('status', 'unknown') in ('closed', 'expired'): + self.update_trade_state(trade, trade.open_order_id, order) + Trade.commit() return True @@ -1264,13 +1335,14 @@ class FreqtradeBot(LoggingMixin): # def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None, - stoploss_order: bool = False) -> bool: + stoploss_order: bool = False, send_msg: bool = True) -> bool: """ Checks trades with open orders and updates the amount if necessary Handles closing both buy and sell orders. :param trade: Trade object of the trade we're analyzing :param order_id: Order-id of the order we're analyzing :param action_order: Already acquired order object + :param send_msg: Send notification - should always be True except in "recovery" methods :return: True if order has been cancelled without being filled partially, False otherwise """ if not order_id: @@ -1278,7 +1350,7 @@ class FreqtradeBot(LoggingMixin): return False # Update trade with order values - logger.info('Found open order for %s', trade) + logger.info(f'Found open order for {trade}') try: order = action_order or self.exchange.fetch_order_or_stoploss_order(order_id, trade.pair, @@ -1294,29 +1366,31 @@ class FreqtradeBot(LoggingMixin): # Handling of this will happen in check_handle_timedout. return True - # Try update amount (binance-fix) - try: - new_amount = self.get_real_amount(trade, order) - if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, - abs_tol=constants.MATH_CLOSE_PREC): - order['amount'] = new_amount - order.pop('filled', None) - trade.recalc_open_trade_value() - except DependencyException as exception: - logger.warning("Could not update trade amount: %s", exception) + order_obj = trade.select_order_by_order_id(order_id) + if not order_obj: + raise DependencyException( + f"Order_obj not found for {order_id}. This should not have happened.") + self.handle_order_fee(trade, order_obj, order) - trade.update(order) + trade.update_trade(order_obj) + # TODO: is the below necessary? it's already done in update_trade for filled buys + trade.recalc_trade_from_orders() Trade.commit() - # Updating wallets when order is closed + if order['status'] in constants.NON_OPEN_EXCHANGE_STATES: + # If a buy order was closed, force update on stoploss on exchange + if order.get('side', None) == 'buy': + trade = self.cancel_stoploss_on_exchange(trade) + # Updating wallets when order is closed + self.wallets.update() + if not trade.is_open: - if not stoploss_order and not trade.open_order_id: + if send_msg and not stoploss_order and not trade.open_order_id: self._notify_exit(trade, '', True) self.handle_protections(trade.pair) - self.wallets.update() - elif not trade.open_order_id: + elif send_msg and not trade.open_order_id: # Buy fill - self._notify_enter_fill(trade) + self._notify_enter(trade, order, fill=True) return False @@ -1351,6 +1425,16 @@ class FreqtradeBot(LoggingMixin): return real_amount return amount + def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: + # Try update amount (binance-fix) + try: + new_amount = self.get_real_amount(trade, order) + if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, + abs_tol=constants.MATH_CLOSE_PREC): + order_obj.ft_fee_base = trade.amount - new_amount + except DependencyException as exception: + logger.warning("Could not update trade amount: %s", exception) + def get_real_amount(self, trade: Trade, order: Dict) -> float: """ Detect and update trade fee. diff --git a/freqtrade/loggers.py b/freqtrade/loggers.py index 5c5831695..e5b6ddbe9 100644 --- a/freqtrade/loggers.py +++ b/freqtrade/loggers.py @@ -7,11 +7,25 @@ from typing import Any, Dict from freqtrade.exceptions import OperationalException +class FTBufferingHandler(BufferingHandler): + def flush(self): + """ + Override Flush behaviour - we keep half of the configured capacity + otherwise, we have moments with "empty" logs. + """ + self.acquire() + try: + # Keep half of the records in buffer. + self.buffer = self.buffer[-int(self.capacity / 2):] + finally: + self.release() + + logger = logging.getLogger(__name__) LOGFORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # Initialize bufferhandler - will be used for /log endpoints -bufferHandler = BufferingHandler(1000) +bufferHandler = FTBufferingHandler(1000) bufferHandler.setFormatter(Formatter(LOGFORMAT)) diff --git a/freqtrade/main.py b/freqtrade/main.py index 6593fbcb6..162b4d029 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -9,8 +9,8 @@ from typing import Any, List # check min. python version -if sys.version_info < (3, 7): # pragma: no cover - sys.exit("Freqtrade requires Python version >= 3.7") +if sys.version_info < (3, 8): # pragma: no cover + sys.exit("Freqtrade requires Python version >= 3.8") from freqtrade.commands import Arguments from freqtrade.exceptions import FreqtradeException, OperationalException diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 6f439866b..133014f39 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -2,11 +2,13 @@ Various tool function for Freqtrade and scripts """ import gzip +import hashlib import logging import re +from copy import deepcopy from datetime import datetime from pathlib import Path -from typing import Any, Iterator, List +from typing import Any, Iterator, List, Union from typing.io import IO from urllib.parse import urlparse @@ -27,18 +29,23 @@ def decimals_per_coin(coin: str): return DECIMALS_PER_COIN.get(coin, DECIMAL_PER_COIN_FALLBACK) -def round_coin_value(value: float, coin: str, show_coin_name=True) -> str: +def round_coin_value( + value: float, coin: str, show_coin_name=True, keep_trailing_zeros=False) -> str: """ Get price value for this coin :param value: Value to be printed :param coin: Which coin are we printing the price / value for :param show_coin_name: Return string in format: "222.22 USDT" or "222.22" + :param keep_trailing_zeros: Keep trailing zeros "222.200" vs. "222.2" :return: Formatted / rounded value (with or without coin name) """ + val = f"{value:.{decimals_per_coin(coin)}f}" + if not keep_trailing_zeros: + val = val.rstrip('0').rstrip('.') if show_coin_name: - return f"{value:.{decimals_per_coin(coin)}f} {coin}" - else: - return f"{value:.{decimals_per_coin(coin)}f}" + val = f"{val} {coin}" + + return val def shorten_date(_date: str) -> str: @@ -228,3 +235,34 @@ def parse_db_uri_for_logging(uri: str): return uri pwd = parsed_db_uri.netloc.split(':')[1].split('@')[0] return parsed_db_uri.geturl().replace(f':{pwd}@', ':*****@') + + +def get_strategy_run_id(strategy) -> str: + """ + Generate unique identification hash for a backtest run. Identical config and strategy file will + always return an identical hash. + :param strategy: strategy object. + :return: hex string id. + """ + digest = hashlib.sha1() + config = deepcopy(strategy.config) + + # Options that have no impact on results of individual backtest. + not_important_keys = ('strategy_list', 'original_config', 'telegram', 'api_server') + for k in not_important_keys: + if k in config: + del config[k] + + # Explicitly allow NaN values (e.g. max_open_trades). + # as it does not matter for getting the hash. + digest.update(rapidjson.dumps(config, default=str, + number_mode=rapidjson.NM_NAN).encode('utf-8')) + with open(strategy.__file__, 'rb') as fp: + digest.update(fp.read()) + return digest.hexdigest().lower() + + +def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path: + """Return metadata filename for specified backtest results file.""" + filename = Path(filename) + return filename.parent / Path(f'{filename.stem}.meta{filename.suffix}') diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 49957c2bb..eca643732 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -11,20 +11,22 @@ from typing import Any, Dict, List, Optional, Tuple from pandas import DataFrame +from freqtrade import constants from freqtrade.configuration import TimeRange, validate_config_consistency from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.data import history -from freqtrade.data.btanalysis import trade_list_to_dataframe +from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import BacktestState, SellType from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds +from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, store_backtest_stats) -from freqtrade.persistence import LocalTrade, PairLocks, Trade +from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager from freqtrade.resolvers import ExchangeResolver, StrategyResolver @@ -60,14 +62,17 @@ class Backtesting: LoggingMixin.show_output = False self.config = config - self.results: Optional[Dict[str, Any]] = None + self.results: Dict[str, Any] = {} + self.trade_id_counter: int = 0 + self.order_id_counter: int = 0 config['dry_run'] = True + self.run_ids: Dict[str, str] = {} self.strategylist: List[IStrategy] = [] self.all_results: Dict[str, Dict] = {} self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) - self.dataprovider = DataProvider(self.config, None) + self.dataprovider = DataProvider(self.config, self.exchange) if self.config.get('strategy_list', None): for strat in list(self.config['strategy_list']): @@ -89,7 +94,8 @@ class Backtesting: self.init_backtest_detail() self.pairlists = PairListManager(self.exchange, self.config) if 'VolumePairList' in self.pairlists.name_list: - raise OperationalException("VolumePairList not allowed for backtesting.") + raise OperationalException("VolumePairList not allowed for backtesting. " + "Please use StaticPairlist instead.") if 'PerformanceFilter' in self.pairlists.name_list: raise OperationalException("PerformanceFilter not allowed for backtesting.") @@ -122,7 +128,8 @@ class Backtesting: def __del__(self): self.cleanup() - def cleanup(self): + @staticmethod + def cleanup(): LoggingMixin.show_output = True PairLocks.use_db = True Trade.use_db = True @@ -227,6 +234,8 @@ class Backtesting: PairLocks.reset_locks() Trade.reset_trades() self.rejected_trades = 0 + self.timedout_entry_orders = 0 + self.timedout_exit_orders = 0 self.dataprovider.clear_cache() if enable_protections: self._load_protections(self.strategy) @@ -245,6 +254,9 @@ class Backtesting: Helper function to convert a processed dataframes into lists for performance reasons. Used by backtest() - so keep this optimized for performance. + + :param processed: a processed dictionary with format {pair, data}, which gets cleared to + optimize memory usage! """ # Every change to this headers list must evaluate further usages of the resulting tuple # and eventually change the constants for indexes at the top @@ -253,7 +265,8 @@ class Backtesting: self.progress.init_step(BacktestState.CONVERT, len(processed)) # Create dict with data - for pair, pair_data in processed.items(): + for pair in processed.keys(): + pair_data = processed[pair] self.check_abort() self.progress.increment() if not pair_data.empty: @@ -265,8 +278,15 @@ class Backtesting: df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() # Trim startup period from analyzed dataframe - df_analyzed = trim_dataframe(df_analyzed, self.timerange, - startup_candles=self.required_startup) + df_analyzed = processed[pair] = pair_data = trim_dataframe( + df_analyzed, self.timerange, startup_candles=self.required_startup) + # Update dataprovider cache + self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) + + # Create a copy of the dataframe before shifting, that way the buy signal/tag + # remains on the correct candle for callbacks. + df_analyzed = df_analyzed.copy() + # To avoid using data from future, we use buy/sell signals shifted # from the previous candle df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1) @@ -274,9 +294,6 @@ class Backtesting: df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1) df_analyzed.loc[:, 'exit_tag'] = df_analyzed.loc[:, 'exit_tag'].shift(1) - # Update dataprovider cache - self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) - df_analyzed = df_analyzed.drop(df_analyzed.head(1).index) # Convert from Pandas to list for performance reasons @@ -341,6 +358,18 @@ class Backtesting: # use Open rate if open_rate > calculated sell rate return sell_row[OPEN_IDX] + if ( + trade_dur == 0 + # Red candle (for longs), TODO: green candle (for shorts) + and sell_row[OPEN_IDX] > sell_row[CLOSE_IDX] # Red candle + and trade.open_rate < sell_row[OPEN_IDX] # trade-open below open_rate + and close_rate > sell_row[CLOSE_IDX] + ): + # ROI on opening candles with custom pricing can only + # trigger if the entry was at Open or lower. + # details: https: // github.com/freqtrade/freqtrade/issues/6261 + # If open_rate is < open, only allow sells below the close on red candles. + raise ValueError("Opening candle ROI on red candles.") # Use the maximum between close_rate and low as we # cannot sell outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. @@ -352,8 +381,42 @@ class Backtesting: else: return sell_row[OPEN_IDX] + def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple + ) -> LocalTrade: + + current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) + min_stake = self.exchange.get_min_pair_stake_amount(trade.pair, row[OPEN_IDX], -0.1) + max_stake = self.wallets.get_available_stake_amount() + stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, + default_retval=None)( + trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], + current_profit=current_profit, min_stake=min_stake, max_stake=max_stake) + + # Check if we should increase our position + if stake_amount is not None and stake_amount > 0.0: + pos_trade = self._enter_trade(trade.pair, row, stake_amount, trade) + if pos_trade is not None: + self.wallets.update() + return pos_trade + + return trade + + def _get_order_filled(self, rate: float, row: Tuple) -> bool: + """ Rate is within candle, therefore filled""" + return row[LOW_IDX] <= rate <= row[HIGH_IDX] + def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: + + # Check if we need to adjust our current positions + if self.strategy.position_adjustment_enable: + check_adjust_buy = True + if self.strategy.max_entry_position_adjustment > -1: + count_of_buys = trade.nr_of_successful_buys + check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment) + if check_adjust_buy: + trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) + sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore sell_candle_time, sell_row[BUY_IDX], @@ -364,10 +427,27 @@ class Backtesting: trade.close_date = sell_candle_time trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) - closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) - + try: + closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) + except ValueError: + return None + # call the custom exit price,with default value as previous closerate + current_profit = trade.calc_profit_ratio(closerate) + order_type = self.strategy.order_types['sell'] + if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL): + # Custom exit pricing only for sell-signals + if order_type == 'limit': + closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, + default_retval=closerate)( + pair=trade.pair, trade=trade, + current_time=sell_candle_time, + proposed_rate=closerate, current_profit=current_profit) + # We can't place orders lower than current low. + # freqtrade does not support this in live, and the order would fill immediately + closerate = max(closerate, sell_row[LOW_IDX]) # Confirm trade exit: time_in_force = self.strategy.order_time_in_force['sell'] + if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount, rate=closerate, @@ -387,7 +467,28 @@ class Backtesting: ): trade.sell_reason = sell_row[EXIT_TAG_IDX] - trade.close(closerate, show_msg=False) + self.order_id_counter += 1 + order = Order( + id=self.order_id_counter, + ft_trade_id=trade.id, + order_date=sell_candle_time, + order_update_date=sell_candle_time, + ft_is_open=True, + ft_pair=trade.pair, + order_id=str(self.order_id_counter), + symbol=trade.pair, + ft_order_side="sell", + side="sell", + order_type=order_type, + status="open", + price=closerate, + average=closerate, + amount=trade.amount, + filled=0, + remaining=trade.amount, + cost=trade.amount * closerate, + ) + trade.orders.append(order) return trade return None @@ -407,7 +508,9 @@ class Backtesting: return self._get_sell_trade_entry_for_candle(trade, sell_row) detail_data.loc[:, 'buy'] = sell_row[BUY_IDX] detail_data.loc[:, 'sell'] = sell_row[SELL_IDX] - headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high'] + detail_data.loc[:, 'buy_tag'] = sell_row[BUY_TAG_IDX] + detail_data.loc[:, 'exit_tag'] = sell_row[EXIT_TAG_IDX] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'exit_tag'] for det_row in detail_data[headers].values.tolist(): res = self._get_sell_trade_entry_for_candle(trade, det_row) if res: @@ -418,49 +521,110 @@ class Backtesting: else: return self._get_sell_trade_entry_for_candle(trade, sell_row) - def _enter_trade(self, pair: str, row: List) -> Optional[LocalTrade]: - try: - stake_amount = self.wallets.get_trade_stake_amount(pair, None) - except DependencyException: - return None + def _enter_trade(self, pair: str, row: Tuple, stake_amount: Optional[float] = None, + trade: Optional[LocalTrade] = None) -> Optional[LocalTrade]: - min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, row[OPEN_IDX], -0.05) or 0 + current_time = row[DATE_IDX].to_pydatetime() + entry_tag = row[BUY_TAG_IDX] if len(row) >= BUY_TAG_IDX + 1 else None + # let's call the custom entry price, using the open price as default price + order_type = self.strategy.order_types['buy'] + propose_rate = row[OPEN_IDX] + if order_type == 'limit': + propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price, + default_retval=row[OPEN_IDX])( + pair=pair, current_time=current_time, + proposed_rate=propose_rate, entry_tag=entry_tag) # default value is the open rate + # We can't place orders higher than current high (otherwise it'd be a stop limit buy) + # which freqtrade does not support in live. + propose_rate = min(propose_rate, row[HIGH_IDX]) + + min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0 max_stake_amount = self.wallets.get_available_stake_amount() - stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, - default_retval=stake_amount)( - pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], - proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount) + pos_adjust = trade is not None + if not pos_adjust: + try: + stake_amount = self.wallets.get_trade_stake_amount(pair, None, update=False) + except DependencyException: + return None + + stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, + default_retval=stake_amount)( + pair=pair, current_time=current_time, current_rate=propose_rate, + proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount, + entry_tag=entry_tag) + stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) if not stake_amount: - return None + # In case of pos adjust, still return the original trade + # If not pos adjust, trade is None + return trade - order_type = self.strategy.order_types['buy'] - time_in_force = self.strategy.order_time_in_force['sell'] + time_in_force = self.strategy.order_time_in_force['buy'] # Confirm trade entry: - if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( - pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX], - time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime()): - return None + if not pos_adjust: + if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( + pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate, + time_in_force=time_in_force, current_time=current_time, + entry_tag=entry_tag): + return None if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): - # Enter trade - has_buy_tag = len(row) >= BUY_TAG_IDX + 1 - trade = LocalTrade( - pair=pair, - open_rate=row[OPEN_IDX], - open_date=row[DATE_IDX].to_pydatetime(), - stake_amount=stake_amount, - amount=round(stake_amount / row[OPEN_IDX], 8), - fee_open=self.fee, - fee_close=self.fee, - is_open=True, - buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, - exchange='backtesting', + self.order_id_counter += 1 + amount = round(stake_amount / propose_rate, 8) + if trade is None: + # Enter trade + self.trade_id_counter += 1 + trade = LocalTrade( + id=self.trade_id_counter, + open_order_id=self.order_id_counter, + pair=pair, + open_rate=propose_rate, + open_rate_requested=propose_rate, + open_date=current_time, + stake_amount=stake_amount, + amount=amount, + amount_requested=amount, + fee_open=self.fee, + fee_close=self.fee, + is_open=True, + buy_tag=entry_tag, + exchange='backtesting', + orders=[] + ) + + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) + + order = Order( + id=self.order_id_counter, + ft_trade_id=trade.id, + ft_is_open=True, + ft_pair=trade.pair, + order_id=str(self.order_id_counter), + symbol=trade.pair, + ft_order_side="buy", + side="buy", + order_type=order_type, + status="open", + order_date=current_time, + order_filled_date=current_time, + order_update_date=current_time, + price=propose_rate, + average=propose_rate, + amount=amount, + filled=0, + remaining=amount, + cost=stake_amount + trade.fee_open, ) - return trade - return None + if pos_adjust and self._get_order_filled(order.price, row): + order.close_bt_order(current_time) + else: + trade.open_order_id = str(self.order_id_counter) + trade.orders.append(order) + trade.recalc_trade_from_orders() + + return trade def handle_left_open(self, open_trades: Dict[str, List[LocalTrade]], data: Dict[str, List[Tuple]]) -> List[LocalTrade]: @@ -471,6 +635,9 @@ class Backtesting: for pair in open_trades.keys(): if len(open_trades[pair]) > 0: for trade in open_trades[pair]: + if trade.open_order_id and trade.nr_of_successful_buys == 0: + # Ignore trade if buy-order did not fill yet + continue sell_row = data[pair][-1] trade.close_date = sell_row[DATE_IDX].to_pydatetime() @@ -491,6 +658,51 @@ class Backtesting: self.rejected_trades += 1 return False + def run_protections(self, enable_protections, pair: str, current_time: datetime): + if enable_protections: + self.protections.stop_per_pair(pair, current_time) + self.protections.global_stop(current_time) + + def check_order_cancel(self, trade: LocalTrade, current_time) -> bool: + """ + Check if an order has been canceled. + Returns True if the trade should be Deleted (initial order was canceled). + """ + for order in [o for o in trade.orders if o.ft_is_open]: + + timedout = self.strategy.ft_check_timed_out(order.side, trade, order, current_time) + if timedout: + if order.side == 'buy': + self.timedout_entry_orders += 1 + if trade.nr_of_successful_buys == 0: + # Remove trade due to buy timeout expiration. + return True + else: + # Close additional buy order + del trade.orders[trade.orders.index(order)] + if order.side == 'sell': + self.timedout_exit_orders += 1 + # Close sell order and retry selling on next signal. + del trade.orders[trade.orders.index(order)] + + return False + + def validate_row( + self, data: Dict, pair: str, row_index: int, current_time: datetime) -> Optional[Tuple]: + try: + # Row is treated as "current incomplete candle". + # Buy / sell signals are shifted by 1 to compensate for this. + row = data[pair][row_index] + except IndexError: + # missing Data for one pair at the end. + # Warnings for this are shown during data loading + return None + + # Waits until the time-counter reaches the start of the data for this pair. + if row[DATE_IDX] > current_time: + return None + return row + def backtest(self, processed: Dict, start_date: datetime, end_date: datetime, max_open_trades: int = 0, position_stacking: bool = False, @@ -502,7 +714,8 @@ class Backtesting: Of course try to not have ugly code. By some accessor are sometime slower than functions. Avoid extensive logging in this method and functions it calls. - :param processed: a processed dictionary with format {pair, data} + :param processed: a processed dictionary with format {pair, data}, which gets cleared to + optimize memory usage! :param start_date: backtesting timerange start datetime :param end_date: backtesting timerange end datetime :param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited @@ -512,14 +725,15 @@ class Backtesting: """ trades: List[LocalTrade] = [] self.prepare_backtest(enable_protections) - + # Ensure wallets are uptodate (important for --strategy-list) + self.wallets.update() # Use dict of lists with data for performance # (looping lists is a lot faster than pandas DataFrames) data: Dict = self._get_ohlcv_as_lists(processed) # Indexes per pair, so some pairs are allowed to have a missing start. indexes: Dict = defaultdict(int) - tmp = start_date + timedelta(minutes=self.timeframe_min) + current_time = start_date + timedelta(minutes=self.timeframe_min) open_trades: Dict[str, List[LocalTrade]] = defaultdict(list) open_trade_count = 0 @@ -528,35 +742,27 @@ class Backtesting: (end_date - start_date) / timedelta(minutes=self.timeframe_min))) # Loop timerange and get candle for each pair at that point in time - while tmp <= end_date: + while current_time <= end_date: open_trade_count_start = open_trade_count self.check_abort() for i, pair in enumerate(data): row_index = indexes[pair] - try: - # Row is treated as "current incomplete candle". - # Buy / sell signals are shifted by 1 to compensate for this. - row = data[pair][row_index] - except IndexError: - # missing Data for one pair at the end. - # Warnings for this are shown during data loading - continue - - # Waits until the time-counter reaches the start of the data for this pair. - if row[DATE_IDX] > tmp: + row = self.validate_row(data, pair, row_index, current_time) + if not row: continue row_index += 1 indexes[pair] = row_index self.dataprovider._set_dataframe_max_index(row_index) + # 1. Process buys. # without positionstacking, we can only have one open trade per pair. # max_open_trades must be respected # don't open on the last row if ( (position_stacking or len(open_trades[pair]) == 0) and self.trade_slot_available(max_open_trades, open_trade_count_start) - and tmp != end_date + and current_time != end_date and row[BUY_IDX] == 1 and row[SELL_IDX] != 1 and not PairLocks.is_pair_locked(pair, row[DATE_IDX]) @@ -564,32 +770,51 @@ class Backtesting: trade = self._enter_trade(pair, row) if trade: # TODO: hacky workaround to avoid opening > max_open_trades - # This emulates previous behaviour - not sure if this is correct + # This emulates previous behavior - not sure if this is correct # Prevents buying if the trade-slot was freed in this candle open_trade_count_start += 1 open_trade_count += 1 # logger.debug(f"{pair} - Emulate creation of new trade: {trade}.") open_trades[pair].append(trade) - LocalTrade.add_bt_trade(trade) for trade in list(open_trades[pair]): - # also check the buying candle for sell conditions. - trade_entry = self._get_sell_trade_entry(trade, row) - # Sell occurred - if trade_entry: + # 2. Process buy orders. + order = trade.select_order('buy', is_open=True) + if order and self._get_order_filled(order.price, row): + order.close_bt_order(current_time) + trade.open_order_id = None + LocalTrade.add_bt_trade(trade) + self.wallets.update() + + # 3. Create sell orders (if any) + if not trade.open_order_id: + self._get_sell_trade_entry(trade, row) # Place sell order if necessary + + # 4. Process sell orders. + order = trade.select_order('sell', is_open=True) + if order and self._get_order_filled(order.price, row): + trade.open_order_id = None + trade.close_date = current_time + trade.close(order.price, show_msg=False) + # logger.debug(f"{pair} - Backtesting sell {trade}") open_trade_count -= 1 open_trades[pair].remove(trade) - LocalTrade.close_bt_trade(trade) - trades.append(trade_entry) - if enable_protections: - self.protections.stop_per_pair(pair, row[DATE_IDX]) - self.protections.global_stop(tmp) + trades.append(trade) + self.wallets.update() + self.run_protections(enable_protections, pair, current_time) + + # 5. Cancel expired buy/sell orders. + if self.check_order_cancel(trade, current_time): + # Close trade due to buy timeout expiration. + open_trade_count -= 1 + open_trades[pair].remove(trade) + self.wallets.update() # Move time one configured time_interval ahead. self.progress.increment() - tmp += timedelta(minutes=self.timeframe_min) + current_time += timedelta(minutes=self.timeframe_min) trades += self.handle_left_open(open_trades, data=data) self.wallets.update() @@ -600,6 +825,8 @@ class Backtesting: 'config': self.strategy.config, 'locks': PairLocks.get_all_locks(), 'rejected_signals': self.rejected_trades, + 'timedout_entry_orders': self.timedout_entry_orders, + 'timedout_exit_orders': self.timedout_exit_orders, 'final_balance': self.wallets.get_total(self.strategy.config['stake_currency']), } @@ -649,6 +876,7 @@ class Backtesting: ) backtest_end_time = datetime.now(timezone.utc) results.update({ + 'run_id': self.run_ids.get(strat.get_strategy_name(), ''), 'backtest_start_time': int(backtest_start_time.timestamp()), 'backtest_end_time': int(backtest_end_time.timestamp()), }) @@ -656,6 +884,33 @@ class Backtesting: return min_date, max_date + def _get_min_cached_backtest_date(self): + min_backtest_date = None + backtest_cache_age = self.config.get('backtest_cache', constants.BACKTEST_CACHE_DEFAULT) + if self.timerange.stopts == 0 or datetime.fromtimestamp( + self.timerange.stopts, tz=timezone.utc) > datetime.now(tz=timezone.utc): + logger.warning('Backtest result caching disabled due to use of open-ended timerange.') + elif backtest_cache_age == 'day': + min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(days=1) + elif backtest_cache_age == 'week': + min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(weeks=1) + elif backtest_cache_age == 'month': + min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(weeks=4) + return min_backtest_date + + def load_prior_backtest(self): + self.run_ids = { + strategy.get_strategy_name(): get_strategy_run_id(strategy) + for strategy in self.strategylist + } + + # Load previous result that will be updated incrementally. + # This can be circumvented in certain instances in combination with downloading more data + min_backtest_date = self._get_min_cached_backtest_date() + if min_backtest_date is not None: + self.results = find_existing_backtest_stats( + self.config['user_data_dir'] / 'backtest_results', self.run_ids, min_backtest_date) + def start(self) -> None: """ Run backtesting end-to-end @@ -667,15 +922,38 @@ class Backtesting: self.load_bt_data_detail() logger.info("Dataload complete. Calculating indicators") - for strat in self.strategylist: - min_date, max_date = self.backtest_one_strategy(strat, data, timerange) - if len(self.strategylist) > 0: + self.load_prior_backtest() - self.results = generate_backtest_stats(data, self.all_results, - min_date=min_date, max_date=max_date) + for strat in self.strategylist: + if self.results and strat.get_strategy_name() in self.results['strategy']: + # When previous result hash matches - reuse that result and skip backtesting. + logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') + continue + min_date, max_date = self.backtest_one_strategy(strat, data, timerange) + + # Update old results with new ones. + if len(self.all_results) > 0: + results = generate_backtest_stats( + data, self.all_results, min_date=min_date, max_date=max_date) + if self.results: + self.results['metadata'].update(results['metadata']) + self.results['strategy'].update(results['strategy']) + self.results['strategy_comparison'].extend(results['strategy_comparison']) + else: + self.results = results if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) + # Results may be mixed up now. Sort them so they follow --strategy-list order. + if 'strategy_list' in self.config and len(self.results) > 0: + self.results['strategy_comparison'] = sorted( + self.results['strategy_comparison'], + key=lambda c: self.config['strategy_list'].index(c['key'])) + self.results['strategy'] = dict( + sorted(self.results['strategy'].items(), + key=lambda kv: self.config['strategy_list'].index(kv[0]))) + + if len(self.strategylist) > 0: # Show backtest results show_backtest_results(self.config, self.results) diff --git a/freqtrade/optimize/bt_progress.py b/freqtrade/optimize/bt_progress.py index d295956c7..c3b105915 100644 --- a/freqtrade/optimize/bt_progress.py +++ b/freqtrade/optimize/bt_progress.py @@ -12,7 +12,7 @@ class BTProgress: def init_step(self, action: BacktestState, max_steps: float): self._action = action self._max_steps = max_steps - self._proress = 0 + self._progress = 0 def set_new_value(self, new_value: float): self._progress = new_value diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index f211da750..cc9bafb0b 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -34,7 +34,7 @@ class EdgeCli: self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.strategy = StrategyResolver.load_strategy(self.config) - self.strategy.dp = DataProvider(config, None) + self.strategy.dp = DataProvider(config, self.exchange) validate_config_consistency(self.config) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 2c7cc0ea7..9664e6f07 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -76,6 +76,7 @@ class Hyperopt: self.config = config self.backtesting = Backtesting(self.config) + self.pairlist = self.backtesting.pairlists.whitelist if not self.config.get('hyperopt'): self.custom_hyperopt = HyperOptAuto(self.config) @@ -332,7 +333,7 @@ class Hyperopt: params_details = self._get_params_details(params_dict) strat_stats = generate_strategy_stats( - processed, self.backtesting.strategy.get_strategy_name(), + self.pairlist, self.backtesting.strategy.get_strategy_name(), backtesting_results, min_date, max_date, market_change=0 ) results_explanation = HyperoptTools.format_results_explanation_string( @@ -366,7 +367,7 @@ class Hyperopt: } def get_optimizer(self, dimensions: List[Dimension], cpu_count) -> Optimizer: - estimator = self.custom_hyperopt.generate_estimator() + estimator = self.custom_hyperopt.generate_estimator(dimensions=dimensions) acq_optimizer = "sampling" if isinstance(estimator, str): @@ -422,6 +423,7 @@ class Hyperopt: self.backtesting.exchange.close() self.backtesting.exchange._api = None # type: ignore self.backtesting.exchange._api_async = None # type: ignore + self.backtesting.exchange.loop = None # type: ignore # self.backtesting.exchange = None # type: ignore self.backtesting.pairlists = None # type: ignore diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py index 63b4b14e1..5bc0af42b 100644 --- a/freqtrade/optimize/hyperopt_auto.py +++ b/freqtrade/optimize/hyperopt_auto.py @@ -91,5 +91,5 @@ class HyperOptAuto(IHyperOpt): def trailing_space(self) -> List['Dimension']: return self._get_func('trailing_space')() - def generate_estimator(self) -> EstimatorType: - return self._get_func('generate_estimator')() + def generate_estimator(self, dimensions: List['Dimension'], **kwargs) -> EstimatorType: + return self._get_func('generate_estimator')(dimensions=dimensions, **kwargs) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 53b4f087c..01ffd7844 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -40,7 +40,7 @@ class IHyperOpt(ABC): IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED IHyperOpt.timeframe = str(config['timeframe']) - def generate_estimator(self) -> EstimatorType: + def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: """ Return base_estimator. Can be any of "GP", "RF", "ET", "GBRT" or an instance of a class diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss_calmar.py index ace08794a..846dae9ea 100644 --- a/freqtrade/optimize/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss_calmar.py @@ -47,10 +47,9 @@ class CalmarHyperOptLoss(IHyperOptLoss): # calculate max drawdown try: - _, _, _, high_val, low_val = calculate_max_drawdown( + _, _, _, _, _, max_drawdown = calculate_max_drawdown( results, value_col="profit_abs" ) - max_drawdown = (high_val - low_val) / high_val except ValueError: max_drawdown = 0 diff --git a/freqtrade/optimize/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss_profit_drawdown.py new file mode 100644 index 000000000..5bd12ff52 --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_profit_drawdown.py @@ -0,0 +1,30 @@ +""" +ProfitDrawDownHyperOptLoss + +This module defines the alternative HyperOptLoss class based on Profit & +Drawdown objective which can be used for Hyperoptimization. + +Possible to change `DRAWDOWN_MULT` to penalize drawdown objective for +individual needs. +""" +from pandas import DataFrame + +from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +# higher numbers penalize drawdowns more severely +DRAWDOWN_MULT = 0.075 + + +class ProfitDrawDownHyperOptLoss(IHyperOptLoss): + @staticmethod + def hyperopt_loss_function(results: DataFrame, trade_count: int, *args, **kwargs) -> float: + total_profit = results["profit_abs"].sum() + + try: + max_drawdown_abs = calculate_max_drawdown(results, value_col="profit_abs")[5] + except ValueError: + max_drawdown_abs = 0 + + return -1 * (total_profit * (1 - max_drawdown_abs * DRAWDOWN_MULT)) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 1204320da..8c84f772a 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -137,6 +137,7 @@ class HyperoptTools(): } if not HyperoptTools._test_hyperopt_results_exist(results_file): # No file found. + logger.warning(f"Hyperopt file {results_file} not found.") return [], 0 epochs = [] @@ -299,8 +300,7 @@ class HyperoptTools(): f"Objective: {results['loss']:.5f}") @staticmethod - def prepare_trials_columns(trials: pd.DataFrame, legacy_mode: bool, - has_drawdown: bool) -> pd.DataFrame: + def prepare_trials_columns(trials: pd.DataFrame, has_drawdown: bool) -> pd.DataFrame: trials['Best'] = '' if 'results_metrics.winsdrawslosses' not in trials.columns: @@ -309,33 +309,26 @@ class HyperoptTools(): if not has_drawdown: # Ensure compatibility with older versions of hyperopt results - trials['results_metrics.max_drawdown_abs'] = None - trials['results_metrics.max_drawdown'] = None + trials['results_metrics.max_drawdown_account'] = None - if not legacy_mode: - # New mode, using backtest result for metrics - trials['results_metrics.winsdrawslosses'] = trials.apply( - lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " - f"{x['results_metrics.losses']:>4}", axis=1) - trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', - 'results_metrics.winsdrawslosses', - 'results_metrics.profit_mean', 'results_metrics.profit_total_abs', - 'results_metrics.profit_total', 'results_metrics.holding_avg', - 'results_metrics.max_drawdown', 'results_metrics.max_drawdown_abs', - 'loss', 'is_initial_point', 'is_best']] + # New mode, using backtest result for metrics + trials['results_metrics.winsdrawslosses'] = trials.apply( + lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " + f"{x['results_metrics.losses']:>4}", axis=1) - else: - # Legacy mode - trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', - 'results_metrics.winsdrawslosses', 'results_metrics.avg_profit', - 'results_metrics.total_profit', 'results_metrics.profit', - 'results_metrics.duration', 'results_metrics.max_drawdown', - 'results_metrics.max_drawdown_abs', 'loss', 'is_initial_point', - 'is_best']] + trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', + 'results_metrics.winsdrawslosses', + 'results_metrics.profit_mean', 'results_metrics.profit_total_abs', + 'results_metrics.profit_total', 'results_metrics.holding_avg', + 'results_metrics.max_drawdown', + 'results_metrics.max_drawdown_account', 'results_metrics.max_drawdown_abs', + 'loss', 'is_initial_point', 'is_best']] - trials.columns = ['Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', - 'Total profit', 'Profit', 'Avg duration', 'Max Drawdown', - 'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best'] + trials.columns = [ + 'Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', + 'Total profit', 'Profit', 'Avg duration', 'max_drawdown', 'max_drawdown_account', + 'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best' + ] return trials @@ -351,10 +344,9 @@ class HyperoptTools(): tabulate.PRESERVE_WHITESPACE = True trials = json_normalize(results, max_level=1) - legacy_mode = 'results_metrics.total_trades' not in trials - has_drawdown = 'results_metrics.max_drawdown_abs' in trials.columns + has_account_drawdown = 'results_metrics.max_drawdown_account' in trials.columns - trials = HyperoptTools.prepare_trials_columns(trials, legacy_mode, has_drawdown) + trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown) trials['is_profit'] = False trials.loc[trials['is_initial_point'], 'Best'] = '* ' @@ -362,12 +354,12 @@ class HyperoptTools(): trials.loc[trials['is_initial_point'] & trials['is_best'], 'Best'] = '* Best' trials.loc[trials['Total profit'] > 0, 'is_profit'] = True trials['Trades'] = trials['Trades'].astype(str) - perc_multi = 1 if legacy_mode else 100 + # perc_multi = 1 if legacy_mode else 100 trials['Epoch'] = trials['Epoch'].apply( lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: f'{x * perc_multi:,.2f}%'.rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: f'{x:,.2%}'.rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Avg duration'] = trials['Avg duration'].apply( lambda x: f'{x:,.1f} m'.rjust(7, ' ') if isinstance(x, float) else f"{x}" @@ -379,24 +371,25 @@ class HyperoptTools(): stake_currency = config['stake_currency'] - if has_drawdown: - trials['Max Drawdown'] = trials.apply( - lambda x: '{} {}'.format( - round_coin_value(x['max_drawdown_abs'], stake_currency), - '({:,.2f}%)'.format(x['Max Drawdown'] * perc_multi).rjust(10, ' ') - ).rjust(25 + len(stake_currency)) - if x['Max Drawdown'] != 0.0 else '--'.rjust(25 + len(stake_currency)), - axis=1 - ) - else: - trials = trials.drop(columns=['Max Drawdown']) + trials[f"Max Drawdown{' (Acct)' if has_account_drawdown else ''}"] = trials.apply( + lambda x: "{} {}".format( + round_coin_value(x['max_drawdown_abs'], stake_currency, keep_trailing_zeros=True), + (f"({x['max_drawdown_account']:,.2%})" + if has_account_drawdown + else f"({x['max_drawdown']:,.2%})" + ).rjust(10, ' ') + ).rjust(25 + len(stake_currency)) + if x['max_drawdown'] != 0.0 or x['max_drawdown_account'] != 0.0 + else '--'.rjust(25 + len(stake_currency)), + axis=1 + ) - trials = trials.drop(columns=['max_drawdown_abs']) + trials = trials.drop(columns=['max_drawdown_abs', 'max_drawdown', 'max_drawdown_account']) trials['Profit'] = trials.apply( lambda x: '{} {}'.format( - round_coin_value(x['Total profit'], stake_currency), - '({:,.2f}%)'.format(x['Profit'] * perc_multi).rjust(10, ' ') + round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True), + f"({x['Profit']:,.2%})".rjust(10, ' ') ).rjust(25+len(stake_currency)) if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), axis=1 diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c4002fcbe..5b1c2e135 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,4 +1,5 @@ import logging +from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Dict, List, Union @@ -10,7 +11,8 @@ from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, calculate_max_drawdown) -from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value +from freqtrade.misc import (decimals_per_coin, file_dump_json, get_backtest_metadata_filename, + round_coin_value) logger = logging.getLogger(__name__) @@ -32,6 +34,11 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N recordfilename.parent, f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' ).with_suffix(recordfilename.suffix) + + # Store metadata separately. + file_dump_json(get_backtest_metadata_filename(filename), stats['metadata']) + del stats['metadata'] + file_dump_json(filename, stats) latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) @@ -46,20 +53,11 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]: '.2f', 'd', 's', 's'] -def _get_line_header(first_column: str, stake_currency: str) -> List[str]: +def _get_line_header(first_column: str, stake_currency: str, direction: str = 'Buys') -> List[str]: """ Generate header lines (goes in line with _generate_result_line()) """ - return [first_column, 'Buys', 'Avg Profit %', 'Cum Profit %', - f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', - 'Win Draw Loss Win%'] - - -def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]: - """ - Generate header lines (goes in line with _generate_result_line()) - """ - return [first_column, 'Sells', 'Avg Profit %', 'Cum Profit %', + return [first_column, direction, 'Avg Profit %', 'Cum Profit %', f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', 'Win Draw Loss Win%'] @@ -107,11 +105,11 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column } -def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_balance: int, +def generate_pair_metrics(pairlist: List[str], stake_currency: str, starting_balance: int, results: DataFrame, skip_nan: bool = False) -> List[Dict]: """ Generates and returns a list for the given backtest data and the results dataframe - :param data: Dict of containing data that was used during backtesting. + :param pairlist: Pairlist used :param stake_currency: stake-currency - used to correctly name headers :param starting_balance: Starting balance :param results: Dataframe containing the backtest results @@ -121,7 +119,7 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_b tabular_data = [] - for pair in data: + for pair in pairlist: result = results[results['pair'] == pair] if skip_nan and result['profit_abs'].isnull().all(): continue @@ -156,7 +154,7 @@ def generate_tag_metrics(tag_type: str, if skip_nan and result['profit_abs'].isnull().all(): continue - tabular_data.append(_generate_tag_result_line(result, starting_balance, tag)) + tabular_data.append(_generate_result_line(result, starting_balance, tag)) # Sort by total profit %: tabular_data = sorted(tabular_data, key=lambda k: k['profit_total_abs'], reverse=True) @@ -168,39 +166,6 @@ def generate_tag_metrics(tag_type: str, return [] -def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: - """ - Generate one result dict, with "first_column" as key. - """ - profit_sum = result['profit_ratio'].sum() - # (end-capital - starting capital) / starting capital - profit_total = result['profit_abs'].sum() / starting_balance - - return { - 'key': first_column, - 'trades': len(result), - 'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0, - 'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0, - 'profit_sum': profit_sum, - 'profit_sum_pct': round(profit_sum * 100.0, 2), - 'profit_total_abs': result['profit_abs'].sum(), - 'profit_total': profit_total, - 'profit_total_pct': round(profit_total * 100.0, 2), - 'duration_avg': str(timedelta( - minutes=round(result['trade_duration'].mean())) - ) if not result.empty else '0:00', - # 'duration_max': str(timedelta( - # minutes=round(result['trade_duration'].max())) - # ) if not result.empty else '0:00', - # 'duration_min': str(timedelta( - # minutes=round(result['trade_duration'].min())) - # ) if not result.empty else '0:00', - 'wins': len(result[result['profit_abs'] > 0]), - 'draws': len(result[result['profit_abs'] == 0]), - 'losses': len(result[result['profit_abs'] < 0]), - } - - def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]: """ Generate small table outlining Backtest results @@ -236,29 +201,21 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List return tabular_data -def generate_strategy_comparison(all_results: Dict) -> List[Dict]: +def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]: """ Generate summary per strategy - :param all_results: Dict of containing results for all strategies + :param bt_stats: Dict of containing results for all strategies :return: List of Dicts containing the metrics per Strategy """ tabular_data = [] - for strategy, results in all_results.items(): - tabular_data.append(_generate_result_line( - results['results'], results['config']['dry_run_wallet'], strategy) - ) - try: - max_drawdown_per, _, _, _, _ = calculate_max_drawdown(results['results'], - value_col='profit_ratio') - max_drawdown_abs, _, _, _, _ = calculate_max_drawdown(results['results'], - value_col='profit_abs') - except ValueError: - max_drawdown_per = 0 - max_drawdown_abs = 0 - tabular_data[-1]['max_drawdown_per'] = round(max_drawdown_per * 100, 2) - tabular_data[-1]['max_drawdown_abs'] = \ - round_coin_value(max_drawdown_abs, results['config']['stake_currency'], False) + for strategy, result in bt_stats.items(): + tabular_data.append(deepcopy(result['results_per_pair'][-1])) + # Update "key" to strategy (results_per_pair has it as "Total"). + tabular_data[-1]['key'] = strategy + tabular_data[-1]['max_drawdown_account'] = result['max_drawdown_account'] + tabular_data[-1]['max_drawdown_abs'] = round_coin_value( + result['max_drawdown_abs'], result['stake_currency'], False) return tabular_data @@ -394,14 +351,14 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]: } -def generate_strategy_stats(btdata: Dict[str, DataFrame], +def generate_strategy_stats(pairlist: List[str], strategy: str, content: Dict[str, Any], min_date: datetime, max_date: datetime, market_change: float ) -> Dict[str, Any]: """ - :param btdata: Backtest data + :param pairlist: List of pairs to backtest :param strategy: Strategy name :param content: Backtest result data in the format: {'results: results, 'config: config}}. @@ -414,11 +371,11 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], if not isinstance(results, DataFrame): return {} config = content['config'] - max_open_trades = min(config['max_open_trades'], len(btdata.keys())) + max_open_trades = min(config['max_open_trades'], len(pairlist)) starting_balance = config['dry_run_wallet'] stake_currency = config['stake_currency'] - pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, + pair_results = generate_pair_metrics(pairlist, stake_currency=stake_currency, starting_balance=starting_balance, results=results, skip_nan=False) @@ -427,7 +384,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, results=results) - left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, + left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency, starting_balance=starting_balance, results=results.loc[results['is_open']], skip_nan=True) @@ -471,7 +428,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'trades_per_day': round(len(results) / backtest_days, 2), 'market_change': market_change, - 'pairlist': list(btdata.keys()), + 'pairlist': pairlist, 'stake_amount': config['stake_amount'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), @@ -479,6 +436,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'dry_run_wallet': starting_balance, 'final_balance': content['final_balance'], 'rejected_signals': content['rejected_signals'], + 'timedout_entry_orders': content['timedout_entry_orders'], + 'timedout_exit_orders': content['timedout_exit_orders'], 'max_open_trades': max_open_trades, 'max_open_trades_setting': (config['max_open_trades'] if config['max_open_trades'] != float('inf') else -1), @@ -504,12 +463,14 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], } try: - max_drawdown, _, _, _, _ = calculate_max_drawdown( + max_drawdown_legacy, _, _, _, _, _ = calculate_max_drawdown( results, value_col='profit_ratio') - drawdown_abs, drawdown_start, drawdown_end, high_val, low_val = calculate_max_drawdown( - results, value_col='profit_abs') + (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val, + max_drawdown) = calculate_max_drawdown( + results, value_col='profit_abs', starting_balance=starting_balance) strat_stats.update({ - 'max_drawdown': max_drawdown, + 'max_drawdown': max_drawdown_legacy, # Deprecated - do not use + 'max_drawdown_account': max_drawdown, 'max_drawdown_abs': drawdown_abs, 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT), 'drawdown_start_ts': drawdown_start.timestamp() * 1000, @@ -529,6 +490,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], except ValueError: strat_stats.update({ 'max_drawdown': 0.0, + 'max_drawdown_account': 0.0, 'max_drawdown_abs': 0.0, 'max_drawdown_low': 0.0, 'max_drawdown_high': 0.0, @@ -555,16 +517,26 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame], :param max_date: Backtest end date :return: Dictionary containing results per strategy and a strategy summary. """ - result: Dict[str, Any] = {'strategy': {}} + result: Dict[str, Any] = { + 'metadata': {}, + 'strategy': {}, + 'strategy_comparison': [], + } market_change = calculate_market_change(btdata, 'close') - + metadata = {} + pairlist = list(btdata.keys()) for strategy, content in all_results.items(): - strat_stats = generate_strategy_stats(btdata, strategy, content, + strat_stats = generate_strategy_stats(pairlist, strategy, content, min_date, max_date, market_change=market_change) + metadata[strategy] = { + 'run_id': content['run_id'], + 'backtest_start_time': content['backtest_start_time'], + } result['strategy'][strategy] = strat_stats - strategy_results = generate_strategy_comparison(all_results=all_results) + strategy_results = generate_strategy_comparison(bt_stats=result['strategy']) + result['metadata'] = metadata result['strategy_comparison'] = strategy_results return result @@ -631,7 +603,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr if(tag_type == "buy_tag"): headers = _get_line_header("TAG", stake_currency) else: - headers = _get_line_header_sell("TAG", stake_currency) + headers = _get_line_header("TAG", stake_currency, 'Sells') floatfmt = _get_line_floatfmt(stake_currency) output = [ [ @@ -688,7 +660,12 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str: headers.append('Drawdown') # Align drawdown string on the center two space separator. - drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results] + if 'max_drawdown_account' in strategy_results[0]: + drawdown = [f'{t["max_drawdown_account"] * 100:.2f}' for t in strategy_results] + else: + # Support for prior backtest results + drawdown = [f'{t["max_drawdown_per"]:.2f}' for t in strategy_results] + dd_pad_abs = max([len(t['max_drawdown_abs']) for t in strategy_results]) dd_pad_per = max([len(dd) for dd in drawdown]) drawdown = [f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%' @@ -751,6 +728,9 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('Rejected Buy signals', strat_results.get('rejected_signals', 'N/A')), + ('Entry/Exit Timeouts', + f"{strat_results.get('timedout_entry_orders', 'N/A')} / " + f"{strat_results.get('timedout_exit_orders', 'N/A')}"), ('', ''), # Empty line to improve readability ('Min balance', round_coin_value(strat_results['csum_min'], @@ -758,7 +738,10 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Max balance', round_coin_value(strat_results['csum_max'], strat_results['stake_currency'])), - ('Drawdown', f"{strat_results['max_drawdown']:.2%}"), + # Compatibility to show old hyperopt results + ('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}") + if 'max_drawdown_account' in strat_results else ( + 'Drawdown', f"{strat_results['max_drawdown']:.2%}"), ('Drawdown', round_coin_value(strat_results['max_drawdown_abs'], strat_results['stake_currency'])), ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 1839c4130..2da24b748 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -28,7 +28,36 @@ def get_backup_name(tabs, backup_prefix: str): return table_back_name -def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, cols: List): +def get_last_sequence_ids(engine, trade_back_name, order_back_name): + order_id: int = None + trade_id: int = None + + if engine.name == 'postgresql': + with engine.begin() as connection: + trade_id = connection.execute(text("select nextval('trades_id_seq')")).fetchone()[0] + order_id = connection.execute(text("select nextval('orders_id_seq')")).fetchone()[0] + with engine.begin() as connection: + connection.execute(text( + f"ALTER SEQUENCE orders_id_seq rename to {order_back_name}_id_seq_bak")) + connection.execute(text( + f"ALTER SEQUENCE trades_id_seq rename to {trade_back_name}_id_seq_bak")) + return order_id, trade_id + + +def set_sequence_ids(engine, order_id, trade_id): + + if engine.name == 'postgresql': + with engine.begin() as connection: + if order_id: + connection.execute(text(f"ALTER SEQUENCE orders_id_seq RESTART WITH {order_id}")) + if trade_id: + connection.execute(text(f"ALTER SEQUENCE trades_id_seq RESTART WITH {trade_id}")) + + +def migrate_trades_and_orders_table( + decl_base, inspector, engine, + trade_back_name: str, cols: List, + order_back_name: str, cols_order: List): fee_open = get_column_def(cols, 'fee_open', 'fee') fee_open_cost = get_column_def(cols, 'fee_open_cost', 'null') fee_open_currency = get_column_def(cols, 'fee_open_currency', 'null') @@ -64,11 +93,20 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col # Schema migration necessary with engine.begin() as connection: - connection.execute(text(f"alter table trades rename to {table_back_name}")) + connection.execute(text(f"alter table trades rename to {trade_back_name}")) + with engine.begin() as connection: # drop indexes on backup table in new session - for index in inspector.get_indexes(table_back_name): - connection.execute(text(f"drop index {index['name']}")) + for index in inspector.get_indexes(trade_back_name): + if engine.name == 'mysql': + connection.execute(text(f"drop index {index['name']} on {trade_back_name}")) + else: + connection.execute(text(f"drop index {index['name']}")) + + order_id, trade_id = get_last_sequence_ids(engine, trade_back_name, order_back_name) + + drop_orders_table(engine, order_back_name) + # let SQLAlchemy create the schema as required decl_base.metadata.create_all(engine) @@ -100,9 +138,12 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col {sell_order_status} sell_order_status, {strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs - from {table_back_name} + from {trade_back_name} """)) + migrate_orders_table(engine, order_back_name, cols_order) + set_sequence_ids(engine, order_id, trade_id) + def migrate_open_orders_to_trades(engine): with engine.begin() as connection: @@ -121,31 +162,40 @@ def migrate_open_orders_to_trades(engine): """)) -def migrate_orders_table(decl_base, inspector, engine, table_back_name: str, cols: List): - # Schema migration necessary +def drop_orders_table(engine, table_back_name: str): + # Drop and recreate orders table as backup + # This drops foreign keys, too. with engine.begin() as connection: - connection.execute(text(f"alter table orders rename to {table_back_name}")) + connection.execute(text(f"create table {table_back_name} as select * from orders")) + connection.execute(text("drop table orders")) - with engine.begin() as connection: - # drop indexes on backup table in new session - for index in inspector.get_indexes(table_back_name): - connection.execute(text(f"drop index {index['name']}")) + +def migrate_orders_table(engine, table_back_name: str, cols_order: List): + + ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null') + average = get_column_def(cols_order, 'average', 'null') # let SQLAlchemy create the schema as required - decl_base.metadata.create_all(engine) with engine.begin() as connection: connection.execute(text(f""" insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, average, remaining, cost, - order_date, order_filled_date, order_update_date) + status, symbol, order_type, side, price, amount, filled, average, remaining, + cost, order_date, order_filled_date, order_update_date, ft_fee_base) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, null average, remaining, cost, - order_date, order_filled_date, order_update_date + status, symbol, order_type, side, price, amount, filled, {average} average, remaining, + cost, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base from {table_back_name} """)) +def set_sqlite_to_wal(engine): + if engine.name == 'sqlite' and str(engine.url) != 'sqlite://': + # Set Mode to + with engine.begin() as connection: + connection.execute(text("PRAGMA journal_mode=wal")) + + def check_migrate(engine, decl_base, previous_tables) -> None: """ Checks if migration is necessary and migrates if necessary @@ -153,26 +203,22 @@ def check_migrate(engine, decl_base, previous_tables) -> None: inspector = inspect(engine) cols = inspector.get_columns('trades') + cols_orders = inspector.get_columns('orders') tabs = get_table_names_for_table(inspector, 'trades') table_back_name = get_backup_name(tabs, 'trades_bak') + order_tabs = get_table_names_for_table(inspector, 'orders') + order_table_bak_name = get_backup_name(order_tabs, 'orders_bak') - # Check for latest column - if not has_column(cols, 'buy_tag'): - logger.info(f'Running database migration for trades - backup: {table_back_name}') - migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) - # Reread columns - the above recreated the table! - inspector = inspect(engine) - cols = inspector.get_columns('trades') + # Check if migration necessary + # Migrates both trades and orders table! + # if not has_column(cols, 'buy_tag'): + if 'orders' not in previous_tables or not has_column(cols_orders, 'ft_fee_base'): + logger.info(f"Running database migration for trades - " + f"backup: {table_back_name}, {order_table_bak_name}") + migrate_trades_and_orders_table( + decl_base, inspector, engine, table_back_name, cols, order_table_bak_name, cols_orders) if 'orders' not in previous_tables and 'trades' in previous_tables: logger.info('Moving open orders to Orders table.') migrate_open_orders_to_trades(engine) - else: - cols_order = inspector.get_columns('orders') - - if not has_column(cols_order, 'average'): - tabs = get_table_names_for_table(inspector, 'orders') - # Empty for now - as there is only one iteration of the orders table so far. - table_back_name = get_backup_name(tabs, 'orders_bak') - - migrate_orders_table(decl_base, inspector, engine, table_back_name, cols) + set_sqlite_to_wal(engine) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 1ef879dfc..b10ba355e 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -16,7 +16,6 @@ from sqlalchemy.sql.schema import UniqueConstraint from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES from freqtrade.enums import SellType from freqtrade.exceptions import DependencyException, OperationalException -from freqtrade.misc import safe_value_fallback from freqtrade.persistence.migrations import check_migrate @@ -39,6 +38,9 @@ def init_db(db_url: str, clean_open_orders: bool = False) -> None: """ kwargs = {} + if db_url == 'sqlite:///': + raise OperationalException( + f'Bad db-url {db_url}. For in-memory database, please use `sqlite://`.') if db_url == 'sqlite://': kwargs.update({ 'poolclass': StaticPool, @@ -113,14 +115,15 @@ class Order(_DECL_BASE): trade = relationship("Trade", back_populates="orders") - ft_order_side = Column(String(25), nullable=False) - ft_pair = Column(String(25), nullable=False) + # order_side can only be 'buy', 'sell' or 'stoploss' + ft_order_side: str = Column(String(25), nullable=False) + ft_pair: str = Column(String(25), nullable=False) ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - order_id = Column(String(255), nullable=False, index=True) + order_id: str = Column(String(255), nullable=False, index=True) status = Column(String(255), nullable=True) symbol = Column(String(25), nullable=True) - order_type = Column(String(50), nullable=True) + order_type: str = Column(String(50), nullable=True) side = Column(String(25), nullable=True) price = Column(Float, nullable=True) average = Column(Float, nullable=True) @@ -132,6 +135,29 @@ class Order(_DECL_BASE): order_filled_date = Column(DateTime, nullable=True) order_update_date = Column(DateTime, nullable=True) + ft_fee_base = Column(Float, nullable=True) + + @property + def order_date_utc(self) -> datetime: + """ Order-date with UTC timezoneinfo""" + return self.order_date.replace(tzinfo=timezone.utc) + + @property + def safe_price(self) -> float: + return self.average or self.price + + @property + def safe_filled(self) -> float: + return self.filled or self.amount or 0.0 + + @property + def safe_fee_base(self) -> float: + return self.ft_fee_base or 0.0 + + @property + def safe_amount_after_fee(self) -> float: + return self.safe_filled - self.safe_fee_base + def __repr__(self): return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, ' @@ -165,6 +191,37 @@ class Order(_DECL_BASE): self.order_filled_date = datetime.now(timezone.utc) self.order_update_date = datetime.now(timezone.utc) + def to_json(self) -> Dict[str, Any]: + return { + 'pair': self.ft_pair, + 'order_id': self.order_id, + 'status': self.status, + 'amount': self.amount, + 'average': round(self.average, 8) if self.average else 0, + 'safe_price': self.safe_price, + 'cost': self.cost if self.cost else 0, + 'filled': self.filled, + 'ft_order_side': self.ft_order_side, + 'is_open': self.ft_is_open, + 'order_date': self.order_date.strftime(DATETIME_PRINT_FORMAT) + if self.order_date else None, + 'order_timestamp': int(self.order_date.replace( + tzinfo=timezone.utc).timestamp() * 1000) if self.order_date else None, + 'order_filled_date': self.order_filled_date.strftime(DATETIME_PRINT_FORMAT) + if self.order_filled_date else None, + 'order_filled_timestamp': int(self.order_filled_date.replace( + tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, + 'order_type': self.order_type, + 'price': self.price, + 'remaining': self.remaining, + } + + def close_bt_order(self, close_date: datetime): + self.order_filled_date = close_date + self.filled = self.amount + self.status = 'closed' + self.ft_is_open = False + @staticmethod def update_orders(orders: List['Order'], order: Dict[str, Any]): """ @@ -247,7 +304,7 @@ class LocalTrade(): # absolute value of the initial stop loss initial_stop_loss: float = 0.0 # percentage value of the initial stop loss - initial_stop_loss_pct: float = 0.0 + initial_stop_loss_pct: Optional[float] = None # stoploss order id which is on exchange stoploss_order_id: Optional[str] = None # last update time of the stoploss order on exchange @@ -282,6 +339,9 @@ class LocalTrade(): return self.close_date.replace(tzinfo=timezone.utc) def to_json(self) -> Dict[str, Any]: + filled_orders = self.select_filled_orders() + orders = [order.to_json() for order in filled_orders] + return { 'trade_id': self.id, 'pair': self.pair, @@ -345,6 +405,7 @@ class LocalTrade(): 'max_rate': self.max_rate, 'open_order_id': self.open_order_id, + 'orders': orders, } @staticmethod @@ -385,7 +446,8 @@ class LocalTrade(): new_loss = float(current_price * (1 - abs(stoploss))) # no stop loss assigned yet - if not self.stop_loss: + # if not self.stop_loss: + if self.initial_stop_loss_pct is None: logger.debug(f"{self.pair} - Assigning new stoploss...") self._set_new_stoploss(new_loss, stoploss) self.initial_stop_loss = new_loss @@ -407,40 +469,39 @@ class LocalTrade(): f"Trailing stoploss saved us: " f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") - def update(self, order: Dict) -> None: + def update_trade(self, order: Order) -> None: """ Updates this entity with amount and actual open/close rates. :param order: order retrieved by exchange.fetch_order() :return: None """ - order_type = order['type'] # Ignore open and cancelled orders - if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None: + if order.status == 'open' or order.safe_price is None: return - logger.info('Updating trade (id=%s) ...', self.id) + logger.info(f'Updating trade (id={self.id}) ...') - if order_type in ('market', 'limit') and order['side'] == 'buy': + if order.ft_order_side == 'buy': # Update open rate and actual amount - self.open_rate = float(safe_value_fallback(order, 'average', 'price')) - self.amount = float(safe_value_fallback(order, 'filled', 'amount')) - self.recalc_open_trade_value() + self.open_rate = order.safe_price + self.amount = order.safe_amount_after_fee if self.is_open: - logger.info(f'{order_type.upper()}_BUY has been fulfilled for {self}.') + logger.info(f'{order.order_type.upper()}_BUY has been fulfilled for {self}.') self.open_order_id = None - elif order_type in ('market', 'limit') and order['side'] == 'sell': + self.recalc_trade_from_orders() + elif order.ft_order_side == 'sell': if self.is_open: - logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.') - self.close(safe_value_fallback(order, 'average', 'price')) - elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): + logger.info(f'{order.order_type.upper()}_SELL has been fulfilled for {self}.') + self.close(order.safe_price) + elif order.ft_order_side == 'stoploss': self.stoploss_order_id = None self.close_rate_requested = self.stop_loss self.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value if self.is_open: - logger.info(f'{order_type.upper()} is hit for {self}.') - self.close(safe_value_fallback(order, 'average', 'price')) + logger.info(f'{order.order_type.upper()} is hit for {self}.') + self.close(order.safe_price) else: - raise ValueError(f'Unknown order type: {order_type}') + raise ValueError(f'Unknown order type: {order.order_type}') Trade.commit() def close(self, rate: float, *, show_msg: bool = True) -> None: @@ -577,14 +638,59 @@ class LocalTrade(): profit_ratio = (close_trade_value / self.open_trade_value) - 1 return float(f"{profit_ratio:.8f}") - def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]: + def recalc_trade_from_orders(self): + # We need at least 2 entry orders for averaging amounts and rates. + if len(self.select_filled_orders('buy')) < 2: + # Just in case, still recalc open trade value + self.recalc_open_trade_value() + return + + total_amount = 0.0 + total_stake = 0.0 + for o in self.orders: + if (o.ft_is_open or + (o.ft_order_side != 'buy') or + (o.status not in NON_OPEN_EXCHANGE_STATES)): + continue + + tmp_amount = o.safe_amount_after_fee + tmp_price = o.average or o.price + if o.filled is not None: + tmp_amount = o.filled + if tmp_amount > 0.0 and tmp_price is not None: + total_amount += tmp_amount + total_stake += tmp_price * tmp_amount + + if total_amount > 0: + self.open_rate = total_stake / total_amount + self.stake_amount = total_stake + self.amount = total_amount + self.fee_open_cost = self.fee_open * self.stake_amount + self.recalc_open_trade_value() + if self.stop_loss_pct is not None and self.open_rate is not None: + self.adjust_stop_loss(self.open_rate, self.stop_loss_pct) + + def select_order_by_order_id(self, order_id: str) -> Optional[Order]: + """ + Finds order object by Order id. + :param order_id: Exchange order id + """ + for o in self.orders: + if o.order_id == order_id: + return o + return None + + def select_order( + self, order_side: str = None, is_open: Optional[bool] = None) -> Optional[Order]: """ Finds latest order for this orderside and status - :param order_side: Side of the order (either 'buy' or 'sell') + :param order_side: ft_order_side of the order (either 'buy', 'sell' or 'stoploss') :param is_open: Only search for open orders? :return: latest Order object if it exists, else None """ - orders = [o for o in self.orders if o.side == order_side] + orders = self.orders + if order_side: + orders = [o for o in self.orders if o.ft_order_side == order_side] if is_open is not None: orders = [o for o in orders if o.ft_is_open == is_open] if len(orders) > 0: @@ -592,6 +698,34 @@ class LocalTrade(): else: return None + def select_filled_orders(self, order_side: Optional[str] = None) -> List['Order']: + """ + Finds filled orders for this orderside. + :param order_side: Side of the order (either 'buy', 'sell', or None) + :return: array of Order objects + """ + return [o for o in self.orders if ((o.ft_order_side == order_side) or (order_side is None)) + and o.ft_is_open is False and + (o.filled or 0) > 0 and + o.status in NON_OPEN_EXCHANGE_STATES] + + @property + def nr_of_successful_buys(self) -> int: + """ + Helper function to count the number of buy orders that have been filled. + :return: int count of buy orders that have been filled for this trade. + """ + + return len(self.select_filled_orders('buy')) + + @property + def nr_of_successful_sells(self) -> int: + """ + Helper function to count the number of sell orders that have been filled. + :return: int count of sell orders that have been filled for this trade. + """ + return len(self.select_filled_orders('sell')) + @staticmethod def get_trades_proxy(*, pair: str = None, is_open: bool = None, open_date: datetime = None, close_date: datetime = None, @@ -662,6 +796,7 @@ class LocalTrade(): logger.info(f"Stoploss for {trade} needs adjustment...") # Force reset of stoploss trade.stop_loss = None + trade.initial_stop_loss_pct = None trade.adjust_stop_loss(trade.open_rate, desired_stoploss) logger.info(f"New stoploss: {trade.stop_loss}.") @@ -679,7 +814,7 @@ class Trade(_DECL_BASE, LocalTrade): id = Column(Integer, primary_key=True) - orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan") + orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan", lazy="joined") exchange = Column(String(25), nullable=False) pair = Column(String(25), nullable=False, index=True) @@ -690,11 +825,11 @@ class Trade(_DECL_BASE, LocalTrade): fee_close = Column(Float, nullable=False, default=0.0) fee_close_cost = Column(Float, nullable=True) fee_close_currency = Column(String(25), nullable=True) - open_rate = Column(Float) + open_rate: float = Column(Float) open_rate_requested = Column(Float) # open_trade_value - calculated via _calc_open_trade_value open_trade_value = Column(Float) - close_rate = Column(Float) + close_rate: Optional[float] = Column(Float) close_rate_requested = Column(Float) close_profit = Column(Float) close_profit_abs = Column(Float) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 6d44d56b1..90c6c1bce 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -5,7 +5,8 @@ from typing import Any, Dict, List import pandas as pd from freqtrade.configuration import TimeRange -from freqtrade.data.btanalysis import (calculate_max_drawdown, combine_dataframes_with_mean, +from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown, + calculate_underwater, combine_dataframes_with_mean, create_cum_profit, extract_trades_of_period, load_trades) from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider @@ -60,8 +61,8 @@ def init_plotscript(config, markets: List, startup_candles: int = 0): startup_candles, min_date) no_trades = False - filename = config.get('exportfilename') - if config.get('no_trades', False): + filename = config.get("exportfilename") + if config.get("no_trades", False): no_trades = True elif config['trade_source'] == 'file': if not filename.is_dir() and not filename.is_file(): @@ -160,7 +161,7 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, Add scatter points indicating max drawdown """ try: - max_drawdown, highdate, lowdate, _, _ = calculate_max_drawdown(trades) + _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades) drawdown = go.Scatter( x=[highdate, lowdate], @@ -185,6 +186,48 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, return fig +def add_underwater(fig, row, trades: pd.DataFrame) -> make_subplots: + """ + Add underwater plot + """ + try: + underwater = calculate_underwater(trades, value_col="profit_abs") + + underwater = go.Scatter( + x=underwater['date'], + y=underwater['drawdown'], + name="Underwater Plot", + fill='tozeroy', + fillcolor='#cc362b', + line={'color': '#cc362b'}, + ) + fig.add_trace(underwater, row, 1) + except ValueError: + logger.warning("No trades found - not plotting underwater plot") + return fig + + +def add_parallelism(fig, row, trades: pd.DataFrame, timeframe: str) -> make_subplots: + """ + Add Chart showing trade parallelism + """ + try: + result = analyze_trade_parallelism(trades, timeframe) + + drawdown = go.Scatter( + x=result.index, + y=result['open_trades'], + name="Parallel trades", + fill='tozeroy', + fillcolor='#242222', + line={'color': '#242222'}, + ) + fig.add_trace(drawdown, row, 1) + except ValueError: + logger.warning("No trades found - not plotting Parallelism.") + return fig + + def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: """ Add trades to "fig" @@ -192,10 +235,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: # Trades can be empty if trades is not None and len(trades) > 0: # Create description for sell summarizing the trade - trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, " - f"{row['sell_reason']}, " - f"{row['trade_duration']} min", - axis=1) + trades['desc'] = trades.apply( + lambda row: f"{row['profit_ratio']:.2%}, " + + (f"{row['buy_tag']}, " if row['buy_tag'] is not None else "") + + f"{row['sell_reason']}, " + + f"{row['trade_duration']} min", + axis=1) trade_buys = go.Scatter( x=trades["open_date"], y=trades["open_rate"], @@ -460,7 +505,12 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], trades: pd.DataFrame, timeframe: str, stake_currency: str) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" - df_comb = combine_dataframes_with_mean(data, "close") + try: + df_comb = combine_dataframes_with_mean(data, "close") + except ValueError: + raise OperationalException( + "No data found. Please make sure that data is available for " + "the timerange and pairs selected.") # Trim trades to available OHLCV data trades = extract_trades_of_period(df_comb, trades, date_index=True) @@ -477,20 +527,30 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], name='Avg close price', ) - fig = make_subplots(rows=3, cols=1, shared_xaxes=True, - row_width=[1, 1, 1], + fig = make_subplots(rows=5, cols=1, shared_xaxes=True, + row_heights=[1, 1, 1, 0.5, 1], vertical_spacing=0.05, - subplot_titles=["AVG Close Price", "Combined Profit", "Profit per pair"]) + subplot_titles=[ + "AVG Close Price", + "Combined Profit", + "Profit per pair", + "Parallelism", + "Underwater", + ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis3'].update(title=f'Profit {stake_currency}') + fig['layout']['yaxis4'].update(title='Trade count') + fig['layout']['yaxis5'].update(title='Underwater Plot') fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) fig.add_trace(avgclose, 1, 1) fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe) + fig = add_parallelism(fig, 4, trades, timeframe) + fig = add_underwater(fig, 5, trades) for pair in pairs: profit_col = f'cum_profit_{pair}' diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 5627d82ce..a6d5ec79b 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -98,7 +98,7 @@ class AgeFilter(IPairList): """ Validate age for the ticker :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache diff --git a/freqtrade/plugins/pairlist/PerformanceFilter.py b/freqtrade/plugins/pairlist/PerformanceFilter.py index 671b6362b..5b02a47ab 100644 --- a/freqtrade/plugins/pairlist/PerformanceFilter.py +++ b/freqtrade/plugins/pairlist/PerformanceFilter.py @@ -60,6 +60,7 @@ class PerformanceFilter(IPairList): # Get pairlist from performance dataframe values list_df = pd.DataFrame({'pair': pairlist}) + list_df['prior_idx'] = list_df.index # Set initial value for pairs with no trades to 0 # Sort the list using: @@ -67,15 +68,15 @@ class PerformanceFilter(IPairList): # - then count (low to high, so as to favor same performance with fewer trades) # - then pair name alphametically sorted_df = list_df.merge(performance, on='pair', how='left')\ - .fillna(0).sort_values(by=['count', 'pair'], ascending=True)\ - .sort_values(by=['profit'], ascending=False) + .fillna(0).sort_values(by=['count', 'prior_idx'], ascending=True)\ + .sort_values(by=['profit_ratio'], ascending=False) if self._min_profit is not None: - removed = sorted_df[sorted_df['profit'] < self._min_profit] + removed = sorted_df[sorted_df['profit_ratio'] < self._min_profit] for _, row in removed.iterrows(): self.log_once( - f"Removing pair {row['pair']} since {row['profit']} is " + f"Removing pair {row['pair']} since {row['profit_ratio']} is " f"below {self._min_profit}", logger.info) - sorted_df = sorted_df[sorted_df['profit'] >= self._min_profit] + sorted_df = sorted_df[sorted_df['profit_ratio'] >= self._min_profit] pairlist = sorted_df['pair'].tolist() diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index a3c262e8c..521f38635 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -51,7 +51,7 @@ class PrecisionFilter(IPairList): :param ticker: ticker dict as returned from ccxt.fetch_tickers() :return: True if the pair can stay, false if it should be removed """ - stop_price = ticker['ask'] * self._stoploss + stop_price = ticker['last'] * self._stoploss # Adjust stop-prices to precision sp = self._exchange.price_to_precision(pair, stop_price) diff --git a/freqtrade/plugins/pairlist/ShuffleFilter.py b/freqtrade/plugins/pairlist/ShuffleFilter.py index 4d3dd29e3..663bba49b 100644 --- a/freqtrade/plugins/pairlist/ShuffleFilter.py +++ b/freqtrade/plugins/pairlist/ShuffleFilter.py @@ -5,6 +5,7 @@ import logging import random from typing import Any, Dict, List +from freqtrade.enums import RunMode from freqtrade.plugins.pairlist.IPairList import IPairList @@ -18,7 +19,15 @@ class ShuffleFilter(IPairList): pairlist_pos: int) -> None: super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) - self._seed = pairlistconfig.get('seed') + # Apply seed in backtesting mode to get comparable results, + # but not in live modes to get a non-repeating order of pairs during live modes. + if config.get('runmode') in (RunMode.LIVE, RunMode.DRY_RUN): + self._seed = None + logger.info("Live mode detected, not applying seed.") + else: + self._seed = pairlistconfig.get('seed') + logger.info(f"Backtesting mode detected, applying seed value: {self._seed}") + self._random = random.Random(self._seed) @property diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index 2d6e728ec..ad0c0f0be 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -47,7 +47,7 @@ class SpreadFilter(IPairList): spread = 1 - ticker['bid'] / ticker['ask'] if spread > self._max_spread_ratio: self.log_once(f"Removed {pair} from whitelist, because spread " - f"{spread * 100:.3%} > {self._max_spread_ratio:.3%}", + f"{spread:.3%} > {self._max_spread_ratio:.3%}", logger.info) return False else: diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 9383e5d06..8a7eeeca8 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional import arrow import numpy as np -from cachetools.ttl import TTLCache +from cachetools import TTLCache from pandas import DataFrame from freqtrade.exceptions import OperationalException @@ -90,7 +90,7 @@ class VolatilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache diff --git a/freqtrade/plugins/pairlist/VolumePairList.py b/freqtrade/plugins/pairlist/VolumePairList.py index 0ffc8a8c8..5d78422bb 100644 --- a/freqtrade/plugins/pairlist/VolumePairList.py +++ b/freqtrade/plugins/pairlist/VolumePairList.py @@ -4,11 +4,10 @@ Volume PairList provider Provides dynamic pair list based on trade volumes """ import logging -from functools import partial from typing import Any, Dict, List import arrow -from cachetools.ttl import TTLCache +from cachetools import TTLCache from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes @@ -120,10 +119,17 @@ class VolumePairList(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], + pairs_only=True, active_only=True).keys()] + # No point in testing for blacklisted pairs... + _pairlist = self.verify_blacklist(_pairlist, logger.info) + filtered_tickers = [ v for k, v in tickers.items() if (self._exchange.get_pair_quote_currency(k) == self._stake_currency - and (self._use_range or v[self._sort_key] is not None))] + and (self._use_range or v[self._sort_key] is not None) + and v['symbol'] in _pairlist)] pairlist = [s['symbol'] for s in filtered_tickers] pairlist = self.filter_pairlist(pairlist, tickers) @@ -178,12 +184,16 @@ class VolumePairList(IPairList): ] if (p['symbol'], self._lookback_timeframe) in candles else None # in case of candle data calculate typical price and quoteVolume for candle if pair_candles is not None and not pair_candles.empty: - pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low'] - + pair_candles['close']) / 3 - pair_candles['quoteVolume'] = ( - pair_candles['volume'] * pair_candles['typical_price'] - ) + if self._exchange._ft_has["ohlcv_volume_currency"] == "base": + pair_candles['typical_price'] = (pair_candles['high'] + pair_candles['low'] + + pair_candles['close']) / 3 + pair_candles['quoteVolume'] = ( + pair_candles['volume'] * pair_candles['typical_price'] + ) + else: + # Exchange ohlcv data is in quote volume already. + pair_candles['quoteVolume'] = pair_candles['volume'] # ensure that a rolling sum over the lookback_period is built # if pair_candles contains more candles than lookback_period quoteVolume = (pair_candles['quoteVolume'] @@ -204,7 +214,7 @@ class VolumePairList(IPairList): # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) - pairs = self.verify_blacklist(pairs, partial(self.log_once, logmethod=logger.info)) + pairs = self.verify_blacklist(pairs, logmethod=logger.info) # Limit pairlist to the requested number of pairs pairs = pairs[:self._number_pairs] diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 3e5a002ff..e17ec2dab 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -6,7 +6,7 @@ from copy import deepcopy from typing import Any, Dict, List, Optional import arrow -from cachetools.ttl import TTLCache +from cachetools import TTLCache from pandas import DataFrame from freqtrade.exceptions import OperationalException @@ -88,7 +88,7 @@ class RangeStabilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py index face79729..1e79dc743 100644 --- a/freqtrade/plugins/pairlistmanager.py +++ b/freqtrade/plugins/pairlistmanager.py @@ -2,13 +2,14 @@ PairList manager class """ import logging -from copy import deepcopy +from functools import partial from typing import Dict, List from cachetools import TTLCache, cached from freqtrade.constants import ListPairsWithTimeframes from freqtrade.exceptions import OperationalException +from freqtrade.mixins import LoggingMixin from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.resolvers import PairListResolver @@ -17,7 +18,7 @@ from freqtrade.resolvers import PairListResolver logger = logging.getLogger(__name__) -class PairListManager(): +class PairListManager(LoggingMixin): def __init__(self, exchange, config: dict) -> None: self._exchange = exchange @@ -41,6 +42,9 @@ class PairListManager(): if not self._pairlist_handlers: raise OperationalException("No Pairlist Handlers defined") + refresh_period = config.get('pairlist_refresh_period', 3600) + LoggingMixin.__init__(self, logger, refresh_period) + @property def whitelist(self) -> List[str]: """The current whitelist""" @@ -108,9 +112,10 @@ class PairListManager(): except ValueError as err: logger.error(f"Pair blacklist contains an invalid Wildcard: {err}") return [] - for pair in deepcopy(pairlist): + log_once = partial(self.log_once, logmethod=logmethod) + for pair in pairlist.copy(): if pair in blacklist: - logmethod(f"Pair {pair} in your blacklist. Removing it from whitelist...") + log_once(f"Pair {pair} in your blacklist. Removing it from whitelist...") pairlist.remove(pair) return pairlist diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index 67e204039..c5d390f52 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -55,7 +55,8 @@ class MaxDrawdown(IProtection): # Drawdown is always positive try: - drawdown, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit') + # TODO: This should use absolute profit calculation, considering account balance. + drawdown, _, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit') except ValueError: return False, None, None diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index a7b95a3c5..e9fcc3496 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -96,7 +96,9 @@ class StrategyResolver(IResolver): ("ignore_roi_if_buy_signal", False), ("sell_profit_offset", 0.0), ("disable_dataframe_checks", False), - ("ignore_buying_expired_candle_after", 0) + ("ignore_buying_expired_candle_after", 0), + ("position_adjustment_enable", False), + ("max_entry_position_adjustment", -1), ] for attribute, default in attributes: StrategyResolver._override_attribute_helper(strategy, config, diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index edbc39772..757ed8aac 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -8,7 +8,7 @@ from freqtrade.configuration.config_validation import validate_config_consistenc from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException from freqtrade.rpc.api_server.api_schemas import BacktestRequest, BacktestResponse -from freqtrade.rpc.api_server.deps import get_config +from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode from freqtrade.rpc.api_server.webserver import ApiServer from freqtrade.rpc.rpc import RPCException @@ -20,8 +20,9 @@ router = APIRouter() @router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) +# flake8: noqa: C901 async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks, - config=Depends(get_config)): + config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): """Start backtesting if not done so already""" if ApiServer._bgtask_running: raise RPCException('Bot Background task already running') @@ -32,11 +33,19 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac for setting in settings.keys(): if settings[setting] is not None: btconfig[setting] = settings[setting] + try: + btconfig['stake_amount'] = float(btconfig['stake_amount']) + except ValueError: + pass + + # Force dry-run for backtesting + btconfig['dry_run'] = True # Start backtesting # Initialize backtesting object def run_backtest(): - from freqtrade.optimize.optimize_reports import generate_backtest_stats + from freqtrade.optimize.optimize_reports import (generate_backtest_stats, + store_backtest_stats) from freqtrade.resolvers import StrategyResolver asyncio.set_event_loop(asyncio.new_event_loop()) try: @@ -53,8 +62,7 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac ): from freqtrade.optimize.backtesting import Backtesting ApiServer._bt = Backtesting(btconfig) - if ApiServer._bt.timeframe_detail: - ApiServer._bt.load_bt_data_detail() + ApiServer._bt.load_bt_data_detail() else: ApiServer._bt.config = btconfig ApiServer._bt.init_backtest() @@ -73,13 +81,25 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac lastconfig['enable_protections'] = btconfig.get('enable_protections') lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet') - ApiServer._bt.abort = False - min_date, max_date = ApiServer._bt.backtest_one_strategy( - strat, ApiServer._bt_data, ApiServer._bt_timerange) + ApiServer._bt.results = {} + ApiServer._bt.load_prior_backtest() + + ApiServer._bt.abort = False + if (ApiServer._bt.results and + strat.get_strategy_name() in ApiServer._bt.results['strategy']): + # When previous result hash matches - reuse that result and skip backtesting. + logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}') + else: + min_date, max_date = ApiServer._bt.backtest_one_strategy( + strat, ApiServer._bt_data, ApiServer._bt_timerange) + + ApiServer._bt.results = generate_backtest_stats( + ApiServer._bt_data, ApiServer._bt.all_results, + min_date=min_date, max_date=max_date) + + if btconfig.get('export', 'none') == 'trades': + store_backtest_stats(btconfig['exportfilename'], ApiServer._bt.results) - ApiServer._bt.results = generate_backtest_stats( - ApiServer._bt_data, ApiServer._bt.all_results, - min_date=min_date, max_date=max_date) logger.info("Backtest finished.") except DependencyException as e: @@ -101,7 +121,7 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac @router.get('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_get_backtest(): +def api_get_backtest(ws_mode=Depends(is_webserver_mode)): """ Get backtesting result. Returns Result after backtesting has been ran. @@ -137,7 +157,7 @@ def api_get_backtest(): @router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_delete_backtest(): +def api_delete_backtest(ws_mode=Depends(is_webserver_mode)): """Reset backtesting""" if ApiServer._bgtask_running: return { @@ -163,7 +183,7 @@ def api_delete_backtest(): @router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_abort(): +def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): if not ApiServer._bgtask_running: return { "status": "not_running", diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index c9ff0ddaf..32c7e9214 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel from freqtrade.constants import DATETIME_PRINT_FORMAT +from freqtrade.enums import OrderTypeValues class Ping(BaseModel): @@ -108,7 +109,7 @@ class SellReason(BaseModel): class Stats(BaseModel): sell_reasons: Dict[str, SellReason] - durations: Dict[str, Union[str, float]] + durations: Dict[str, Optional[float]] class DailyRecord(BaseModel): @@ -125,28 +126,30 @@ class Daily(BaseModel): class UnfilledTimeout(BaseModel): - buy: int - sell: int - unit: str + buy: Optional[int] + sell: Optional[int] + unit: Optional[str] exit_timeout_count: Optional[int] class OrderTypes(BaseModel): - buy: str - sell: str - emergencysell: Optional[str] - forcesell: Optional[str] - forcebuy: Optional[str] - stoploss: str + buy: OrderTypeValues + sell: OrderTypeValues + emergencysell: Optional[OrderTypeValues] + forcesell: Optional[OrderTypeValues] + forcebuy: Optional[OrderTypeValues] + stoploss: OrderTypeValues stoploss_on_exchange: bool stoploss_on_exchange_interval: Optional[int] class ShowConfig(BaseModel): version: str + strategy_version: Optional[str] + api_version: float dry_run: bool stake_currency: str - stake_amount: Union[float, str] + stake_amount: str available_capital: Optional[float] stake_currency_decimals: int max_open_trades: int @@ -157,7 +160,7 @@ class ShowConfig(BaseModel): trailing_stop_positive_offset: Optional[float] trailing_only_offset_is_reached: Optional[bool] unfilledtimeout: UnfilledTimeout - order_types: OrderTypes + order_types: Optional[OrderTypes] use_custom_stoploss: Optional[bool] timeframe: Optional[str] timeframe_ms: int @@ -170,6 +173,24 @@ class ShowConfig(BaseModel): bot_name: str state: str runmode: str + position_adjustment_enable: bool + max_entry_position_adjustment: int + + +class OrderSchema(BaseModel): + pair: str + order_id: str + status: str + remaining: float + amount: float + safe_price: float + cost: float + filled: float + ft_order_side: str + order_type: str + is_open: bool + order_timestamp: Optional[int] + order_filled_timestamp: Optional[int] class TradeSchema(BaseModel): @@ -219,6 +240,7 @@ class TradeSchema(BaseModel): min_rate: Optional[float] max_rate: Optional[float] open_order_id: Optional[str] + orders: List[OrderSchema] class OpenTradeSchema(TradeSchema): @@ -273,10 +295,14 @@ class Logs(BaseModel): class ForceBuyPayload(BaseModel): pair: str price: Optional[float] + ordertype: Optional[OrderTypeValues] + stakeamount: Optional[float] + entry_tag: Optional[str] class ForceSellPayload(BaseModel): tradeid: str + ordertype: Optional[OrderTypeValues] class BlacklistPayload(BaseModel): @@ -357,7 +383,7 @@ class BacktestRequest(BaseModel): timeframe_detail: Optional[str] timerange: Optional[str] max_open_trades: Optional[int] - stake_amount: Optional[Union[float, str]] + stake_amount: Optional[str] enable_protections: bool dry_run_wallet: Optional[float] @@ -376,3 +402,8 @@ class BacktestResponse(BaseModel): class SysInfo(BaseModel): cpu_pct: List[float] ram_pct: float + + +class Health(BaseModel): + last_process: datetime + last_process_ts: int diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 06230a7db..6379150ee 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -3,7 +3,7 @@ from copy import deepcopy from pathlib import Path from typing import List, Optional -from fastapi import APIRouter, Depends +from fastapi import APIRouter, Depends, Query from fastapi.exceptions import HTTPException from freqtrade import __version__ @@ -14,18 +14,27 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, DeleteLockRequest, DeleteTrade, ForceBuyPayload, - ForceBuyResponse, ForceSellPayload, Locks, Logs, - OpenTradeSchema, PairHistory, PerformanceEntry, - Ping, PlotConfig, Profit, ResultMsg, ShowConfig, - Stats, StatusMsg, StrategyListResponse, - StrategyResponse, SysInfo, Version, - WhitelistResponse) -from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional + ForceBuyResponse, ForceSellPayload, Health, Locks, + Logs, OpenTradeSchema, PairHistory, + PerformanceEntry, Ping, PlotConfig, Profit, + ResultMsg, ShowConfig, Stats, StatusMsg, + StrategyListResponse, StrategyResponse, SysInfo, + Version, WhitelistResponse) +from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional from freqtrade.rpc.rpc import RPCException logger = logging.getLogger(__name__) +# API version +# Pre-1.1, no version was provided +# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen. +# 1.11: forcebuy and forcesell accept ordertype +# 1.12: add blacklist delete endpoint +# 1.13: forcebuy supports stake_amount +# 1.14: Add entry/exit orders to trade response +API_VERSION = 1.14 + # Public API, requires no auth. router_public = APIRouter() # Private API, protected by authentication @@ -115,14 +124,22 @@ def edge(rpc: RPC = Depends(get_rpc)): @router.get('/show_config', response_model=ShowConfig, tags=['info']) def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(get_config)): state = '' + strategy_version = None if rpc: state = rpc._freqtrade.state - return RPC._rpc_show_config(config, state) + strategy_version = rpc._freqtrade.strategy.version() + resp = RPC._rpc_show_config(config, state, strategy_version) + resp['api_version'] = API_VERSION + return resp @router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading']) def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): - trade = rpc._rpc_forcebuy(payload.pair, payload.price) + ordertype = payload.ordertype.value if payload.ordertype else None + stake_amount = payload.stakeamount if payload.stakeamount else None + entry_tag = payload.entry_tag if payload.entry_tag else None + + trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) if trade: return ForceBuyResponse.parse_obj(trade.to_json()) @@ -132,7 +149,8 @@ def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): - return rpc._rpc_forcesell(payload.tradeid) + ordertype = payload.ordertype.value if payload.ordertype else None + return rpc._rpc_forcesell(payload.tradeid, ordertype) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) @@ -145,6 +163,13 @@ def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)): return rpc._rpc_blacklist(payload.blacklist) +@router.delete('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) +def blacklist_delete(pairs_to_delete: List[str] = Query([]), rpc: RPC = Depends(get_rpc)): + """Provide a list of pairs to delete from the blacklist""" + + return rpc._rpc_blacklist_delete(pairs_to_delete) + + @router.get('/whitelist', response_model=WhitelistResponse, tags=['info', 'pairlist']) def whitelist(rpc: RPC = Depends(get_rpc)): return rpc._rpc_whitelist() @@ -191,18 +216,21 @@ def reload_config(rpc: RPC = Depends(get_rpc)): @router.get('/pair_candles', response_model=PairHistory, tags=['candle data']) -def pair_candles(pair: str, timeframe: str, limit: Optional[int], rpc: RPC = Depends(get_rpc)): +def pair_candles( + pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_analysed_dataframe(pair, timeframe, limit) @router.get('/pair_history', response_model=PairHistory, tags=['candle data']) def pair_history(pair: str, timeframe: str, timerange: str, strategy: str, - config=Depends(get_config)): + config=Depends(get_config), exchange=Depends(get_exchange)): + # The initial call to this endpoint can be slow, as it may need to initialize + # the exchange class. config = deepcopy(config) config.update({ 'strategy': strategy, }) - return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange) + return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange, exchange) @router.get('/plot_config', response_model=PlotConfig, tags=['candle data']) @@ -265,3 +293,8 @@ def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Option @router.get('/sysinfo', response_model=SysInfo, tags=['info']) def sysinfo(): return RPC._rpc_sysinfo() + + +@router.get('/health', response_model=Health, tags=['info']) +def health(rpc: RPC = Depends(get_rpc)): + return rpc._health() diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py index 16f9a78c0..f5e61602e 100644 --- a/freqtrade/rpc/api_server/deps.py +++ b/freqtrade/rpc/api_server/deps.py @@ -1,5 +1,8 @@ from typing import Any, Dict, Iterator, Optional +from fastapi import Depends + +from freqtrade.enums import RunMode from freqtrade.persistence import Trade from freqtrade.rpc.rpc import RPC, RPCException @@ -28,3 +31,17 @@ def get_config() -> Dict[str, Any]: def get_api_config() -> Dict[str, Any]: return ApiServer._config['api_server'] + + +def get_exchange(config=Depends(get_config)): + if not ApiServer._exchange: + from freqtrade.resolvers import ExchangeResolver + ApiServer._exchange = ExchangeResolver.load_exchange( + config['exchange']['name'], config) + return ApiServer._exchange + + +def is_webserver_mode(config=Depends(get_config)): + if config['runmode'] != RunMode.WEBSERVER: + raise RPCException('Bot is not in the correct state') + return None diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py index 79af659c7..a79c1a5fc 100644 --- a/freqtrade/rpc/api_server/uvicorn_threaded.py +++ b/freqtrade/rpc/api_server/uvicorn_threaded.py @@ -47,7 +47,7 @@ class UvicornServer(uvicorn.Server): else: asyncio.set_event_loop(uvloop.new_event_loop()) try: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() except RuntimeError: # When running in a thread, we'll not have an eventloop yet. loop = asyncio.new_event_loop() diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 235063191..63812f52f 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -41,6 +41,8 @@ class ApiServer(RPCHandler): _has_rpc: bool = False _bgtask_running: bool = False _config: Dict[str, Any] = {} + # Exchange - only available in webserver mode. + _exchange = None def __new__(cls, *args, **kwargs): """ diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index f4e82261e..70f3647b6 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -7,7 +7,7 @@ import datetime import logging from typing import Dict, List -from cachetools.ttl import TTLCache +from cachetools import TTLCache from pycoingecko import CoinGeckoAPI from requests.exceptions import RequestException @@ -17,6 +17,16 @@ from freqtrade.constants import SUPPORTED_FIAT logger = logging.getLogger(__name__) +# Manually map symbol to ID for some common coins +# with duplicate coingecko entries +coingecko_mapping = { + 'eth': 'ethereum', + 'bnb': 'binancecoin', + 'sol': 'solana', + 'usdt': 'tether', +} + + class CryptoToFiatConverter: """ Main class to initiate Crypto to FIAT. @@ -77,6 +87,10 @@ class CryptoToFiatConverter: else: return None found = [x for x in self._coinlistings if x['symbol'] == crypto_symbol] + + if crypto_symbol in coingecko_mapping.keys(): + found = [x for x in self._coinlistings if x['id'] == coingecko_mapping[crypto_symbol]] + if len(found) == 1: return found[0]['id'] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 28585e4e8..3d4fffbc9 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -10,8 +10,9 @@ from typing import Any, Dict, List, Optional, Tuple, Union import arrow import psutil from dateutil.relativedelta import relativedelta +from dateutil.tz import tzlocal from numpy import NAN, inf, int64, mean -from pandas import DataFrame +from pandas import DataFrame, NaT from freqtrade import __version__ from freqtrade.configuration.timerange import TimeRange @@ -98,7 +99,8 @@ class RPC: self._fiat_converter = CryptoToFiatConverter() @staticmethod - def _rpc_show_config(config, botstate: Union[State, str]) -> Dict[str, Any]: + def _rpc_show_config(config, botstate: Union[State, str], + strategy_version: Optional[str] = None) -> Dict[str, Any]: """ Return a dict of config options. Explicitly does NOT return the full config to avoid leakage of sensitive @@ -106,10 +108,11 @@ class RPC: """ val = { 'version': __version__, + 'strategy_version': strategy_version, 'dry_run': config['dry_run'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), - 'stake_amount': config['stake_amount'], + 'stake_amount': str(config['stake_amount']), 'available_capital': config.get('available_capital'), 'max_open_trades': (config['max_open_trades'] if config['max_open_trades'] != float('inf') else -1), @@ -134,7 +137,12 @@ class RPC: 'ask_strategy': config.get('ask_strategy', {}), 'bid_strategy': config.get('bid_strategy', {}), 'state': str(botstate), - 'runmode': config['runmode'].value + 'runmode': config['runmode'].value, + 'position_adjustment_enable': config.get('position_adjustment_enable', False), + 'max_entry_position_adjustment': ( + config.get('max_entry_position_adjustment', -1) + if config.get('max_entry_position_adjustment') != float('inf') + else -1) } return val @@ -236,19 +244,29 @@ class RPC: profit_str += f" ({fiat_profit:.2f})" fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \ else fiat_profit_sum + fiat_profit - trades_list.append([ + detail_trade = [ trade.id, trade.pair + ('*' if (trade.open_order_id is not None and trade.close_rate_requested is None) else '') - + ('**' if (trade.close_rate_requested is not None) else ''), + + ('**' if (trade.close_rate_requested is not None) else ''), shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), profit_str - ]) + ] + if self._config.get('position_adjustment_enable', False): + max_buy_str = '' + if self._config.get('max_entry_position_adjustment', -1) > 0: + max_buy_str = f"/{self._config['max_entry_position_adjustment'] + 1}" + filled_buys = trade.nr_of_successful_buys + detail_trade.append(f"{filled_buys}{max_buy_str}") + trades_list.append(detail_trade) profitcol = "Profit" if self._fiat_converter: profitcol += " (" + fiat_display_currency + ")" - columns = ['ID', 'Pair', 'Since', profitcol] + if self._config.get('position_adjustment_enable', False): + columns = ['ID', 'Pair', 'Since', profitcol, '# Entries'] + else: + columns = ['ID', 'Pair', 'Since', profitcol] return trades_list, columns, fiat_profit_sum def _rpc_daily_profit( @@ -422,9 +440,9 @@ class RPC: trade_dur = (trade.close_date - trade.open_date).total_seconds() dur[trade_win_loss(trade)].append(trade_dur) - wins_dur = sum(dur['wins']) / len(dur['wins']) if len(dur['wins']) > 0 else 'N/A' - draws_dur = sum(dur['draws']) / len(dur['draws']) if len(dur['draws']) > 0 else 'N/A' - losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else 'N/A' + wins_dur = sum(dur['wins']) / len(dur['wins']) if len(dur['wins']) > 0 else None + draws_dur = sum(dur['draws']) / len(dur['draws']) if len(dur['draws']) > 0 else None + losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else None durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur} return {'sell_reasons': sell_reasons, 'durations': durations} @@ -564,7 +582,7 @@ class RPC: else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - rate = tickers.get(pair, {}).get('bid', None) + rate = tickers.get(pair, {}).get('last', None) if rate: if pair.startswith(stake_currency) and not pair.endswith(stake_currency): rate = 1.0 / rate @@ -581,15 +599,11 @@ class RPC: 'est_stake': est_stake or 0, 'stake': stake_currency, }) - if total == 0.0: - if self._freqtrade.config['dry_run']: - raise RPCException('Running in Dry Run, balances are not available.') - else: - raise RPCException('All balances are zero.') value = self._fiat_converter.convert_amount( total, stake_currency, fiat_display_currency) if self._fiat_converter else 0 + trade_count = len(Trade.get_trades_proxy()) starting_capital_ratio = 0.0 starting_capital_ratio = (total / starting_capital) - 1 if starting_capital else 0.0 starting_cap_fiat_ratio = (value / starting_cap_fiat) - 1 if starting_cap_fiat else 0.0 @@ -606,6 +620,7 @@ class RPC: 'starting_capital_fiat': starting_cap_fiat, 'starting_capital_fiat_ratio': starting_cap_fiat_ratio, 'starting_capital_fiat_pct': round(starting_cap_fiat_ratio * 100, 2), + 'trade_count': trade_count, 'note': 'Simulated balances' if self._freqtrade.config['dry_run'] else '' } @@ -640,7 +655,7 @@ class RPC: return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} - def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]: + def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: """ Handler for forcesell . Sells the given trade at current price @@ -664,7 +679,11 @@ class RPC: current_rate = self._freqtrade.exchange.get_rate( trade.pair, refresh=False, side="sell") sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) - self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason) + order_type = ordertype or self._freqtrade.strategy.order_types.get( + "forcesell", self._freqtrade.strategy.order_types["sell"]) + + self._freqtrade.execute_trade_exit( + trade, current_rate, sell_reason, ordertype=order_type) # ---- EOF def _exec_forcesell ---- if self._freqtrade.state != State.RUNNING: @@ -692,7 +711,9 @@ class RPC: self._freqtrade.wallets.update() return {'result': f'Created sell order for trade {trade_id}.'} - def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]: + def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, + stake_amount: Optional[float] = None, + buy_tag: Optional[str] = None) -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price @@ -714,13 +735,19 @@ class RPC: # check if pair already has an open pair trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() if trade: - raise RPCException(f'position for {pair} already open - id: {trade.id}') + if not self._freqtrade.strategy.position_adjustment_enable: + raise RPCException(f'position for {pair} already open - id: {trade.id}') - # gen stake amount - stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) + if not stake_amount: + # gen stake amount + stake_amount = self._freqtrade.wallets.get_trade_stake_amount(pair) # execute buy - if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True): + if not order_type: + order_type = self._freqtrade.strategy.order_types.get( + 'forcebuy', self._freqtrade.strategy.order_types['buy']) + if self._freqtrade.execute_entry(pair, stake_amount, price, + ordertype=order_type, trade=trade, buy_tag=buy_tag): Trade.commit() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() return trade @@ -850,6 +877,20 @@ class RPC: } return res + def _rpc_blacklist_delete(self, delete: List[str]) -> Dict: + """ Removes pairs from currently active blacklist """ + errors = {} + for pair in delete: + if pair in self._freqtrade.pairlists.blacklist: + self._freqtrade.pairlists.blacklist.remove(pair) + else: + errors[pair] = { + 'error_msg': f"Pair {pair} is not in the current blacklist." + } + resp = self._rpc_blacklist() + resp['errors'] = errors + return resp + def _rpc_blacklist(self, add: List[str] = None) -> Dict: """ Returns the currently active blacklist""" errors = {} @@ -918,8 +959,16 @@ class RPC: sell_mask = (dataframe['sell'] == 1) sell_signals = int(sell_mask.sum()) dataframe.loc[sell_mask, '_sell_signal_close'] = dataframe.loc[sell_mask, 'close'] - dataframe = dataframe.replace([inf, -inf], NAN) - dataframe = dataframe.replace({NAN: None}) + + # band-aid until this is fixed: + # https://github.com/pandas-dev/pandas/issues/45836 + datetime_types = ['datetime', 'datetime64', 'datetime64[ns, UTC]'] + date_columns = dataframe.select_dtypes(include=datetime_types) + for date_column in date_columns: + # replace NaT with `None` + dataframe[date_column] = dataframe[date_column].astype(object).replace({NaT: None}) + + dataframe = dataframe.replace({inf: None, -inf: None, NAN: None}) res = { 'pair': pair, @@ -960,7 +1009,7 @@ class RPC: @staticmethod def _rpc_analysed_history_full(config, pair: str, timeframe: str, - timerange: str) -> Dict[str, Any]: + timerange: str, exchange) -> Dict[str, Any]: timerange_parsed = TimeRange.parse_timerange(timerange) _data = load_data( @@ -975,7 +1024,7 @@ class RPC: from freqtrade.data.dataprovider import DataProvider from freqtrade.resolvers.strategy_resolver import StrategyResolver strategy = StrategyResolver.load_strategy(config) - strategy.dp = DataProvider(config, exchange=None, pairlists=None) + strategy.dp = DataProvider(config, exchange=exchange, pairlists=None) df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair}) @@ -994,3 +1043,11 @@ class RPC: "cpu_pct": psutil.cpu_percent(interval=1, percpu=True), "ram_pct": psutil.virtual_memory().percent } + + def _health(self) -> Dict[str, Union[str, int]]: + last_p = self._freqtrade.last_process + return { + 'last_process': str(last_p), + 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), + 'last_process_ts': int(last_p.timestamp()), + } diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 8085ece94..d97d1df5f 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -60,6 +60,10 @@ class RPCManager: } """ logger.info('Sending rpc message: %s', msg) + if 'pair' in msg: + msg.update({ + 'base_currency': self._rpc._freqtrade.exchange.get_pair_base_currency(msg['pair']) + }) for mod in self.registered_modules: logger.debug('Forwarding message to rpc.%s', mod.name) try: @@ -81,12 +85,14 @@ class RPCManager: timeframe = config['timeframe'] exchange_name = config['exchange']['name'] strategy_name = config.get('strategy', '') + pos_adjust_enabled = 'On' if config['position_adjustment_enable'] else 'Off' self.send_msg({ 'type': RPCMessageType.STARTUP, 'status': f'*Exchange:* `{exchange_name}`\n' f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' f'*Minimum ROI:* `{minimal_roi}`\n' f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' + f'*Position adjustment:* `{pos_adjust_enabled}`\n' f'*Timeframe:* `{timeframe}`\n' f'*Strategy:* `{strategy_name}`' }) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0e1a6fe27..5a20520dd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -111,8 +111,9 @@ class Telegram(RPCHandler): r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+', r'/stats$', r'/count$', r'/locks$', r'/balance$', r'/stopbuy$', r'/reload_config$', r'/show_config$', - r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$', - r'/forcebuy$', r'/help$', r'/version$'] + r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', + r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', + r'/forcebuy$', r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -169,8 +170,10 @@ class Telegram(RPCHandler): CommandHandler('stopbuy', self._stopbuy), CommandHandler('whitelist', self._whitelist), CommandHandler('blacklist', self._blacklist), + CommandHandler(['blacklist_delete', 'bl_delete'], self._blacklist_delete), CommandHandler('logs', self._logs), CommandHandler('edge', self._edge), + CommandHandler('health', self._health), CommandHandler('help', self._help), CommandHandler('version', self._version), ] @@ -197,8 +200,8 @@ class Telegram(RPCHandler): self._updater.start_polling( bootstrap_retries=-1, - timeout=30, - read_latency=60, + timeout=20, + read_latency=60, # Assumed transmission latency drop_pending_updates=True, ) logger.info( @@ -211,6 +214,7 @@ class Telegram(RPCHandler): Stops all running telegram threads. :return: None """ + # This can take up to `timeout` from the call to `start_polling`. self._updater.stop() def _format_buy_msg(self, msg: Dict[str, Any]) -> str: @@ -274,11 +278,11 @@ class Telegram(RPCHandler): f"*Buy Tag:* `{msg['buy_tag']}`\n" f"*Sell Reason:* `{msg['sell_reason']}`\n" f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n" - f"*Amount:* `{msg['amount']:.8f}`\n") + f"*Amount:* `{msg['amount']:.8f}`\n" + f"*Open Rate:* `{msg['open_rate']:.8f}`\n") if msg['type'] == RPCMessageType.SELL: - message += (f"*Open Rate:* `{msg['open_rate']:.8f}`\n" - f"*Current Rate:* `{msg['current_rate']:.8f}`\n" + message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n" f"*Close Rate:* `{msg['limit']:.8f}`") elif msg['type'] == RPCMessageType.SELL_FILL: @@ -366,6 +370,54 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" + def _prepare_entry_details(self, filled_orders: List, base_currency: str, is_open: bool): + """ + Prepare details of trade with entry adjustment enabled + """ + lines: List[str] = [] + if len(filled_orders) > 0: + first_avg = filled_orders[0]["safe_price"] + + for x, order in enumerate(filled_orders): + if order['ft_order_side'] != 'buy': + continue + cur_entry_datetime = arrow.get(order["order_filled_date"]) + cur_entry_amount = order["amount"] + cur_entry_average = order["safe_price"] + lines.append(" ") + if x == 0: + lines.append(f"*Entry #{x+1}:*") + lines.append( + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + lines.append(f"*Average Entry Price:* {cur_entry_average}") + else: + sumA = 0 + sumB = 0 + for y in range(x): + sumA += (filled_orders[y]["amount"] * filled_orders[y]["safe_price"]) + sumB += filled_orders[y]["amount"] + prev_avg_price = sumA / sumB + price_to_1st_entry = ((cur_entry_average - first_avg) / first_avg) + minus_on_entry = 0 + if prev_avg_price: + minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price + + dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"]) + days = dur_entry.days + hours, remainder = divmod(dur_entry.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + lines.append(f"*Entry #{x+1}:* at {minus_on_entry:.2%} avg profit") + if is_open: + lines.append("({})".format(cur_entry_datetime + .humanize(granularity=["day", "hour", "minute"]))) + lines.append( + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + lines.append(f"*Average Entry Price:* {cur_entry_average} " + f"({price_to_1st_entry:.2%} from 1st entry rate)") + lines.append(f"*Order filled at:* {order['order_filled_date']}") + lines.append(f"({days}d {hours}h {minutes}m {seconds}s from previous entry)") + return lines + @authorized_only def _status(self, update: Update, context: CallbackContext) -> None: """ @@ -389,37 +441,57 @@ class Telegram(RPCHandler): trade_ids = [int(i) for i in context.args if i.isnumeric()] results = self._rpc._rpc_trade_status(trade_ids=trade_ids) - + position_adjust = self._config.get('position_adjustment_enable', False) + max_entries = self._config.get('max_entry_position_adjustment', -1) messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() + r['num_entries'] = len([o for o in r['orders'] if o['ft_order_side'] == 'buy']) + r['sell_reason'] = r.get('sell_reason', "") lines = [ - "*Trade ID:* `{trade_id}` `(since {open_date_hum})`", + "*Trade ID:* `{trade_id}`" + + ("` (since {open_date_hum})`" if r['is_open'] else ""), "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", - "*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "", + "*Entry Tag:* `{buy_tag}`" if r['buy_tag'] else "", + "*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "", + ] + + if position_adjust: + max_buy_str = (f"/{max_entries + 1}" if (max_entries > 0) else "") + lines.append("*Number of Entries:* `{num_entries}`" + max_buy_str) + + lines.extend([ "*Open Rate:* `{open_rate:.8f}`", - "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", - "*Current Rate:* `{current_rate:.8f}`", + "*Close Rate:* `{close_rate:.8f}`" if r['close_rate'] else "", + "*Open Date:* `{open_date}`", + "*Close Date:* `{close_date}`" if r['close_date'] else "", + "*Current Rate:* `{current_rate:.8f}`" if r['is_open'] else "", ("*Current Profit:* " if r['is_open'] else "*Close Profit: *") + "`{profit_ratio:.2%}`", - ] - if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] - and r['initial_stop_loss_ratio'] is not None): - # Adding initial stoploss only if it is different from stoploss - lines.append("*Initial Stoploss:* `{initial_stop_loss_abs:.8f}` " - "`({initial_stop_loss_ratio:.2%})`") + ]) - # Adding stoploss and stoploss percentage only if it is not None - lines.append("*Stoploss:* `{stop_loss_abs:.8f}` " + - ("`({stop_loss_ratio:.2%})`" if r['stop_loss_ratio'] else "")) - lines.append("*Stoploss distance:* `{stoploss_current_dist:.8f}` " - "`({stoploss_current_dist_ratio:.2%})`") - if r['open_order']: - if r['sell_order_status']: - lines.append("*Open Order:* `{open_order}` - `{sell_order_status}`") - else: - lines.append("*Open Order:* `{open_order}`") + if r['is_open']: + if (r['stop_loss_abs'] != r['initial_stop_loss_abs'] + and r['initial_stop_loss_ratio'] is not None): + # Adding initial stoploss only if it is different from stoploss + lines.append("*Initial Stoploss:* `{initial_stop_loss_abs:.8f}` " + "`({initial_stop_loss_ratio:.2%})`") + + # Adding stoploss and stoploss percentage only if it is not None + lines.append("*Stoploss:* `{stop_loss_abs:.8f}` " + + ("`({stop_loss_ratio:.2%})`" if r['stop_loss_ratio'] else "")) + lines.append("*Stoploss distance:* `{stoploss_current_dist:.8f}` " + "`({stoploss_current_dist_ratio:.2%})`") + if r['open_order']: + if r['sell_order_status']: + lines.append("*Open Order:* `{open_order}` - `{sell_order_status}`") + else: + lines.append("*Open Order:* `{open_order}`") + + lines_detail = self._prepare_entry_details( + r['orders'], r['base_currency'], r['is_open']) + lines.extend(lines_detail if lines_detail else "") # Filter empty lines using list-comprehension messages.append("\n".join([line for line in lines if line]).format(**r)) @@ -700,9 +772,9 @@ class Telegram(RPCHandler): duration_msg = tabulate( [ ['Wins', str(timedelta(seconds=durations['wins'])) - if durations['wins'] != 'N/A' else 'N/A'], + if durations['wins'] is not None else 'N/A'], ['Losses', str(timedelta(seconds=durations['losses'])) - if durations['losses'] != 'N/A' else 'N/A'] + if durations['losses'] is not None else 'N/A'] ], headers=['', 'Avg. Duration'] ) @@ -724,12 +796,13 @@ class Telegram(RPCHandler): output = '' if self._config['dry_run']: output += "*Warning:* Simulated balances in Dry Mode.\n" - - output += ("Starting capital: " - f"`{result['starting_capital']}` {self._config['stake_currency']}" - ) - output += (f" `{result['starting_capital_fiat']}` " - f"{self._config['fiat_display_currency']}.\n" + starting_cap = round_coin_value( + result['starting_capital'], self._config['stake_currency']) + output += f"Starting capital: `{starting_cap}`" + starting_cap_fiat = round_coin_value( + result['starting_capital_fiat'], self._config['fiat_display_currency'] + ) if result['starting_capital_fiat'] > 0 else '' + output += (f" `, {starting_cap_fiat}`.\n" ) if result['starting_capital_fiat'] > 0 else '.\n' total_dust_balance = 0 @@ -762,14 +835,17 @@ class Telegram(RPCHandler): f"(< {balance_dust_level} {result['stake']}):*\n" f"\t`Est. {result['stake']}: " f"{round_coin_value(total_dust_balance, result['stake'], False)}`\n") + tc = result['trade_count'] > 0 + stake_improve = f" `({result['starting_capital_ratio']:.2%})`" if tc else '' + fiat_val = f" `({result['starting_capital_fiat_ratio']:.2%})`" if tc else '' output += ("\n*Estimated Value*:\n" f"\t`{result['stake']}: " f"{round_coin_value(result['total'], result['stake'], False)}`" - f" `({result['starting_capital_ratio']:.2%})`\n" + f"{stake_improve}\n" f"\t`{result['symbol']}: " f"{round_coin_value(result['value'], result['symbol'], False)}`" - f" `({result['starting_capital_fiat_ratio']:.2%})`\n") + f"{fiat_val}\n") self._send_msg(output, reload_able=True, callback_path="update_balance", query=update.callback_query) except RPCException as e: @@ -845,10 +921,11 @@ class Telegram(RPCHandler): self._send_msg(str(e)) def _forcebuy_action(self, pair, price=None): - try: - self._rpc._rpc_forcebuy(pair, price) - except RPCException as e: - self._send_msg(str(e)) + if pair != 'cancel': + try: + self._rpc._rpc_forcebuy(pair, price) + except RPCException as e: + self._send_msg(str(e)) def _forcebuy_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: @@ -878,10 +955,13 @@ class Telegram(RPCHandler): self._forcebuy_action(pair, price) else: whitelist = self._rpc._rpc_whitelist()['whitelist'] - pairs = [InlineKeyboardButton(text=pair, callback_data=pair) for pair in whitelist] + pair_buttons = [ + InlineKeyboardButton(text=pair, callback_data=pair) for pair in sorted(whitelist)] + buttons_aligned = self._layout_inline_keyboard(pair_buttons) + buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=self._layout_inline_keyboard(pairs)) + keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: @@ -1161,22 +1241,28 @@ class Telegram(RPCHandler): Handler for /blacklist Shows the currently active blacklist """ - try: + self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args)) - blacklist = self._rpc._rpc_blacklist(context.args) - errmsgs = [] - for pair, error in blacklist['errors'].items(): - errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") - if errmsgs: - self._send_msg('\n'.join(errmsgs)) + def send_blacklist_msg(self, blacklist: Dict): + errmsgs = [] + for pair, error in blacklist['errors'].items(): + errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") + if errmsgs: + self._send_msg('\n'.join(errmsgs)) - message = f"Blacklist contains {blacklist['length']} pairs\n" - message += f"`{', '.join(blacklist['blacklist'])}`" + message = f"Blacklist contains {blacklist['length']} pairs\n" + message += f"`{', '.join(blacklist['blacklist'])}`" - logger.debug(message) - self._send_msg(message) - except RPCException as e: - self._send_msg(str(e)) + logger.debug(message) + self._send_msg(message) + + @authorized_only + def _blacklist_delete(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /bl_delete + Deletes pair(s) from current blacklist + """ + self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or [])) @authorized_only def _logs(self, update: Update, context: CallbackContext) -> None: @@ -1257,6 +1343,8 @@ class Telegram(RPCHandler): "*/whitelist:* `Show current whitelist` \n" "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " "to the blacklist.` \n" + "*/blacklist_delete [pairs]| /bl_delete [pairs]:* " + "`Delete pair / pattern from blacklist. Will reset on reload_conf.` \n" "*/reload_config:* `Reload configuration file` \n" "*/unlock :* `Unlock this Pair (or this lock id if it's numeric)`\n" @@ -1268,6 +1356,7 @@ class Telegram(RPCHandler): "*/logs [limit]:* `Show latest logs - defaults to 10` \n" "*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" + "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" "_Statistics_\n" "------------\n" @@ -1295,6 +1384,19 @@ class Telegram(RPCHandler): self._send_msg(message, parse_mode=ParseMode.MARKDOWN) + @authorized_only + def _health(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /health + Shows the last process timestamp + """ + try: + health = self._rpc._health() + message = f"Last process: `{health['last_process_loc']}`" + self._send_msg(message) + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _version(self, update: Update, context: CallbackContext) -> None: """ @@ -1304,7 +1406,12 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - self._send_msg('*Version:* `{}`'.format(__version__)) + strategy_version = self._rpc._freqtrade.strategy.version() + version_string = f'*Version:* `{__version__}`' + if strategy_version is not None: + version_string += f', *Strategy version: * `{strategy_version}`' + + self._send_msg(version_string) @authorized_only def _show_config(self, update: Update, context: CallbackContext) -> None: @@ -1328,6 +1435,14 @@ class Telegram(RPCHandler): else: sl_info = f"*Stoploss:* `{val['stoploss']}`\n" + if val['position_adjustment_enable']: + pa_info = ( + f"*Position adjustment:* On\n" + f"*Max enter position adjustment:* `{val['max_entry_position_adjustment']}`\n" + ) + else: + pa_info = "*Position adjustment:* Off\n" + self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" @@ -1337,6 +1452,7 @@ class Telegram(RPCHandler): f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n" f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" f"{sl_info}" + f"{pa_info}" f"*Timeframe:* `{val['timeframe']}`\n" f"*Strategy:* `{val['strategy']}`\n" f"*Current state:* `{val['state']}`" diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py index b4c55649e..58b75769e 100644 --- a/freqtrade/rpc/webhook.py +++ b/freqtrade/rpc/webhook.py @@ -2,6 +2,7 @@ This module manages webhook communication """ import logging +import time from typing import Any, Dict from requests import RequestException, post @@ -28,12 +29,9 @@ class Webhook(RPCHandler): super().__init__(rpc, config) self._url = self._config['webhook']['url'] - self._format = self._config['webhook'].get('format', 'form') - - if self._format != 'form' and self._format != 'json': - raise NotImplementedError('Unknown webhook format `{}`, possible values are ' - '`form` (default) and `json`'.format(self._format)) + self._retries = self._config['webhook'].get('retries', 0) + self._retry_delay = self._config['webhook'].get('retry_delay', 0.1) def cleanup(self) -> None: """ @@ -77,13 +75,30 @@ class Webhook(RPCHandler): def _send_msg(self, payload: dict) -> None: """do the actual call to the webhook""" - try: - if self._format == 'form': - post(self._url, data=payload) - elif self._format == 'json': - post(self._url, json=payload) - else: - raise NotImplementedError('Unknown format: {}'.format(self._format)) + success = False + attempts = 0 + while not success and attempts <= self._retries: + if attempts: + if self._retry_delay: + time.sleep(self._retry_delay) + logger.info("Retrying webhook...") - except RequestException as exc: - logger.warning("Could not call webhook url. Exception: %s", exc) + attempts += 1 + + try: + if self._format == 'form': + response = post(self._url, data=payload) + elif self._format == 'json': + response = post(self._url, json=payload) + elif self._format == 'raw': + response = post(self._url, data=payload['data'], + headers={'Content-Type': 'text/plain'}) + else: + raise NotImplementedError('Unknown format: {}'.format(self._format)) + + # Throw a RequestException if the post was not successful + response.raise_for_status() + success = True + + except RequestException as exc: + logger.warning("Could not call webhook url. Exception: %s", exc) diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py index 4c5f21108..722e7a128 100644 --- a/freqtrade/strategy/informative_decorator.py +++ b/freqtrade/strategy/informative_decorator.py @@ -80,12 +80,11 @@ def _create_and_merge_informative_pair(strategy, dataframe: DataFrame, metadata: # Not specifying an asset will define informative dataframe for current pair. asset = metadata['pair'] - if '/' in asset: - base, quote = asset.split('/') - else: - # When futures are supported this may need reevaluation. - # base, quote = asset, '' - raise OperationalException('Not implemented.') + market = strategy.dp.market(asset) + if market is None: + raise OperationalException(f'Market {asset} is not available.') + base = market['base'] + quote = market['quote'] # Default format. This optimizes for the common case: informative pairs using same stake # currency. When quote currency matches stake currency, column name will omit base currency. diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d4b496ed0..2f3657059 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -18,6 +18,7 @@ from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange.exchange import timeframe_to_next_date from freqtrade.persistence import PairLocks, Trade +from freqtrade.persistence.models import LocalTrade, Order from freqtrade.strategy.hyper import HyperStrategyMixin from freqtrade.strategy.informative_decorator import (InformativeData, PopulateIndicators, _create_and_merge_informative_pair, @@ -106,6 +107,10 @@ class IStrategy(ABC, HyperStrategyMixin): sell_profit_offset: float ignore_roi_if_buy_signal: bool + # Position adjustment is disabled by default + position_adjustment_enable: bool = False + max_entry_position_adjustment: int = -1 + # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 @@ -185,7 +190,17 @@ class IStrategy(ABC, HyperStrategyMixin): """ return dataframe - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def bot_loop_start(self, **kwargs) -> None: + """ + Called at the start of the bot iteration (one loop). + Might be used to perform pair-independent tasks + (e.g. gather some remote resource for comparison) + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + + def check_buy_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: """ Check buy timeout function callback. This method can be used to override the buy-timeout. @@ -198,12 +213,14 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair the trade is for :param trade: trade object. :param order: Order dictionary as returned from CCXT. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is cancelled. """ return False - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: + def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + current_time: datetime, **kwargs) -> bool: """ Check sell timeout function callback. This method can be used to override the sell-timeout. @@ -216,22 +233,15 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair the trade is for :param trade: trade object. :param order: Order dictionary as returned from CCXT. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the sell-order is cancelled. """ return False - def bot_loop_start(self, **kwargs) -> None: - """ - Called at the start of the bot iteration (one loop). - Might be used to perform pair-independent tasks - (e.g. gather some remote resource for comparison) - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - """ - pass - def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time: datetime, **kwargs) -> bool: + time_in_force: str, current_time: datetime, entry_tag: Optional[str], + **kwargs) -> bool: """ Called right before placing a buy order. Timing for this function is critical, so avoid doing heavy computations or @@ -247,6 +257,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -304,7 +315,7 @@ class IStrategy(ABC, HyperStrategyMixin): return self.stoploss def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, - **kwargs) -> float: + entry_tag: Optional[str], **kwargs) -> float: """ Custom entry price logic, returning the new entry price. @@ -315,6 +326,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param pair: Pair that's currently analyzed :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New entry price value if provided """ @@ -366,7 +378,7 @@ class IStrategy(ABC, HyperStrategyMixin): def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - **kwargs) -> float: + entry_tag: Optional[str], **kwargs) -> float: """ Customize stake size for each new trade. This method is not called when edge module is enabled. @@ -377,10 +389,34 @@ class IStrategy(ABC, HyperStrategyMixin): :param proposed_stake: A stake amount proposed by the bot. :param min_stake: Minimal stake size allowed by exchange. :param max_stake: Balance available for trading. + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :return: A stake size, which is between min_stake and max_stake. """ return proposed_stake + def adjust_trade_position(self, trade: Trade, current_time: datetime, + current_rate: float, current_profit: float, min_stake: float, + max_stake: float, **kwargs) -> Optional[float]: + """ + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. + Only called when `position_adjustment_enable` is set to True. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None + + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Current buy rate. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param min_stake: Minimal stake size allowed by exchange. + :param max_stake: Balance available for trading. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade + """ + return None + def informative_pairs(self) -> ListPairsWithTimeframes: """ Define additional, informative pair/interval combinations to be cached from the exchange. @@ -394,6 +430,12 @@ class IStrategy(ABC, HyperStrategyMixin): """ return [] + def version(self) -> Optional[str]: + """ + Returns version of the strategy. + """ + return None + ### # END - Intended to be overridden by strategy ### @@ -623,6 +665,9 @@ class IStrategy(ABC, HyperStrategyMixin): buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None) + # Tags can be None, which does not resolve to False. + buy_tag = buy_tag if isinstance(buy_tag, str) else None + exit_tag = exit_tag if isinstance(exit_tag, str) else None logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) @@ -642,7 +687,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: return False - def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, + def should_sell(self, trade: Trade, rate: float, current_time: datetime, buy: bool, sell: bool, low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: """ @@ -659,7 +704,8 @@ class IStrategy(ABC, HyperStrategyMixin): trade.adjust_min_max_rates(high or current_rate, low or current_rate) stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, - current_time=date, current_profit=current_profit, + current_time=current_time, + current_profit=current_profit, force_stoploss=force_stoploss, low=low, high=high) # Set current rate to high for backtesting sell @@ -669,7 +715,7 @@ class IStrategy(ABC, HyperStrategyMixin): # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. roi_reached = (not (buy and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, - current_time=date)) + current_time=current_time)) sell_signal = SellType.NONE custom_reason = '' @@ -685,8 +731,8 @@ class IStrategy(ABC, HyperStrategyMixin): sell_signal = SellType.SELL_SIGNAL else: custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)( - pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate, - current_profit=current_profit) + pair=trade.pair, trade=trade, current_time=current_time, + current_rate=current_rate, current_profit=current_profit) if custom_reason: sell_signal = SellType.CUSTOM_SELL if isinstance(custom_reason, str): @@ -697,23 +743,21 @@ class IStrategy(ABC, HyperStrategyMixin): custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH] else: custom_reason = None - # TODO: return here if sell-signal should be favored over ROI + if sell_signal in (SellType.CUSTOM_SELL, SellType.SELL_SIGNAL): + logger.debug(f"{trade.pair} - Sell signal received. " + f"sell_type=SellType.{sell_signal.name}" + + (f", custom_reason={custom_reason}" if custom_reason else "")) + return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason) # Start evaluations # Sequence: - # ROI (if not stoploss) # Sell-signal + # ROI (if not stoploss) # Stoploss if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI") return SellCheckTuple(sell_type=SellType.ROI) - if sell_signal != SellType.NONE: - logger.debug(f"{trade.pair} - Sell signal received. " - f"sell_type=SellType.{sell_signal.name}" + - (f", custom_reason={custom_reason}" if custom_reason else "")) - return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason) - if stoplossflag.sell_flag: logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.sell_type}") @@ -820,6 +864,28 @@ class IStrategy(ABC, HyperStrategyMixin): else: return current_profit > roi + def ft_check_timed_out(self, side: str, trade: LocalTrade, order: Order, + current_time: datetime) -> bool: + """ + FT Internal method. + Check if timeout is active, and if the order is still open and timed out + """ + timeout = self.config.get('unfilledtimeout', {}).get(side) + if timeout is not None: + timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes') + timeout_kwargs = {timeout_unit: -timeout} + timeout_threshold = current_time + timedelta(**timeout_kwargs) + timedout = (order.status == 'open' and order.side == side + and order.order_date_utc < timeout_threshold) + if timedout: + return True + time_method = self.check_sell_timeout if order.side == 'sell' else self.check_buy_timeout + + return strategy_safe_wrapper(time_method, + default_retval=False)( + pair=trade.pair, trade=trade, order=order, + current_time=current_time) + def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index e2fa1c63e..c91715b1f 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -15,7 +15,8 @@ "cancel_open_orders_on_exit": false, "unfilledtimeout": { "buy": 10, - "sell": 30, + "sell": 10, + "exit_timeout_count": 0, "unit": "minutes" }, "bid_strategy": { diff --git a/freqtrade/templates/base_strategy.py.j2 b/freqtrade/templates/base_strategy.py.j2 index 06d7cbc5c..035468d58 100644 --- a/freqtrade/templates/base_strategy.py.j2 +++ b/freqtrade/templates/base_strategy.py.j2 @@ -12,6 +12,7 @@ from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalP # -------------------------------- # Add your lib to import here import talib.abstract as ta +import pandas_ta as pta import freqtrade.vendor.qtpylib.indicators as qtpylib @@ -36,6 +37,9 @@ class {{ strategy }}(IStrategy): # Check the documentation or the Sample strategy to get the latest version. INTERFACE_VERSION = 2 + # Optimal timeframe for the strategy. + timeframe = '5m' + # Minimal ROI designed for the strategy. # This attribute will be overridden if the config file contains "minimal_roi". minimal_roi = { @@ -54,9 +58,6 @@ class {{ strategy }}(IStrategy): # trailing_stop_positive = 0.01 # trailing_stop_positive_offset = 0.0 # Disabled / not configured - # Optimal timeframe for the strategy. - timeframe = '5m' - # Run "populate_indicators()" only for new candle. process_only_new_candles = False @@ -68,6 +69,10 @@ class {{ strategy }}(IStrategy): # Number of candles the strategy requires before producing valid signals startup_candle_count: int = 30 + # Strategy parameters + buy_rsi = IntParameter(10, 40, default=30, space="buy") + sell_rsi = IntParameter(60, 90, default=70, space="sell") + # Optional order type mapping. order_types = { 'buy': 'limit', @@ -82,6 +87,7 @@ class {{ strategy }}(IStrategy): 'sell': 'gtc' } {{ plot_config | indent(4) }} + def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 99720ae6e..3b937d1c5 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -79,7 +79,9 @@ "source": [ "# Load strategy using values set above\n", "from freqtrade.resolvers import StrategyResolver\n", + "from freqtrade.data.dataprovider import DataProvider\n", "strategy = StrategyResolver.load_strategy(config)\n", + "strategy.dp = DataProvider(config, None, None)\n", "\n", "# Generate buy/sell signals using strategy\n", "df = strategy.analyze_ticker(candles, {'pair': pair})\n", diff --git a/freqtrade/templates/subtemplates/buy_trend_full.j2 b/freqtrade/templates/subtemplates/buy_trend_full.j2 index 1a0d326b3..aac8325a7 100644 --- a/freqtrade/templates/subtemplates/buy_trend_full.j2 +++ b/freqtrade/templates/subtemplates/buy_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 +(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising diff --git a/freqtrade/templates/subtemplates/buy_trend_minimal.j2 b/freqtrade/templates/subtemplates/buy_trend_minimal.j2 index 6a4079cf3..e89d3779e 100644 --- a/freqtrade/templates/subtemplates/buy_trend_minimal.j2 +++ b/freqtrade/templates/subtemplates/buy_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30 +(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi diff --git a/freqtrade/templates/subtemplates/exchange_huobi.j2 b/freqtrade/templates/subtemplates/exchange_huobi.j2 new file mode 100644 index 000000000..3cb521785 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_huobi.j2 @@ -0,0 +1,12 @@ +"exchange": { + "name": "{{ exchange_name | lower }}", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + ], + "pair_blacklist": [ + "HT/.*" + ] +} diff --git a/freqtrade/templates/subtemplates/plot_config_full.j2 b/freqtrade/templates/subtemplates/plot_config_full.j2 index ab02c7892..e3f9e7ca0 100644 --- a/freqtrade/templates/subtemplates/plot_config_full.j2 +++ b/freqtrade/templates/subtemplates/plot_config_full.j2 @@ -1,18 +1,20 @@ -plot_config = { - # Main plot indicators (Moving averages, ...) - 'main_plot': { - 'tema': {}, - 'sar': {'color': 'white'}, - }, - 'subplots': { - # Subplots - each dict defines one additional plot - "MACD": { - 'macd': {'color': 'blue'}, - 'macdsignal': {'color': 'orange'}, +@property +def plot_config(self): + return { + # Main plot indicators (Moving averages, ...) + 'main_plot': { + 'tema': {}, + 'sar': {'color': 'white'}, }, - "RSI": { - 'rsi': {'color': 'red'}, + 'subplots': { + # Subplots - each dict defines one additional plot + "MACD": { + 'macd': {'color': 'blue'}, + 'macdsignal': {'color': 'orange'}, + }, + "RSI": { + 'rsi': {'color': 'red'}, + } } } -} diff --git a/freqtrade/templates/subtemplates/sell_trend_full.j2 b/freqtrade/templates/subtemplates/sell_trend_full.j2 index 36c08c947..3068d8d57 100644 --- a/freqtrade/templates/subtemplates/sell_trend_full.j2 +++ b/freqtrade/templates/subtemplates/sell_trend_full.j2 @@ -1,3 +1,3 @@ -(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 +(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling diff --git a/freqtrade/templates/subtemplates/sell_trend_minimal.j2 b/freqtrade/templates/subtemplates/sell_trend_minimal.j2 index 42a7b81a2..5dabc5910 100644 --- a/freqtrade/templates/subtemplates/sell_trend_minimal.j2 +++ b/freqtrade/templates/subtemplates/sell_trend_minimal.j2 @@ -1 +1 @@ -(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70 +(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index fb467ecaa..db12094ed 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -12,9 +12,47 @@ def bot_loop_start(self, **kwargs) -> None: """ pass +def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: float, + entry_tag: 'Optional[str]', **kwargs) -> float: + """ + Custom entry price logic, returning the new entry price. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None, orderbook is used to set entry price + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: New entry price value if provided + """ + return proposed_rate + +def custom_exit_price(self, pair: str, trade: 'Trade', + current_time: 'datetime', proposed_rate: float, + current_profit: float, **kwargs) -> float: + """ + Custom exit price logic, returning the new exit price. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None, orderbook is used to set exit price + + :param pair: Pair that's currently analyzed + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param proposed_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: New exit price value if provided + """ + return proposed_rate + def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, - **kwargs) -> float: + entry_tag: 'Optional[str]', **kwargs) -> float: """ Customize stake size for each new trade. This method is not called when edge module is enabled. @@ -25,6 +63,7 @@ def custom_stake_amount(self, pair: str, current_time: 'datetime', current_rate: :param proposed_stake: A stake amount proposed by the bot. :param min_stake: Minimal stake size allowed by exchange. :param max_stake: Balance available for trading. + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :return: A stake size, which is between min_stake and max_stake. """ return proposed_stake @@ -78,7 +117,8 @@ def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', curre return None def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time: 'datetime', **kwargs) -> bool: + time_in_force: str, current_time: 'datetime', entry_tag: 'Optional[str]', + **kwargs) -> bool: """ Called right before placing a buy order. Timing for this function is critical, so avoid doing heavy computations or @@ -94,6 +134,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f :param rate: Rate that's going to be used when using limit orders :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param current_time: datetime object, containing the current datetime + :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the buy-order is placed on the exchange. False aborts the process @@ -167,3 +208,26 @@ def check_sell_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) - :return bool: When True is returned, then the sell-order is cancelled. """ return False + +def adjust_trade_position(self, trade: 'Trade', current_time: 'datetime', + current_rate: float, current_profit: float, min_stake: float, + max_stake: float, **kwargs) -> 'Optional[float]': + """ + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. + Only called when `position_adjustment_enable` is set to True. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None + + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Current buy rate. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param min_stake: Minimal stake size allowed by exchange. + :param max_stake: Balance available for trading. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade + """ + return None diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index d10847099..93f3d3800 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -3,7 +3,7 @@ import logging from copy import deepcopy -from typing import Any, Dict, NamedTuple +from typing import Any, Dict, NamedTuple, Optional import arrow @@ -211,7 +211,7 @@ class Wallets: return stake_amount - def get_trade_stake_amount(self, pair: str, edge=None) -> float: + def get_trade_stake_amount(self, pair: str, edge=None, update: bool = True) -> float: """ Calculate stake amount for the trade :return: float: Stake amount @@ -219,7 +219,8 @@ class Wallets: """ stake_amount: float # Ensure wallets are uptodate. - self.update() + if update: + self.update() val_tied_up = Trade.total_open_trades_stakes() available_amount = self.get_available_stake_amount() @@ -238,14 +239,15 @@ class Wallets: return self._check_available_stake_amount(stake_amount, available_amount) - def validate_stake_amount(self, pair, stake_amount, min_stake_amount): + def validate_stake_amount( + self, pair: str, stake_amount: Optional[float], min_stake_amount: Optional[float]): if not stake_amount: logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.") return 0 max_stake_amount = self.get_available_stake_amount() - if min_stake_amount > max_stake_amount: + if min_stake_amount is not None and min_stake_amount > max_stake_amount: if self._log: logger.warning("Minimum stake amount > available balance.") return 0 @@ -260,8 +262,8 @@ class Wallets: if self._log: logger.info( f"Adjusted stake amount for pair {pair} is more than 30% bigger than " - f"the desired stake ({stake_amount} * 1.3 > {max_stake_amount}), " - f"ignoring trade." + f"the desired stake amount of ({stake_amount:.8f} * 1.3 = " + f"{stake_amount * 1.3:.8f}) < {min_stake_amount}), ignoring trade." ) return 0 stake_amount = min_stake_amount diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 5c0de86ff..66f718af0 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -85,9 +85,12 @@ class Worker: # Log state transition if state != old_state: - self.freqtrade.notify_status(f'{state.name.lower()}') - logger.info(f"Changing state to: {state.name}") + if old_state != State.RELOAD_CONFIG: + self.freqtrade.notify_status(f'{state.name.lower()}') + + logger.info( + f"Changing state{f' from {old_state.name}' if old_state else ''} to: {state.name}") if state == State.RUNNING: self.freqtrade.startup() @@ -113,8 +116,12 @@ class Worker: if self._heartbeat_interval: now = time.time() if (now - self._heartbeat_msg) > self._heartbeat_interval: + version = __version__ + strategy_version = self.freqtrade.strategy.version() + if (strategy_version is not None): + version += ', strategy_version: ' + strategy_version logger.info(f"Bot heartbeat. PID={getpid()}, " - f"version='{__version__}', state='{state.name}'") + f"version='{version}', state='{state.name}'") self._heartbeat_msg = now return state diff --git a/mkdocs.yml b/mkdocs.yml index 0daf462c2..fb1b80ebf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,8 +11,9 @@ nav: - Freqtrade Basics: bot-basics.md - Configuration: configuration.md - Strategy Customization: strategy-customization.md - - Plugins: plugins.md + - Strategy Callbacks: strategy-callbacks.md - Stoploss: stoploss.md + - Plugins: plugins.md - Start the bot: bot-usage.md - Control the bot: - Telegram: telegram-usage.md @@ -80,8 +81,10 @@ markdown_extensions: - pymdownx.snippets: base_path: docs check_paths: true - - pymdownx.tabbed - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true - pymdownx.tasklist: custom_checkbox: true + - pymdownx.tilde - mdx_truly_sane_lists diff --git a/pyproject.toml b/pyproject.toml index f0637d8c6..50f0242a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,10 @@ exclude = ''' line_length = 100 multi_line_output=0 lines_after_imports=2 +skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" [build-system] requires = ["setuptools >= 46.4.0", "wheel"] diff --git a/requirements-dev.txt b/requirements-dev.txt index ab06468b9..c2f3eae8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,25 +5,25 @@ coveralls==3.3.1 flake8==4.0.1 -flake8-tidy-imports==4.5.0 -mypy==0.910 -pytest==6.2.5 -pytest-asyncio==0.16.0 +flake8-tidy-imports==4.6.0 +mypy==0.940 +pytest==7.1.0 +pytest-asyncio==0.18.2 pytest-cov==3.0.0 -pytest-mock==3.6.1 +pytest-mock==3.7.0 pytest-random-order==1.0.4 isort==5.10.1 # For datetime mocking -time-machine==2.4.0 +time-machine==2.6.0 # Convert jupyter notebooks to markdown documents -nbconvert==6.3.0 +nbconvert==6.4.4 # mypy types -types-cachetools==4.2.4 -types-filelock==3.2.1 -types-requests==2.26.0 -types-tabulate==0.8.3 +types-cachetools==5.0.0 +types-filelock==3.2.5 +types-requests==2.27.12 +types-tabulate==0.8.5 # Extensions to datetime library -types-python-dateutil==2.8.2 \ No newline at end of file +types-python-dateutil==2.8.9 \ No newline at end of file diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 7efbb47cd..aeb7be035 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -2,10 +2,9 @@ -r requirements.txt # Required for hyperopt -scipy==1.7.2 -scikit-learn==1.0.1 +scipy==1.8.0 +scikit-learn==1.0.2 scikit-optimize==0.9.0 -filelock==3.3.2 +filelock==3.6.0 joblib==1.1.0 -psutil==5.8.0 -progressbar2==3.55.0 +progressbar2==4.0.0 diff --git a/requirements-plot.txt b/requirements-plot.txt index 8e17232b0..bb2132f87 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.3.1 +plotly==5.6.0 diff --git a/requirements.txt b/requirements.txt index d715b8f52..f0f030e78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,46 +1,46 @@ -numpy==1.21.4 -pandas==1.3.4 +numpy==1.22.3 +pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.61.24 +ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels -cryptography==35.0.0 -aiohttp==3.7.4.post0 -SQLAlchemy==1.4.27 -python-telegram-bot==13.8.1 -arrow==1.2.1 +cryptography==36.0.1 +aiohttp==3.8.1 +SQLAlchemy==1.4.32 +python-telegram-bot==13.11 +arrow==1.2.2 cachetools==4.2.2 -requests==2.26.0 -urllib3==1.26.7 -jsonschema==4.2.1 -TA-Lib==0.4.21 +requests==2.27.1 +urllib3==1.26.8 +jsonschema==4.4.0 +TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 jinja2==3.0.3 -tables==3.6.1 +tables==3.7.0 blosc==1.10.6 # find first, C search in arrays py_find_1st==1.1.5 # Load ticker files 30% faster -python-rapidjson==1.5 +python-rapidjson==1.6 # Notify systemd sdnotify==0.3.2 # API Server -fastapi==0.70.0 -uvicorn==0.15.0 +fastapi==0.75.0 +uvicorn==0.17.6 pyjwt==2.3.0 -aiofiles==0.7.0 -psutil==5.8.0 +aiofiles==0.8.0 +psutil==5.9.0 # Support for colorized terminal output colorama==0.4.4 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.22 +prompt-toolkit==3.0.28 # Extensions to datetime library -python-dateutil==2.8.2 \ No newline at end of file +python-dateutil==2.8.2 diff --git a/setup.cfg b/setup.cfg index b311c94da..6aaec9d73 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,9 +14,9 @@ classifiers = Environment :: Console Intended Audience :: Science/Research License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Operating System :: MacOS Operating System :: Unix Topic :: Office/Business :: Financial :: Investment diff --git a/setup.py b/setup.py index 630bc0f86..a89e717a1 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,6 @@ hyperopt = [ 'filelock', 'joblib', 'progressbar2', - 'psutil', ] develop = [ @@ -43,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.60.11', + 'ccxt>=1.76.5', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', @@ -69,6 +68,7 @@ setup( 'blosc', 'fastapi', 'uvicorn', + 'psutil', 'pyjwt', 'aiofiles' ], diff --git a/setup.sh b/setup.sh index 16ccde0df..ebfabaca5 100755 --- a/setup.sh +++ b/setup.sh @@ -25,7 +25,7 @@ function check_installed_python() { exit 2 fi - for v in 9 8 7 + for v in 9 10 8 do PYTHON="python3.${v}" which $PYTHON @@ -36,7 +36,7 @@ function check_installed_python() { fi done - echo "No usable python found. Please make sure to have python3.7 or newer installed" + echo "No usable python found. Please make sure to have python3.8 or newer installed." exit 1 } @@ -132,6 +132,9 @@ function install_macos() { echo_block "Installing Brew" /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" fi + + brew install gettext + #Gets number after decimal in python version version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g') @@ -205,7 +208,7 @@ function config() { } function install() { - + echo_block "Installing mandatory dependencies" if [ "$(uname -s)" == "Darwin" ]; then @@ -219,7 +222,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.7 - 3.9, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.8 - 3.10, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi @@ -246,7 +249,7 @@ function help() { echo " -p,--plot Install dependencies for Plotting scripts." } -# Verify if 3.7 or 3.8 is installed +# Verify if 3.8+ is installed check_installed_python case $* in diff --git a/tests/conftest.py b/tests/conftest.py index 3ce064ee3..57122c01c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,6 @@ import logging import re from copy import deepcopy from datetime import datetime, timedelta -from functools import reduce from pathlib import Path from unittest.mock import MagicMock, Mock, PropertyMock @@ -20,13 +19,14 @@ from freqtrade.edge import PairInfo from freqtrade.enums import RunMode from freqtrade.exchange import Exchange from freqtrade.freqtradebot import FreqtradeBot -from freqtrade.persistence import LocalTrade, Trade, init_db +from freqtrade.persistence import LocalTrade, Order, Trade, init_db from freqtrade.resolvers import ExchangeResolver from freqtrade.worker import Worker from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, mock_trade_5, mock_trade_6) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, - mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6) + mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6, + mock_trade_usdt_7) logging.getLogger('').setLevel(logging.INFO) @@ -50,17 +50,23 @@ def pytest_configure(config): def log_has(line, logs): - # caplog mocker returns log as a tuple: ('freqtrade.something', logging.WARNING, 'foobar') - # and we want to match line against foobar in the tuple - return reduce(lambda a, b: a or b, - filter(lambda x: x[2] == line, logs.record_tuples), - False) + """Check if line is found on some caplog's message.""" + return any(line == message for message in logs.messages) def log_has_re(line, logs): - return reduce(lambda a, b: a or b, - filter(lambda x: re.match(line, x[2]), logs.record_tuples), - False) + """Check if line matches some caplog's message.""" + return any(re.match(line, message) for message in logs.messages) + + +def num_log_has(line, logs): + """Check how many times line is found in caplog's messages.""" + return sum(line == message for message in logs.messages) + + +def num_log_has_re(line, logs): + """Check how many times line matches caplog's messages.""" + return sum(bool(re.match(line, message)) for message in logs.messages) def get_args(args): @@ -101,6 +107,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( + return_value=['5m', '15m', '1h', '1d'])) def get_patched_exchange(mocker, config, api_mock=None, id='binance', @@ -195,6 +203,9 @@ def create_mock_trades(fee, use_db: bool = True): """ Create some fake trades ... """ + if use_db: + Trade.query.session.rollback() + def add_trade(trade): if use_db: Trade.query.session.add(trade) @@ -253,6 +264,8 @@ def create_mock_trades_usdt(fee, use_db: bool = True): trade = mock_trade_usdt_6(fee) add_trade(trade) + trade = mock_trade_usdt_7(fee) + add_trade(trade) if use_db: Trade.commit() @@ -1213,7 +1226,7 @@ def limit_sell_order_open(): 'id': 'mocked_limit_sell', 'type': 'limit', 'side': 'sell', - 'pair': 'mocked', + 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, @@ -1977,7 +1990,7 @@ def import_fails() -> None: @pytest.fixture(scope="function") def open_trade(): - return Trade( + trade = Trade( pair='ETH/BTC', open_rate=0.00001099, exchange='binance', @@ -1989,6 +2002,26 @@ def open_trade(): open_date=arrow.utcnow().shift(minutes=-601).datetime, is_open=True ) + trade.orders = [ + Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + order_id='123456789', + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate, + average=trade.open_rate, + filled=trade.amount, + remaining=0, + cost=trade.open_rate * trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + ] + return trade @pytest.fixture(scope="function") @@ -2015,7 +2048,7 @@ def saved_hyperopt_results(): 'params_dict': { 'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501 - 'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'holding_avg': timedelta(minutes=3930.0), 'stake_currency': 'BTC', 'strategy_name': 'SampleStrategy'}, # noqa: E501 + 'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'max_drawdown': 0.23, 'max_drawdown_abs': -0.00125625, 'holding_avg': timedelta(minutes=3930.0), 'stake_currency': 'BTC', 'strategy_name': 'SampleStrategy'}, # noqa: E501 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501 'total_profit': -0.00125625, 'current_epoch': 1, @@ -2031,7 +2064,7 @@ def saved_hyperopt_results(): 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501 'stoploss': {'stoploss': -0.338070047333259}}, - 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501 + 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'max_drawdown': 0.23, 'max_drawdown_abs': -0.00125625, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501 'total_profit': 6.185e-05, 'current_epoch': 2, @@ -2041,7 +2074,7 @@ def saved_hyperopt_results(): 'loss': 14.241196856510731, 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501 - 'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501 + 'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'max_drawdown': 0.25, 'max_drawdown_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501 'total_profit': -0.13639474, 'current_epoch': 3, @@ -2058,7 +2091,7 @@ def saved_hyperopt_results(): 'loss': 0.22195522184191518, 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501 - 'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501 + 'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'max_drawdown': 0.34, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501 'total_profit': -0.002480140000000001, 'current_epoch': 5, @@ -2068,7 +2101,7 @@ def saved_hyperopt_results(): 'loss': 0.545315889154162, 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501 - 'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501 + 'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'max_drawdown': 0.45, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501 'total_profit': -0.0041773, 'current_epoch': 6, @@ -2080,7 +2113,7 @@ def saved_hyperopt_results(): 'params_details': { 'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501 'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501 - 'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501 + 'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'max_drawdown': 0.50, 'max_drawdown_abs': -200.955321, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501 'total_profit': -0.06339929, 'current_epoch': 7, @@ -2090,7 +2123,7 @@ def saved_hyperopt_results(): 'loss': 20.0, # noqa: E501 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501 - 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501 + 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'max_drawdown': 0.0, 'max_drawdown_abs': 0.52, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501 'total_profit': 0.0, 'current_epoch': 8, @@ -2100,7 +2133,7 @@ def saved_hyperopt_results(): 'loss': 2.4731817780991223, 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501 - 'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501 + 'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'max_drawdown': 0.41, 'max_drawdown_abs': -150.955321, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501 'total_profit': -0.044050070000000004, # noqa: E501 'current_epoch': 9, @@ -2110,7 +2143,7 @@ def saved_hyperopt_results(): 'loss': -0.2604606005845212, # noqa: E501 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501 - 'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501 + 'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'max_drawdown': 0.13, 'max_drawdown_abs': -4.955321, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501 'total_profit': 0.00021629, 'current_epoch': 10, @@ -2121,7 +2154,7 @@ def saved_hyperopt_results(): 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501 # New Hyperopt mode! - 'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501 + 'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'max_drawdown': 0.52, 'max_drawdown_abs': -224.955321, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501 'total_profit': -0.07436117, 'current_epoch': 11, @@ -2131,7 +2164,7 @@ def saved_hyperopt_results(): 'loss': 100000, 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501 - 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501 + 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'max_drawdown': 0.0, 'max_drawdown_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 'total_profit': 0, 'current_epoch': 12, @@ -2180,7 +2213,7 @@ def limit_sell_order_usdt_open(): 'id': 'mocked_limit_sell_usdt', 'type': 'limit', 'side': 'sell', - 'pair': 'mocked', + 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 4496df37d..70a2a99a2 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -14,6 +14,7 @@ def mock_order_1(): 'side': 'buy', 'type': 'limit', 'price': 0.123, + 'average': 0.123, 'amount': 123.0, 'filled': 123.0, 'remaining': 0.0, diff --git a/tests/conftest_trades_usdt.py b/tests/conftest_trades_usdt.py index 1a03f0381..508e54f03 100644 --- a/tests/conftest_trades_usdt.py +++ b/tests/conftest_trades_usdt.py @@ -303,3 +303,61 @@ def mock_trade_usdt_6(fee): o = Order.parse_from_ccxt_object(mock_order_usdt_6_sell(), 'LTC/USDT', 'sell') trade.orders.append(o) return trade + + +def mock_order_usdt_7(): + return { + 'id': 'prod_buy_7', + 'symbol': 'LTC/USDT', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 10.0, + 'amount': 2.0, + 'filled': 2.0, + 'remaining': 0.0, + } + + +def mock_order_usdt_7_sell(): + return { + 'id': 'prod_sell_7', + 'symbol': 'LTC/USDT', + 'status': 'closed', + 'side': 'sell', + 'type': 'limit', + 'price': 8.0, + 'amount': 2.0, + 'filled': 2.0, + 'remaining': 0.0, + } + + +def mock_trade_usdt_7(fee): + """ + Simulate prod entry with open sell order + """ + trade = Trade( + pair='LTC/USDT', + stake_amount=20.0, + amount=2.0, + amount_requested=2.0, + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), + close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=5), + fee_open=fee.return_value, + fee_close=fee.return_value, + is_open=False, + open_rate=10.0, + close_rate=8.0, + close_profit=-0.2, + close_profit_abs=-4.0, + exchange='binance', + strategy='SampleStrategy', + open_order_id="prod_sell_6", + timeframe=5, + ) + o = Order.parse_from_ccxt_object(mock_order_usdt_7(), 'LTC/USDT', 'buy') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(mock_order_usdt_7_sell(), 'LTC/USDT', 'sell') + trade.orders.append(o) + return trade diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 1dcd04a80..341645854 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,14 +8,15 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange from freqtrade.constants import LAST_BT_RESULT_FN -from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, BT_DATA_COLUMNS_MID, BT_DATA_COLUMNS_OLD, - analyze_trade_parallelism, calculate_csum, +from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_csum, calculate_market_change, calculate_max_drawdown, - combine_dataframes_with_mean, create_cum_profit, - extract_trades_of_period, get_latest_backtest_filename, - get_latest_hyperopt_file, load_backtest_data, load_trades, + calculate_underwater, combine_dataframes_with_mean, + create_cum_profit, extract_trades_of_period, + get_latest_backtest_filename, get_latest_hyperopt_file, + load_backtest_data, load_backtest_metadata, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history +from freqtrade.exceptions import OperationalException from tests.conftest import create_mock_trades from tests.conftest_trades import MOCK_TRADE_COUNT @@ -40,7 +41,7 @@ def test_get_latest_backtest_filename(testdatadir, mocker): get_latest_backtest_filename(testdatadir) -def test_get_latest_hyperopt_file(testdatadir, mocker): +def test_get_latest_hyperopt_file(testdatadir): res = get_latest_hyperopt_file(testdatadir / 'does_not_exist', 'testfile.pickle') assert res == testdatadir / 'does_not_exist/testfile.pickle' @@ -50,21 +51,32 @@ def test_get_latest_hyperopt_file(testdatadir, mocker): res = get_latest_hyperopt_file(str(testdatadir.parent)) assert res == testdatadir.parent / "hyperopt_results.pickle" + # Test with absolute path + with pytest.raises( + OperationalException, + match="--hyperopt-filename expects only the filename, not an absolute path."): + get_latest_hyperopt_file(str(testdatadir.parent), str(testdatadir.parent)) -def test_load_backtest_data_old_format(testdatadir): - filename = testdatadir / "backtest-result_test.json" - bt_data = load_backtest_data(filename) - assert isinstance(bt_data, DataFrame) - assert list(bt_data.columns) == BT_DATA_COLUMNS_OLD + ['profit_abs', 'profit_ratio'] - assert len(bt_data) == 179 +def test_load_backtest_metadata(mocker, testdatadir): + res = load_backtest_metadata(testdatadir / 'nonexistant.file.json') + assert res == {} - # Test loading from string (must yield same result) - bt_data2 = load_backtest_data(str(filename)) - assert bt_data.equals(bt_data2) + mocker.patch('freqtrade.data.btanalysis.get_backtest_metadata_filename') + mocker.patch('freqtrade.data.btanalysis.json_load', side_effect=Exception()) + with pytest.raises(OperationalException, + match=r"Unexpected error.*loading backtest metadata\."): + load_backtest_metadata(testdatadir / 'nonexistant.file.json') - with pytest.raises(ValueError, match=r"File .* does not exist\."): - load_backtest_data(str("filename") + "nofile") + +def test_load_backtest_data_old_format(testdatadir, mocker): + + filename = testdatadir / "backtest-result_test222.json" + mocker.patch('freqtrade.data.btanalysis.load_backtest_stats', return_value=[]) + + with pytest.raises(OperationalException, + match=r"Backtest-results with only trades data are no longer supported."): + load_backtest_data(filename) def test_load_backtest_data_new_format(testdatadir): @@ -72,7 +84,7 @@ def test_load_backtest_data_new_format(testdatadir): filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) assert isinstance(bt_data, DataFrame) - assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) + assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) assert len(bt_data) == 179 # Test loading from string (must yield same result) @@ -96,7 +108,7 @@ def test_load_backtest_data_multi(testdatadir): for strategy in ('StrategyTestV2', 'TestStrategy'): bt_data = load_backtest_data(filename, strategy=strategy) assert isinstance(bt_data, DataFrame) - assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID) + assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) assert len(bt_data) == 179 # Test loading from string (must yield same result) @@ -167,8 +179,8 @@ def test_extract_trades_of_period(testdatadir): assert trades1.iloc[-1].close_date == Arrow(2017, 11, 14, 15, 25, 0).datetime -def test_analyze_trade_parallelism(default_conf, mocker, testdatadir): - filename = testdatadir / "backtest-result_test.json" +def test_analyze_trade_parallelism(testdatadir): + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) res = analyze_trade_parallelism(bt_data, "5m") @@ -234,8 +246,15 @@ def test_combine_dataframes_with_mean(testdatadir): assert "mean" in df.columns +def test_combine_dataframes_with_mean_no_data(testdatadir): + pairs = ["ETH/BTC", "ADA/BTC"] + data = load_data(datadir=testdatadir, pairs=pairs, timeframe='6m') + with pytest.raises(ValueError, match=r"No objects to concatenate"): + combine_dataframes_with_mean(data) + + def test_create_cum_profit(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -251,7 +270,7 @@ def test_create_cum_profit(testdatadir): def test_create_cum_profit1(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) # Move close-time to "off" the candle, to make sure the logic still works bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20) @@ -273,23 +292,31 @@ def test_create_cum_profit1(testdatadir): def test_calculate_max_drawdown(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) - drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(bt_data) + _, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown( + bt_data, value_col="profit_abs") assert isinstance(drawdown, float) - assert pytest.approx(drawdown) == 0.21142322 + assert pytest.approx(drawdown) == 0.12071099 assert isinstance(hdate, Timestamp) assert isinstance(lowdate, Timestamp) assert isinstance(hval, float) assert isinstance(lval, float) - assert hdate == Timestamp('2018-01-24 14:25:00', tz='UTC') - assert lowdate == Timestamp('2018-01-30 04:45:00', tz='UTC') + assert hdate == Timestamp('2018-01-25 01:30:00', tz='UTC') + assert lowdate == Timestamp('2018-01-25 03:50:00', tz='UTC') + + underwater = calculate_underwater(bt_data) + assert isinstance(underwater, DataFrame) + with pytest.raises(ValueError, match='Trade dataframe empty.'): - drawdown, hdate, lowdate, hval, lval = calculate_max_drawdown(DataFrame()) + calculate_max_drawdown(DataFrame()) + + with pytest.raises(ValueError, match='Trade dataframe empty.'): + calculate_underwater(DataFrame()) def test_calculate_csum(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) csum_min, csum_max = calculate_csum(bt_data) @@ -317,12 +344,13 @@ def test_calculate_max_drawdown2(): # sort by profit and reset index df = df.sort_values('profit').reset_index(drop=True) df1 = df.copy() - drawdown, hdate, ldate, hval, lval = calculate_max_drawdown( + drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( df, date_col='open_date', value_col='profit') # Ensure df has not been altered. assert df.equals(df1) assert isinstance(drawdown, float) + assert isinstance(drawdown_rel, float) # High must be before low assert hdate < ldate # High value must be higher than low value diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 575a590e7..627e29444 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -311,7 +311,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: assert td != len(data['UNITTEST/BTC']) start_real = data['UNITTEST/BTC'].iloc[0, 0] assert log_has(f'Missing data at start for pair ' - f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', + f'UNITTEST/BTC at 5m, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) # Make sure we start fresh - test missing data at end caplog.clear() @@ -326,7 +326,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: # Shift endtime with +5 - as last candle is dropped (partial candle) end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) assert log_has(f'Missing data at end for pair ' - f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', + f'UNITTEST/BTC at 5m, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) diff --git a/tests/exchange/test_bitpanda.py b/tests/exchange/test_bitpanda.py new file mode 100644 index 000000000..4bd168e7e --- /dev/null +++ b/tests/exchange/test_bitpanda.py @@ -0,0 +1,47 @@ +from datetime import datetime +from unittest.mock import MagicMock + +from tests.conftest import get_patched_exchange + + +def test_get_trades_for_order(default_conf, mocker): + exchange_name = 'bitpanda' + order_id = 'ABCD-ABCD' + since = datetime(2018, 5, 5, 0, 0, 0) + default_conf["dry_run"] = False + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + api_mock = MagicMock() + + api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', + 'order': 'ABCD-ABCD', + 'info': {'pair': 'XLTCZBTC', + 'time': 1519860024.4388, + 'type': 'buy', + 'ordertype': 'limit', + 'price': '20.00000', + 'cost': '38.62000', + 'fee': '0.06179', + 'vol': '5', + 'id': 'ABCD-ABCD'}, + 'timestamp': 1519860024438, + 'datetime': '2018-02-28T23:20:24.438Z', + 'symbol': 'LTC/BTC', + 'type': 'limit', + 'side': 'buy', + 'price': 165.0, + 'amount': 0.2340606, + 'fee': {'cost': 0.06179, 'currency': 'BTC'} + }]) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) + + orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) + assert len(orders) == 1 + assert orders[0]['price'] == 165 + assert api_mock.fetch_my_trades.call_count == 1 + # since argument should be + assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int) + assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' + # Same test twice, hardcoded number and doing the same calculation + assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 + # bitpanda requires "to" argument. + assert 'to' in api_mock.fetch_my_trades.call_args[1]['params'] diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 2f629528c..877d53fe7 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -19,36 +19,55 @@ from tests.conftest import get_default_conf EXCHANGES = { 'bittrex': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': False, 'timeframe': '1h', }, 'binance': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', }, 'kraken': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', }, 'ftx': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', }, 'kucoin': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', }, 'gateio': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', 'hasQuoteVolume': True, 'timeframe': '5m', }, - 'okex': { + 'okx': { 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + }, + 'huobi': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + }, + 'bitvavo': { + 'pair': 'BTC/EUR', + 'stake_currency': 'EUR', 'hasQuoteVolume': True, 'timeframe': '5m', }, @@ -68,6 +87,7 @@ def exchange_conf(): @pytest.fixture(params=EXCHANGES, scope="class") def exchange(request, exchange_conf): exchange_conf['exchange']['name'] = request.param + exchange_conf['stake_currency'] = EXCHANGES[request.param]['stake_currency'] exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True) yield exchange, request.param @@ -126,7 +146,10 @@ class TestCCXTExchange(): else: next_limit = exchange.get_next_limit_in_list( val, l2_limit_range, l2_limit_range_required) - if next_limit is None or next_limit > 200: + if next_limit is None: + assert len(l2['asks']) > 100 + assert len(l2['asks']) > 100 + elif next_limit > 200: # Large orderbook sizes can be a problem for some exchanges (bitrex ...) assert len(l2['asks']) > 200 assert len(l2['asks']) > 200 diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 12b11ff3d..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -20,7 +20,7 @@ from freqtrade.exchange.exchange import (market_is_active, timeframe_to_minutes, timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_seconds) from freqtrade.resolvers.exchange_resolver import ExchangeResolver -from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re +from tests.conftest import get_mock_coro, get_patched_exchange, log_has, log_has_re, num_log_has_re # Make sure to always keep one exchange here which is NOT subclassed!! @@ -166,7 +166,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - exchange = ExchangeResolver.load_exchange('huobi', default_conf) + exchange = ExchangeResolver.load_exchange('zaif', default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() @@ -1018,6 +1018,7 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, assert order_book_l2_usd.call_count == 1 assert order_closed['status'] == 'open' assert not order['fee'] + assert order_closed['filled'] == 0 order_book_l2_usd.reset_mock() order_closed['price'] = endprice @@ -1025,6 +1026,14 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice, order_closed = exchange.fetch_dry_run_order(order['id']) assert order_closed['status'] == 'closed' assert order['fee'] + assert order_closed['filled'] == 1 + assert order_closed['filled'] == order_closed['amount'] + + # Empty orderbook test + mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book', + return_value={'asks': [], 'bids': []}) + exchange._dry_run_open_orders[order['id']]['status'] = 'open' + order_closed = exchange.fetch_dry_run_order(order['id']) @pytest.mark.parametrize("side,rate,amount,endprice", [ @@ -1058,6 +1067,7 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou assert order["type"] == "market" assert order["symbol"] == "LTC/USDT" assert order['status'] == 'closed' + assert order['filled'] == amount assert round(order["average"], 4) == round(endprice, 4) @@ -1667,12 +1677,28 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: assert len(res) == len(pairs) assert exchange._api_async.fetch_ohlcv.call_count == 0 + exchange.required_candle_call_count = 1 assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, " f"timeframe {pairs[0][1]} ...", caplog) res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')], cache=False) assert len(res) == 3 + assert exchange._api_async.fetch_ohlcv.call_count == 3 + + # Test the same again, should NOT return from cache! + exchange._api_async.fetch_ohlcv.reset_mock() + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')], + cache=False) + assert len(res) == 3 + assert exchange._api_async.fetch_ohlcv.call_count == 3 + exchange._api_async.fetch_ohlcv.reset_mock() + caplog.clear() + # Call with invalid timeframe + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')], cache=False) + assert not res + assert len(res) == 0 + assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) @pytest.mark.asyncio @@ -1725,6 +1751,44 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_ (arrow.utcnow().int_timestamp - 2000) * 1000) +@pytest.mark.asyncio +async def test__async_kucoin_get_candle_history(default_conf, mocker, caplog): + caplog.set_level(logging.INFO) + api_mock = MagicMock() + api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection( + "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" + "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" + "429 Too Many Requests" '{"code":"429000","msg":"Too Many Requests"}')) + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kucoin") + + msg = "Kucoin 429 error, avoid triggering DDosProtection backoff delay" + assert not num_log_has_re(msg, caplog) + + for _ in range(3): + with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): + await exchange._async_get_candle_history( + "ETH/BTC", "5m", (arrow.utcnow().int_timestamp - 2000) * 1000, count=3) + assert num_log_has_re(msg, caplog) == 3 + + caplog.clear() + # Test regular non-kucoin message + api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.DDoSProtection( + "kucoin GET https://openapi-v2.kucoin.com/api/v1/market/candles?" + "symbol=ETH-BTC&type=5min&startAt=1640268735&endAt=1640418735" + "429 Too Many Requests" '{"code":"2222222","msg":"Too Many Requests"}')) + + msg = r'_async_get_candle_history\(\) returned exception: .*' + msg2 = r'Applying DDosProtection backoff delay: .*' + with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)): + for _ in range(3): + with pytest.raises(DDosProtection, match=r'429 Too Many Requests'): + await exchange._async_get_candle_history( + "ETH/BTC", "5m", (arrow.utcnow().int_timestamp - 2000) * 1000, count=3) + # Expect the "returned exception" message 12 times (4 retries * 3 (loop)) + assert num_log_has_re(msg, caplog) == 12 + assert num_log_has_re(msg2, caplog) == 9 + + @pytest.mark.asyncio async def test__async_get_candle_history_empty(default_conf, mocker, caplog): """ Test empty exchange result """ @@ -1768,7 +1832,7 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog): assert len(res) == 1 # Test that each is in list at least once as order is not guaranteed assert log_has("Error loading ETH/BTC. Result was [[]].", caplog) - assert log_has("Async code raised an exception: TypeError", caplog) + assert log_has("Async code raised an exception: TypeError()", caplog) def test_get_next_limit_in_list(): @@ -2933,39 +2997,49 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None: assert ex.extract_cost_curr_rate(order) == expected -@pytest.mark.parametrize("order,expected", [ +@pytest.mark.parametrize("order,unknown_fee_rate,expected", [ # Using base-currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1), + 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.1), ({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05, - 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08), + 'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.08), # Using quote currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1), + 'fee': {'currency': 'BTC', 'cost': 0.005}}, None, 0.1), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04), + 'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, None, 0.04), # Using foreign currency ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944), + 'fee': {'currency': 'NEO', 'cost': 0.0012}}, None, 0.001944), ({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561, - 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305), + 'fee': {'currency': 'NEO', 'cost': 0.00027452}}, None, 0.00074305), # Rate included in return - return as is ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01), + 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, None, 0.01), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05, - 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005), + 'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, None, 0.005), # 0.1% filled - no costs (kraken - #3431) ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None), + 'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None, None), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0), + 'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, None, 0.0), ({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0, - 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None), + 'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None, None), + # Invalid pair combination - POINT/BTC is not a pair + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, None, None), + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0), + ({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5, + 'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0), ]) -def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None: +def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None: mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081}) + if unknown_fee_rate: + default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate ex = get_patched_exchange(mocker, default_conf) + assert ex.calculate_fee_rate(order) == expected diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py index 3794bb79c..c2fb90c9d 100644 --- a/tests/exchange/test_ftx.py +++ b/tests/exchange/test_ftx.py @@ -125,7 +125,7 @@ def test_stoploss_adjust_ftx(mocker, default_conf): assert not exchange.stoploss_adjust(1501, order) -def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): +def test_fetch_stoploss_order_ftx(default_conf, mocker, limit_sell_order): default_conf['dry_run'] = True order = MagicMock() order.myid = 123 @@ -147,9 +147,15 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"): exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] - api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': 'closed'}]) + # stoploss Limit order + api_mock.fetch_orders = MagicMock(return_value=[ + {'id': 'X', 'status': 'closed', + 'info': { + 'orderId': 'mocked_limit_sell', + }}]) api_mock.fetch_order = MagicMock(return_value=limit_sell_order) + # No orderId field - no call to fetch_order resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') assert resp assert api_mock.fetch_order.call_count == 1 @@ -158,6 +164,17 @@ def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order): assert resp['type'] == 'stop' assert resp['status_stop'] == 'triggered' + # Stoploss market order + # Contains no new Order, but "average" instead + order = {'id': 'X', 'status': 'closed', 'info': {'orderId': None}, 'average': 0.254} + api_mock.fetch_orders = MagicMock(return_value=[order]) + api_mock.fetch_order.reset_mock() + resp = exchange.fetch_stoploss_order('X', 'TKN/BTC') + assert resp + # fetch_order not called (no regular order ID) + assert api_mock.fetch_order.call_count == 0 + assert order == order + with pytest.raises(InvalidOrderException): api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx') diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index 6f7862909..ce356be8c 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -1,8 +1,11 @@ +from unittest.mock import MagicMock + import pytest from freqtrade.exceptions import OperationalException from freqtrade.exchange import Gateio from freqtrade.resolvers.exchange_resolver import ExchangeResolver +from tests.conftest import get_patched_exchange def test_validate_order_types_gateio(default_conf, mocker): @@ -26,3 +29,39 @@ def test_validate_order_types_gateio(default_conf, mocker): with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): ExchangeResolver.load_exchange('gateio', default_conf, True) + + +def test_fetch_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + fetch_order_mock = MagicMock() + exchange.fetch_order = fetch_order_mock + + exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert fetch_order_mock.call_count == 1 + assert fetch_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_cancel_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + cancel_order_mock = MagicMock() + exchange.cancel_order = cancel_order_mock + + exchange.cancel_stoploss_order('1234', 'ETH/BTC') + assert cancel_order_mock.call_count == 1 + assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_stoploss_adjust_gateio(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + order = { + 'price': 1500, + 'stopPrice': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py new file mode 100644 index 000000000..b39b5ab30 --- /dev/null +++ b/tests/exchange/test_huobi.py @@ -0,0 +1,109 @@ +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException +from tests.conftest import get_patched_exchange +from tests.exchange.test_exchange import ccxt_exceptionhandlers + + +@pytest.mark.parametrize('limitratio,expected', [ + (None, 220 * 0.99), + (0.99, 220 * 0.99), + (0.98, 220 * 0.98), +]) +def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'stop-limit' + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' + assert api_mock.create_order.call_args_list[0][1]['type'] == order_type + assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 + # Price should be 1% below stopprice + assert api_mock.create_order.call_args_list[0][1]['price'] == expected + assert api_mock.create_order.call_args_list[0][1]['params'] == {"stopPrice": 220, + "operator": "lte", + } + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(InvalidOrderException): + api_mock.create_order = MagicMock( + side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi", + "stoploss", "create_order", retries=1, + pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + +def test_stoploss_order_dry_run_huobi(default_conf, mocker): + api_mock = MagicMock() + order_type = 'stop-limit' + default_conf['dry_run'] = True + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 + + +def test_stoploss_adjust_huobi(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='huobi') + order = { + 'type': 'stop', + 'price': 1500, + 'stopPrice': '1500', + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['type'] = 'stop_loss' + assert not exchange.stoploss_adjust(1501, order) diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py new file mode 100644 index 000000000..87f9ae8d9 --- /dev/null +++ b/tests/exchange/test_kucoin.py @@ -0,0 +1,120 @@ +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException +from tests.conftest import get_patched_exchange +from tests.exchange.test_exchange import ccxt_exceptionhandlers + + +@pytest.mark.parametrize('order_type', ['market', 'limit']) +@pytest.mark.parametrize('limitratio,expected', [ + (None, 220 * 0.99), + (0.99, 220 * 0.99), + (0.98, 220 * 0.98), +]) +def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order_type): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + if order_type == 'limit': + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={ + 'stoploss': order_type, + 'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + order_types = {'stoploss': order_type} + if limitratio is not None: + order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' + assert api_mock.create_order.call_args_list[0][1]['type'] == order_type + assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 + # Price should be 1% below stopprice + if order_type == 'limit': + assert api_mock.create_order.call_args_list[0][1]['price'] == expected + else: + assert api_mock.create_order.call_args_list[0][1]['price'] is None + + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'stopPrice': 220, + 'stop': 'loss' + } + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(InvalidOrderException): + api_mock.create_order = MagicMock( + side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately.")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin", + "stoploss", "create_order", retries=1, + pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + +def test_stoploss_order_dry_run_kucoin(default_conf, mocker): + api_mock = MagicMock() + order_type = 'market' + default_conf['dry_run'] = True + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) + + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss': 'limit', + 'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 + + +def test_stoploss_adjust_kucoin(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='kucoin') + order = { + 'type': 'limit', + 'price': 1500, + 'stopPrice': 1500, + 'info': {'stopPrice': 1500, 'stop': "limit"}, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['info']['stop'] = None + assert not exchange.stoploss_adjust(1501, order) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 68088d2d5..ce6ea0f0c 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -36,6 +36,8 @@ class BTContainer(NamedTuple): trailing_stop_positive_offset: float = 0.0 use_sell_signal: bool = False use_custom_stoploss: bool = False + custom_entry_price: Optional[float] = None + custom_exit_price: Optional[float] = None def _get_frame_time_from_offset(offset): diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 775f15b87..977563eeb 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -1,5 +1,6 @@ # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument import logging +from unittest.mock import MagicMock import pytest @@ -426,8 +427,6 @@ tc26 = BTContainer(data=[ # Test 27: Sell with signal sell in candle 3 (ROI at signal candle) # Stoploss at 10% (irrelevant), ROI at 5% (will trigger) - Wins over Sell-signal -# TODO: figure out if sell-signal should win over ROI -# Sell-signal wins over stoploss tc27 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], @@ -436,8 +435,8 @@ tc27 = BTContainer(data=[ [3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], - stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, - trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)] + stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True, + trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) # Test 28: trailing_stop should raise so candle 3 causes a stoploss @@ -536,6 +535,94 @@ tc33 = BTContainer(data=[ )] ) +# Test 34: Custom-entry-price below all candles should timeout - so no trade happens. +tc34 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # timeout + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.01, roi={"0": 0.10}, profit_perc=0.0, + custom_entry_price=4200, trades=[] +) + +# Test 35: Custom-entry-price above all candles should have rate adjusted to "entry candle high" +tc35 = BTContainer(data=[ + # D O H L C V B S + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # Timeout + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, + custom_entry_price=7200, trades=[ + BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1) + ] +) + +# Test 36: Custom-entry-price around candle low +# Would cause immediate ROI exit, but since the trade was entered +# below open, we treat this as cheating, and delay the sell by 1 candle. +# details: https://github.com/freqtrade/freqtrade/issues/6261 +tc36 = BTContainer(data=[ + # D O H L C V B S BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 4951, 4999, 6172, 0, 0], # Enter and immediate ROI + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, + custom_entry_price=4952, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] +) + +# Test 37: Custom-entry-price around candle low +# Would cause immediate ROI exit below close +# details: https://github.com/freqtrade/freqtrade/issues/6261 +tc37 = BTContainer(data=[ + # D O H L C V B S BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5400, 5500, 4951, 5100, 6172, 0, 0], # Enter and immediate ROI + [2, 4900, 5250, 4500, 5100, 6172, 0, 0], + [3, 5100, 5100, 4650, 4750, 6172, 0, 0], + [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, + custom_entry_price=4952, + trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] +) + +# Test 38: Custom exit price below all candles +# Price adjusted to candle Low. +tc38 = BTContainer(data=[ + # D O H L C V B S BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], + [2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout + [3, 5100, 5100, 4950, 4950, 6172, 0, 0], + [4, 5000, 5100, 4950, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, + use_sell_signal=True, + custom_exit_price=4552, + trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=3)] +) + +# Test 39: Custom exit price above all candles +# causes sell signal timeout +tc39 = BTContainer(data=[ + # D O H L C V B S BT + [0, 5000, 5050, 4950, 5000, 6172, 1, 0], + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], + [2, 4900, 5250, 4900, 5100, 6172, 0, 1], # exit - but timeout + [3, 5100, 5100, 4950, 4950, 6172, 0, 0], + [4, 5000, 5100, 4950, 4950, 6172, 0, 0]], + stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, + use_sell_signal=True, + custom_exit_price=6052, + trades=[BTrade(sell_reason=SellType.FORCE_SELL, open_tick=1, close_tick=4)] +) + + TESTS = [ tc0, tc1, @@ -571,6 +658,12 @@ TESTS = [ tc31, tc32, tc33, + tc34, + tc35, + tc36, + tc37, + tc38, + tc39, ] @@ -599,6 +692,10 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: backtesting.required_startup = 0 backtesting.strategy.advise_buy = lambda a, m: frame backtesting.strategy.advise_sell = lambda a, m: frame + if data.custom_entry_price: + backtesting.strategy.custom_entry_price = MagicMock(return_value=data.custom_entry_price) + if data.custom_exit_price: + backtesting.strategy.custom_exit_price = MagicMock(return_value=data.custom_exit_price) backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss caplog.set_level(logging.DEBUG) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index ab7aa74a1..a8998eb63 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument import random +from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path from unittest.mock import MagicMock, PropertyMock @@ -10,6 +11,7 @@ import pandas as pd import pytest from arrow import Arrow +from freqtrade import constants from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_backtesting from freqtrade.configuration import TimeRange from freqtrade.data import history @@ -19,6 +21,8 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import get_timerange from freqtrade.enums import RunMode, SellType from freqtrade.exceptions import DependencyException, OperationalException +from freqtrade.exchange.exchange import timeframe_to_next_date +from freqtrade.misc import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade from freqtrade.resolvers import StrategyResolver @@ -48,6 +52,13 @@ def trim_dictlist(dict_list, num): return new +@pytest.fixture(autouse=True) +def backtesting_cleanup() -> None: + yield None + + Backtesting.cleanup() + + def load_data_test(what, testdatadir): timerange = TimeRange.parse_timerange('1510694220-1510700340') data = history.load_pair_history(pair='UNITTEST/BTC', datadir=testdatadir, @@ -438,7 +449,8 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) -> Backtesting(default_conf) default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}] - with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'): + with pytest.raises(OperationalException, + match=r'VolumePairList not allowed for backtesting\..*StaticPairlist.*'): Backtesting(default_conf) default_conf.update({ @@ -470,7 +482,8 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti default_conf['timerange'] = '20180101-20180102' default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}] - with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'): + with pytest.raises(OperationalException, + match=r'VolumePairList not allowed for backtesting\..*StaticPairlist.*'): Backtesting(default_conf) default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}] @@ -515,6 +528,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: # Fake 2 trades, so there's not enough amount for the next trade left. LocalTrade.trades_open.append(trade) LocalTrade.trades_open.append(trade) + backtesting.wallets.update() trade = backtesting._enter_trade(pair, row=row) assert trade is None LocalTrade.trades_open.pop() @@ -522,6 +536,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: assert trade is not None backtesting.strategy.custom_stake_amount = lambda **kwargs: 123.5 + backtesting.wallets.update() trade = backtesting._enter_trade(pair, row=row) assert trade assert trade.stake_amount == 123.5 @@ -545,8 +560,6 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None: trade = backtesting._enter_trade(pair, row=row) assert trade is None - backtesting.cleanup() - def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: default_conf['use_sell_signal'] = False @@ -629,7 +642,8 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: assert res.sell_reason == SellType.ROI.value # Sell at minute 3 (not available above!) assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc) - assert round(res.close_rate, 3) == round(209.0225, 3) + sell_order = res.select_order('sell', True) + assert sell_order is not None def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: @@ -645,8 +659,9 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: timerange=timerange) processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) + result = backtesting.backtest( - processed=processed, + processed=deepcopy(processed), start_date=min_date, end_date=max_date, max_open_trades=10, @@ -736,6 +751,46 @@ def test_processed(default_conf, mocker, testdatadir) -> None: assert col in cols +def test_backtest_dataprovider_analyzed_df(default_conf, fee, mocker, testdatadir) -> None: + default_conf['use_sell_signal'] = False + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + patch_exchange(mocker) + backtesting = Backtesting(default_conf) + backtesting._set_strategy(backtesting.strategylist[0]) + timerange = TimeRange('date', None, 1517227800, 0) + data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], + timerange=timerange) + processed = backtesting.strategy.advise_all_indicators(data) + min_date, max_date = get_timerange(processed) + + global count + count = 0 + + def tmp_confirm_entry(pair, current_time, **kwargs): + dp = backtesting.strategy.dp + df, _ = dp.get_analyzed_dataframe(pair, backtesting.strategy.timeframe) + current_candle = df.iloc[-1].squeeze() + assert current_candle['buy'] == 1 + + candle_date = timeframe_to_next_date(backtesting.strategy.timeframe, current_candle['date']) + assert candle_date == current_time + # These asserts don't properly raise as they are nested, + # therefore we increment count and assert for that. + global count + count = count + 1 + + backtesting.strategy.confirm_trade_entry = tmp_confirm_entry + backtesting.backtest( + processed=deepcopy(processed), + start_date=min_date, + end_date=max_date, + max_open_trades=10, + position_stacking=False, + ) + assert count == 5 + + def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatadir) -> None: # While this test IS a copy of test_backtest_pricecontours, it's needed to ensure # results do not carry-over to the next run, which is not given by using parametrize. @@ -758,6 +813,8 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad # While buy-signals are unrealistic, running backtesting # over and over again should not cause different results for [contour, numres] in tests: + # Debug output for random test failure + print(f"{contour}, {numres}") assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres @@ -885,7 +942,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) processed = backtesting.strategy.advise_all_indicators(data) min_date, max_date = get_timerange(processed) backtest_conf = { - 'processed': processed, + 'processed': deepcopy(processed), 'start_date': min_date, 'end_date': max_date, 'max_open_trades': 3, @@ -907,7 +964,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) 'NXT/BTC', '5m')[0]) == len(data['NXT/BTC']) - 1 - backtesting.strategy.startup_candle_count backtest_conf = { - 'processed': processed, + 'processed': deepcopy(processed), 'start_date': min_date, 'end_date': max_date, 'max_open_trades': 1, @@ -971,6 +1028,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'config': default_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, }) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', @@ -1079,6 +1138,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'config': default_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, }, { @@ -1086,6 +1147,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'config': default_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, } ]) @@ -1188,6 +1251,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, 'config': default_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, }, { @@ -1195,6 +1260,8 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, 'config': default_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, } ]) @@ -1236,3 +1303,132 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, assert 'BACKTESTING REPORT' in captured.out assert 'SELL REASON STATS' in captured.out assert 'LEFT OPEN TRADES REPORT' in captured.out + + +@pytest.mark.filterwarnings("ignore:deprecated") +@pytest.mark.parametrize('run_id', ['2', 'changed']) +@pytest.mark.parametrize('start_delta', [{'days': 0}, {'days': 1}, {'weeks': 1}, {'weeks': 4}]) +@pytest.mark.parametrize('cache', constants.BACKTEST_CACHE_AGE) +def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testdatadir, run_id, + start_delta, cache): + default_conf.update({ + "use_sell_signal": True, + "sell_profit_only": False, + "sell_profit_offset": 0.0, + "ignore_roi_if_buy_signal": False, + }) + patch_exchange(mocker) + backtestmock = MagicMock(return_value={ + 'results': pd.DataFrame(columns=BT_DATA_COLUMNS), + 'config': default_conf, + 'locks': [], + 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, + 'final_balance': 1000, + }) + mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', + PropertyMock(return_value=['UNITTEST/BTC'])) + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + mocker.patch('freqtrade.optimize.backtesting.show_backtest_results', MagicMock()) + + now = min_backtest_date = datetime.now(tz=timezone.utc) + start_time = now - timedelta(**start_delta) + timedelta(hours=1) + if cache == 'none': + min_backtest_date = now + timedelta(days=1) + elif cache == 'day': + min_backtest_date = now - timedelta(days=1) + elif cache == 'week': + min_backtest_date = now - timedelta(weeks=1) + elif cache == 'month': + min_backtest_date = now - timedelta(weeks=4) + load_backtest_metadata = MagicMock(return_value={ + 'StrategyTestV2': {'run_id': '1', 'backtest_start_time': now.timestamp()}, + 'TestStrategyLegacyV1': {'run_id': run_id, 'backtest_start_time': start_time.timestamp()} + }) + load_backtest_stats = MagicMock(side_effect=[ + { + 'metadata': {'StrategyTestV2': {'run_id': '1'}}, + 'strategy': {'StrategyTestV2': {}}, + 'strategy_comparison': [{'key': 'StrategyTestV2'}] + }, + { + 'metadata': {'TestStrategyLegacyV1': {'run_id': '2'}}, + 'strategy': {'TestStrategyLegacyV1': {}}, + 'strategy_comparison': [{'key': 'TestStrategyLegacyV1'}] + } + ]) + mocker.patch('pathlib.Path.glob', return_value=[ + Path(datetime.strftime(datetime.now(), 'backtest-result-%Y-%m-%d_%H-%M-%S.json'))]) + mocker.patch.multiple('freqtrade.data.btanalysis', + load_backtest_metadata=load_backtest_metadata, + load_backtest_stats=load_backtest_stats) + mocker.patch('freqtrade.optimize.backtesting.get_strategy_run_id', side_effect=['1', '2', '2']) + + patched_configuration_load_config_file(mocker, default_conf) + + args = [ + 'backtesting', + '--config', 'config.json', + '--datadir', str(testdatadir), + '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), + '--timeframe', '1m', + '--timerange', '1510694220-1510700340', + '--enable-position-stacking', + '--disable-max-market-positions', + '--cache', cache, + '--strategy-list', + 'StrategyTestV2', + 'TestStrategyLegacyV1', + ] + args = get_args(args) + start_backtesting(args) + + # check the logs, that will contain the backtest result + exists = [ + 'Parameter -i/--timeframe detected ... Using timeframe: 1m ...', + 'Parameter --timerange detected: 1510694220-1510700340 ...', + f'Using data directory: {testdatadir} ...', + 'Loading data from 2017-11-14 20:57:00 ' + 'up to 2017-11-14 22:58:00 (0 days).', + 'Parameter --enable-position-stacking detected ...', + ] + + for line in exists: + assert log_has(line, caplog) + + if cache == 'none': + assert backtestmock.call_count == 2 + exists = [ + 'Running backtesting for Strategy StrategyTestV2', + 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', + 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:58:00 (0 days).', + ] + elif run_id == '2' and min_backtest_date < start_time: + assert backtestmock.call_count == 0 + exists = [ + 'Reusing result of previous backtest for StrategyTestV2', + 'Reusing result of previous backtest for TestStrategyLegacyV1', + ] + else: + exists = [ + 'Reusing result of previous backtest for StrategyTestV2', + 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', + 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:58:00 (0 days).', + ] + assert backtestmock.call_count == 1 + + for line in exists: + assert log_has(line, caplog) + + +def test_get_strategy_run_id(default_conf_usdt): + default_conf_usdt.update({ + 'strategy': 'StrategyTestV2', + 'max_open_trades': float('inf') + }) + strategy = StrategyResolver.load_strategy(default_conf_usdt) + x = get_strategy_run_id(strategy) + assert isinstance(x, str) diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py new file mode 100644 index 000000000..a7f953008 --- /dev/null +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -0,0 +1,82 @@ +# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument + +from copy import deepcopy + +import pandas as pd +from arrow import Arrow + +from freqtrade.configuration import TimeRange +from freqtrade.data import history +from freqtrade.data.history import get_timerange +from freqtrade.enums import SellType +from freqtrade.optimize.backtesting import Backtesting +from tests.conftest import patch_exchange + + +def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> None: + default_conf['use_sell_signal'] = False + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001) + patch_exchange(mocker) + default_conf.update({ + "stake_amount": 100.0, + "dry_run_wallet": 1000.0, + "strategy": "StrategyTestV2" + }) + backtesting = Backtesting(default_conf) + backtesting._set_strategy(backtesting.strategylist[0]) + pair = 'UNITTEST/BTC' + timerange = TimeRange('date', None, 1517227800, 0) + data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], + timerange=timerange) + backtesting.strategy.position_adjustment_enable = True + processed = backtesting.strategy.advise_all_indicators(data) + min_date, max_date = get_timerange(processed) + result = backtesting.backtest( + processed=deepcopy(processed), + start_date=min_date, + end_date=max_date, + max_open_trades=10, + position_stacking=False, + ) + results = result['results'] + assert not results.empty + assert len(results) == 2 + + expected = pd.DataFrame( + {'pair': [pair, pair], + 'stake_amount': [500.0, 100.0], + 'amount': [4806.87657523, 970.63960782], + 'open_date': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime, + Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True + ), + 'close_date': pd.to_datetime([Arrow(2018, 1, 29, 22, 00, 0).datetime, + Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True), + 'open_rate': [0.10401764894444211, 0.10302485], + 'close_rate': [0.10453904066847439, 0.103541], + 'fee_open': [0.0025, 0.0025], + 'fee_close': [0.0025, 0.0025], + 'trade_duration': [200, 40], + 'profit_ratio': [0.0, 0.0], + 'profit_abs': [0.0, 0.0], + 'sell_reason': [SellType.ROI.value, SellType.ROI.value], + 'initial_stop_loss_abs': [0.0940005, 0.09272236], + 'initial_stop_loss_ratio': [-0.1, -0.1], + 'stop_loss_abs': [0.0940005, 0.09272236], + 'stop_loss_ratio': [-0.1, -0.1], + 'min_rate': [0.10370188, 0.10300000000000001], + 'max_rate': [0.10481985, 0.1038888], + 'is_open': [False, False], + 'buy_tag': [None, None], + }) + pd.testing.assert_frame_equal(results, expected) + data_pair = processed[pair] + for _, t in results.iterrows(): + ln = data_pair.loc[data_pair["date"] == t["open_date"]] + # Check open trade rate alignes to open rate + assert ln is not None + # check close trade rate alignes to close rate or is between high and low + ln = data_pair.loc[data_pair["date"] == t["close_date"]] + assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or + round(ln.iloc[0]["low"], 6) < round( + t["close_rate"], 6) < round(ln.iloc[0]["high"], 6)) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index a43c62376..2328585dd 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -1,5 +1,5 @@ # pragma pylint: disable=missing-docstring,W0212,C0103 -from datetime import datetime +from datetime import datetime, timedelta from pathlib import Path from unittest.mock import ANY, MagicMock @@ -22,6 +22,29 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, patched_configuration_load_config_file) +def generate_result_metrics(): + return { + 'trade_count': 1, + 'total_trades': 1, + 'avg_profit': 0.1, + 'total_profit': 0.001, + 'profit': 0.01, + 'duration': 20.0, + 'wins': 1, + 'draws': 0, + 'losses': 0, + 'profit_mean': 0.01, + 'profit_total_abs': 0.001, + 'profit_total': 0.01, + 'holding_avg': timedelta(minutes=20), + 'max_drawdown': 0.001, + 'max_drawdown_abs': 0.001, + 'loss': 0.001, + 'is_initial_point': 0.001, + 'is_best': 1, + } + + def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) @@ -168,7 +191,8 @@ def test_start_no_hyperopt_allowed(mocker, hyperopt_conf, caplog) -> None: start_hyperopt(pargs) -def test_start_no_data(mocker, hyperopt_conf) -> None: +def test_start_no_data(mocker, hyperopt_conf, tmpdir) -> None: + hyperopt_conf['user_data_dir'] = Path(tmpdir) patched_configuration_load_config_file(mocker, hyperopt_conf) mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=pd.DataFrame)) mocker.patch( @@ -177,7 +201,6 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: ) patch_exchange(mocker) - # TODO: migrate to strategy-based hyperopt args = [ 'hyperopt', '--config', 'config.json', @@ -189,6 +212,12 @@ def test_start_no_data(mocker, hyperopt_conf) -> None: with pytest.raises(OperationalException, match='No data found. Terminating.'): start_hyperopt(pargs) + # Cleanup since that failed hyperopt start leaves a lockfile. + try: + Path(Hyperopt.get_lock_filename(hyperopt_conf)).unlink() + except Exception: + pass + def test_start_filelock(mocker, hyperopt_conf, caplog) -> None: hyperopt_mock = MagicMock(side_effect=Timeout(Hyperopt.get_lock_filename(hyperopt_conf))) @@ -215,14 +244,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: hyperopt.print_results( { 'loss': 1, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - }, + 'results_metrics': generate_result_metrics(), 'total_profit': 0, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'is_initial_point': False, @@ -231,7 +253,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: ) out, err = capsys.readouterr() assert all(x in out - for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "20.0 m"]) + for x in ["Best", "2/2", " 1", "0.10%", "0.00100000 BTC (1.00%)", "00:20:00"]) def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: @@ -288,14 +310,7 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {'buy': {}, 'sell': {}, 'roi': {}, 'stoploss': 0.0}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - }, + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -349,10 +364,12 @@ def test_hyperopt_format_results(hyperopt): 'locks': [], 'final_balance': 0.02, 'rejected_signals': 2, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'backtest_start_time': 1619718665, 'backtest_end_time': 1619718665, } - results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result, + results_metrics = generate_strategy_stats(['XRP/BTC'], '', bt_result, Arrow(2017, 11, 14, 19, 32, 00), Arrow(2017, 12, 14, 19, 32, 00), market_change=0) @@ -416,6 +433,8 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: 'config': hyperopt_conf, 'locks': [], 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'final_balance': 1000, } @@ -521,14 +540,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: 'roi': {}, 'stoploss': {'stoploss': None}, 'trailing': {'trailing_stop': None} }, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -577,14 +589,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: 'sell': {'sell-mfi-value': None}, 'roi': {}, 'stoploss': {'stoploss': None} }, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -622,14 +627,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, 'params_details': {'roi': {}, 'stoploss': {'stoploss': None}}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -669,14 +667,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {'stoploss': 0.0}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -749,14 +740,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) @@ -798,14 +782,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', MagicMock(return_value=[{ 'loss': 1, 'results_explanation': 'foo result', 'params': {}, - 'results_metrics': - { - 'trade_count': 1, - 'avg_profit': 0.1, - 'total_profit': 0.001, - 'profit': 1.0, - 'duration': 20.0 - } + 'results_metrics': generate_result_metrics(), }]) ) patch_exchange(mocker) diff --git a/tests/optimize/test_hyperopt_tools.py b/tests/optimize/test_hyperopt_tools.py index d9a52db39..6cc0caf40 100644 --- a/tests/optimize/test_hyperopt_tools.py +++ b/tests/optimize/test_hyperopt_tools.py @@ -10,7 +10,7 @@ import rapidjson from freqtrade.constants import FTHYPT_FILEVERSION from freqtrade.exceptions import OperationalException from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer -from tests.conftest import log_has +from tests.conftest import log_has, log_has_re # Functions for recurrent object patching @@ -24,6 +24,7 @@ def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None: hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt') hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {}) + assert log_has_re("Hyperopt file .* not found.", caplog) assert hyperopt_epochs == ([], 0) # Test writing to temp dir and reading again diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index e4a2eec2e..e3f6daf6c 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -86,6 +86,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "SharpeHyperOptLossDaily", "MaxDrawDownHyperOptLoss", "CalmarHyperOptLoss", + "ProfitDrawDownHyperOptLoss", ]) def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunction) -> None: @@ -106,7 +107,7 @@ def test_loss_functions_better_profits(default_conf, hyperopt_results, lossfunct config=default_conf, processed=None, backtest_stats={'profit_total': hyperopt_results['profit_abs'].sum()} - ) + ) over = hl.hyperopt_loss_function( results_over, trade_count=len(results_over), diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index e56572522..c8768e236 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -1,4 +1,3 @@ -import datetime import re from datetime import timedelta from pathlib import Path @@ -49,7 +48,7 @@ def test_text_table_bt_results(): ' 0:20:00 | 2 0 1 66.7 |' ) - pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', + pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC', starting_balance=4, results=results) assert text_table_bt_results(pair_results, stake_currency='BTC') == result_str @@ -83,8 +82,11 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): 'locks': [], 'final_balance': 1000.02, 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp, + 'run_id': '123', } } timerange = TimeRange.parse_timerange('1510688220-1510700340') @@ -103,7 +105,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT) assert strat_stats['total_trades'] == len(results['DefStrat']['results']) # Above sample had no loosing trade - assert strat_stats['max_drawdown'] == 0.0 + assert strat_stats['max_drawdown_account'] == 0.0 # Retry with losing trade results = {'DefStrat': { @@ -131,8 +133,11 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): 'locks': [], 'final_balance': 1000.02, 'rejected_signals': 20, + 'timedout_entry_orders': 0, + 'timedout_exit_orders': 0, 'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp, + 'run_id': '124', } } @@ -143,7 +148,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): assert 'strategy_comparison' in stats strat_stats = stats['strategy']['DefStrat'] - assert strat_stats['max_drawdown'] == 0.013803 + assert pytest.approx(strat_stats['max_drawdown_account']) == 1.399999e-08 assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00' assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00' assert strat_stats['drawdown_end_ts'] == 1510699380000 @@ -165,7 +170,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): filename1 = Path(tmpdir / last_fn) assert filename1.is_file() content = filename1.read_text() - assert 'max_drawdown' in content + assert 'max_drawdown_account' in content assert 'strategy' in content assert 'pairlist' in content @@ -179,16 +184,16 @@ def test_store_backtest_stats(testdatadir, mocker): dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.file_dump_json') - store_backtest_stats(testdatadir, {}) + store_backtest_stats(testdatadir, {'metadata': {}}) - assert dump_mock.call_count == 2 + assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir/'backtest-result')) dump_mock.reset_mock() filename = testdatadir / 'testresult.json' - store_backtest_stats(filename, {}) - assert dump_mock.call_count == 2 + store_backtest_stats(filename, {'metadata': {}}) + assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) # result will be testdatadir / testresult-.json assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult')) @@ -208,7 +213,7 @@ def test_generate_pair_metrics(): } ) - pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC', + pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC', starting_balance=2, results=results) assert isinstance(pair_results, list) assert len(pair_results) == 2 @@ -227,9 +232,9 @@ def test_generate_daily_stats(testdatadir): assert isinstance(res, dict) assert round(res['backtest_best_day'], 4) == 0.1796 assert round(res['backtest_worst_day'], 4) == -0.1468 - assert res['winning_days'] == 14 - assert res['draw_days'] == 4 - assert res['losing_days'] == 3 + assert res['winning_days'] == 19 + assert res['draw_days'] == 0 + assert res['losing_days'] == 2 # Select empty dataframe! res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :]) @@ -324,51 +329,25 @@ def test_generate_sell_reason_stats(): assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) -def test_text_table_strategy(default_conf): - default_conf['max_open_trades'] = 2 - default_conf['dry_run_wallet'] = 3 - results = {} - date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30) - delta = datetime.timedelta(days=1) - results['TestStrategy1'] = {'results': pd.DataFrame( - { - 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'], - 'close_date': [date, date + delta, date + delta * 2], - 'profit_ratio': [0.1, 0.2, 0.3], - 'profit_abs': [0.2, 0.4, 0.5], - 'trade_duration': [10, 30, 10], - 'wins': [2, 0, 0], - 'draws': [0, 0, 0], - 'losses': [0, 0, 1], - 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] - } - ), 'config': default_conf} - results['TestStrategy2'] = {'results': pd.DataFrame( - { - 'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'], - 'close_date': [date, date + delta, date + delta * 2], - 'profit_ratio': [0.4, 0.2, 0.3], - 'profit_abs': [0.4, 0.4, 0.5], - 'trade_duration': [15, 30, 15], - 'wins': [4, 1, 0], - 'draws': [0, 0, 0], - 'losses': [0, 0, 1], - 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] - } - ), 'config': default_conf} +def test_text_table_strategy(testdatadir): + filename = testdatadir / "backtest-result_multistrat.json" + bt_res_data = load_backtest_stats(filename) + + bt_res_data_comparison = bt_res_data.pop('strategy_comparison') result_str = ( - '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' + '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' - '|---------------+--------+----------------+----------------+------------------+' + '|----------------+--------+----------------+----------------+------------------+' '----------------+----------------+-------------------------+-----------------------|\n' - '| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |' - ' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n' - '| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |' - ' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |' + '| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |' + ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n' + '| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |' + ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |' ) - strategy_results = generate_strategy_comparison(all_results=results) + strategy_results = generate_strategy_comparison(bt_stats=bt_res_data['strategy']) + assert strategy_results == bt_res_data_comparison assert text_table_strategy(strategy_results, 'BTC') == result_str diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 6333266aa..52158a889 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -1,19 +1,22 @@ # pragma pylint: disable=missing-docstring,C0103,protected-access +import logging import time from unittest.mock import MagicMock, PropertyMock +import pandas as pd import pytest import time_machine from freqtrade.constants import AVAILABLE_PAIRLISTS +from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.persistence import Trade from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.resolvers import PairListResolver -from tests.conftest import (create_mock_trades, get_patched_exchange, get_patched_freqtradebot, - log_has, log_has_re) +from tests.conftest import (create_mock_trades_usdt, get_patched_exchange, get_patched_freqtradebot, + log_has, log_has_re, num_log_has) @pytest.fixture(scope="function") @@ -216,6 +219,34 @@ def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog): log_has_re(r"Pair blacklist contains an invalid Wildcard.*", caplog) +def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_conf, caplog): + logger = logging.getLogger(__name__) + freqtrade = get_patched_freqtradebot(mocker, static_pl_conf) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + exchange_has=MagicMock(return_value=True), + markets=PropertyMock(return_value=markets), + ) + freqtrade.pairlists.refresh_pairlist() + whitelist = ['ETH/BTC', 'TKN/BTC'] + caplog.clear() + caplog.set_level(logging.INFO) + + # Ensure all except those in whitelist are removed. + assert set(whitelist) == set(freqtrade.pairlists.whitelist) + assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist + # Ensure that log message wasn't generated. + assert not log_has('Pair BLK/BTC in your blacklist. Removing it from whitelist...', caplog) + + for _ in range(3): + new_whitelist = freqtrade.pairlists.verify_blacklist( + whitelist + ['BLK/BTC'], logger.warning) + # Ensure that the pair is removed from the white list, and properly logged. + assert set(whitelist) == set(new_whitelist) + assert num_log_has('Pair BLK/BTC in your blacklist. Removing it from whitelist...', + caplog) == 1 + + def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf): mocker.patch.multiple( @@ -462,7 +493,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t ohlcv_data = { ('ETH/BTC', '1d'): ohlcv_history, ('TKN/BTC', '1d'): ohlcv_history, - ('LTC/BTC', '1d'): ohlcv_history.append(ohlcv_history), + ('LTC/BTC', '1d'): pd.concat([ohlcv_history, ohlcv_history]), ('XRP/BTC', '1d'): ohlcv_history, ('HOT/BTC', '1d'): ohlcv_history_high_vola, } @@ -535,36 +566,41 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t assert log_has_re(r'^Removed .* from whitelist, because volatility.*$', caplog) -@pytest.mark.parametrize("pairlists,base_currency,volumefilter_result", [ +@pytest.mark.parametrize("pairlists,base_currency,exchange,volumefilter_result", [ # default refresh of 1800 to small for daily candle lookback ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_days": 1}], - "BTC", "default_refresh_too_short"), # OperationalException expected + "BTC", "binance", "default_refresh_too_short"), # OperationalException expected # ambigous configuration with lookback days and period ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_days": 1, "lookback_period": 1}], - "BTC", "lookback_days_and_period"), # OperationalException expected + "BTC", "binance", "lookback_days_and_period"), # OperationalException expected # negative lookback period ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1d", "lookback_period": -1}], - "BTC", "lookback_period_negative"), # OperationalException expected + "BTC", "binance", "lookback_period_negative"), # OperationalException expected # lookback range exceedes exchange limit ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1m", "lookback_period": 2000, "refresh_period": 3600}], - "BTC", 'lookback_exceeds_exchange_request_size'), # OperationalException expected + "BTC", "binance", "lookback_exceeds_exchange_request_size"), # OperationalException expected # expecing pairs as given ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], - "BTC", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), + "BTC", "binance", ['LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC', 'HOT/BTC']), # expecting pairs from default tickers, because 1h candles are not available ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", "lookback_timeframe": "1h", "lookback_period": 2, "refresh_period": 3600}], - "BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + "BTC", "binance", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'HOT/BTC', 'FUEL/BTC']), + # ftx data is already in Quote currency, therefore won't require conversion + ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", + "lookback_timeframe": "1d", "lookback_period": 1, "refresh_period": 86400}], + "BTC", "ftx", ['HOT/BTC', 'LTC/BTC', 'ETH/BTC', 'TKN/BTC', 'XRP/BTC']), ]) def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, - pairlists, base_currency, volumefilter_result, caplog) -> None: + pairlists, base_currency, exchange, volumefilter_result) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency + whitelist_conf['exchange']['name'] = exchange ohlcv_history_high_vola = ohlcv_history.copy() ohlcv_history_high_vola.loc[ohlcv_history_high_vola.index == 1, 'close'] = 0.00090 @@ -573,9 +609,14 @@ def test_VolumePairList_range(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history_medium_volume = ohlcv_history.copy() ohlcv_history_medium_volume.loc[ohlcv_history_medium_volume.index == 2, 'volume'] = 5 - # create candles for high volume with all candles high volume + # create candles for high volume with all candles high volume, but very low price. ohlcv_history_high_volume = ohlcv_history.copy() ohlcv_history_high_volume.loc[:, 'volume'] = 10 + ohlcv_history_high_volume.loc[:, 'low'] = ohlcv_history_high_volume.loc[:, 'low'] * 0.01 + ohlcv_history_high_volume.loc[:, 'high'] = ohlcv_history_high_volume.loc[:, 'high'] * 0.01 + ohlcv_history_high_volume.loc[:, 'close'] = ohlcv_history_high_volume.loc[:, 'close'] * 0.01 + + mocker.patch('freqtrade.exchange.ftx.Ftx.market_is_tradable', return_value=True) ohlcv_data = { ('ETH/BTC', '1d'): ohlcv_history, @@ -657,30 +698,75 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None: assert log_has("PerformanceFilter is not available in this mode.", caplog) -@pytest.mark.usefixtures("init_persistence") -def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee, caplog) -> None: - whitelist_conf['exchange']['pair_whitelist'].append('XRP/BTC') +def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None: whitelist_conf['pairlists'] = [ + {"method": "StaticPairList"}, + {"method": "ShuffleFilter", "seed": 42} + ] + + exchange = get_patched_exchange(mocker, whitelist_conf) + PairListManager(exchange, whitelist_conf) + assert log_has("Backtesting mode detected, applying seed value: 42", caplog) + caplog.clear() + whitelist_conf['runmode'] = RunMode.DRY_RUN + PairListManager(exchange, whitelist_conf) + assert not log_has("Backtesting mode detected, applying seed value: 42", caplog) + assert log_has("Live mode detected, not applying seed.", caplog) + + +@pytest.mark.usefixtures("init_persistence") +def test_PerformanceFilter_lookback(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt['exchange']['pair_whitelist'].extend(['ADA/USDT', 'XRP/USDT', 'ETC/USDT']) + default_conf_usdt['pairlists'] = [ {"method": "StaticPairList"}, {"method": "PerformanceFilter", "minutes": 60, "min_profit": 0.01} ] mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) - exchange = get_patched_exchange(mocker, whitelist_conf) - pm = PairListManager(exchange, whitelist_conf) + exchange = get_patched_exchange(mocker, default_conf_usdt) + pm = PairListManager(exchange, default_conf_usdt) pm.refresh_pairlist() - assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC'] + assert pm.whitelist == ['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'] with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: - create_mock_trades(fee) + create_mock_trades_usdt(fee) pm.refresh_pairlist() - assert pm.whitelist == ['XRP/BTC'] + assert pm.whitelist == ['XRP/USDT'] assert log_has_re(r'Removing pair .* since .* is below .*', caplog) # Move to "outside" of lookback window, so original sorting is restored. t.move_to("2021-09-01 07:00:00 +00:00") pm.refresh_pairlist() - assert pm.whitelist == ['ETH/BTC', 'TKN/BTC', 'XRP/BTC'] + assert pm.whitelist == ['ETH/USDT', 'XRP/USDT', 'NEO/USDT', 'TKN/USDT'] + + +@pytest.mark.usefixtures("init_persistence") +def test_PerformanceFilter_keep_mid_order(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt['exchange']['pair_whitelist'].extend(['ADA/USDT', 'ETC/USDT']) + default_conf_usdt['pairlists'] = [ + {"method": "StaticPairList", "allow_inactive": True}, + {"method": "PerformanceFilter", "minutes": 60, } + ] + mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) + exchange = get_patched_exchange(mocker, default_conf_usdt) + pm = PairListManager(exchange, default_conf_usdt) + pm.refresh_pairlist() + + assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT'] + + with time_machine.travel("2021-09-01 05:00:00 +00:00") as t: + create_mock_trades_usdt(fee) + pm.refresh_pairlist() + assert pm.whitelist == ['XRP/USDT', 'ETC/USDT', 'ETH/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'LTC/USDT'] + # assert log_has_re(r'Removing pair .* since .* is below .*', caplog) + + # Move to "outside" of lookback window, so original sorting is restored. + t.move_to("2021-09-01 07:00:00 +00:00") + pm.refresh_pairlist() + assert pm.whitelist == ['ETH/USDT', 'LTC/USDT', 'XRP/USDT', + 'NEO/USDT', 'TKN/USDT', 'ADA/USDT', 'ETC/USDT'] def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: @@ -1089,34 +1175,35 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf): # Happy path: Descending order, all values filled ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC'], - [{'pair': 'TKN/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], + [{'pair': 'TKN/BTC', 'profit_ratio': 0.05, 'count': 3}, + {'pair': 'ETH/BTC', 'profit_ratio': 0.04, 'count': 2}], ['TKN/BTC', 'ETH/BTC']), # Performance data outside allow list ignored ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC'], - [{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3}, - {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}], + [{'pair': 'OTHER/BTC', 'profit_ratio': 0.05, 'count': 3}, + {'pair': 'ETH/BTC', 'profit_ratio': 0.04, 'count': 2}], ['ETH/BTC', 'TKN/BTC']), # Partial performance data missing and sorted between positive and negative profit ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], - [{'pair': 'ETH/BTC', 'profit': -5, 'count': 100}, - {'pair': 'TKN/BTC', 'profit': 4, 'count': 2}], + [{'pair': 'ETH/BTC', 'profit_ratio': -0.05, 'count': 100}, + {'pair': 'TKN/BTC', 'profit_ratio': 0.04, 'count': 2}], ['TKN/BTC', 'LTC/BTC', 'ETH/BTC']), # Tie in performance data broken by count (ascending) ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], - [{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 101}, - {'pair': 'TKN/BTC', 'profit': -5.01, 'count': 2}, - {'pair': 'ETH/BTC', 'profit': -5.01, 'count': 100}], + [{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 101}, + {'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 2}, + {'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 100}], ['TKN/BTC', 'ETH/BTC', 'LTC/BTC']), - # Tie in performance and count, broken by alphabetical sort + # Tie in performance and count, broken by prior sorting sort ([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}], ['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], - [{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 1}, - {'pair': 'TKN/BTC', 'profit': -5.01, 'count': 1}, - {'pair': 'ETH/BTC', 'profit': -5.01, 'count': 1}], - ['ETH/BTC', 'LTC/BTC', 'TKN/BTC']), + [{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 1}, + {'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 1}, + {'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 1}], + ['ETH/BTC', 'TKN/BTC', 'LTC/BTC']), ]) def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance, allowlist_result, tickers, markets, ohlcv_history_list): diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 2fe5d4a56..c87cea259 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -148,10 +148,13 @@ def test_fiat_multiple_coins(mocker, caplog): {'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'}, {'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'}, {'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'}, + {'id': 'ethereum', 'symbol': 'eth', 'name': 'Ethereum'}, + {'id': 'ethereum-wormhole', 'symbol': 'eth', 'name': 'Ethereum Wormhole'}, ] assert fiat_convert._get_gekko_id('btc') == 'bitcoin' assert fiat_convert._get_gekko_id('hnt') is None + assert fiat_convert._get_gekko_id('eth') == 'ethereum' assert log_has('Found multiple mappings in goingekko for hnt.', caplog) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 2852ada81..d738760be 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -11,6 +11,7 @@ from freqtrade.edge import PairInfo from freqtrade.enums import State from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError from freqtrade.persistence import Trade +from freqtrade.persistence.models import Order from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter @@ -78,7 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': 1.099e-05, 'amount': 91.07468123, - 'amount_requested': 91.07468123, + 'amount_requested': 91.07468124, 'stake_amount': 0.001, 'trade_duration': None, 'trade_duration_s': None, @@ -108,6 +109,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'orders': [{ + 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, + 'remaining': ANY, 'status': ANY}], } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -145,7 +153,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': ANY, 'amount': 91.07468123, - 'amount_requested': 91.07468123, + 'amount_requested': 91.07468124, 'trade_duration': ANY, 'trade_duration_s': ANY, 'stake_amount': 0.001, @@ -175,6 +183,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', + 'orders': [{ + 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, + 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', + 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, + 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, + 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, + 'remaining': ANY, 'status': ANY}], } @@ -214,11 +229,21 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') assert "Since" in headers assert "Pair" in headers + assert len(result[0]) == 4 assert 'instantly' == result[0][2] assert 'ETH/BTC' in result[0][1] assert '-0.41% (-0.06)' == result[0][3] assert '-0.06' == f'{fiat_profit_sum:.2f}' + rpc._config['position_adjustment_enable'] = True + rpc._config['max_entry_position_adjustment'] = 3 + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + assert "# Entries" in headers + assert len(result[0]) == 5 + # 4th column should be 1/4 - as 1 order filled (a total of 4 is possible) + # 3 on top of the initial one. + assert result[0][4] == '1/4' + mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') @@ -251,8 +276,10 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee, assert trade # Simulate buy & sell - trade.update(limit_buy_order) - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -389,28 +416,32 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'sell') + trade.update_trade(oobj) # Update the ticker with a market going up mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up ) - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Update the ticker with a market going up mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up ) - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -424,7 +455,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, assert stats['trade_count'] == 2 assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' - assert stats['avg_duration'] in ('0:00:00', '0:00:01') + assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02') assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) @@ -435,7 +466,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee, assert stats['trade_count'] == 2 assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' - assert stats['avg_duration'] in ('0:00:00', '0:00:01') + assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02') assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) assert isnan(stats['profit_all_coin']) @@ -469,14 +500,16 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Update the ticker with a market going up mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker_sell_up, get_fee=fee ) - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -572,8 +605,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12.309096315) - assert prec_satoshi(result['value'], 184636.44472997) + assert prec_satoshi(result['total'], 12.30909624) + assert prec_satoshi(result['value'], 184636.443606915) assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] @@ -591,17 +624,16 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): 'est_stake': 0.30794, 'used': 4.0, 'stake': 'BTC', - }, {'free': 5.0, 'balance': 10.0, 'currency': 'USDT', - 'est_stake': 0.0011563153318162476, + 'est_stake': 0.0011562404610161968, 'used': 5.0, 'stake': 'BTC', } ] - assert result['total'] == 12.309096315331816 + assert result['total'] == 12.309096240461017 def test_rpc_start(mocker, default_conf) -> None: @@ -728,13 +760,13 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: mocker.patch( 'freqtrade.exchange.Exchange.fetch_order', side_effect=[{ - 'id': '1234', + 'id': trade.orders[0].order_id, 'status': 'open', 'type': 'limit', 'side': 'buy', 'filled': filled_amount }, { - 'id': '1234', + 'id': trade.orders[0].order_id, 'status': 'closed', 'type': 'limit', 'side': 'buy', @@ -814,10 +846,12 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -848,10 +882,12 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -920,10 +956,12 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -992,10 +1030,12 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -1093,7 +1133,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'): rpc._rpc_forcebuy(pair, 0.0001) pair = 'XRP/BTC' - trade = rpc._rpc_forcebuy(pair, 0.0001) + trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit') assert isinstance(trade, Trade) assert trade.pair == pair assert trade.open_rate == 0.0001 @@ -1102,9 +1142,14 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> with pytest.raises(RPCException, match=r'Wrong pair selected. Only pairs with stake-currency.*'): rpc._rpc_forcebuy('LTC/ETH', 0.0001) - pair = 'XRP/BTC' + + # Test with defined stake_amount + pair = 'LTC/BTC' + trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) + assert trade.stake_amount == 0.05 # Test not buying + pair = 'XRP/BTC' freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot.config['stake_amount'] = 0 patch_get_signal(freqtradebot) @@ -1225,6 +1270,16 @@ def test_rpc_blacklist(mocker, default_conf) -> None: assert 'errors' in ret assert isinstance(ret['errors'], dict) + ret = rpc._rpc_blacklist_delete(["DOGE/BTC", 'HOT/BTC']) + + assert 'StaticPairList' in ret['method'] + assert len(ret['blacklist']) == 2 + assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] + assert ret['blacklist'] == ['ETH/BTC', 'XRP/.*'] + assert ret['blacklist_expanded'] == ['ETH/BTC', 'XRP/BTC', 'XRP/USDT'] + assert 'errors' in ret + assert isinstance(ret['errors'], dict) + def test_rpc_edge_disabled(mocker, default_conf) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) @@ -1251,3 +1306,13 @@ def test_rpc_edge_enabled(mocker, edge_conf) -> None: assert ret[0]['Winrate'] == 0.66 assert ret[0]['Expectancy'] == 1.71 assert ret[0]['Stoploss'] == -0.02 + + +def test_rpc_health(mocker, default_conf) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(freqtradebot) + result = rpc._health() + assert result['last_process'] == '1970-01-01 00:00:00+00:00' + assert result['last_process_ts'] == 0 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 074e312d9..84a18440e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -7,6 +7,7 @@ from datetime import datetime, timedelta, timezone from pathlib import Path from unittest.mock import ANY, MagicMock, PropertyMock +import pandas as pd import pytest import uvicorn from fastapi import FastAPI @@ -533,11 +534,14 @@ def test_api_show_config(botclient): assert rc.json()['timeframe_min'] == 5 assert rc.json()['state'] == 'running' assert rc.json()['bot_name'] == 'freqtrade' + assert rc.json()['strategy_version'] is None assert not rc.json()['trailing_stop'] assert 'bid_strategy' in rc.json() assert 'ask_strategy' in rc.json() assert 'unfilledtimeout' in rc.json() assert 'version' in rc.json() + assert 'api_version' in rc.json() + assert 1.1 <= rc.json()['api_version'] <= 1.2 def test_api_daily(botclient, mocker, ticker, fee, markets): @@ -898,6 +902,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'orders': [ANY], + } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -952,6 +958,38 @@ def test_api_blacklist(botclient, mocker): "errors": {}, } + rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC") + assert_response(rc) + assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], + "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], + "length": 3, + "method": ["StaticPairList"], + "errors": {}, + } + + rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=NOTHING/BTC") + assert_response(rc) + assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], + "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], + "length": 3, + "method": ["StaticPairList"], + "errors": { + "NOTHING/BTC": { + "error_msg": "Pair NOTHING/BTC is not in the current blacklist." + } + }, + } + rc = client_delete( + client, + f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC") + assert_response(rc) + assert rc.json() == {"blacklist": ["XRP/.*"], + "blacklist_expanded": ["XRP/BTC", "XRP/USDT"], + "length": 1, + "method": ["StaticPairList"], + "errors": {}, + } + def test_api_whitelist(botclient): ftbot, client = botclient @@ -1053,6 +1091,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'orders': [], } @@ -1072,6 +1111,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): data='{"tradeid": "1"}') assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/forcesell: invalid argument"} + Trade.query.session.rollback() ftbot.enter_positions() @@ -1146,6 +1186,24 @@ def test_api_pair_candles(botclient, ohlcv_history): 0.7039405, 8.885e-05, 0, 0, 1511686800000, None, None] ]) + ohlcv_history['sell'] = ohlcv_history['sell'].astype('float64') + ohlcv_history.at[0, 'sell'] = float('inf') + ohlcv_history['date1'] = ohlcv_history['date'] + ohlcv_history.at[0, 'date1'] = pd.NaT + + ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history) + rc = client_get(client, + f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") + assert_response(rc) + assert (rc.json()['data'] == + [['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869, + None, 0, None, None, 1511686200000, None, None], + ['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05, + 8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, '2017-11-26 08:55:00', + 1511686500000, 8.893e-05, None], + ['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, + 0.7039405, 8.885e-05, 0, 0.0, '2017-11-26 09:00:00', 1511686800000, None, None] + ]) def test_api_pair_history(botclient, ohlcv_history): @@ -1291,10 +1349,15 @@ def test_sysinfo(botclient): assert 'ram_pct' in result -def test_api_backtesting(botclient, mocker, fee, caplog): +def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): ftbot, client = botclient mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + rc = client_get(client, f"{BASE_URI}/backtest") + # Backtest prevented in default mode + assert_response(rc, 502) + + ftbot.config['runmode'] = RunMode.WEBSERVER # Backtesting not started yet rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) @@ -1312,6 +1375,11 @@ def test_api_backtesting(botclient, mocker, fee, caplog): assert result['status'] == 'reset' assert not result['running'] assert result['status_msg'] == 'Backtest reset' + ftbot.config['export'] = 'trades' + ftbot.config['backtest_cache'] = 'none' + ftbot.config['user_data_dir'] = Path(tmpdir) + ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results" + ftbot.config['exportfilename'].mkdir() # start backtesting data = { @@ -1386,6 +1454,14 @@ def test_api_backtesting(botclient, mocker, fee, caplog): rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) assert log_has("Backtesting caused an error: ", caplog) + ftbot.config['backtest_cache'] = 'day' + + # Rerun backtest (should get previous result) + rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) + assert_response(rc) + result = rc.json() + assert log_has_re('Reusing result of previous backtest.*', caplog) + # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) @@ -1394,3 +1470,14 @@ def test_api_backtesting(botclient, mocker, fee, caplog): assert result['status'] == 'reset' assert not result['running'] assert result['status_msg'] == 'Backtest reset' + + +def test_health(botclient): + ftbot, client = botclient + + rc = client_get(client, f"{BASE_URI}/health") + + assert_response(rc) + ret = rc.json() + assert ret['last_process_ts'] == 0 + assert ret['last_process'] == '1970-01-01T00:00:00+00:00' diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 1247affae..f53f48cc2 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -4,7 +4,7 @@ import logging import re -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from functools import reduce from random import choice, randint from string import ascii_uppercase @@ -23,7 +23,9 @@ from freqtrade.exceptions import OperationalException from freqtrade.freqtradebot import FreqtradeBot from freqtrade.loggers import setup_logging from freqtrade.persistence import PairLocks, Trade +from freqtrade.persistence.models import Order from freqtrade.rpc import RPC +from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.telegram import Telegram, authorized_only from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist) @@ -97,8 +99,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['unlock', 'delete_locks'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " - "['stopbuy'], ['whitelist'], ['blacklist'], " - "['logs'], ['edge'], ['help'], ['version']" + "['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], " + "['logs'], ['edge'], ['health'], ['help'], ['version']" "]") assert log_has(message_str, caplog) @@ -200,7 +202,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'stoploss_current_dist_ratio': -0.0002, 'stop_loss_ratio': -0.0001, 'open_order': '(limit buy rem=0.00000000)', - 'is_open': True + 'is_open': True, + 'orders': [] }]), ) @@ -216,6 +219,82 @@ def test_telegram_status(default_conf, update, mocker) -> None: assert status_table.call_count == 1 +@pytest.mark.usefixtures("init_persistence") +def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: + update.message.chat.id = "123" + default_conf['telegram']['enabled'] = False + default_conf['telegram']['chat_id'] = "123" + default_conf['position_adjustment_enable'] = True + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_order=MagicMock(return_value=None), + get_rate=MagicMock(return_value=0.22), + ) + + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + + create_mock_trades(fee) + trades = Trade.get_open_trades() + trade = trades[0] + # Average may be empty on some exchanges + trade.orders[0].average = 0 + trade.orders.append(Order( + order_id='5412vbb', + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate * 0.95, + average=0, + filled=trade.amount, + remaining=0, + cost=trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + ) + trade.recalc_trade_from_orders() + Trade.commit() + + telegram._status(update=update, context=MagicMock()) + assert msg_mock.call_count == 4 + msg = msg_mock.call_args_list[0][0][0] + assert re.search(r'Number of Entries.*2', msg) + assert re.search(r'Average Entry Price', msg) + assert re.search(r'Order filled at', msg) + assert re.search(r'Close Date:', msg) is None + assert re.search(r'Close Profit:', msg) is None + + +@pytest.mark.usefixtures("init_persistence") +def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None: + update.message.chat.id = "123" + default_conf['telegram']['enabled'] = False + default_conf['telegram']['chat_id'] = "123" + default_conf['position_adjustment_enable'] = True + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_order=MagicMock(return_value=None), + get_rate=MagicMock(return_value=0.22), + ) + + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + + create_mock_trades(fee) + trades = Trade.get_trades([Trade.is_open.is_(False)]) + trade = trades[0] + context = MagicMock() + context.args = [str(trade.id)] + telegram._status(update=update, context=context) + assert msg_mock.call_count == 1 + msg = msg_mock.call_args_list[0][0][0] + assert re.search(r'Close Date:', msg) + assert re.search(r'Close Profit:', msg) + + def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: default_conf['max_open_trades'] = 3 mocker.patch.multiple( @@ -341,10 +420,12 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -384,8 +465,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee, trades = Trade.query.all() for trade in trades: - trade.update(limit_buy_order) - trade.update(limit_sell_order) + trade.update_trade(oobj) + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -450,10 +531,12 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -497,8 +580,8 @@ def test_weekly_handle(default_conf, update, ticker, limit_buy_order, fee, trades = Trade.query.all() for trade in trades: - trade.update(limit_buy_order) - trade.update(limit_sell_order) + trade.update_trade(oobj) + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -566,10 +649,12 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobjs = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -583,7 +668,7 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee, assert 'Monthly Profit over the last 2 months:' in msg_mock.call_args_list[0][0][0] assert 'Month ' in msg_mock.call_args_list[0][0][0] today = datetime.utcnow().date() - current_month = f"{today.year}-{today.month} " + current_month = f"{today.year}-{today.month:02} " assert current_month in msg_mock.call_args_list[0][0][0] assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] @@ -613,8 +698,8 @@ def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee, trades = Trade.query.all() for trade in trades: - trade.update(limit_buy_order) - trade.update(limit_sell_order) + trade.update_trade(oobj) + trade.update_trade(oobjs) trade.close_date = datetime.utcnow() trade.is_open = False @@ -684,7 +769,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, trade = Trade.query.first() # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) + context = MagicMock() # Test with invalid 2nd argument (should silently pass) context.args = ["aaa"] @@ -693,18 +780,22 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee, assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=0.01) - assert ('∙ `-0.00000500 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`' + assert ('∙ `-0.000005 BTC (-0.50%) (-0.0 \N{GREEK CAPITAL LETTER SIGMA}%)`' in msg_mock.call_args_list[-1][0][0]) msg_mock.reset_mock() # Update the ticker with a market going up mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) - trade.update(limit_sell_order) + # Simulate fulfilled LIMIT_SELL order for trade + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) - trade.close_date = datetime.utcnow() + trade.close_date = datetime.now(timezone.utc) trade.is_open = False + Trade.commit() - telegram._profit(update=update, context=MagicMock()) + context.args = [3] + telegram._profit(update=update, context=context) assert msg_mock.call_count == 1 assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] assert ('∙ `0.00006217 BTC (6.20%) (0.62 \N{GREEK CAPITAL LETTER SIGMA}%)`' @@ -766,7 +857,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick assert '*XRP:*' not in result assert 'Balance:' in result assert 'Est. BTC:' in result - assert 'BTC: 12.00000000' in result + assert 'BTC: 12' in result assert "*3 Other Currencies (< 0.0001 BTC):*" in result assert 'BTC: 0.00000309' in result @@ -782,7 +873,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None: telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 - assert 'All balances are zero.' in result + assert 'Starting capital: `0 BTC' in result def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: @@ -795,7 +886,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 assert "*Warning:* Simulated balances in Dry Mode." in result - assert "Starting capital: `1000` BTC" in result + assert "Starting capital: `1000 BTC`" in result def test_balance_handle_too_large_response(default_conf, update, mocker) -> None: @@ -936,7 +1027,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, telegram._forcesell(update=update, context=context) assert msg_mock.call_count == 4 - last_msg = msg_mock.call_args_list[-1][0][0] + last_msg = msg_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -951,6 +1042,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, 'profit_amount': 6.314e-05, 'profit_ratio': 0.0629778, 'stake_currency': 'BTC', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, @@ -1000,7 +1092,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, assert msg_mock.call_count == 4 - last_msg = msg_mock.call_args_list[-1][0][0] + last_msg = msg_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -1015,6 +1107,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, 'profit_amount': -5.497e-05, 'profit_ratio': -0.05482878, 'stake_currency': 'BTC', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, @@ -1054,7 +1147,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None # Called for each trade 2 times assert msg_mock.call_count == 8 - msg = msg_mock.call_args_list[1][0][0] + msg = msg_mock.call_args_list[0][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -1069,6 +1162,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'profit_amount': -4.09e-06, 'profit_ratio': -0.00408133, 'stake_currency': 'BTC', + 'base_currency': 'ETH', 'fiat_currency': 'USD', 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, @@ -1178,7 +1272,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?' # assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy' keyboard = msg_mock.call_args_list[0][1]['keyboard'] - assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4 + # One additional button - cancel + assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 update = MagicMock() update.callback_query = MagicMock() update.callback_query.data = 'XRP/USDT' @@ -1186,8 +1281,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None: assert fbuy_mock.call_count == 1 -def test_performance_handle(default_conf, update, ticker, fee, - limit_buy_order, limit_sell_order, mocker) -> None: +def test_telegram_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -1203,10 +1298,12 @@ def test_performance_handle(default_conf, update, ticker, fee, assert trade # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False @@ -1216,8 +1313,8 @@ def test_performance_handle(default_conf, update, ticker, fee, assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] -def test_buy_tag_performance_handle(default_conf, update, ticker, fee, - limit_buy_order, limit_sell_order, mocker) -> None: +def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, @@ -1230,25 +1327,39 @@ def test_buy_tag_performance_handle(default_conf, update, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() assert trade + trade.buy_tag = "TESTBUY" # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) - trade.buy_tag = "TESTBUY" # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False - - telegram._buy_tag_performance(update=update, context=MagicMock()) + context = MagicMock() + telegram._buy_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'TESTBUY\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + context.args = [trade.pair] + telegram._buy_tag_performance(update=update, context=context) + assert msg_mock.call_count == 2 -def test_sell_reason_performance_handle(default_conf, update, ticker, fee, - limit_buy_order, limit_sell_order, mocker) -> None: + msg_mock.reset_mock() + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_buy_tag_performance', + side_effect=RPCException('Error')) + telegram._buy_tag_performance(update=update, context=MagicMock()) + + assert msg_mock.call_count == 1 + assert "Error" in msg_mock.call_args_list[0][0][0] + + +def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, @@ -1261,25 +1372,38 @@ def test_sell_reason_performance_handle(default_conf, update, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() assert trade - - # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) - trade.sell_reason = 'TESTSELL' + # Simulate fulfilled LIMIT_BUY order for trade + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) + # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False - - telegram._sell_reason_performance(update=update, context=MagicMock()) + context = MagicMock() + telegram._sell_reason_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0] assert 'TESTSELL\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + context.args = [trade.pair] + + telegram._sell_reason_performance(update=update, context=context) + assert msg_mock.call_count == 2 + + msg_mock.reset_mock() + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_sell_reason_performance', + side_effect=RPCException('Error')) + telegram._sell_reason_performance(update=update, context=MagicMock()) + + assert msg_mock.call_count == 1 + assert "Error" in msg_mock.call_args_list[0][0][0] -def test_mix_tag_performance_handle(default_conf, update, ticker, fee, - limit_buy_order, limit_sell_order, mocker) -> None: +def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: mocker.patch.multiple( 'freqtrade.exchange.Exchange', fetch_ticker=ticker, @@ -1292,25 +1416,39 @@ def test_mix_tag_performance_handle(default_conf, update, ticker, fee, freqtradebot.enter_positions() trade = Trade.query.first() assert trade - - # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order) - trade.buy_tag = "TESTBUY" trade.sell_reason = "TESTSELL" + # Simulate fulfilled LIMIT_BUY order for trade + oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') + trade.update_trade(oobj) + # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order) + oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell') + trade.update_trade(oobj) trade.close_date = datetime.utcnow() trade.is_open = False - telegram._mix_tag_performance(update=update, context=MagicMock()) + context = MagicMock() + telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] assert ('TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0]) + context.args = [trade.pair] + telegram._mix_tag_performance(update=update, context=context) + assert msg_mock.call_count == 2 + + msg_mock.reset_mock() + mocker.patch('freqtrade.rpc.rpc.RPC._rpc_mix_tag_performance', + side_effect=RPCException('Error')) + telegram._mix_tag_performance(update=update, context=MagicMock()) + + assert msg_mock.call_count == 1 + assert "Error" in msg_mock.call_args_list[0][0][0] + def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( @@ -1432,6 +1570,13 @@ def test_blacklist_static(default_conf, update, mocker) -> None: in msg_mock.call_args_list[0][0][0]) assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"] + msg_mock.reset_mock() + context.args = ["DOGE/BTC"] + telegram._blacklist_delete(update=update, context=context) + assert msg_mock.call_count == 1 + assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`" + in msg_mock.call_args_list[0][0][0]) + def test_telegram_logs(default_conf, update, mocker) -> None: mocker.patch.multiple( @@ -1559,12 +1704,20 @@ def test_help_handle(default_conf, update, mocker) -> None: def test_version_handle(default_conf, update, mocker) -> None: - telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram._version(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + msg_mock.reset_mock() + freqtradebot.strategy.version = lambda: '1.1.1' + + telegram._version(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0] + def test_show_config_handle(default_conf, update, mocker) -> None: @@ -1599,7 +1752,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: 'pair': 'ETH/BTC', 'limit': 1.099e-05, 'order_type': 'limit', - 'stake_amount': 0.001, + 'stake_amount': 0.01465333, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': 'USD', @@ -1616,7 +1769,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ '*Current Rate:* `0.00001099`\n' \ - '*Total:* `(0.00100000 BTC, 12.345 USD)`' + '*Total:* `(0.01465333 BTC, 180.895 USD)`' freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'} caplog.clear() @@ -1690,7 +1843,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: 'buy_tag': 'buy_signal_01', 'exchange': 'Binance', 'pair': 'ETH/BTC', - 'stake_amount': 0.001, + 'stake_amount': 0.01465333, # 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': 'USD', @@ -1704,7 +1857,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: '*Buy Tag:* `buy_signal_01`\n' \ '*Amount:* `1333.33333333`\n' \ '*Open Rate:* `0.00001099`\n' \ - '*Total:* `(0.00100000 BTC, 12.345 USD)`' + '*Total:* `(0.01465333 BTC, 180.895 USD)`' def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1843,6 +1996,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None: '*Sell Reason:* `stop_loss`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Amount:* `1333.33333333`\n' + '*Open Rate:* `0.00007500`\n' '*Close Rate:* `0.00003201`' ) @@ -1895,7 +2049,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: 'pair': 'ETH/BTC', 'limit': 1.099e-05, 'order_type': 'limit', - 'stake_amount': 0.001, + 'stake_amount': 0.01465333, 'stake_amount_fiat': 0.0, 'stake_currency': 'BTC', 'fiat_currency': None, @@ -1908,7 +2062,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: '*Amount:* `1333.33333333`\n' '*Open Rate:* `0.00001099`\n' '*Current Rate:* `0.00001099`\n' - '*Total:* `(0.00100000 BTC)`') + '*Total:* `(0.01465333 BTC)`') def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index 04e63a3be..17d1baca9 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -292,3 +292,15 @@ def test__send_msg_with_json_format(default_conf, mocker, caplog): webhook._send_msg(msg) assert post.call_args[1] == {'json': msg} + + +def test__send_msg_with_raw_format(default_conf, mocker, caplog): + default_conf["webhook"] = get_webhook_dict() + default_conf["webhook"]["format"] = "raw" + webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf) + msg = {'data': 'Hello'} + post = MagicMock() + mocker.patch("freqtrade.rpc.webhook.post", post) + webhook._send_msg(msg) + + assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}} diff --git a/tests/strategy/strats/informative_decorator_strategy.py b/tests/strategy/strats/informative_decorator_strategy.py index a32ad79e8..17d4df018 100644 --- a/tests/strategy/strats/informative_decorator_strategy.py +++ b/tests/strategy/strats/informative_decorator_strategy.py @@ -20,7 +20,7 @@ class InformativeDecoratorTest(IStrategy): startup_candle_count: int = 20 def informative_pairs(self): - return [('BTC/USDT', '5m')] + return [('NEO/USDT', '5m')] def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['buy'] = 0 @@ -38,8 +38,8 @@ class InformativeDecoratorTest(IStrategy): return dataframe # Simple informative test. - @informative('1h', 'BTC/{stake}') - def populate_indicators_btc_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + @informative('1h', 'NEO/{stake}') + def populate_indicators_neo_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['rsi'] = 14 return dataframe @@ -50,7 +50,7 @@ class InformativeDecoratorTest(IStrategy): return dataframe # Formatting test. - @informative('30m', 'BTC/{stake}', '{column}_{BASE}_{QUOTE}_{base}_{quote}_{asset}_{timeframe}') + @informative('30m', 'NEO/{stake}', '{column}_{BASE}_{QUOTE}_{base}_{quote}_{asset}_{timeframe}') def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe['rsi'] = 14 return dataframe @@ -68,7 +68,7 @@ class InformativeDecoratorTest(IStrategy): dataframe['rsi_less'] = dataframe['rsi'] < dataframe['rsi_1h'] # Mixing manual informative pairs with decorators. - informative = self.dp.get_pair_dataframe('BTC/USDT', '5m') + informative = self.dp.get_pair_dataframe('NEO/USDT', '5m') informative['rsi'] = 14 dataframe = merge_informative_pair(dataframe, informative, self.timeframe, '5m', ffill=True) diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index 53e39526f..c57becdad 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -1,9 +1,12 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +from datetime import datetime + import talib.abstract as ta from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib +from freqtrade.persistence import Trade from freqtrade.strategy.interface import IStrategy @@ -48,6 +51,9 @@ class StrategyTestV2(IStrategy): 'sell': 'gtc', } + # By default this strategy does not use Position Adjustments + position_adjustment_enable = False + def informative_pairs(self): """ Define additional, informative pair/interval combinations to be cached from the exchange. @@ -154,3 +160,12 @@ class StrategyTestV2(IStrategy): ), 'sell'] = 1 return dataframe + + def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, + current_profit: float, min_stake: float, max_stake: float, **kwargs): + + if current_profit < -0.0075: + orders = trade.select_filled_orders('buy') + return round(orders[0].cost, 0) + + return None diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 6426ebe5f..a2d7df720 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -37,7 +37,7 @@ def test_strategy_test_v2(result, fee): assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', - current_time=datetime.utcnow()) is True + current_time=datetime.utcnow(), entry_tag=None) is True assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', sell_reason='roi', current_time=datetime.utcnow()) is True diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index f57a9f34e..174ce95c6 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -36,6 +36,10 @@ def test_returns_latest_signal(ohlcv_history): mocked_history = ohlcv_history.copy() mocked_history['sell'] = 0 mocked_history['buy'] = 0 + # Set tags in lines that don't matter to test nan in the sell line + mocked_history.loc[0, 'buy_tag'] = 'wrong_line' + mocked_history.loc[0, 'exit_tag'] = 'wrong_line' + mocked_history.loc[1, 'sell'] = 1 assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None, None) @@ -433,7 +437,8 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili strategy.custom_stoploss = custom_stop now = arrow.utcnow().datetime - sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit), trade=trade, + current_rate = trade.open_rate * (1 + profit) + sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade, current_time=now, current_profit=profit, force_stoploss=0, high=None) assert isinstance(sl_flag, SellCheckTuple) @@ -443,8 +448,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili else: assert sl_flag.sell_flag is True assert round(trade.stop_loss, 2) == adjusted + current_rate2 = trade.open_rate * (1 + profit2) - sl_flag = strategy.stop_loss_reached(current_rate=trade.open_rate * (1 + profit2), trade=trade, + sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade, current_time=now, current_profit=profit2, force_stoploss=0, high=None) assert sl_flag.sell_type == expected2 diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index cb7cf97a1..9e546869a 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -7,6 +7,7 @@ import pytest from freqtrade.data.dataprovider import DataProvider from freqtrade.strategy import (merge_informative_pair, stoploss_from_absolute, stoploss_from_open, timeframe_to_minutes) +from tests.conftest import get_patched_exchange def generate_test_data(timeframe: str, size: int, start: str = '2020-07-05'): @@ -155,9 +156,9 @@ def test_informative_decorator(mocker, default_conf): ('LTC/USDT', '5m'): test_data_5m, ('LTC/USDT', '30m'): test_data_30m, ('LTC/USDT', '1h'): test_data_1h, - ('BTC/USDT', '30m'): test_data_30m, - ('BTC/USDT', '5m'): test_data_5m, - ('BTC/USDT', '1h'): test_data_1h, + ('NEO/USDT', '30m'): test_data_30m, + ('NEO/USDT', '5m'): test_data_5m, + ('NEO/USDT', '1h'): test_data_1h, ('ETH/USDT', '1h'): test_data_1h, ('ETH/USDT', '30m'): test_data_30m, ('ETH/BTC', '1h'): test_data_1h, @@ -165,15 +166,16 @@ def test_informative_decorator(mocker, default_conf): from .strats.informative_decorator_strategy import InformativeDecoratorTest default_conf['stake_currency'] = 'USDT' strategy = InformativeDecoratorTest(config=default_conf) - strategy.dp = DataProvider({}, None, None) + exchange = get_patched_exchange(mocker, default_conf) + strategy.dp = DataProvider({}, exchange, None) mocker.patch.object(strategy.dp, 'current_whitelist', return_value=[ - 'XRP/USDT', 'LTC/USDT', 'BTC/USDT' + 'XRP/USDT', 'LTC/USDT', 'NEO/USDT' ]) assert len(strategy._ft_informative) == 6 # Equal to number of decorators used informative_pairs = [('XRP/USDT', '1h'), ('LTC/USDT', '1h'), ('XRP/USDT', '30m'), - ('LTC/USDT', '30m'), ('BTC/USDT', '1h'), ('BTC/USDT', '30m'), - ('BTC/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')] + ('LTC/USDT', '30m'), ('NEO/USDT', '1h'), ('NEO/USDT', '30m'), + ('NEO/USDT', '5m'), ('ETH/BTC', '1h'), ('ETH/USDT', '30m')] for inf_pair in informative_pairs: assert inf_pair in strategy.gather_informative_pairs() @@ -186,8 +188,8 @@ def test_informative_decorator(mocker, default_conf): {p: data[(p, strategy.timeframe)] for p in ('XRP/USDT', 'LTC/USDT')}) expected_columns = [ 'rsi_1h', 'rsi_30m', # Stacked informative decorators - 'btc_usdt_rsi_1h', # BTC 1h informative - 'rsi_BTC_USDT_btc_usdt_BTC/USDT_30m', # Column formatting + 'neo_usdt_rsi_1h', # NEO 1h informative + 'rsi_NEO_USDT_neo_usdt_NEO/USDT_30m', # Column formatting 'rsi_from_callable', # Custom column formatter 'eth_btc_rsi_1h', # Quote currency not matching stake currency 'rsi', 'rsi_less', # Non-informative columns diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 57add66bf..0a6935649 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -22,7 +22,7 @@ from freqtrade.configuration.load_config import load_config_file, load_file, log from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException -from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre +from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre from tests.conftest import log_has, log_has_re, patched_configuration_load_config_file @@ -686,7 +686,7 @@ def test_set_loggers_syslog(): assert len(logger.handlers) == 3 assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] - assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + assert [x for x in logger.handlers if type(x) == FTBufferingHandler] # setting up logging again should NOT cause the loggers to be added a second time. setup_logging(config) assert len(logger.handlers) == 3 @@ -709,7 +709,7 @@ def test_set_loggers_Filehandler(tmpdir): assert len(logger.handlers) == 3 assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler] assert [x for x in logger.handlers if type(x) == logging.StreamHandler] - assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler] + assert [x for x in logger.handlers if type(x) == FTBufferingHandler] # setting up logging again should NOT cause the loggers to be added a second time. setup_logging(config) assert len(logger.handlers) == 3 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e5dae5461..1aeb56cdd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5,7 +5,8 @@ import logging import time from copy import deepcopy from math import isclose -from unittest.mock import ANY, MagicMock, PropertyMock +from typing import List +from unittest.mock import ANY, MagicMock, PropertyMock, patch import arrow import pytest @@ -226,7 +227,8 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker, freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() trade = Trade.query.first() - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) ############################################# # stoploss shoud be hit @@ -291,7 +293,8 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, fee, assert trade.exchange == 'binance' # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.open_rate == 2.0 assert trade.amount == 30.0 @@ -724,7 +727,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt, call_args = buy_mm.call_args_list[0][1] assert call_args['pair'] == pair assert call_args['rate'] == bid - assert call_args['amount'] == round(stake_amount / bid, 8) + assert call_args['amount'] == stake_amount / bid buy_rate_mock.reset_mock() # Should create an open trade with an open order id @@ -745,7 +748,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt, call_args = buy_mm.call_args_list[1][1] assert call_args['pair'] == pair assert call_args['rate'] == fix_price - assert call_args['amount'] == round(stake_amount / fix_price, 8) + assert call_args['amount'] == stake_amount / fix_price # In case of closed order limit_buy_order_usdt['status'] = 'closed' @@ -923,12 +926,10 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, }), create_order=MagicMock(side_effect=[ {'id': limit_buy_order_usdt['id']}, - {'id': limit_sell_order_usdt['id']}, + limit_sell_order_usdt, + # {'id': limit_sell_order_usdt['id']}, ]), get_fee=fee, - ) - mocker.patch.multiple( - 'freqtrade.exchange.Binance', stoploss=stoploss ) freqtrade = FreqtradeBot(default_conf_usdt) @@ -953,7 +954,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.stoploss_order_id = 100 hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', hanging_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == 100 @@ -966,7 +967,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.stoploss_order_id = 100 canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', canceled_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order) stoploss.reset_mock() assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -981,18 +982,24 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade = Trade.query.first() trade.is_open = True trade.open_order_id = None - trade.stoploss_order_id = 100 + trade.stoploss_order_id = "100" + trade.orders.append(Order( + ft_order_side='stoploss', + order_id='100', + ft_pair=trade.pair, + ft_is_open=True, + )) assert trade stoploss_order_hit = MagicMock(return_value={ - 'id': 100, + 'id': "100", 'status': 'closed', 'type': 'stop_loss_limit', 'price': 3, 'average': 2, 'amount': limit_buy_order_usdt['amount'], }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) assert trade.stoploss_order_id is None @@ -1000,7 +1007,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, caplog.clear() mocker.patch( - 'freqtrade.exchange.Binance.stoploss', + 'freqtrade.exchange.Exchange.stoploss', side_effect=ExchangeError() ) trade.is_open = True @@ -1012,9 +1019,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, # It should try to add stoploss order trade.stoploss_order_id = 100 stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1024,10 +1031,37 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.is_open = False stoploss.reset_mock() mocker.patch('freqtrade.exchange.Exchange.fetch_order') - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 0 + # Seventh case: emergency exit triggered + # Trailing stop should not act anymore + stoploss_order_cancelled = MagicMock(side_effect=[{ + 'id': "100", + 'status': 'canceled', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'amount': limit_buy_order_usdt['amount'], + 'info': {'stopPrice': 22}, + }]) + trade.stoploss_order_id = 100 + trade.is_open = True + trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime + trade.stop_loss = 24 + freqtrade.config['trailing_stop'] = True + stoploss = MagicMock(side_effect=InvalidOrderException()) + + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', + side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stoploss_order_id is None + assert trade.is_open is False + assert trade.sell_reason == str(SellType.EMERGENCY_SELL) + def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt) -> None: @@ -1257,7 +1291,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee, cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') stoploss_order_mock.assert_called_once_with( - amount=27.39726027, + amount=pytest.approx(27.39726027), pair='ETH/USDT', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.95 @@ -1351,6 +1385,32 @@ def test_handle_stoploss_on_exchange_trailing_error( assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) +def test_stoploss_on_exchange_price_rounding( + mocker, default_conf_usdt, fee, open_trade_usdt) -> None: + patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_fee=fee, + ) + price_mock = MagicMock(side_effect=lambda p, s: int(s)) + stoploss_mock = MagicMock(return_value={'id': '13434334'}) + adjust_mock = MagicMock(return_value=False) + mocker.patch.multiple( + 'freqtrade.exchange.Binance', + stoploss=stoploss_mock, + stoploss_adjust=adjust_mock, + price_to_precision=price_mock, + ) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + open_trade_usdt.stoploss_order_id = '13434334' + open_trade_usdt.stop_loss = 222.55 + + freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {}) + assert price_mock.call_count == 1 + assert adjust_mock.call_count == 1 + assert adjust_mock.call_args_list[0][0][0] == 222 + + @pytest.mark.usefixtures("init_persistence") def test_handle_stoploss_on_exchange_custom_stop( mocker, default_conf_usdt, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None: @@ -1449,7 +1509,7 @@ def test_handle_stoploss_on_exchange_custom_stop( cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') stoploss_order_mock.assert_called_once_with( - amount=31.57894736, + amount=pytest.approx(31.57894736), pair='ETH/USDT', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.96 @@ -1574,7 +1634,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, assert trade.stop_loss == 4.4 * 0.99 cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') stoploss_order_mock.assert_called_once_with( - amount=11.41438356, + amount=pytest.approx(11.41438356), pair='NEO/BTC', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.99 @@ -1631,9 +1691,9 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=limit_buy_order_usdt['amount']) - + order_id = limit_buy_order_usdt['id'] trade = Trade( - open_order_id=123, + open_order_id=order_id, fee_open=0.001, fee_close=0.001, open_rate=0.01, @@ -1641,29 +1701,35 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap amount=11, exchange="binance", ) + trade.orders.append(Order( + ft_order_side='buy', + price=0.01, + order_id=order_id, + + )) assert not freqtrade.update_trade_state(trade, None) assert log_has_re(r'Orderid for trade .* is empty.', caplog) caplog.clear() # Add datetime explicitly since sqlalchemy defaults apply only once written to database - freqtrade.update_trade_state(trade, '123') + freqtrade.update_trade_state(trade, order_id) # Test amount not modified by fee-logic assert not log_has_re(r'Applying fee to .*', caplog) caplog.clear() assert trade.open_order_id is None assert trade.amount == limit_buy_order_usdt['amount'] - trade.open_order_id = '123' + trade.open_order_id = order_id mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81) assert trade.amount != 90.81 # test amount modified by fee-logic - freqtrade.update_trade_state(trade, '123') + freqtrade.update_trade_state(trade, order_id) assert trade.amount == 90.81 assert trade.open_order_id is None trade.is_open = True trade.open_order_id = None # Assert we call handle_trade() if trade is feasible for execution - freqtrade.update_trade_state(trade, '123') + freqtrade.update_trade_state(trade, order_id) assert log_has_re('Found open order for.*', caplog) limit_buy_order_usdt_new = deepcopy(limit_buy_order_usdt) @@ -1672,7 +1738,7 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', side_effect=ValueError) mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order_usdt_new) - res = freqtrade.update_trade_state(trade, '123') + res = freqtrade.update_trade_state(trade, order_id) # Cancelled empty assert res is True @@ -1684,6 +1750,8 @@ def test_update_trade_state(mocker, default_conf_usdt, limit_buy_order_usdt, cap def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, limit_buy_order_usdt, fee, mocker, initial_amount, has_rounding_fee, caplog): trades_for_order[0]['amount'] = initial_amount + order_id = "oid_123456" + limit_buy_order_usdt['id'] = order_id mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order) # fetch_order should not be called!! mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError)) @@ -1699,14 +1767,26 @@ def test_update_trade_state_withorderdict(default_conf_usdt, trades_for_order, l open_date=arrow.utcnow().datetime, fee_open=fee.return_value, fee_close=fee.return_value, - open_order_id="123456", + open_order_id=order_id, is_open=True, ) - freqtrade.update_trade_state(trade, '123456', limit_buy_order_usdt) + trade.orders.append( + Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=True, + order_id=order_id, + ) + ) + freqtrade.update_trade_state(trade, order_id, limit_buy_order_usdt) assert trade.amount != amount - assert trade.amount == limit_buy_order_usdt['amount'] + log_text = r'Applying fee on amount for .*' if has_rounding_fee: - assert log_has_re(r'Applying fee on amount for .*', caplog) + assert pytest.approx(trade.amount) == 29.992 + assert log_has_re(log_text, caplog) + else: + assert pytest.approx(trade.amount) == limit_buy_order_usdt['amount'] + assert not log_has_re(log_text, caplog) def test_update_trade_state_exception(mocker, default_conf_usdt, @@ -1761,7 +1841,7 @@ def test_update_trade_state_sell(default_conf_usdt, trades_for_order, limit_sell fee_open=0.0025, fee_close=0.0025, open_date=arrow.utcnow().datetime, - open_order_id="123456", + open_order_id=limit_sell_order_usdt_open['id'], is_open=True, ) order = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'LTC/ETH', 'sell') @@ -1802,7 +1882,8 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_ assert trade time.sleep(0.01) # Race condition fix - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) assert trade.is_open is True freqtrade.wallets.update() @@ -1811,7 +1892,9 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_ assert trade.open_order_id == limit_sell_order_usdt['id'] # Simulate fulfilled LIMIT_SELL order for trade - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object( + limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') + trade.update_trade(oobj) assert trade.close_rate == 2.2 assert trade.close_profit == 0.09451372 @@ -1904,7 +1987,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o # we might just want to check if we are in a sell condition without # executing # if ROI is reached we must sell - patch_get_signal(freqtrade, value=(False, True, None, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) assert freqtrade.handle_trade(trade) assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI", caplog) @@ -1961,8 +2044,11 @@ def test_close_trade(default_conf_usdt, ticker_usdt, limit_buy_order_usdt, trade = Trade.query.first() assert trade - trade.update(limit_buy_order_usdt) - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) + oobj = Order.parse_from_ccxt_object( + limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') + trade.update_trade(oobj) assert trade.is_open is False with pytest.raises(DependencyException, match=r'.*closed trade.*'): @@ -1985,7 +2071,7 @@ def test_bot_loop_start_called_once(mocker, default_conf_usdt, caplog): def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, fee, mocker) -> None: default_conf_usdt["unfilledtimeout"] = {"buy": 1400, "sell": 30} - + limit_buy_order_old['id'] = open_trade.open_order_id rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock(return_value=limit_buy_order_old) cancel_buy_order = deepcopy(limit_buy_order_old) @@ -2041,6 +2127,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf_usdt, ticker_usdt, li def test_check_handle_timedout_buy(default_conf_usdt, ticker_usdt, limit_buy_order_old, open_trade, fee, mocker) -> None: rpc_mock = patch_RPCManager(mocker) + limit_buy_order_old['id'] = open_trade.open_order_id limit_buy_cancel = deepcopy(limit_buy_order_old) limit_buy_cancel['status'] = 'canceled' cancel_order_mock = MagicMock(return_value=limit_buy_cancel) @@ -2125,6 +2212,8 @@ def test_check_handle_timedout_buy_exception(default_conf_usdt, ticker_usdt, def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, limit_sell_order_old, mocker, open_trade, caplog) -> None: default_conf_usdt["unfilledtimeout"] = {"buy": 1440, "sell": 1440, "exit_timeout_count": 1} + limit_sell_order_old['id'] = open_trade.open_order_id + rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock() patch_exchange(mocker) @@ -2171,10 +2260,25 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l assert open_trade.is_open is True assert freqtrade.strategy.check_sell_timeout.call_count == 1 - # 2nd canceled trade ... + # 2nd canceled trade - Fail execute sell caplog.clear() - open_trade.open_order_id = 'order_id_2' + open_trade.open_order_id = limit_sell_order_old['id'] mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1) + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit', + side_effect=DependencyException) + freqtrade.check_handle_timedout() + assert log_has_re('Unable to emergency sell .*', caplog) + + et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit') + caplog.clear() + # 2nd canceled trade ... + open_trade.open_order_id = limit_sell_order_old['id'] + + # If cancelling fails - no emergency sell! + with patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit', return_value=False): + freqtrade.check_handle_timedout() + assert et_mock.call_count == 0 + freqtrade.check_handle_timedout() assert log_has_re('Emergencyselling trade.*', caplog) assert et_mock.call_count == 1 @@ -2184,6 +2288,7 @@ def test_check_handle_timedout_sell(default_conf_usdt, ticker_usdt, limit_sell_o open_trade) -> None: rpc_mock = patch_RPCManager(mocker) cancel_order_mock = MagicMock() + limit_sell_order_old['id'] = open_trade.open_order_id patch_exchange(mocker) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -2242,6 +2347,7 @@ def test_check_handle_cancelled_sell(default_conf_usdt, ticker_usdt, limit_sell_ def test_check_handle_timedout_partial(default_conf_usdt, ticker_usdt, limit_buy_order_old_partial, open_trade, mocker) -> None: rpc_mock = patch_RPCManager(mocker) + limit_buy_order_old_partial['id'] = open_trade.open_order_id limit_buy_canceled = deepcopy(limit_buy_order_old_partial) limit_buy_canceled['status'] = 'canceled' @@ -2272,6 +2378,8 @@ def test_check_handle_timedout_partial_fee(default_conf_usdt, ticker_usdt, open_ limit_buy_order_old_partial, trades_for_order, limit_buy_order_old_partial_canceled, mocker) -> None: rpc_mock = patch_RPCManager(mocker) + limit_buy_order_old_partial['id'] = open_trade.open_order_id + limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=0)) patch_exchange(mocker) @@ -2311,6 +2419,8 @@ def test_check_handle_timedout_partial_except(default_conf_usdt, ticker_usdt, op fee, limit_buy_order_old_partial, trades_for_order, limit_buy_order_old_partial_canceled, mocker) -> None: rpc_mock = patch_RPCManager(mocker) + limit_buy_order_old_partial_canceled['id'] = open_trade.open_order_id + limit_buy_order_old_partial['id'] = open_trade.open_order_id cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled) patch_exchange(mocker) mocker.patch.multiple( @@ -2417,6 +2527,9 @@ def test_handle_cancel_enter(mocker, caplog, default_conf_usdt, limit_buy_order_ mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock) assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason) assert log_has_re(r"Order .* for .* not cancelled.", caplog) + # min_pair_stake empty should not crash + mocker.patch('freqtrade.exchange.Exchange.get_min_pair_stake_amount', return_value=None) + assert not freqtrade.handle_cancel_enter(trade, limit_buy_order_usdt, reason) @pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'], @@ -2492,9 +2605,12 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: exchange='binance', open_rate=0.245441, open_order_id="123456", - open_date=arrow.utcnow().datetime, + open_date=arrow.utcnow().shift(days=-2).datetime, fee_open=fee.return_value, fee_close=fee.return_value, + close_rate=0.555, + close_date=arrow.utcnow().datetime, + sell_reason="sell_reason_whatever", ) order = {'remaining': 1, 'amount': 1, @@ -2503,17 +2619,23 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: assert freqtrade.handle_cancel_exit(trade, order, reason) assert cancel_order_mock.call_count == 1 assert send_msg_mock.call_count == 1 + assert trade.close_rate is None + assert trade.sell_reason is None send_msg_mock.reset_mock() order['amount'] = 2 - assert freqtrade.handle_cancel_exit(trade, order, reason - ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] + assert not freqtrade.handle_cancel_exit(trade, order, reason) # Assert cancel_order was not called (callcount remains unchanged) assert cancel_order_mock.call_count == 1 assert send_msg_mock.call_count == 1 - assert freqtrade.handle_cancel_exit(trade, order, reason - ) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] + assert (send_msg_mock.call_args_list[0][0][0]['reason'] + == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']) + + assert not freqtrade.handle_cancel_exit(trade, order, reason) + + send_msg_mock.call_args_list[0][0][0]['reason'] = CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] + # Message should not be iterated again assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] assert send_msg_mock.call_count == 1 @@ -2532,7 +2654,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None: order = {'remaining': 1, 'amount': 1, 'status': "open"} - assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order' + assert not freqtrade.handle_cancel_exit(trade, order, reason) def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_up, mocker @@ -2979,7 +3101,7 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee, assert trade.close_profit == 0.09451372 assert rpc_mock.call_count == 3 - last_msg = rpc_mock.call_args_list[-1][0][0] + last_msg = rpc_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -3081,7 +3203,8 @@ def test_sell_profit_only( freqtrade.enter_positions() trade = Trade.query.first() - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) freqtrade.wallets.update() patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is handle_first @@ -3117,7 +3240,9 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_ trade = Trade.query.first() amnt = trade.amount - trade.update(limit_buy_order_usdt) + + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) patch_get_signal(freqtrade, value=(False, True, None, None)) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) @@ -3225,13 +3350,14 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt, freqtrade.enter_positions() trade = Trade.query.first() - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) freqtrade.wallets.update() patch_get_signal(freqtrade, value=(True, True, None, None)) assert freqtrade.handle_trade(trade) is False # Test if buy-signal is absent (should sell due to roi = true) - patch_get_signal(freqtrade, value=(False, True, None, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3328,7 +3454,8 @@ def test_trailing_stop_loss_positive( freqtrade.enter_positions() trade = Trade.query.first() - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) caplog.set_level(logging.DEBUG) # stop-loss not reached assert freqtrade.handle_trade(trade) is False @@ -3415,13 +3542,14 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd freqtrade.enter_positions() trade = Trade.query.first() - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) # Sell due to min_roi_reached - patch_get_signal(freqtrade, value=(True, True, None, None)) + patch_get_signal(freqtrade, value=(True, False, None, None)) assert freqtrade.handle_trade(trade) is True # Test if buy-signal is absent - patch_get_signal(freqtrade, value=(False, True, None, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3790,7 +3918,8 @@ def test_order_book_depth_of_market( assert len(Trade.query.all()) == 1 # Simulate fulfilled LIMIT_BUY order for trade - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.open_rate == 2.0 assert whitelist == default_conf_usdt['exchange']['pair_whitelist'] @@ -3884,7 +4013,8 @@ def test_order_book_ask_strategy( assert trade time.sleep(0.01) # Race condition fix - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, limit_buy_order_usdt['symbol'], 'buy') + trade.update_trade(oobj) freqtrade.wallets.update() assert trade.is_open is True @@ -4091,15 +4221,17 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + return_value={'status': 'open'}) create_mock_trades(fee) trades = Trade.get_trades().all() - freqtrade.reupdate_enter_order_fees(trades[0]) - assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) + freqtrade.handle_insufficient_funds(trades[3]) + # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) assert mock_uts.call_count == 1 - assert mock_uts.call_args_list[0][0][0] == trades[0] - assert mock_uts.call_args_list[0][0][1] == mock_order_1()['id'] - assert log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog) + assert mock_uts.call_args_list[0][0][0] == trades[3] + assert mock_uts.call_args_list[0][0][1] == mock_order_4()['id'] + assert log_has_re(r"Trying to refind lost order for .*", caplog) mock_uts.reset_mock() caplog.clear() @@ -4117,52 +4249,13 @@ def test_reupdate_enter_order_fees(mocker, default_conf_usdt, fee, caplog): ) Trade.query.session.add(trade) - freqtrade.reupdate_enter_order_fees(trade) - assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) + freqtrade.handle_insufficient_funds(trade) + # assert log_has_re(r"Trying to reupdate buy fees for .*", caplog) assert mock_uts.call_count == 0 - assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog) @pytest.mark.usefixtures("init_persistence") -def test_handle_insufficient_funds(mocker, default_conf_usdt, fee): - freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order') - mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees') - create_mock_trades(fee) - trades = Trade.get_trades().all() - - # Trade 0 has only a open buy order, no closed order - freqtrade.handle_insufficient_funds(trades[0]) - assert mock_rlo.call_count == 0 - assert mock_bof.call_count == 1 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 1 has closed buy and sell orders - freqtrade.handle_insufficient_funds(trades[1]) - assert mock_rlo.call_count == 1 - assert mock_bof.call_count == 0 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 2 has closed buy and sell orders - freqtrade.handle_insufficient_funds(trades[2]) - assert mock_rlo.call_count == 1 - assert mock_bof.call_count == 0 - - mock_rlo.reset_mock() - mock_bof.reset_mock() - - # Trade 3 has an opne buy order - freqtrade.handle_insufficient_funds(trades[3]) - assert mock_rlo.call_count == 0 - assert mock_bof.call_count == 1 - - -@pytest.mark.usefixtures("init_persistence") -def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): +def test_handle_insufficient_funds(mocker, default_conf_usdt, fee, caplog): caplog.set_level(logging.DEBUG) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state') @@ -4185,7 +4278,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_1() assert log_has_re(r"Order Order(.*order_id=" + order['id'] + ".*) is no longer open.", caplog) assert mock_fo.call_count == 0 @@ -4203,13 +4296,13 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_4() assert log_has_re(r"Trying to refind Order\(.*", caplog) - assert mock_fo.call_count == 0 - assert mock_uts.call_count == 0 - # No change to orderid - as update_trade_state is mocked - assert trade.open_order_id is None + assert mock_fo.call_count == 1 + assert mock_uts.call_count == 1 + # Found open buy order + assert trade.open_order_id is not None assert trade.stoploss_order_id is None caplog.clear() @@ -4221,11 +4314,11 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_5_stoploss() assert log_has_re(r"Trying to refind Order\(.*", caplog) assert mock_fo.call_count == 1 - assert mock_uts.call_count == 1 + assert mock_uts.call_count == 2 # stoploss_order_id is "refound" and added to the trade assert trade.open_order_id is None assert trade.stoploss_order_id is not None @@ -4240,7 +4333,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): assert trade.open_order_id is None assert trade.stoploss_order_id is None - freqtrade.refind_lost_order(trade) + freqtrade.handle_insufficient_funds(trade) order = mock_order_6_sell() assert log_has_re(r"Trying to refind Order\(.*", caplog) assert mock_fo.call_count == 1 @@ -4256,7 +4349,7 @@ def test_refind_lost_order(mocker, default_conf_usdt, fee, caplog): side_effect=ExchangeError()) order = mock_order_5_stoploss() - freqtrade.refind_lost_order(trades[4]) + freqtrade.handle_insufficient_funds(trades[4]) assert log_has(f"Error updating {order['id']}.", caplog) @@ -4298,3 +4391,273 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None: assert valid_price_at_min_alwd > custom_price_under_min_alwd assert valid_price_at_min_alwd < proposed_price + + +def test_position_adjust(mocker, default_conf_usdt, fee) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + patch_wallet(mocker, free=10000) + default_conf_usdt.update({ + "position_adjustment_enable": True, + "dry_run": False, + "stake_amount": 10.0, + "dry_run_wallet": 1000.0, + }) + freqtrade = FreqtradeBot(default_conf_usdt) + freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True) + bid = 11 + stake_amount = 10 + buy_rate_mock = MagicMock(return_value=bid) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_rate=buy_rate_mock, + fetch_ticker=MagicMock(return_value={ + 'bid': 10, + 'ask': 12, + 'last': 11 + }), + get_min_pair_stake_amount=MagicMock(return_value=1), + get_fee=fee, + ) + pair = 'ETH/USDT' + + # Initial buy + closed_successful_buy_order = { + 'pair': pair, + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': 'closed', + 'price': bid, + 'average': bid, + 'cost': bid * stake_amount, + 'amount': stake_amount, + 'filled': stake_amount, + 'ft_is_open': False, + 'id': '650', + 'order_id': '650' + } + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_successful_buy_order)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_successful_buy_order)) + assert freqtrade.execute_entry(pair, stake_amount) + # Should create an closed trade with an no open order id + # Order is filled and trade is open + orders = Order.query.all() + assert orders + assert len(orders) == 1 + trade = Trade.query.first() + assert trade + assert trade.is_open is True + assert trade.open_order_id is None + assert trade.open_rate == 11 + assert trade.stake_amount == 110 + + # Assume it does nothing since order is closed and trade is open + freqtrade.update_closed_trades_without_assigned_fees() + + trade = Trade.query.first() + assert trade + assert trade.is_open is True + assert trade.open_order_id is None + assert trade.open_rate == 11 + assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + + freqtrade.check_handle_timedout() + + trade = Trade.query.first() + assert trade + assert trade.is_open is True + assert trade.open_order_id is None + assert trade.open_rate == 11 + assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + + # First position adjustment buy. + open_dca_order_1 = { + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': None, + 'price': 9, + 'amount': 12, + 'cost': 100, + 'ft_is_open': True, + 'id': '651', + 'order_id': '651' + } + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=open_dca_order_1)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=open_dca_order_1)) + assert freqtrade.execute_entry(pair, stake_amount, trade=trade) + + orders = Order.query.all() + assert orders + assert len(orders) == 2 + trade = Trade.query.first() + assert trade + assert trade.open_order_id == '651' + assert trade.open_rate == 11 + assert trade.amount == 10 + assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + assert len(trades) == 1 + assert trade.is_open + assert not trade.fee_updated('buy') + order = trade.select_order('buy', False) + assert order + assert order.order_id == '650' + + def make_sure_its_651(*args, **kwargs): + + if args[0] == '650': + return closed_successful_buy_order + if args[0] == '651': + return open_dca_order_1 + return None + + # Assume it does nothing since order is still open + fetch_order_mm = MagicMock(side_effect=make_sure_its_651) + mocker.patch('freqtrade.exchange.Exchange.create_order', fetch_order_mm) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', fetch_order_mm) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', fetch_order_mm) + freqtrade.update_closed_trades_without_assigned_fees() + + orders = Order.query.all() + assert orders + assert len(orders) == 2 + # Assert that the trade is found as open and without fees + trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + assert len(trades) == 1 + # Assert trade is as expected + trade = Trade.query.first() + assert trade + assert trade.open_order_id == '651' + assert trade.open_rate == 11 + assert trade.amount == 10 + assert trade.stake_amount == 110 + assert not trade.fee_updated('buy') + + # Make sure the closed order is found as the first order. + order = trade.select_order('buy', False) + assert order.order_id == '650' + + # Now close the order so it should update. + closed_dca_order_1 = { + 'ft_pair': pair, + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'status': 'closed', + 'price': 9, + 'average': 9, + 'amount': 12, + 'filled': 12, + 'cost': 108, + 'ft_is_open': False, + 'id': '651', + 'order_id': '651' + } + + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_dca_order_1)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + MagicMock(return_value=closed_dca_order_1)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_dca_order_1)) + freqtrade.check_handle_timedout() + + # Assert trade is as expected (averaged dca) + trade = Trade.query.first() + assert trade + assert trade.open_order_id is None + assert pytest.approx(trade.open_rate) == 9.90909090909 + assert trade.amount == 22 + assert trade.stake_amount == 218 + + orders = Order.query.all() + assert orders + assert len(orders) == 2 + + # Make sure the closed order is found as the second order. + order = trade.select_order('buy', False) + assert order.order_id == '651' + + # Assert that the trade is not found as open and without fees + trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() + assert len(trades) == 1 + + # Add a second DCA + closed_dca_order_2 = { + 'ft_pair': pair, + 'status': 'closed', + 'ft_order_side': 'buy', + 'side': 'buy', + 'type': 'limit', + 'price': 7, + 'average': 7, + 'amount': 15, + 'filled': 15, + 'cost': 105, + 'ft_is_open': False, + 'id': '652', + 'order_id': '652' + } + mocker.patch('freqtrade.exchange.Exchange.create_order', + MagicMock(return_value=closed_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + MagicMock(return_value=closed_dca_order_2)) + mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order', + MagicMock(return_value=closed_dca_order_2)) + assert freqtrade.execute_entry(pair, stake_amount, trade=trade) + + # Assert trade is as expected (averaged dca) + trade = Trade.query.first() + assert trade + assert trade.open_order_id is None + assert pytest.approx(trade.open_rate) == 8.729729729729 + assert trade.amount == 37 + assert trade.stake_amount == 323 + + orders = Order.query.all() + assert orders + assert len(orders) == 3 + + # Make sure the closed order is found as the second order. + order = trade.select_order('buy', False) + assert order.order_id == '652' + + +def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt.update({ + "position_adjustment_enable": True, + }) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.check_and_call_adjust_trade_position', + side_effect=DependencyException()) + + create_mock_trades(fee) + + freqtrade.process_open_trade_positions() + assert log_has_re(r"Unable to adjust position of trade for .*", caplog) + + +def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt.update({ + "position_adjustment_enable": True, + "max_entry_position_adjustment": 0, + }) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + create_mock_trades(fee) + caplog.set_level(logging.DEBUG) + + freqtrade.process_open_trade_positions() + assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog) diff --git a/tests/test_integration.py b/tests/test_integration.py index a3484d438..db3b1b5fc 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,6 +4,7 @@ import pytest from freqtrade.enums import SellType from freqtrade.persistence import Trade +from freqtrade.persistence.models import Order from freqtrade.rpc.rpc import RPC from freqtrade.strategy.interface import SellCheckTuple from tests.conftest import get_patched_freqtradebot, patch_get_signal @@ -94,7 +95,11 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, trades = Trade.query.all() # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state) for trade in trades: - trade.stoploss_order_id = 3 + stoploss_order_closed['id'] = '3' + oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss') + + trade.orders.append(oobj) + trade.stoploss_order_id = '3' trade.open_order_id = None n = freqtrade.exit_positions(trades) @@ -127,8 +132,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee, (1, 200), (0.99, 198), ]) -def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, mocker, balance_ratio, - result1) -> None: +def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_ratio, result1) -> None: """ Tests workflow unlimited stake-amount Buy 4 trades, forcebuy a 5th trade @@ -207,3 +211,72 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc assert len(bals2) == 5 assert 'LTC' in bals assert 'LTC' not in bals2 + + +def test_dca_buying(default_conf_usdt, ticker_usdt, fee, mocker) -> None: + default_conf_usdt['position_adjustment_enable'] = True + + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker_usdt, + get_fee=fee, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, + ) + + patch_get_signal(freqtrade) + freqtrade.enter_positions() + + assert len(Trade.get_trades().all()) == 1 + trade = Trade.get_trades().first() + assert len(trade.orders) == 1 + assert trade.stake_amount == 60 + assert trade.open_rate == 2.0 + # No adjustment + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 1 + assert trade.stake_amount == 60 + + # Reduce bid amount + ticker_usdt_modif = ticker_usdt.return_value + ticker_usdt_modif['bid'] = ticker_usdt_modif['bid'] * 0.995 + mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value=ticker_usdt_modif) + + # additional buy order + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 2 + for o in trade.orders: + assert o.status == "closed" + assert trade.stake_amount == 120 + + # Open-rate averaged between 2.0 and 2.0 * 0.995 + assert trade.open_rate < 2.0 + assert trade.open_rate > 2.0 * 0.995 + + # No action - profit raised above 1% (the bar set in the strategy). + freqtrade.process() + trade = Trade.get_trades().first() + assert len(trade.orders) == 2 + assert trade.stake_amount == 120 + assert trade.orders[0].amount == 30 + assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid'] + + assert trade.amount == trade.orders[0].amount + trade.orders[1].amount + assert trade.nr_of_successful_buys == 2 + + # Sell + patch_get_signal(freqtrade, value=(False, True, None, None)) + freqtrade.process() + trade = Trade.get_trades().first() + assert trade.is_open is False + assert trade.orders[0].amount == 30 + assert trade.orders[0].side == 'buy' + assert trade.orders[1].amount == 60 / ticker_usdt_modif['bid'] + # Sold everything + assert trade.orders[-1].side == 'sell' + assert trade.orders[2].amount == trade.amount + + assert trade.nr_of_successful_buys == 2 diff --git a/tests/test_misc.py b/tests/test_misc.py index 221c7b712..4fd5338ad 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -21,16 +21,19 @@ def test_decimals_per_coin(): def test_round_coin_value(): assert round_coin_value(222.222222, 'USDT') == '222.222 USDT' - assert round_coin_value(222.2, 'USDT') == '222.200 USDT' + assert round_coin_value(222.2, 'USDT', keep_trailing_zeros=True) == '222.200 USDT' + assert round_coin_value(222.2, 'USDT') == '222.2 USDT' assert round_coin_value(222.12745, 'EUR') == '222.127 EUR' assert round_coin_value(0.1274512123, 'BTC') == '0.12745121 BTC' assert round_coin_value(0.1274512123, 'ETH') == '0.12745 ETH' assert round_coin_value(222.222222, 'USDT', False) == '222.222' - assert round_coin_value(222.2, 'USDT', False) == '222.200' + assert round_coin_value(222.2, 'USDT', False) == '222.2' + assert round_coin_value(222.00, 'USDT', False) == '222' assert round_coin_value(222.12745, 'EUR', False) == '222.127' assert round_coin_value(0.1274512123, 'BTC', False) == '0.12745121' assert round_coin_value(0.1274512123, 'ETH', False) == '0.12745' + assert round_coin_value(222.2, 'USDT', False, True) == '222.200' def test_shorten_date() -> None: @@ -67,6 +70,9 @@ def test_file_load_json(mocker, testdatadir) -> None: @pytest.mark.parametrize("pair,expected_result", [ ("ETH/BTC", 'ETH_BTC'), + ("ETH/USDT", 'ETH_USDT'), + ("ETH/USDT:USDT", 'ETH_USDT_USDT'), # swap with USDT as settlement currency + ("ETH/USDT:USDT-210625", 'ETH_USDT_USDT_210625'), # expiring futures ("Fabric Token/ETH", 'Fabric_Token_ETH'), ("ETHH20", 'ETHH20'), (".XBTBON2H", '_XBTBON2H'), @@ -181,16 +187,18 @@ def test_render_template_fallback(mocker): assert 'if self.dp' in val -def test_parse_db_uri_for_logging() -> None: - postgresql_conn_uri = "postgresql+psycopg2://scott123:scott123@host/dbname" - mariadb_conn_uri = "mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company" - mysql_conn_uri = "mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4" - sqlite_conn_uri = "sqlite:////freqtrade/user_data/tradesv3.sqlite" - censored_pwd = "*****" +@pytest.mark.parametrize('conn_url,expected', [ + ("postgresql+psycopg2://scott123:scott123@host:1245/dbname", + "postgresql+psycopg2://scott123:*****@host:1245/dbname"), + ("postgresql+psycopg2://scott123:scott123@host.name.com/dbname", + "postgresql+psycopg2://scott123:*****@host.name.com/dbname"), + ("mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company", + "mariadb+mariadbconnector://app_user:*****@127.0.0.1:3306/company"), + ("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4", + "mysql+pymysql://user:*****@some_mariadb/dbname?charset=utf8mb4"), + ("sqlite:////freqtrade/user_data/tradesv3.sqlite", + "sqlite:////freqtrade/user_data/tradesv3.sqlite"), +]) +def test_parse_db_uri_for_logging(conn_url, expected) -> None: - def get_pwd(x): return x.split(':')[2].split('@')[0] - - assert get_pwd(parse_db_uri_for_logging(postgresql_conn_uri)) == censored_pwd - assert get_pwd(parse_db_uri_for_logging(mariadb_conn_uri)) == censored_pwd - assert get_pwd(parse_db_uri_for_logging(mysql_conn_uri)) == censored_pwd - assert sqlite_conn_uri == parse_db_uri_for_logging(sqlite_conn_uri) + assert parse_db_uri_for_logging(conn_url) == expected diff --git a/tests/test_periodiccache.py b/tests/test_periodiccache.py index f874f9041..b2bd8ba2b 100644 --- a/tests/test_periodiccache.py +++ b/tests/test_periodiccache.py @@ -26,7 +26,9 @@ def test_ttl_cache(): assert 'a' in cache1h t.move_to("2021-09-01 05:59:59 +00:00") + assert 'a' not in cache assert 'a' in cache1h t.move_to("2021-09-01 06:00:00 +00:00") + assert 'a' not in cache assert 'a' not in cache1h diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d1d3ce382..32253d1cb 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -8,11 +8,12 @@ from unittest.mock import MagicMock import arrow import pytest -from sqlalchemy import create_engine, inspect, text +from sqlalchemy import create_engine, text from freqtrade import constants from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db +from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids from tests.conftest import create_mock_trades, create_mock_trades_usdt, log_has, log_has_re @@ -32,13 +33,17 @@ def test_init_custom_db_url(default_conf, tmpdir): init_db(default_conf['db_url'], default_conf['dry_run']) assert Path(filename).is_file() + r = Trade._session.execute(text("PRAGMA journal_mode")) + assert r.first() == ('wal',) -def test_init_invalid_db_url(default_conf): +def test_init_invalid_db_url(): # Update path to a value other than default, but still in-memory - default_conf.update({'db_url': 'unknown:///some.url'}) with pytest.raises(OperationalException, match=r'.*no valid database URL*'): - init_db(default_conf['db_url'], default_conf['dry_run']) + init_db('unknown:///some.url', True) + + with pytest.raises(OperationalException, match=r'Bad db-url.*For in-memory database, pl.*'): + init_db('sqlite:///', True) def test_init_prod_db(default_conf, mocker): @@ -107,7 +112,8 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca assert trade.close_date is None trade.open_order_id = 'something' - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.open_order_id is None assert trade.open_rate == 2.00 assert trade.close_profit is None @@ -118,7 +124,8 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca caplog.clear() trade.open_order_id = 'something' - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert trade.open_order_id is None assert trade.close_rate == 2.20 assert trade.close_profit == round(0.0945137157107232, 8) @@ -145,7 +152,8 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, ) trade.open_order_id = 'something' - trade.update(market_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(market_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.open_order_id is None assert trade.open_rate == 2.0 assert trade.close_profit is None @@ -157,7 +165,8 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, caplog.clear() trade.is_open = True trade.open_order_id = 'something' - trade.update(market_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(market_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert trade.open_order_id is None assert trade.close_rate == 2.2 assert trade.close_profit == round(0.0945137157107232, 8) @@ -180,9 +189,11 @@ def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt ) trade.open_order_id = 'something' - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade._calc_open_trade_value() == 60.15 - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert isclose(trade.calc_close_trade_value(), 65.835) # Profit in USDT @@ -235,7 +246,8 @@ def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee): ) trade.open_order_id = 'something' - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.calc_close_trade_value() == 0.0 @@ -256,7 +268,8 @@ def test_update_open_order(limit_buy_order_usdt): assert trade.close_date is None limit_buy_order_usdt['status'] = 'open' - trade.update(limit_buy_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) assert trade.open_order_id is None assert trade.close_profit is None @@ -275,8 +288,9 @@ def test_update_invalid_order(limit_buy_order_usdt): exchange='binance', ) limit_buy_order_usdt['type'] = 'invalid' + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'meep') with pytest.raises(ValueError, match=r'Unknown order type'): - trade.update(limit_buy_order_usdt) + trade.update_trade(oobj) @pytest.mark.usefixtures("init_persistence") @@ -303,7 +317,8 @@ def test_calc_open_trade_value(limit_buy_order_usdt, fee): exchange='binance', ) trade.open_order_id = 'open_trade' - trade.update(limit_buy_order_usdt) # Buy @ 2.0 + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) # Buy @ 2.0 # Get the open rate price with the standard fee rate assert trade._calc_open_trade_value() == 60.15 @@ -324,14 +339,16 @@ def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee exchange='binance', ) trade.open_order_id = 'close_trade' - trade.update(limit_buy_order_usdt) # Buy @ 2.0 + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) # Buy @ 2.0 # Get the close rate price with a custom close rate and a regular fee rate assert trade.calc_close_trade_value(rate=2.5) == 74.8125 # Get the close rate price with a custom close rate and a custom fee rate assert trade.calc_close_trade_value(rate=2.5, fee=0.003) == 74.775 # Test when we apply a Sell order, and ask price with a custom fee rate - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert trade.calc_close_trade_value(fee=0.005) == 65.67 @@ -408,7 +425,9 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee): exchange='binance', ) trade.open_order_id = 'something' - trade.update(limit_buy_order_usdt) # Buy @ 2.0 + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + + trade.update_trade(oobj) # Buy @ 2.0 # Custom closing rate and regular fee rate # Higher than open rate - 2.1 quote @@ -423,7 +442,8 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee): assert trade.calc_profit(rate=1.9, fee=0.003) == round(-3.320999999999998, 8) # Test when we apply a Sell order. Sell higher than open rate @ 2.2 - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert trade.calc_profit() == round(5.684999999999995, 8) # Test with a custom fee rate on the close trade @@ -442,7 +462,9 @@ def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee): exchange='binance' ) trade.open_order_id = 'something' - trade.update(limit_buy_order_usdt) # Buy @ 2.0 + + oobj = Order.parse_from_ccxt_object(limit_buy_order_usdt, 'ADA/USDT', 'buy') + trade.update_trade(oobj) # Buy @ 2.0 # Higher than open rate - 2.1 quote assert trade.calc_profit_ratio(rate=2.1) == round(0.04476309226932673, 8) @@ -456,7 +478,8 @@ def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee): assert trade.calc_profit_ratio(rate=1.9, fee=0.003) == round(-0.05521197007481293, 8) # Test when we apply a Sell order. Sell higher than open rate @ 2.2 - trade.update(limit_sell_order_usdt) + oobj = Order.parse_from_ccxt_object(limit_sell_order_usdt, 'ADA/USDT', 'sell') + trade.update_trade(oobj) assert trade.calc_profit_ratio() == round(0.0945137157107232, 8) # Test with a custom fee rate on the close trade @@ -600,7 +623,8 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.stoploss_last_update is None assert log_has("trying trades_bak1", caplog) assert log_has("trying trades_bak2", caplog) - assert log_has("Running database migration for trades - backup: trades_bak2", caplog) + assert log_has("Running database migration for trades - backup: trades_bak2, orders_bak0", + caplog) assert trade.open_trade_value == trade._calc_open_trade_value() assert trade.close_profit_abs is None @@ -613,65 +637,6 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert orders[1].order_id == 'stop_order_id222' assert orders[1].ft_order_side == 'stoploss' - caplog.clear() - # Drop latest column - with engine.begin() as connection: - connection.execute(text("alter table orders rename to orders_bak")) - inspector = inspect(engine) - - with engine.begin() as connection: - for index in inspector.get_indexes('orders_bak'): - connection.execute(text(f"drop index {index['name']}")) - # Recreate table - connection.execute(text(""" - CREATE TABLE orders ( - id INTEGER NOT NULL, - ft_trade_id INTEGER, - ft_order_side VARCHAR NOT NULL, - ft_pair VARCHAR NOT NULL, - ft_is_open BOOLEAN NOT NULL, - order_id VARCHAR NOT NULL, - status VARCHAR, - symbol VARCHAR, - order_type VARCHAR, - side VARCHAR, - price FLOAT, - amount FLOAT, - filled FLOAT, - remaining FLOAT, - cost FLOAT, - order_date DATETIME, - order_filled_date DATETIME, - order_update_date DATETIME, - PRIMARY KEY (id), - CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id), - FOREIGN KEY(ft_trade_id) REFERENCES trades (id) - ) - """)) - - connection.execute(text(""" - insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, - symbol, order_type, side, price, amount, filled, remaining, cost, order_date, - order_filled_date, order_update_date) - select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, - symbol, order_type, side, price, amount, filled, remaining, cost, order_date, - order_filled_date, order_update_date - from orders_bak - """)) - - # Run init to test migration - init_db(default_conf['db_url'], default_conf['dry_run']) - - assert log_has("trying orders_bak1", caplog) - - orders = Order.query.all() - assert len(orders) == 2 - assert orders[0].order_id == 'buy_order' - assert orders[0].ft_order_side == 'buy' - - assert orders[1].order_id == 'stop_order_id222' - assert orders[1].ft_order_side == 'stoploss' - def test_migrate_mid_state(mocker, default_conf, fee, caplog): """ @@ -733,7 +698,40 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): assert trade.initial_stop_loss == 0.0 assert trade.open_trade_value == trade._calc_open_trade_value() assert log_has("trying trades_bak0", caplog) - assert log_has("Running database migration for trades - backup: trades_bak0", caplog) + assert log_has("Running database migration for trades - backup: trades_bak0, orders_bak0", + caplog) + + +def test_migrate_get_last_sequence_ids(): + engine = MagicMock() + engine.begin = MagicMock() + engine.name = 'postgresql' + get_last_sequence_ids(engine, 'trades_bak', 'orders_bak') + + assert engine.begin.call_count == 2 + engine.reset_mock() + engine.begin.reset_mock() + + engine.name = 'somethingelse' + get_last_sequence_ids(engine, 'trades_bak', 'orders_bak') + + assert engine.begin.call_count == 0 + + +def test_migrate_set_sequence_ids(): + engine = MagicMock() + engine.begin = MagicMock() + engine.name = 'postgresql' + set_sequence_ids(engine, 22, 55) + + assert engine.begin.call_count == 1 + engine.reset_mock() + engine.begin.reset_mock() + + engine.name = 'somethingelse' + set_sequence_ids(engine, 22, 55) + + assert engine.begin.call_count == 0 def test_adjust_stop_loss(fee): @@ -903,6 +901,7 @@ def test_to_json(default_conf, fee): 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', + 'orders': [], } # Simulate dry_run entries @@ -970,6 +969,7 @@ def test_to_json(default_conf, fee): 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', + 'orders': [], } @@ -1297,11 +1297,14 @@ def test_select_order(fee): order = trades[4].select_order('buy', False) assert order is not None + trades[4].orders[1].ft_order_side = 'sell' order = trades[4].select_order('sell', True) assert order is not None + + trades[4].orders[1].ft_order_side = 'stoploss' + order = trades[4].select_order('stoploss', None) + assert order is not None assert order.ft_order_side == 'stoploss' - order = trades[4].select_order('sell', False) - assert order is None def test_Trade_object_idem(): @@ -1343,3 +1346,394 @@ def test_Trade_object_idem(): and item not in ('trades', 'trades_open', 'total_profit') and type(getattr(LocalTrade, item)) not in (property, FunctionType)): assert item in trade + + +def test_recalc_trade_from_orders(fee): + + o1_amount = 100 + o1_rate = 1 + o1_cost = o1_amount * o1_rate + o1_fee_cost = o1_cost * fee.return_value + o1_trade_val = o1_cost + o1_fee_cost + + trade = Trade( + pair='ADA/USDT', + stake_amount=o1_cost, + open_date=arrow.utcnow().shift(hours=-2).datetime, + amount=o1_amount, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + open_rate=o1_rate, + max_rate=o1_rate, + ) + + assert fee.return_value == 0.0025 + assert trade._calc_open_trade_value() == o1_trade_val + assert trade.amount == o1_amount + assert trade.stake_amount == o1_cost + assert trade.open_rate == o1_rate + assert trade.open_trade_value == o1_trade_val + + # Calling without orders should not throw exceptions and change nothing + trade.recalc_trade_from_orders() + assert trade.amount == o1_amount + assert trade.stake_amount == o1_cost + assert trade.open_rate == o1_rate + assert trade.open_trade_value == o1_trade_val + + trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, 'buy') + + assert len(trade.orders) == 0 + + # Check with 1 order + order1 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + trade.orders.append(order1) + trade.recalc_trade_from_orders() + + # Calling recalc with single initial order should not change anything + assert trade.amount == o1_amount + assert trade.stake_amount == o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == o1_fee_cost + assert trade.open_trade_value == o1_trade_val + + # One additional adjustment / DCA order + o2_amount = 125 + o2_rate = 0.9 + o2_cost = o2_amount * o2_rate + o2_fee_cost = o2_cost * fee.return_value + o2_trade_val = o2_cost + o2_fee_cost + + order2 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o2_rate, + average=o2_rate, + filled=o2_amount, + remaining=0, + cost=o2_cost, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order2) + trade.recalc_trade_from_orders() + + # Validate that the trade now has new averaged open price and total values + avg_price = (o1_cost + o2_cost) / (o1_amount + o2_amount) + assert trade.amount == o1_amount + o2_amount + assert trade.stake_amount == o1_amount + o2_cost + assert trade.open_rate == avg_price + assert trade.fee_open_cost == o1_fee_cost + o2_fee_cost + assert trade.open_trade_value == o1_trade_val + o2_trade_val + + # Let's try with multiple additional orders + o3_amount = 150 + o3_rate = 0.85 + o3_cost = o3_amount * o3_rate + o3_fee_cost = o3_cost * fee.return_value + o3_trade_val = o3_cost + o3_fee_cost + + order3 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o3_rate, + average=o3_rate, + filled=o3_amount, + remaining=0, + cost=o3_cost, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order3) + trade.recalc_trade_from_orders() + + # Validate that the sum is still correct and open rate is averaged + avg_price = (o1_cost + o2_cost + o3_cost) / (o1_amount + o2_amount + o3_amount) + assert trade.amount == o1_amount + o2_amount + o3_amount + assert trade.stake_amount == o1_cost + o2_cost + o3_cost + assert trade.open_rate == avg_price + assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost + assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val + + # Just to make sure sell orders are ignored, let's calculate one more time. + sell1 = Order( + ft_order_side='sell', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="sell", + price=avg_price + 0.95, + average=avg_price + 0.95, + filled=o1_amount + o2_amount + o3_amount, + remaining=0, + cost=o1_cost + o2_cost + o3_cost, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + trade.orders.append(sell1) + trade.recalc_trade_from_orders() + + assert trade.amount == o1_amount + o2_amount + o3_amount + assert trade.stake_amount == o1_cost + o2_cost + o3_cost + assert trade.open_rate == avg_price + assert pytest.approx(trade.fee_open_cost) == o1_fee_cost + o2_fee_cost + o3_fee_cost + assert pytest.approx(trade.open_trade_value) == o1_trade_val + o2_trade_val + o3_trade_val + + +def test_recalc_trade_from_orders_ignores_bad_orders(fee): + + o1_amount = 100 + o1_rate = 1 + o1_cost = o1_amount * o1_rate + o1_fee_cost = o1_cost * fee.return_value + o1_trade_val = o1_cost + o1_fee_cost + + trade = Trade( + pair='ADA/USDT', + stake_amount=o1_cost, + open_date=arrow.utcnow().shift(hours=-2).datetime, + amount=o1_amount, + fee_open=fee.return_value, + fee_close=fee.return_value, + exchange='binance', + open_rate=o1_rate, + max_rate=o1_rate, + ) + trade.update_fee(o1_fee_cost, 'BNB', fee.return_value, 'buy') + # Check with 1 order + order1 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + trade.orders.append(order1) + trade.recalc_trade_from_orders() + + # Calling recalc with single initial order should not change anything + assert trade.amount == o1_amount + assert trade.stake_amount == o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == o1_fee_cost + assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys == 1 + + order2 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=True, + status="open", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_cost, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order2) + trade.recalc_trade_from_orders() + + # Validate that the trade values have not been changed + assert trade.amount == o1_amount + assert trade.stake_amount == o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == o1_fee_cost + assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys == 1 + + # Let's try with some other orders + order3 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="cancelled", + symbol=trade.pair, + order_type="market", + side="buy", + price=1, + average=2, + filled=0, + remaining=4, + cost=5, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order3) + trade.recalc_trade_from_orders() + + # Validate that the order values still are ignoring orders 2 and 3 + assert trade.amount == o1_amount + assert trade.stake_amount == o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == o1_fee_cost + assert trade.open_trade_value == o1_trade_val + assert trade.nr_of_successful_buys == 1 + + order4 = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=o1_rate, + filled=o1_amount, + remaining=0, + cost=o1_cost, + order_date=arrow.utcnow().shift(hours=-1).datetime, + order_filled_date=arrow.utcnow().shift(hours=-1).datetime, + ) + trade.orders.append(order4) + trade.recalc_trade_from_orders() + + # Validate that the trade values have been changed + assert trade.amount == 2 * o1_amount + assert trade.stake_amount == 2 * o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.open_trade_value == 2 * o1_trade_val + assert trade.nr_of_successful_buys == 2 + + # Just to make sure sell orders are ignored, let's calculate one more time. + sell1 = Order( + ft_order_side='sell', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="sell", + price=4, + average=3, + filled=2, + remaining=1, + cost=5, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + trade.orders.append(sell1) + trade.recalc_trade_from_orders() + + assert trade.amount == 2 * o1_amount + assert trade.stake_amount == 2 * o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == 2 * o1_fee_cost + assert trade.open_trade_value == 2 * o1_trade_val + assert trade.nr_of_successful_buys == 2 + # Check with 1 order + order_noavg = Order( + ft_order_side='buy', + ft_pair=trade.pair, + ft_is_open=False, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=o1_rate, + average=None, + filled=o1_amount, + remaining=0, + cost=o1_amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) + trade.orders.append(order_noavg) + trade.recalc_trade_from_orders() + + # Calling recalc with single initial order should not change anything + assert trade.amount == 3 * o1_amount + assert trade.stake_amount == 3 * o1_amount + assert trade.open_rate == o1_rate + assert trade.fee_open_cost == 3 * o1_fee_cost + assert trade.open_trade_value == 3 * o1_trade_val + assert trade.nr_of_successful_buys == 3 + + +@pytest.mark.usefixtures("init_persistence") +def test_select_filled_orders(fee): + create_mock_trades(fee) + + trades = Trade.get_trades().all() + + # Closed buy order, no sell order + orders = trades[0].select_filled_orders('buy') + assert orders is not None + assert len(orders) == 1 + order = orders[0] + assert order.amount > 0 + assert order.filled > 0 + assert order.side == 'buy' + assert order.ft_order_side == 'buy' + assert order.status == 'closed' + orders = trades[0].select_filled_orders('sell') + assert orders is not None + assert len(orders) == 0 + + # closed buy order, and closed sell order + orders = trades[1].select_filled_orders('buy') + assert orders is not None + assert len(orders) == 1 + + orders = trades[1].select_filled_orders('sell') + assert orders is not None + assert len(orders) == 1 + + # Has open buy order + orders = trades[3].select_filled_orders('buy') + assert orders is not None + assert len(orders) == 0 + orders = trades[3].select_filled_orders('sell') + assert orders is not None + assert len(orders) == 0 + + # Open sell order + orders = trades[4].select_filled_orders('buy') + assert orders is not None + assert len(orders) == 1 + orders = trades[4].select_filled_orders('sell') + assert orders is not None + assert len(orders) == 0 diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 8a40f4a20..6846bb5f2 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -45,7 +45,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir): default_conf['trade_source'] = "file" default_conf['timeframe'] = "5m" default_conf["datadir"] = testdatadir - default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" + default_conf['exportfilename'] = testdatadir / "backtest-result_new.json" supported_markets = ["TRX/BTC", "ADA/BTC"] ret = init_plotscript(default_conf, supported_markets) assert "ohlcv" in ret @@ -157,7 +157,7 @@ def test_plot_trades(testdatadir, caplog): assert fig == fig1 assert log_has("No trades found.", caplog) pair = "ADA/BTC" - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" trades = load_backtest_data(filename) trades = trades.loc[trades['pair'] == pair] @@ -171,7 +171,7 @@ def test_plot_trades(testdatadir, caplog): assert len(trades) == len(trade_buy.x) assert trade_buy.marker.color == 'cyan' assert trade_buy.marker.symbol == 'circle-open' - assert trade_buy.text[0] == '3.99%, roi, 15 min' + assert trade_buy.text[0] == '3.99%, buy_tag, roi, 15 min' trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit') assert isinstance(trade_sell, go.Scatter) @@ -179,7 +179,7 @@ def test_plot_trades(testdatadir, caplog): assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x) assert trade_sell.marker.color == 'green' assert trade_sell.marker.symbol == 'square-open' - assert trade_sell.text[0] == '3.99%, roi, 15 min' + assert trade_sell.text[0] == '3.99%, buy_tag, roi, 15 min' trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss') assert isinstance(trade_sell_loss, go.Scatter) @@ -294,7 +294,7 @@ def test_generate_plot_file(mocker, caplog): def test_add_profit(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -314,7 +314,7 @@ def test_add_profit(testdatadir): def test_generate_profit_graph(testdatadir): - filename = testdatadir / "backtest-result_test.json" + filename = testdatadir / "backtest-result_new.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["TRX/BTC", "XLM/BTC"] @@ -336,15 +336,20 @@ def test_generate_profit_graph(testdatadir): assert fig.layout.yaxis3.title.text == "Profit BTC" figure = fig.layout.figure - assert len(figure.data) == 5 + assert len(figure.data) == 7 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scatter) profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scatter) - profit = find_trace_in_fig_data(figure.data, "Max drawdown 10.45%") - assert isinstance(profit, go.Scatter) + drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 35.69%") + assert isinstance(drawdown, go.Scatter) + parallel = find_trace_in_fig_data(figure.data, "Parallel trades") + assert isinstance(parallel, go.Scatter) + + underwater = find_trace_in_fig_data(figure.data, "Underwater Plot") + assert isinstance(underwater, go.Scatter) for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") @@ -376,7 +381,7 @@ def test_load_and_plot_trades(default_conf, mocker, caplog, testdatadir): default_conf['trade_source'] = 'file' default_conf["datadir"] = testdatadir - default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" + default_conf['exportfilename'] = testdatadir / "backtest-result_new.json" default_conf['indicators1'] = ["sma5", "ema10"] default_conf['indicators2'] = ["macd"] default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"] @@ -447,7 +452,7 @@ def test_plot_profit(default_conf, mocker, testdatadir): match=r"No trades found, cannot generate Profit-plot.*"): plot_profit(default_conf) - default_conf['exportfilename'] = testdatadir / "backtest-result_test.json" + default_conf['exportfilename'] = testdatadir / "backtest-result_new.json" plot_profit(default_conf) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 3e02cdb09..12297e5ed 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -188,6 +188,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r (9, 11, 100, 11), # Below min stake (1, 15, 10, 0), # Below min stake and min_stake > max_stake (20, 50, 100, 0), # Below min stake and stake * 1.3 > min_stake + (1000, None, 1000, 1000), # No min-stake-amount could be determined ]) def test_validate_stake_amount(mocker, default_conf, diff --git a/tests/test_worker.py b/tests/test_worker.py index c3773d296..ddca9525b 100644 --- a/tests/test_worker.py +++ b/tests/test_worker.py @@ -43,7 +43,7 @@ def test_worker_stopped(mocker, default_conf, caplog) -> None: worker.freqtrade.state = State.STOPPED state = worker._worker(old_state=State.RUNNING) assert state is State.STOPPED - assert log_has('Changing state to: STOPPED', caplog) + assert log_has('Changing state from RUNNING to: STOPPED', caplog) assert mock_throttle.call_count == 1 diff --git a/tests/testdata/backtest-result_multistrat.json b/tests/testdata/backtest-result_multistrat.json index 553783dfa..80827fa79 100644 --- a/tests/testdata/backtest-result_multistrat.json +++ b/tests/testdata/backtest-result_multistrat.json @@ -1 +1 @@ -{"strategy": {"StrategyTestV2": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}, "TestStrategy": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0,"pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "StrategyTestV2", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}, {"key": "TestStrategy", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} +{"strategy":{"StrategyTestV2":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386},"TestStrategy":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"},{"key":"TestStrategy","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]} \ No newline at end of file diff --git a/tests/testdata/backtest-result_new.json b/tests/testdata/backtest-result_new.json index 84f3806ea..f0d58cba4 100644 --- a/tests/testdata/backtest-result_new.json +++ b/tests/testdata/backtest-result_new.json @@ -1 +1 @@ -{"strategy": {"StrategyTestV2": {"trades": [{"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:20:00+00:00", "trade_duration": 5, "open_rate": 9.64e-05, "close_rate": 0.00010074887218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1037.344398340249, "profit_abs": 0.00399999999999999}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:15:00+00:00", "close_date": "2018-01-10 07:30:00+00:00", "trade_duration": 15, "open_rate": 4.756e-05, "close_rate": 4.9705563909774425e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2102.6072329688814, "profit_abs": 0.00399999999999999}, {"pair": "XLM/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:35:00+00:00", "trade_duration": 10, "open_rate": 3.339e-05, "close_rate": 3.489631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2994.908655286014, "profit_abs": 0.0040000000000000036}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-10 07:25:00+00:00", "close_date": "2018-01-10 07:40:00+00:00", "trade_duration": 15, "open_rate": 9.696e-05, "close_rate": 0.00010133413533834584, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1031.3531353135315, "profit_abs": 0.00399999999999999}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 07:35:00+00:00", "close_date": "2018-01-10 08:35:00+00:00", "trade_duration": 60, "open_rate": 0.0943, "close_rate": 0.09477268170426063, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0604453870625663, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 07:40:00+00:00", "close_date": "2018-01-10 08:10:00+00:00", "trade_duration": 30, "open_rate": 0.02719607, "close_rate": 0.02760503345864661, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.677001860930642, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 08:15:00+00:00", "close_date": "2018-01-10 09:55:00+00:00", "trade_duration": 100, "open_rate": 0.04634952, "close_rate": 0.046581848421052625, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1575196463739, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 14:45:00+00:00", "close_date": "2018-01-10 15:50:00+00:00", "trade_duration": 65, "open_rate": 3.066e-05, "close_rate": 3.081368421052631e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3261.5786040443577, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 16:35:00+00:00", "close_date": "2018-01-10 17:15:00+00:00", "trade_duration": 40, "open_rate": 0.0168999, "close_rate": 0.016984611278195488, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.917194776300452, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 16:40:00+00:00", "close_date": "2018-01-10 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.09132568, "close_rate": 0.0917834528320802, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0949822656672252, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 18:50:00+00:00", "close_date": "2018-01-10 19:45:00+00:00", "trade_duration": 55, "open_rate": 0.08898003, "close_rate": 0.08942604518796991, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1238476768326557, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-10 22:15:00+00:00", "close_date": "2018-01-10 23:00:00+00:00", "trade_duration": 45, "open_rate": 0.08560008, "close_rate": 0.08602915308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1682232072680307, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-10 22:50:00+00:00", "close_date": "2018-01-10 23:20:00+00:00", "trade_duration": 30, "open_rate": 0.00249083, "close_rate": 0.0025282860902255634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 40.147260150231055, "profit_abs": 0.000999999999999987}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-10 23:15:00+00:00", "close_date": "2018-01-11 00:15:00+00:00", "trade_duration": 60, "open_rate": 3.022e-05, "close_rate": 3.037147869674185e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3309.0668431502318, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-10 23:40:00+00:00", "close_date": "2018-01-11 00:05:00+00:00", "trade_duration": 25, "open_rate": 0.002437, "close_rate": 0.0024980776942355883, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.03405826836274, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 00:00:00+00:00", "close_date": "2018-01-11 00:35:00+00:00", "trade_duration": 35, "open_rate": 0.04771803, "close_rate": 0.04843559436090225, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0956439316543456, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-11 03:40:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 45, "open_rate": 3.651e-05, "close_rate": 3.2859000000000005e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2738.9756231169545, "profit_abs": -0.01047499999999997}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 03:55:00+00:00", "close_date": "2018-01-11 04:25:00+00:00", "trade_duration": 30, "open_rate": 0.08824105, "close_rate": 0.08956798308270676, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1332594070446804, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 04:00:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 50, "open_rate": 0.00243, "close_rate": 0.002442180451127819, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 41.1522633744856, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:55:00+00:00", "trade_duration": 25, "open_rate": 0.04545064, "close_rate": 0.046589753784461146, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.200189040242338, "profit_abs": 0.001999999999999988}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:30:00+00:00", "close_date": "2018-01-11 04:50:00+00:00", "trade_duration": 20, "open_rate": 3.372e-05, "close_rate": 3.456511278195488e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2965.599051008304, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 04:55:00+00:00", "close_date": "2018-01-11 05:15:00+00:00", "trade_duration": 20, "open_rate": 0.02644, "close_rate": 0.02710265664160401, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7821482602118004, "profit_abs": 0.001999999999999988}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:20:00+00:00", "close_date": "2018-01-11 12:00:00+00:00", "trade_duration": 40, "open_rate": 0.08812, "close_rate": 0.08856170426065162, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1348161597821154, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 11:35:00+00:00", "close_date": "2018-01-11 12:15:00+00:00", "trade_duration": 40, "open_rate": 0.02683577, "close_rate": 0.026970285137844607, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.7263696923919087, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-11 14:00:00+00:00", "close_date": "2018-01-11 14:25:00+00:00", "trade_duration": 25, "open_rate": 4.919e-05, "close_rate": 5.04228320802005e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.9335230737956, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 19:25:00+00:00", "close_date": "2018-01-11 20:35:00+00:00", "trade_duration": 70, "open_rate": 0.08784896, "close_rate": 0.08828930566416039, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1383174029607181, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:35:00+00:00", "close_date": "2018-01-11 23:30:00+00:00", "trade_duration": 55, "open_rate": 5.105e-05, "close_rate": 5.130588972431077e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1958.8638589618022, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:25:00+00:00", "trade_duration": 30, "open_rate": 3.96e-05, "close_rate": 4.019548872180451e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2525.252525252525, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 22:55:00+00:00", "close_date": "2018-01-11 23:35:00+00:00", "trade_duration": 40, "open_rate": 2.885e-05, "close_rate": 2.899461152882205e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3466.204506065858, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-11 23:30:00+00:00", "close_date": "2018-01-12 00:05:00+00:00", "trade_duration": 35, "open_rate": 0.02645, "close_rate": 0.026847744360902256, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.780718336483932, "profit_abs": 0.0010000000000000148}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-11 23:55:00+00:00", "close_date": "2018-01-12 01:15:00+00:00", "trade_duration": 80, "open_rate": 0.048, "close_rate": 0.04824060150375939, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0833333333333335, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-12 21:15:00+00:00", "close_date": "2018-01-12 21:40:00+00:00", "trade_duration": 25, "open_rate": 4.692e-05, "close_rate": 4.809593984962405e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2131.287297527707, "profit_abs": 0.001999999999999974}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 00:55:00+00:00", "close_date": "2018-01-13 06:20:00+00:00", "trade_duration": 325, "open_rate": 0.00256966, "close_rate": 0.0025825405012531327, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.91565421106294, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 10:55:00+00:00", "close_date": "2018-01-13 11:35:00+00:00", "trade_duration": 40, "open_rate": 6.262e-05, "close_rate": 6.293388471177944e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1596.933886937081, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-13 13:05:00+00:00", "close_date": "2018-01-15 14:10:00+00:00", "trade_duration": 2945, "open_rate": 4.73e-05, "close_rate": 4.753709273182957e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2114.1649048625795, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:30:00+00:00", "close_date": "2018-01-13 14:45:00+00:00", "trade_duration": 75, "open_rate": 6.063e-05, "close_rate": 6.0933909774436085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1649.348507339601, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 13:40:00+00:00", "close_date": "2018-01-13 23:30:00+00:00", "trade_duration": 590, "open_rate": 0.00011082, "close_rate": 0.00011137548872180448, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 902.3641941887746, "profit_abs": -2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 15:15:00+00:00", "close_date": "2018-01-13 15:55:00+00:00", "trade_duration": 40, "open_rate": 5.93e-05, "close_rate": 5.9597243107769415e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1686.3406408094436, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 16:30:00+00:00", "close_date": "2018-01-13 17:10:00+00:00", "trade_duration": 40, "open_rate": 0.04850003, "close_rate": 0.04874313791979949, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.0618543947292407, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-13 22:05:00+00:00", "close_date": "2018-01-14 06:25:00+00:00", "trade_duration": 500, "open_rate": 0.09825019, "close_rate": 0.09874267215538848, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0178097365511456, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 00:20:00+00:00", "close_date": "2018-01-14 22:55:00+00:00", "trade_duration": 1355, "open_rate": 6.018e-05, "close_rate": 6.048165413533834e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1661.681621801263, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 12:45:00+00:00", "close_date": "2018-01-14 13:25:00+00:00", "trade_duration": 40, "open_rate": 0.09758999, "close_rate": 0.0980791628822055, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.024695258191952, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-14 15:30:00+00:00", "close_date": "2018-01-14 16:00:00+00:00", "trade_duration": 30, "open_rate": 0.00311, "close_rate": 0.0031567669172932328, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.154340836012864, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 20:45:00+00:00", "close_date": "2018-01-14 22:15:00+00:00", "trade_duration": 90, "open_rate": 0.00312401, "close_rate": 0.003139669197994987, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 32.010140812609436, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-14 23:35:00+00:00", "close_date": "2018-01-15 00:30:00+00:00", "trade_duration": 55, "open_rate": 0.0174679, "close_rate": 0.017555458395989976, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 5.724786608579165, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-14 23:45:00+00:00", "close_date": "2018-01-15 00:25:00+00:00", "trade_duration": 40, "open_rate": 0.07346846, "close_rate": 0.07383672295739348, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.3611282991367997, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 02:25:00+00:00", "close_date": "2018-01-15 03:05:00+00:00", "trade_duration": 40, "open_rate": 0.097994, "close_rate": 0.09848519799498744, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.020470641059657, "profit_abs": -2.7755575615628914e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 07:20:00+00:00", "close_date": "2018-01-15 08:00:00+00:00", "trade_duration": 40, "open_rate": 0.09659, "close_rate": 0.09707416040100247, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0353038616834043, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-15 08:20:00+00:00", "close_date": "2018-01-15 08:55:00+00:00", "trade_duration": 35, "open_rate": 9.987e-05, "close_rate": 0.00010137180451127818, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1001.3016921998599, "profit_abs": 0.0010000000000000009}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-15 12:10:00+00:00", "close_date": "2018-01-16 02:50:00+00:00", "trade_duration": 880, "open_rate": 0.0948969, "close_rate": 0.09537257368421052, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0537752023511833, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:10:00+00:00", "close_date": "2018-01-15 17:40:00+00:00", "trade_duration": 210, "open_rate": 0.071, "close_rate": 0.07135588972431077, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4084507042253522, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 14:30:00+00:00", "close_date": "2018-01-15 15:10:00+00:00", "trade_duration": 40, "open_rate": 0.04600501, "close_rate": 0.046235611553884705, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.173676301776698, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:10:00+00:00", "close_date": "2018-01-15 19:25:00+00:00", "trade_duration": 75, "open_rate": 9.438e-05, "close_rate": 9.485308270676693e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1059.5465140919687, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 18:35:00+00:00", "close_date": "2018-01-15 19:15:00+00:00", "trade_duration": 40, "open_rate": 0.03040001, "close_rate": 0.030552391002506264, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.2894726021471703, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-15 20:25:00+00:00", "close_date": "2018-01-16 08:25:00+00:00", "trade_duration": 720, "open_rate": 5.837e-05, "close_rate": 5.2533e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1713.2088401576154, "profit_abs": -0.010474999999999984}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-15 20:40:00+00:00", "close_date": "2018-01-15 22:00:00+00:00", "trade_duration": 80, "open_rate": 0.046036, "close_rate": 0.04626675689223057, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.1722130506560084, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 00:30:00+00:00", "close_date": "2018-01-16 01:10:00+00:00", "trade_duration": 40, "open_rate": 0.0028685, "close_rate": 0.0028828784461152877, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 34.86142583231654, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 01:15:00+00:00", "close_date": "2018-01-16 02:35:00+00:00", "trade_duration": 80, "open_rate": 0.06731755, "close_rate": 0.0676549813283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4854967241083492, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 07:45:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 55, "open_rate": 0.09217614, "close_rate": 0.09263817578947368, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0848794492804754, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:55:00+00:00", "trade_duration": 20, "open_rate": 0.0165, "close_rate": 0.016913533834586467, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.0606060606060606, "profit_abs": 0.0020000000000000018}, {"pair": "TRX/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 08:35:00+00:00", "close_date": "2018-01-16 08:40:00+00:00", "trade_duration": 5, "open_rate": 7.953e-05, "close_rate": 8.311781954887218e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1257.387149503332, "profit_abs": 0.00399999999999999}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 08:45:00+00:00", "close_date": "2018-01-16 09:50:00+00:00", "trade_duration": 65, "open_rate": 0.045202, "close_rate": 0.04542857644110275, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2122914915269236, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:45:00+00:00", "trade_duration": 30, "open_rate": 5.248e-05, "close_rate": 5.326917293233082e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1905.487804878049, "profit_abs": 0.0010000000000000009}, {"pair": "XMR/BTC", "profit_percent": 0.0, "open_date": "2018-01-16 09:15:00+00:00", "close_date": "2018-01-16 09:55:00+00:00", "trade_duration": 40, "open_rate": 0.02892318, "close_rate": 0.02906815834586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.457434486802627, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 09:50:00+00:00", "close_date": "2018-01-16 10:10:00+00:00", "trade_duration": 20, "open_rate": 5.158e-05, "close_rate": 5.287273182957392e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1938.735944164405, "profit_abs": 0.001999999999999988}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:35:00+00:00", "trade_duration": 30, "open_rate": 0.02828232, "close_rate": 0.02870761804511278, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5357778286929786, "profit_abs": 0.0010000000000000009}, {"pair": "ZEC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 10:05:00+00:00", "close_date": "2018-01-16 10:40:00+00:00", "trade_duration": 35, "open_rate": 0.04357584, "close_rate": 0.044231115789473675, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.294849623093898, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 13:45:00+00:00", "close_date": "2018-01-16 14:20:00+00:00", "trade_duration": 35, "open_rate": 5.362e-05, "close_rate": 5.442631578947368e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1864.975755315181, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-16 17:30:00+00:00", "close_date": "2018-01-16 18:25:00+00:00", "trade_duration": 55, "open_rate": 5.302e-05, "close_rate": 5.328576441102756e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.0807242549984, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:45:00+00:00", "trade_duration": 30, "open_rate": 0.09129999, "close_rate": 0.09267292218045112, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0952903718828448, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 18:15:00+00:00", "close_date": "2018-01-16 18:35:00+00:00", "trade_duration": 20, "open_rate": 3.808e-05, "close_rate": 3.903438596491228e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2626.0504201680674, "profit_abs": 0.0020000000000000018}, {"pair": "XMR/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-16 19:00:00+00:00", "close_date": "2018-01-16 19:30:00+00:00", "trade_duration": 30, "open_rate": 0.02811012, "close_rate": 0.028532828571428567, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.557437677249333, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 60, "open_rate": 0.00258379, "close_rate": 0.002325411, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.702835756775904, "profit_abs": -0.010474999999999984}, {"pair": "NXT/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:25:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 80, "open_rate": 2.559e-05, "close_rate": 2.3031e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3907.7764751856193, "profit_abs": -0.010474999999999998}, {"pair": "TRX/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-16 21:35:00+00:00", "close_date": "2018-01-16 22:25:00+00:00", "trade_duration": 50, "open_rate": 7.62e-05, "close_rate": 6.858e-05, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1312.3359580052495, "profit_abs": -0.010474999999999984}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:35:00+00:00", "trade_duration": 5, "open_rate": 0.00229844, "close_rate": 0.002402129022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 43.507770487809125, "profit_abs": 0.004000000000000017}, {"pair": "LTC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:30:00+00:00", "close_date": "2018-01-16 22:40:00+00:00", "trade_duration": 10, "open_rate": 0.0151, "close_rate": 0.015781203007518795, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.622516556291391, "profit_abs": 0.00399999999999999}, {"pair": "ETC/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:40:00+00:00", "close_date": "2018-01-16 22:45:00+00:00", "trade_duration": 5, "open_rate": 0.00235676, "close_rate": 0.00246308, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 42.431134269081284, "profit_abs": 0.0040000000000000036}, {"pair": "DASH/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-16 22:45:00+00:00", "close_date": "2018-01-16 23:05:00+00:00", "trade_duration": 20, "open_rate": 0.0630692, "close_rate": 0.06464988170426066, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.585559988076589, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-16 22:50:00+00:00", "close_date": "2018-01-16 22:55:00+00:00", "trade_duration": 5, "open_rate": 2.2e-05, "close_rate": 2.299248120300751e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 4545.454545454546, "profit_abs": 0.003999999999999976}, {"pair": "ADA/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-17 03:30:00+00:00", "close_date": "2018-01-17 04:00:00+00:00", "trade_duration": 30, "open_rate": 4.974e-05, "close_rate": 5.048796992481203e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2010.454362685967, "profit_abs": 0.0010000000000000009}, {"pair": "TRX/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-17 03:55:00+00:00", "close_date": "2018-01-17 04:15:00+00:00", "trade_duration": 20, "open_rate": 7.108e-05, "close_rate": 7.28614536340852e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1406.8655036578502, "profit_abs": 0.001999999999999974}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 09:35:00+00:00", "close_date": "2018-01-17 10:15:00+00:00", "trade_duration": 40, "open_rate": 0.04327, "close_rate": 0.04348689223057644, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.3110700254217704, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:20:00+00:00", "close_date": "2018-01-17 17:00:00+00:00", "trade_duration": 400, "open_rate": 4.997e-05, "close_rate": 5.022047619047618e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2001.2007204322595, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:25:00+00:00", "trade_duration": 55, "open_rate": 0.06836818, "close_rate": 0.06871087764411027, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4626687444363737, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 10:30:00+00:00", "close_date": "2018-01-17 11:10:00+00:00", "trade_duration": 40, "open_rate": 3.63e-05, "close_rate": 3.648195488721804e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2754.8209366391184, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:30:00+00:00", "close_date": "2018-01-17 22:05:00+00:00", "trade_duration": 575, "open_rate": 0.0281, "close_rate": 0.02824085213032581, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5587188612099645, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-17 12:35:00+00:00", "close_date": "2018-01-17 16:55:00+00:00", "trade_duration": 260, "open_rate": 0.08651001, "close_rate": 0.08694364413533832, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1559355963546878, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 05:00:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 55, "open_rate": 5.633e-05, "close_rate": 5.6612355889724306e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1775.2529735487308, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 05:20:00+00:00", "close_date": "2018-01-18 05:55:00+00:00", "trade_duration": 35, "open_rate": 0.06988494, "close_rate": 0.07093584135338346, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.430923457900944, "profit_abs": 0.0010000000000000009}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 07:35:00+00:00", "close_date": "2018-01-18 08:15:00+00:00", "trade_duration": 40, "open_rate": 5.545e-05, "close_rate": 5.572794486215538e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1803.4265103697026, "profit_abs": -1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 09:00:00+00:00", "close_date": "2018-01-18 09:40:00+00:00", "trade_duration": 40, "open_rate": 0.01633527, "close_rate": 0.016417151052631574, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.121723118136401, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 16:40:00+00:00", "close_date": "2018-01-18 17:20:00+00:00", "trade_duration": 40, "open_rate": 0.00269734, "close_rate": 0.002710860501253133, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.073561360451414, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-18 18:05:00+00:00", "close_date": "2018-01-18 18:30:00+00:00", "trade_duration": 25, "open_rate": 4.475e-05, "close_rate": 4.587155388471177e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2234.63687150838, "profit_abs": 0.0020000000000000018}, {"pair": "NXT/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-18 18:25:00+00:00", "close_date": "2018-01-18 18:55:00+00:00", "trade_duration": 30, "open_rate": 2.79e-05, "close_rate": 2.8319548872180444e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3584.2293906810037, "profit_abs": 0.000999999999999987}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 20:10:00+00:00", "close_date": "2018-01-18 20:50:00+00:00", "trade_duration": 40, "open_rate": 0.04439326, "close_rate": 0.04461578260651629, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.2525942001105577, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 21:30:00+00:00", "close_date": "2018-01-19 00:35:00+00:00", "trade_duration": 185, "open_rate": 4.49e-05, "close_rate": 4.51250626566416e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2227.1714922049, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-18 21:55:00+00:00", "close_date": "2018-01-19 05:05:00+00:00", "trade_duration": 430, "open_rate": 0.02855, "close_rate": 0.028693107769423555, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.502626970227671, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 22:10:00+00:00", "close_date": "2018-01-18 22:50:00+00:00", "trade_duration": 40, "open_rate": 5.796e-05, "close_rate": 5.8250526315789473e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1725.3278122843342, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-18 23:50:00+00:00", "close_date": "2018-01-19 00:30:00+00:00", "trade_duration": 40, "open_rate": 0.04340323, "close_rate": 0.04362079005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.303975994413319, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-19 16:45:00+00:00", "close_date": "2018-01-19 17:35:00+00:00", "trade_duration": 50, "open_rate": 0.04454455, "close_rate": 0.04476783095238095, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.244943545282195, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:15:00+00:00", "close_date": "2018-01-19 19:55:00+00:00", "trade_duration": 160, "open_rate": 5.62e-05, "close_rate": 5.648170426065162e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1779.3594306049824, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-19 17:20:00+00:00", "close_date": "2018-01-19 20:15:00+00:00", "trade_duration": 175, "open_rate": 4.339e-05, "close_rate": 4.360749373433584e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2304.6784973496196, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-20 04:45:00+00:00", "close_date": "2018-01-20 17:35:00+00:00", "trade_duration": 770, "open_rate": 0.0001009, "close_rate": 0.00010140576441102755, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 991.0802775024778, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 15:15:00+00:00", "trade_duration": 625, "open_rate": 0.00270505, "close_rate": 0.002718609147869674, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.96789338459548, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 04:50:00+00:00", "close_date": "2018-01-20 07:00:00+00:00", "trade_duration": 130, "open_rate": 0.03000002, "close_rate": 0.030150396040100245, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.3333311111125927, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 09:00:00+00:00", "close_date": "2018-01-20 09:40:00+00:00", "trade_duration": 40, "open_rate": 5.46e-05, "close_rate": 5.4873684210526304e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1831.5018315018317, "profit_abs": -1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.10448878, "open_date": "2018-01-20 18:25:00+00:00", "close_date": "2018-01-25 03:50:00+00:00", "trade_duration": 6325, "open_rate": 0.03082222, "close_rate": 0.027739998, "open_at_end": false, "sell_reason": "stop_loss", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.244412634781012, "profit_abs": -0.010474999999999998}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-20 22:25:00+00:00", "close_date": "2018-01-20 23:15:00+00:00", "trade_duration": 50, "open_rate": 0.08969999, "close_rate": 0.09014961401002504, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1148273260677064, "profit_abs": 0.0}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 02:50:00+00:00", "close_date": "2018-01-21 14:30:00+00:00", "trade_duration": 700, "open_rate": 0.01632501, "close_rate": 0.01640683962406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.125570520324337, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 10:20:00+00:00", "close_date": "2018-01-21 11:00:00+00:00", "trade_duration": 40, "open_rate": 0.070538, "close_rate": 0.07089157393483708, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.417675579120474, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 15:50:00+00:00", "close_date": "2018-01-21 18:45:00+00:00", "trade_duration": 175, "open_rate": 5.301e-05, "close_rate": 5.327571428571427e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1886.4365214110546, "profit_abs": -2.7755575615628914e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-21 16:20:00+00:00", "close_date": "2018-01-21 17:00:00+00:00", "trade_duration": 40, "open_rate": 3.955e-05, "close_rate": 3.9748245614035085e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2528.4450063211125, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:45:00+00:00", "trade_duration": 30, "open_rate": 0.00258505, "close_rate": 0.002623922932330827, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.6839712964933, "profit_abs": 0.0010000000000000009}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-21 21:15:00+00:00", "close_date": "2018-01-21 21:55:00+00:00", "trade_duration": 40, "open_rate": 3.903e-05, "close_rate": 3.922563909774435e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2562.1316935690497, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 00:35:00+00:00", "close_date": "2018-01-22 10:35:00+00:00", "trade_duration": 600, "open_rate": 5.236e-05, "close_rate": 5.262245614035087e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1909.8548510313217, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 01:30:00+00:00", "close_date": "2018-01-22 02:10:00+00:00", "trade_duration": 40, "open_rate": 9.028e-05, "close_rate": 9.07325313283208e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1107.6650420912717, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 12:25:00+00:00", "close_date": "2018-01-22 14:35:00+00:00", "trade_duration": 130, "open_rate": 0.002687, "close_rate": 0.002700468671679198, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 37.21622627465575, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 13:15:00+00:00", "close_date": "2018-01-22 13:55:00+00:00", "trade_duration": 40, "open_rate": 4.168e-05, "close_rate": 4.188892230576441e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2399.232245681382, "profit_abs": 1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-22 14:00:00+00:00", "close_date": "2018-01-22 14:30:00+00:00", "trade_duration": 30, "open_rate": 8.821e-05, "close_rate": 8.953646616541353e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1133.6583153837435, "profit_abs": 0.0010000000000000148}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-22 15:55:00+00:00", "close_date": "2018-01-22 16:40:00+00:00", "trade_duration": 45, "open_rate": 5.172e-05, "close_rate": 5.1979248120300745e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1933.4880123743235, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-22 16:05:00+00:00", "close_date": "2018-01-22 16:25:00+00:00", "trade_duration": 20, "open_rate": 3.026e-05, "close_rate": 3.101839598997494e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3304.692663582287, "profit_abs": 0.0020000000000000157}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 19:50:00+00:00", "close_date": "2018-01-23 00:10:00+00:00", "trade_duration": 260, "open_rate": 0.07064, "close_rate": 0.07099408521303258, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.415628539071348, "profit_abs": 1.3877787807814457e-17}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-22 21:25:00+00:00", "close_date": "2018-01-22 22:05:00+00:00", "trade_duration": 40, "open_rate": 0.01644483, "close_rate": 0.01652726022556391, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.080938507725528, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-23 00:05:00+00:00", "close_date": "2018-01-23 00:35:00+00:00", "trade_duration": 30, "open_rate": 4.331e-05, "close_rate": 4.3961278195488714e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2308.935580697299, "profit_abs": 0.0010000000000000148}, {"pair": "NXT/BTC", "profit_percent": 0.01995012, "open_date": "2018-01-23 01:50:00+00:00", "close_date": "2018-01-23 02:15:00+00:00", "trade_duration": 25, "open_rate": 3.2e-05, "close_rate": 3.2802005012531326e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3125.0000000000005, "profit_abs": 0.0020000000000000018}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 04:25:00+00:00", "close_date": "2018-01-23 05:15:00+00:00", "trade_duration": 50, "open_rate": 0.09167706, "close_rate": 0.09213659413533835, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0907854156754153, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 07:35:00+00:00", "close_date": "2018-01-23 09:00:00+00:00", "trade_duration": 85, "open_rate": 0.0692498, "close_rate": 0.06959691679197995, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4440474918339115, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 10:50:00+00:00", "close_date": "2018-01-23 13:05:00+00:00", "trade_duration": 135, "open_rate": 3.182e-05, "close_rate": 3.197949874686716e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3142.677561282213, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 11:05:00+00:00", "close_date": "2018-01-23 16:05:00+00:00", "trade_duration": 300, "open_rate": 0.04088, "close_rate": 0.04108491228070175, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4461839530332683, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 14:55:00+00:00", "close_date": "2018-01-23 15:35:00+00:00", "trade_duration": 40, "open_rate": 5.15e-05, "close_rate": 5.175814536340851e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1941.747572815534, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-23 16:35:00+00:00", "close_date": "2018-01-24 00:05:00+00:00", "trade_duration": 450, "open_rate": 0.09071698, "close_rate": 0.09117170170426064, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.1023294646713329, "profit_abs": 0.0}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 17:25:00+00:00", "close_date": "2018-01-23 18:45:00+00:00", "trade_duration": 80, "open_rate": 3.128e-05, "close_rate": 3.1436791979949865e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3196.9309462915603, "profit_abs": -2.7755575615628914e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 20:15:00+00:00", "close_date": "2018-01-23 22:00:00+00:00", "trade_duration": 105, "open_rate": 9.555e-05, "close_rate": 9.602894736842104e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1046.5724751439038, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 22:30:00+00:00", "close_date": "2018-01-23 23:10:00+00:00", "trade_duration": 40, "open_rate": 0.04080001, "close_rate": 0.0410045213283208, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.450979791426522, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-23 23:50:00+00:00", "close_date": "2018-01-24 03:35:00+00:00", "trade_duration": 225, "open_rate": 5.163e-05, "close_rate": 5.18887969924812e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1936.8584156498162, "profit_abs": 1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 00:20:00+00:00", "close_date": "2018-01-24 01:50:00+00:00", "trade_duration": 90, "open_rate": 0.04040781, "close_rate": 0.04061035541353383, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.474769110228938, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 06:45:00+00:00", "close_date": "2018-01-24 07:25:00+00:00", "trade_duration": 40, "open_rate": 5.132e-05, "close_rate": 5.157724310776942e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1948.5580670303975, "profit_abs": 0.0}, {"pair": "ADA/BTC", "profit_percent": 0.03990025, "open_date": "2018-01-24 14:15:00+00:00", "close_date": "2018-01-24 14:25:00+00:00", "trade_duration": 10, "open_rate": 5.198e-05, "close_rate": 5.432496240601503e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1923.8168526356292, "profit_abs": 0.0040000000000000036}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 14:50:00+00:00", "close_date": "2018-01-24 16:35:00+00:00", "trade_duration": 105, "open_rate": 3.054e-05, "close_rate": 3.069308270676692e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3274.3942370661425, "profit_abs": 0.0}, {"pair": "TRX/BTC", "profit_percent": 0.0, "open_date": "2018-01-24 15:10:00+00:00", "close_date": "2018-01-24 16:15:00+00:00", "trade_duration": 65, "open_rate": 9.263e-05, "close_rate": 9.309431077694236e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1079.5638562020945, "profit_abs": 2.7755575615628914e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-24 22:40:00+00:00", "close_date": "2018-01-24 23:25:00+00:00", "trade_duration": 45, "open_rate": 5.514e-05, "close_rate": 5.54163909774436e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1813.5654697134569, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 00:50:00+00:00", "close_date": "2018-01-25 01:30:00+00:00", "trade_duration": 40, "open_rate": 4.921e-05, "close_rate": 4.9456666666666664e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2032.1072952651903, "profit_abs": 1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-25 08:15:00+00:00", "close_date": "2018-01-25 12:15:00+00:00", "trade_duration": 240, "open_rate": 0.0026, "close_rate": 0.002613032581453634, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.46153846153847, "profit_abs": 1.3877787807814457e-17}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 10:25:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 350, "open_rate": 0.02799871, "close_rate": 0.028139054411027563, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.571593119825878, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 11:00:00+00:00", "close_date": "2018-01-25 11:45:00+00:00", "trade_duration": 45, "open_rate": 0.04078902, "close_rate": 0.0409934762406015, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4516401717913303, "profit_abs": -1.3877787807814457e-17}, {"pair": "NXT/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:05:00+00:00", "close_date": "2018-01-25 13:45:00+00:00", "trade_duration": 40, "open_rate": 2.89e-05, "close_rate": 2.904486215538847e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3460.2076124567475, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 13:20:00+00:00", "close_date": "2018-01-25 14:05:00+00:00", "trade_duration": 45, "open_rate": 0.041103, "close_rate": 0.04130903007518797, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4329124394813033, "profit_abs": 1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-25 15:45:00+00:00", "close_date": "2018-01-25 16:15:00+00:00", "trade_duration": 30, "open_rate": 5.428e-05, "close_rate": 5.509624060150376e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1842.2991893883568, "profit_abs": 0.0010000000000000148}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 17:45:00+00:00", "close_date": "2018-01-25 23:15:00+00:00", "trade_duration": 330, "open_rate": 5.414e-05, "close_rate": 5.441137844611528e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1847.063169560399, "profit_abs": -1.3877787807814457e-17}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-25 21:15:00+00:00", "close_date": "2018-01-25 21:55:00+00:00", "trade_duration": 40, "open_rate": 0.04140777, "close_rate": 0.0416153277443609, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.415005686130888, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 02:05:00+00:00", "close_date": "2018-01-26 02:45:00+00:00", "trade_duration": 40, "open_rate": 0.00254309, "close_rate": 0.002555837318295739, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.32224183965177, "profit_abs": 1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 02:55:00+00:00", "close_date": "2018-01-26 15:10:00+00:00", "trade_duration": 735, "open_rate": 5.607e-05, "close_rate": 5.6351052631578935e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1783.4849295523454, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETC/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 06:10:00+00:00", "close_date": "2018-01-26 09:25:00+00:00", "trade_duration": 195, "open_rate": 0.00253806, "close_rate": 0.0025507821052631577, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 39.400171784748984, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 07:25:00+00:00", "close_date": "2018-01-26 09:55:00+00:00", "trade_duration": 150, "open_rate": 0.0415, "close_rate": 0.04170802005012531, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4096385542168677, "profit_abs": 0.0}, {"pair": "XLM/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-26 09:55:00+00:00", "close_date": "2018-01-26 10:25:00+00:00", "trade_duration": 30, "open_rate": 5.321e-05, "close_rate": 5.401015037593984e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1879.3459875963165, "profit_abs": 0.000999999999999987}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-26 16:05:00+00:00", "close_date": "2018-01-26 16:45:00+00:00", "trade_duration": 40, "open_rate": 0.02772046, "close_rate": 0.02785940967418546, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.6074437437185387, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": 0.0, "open_date": "2018-01-26 23:35:00+00:00", "close_date": "2018-01-27 00:15:00+00:00", "trade_duration": 40, "open_rate": 0.09461341, "close_rate": 0.09508766268170424, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0569326272036914, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 00:35:00+00:00", "close_date": "2018-01-27 01:30:00+00:00", "trade_duration": 55, "open_rate": 5.615e-05, "close_rate": 5.643145363408521e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1780.9439002671415, "profit_abs": -1.3877787807814457e-17}, {"pair": "ADA/BTC", "profit_percent": -0.07877175, "open_date": "2018-01-27 00:45:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 4560, "open_rate": 5.556e-05, "close_rate": 5.144e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1799.8560115190785, "profit_abs": -0.007896868250539965}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 02:30:00+00:00", "close_date": "2018-01-27 11:25:00+00:00", "trade_duration": 535, "open_rate": 0.06900001, "close_rate": 0.06934587471177944, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492751522789635, "profit_abs": 0.0}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 06:25:00+00:00", "close_date": "2018-01-27 07:05:00+00:00", "trade_duration": 40, "open_rate": 0.09449985, "close_rate": 0.0949735334586466, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.058202737887944, "profit_abs": 0.0}, {"pair": "ZEC/BTC", "profit_percent": -0.04815133, "open_date": "2018-01-27 09:40:00+00:00", "close_date": "2018-01-30 04:40:00+00:00", "trade_duration": 4020, "open_rate": 0.0410697, "close_rate": 0.03928809, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 2.4348850855983852, "profit_abs": -0.004827170578309559}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 11:45:00+00:00", "close_date": "2018-01-27 12:30:00+00:00", "trade_duration": 45, "open_rate": 0.0285, "close_rate": 0.02864285714285714, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.5087719298245617, "profit_abs": 0.0}, {"pair": "XMR/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 12:35:00+00:00", "close_date": "2018-01-27 15:25:00+00:00", "trade_duration": 170, "open_rate": 0.02866372, "close_rate": 0.02880739779448621, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 3.4887307020861216, "profit_abs": -1.3877787807814457e-17}, {"pair": "ETH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 15:50:00+00:00", "close_date": "2018-01-27 16:50:00+00:00", "trade_duration": 60, "open_rate": 0.095381, "close_rate": 0.09585910025062656, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.0484268355332824, "profit_abs": 1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 17:05:00+00:00", "close_date": "2018-01-27 17:45:00+00:00", "trade_duration": 40, "open_rate": 0.06759092, "close_rate": 0.06792972160401002, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4794886650455417, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": -0.0, "open_date": "2018-01-27 23:40:00+00:00", "close_date": "2018-01-28 01:05:00+00:00", "trade_duration": 85, "open_rate": 0.00258501, "close_rate": 0.002597967443609022, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 38.684569885609726, "profit_abs": -1.3877787807814457e-17}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 02:25:00+00:00", "close_date": "2018-01-28 08:10:00+00:00", "trade_duration": 345, "open_rate": 0.06698502, "close_rate": 0.0673207845112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4928710926711672, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-28 10:25:00+00:00", "close_date": "2018-01-28 16:30:00+00:00", "trade_duration": 365, "open_rate": 0.0677177, "close_rate": 0.06805713709273183, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4767187899175547, "profit_abs": -1.3877787807814457e-17}, {"pair": "XLM/BTC", "profit_percent": 0.0, "open_date": "2018-01-28 20:35:00+00:00", "close_date": "2018-01-28 21:35:00+00:00", "trade_duration": 60, "open_rate": 5.215e-05, "close_rate": 5.2411403508771925e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1917.5455417066157, "profit_abs": 0.0}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-28 22:00:00+00:00", "close_date": "2018-01-28 22:30:00+00:00", "trade_duration": 30, "open_rate": 0.00273809, "close_rate": 0.002779264285714285, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.5218089982433, "profit_abs": 0.0010000000000000009}, {"pair": "ETC/BTC", "profit_percent": 0.00997506, "open_date": "2018-01-29 00:00:00+00:00", "close_date": "2018-01-29 00:30:00+00:00", "trade_duration": 30, "open_rate": 0.00274632, "close_rate": 0.002787618045112782, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 36.412362725392526, "profit_abs": 0.0010000000000000148}, {"pair": "LTC/BTC", "profit_percent": 0.0, "open_date": "2018-01-29 02:15:00+00:00", "close_date": "2018-01-29 03:00:00+00:00", "trade_duration": 45, "open_rate": 0.01622478, "close_rate": 0.016306107218045113, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 6.163411768911504, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 03:05:00+00:00", "close_date": "2018-01-29 03:45:00+00:00", "trade_duration": 40, "open_rate": 0.069, "close_rate": 0.06934586466165413, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4492753623188406, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 05:20:00+00:00", "close_date": "2018-01-29 06:55:00+00:00", "trade_duration": 95, "open_rate": 8.755e-05, "close_rate": 8.798884711779448e-05, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1142.204454597373, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 07:00:00+00:00", "close_date": "2018-01-29 19:25:00+00:00", "trade_duration": 745, "open_rate": 0.06825763, "close_rate": 0.06859977350877192, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4650376815016872, "profit_abs": 0.0}, {"pair": "DASH/BTC", "profit_percent": -0.0, "open_date": "2018-01-29 19:45:00+00:00", "close_date": "2018-01-29 20:25:00+00:00", "trade_duration": 40, "open_rate": 0.06713892, "close_rate": 0.06747545593984962, "open_at_end": false, "sell_reason": "roi", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1.4894490408841845, "profit_abs": -1.3877787807814457e-17}, {"pair": "TRX/BTC", "profit_percent": -0.0199116, "open_date": "2018-01-29 23:30:00+00:00", "close_date": "2018-01-30 04:45:00+00:00", "trade_duration": 315, "open_rate": 8.934e-05, "close_rate": 8.8e-05, "open_at_end": true, "sell_reason": "force_sell", "fee_open": 0.0025, "fee_close": 0.0025, "amount": 1119.3194537721067, "profit_abs": -0.0019961383478844796}], "results_per_pair": [{"key": "TRX/BTC", "trades": 15, "profit_mean": 0.0023467073333333323, "profit_mean_pct": 0.23467073333333321, "profit_sum": 0.035200609999999986, "profit_sum_pct": 3.5200609999999988, "profit_total_abs": 0.0035288616521155086, "profit_total_pct": 1.1733536666666662, "duration_avg": "2:28:00", "wins": 9, "draws": 2, "losses": 4}, {"key": "ADA/BTC", "trades": 29, "profit_mean": -0.0011598141379310352, "profit_mean_pct": -0.11598141379310352, "profit_sum": -0.03363461000000002, "profit_sum_pct": -3.3634610000000023, "profit_total_abs": -0.0033718682505400333, "profit_total_pct": -1.1211536666666675, "duration_avg": "5:35:00", "wins": 9, "draws": 11, "losses": 9}, {"key": "XLM/BTC", "trades": 21, "profit_mean": 0.0026243899999999994, "profit_mean_pct": 0.2624389999999999, "profit_sum": 0.05511218999999999, "profit_sum_pct": 5.511218999999999, "profit_total_abs": 0.005525000000000002, "profit_total_pct": 1.8370729999999995, "duration_avg": "3:21:00", "wins": 12, "draws": 3, "losses": 6}, {"key": "ETH/BTC", "trades": 21, "profit_mean": 0.0009500057142857142, "profit_mean_pct": 0.09500057142857142, "profit_sum": 0.01995012, "profit_sum_pct": 1.9950119999999998, "profit_total_abs": 0.0019999999999999463, "profit_total_pct": 0.6650039999999999, "duration_avg": "2:17:00", "wins": 5, "draws": 10, "losses": 6}, {"key": "XMR/BTC", "trades": 16, "profit_mean": -0.0027899012500000007, "profit_mean_pct": -0.2789901250000001, "profit_sum": -0.04463842000000001, "profit_sum_pct": -4.463842000000001, "profit_total_abs": -0.0044750000000000345, "profit_total_pct": -1.4879473333333337, "duration_avg": "8:41:00", "wins": 6, "draws": 5, "losses": 5}, {"key": "ZEC/BTC", "trades": 21, "profit_mean": -0.00039290904761904774, "profit_mean_pct": -0.03929090476190478, "profit_sum": -0.008251090000000003, "profit_sum_pct": -0.8251090000000003, "profit_total_abs": -0.000827170578309569, "profit_total_pct": -0.27503633333333344, "duration_avg": "4:17:00", "wins": 8, "draws": 7, "losses": 6}, {"key": "NXT/BTC", "trades": 12, "profit_mean": -0.0012261025000000006, "profit_mean_pct": -0.12261025000000006, "profit_sum": -0.014713230000000008, "profit_sum_pct": -1.4713230000000008, "profit_total_abs": -0.0014750000000000874, "profit_total_pct": -0.4904410000000003, "duration_avg": "0:57:00", "wins": 4, "draws": 3, "losses": 5}, {"key": "LTC/BTC", "trades": 8, "profit_mean": 0.00748129625, "profit_mean_pct": 0.748129625, "profit_sum": 0.05985037, "profit_sum_pct": 5.985037, "profit_total_abs": 0.006000000000000019, "profit_total_pct": 1.9950123333333334, "duration_avg": "1:59:00", "wins": 5, "draws": 2, "losses": 1}, {"key": "ETC/BTC", "trades": 20, "profit_mean": 0.0022568569999999997, "profit_mean_pct": 0.22568569999999996, "profit_sum": 0.04513713999999999, "profit_sum_pct": 4.513713999999999, "profit_total_abs": 0.004525000000000001, "profit_total_pct": 1.504571333333333, "duration_avg": "1:45:00", "wins": 11, "draws": 4, "losses": 5}, {"key": "DASH/BTC", "trades": 16, "profit_mean": 0.0018703237499999997, "profit_mean_pct": 0.18703237499999997, "profit_sum": 0.029925179999999996, "profit_sum_pct": 2.9925179999999996, "profit_total_abs": 0.002999999999999961, "profit_total_pct": 0.9975059999999999, "duration_avg": "3:03:00", "wins": 4, "draws": 7, "losses": 5}, {"key": "TOTAL", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}], "sell_reason_summary": [{"sell_reason": "roi", "trades": 170, "wins": 73, "draws": 54, "losses": 43, "profit_mean": 0.005398268352941177, "profit_mean_pct": 0.54, "profit_sum": 0.91770562, "profit_sum_pct": 91.77, "profit_total_abs": 0.09199999999999964, "profit_pct_total": 30.59}, {"sell_reason": "stop_loss", "trades": 6, "wins": 0, "draws": 0, "losses": 6, "profit_mean": -0.10448878000000002, "profit_mean_pct": -10.45, "profit_sum": -0.6269326800000001, "profit_sum_pct": -62.69, "profit_total_abs": -0.06284999999999992, "profit_pct_total": -20.9}, {"sell_reason": "force_sell", "trades": 3, "wins": 0, "draws": 0, "losses": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.89, "profit_sum": -0.14683468, "profit_sum_pct": -14.68, "profit_total_abs": -0.014720177176734003, "profit_pct_total": -4.89}], "left_open_trades": [{"key": "TRX/BTC", "trades": 1, "profit_mean": -0.0199116, "profit_mean_pct": -1.9911600000000003, "profit_sum": -0.0199116, "profit_sum_pct": -1.9911600000000003, "profit_total_abs": -0.0019961383478844796, "profit_total_pct": -0.6637200000000001, "duration_avg": "5:15:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ADA/BTC", "trades": 1, "profit_mean": -0.07877175, "profit_mean_pct": -7.877175, "profit_sum": -0.07877175, "profit_sum_pct": -7.877175, "profit_total_abs": -0.007896868250539965, "profit_total_pct": -2.625725, "duration_avg": "3 days, 4:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "ZEC/BTC", "trades": 1, "profit_mean": -0.04815133, "profit_mean_pct": -4.815133, "profit_sum": -0.04815133, "profit_sum_pct": -4.815133, "profit_total_abs": -0.004827170578309559, "profit_total_pct": -1.6050443333333335, "duration_avg": "2 days, 19:00:00", "wins": 0, "draws": 0, "losses": 1}, {"key": "TOTAL", "trades": 3, "profit_mean": -0.04894489333333333, "profit_mean_pct": -4.894489333333333, "profit_sum": -0.14683468, "profit_sum_pct": -14.683468, "profit_total_abs": -0.014720177176734003, "profit_total_pct": -4.8944893333333335, "duration_avg": "2 days, 1:25:00", "wins": 0, "draws": 0, "losses": 3}], "total_trades": 179, "backtest_start": "2018-01-30 04:45:00+00:00", "backtest_start_ts": 1517287500, "backtest_end": "2018-01-30 04:45:00+00:00", "backtest_end_ts": 1517287500, "backtest_days": 0, "trades_per_day": null, "market_change": 0.25, "stake_amount": 0.1, "max_drawdown": 0.21142322000000008, "drawdown_start": "2018-01-24 14:25:00+00:00", "drawdown_start_ts": 1516803900.0, "drawdown_end": "2018-01-30 04:45:00+00:00", "drawdown_end_ts": 1517287500.0, "pairlist": ["TRX/BTC", "ADA/BTC", "XLM/BTC", "ETH/BTC", "XMR/BTC", "ZEC/BTC","NXT/BTC", "LTC/BTC", "ETC/BTC", "DASH/BTC"]}}, "strategy_comparison": [{"key": "StrategyTestV2", "trades": 179, "profit_mean": 0.0008041243575418989, "profit_mean_pct": 0.0804124357541899, "profit_sum": 0.1439382599999999, "profit_sum_pct": 14.39382599999999, "profit_total_abs": 0.014429822823265714, "profit_total_pct": 4.797941999999996, "duration_avg": "3:40:00", "wins": 73, "draws": 54, "losses": 52}]} +{"strategy":{"StrategyTestV2":{"trades":[{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.37344398340249,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:20:00+00:00","open_rate":9.64e-05,"close_rate":0.00010074887218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":4.348872180451118e-06,"sell_reason":"roi","initial_stop_loss_abs":8.676e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.676e-05,"stop_loss_ratio":0.1,"min_rate":9.64e-05,"max_rate":0.00010074887218045112,"is_open":false,"buy_tag":null,"open_timestamp":1515568500000.0,"close_timestamp":1515568800000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":21.026072329688816,"open_date":"2018-01-10 07:15:00+00:00","close_date":"2018-01-10 07:30:00+00:00","open_rate":4.756e-05,"close_rate":4.9705563909774425e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":2.1455639097744267e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2804e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2804e-05,"stop_loss_ratio":0.1,"min_rate":4.756e-05,"max_rate":4.9705563909774425e-05,"is_open":false,"buy_tag":"buy_tag","open_timestamp":1515568500000.0,"close_timestamp":1515569400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.94908655286014,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:35:00+00:00","open_rate":3.339e-05,"close_rate":3.489631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":1.506315789473681e-06,"sell_reason":"roi","initial_stop_loss_abs":3.0050999999999997e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0050999999999997e-05,"stop_loss_ratio":0.1,"min_rate":3.339e-05,"max_rate":3.489631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515569700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.313531353135314,"open_date":"2018-01-10 07:25:00+00:00","close_date":"2018-01-10 07:40:00+00:00","open_rate":9.696e-05,"close_rate":0.00010133413533834584,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":15,"profit_ratio":0.03990025,"profit_abs":4.3741353383458455e-06,"sell_reason":"roi","initial_stop_loss_abs":8.7264e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.7264e-05,"stop_loss_ratio":0.1,"min_rate":9.696e-05,"max_rate":0.00010133413533834584,"is_open":false,"buy_tag":null,"open_timestamp":1515569100000.0,"close_timestamp":1515570000000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010604453870625663,"open_date":"2018-01-10 07:35:00+00:00","close_date":"2018-01-10 08:35:00+00:00","open_rate":0.0943,"close_rate":0.09477268170426063,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.0004726817042606385,"sell_reason":"roi","initial_stop_loss_abs":0.08487,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08487,"stop_loss_ratio":0.1,"min_rate":0.0943,"max_rate":0.09477268170426063,"is_open":false,"buy_tag":null,"open_timestamp":1515569700000.0,"close_timestamp":1515573300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03677001860930642,"open_date":"2018-01-10 07:40:00+00:00","close_date":"2018-01-10 08:10:00+00:00","open_rate":0.02719607,"close_rate":0.02760503345864661,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00040896345864661204,"sell_reason":"roi","initial_stop_loss_abs":0.024476463,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024476463,"stop_loss_ratio":0.1,"min_rate":0.02719607,"max_rate":0.02760503345864661,"is_open":false,"buy_tag":null,"open_timestamp":1515570000000.0,"close_timestamp":1515571800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021575196463739,"open_date":"2018-01-10 08:15:00+00:00","close_date":"2018-01-10 09:55:00+00:00","open_rate":0.04634952,"close_rate":0.046581848421052625,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":100,"profit_ratio":0.0,"profit_abs":0.0002323284210526272,"sell_reason":"roi","initial_stop_loss_abs":0.041714568,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041714568,"stop_loss_ratio":0.1,"min_rate":0.04634952,"max_rate":0.046581848421052625,"is_open":false,"buy_tag":null,"open_timestamp":1515572100000.0,"close_timestamp":1515578100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.615786040443574,"open_date":"2018-01-10 14:45:00+00:00","close_date":"2018-01-10 15:50:00+00:00","open_rate":3.066e-05,"close_rate":3.081368421052631e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":1.5368421052630647e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7594e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7594e-05,"stop_loss_ratio":0.1,"min_rate":3.066e-05,"max_rate":3.081368421052631e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515595500000.0,"close_timestamp":1515599400000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.05917194776300452,"open_date":"2018-01-10 16:35:00+00:00","close_date":"2018-01-10 17:15:00+00:00","open_rate":0.0168999,"close_rate":0.016984611278195488,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.471127819548868e-05,"sell_reason":"roi","initial_stop_loss_abs":0.01520991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01520991,"stop_loss_ratio":0.1,"min_rate":0.0168999,"max_rate":0.016984611278195488,"is_open":false,"buy_tag":null,"open_timestamp":1515602100000.0,"close_timestamp":1515604500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010949822656672253,"open_date":"2018-01-10 16:40:00+00:00","close_date":"2018-01-10 17:20:00+00:00","open_rate":0.09132568,"close_rate":0.0917834528320802,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004577728320801916,"sell_reason":"roi","initial_stop_loss_abs":0.08219311200000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08219311200000001,"stop_loss_ratio":0.1,"min_rate":0.09132568,"max_rate":0.0917834528320802,"is_open":false,"buy_tag":null,"open_timestamp":1515602400000.0,"close_timestamp":1515604800000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011238476768326556,"open_date":"2018-01-10 18:50:00+00:00","close_date":"2018-01-10 19:45:00+00:00","open_rate":0.08898003,"close_rate":0.08942604518796991,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00044601518796991146,"sell_reason":"roi","initial_stop_loss_abs":0.080082027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080082027,"stop_loss_ratio":0.1,"min_rate":0.08898003,"max_rate":0.08942604518796991,"is_open":false,"buy_tag":null,"open_timestamp":1515610200000.0,"close_timestamp":1515613500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011682232072680307,"open_date":"2018-01-10 22:15:00+00:00","close_date":"2018-01-10 23:00:00+00:00","open_rate":0.08560008,"close_rate":0.08602915308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":0.00042907308270676014,"sell_reason":"roi","initial_stop_loss_abs":0.077040072,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077040072,"stop_loss_ratio":0.1,"min_rate":0.08560008,"max_rate":0.08602915308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515622500000.0,"close_timestamp":1515625200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4014726015023105,"open_date":"2018-01-10 22:50:00+00:00","close_date":"2018-01-10 23:20:00+00:00","open_rate":0.00249083,"close_rate":0.0025282860902255634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.745609022556351e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002241747,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002241747,"stop_loss_ratio":0.1,"min_rate":0.00249083,"max_rate":0.0025282860902255634,"is_open":false,"buy_tag":null,"open_timestamp":1515624600000.0,"close_timestamp":1515626400000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.090668431502316,"open_date":"2018-01-10 23:15:00+00:00","close_date":"2018-01-11 00:15:00+00:00","open_rate":3.022e-05,"close_rate":3.037147869674185e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":1.5147869674185174e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7198e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7198e-05,"stop_loss_ratio":0.1,"min_rate":3.022e-05,"max_rate":3.037147869674185e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515626100000.0,"close_timestamp":1515629700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.41034058268362744,"open_date":"2018-01-10 23:40:00+00:00","close_date":"2018-01-11 00:05:00+00:00","open_rate":0.002437,"close_rate":0.0024980776942355883,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":6.107769423558838e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0021933,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0021933,"stop_loss_ratio":0.1,"min_rate":0.002437,"max_rate":0.0024980776942355883,"is_open":false,"buy_tag":null,"open_timestamp":1515627600000.0,"close_timestamp":1515629100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02095643931654345,"open_date":"2018-01-11 00:00:00+00:00","close_date":"2018-01-11 00:35:00+00:00","open_rate":0.04771803,"close_rate":0.04843559436090225,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0007175643609022495,"sell_reason":"roi","initial_stop_loss_abs":0.042946227000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.042946227000000003,"stop_loss_ratio":0.1,"min_rate":0.04771803,"max_rate":0.04843559436090225,"is_open":false,"buy_tag":null,"open_timestamp":1515628800000.0,"close_timestamp":1515630900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.389756231169542,"open_date":"2018-01-11 03:40:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":3.651e-05,"close_rate":3.2859000000000005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.10448878,"profit_abs":-3.650999999999996e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":3.2859000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2859000000000005e-05,"stop_loss_ratio":0.1,"min_rate":3.2859000000000005e-05,"max_rate":3.651e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515642000000.0,"close_timestamp":1515644700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011332594070446804,"open_date":"2018-01-11 03:55:00+00:00","close_date":"2018-01-11 04:25:00+00:00","open_rate":0.08824105,"close_rate":0.08956798308270676,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013269330827067605,"sell_reason":"roi","initial_stop_loss_abs":0.079416945,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079416945,"stop_loss_ratio":0.1,"min_rate":0.08824105,"max_rate":0.08956798308270676,"is_open":false,"buy_tag":null,"open_timestamp":1515642900000.0,"close_timestamp":1515644700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.411522633744856,"open_date":"2018-01-11 04:00:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":0.00243,"close_rate":0.002442180451127819,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":1.2180451127819219e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002187,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002187,"stop_loss_ratio":0.1,"min_rate":0.00243,"max_rate":0.002442180451127819,"is_open":false,"buy_tag":null,"open_timestamp":1515643200000.0,"close_timestamp":1515646200000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022001890402423376,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:55:00+00:00","open_rate":0.04545064,"close_rate":0.046589753784461146,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":0.001139113784461146,"sell_reason":"roi","initial_stop_loss_abs":0.040905576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040905576,"stop_loss_ratio":0.1,"min_rate":0.04545064,"max_rate":0.046589753784461146,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":29.655990510083036,"open_date":"2018-01-11 04:30:00+00:00","close_date":"2018-01-11 04:50:00+00:00","open_rate":3.372e-05,"close_rate":3.456511278195488e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":8.4511278195488e-07,"sell_reason":"roi","initial_stop_loss_abs":3.0348e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.0348e-05,"stop_loss_ratio":0.1,"min_rate":3.372e-05,"max_rate":3.456511278195488e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515645000000.0,"close_timestamp":1515646200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037821482602118005,"open_date":"2018-01-11 04:55:00+00:00","close_date":"2018-01-11 05:15:00+00:00","open_rate":0.02644,"close_rate":0.02710265664160401,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0006626566416040071,"sell_reason":"roi","initial_stop_loss_abs":0.023796,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023796,"stop_loss_ratio":0.1,"min_rate":0.02644,"max_rate":0.02710265664160401,"is_open":false,"buy_tag":null,"open_timestamp":1515646500000.0,"close_timestamp":1515647700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011348161597821153,"open_date":"2018-01-11 11:20:00+00:00","close_date":"2018-01-11 12:00:00+00:00","open_rate":0.08812,"close_rate":0.08856170426065162,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004417042606516125,"sell_reason":"roi","initial_stop_loss_abs":0.079308,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079308,"stop_loss_ratio":0.1,"min_rate":0.08812,"max_rate":0.08856170426065162,"is_open":false,"buy_tag":null,"open_timestamp":1515669600000.0,"close_timestamp":1515672000000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.037263696923919086,"open_date":"2018-01-11 11:35:00+00:00","close_date":"2018-01-11 12:15:00+00:00","open_rate":0.02683577,"close_rate":0.026970285137844607,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013451513784460897,"sell_reason":"roi","initial_stop_loss_abs":0.024152193,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024152193,"stop_loss_ratio":0.1,"min_rate":0.02683577,"max_rate":0.026970285137844607,"is_open":false,"buy_tag":null,"open_timestamp":1515670500000.0,"close_timestamp":1515672900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.329335230737954,"open_date":"2018-01-11 14:00:00+00:00","close_date":"2018-01-11 14:25:00+00:00","open_rate":4.919e-05,"close_rate":5.04228320802005e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.232832080200495e-06,"sell_reason":"roi","initial_stop_loss_abs":4.4271000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4271000000000004e-05,"stop_loss_ratio":0.1,"min_rate":4.919e-05,"max_rate":5.04228320802005e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515679200000.0,"close_timestamp":1515680700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.01138317402960718,"open_date":"2018-01-11 19:25:00+00:00","close_date":"2018-01-11 20:35:00+00:00","open_rate":0.08784896,"close_rate":0.08828930566416039,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":70,"profit_ratio":-0.0,"profit_abs":0.0004403456641603881,"sell_reason":"roi","initial_stop_loss_abs":0.079064064,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.079064064,"stop_loss_ratio":0.1,"min_rate":0.08784896,"max_rate":0.08828930566416039,"is_open":false,"buy_tag":null,"open_timestamp":1515698700000.0,"close_timestamp":1515702900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.58863858961802,"open_date":"2018-01-11 22:35:00+00:00","close_date":"2018-01-11 23:30:00+00:00","open_rate":5.105e-05,"close_rate":5.130588972431077e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.558897243107704e-07,"sell_reason":"roi","initial_stop_loss_abs":4.5945e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.5945e-05,"stop_loss_ratio":0.1,"min_rate":5.105e-05,"max_rate":5.130588972431077e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515710100000.0,"close_timestamp":1515713400000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.252525252525253,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:25:00+00:00","open_rate":3.96e-05,"close_rate":4.019548872180451e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":5.954887218045116e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5640000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5640000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.96e-05,"max_rate":4.019548872180451e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713100000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.66204506065858,"open_date":"2018-01-11 22:55:00+00:00","close_date":"2018-01-11 23:35:00+00:00","open_rate":2.885e-05,"close_rate":2.899461152882205e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4461152882205115e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5965e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5965e-05,"stop_loss_ratio":0.1,"min_rate":2.885e-05,"max_rate":2.899461152882205e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515711300000.0,"close_timestamp":1515713700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03780718336483932,"open_date":"2018-01-11 23:30:00+00:00","close_date":"2018-01-12 00:05:00+00:00","open_rate":0.02645,"close_rate":0.026847744360902256,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0003977443609022545,"sell_reason":"roi","initial_stop_loss_abs":0.023805000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.023805000000000003,"stop_loss_ratio":0.1,"min_rate":0.02645,"max_rate":0.026847744360902256,"is_open":false,"buy_tag":null,"open_timestamp":1515713400000.0,"close_timestamp":1515715500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020833333333333332,"open_date":"2018-01-11 23:55:00+00:00","close_date":"2018-01-12 01:15:00+00:00","open_rate":0.048,"close_rate":0.04824060150375939,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00024060150375938838,"sell_reason":"roi","initial_stop_loss_abs":0.0432,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0432,"stop_loss_ratio":0.1,"min_rate":0.048,"max_rate":0.04824060150375939,"is_open":false,"buy_tag":null,"open_timestamp":1515714900000.0,"close_timestamp":1515719700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.31287297527707,"open_date":"2018-01-12 21:15:00+00:00","close_date":"2018-01-12 21:40:00+00:00","open_rate":4.692e-05,"close_rate":4.809593984962405e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1759398496240516e-06,"sell_reason":"roi","initial_stop_loss_abs":4.2227999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.2227999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.692e-05,"max_rate":4.809593984962405e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515791700000.0,"close_timestamp":1515793200000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38915654211062944,"open_date":"2018-01-13 00:55:00+00:00","close_date":"2018-01-13 06:20:00+00:00","open_rate":0.00256966,"close_rate":0.0025825405012531327,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":325,"profit_ratio":-0.0,"profit_abs":1.2880501253132587e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002312694,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002312694,"stop_loss_ratio":0.1,"min_rate":0.00256966,"max_rate":0.0025825405012531327,"is_open":false,"buy_tag":null,"open_timestamp":1515804900000.0,"close_timestamp":1515824400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":15.96933886937081,"open_date":"2018-01-13 10:55:00+00:00","close_date":"2018-01-13 11:35:00+00:00","open_rate":6.262e-05,"close_rate":6.293388471177944e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":3.138847117794446e-07,"sell_reason":"roi","initial_stop_loss_abs":5.6358e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.6358e-05,"stop_loss_ratio":0.1,"min_rate":6.262e-05,"max_rate":6.293388471177944e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515840900000.0,"close_timestamp":1515843300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":21.141649048625794,"open_date":"2018-01-13 13:05:00+00:00","close_date":"2018-01-15 14:10:00+00:00","open_rate":4.73e-05,"close_rate":4.753709273182957e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":2945,"profit_ratio":0.0,"profit_abs":2.3709273182957117e-07,"sell_reason":"roi","initial_stop_loss_abs":4.257e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.257e-05,"stop_loss_ratio":0.1,"min_rate":4.73e-05,"max_rate":4.753709273182957e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515848700000.0,"close_timestamp":1516025400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.49348507339601,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 14:45:00+00:00","open_rate":6.063e-05,"close_rate":6.0933909774436085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":3.039097744360846e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4567e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4567e-05,"stop_loss_ratio":0.1,"min_rate":6.063e-05,"max_rate":6.0933909774436085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515850200000.0,"close_timestamp":1515854700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.023641941887746,"open_date":"2018-01-13 13:40:00+00:00","close_date":"2018-01-13 23:30:00+00:00","open_rate":0.00011082,"close_rate":0.00011137548872180448,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":590,"profit_ratio":-0.0,"profit_abs":5.554887218044781e-07,"sell_reason":"roi","initial_stop_loss_abs":9.9738e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.9738e-05,"stop_loss_ratio":0.1,"min_rate":0.00011082,"max_rate":0.00011137548872180448,"is_open":false,"buy_tag":null,"open_timestamp":1515850800000.0,"close_timestamp":1515886200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.863406408094438,"open_date":"2018-01-13 15:15:00+00:00","close_date":"2018-01-13 15:55:00+00:00","open_rate":5.93e-05,"close_rate":5.9597243107769415e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.9724310776941686e-07,"sell_reason":"roi","initial_stop_loss_abs":5.337e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.337e-05,"stop_loss_ratio":0.1,"min_rate":5.93e-05,"max_rate":5.9597243107769415e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515856500000.0,"close_timestamp":1515858900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.020618543947292404,"open_date":"2018-01-13 16:30:00+00:00","close_date":"2018-01-13 17:10:00+00:00","open_rate":0.04850003,"close_rate":0.04874313791979949,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00024310791979949287,"sell_reason":"roi","initial_stop_loss_abs":0.043650027,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.043650027,"stop_loss_ratio":0.1,"min_rate":0.04850003,"max_rate":0.04874313791979949,"is_open":false,"buy_tag":null,"open_timestamp":1515861000000.0,"close_timestamp":1515863400000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010178097365511457,"open_date":"2018-01-13 22:05:00+00:00","close_date":"2018-01-14 06:25:00+00:00","open_rate":0.09825019,"close_rate":0.09874267215538848,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":500,"profit_ratio":-0.0,"profit_abs":0.0004924821553884823,"sell_reason":"roi","initial_stop_loss_abs":0.088425171,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.088425171,"stop_loss_ratio":0.1,"min_rate":0.09825019,"max_rate":0.09874267215538848,"is_open":false,"buy_tag":null,"open_timestamp":1515881100000.0,"close_timestamp":1515911100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":16.616816218012627,"open_date":"2018-01-14 00:20:00+00:00","close_date":"2018-01-14 22:55:00+00:00","open_rate":6.018e-05,"close_rate":6.048165413533834e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":1355,"profit_ratio":0.0,"profit_abs":3.0165413533833987e-07,"sell_reason":"roi","initial_stop_loss_abs":5.4162e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.4162e-05,"stop_loss_ratio":0.1,"min_rate":6.018e-05,"max_rate":6.048165413533834e-05,"is_open":false,"buy_tag":null,"open_timestamp":1515889200000.0,"close_timestamp":1515970500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010246952581919518,"open_date":"2018-01-14 12:45:00+00:00","close_date":"2018-01-14 13:25:00+00:00","open_rate":0.09758999,"close_rate":0.0980791628822055,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004891728822054991,"sell_reason":"roi","initial_stop_loss_abs":0.087830991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.087830991,"stop_loss_ratio":0.1,"min_rate":0.09758999,"max_rate":0.0980791628822055,"is_open":false,"buy_tag":null,"open_timestamp":1515933900000.0,"close_timestamp":1515936300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3215434083601286,"open_date":"2018-01-14 15:30:00+00:00","close_date":"2018-01-14 16:00:00+00:00","open_rate":0.00311,"close_rate":0.0031567669172932328,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.676691729323286e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002799,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002799,"stop_loss_ratio":0.1,"min_rate":0.00311,"max_rate":0.0031567669172932328,"is_open":false,"buy_tag":null,"open_timestamp":1515943800000.0,"close_timestamp":1515945600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.32010140812609433,"open_date":"2018-01-14 20:45:00+00:00","close_date":"2018-01-14 22:15:00+00:00","open_rate":0.00312401,"close_rate":0.003139669197994987,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":-0.0,"profit_abs":1.5659197994987058e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002811609,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002811609,"stop_loss_ratio":0.1,"min_rate":0.00312401,"max_rate":0.003139669197994987,"is_open":false,"buy_tag":null,"open_timestamp":1515962700000.0,"close_timestamp":1515968100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.057247866085791646,"open_date":"2018-01-14 23:35:00+00:00","close_date":"2018-01-15 00:30:00+00:00","open_rate":0.0174679,"close_rate":0.017555458395989976,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":8.755839598997492e-05,"sell_reason":"roi","initial_stop_loss_abs":0.015721110000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.015721110000000003,"stop_loss_ratio":0.1,"min_rate":0.0174679,"max_rate":0.017555458395989976,"is_open":false,"buy_tag":null,"open_timestamp":1515972900000.0,"close_timestamp":1515976200000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.013611282991367997,"open_date":"2018-01-14 23:45:00+00:00","close_date":"2018-01-15 00:25:00+00:00","open_rate":0.07346846,"close_rate":0.07383672295739348,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00036826295739347814,"sell_reason":"roi","initial_stop_loss_abs":0.066121614,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.066121614,"stop_loss_ratio":0.1,"min_rate":0.07346846,"max_rate":0.07383672295739348,"is_open":false,"buy_tag":null,"open_timestamp":1515973500000.0,"close_timestamp":1515975900000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010204706410596568,"open_date":"2018-01-15 02:25:00+00:00","close_date":"2018-01-15 03:05:00+00:00","open_rate":0.097994,"close_rate":0.09848519799498744,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004911979949874384,"sell_reason":"roi","initial_stop_loss_abs":0.0881946,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0881946,"stop_loss_ratio":0.1,"min_rate":0.097994,"max_rate":0.09848519799498744,"is_open":false,"buy_tag":null,"open_timestamp":1515983100000.0,"close_timestamp":1515985500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010353038616834042,"open_date":"2018-01-15 07:20:00+00:00","close_date":"2018-01-15 08:00:00+00:00","open_rate":0.09659,"close_rate":0.09707416040100247,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004841604010024786,"sell_reason":"roi","initial_stop_loss_abs":0.086931,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.086931,"stop_loss_ratio":0.1,"min_rate":0.09659,"max_rate":0.09707416040100247,"is_open":false,"buy_tag":null,"open_timestamp":1516000800000.0,"close_timestamp":1516003200000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.013016921998599,"open_date":"2018-01-15 08:20:00+00:00","close_date":"2018-01-15 08:55:00+00:00","open_rate":9.987e-05,"close_rate":0.00010137180451127818,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":1.501804511278178e-06,"sell_reason":"roi","initial_stop_loss_abs":8.9883e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.9883e-05,"stop_loss_ratio":0.1,"min_rate":9.987e-05,"max_rate":0.00010137180451127818,"is_open":false,"buy_tag":null,"open_timestamp":1516004400000.0,"close_timestamp":1516006500000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010537752023511832,"open_date":"2018-01-15 12:10:00+00:00","close_date":"2018-01-16 02:50:00+00:00","open_rate":0.0948969,"close_rate":0.09537257368421052,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":880,"profit_ratio":0.0,"profit_abs":0.0004756736842105175,"sell_reason":"roi","initial_stop_loss_abs":0.08540721000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08540721000000001,"stop_loss_ratio":0.1,"min_rate":0.0948969,"max_rate":0.09537257368421052,"is_open":false,"buy_tag":null,"open_timestamp":1516018200000.0,"close_timestamp":1516071000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014084507042253523,"open_date":"2018-01-15 14:10:00+00:00","close_date":"2018-01-15 17:40:00+00:00","open_rate":0.071,"close_rate":0.07135588972431077,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":210,"profit_ratio":-0.0,"profit_abs":0.00035588972431077615,"sell_reason":"roi","initial_stop_loss_abs":0.0639,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0639,"stop_loss_ratio":0.1,"min_rate":0.071,"max_rate":0.07135588972431077,"is_open":false,"buy_tag":null,"open_timestamp":1516025400000.0,"close_timestamp":1516038000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021736763017766978,"open_date":"2018-01-15 14:30:00+00:00","close_date":"2018-01-15 15:10:00+00:00","open_rate":0.04600501,"close_rate":0.046235611553884705,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00023060155388470588,"sell_reason":"roi","initial_stop_loss_abs":0.041404509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.041404509,"stop_loss_ratio":0.1,"min_rate":0.04600501,"max_rate":0.046235611553884705,"is_open":false,"buy_tag":null,"open_timestamp":1516026600000.0,"close_timestamp":1516029000000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.595465140919686,"open_date":"2018-01-15 18:10:00+00:00","close_date":"2018-01-15 19:25:00+00:00","open_rate":9.438e-05,"close_rate":9.485308270676693e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":75,"profit_ratio":-0.0,"profit_abs":4.7308270676692514e-07,"sell_reason":"roi","initial_stop_loss_abs":8.4942e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.4942e-05,"stop_loss_ratio":0.1,"min_rate":9.438e-05,"max_rate":9.485308270676693e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516039800000.0,"close_timestamp":1516044300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.032894726021471705,"open_date":"2018-01-15 18:35:00+00:00","close_date":"2018-01-15 19:15:00+00:00","open_rate":0.03040001,"close_rate":0.030552391002506264,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0001523810025062626,"sell_reason":"roi","initial_stop_loss_abs":0.027360009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027360009,"stop_loss_ratio":0.1,"min_rate":0.03040001,"max_rate":0.030552391002506264,"is_open":false,"buy_tag":null,"open_timestamp":1516041300000.0,"close_timestamp":1516043700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.13208840157615,"open_date":"2018-01-15 20:25:00+00:00","close_date":"2018-01-16 08:25:00+00:00","open_rate":5.837e-05,"close_rate":5.2533e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":720,"profit_ratio":-0.10448878,"profit_abs":-5.8369999999999985e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":5.2533e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2533e-05,"stop_loss_ratio":0.1,"min_rate":5.2533e-05,"max_rate":5.837e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516047900000.0,"close_timestamp":1516091100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.021722130506560085,"open_date":"2018-01-15 20:40:00+00:00","close_date":"2018-01-15 22:00:00+00:00","open_rate":0.046036,"close_rate":0.04626675689223057,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":0.00023075689223057277,"sell_reason":"roi","initial_stop_loss_abs":0.0414324,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0414324,"stop_loss_ratio":0.1,"min_rate":0.046036,"max_rate":0.04626675689223057,"is_open":false,"buy_tag":null,"open_timestamp":1516048800000.0,"close_timestamp":1516053600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.34861425832316545,"open_date":"2018-01-16 00:30:00+00:00","close_date":"2018-01-16 01:10:00+00:00","open_rate":0.0028685,"close_rate":0.0028828784461152877,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4378446115287727e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00258165,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00258165,"stop_loss_ratio":0.1,"min_rate":0.0028685,"max_rate":0.0028828784461152877,"is_open":false,"buy_tag":null,"open_timestamp":1516062600000.0,"close_timestamp":1516065000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014854967241083492,"open_date":"2018-01-16 01:15:00+00:00","close_date":"2018-01-16 02:35:00+00:00","open_rate":0.06731755,"close_rate":0.0676549813283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":0.0,"profit_abs":0.00033743132832080025,"sell_reason":"roi","initial_stop_loss_abs":0.060585795000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060585795000000005,"stop_loss_ratio":0.1,"min_rate":0.06731755,"max_rate":0.0676549813283208,"is_open":false,"buy_tag":null,"open_timestamp":1516065300000.0,"close_timestamp":1516070100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010848794492804754,"open_date":"2018-01-16 07:45:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":0.09217614,"close_rate":0.09263817578947368,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":0.0,"profit_abs":0.0004620357894736804,"sell_reason":"roi","initial_stop_loss_abs":0.082958526,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082958526,"stop_loss_ratio":0.1,"min_rate":0.09217614,"max_rate":0.09263817578947368,"is_open":false,"buy_tag":null,"open_timestamp":1516088700000.0,"close_timestamp":1516092000000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06060606060606061,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:55:00+00:00","open_rate":0.0165,"close_rate":0.016913533834586467,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.00041353383458646656,"sell_reason":"roi","initial_stop_loss_abs":0.01485,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.01485,"stop_loss_ratio":0.1,"min_rate":0.0165,"max_rate":0.016913533834586467,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":12.57387149503332,"open_date":"2018-01-16 08:35:00+00:00","close_date":"2018-01-16 08:40:00+00:00","open_rate":7.953e-05,"close_rate":8.311781954887218e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":3.587819548872171e-06,"sell_reason":"roi","initial_stop_loss_abs":7.157700000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.157700000000001e-05,"stop_loss_ratio":0.1,"min_rate":7.953e-05,"max_rate":8.311781954887218e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516091700000.0,"close_timestamp":1516092000000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022122914915269236,"open_date":"2018-01-16 08:45:00+00:00","close_date":"2018-01-16 09:50:00+00:00","open_rate":0.045202,"close_rate":0.04542857644110275,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":-0.0,"profit_abs":0.00022657644110275071,"sell_reason":"roi","initial_stop_loss_abs":0.0406818,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0406818,"stop_loss_ratio":0.1,"min_rate":0.045202,"max_rate":0.04542857644110275,"is_open":false,"buy_tag":null,"open_timestamp":1516092300000.0,"close_timestamp":1516096200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.054878048780488,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:45:00+00:00","open_rate":5.248e-05,"close_rate":5.326917293233082e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.891729323308177e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7232e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7232e-05,"stop_loss_ratio":0.1,"min_rate":5.248e-05,"max_rate":5.326917293233082e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516095900000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03457434486802627,"open_date":"2018-01-16 09:15:00+00:00","close_date":"2018-01-16 09:55:00+00:00","open_rate":0.02892318,"close_rate":0.02906815834586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0001449783458646603,"sell_reason":"roi","initial_stop_loss_abs":0.026030862000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.026030862000000002,"stop_loss_ratio":0.1,"min_rate":0.02892318,"max_rate":0.02906815834586466,"is_open":false,"buy_tag":null,"open_timestamp":1516094100000.0,"close_timestamp":1516096500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.38735944164405,"open_date":"2018-01-16 09:50:00+00:00","close_date":"2018-01-16 10:10:00+00:00","open_rate":5.158e-05,"close_rate":5.287273182957392e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.2927318295739246e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6422e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6422e-05,"stop_loss_ratio":0.1,"min_rate":5.158e-05,"max_rate":5.287273182957392e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516096200000.0,"close_timestamp":1516097400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035357778286929785,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:35:00+00:00","open_rate":0.02828232,"close_rate":0.02870761804511278,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042529804511277913,"sell_reason":"roi","initial_stop_loss_abs":0.025454088,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025454088,"stop_loss_ratio":0.1,"min_rate":0.02828232,"max_rate":0.02870761804511278,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516098900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022948496230938982,"open_date":"2018-01-16 10:05:00+00:00","close_date":"2018-01-16 10:40:00+00:00","open_rate":0.04357584,"close_rate":0.044231115789473675,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0006552757894736777,"sell_reason":"roi","initial_stop_loss_abs":0.039218256,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039218256,"stop_loss_ratio":0.1,"min_rate":0.04357584,"max_rate":0.044231115789473675,"is_open":false,"buy_tag":null,"open_timestamp":1516097100000.0,"close_timestamp":1516099200000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.64975755315181,"open_date":"2018-01-16 13:45:00+00:00","close_date":"2018-01-16 14:20:00+00:00","open_rate":5.362e-05,"close_rate":5.442631578947368e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":8.063157894736843e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8258e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8258e-05,"stop_loss_ratio":0.1,"min_rate":5.362e-05,"max_rate":5.442631578947368e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516110300000.0,"close_timestamp":1516112400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.86080724254998,"open_date":"2018-01-16 17:30:00+00:00","close_date":"2018-01-16 18:25:00+00:00","open_rate":5.302e-05,"close_rate":5.328576441102756e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.6576441102756397e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7718e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7718e-05,"stop_loss_ratio":0.1,"min_rate":5.302e-05,"max_rate":5.328576441102756e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516123800000.0,"close_timestamp":1516127100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010952903718828448,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:45:00+00:00","open_rate":0.09129999,"close_rate":0.09267292218045112,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.0013729321804511196,"sell_reason":"roi","initial_stop_loss_abs":0.082169991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.082169991,"stop_loss_ratio":0.1,"min_rate":0.09129999,"max_rate":0.09267292218045112,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516128300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":26.26050420168067,"open_date":"2018-01-16 18:15:00+00:00","close_date":"2018-01-16 18:35:00+00:00","open_rate":3.808e-05,"close_rate":3.903438596491228e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":9.543859649122774e-07,"sell_reason":"roi","initial_stop_loss_abs":3.4272e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.4272e-05,"stop_loss_ratio":0.1,"min_rate":3.808e-05,"max_rate":3.903438596491228e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516126500000.0,"close_timestamp":1516127700000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.035574376772493324,"open_date":"2018-01-16 19:00:00+00:00","close_date":"2018-01-16 19:30:00+00:00","open_rate":0.02811012,"close_rate":0.028532828571428567,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":0.00042270857142856846,"sell_reason":"roi","initial_stop_loss_abs":0.025299108,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025299108,"stop_loss_ratio":0.1,"min_rate":0.02811012,"max_rate":0.028532828571428567,"is_open":false,"buy_tag":null,"open_timestamp":1516129200000.0,"close_timestamp":1516131000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.387028357567759,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":0.00258379,"close_rate":0.002325411,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.10448878,"profit_abs":-0.000258379,"sell_reason":"stop_loss","initial_stop_loss_abs":0.002325411,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002325411,"stop_loss_ratio":0.1,"min_rate":0.002325411,"max_rate":0.00258379,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516141500000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":39.07776475185619,"open_date":"2018-01-16 21:25:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":2.559e-05,"close_rate":2.3031e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.10448878,"profit_abs":-2.5590000000000004e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":2.3031e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.3031e-05,"stop_loss_ratio":0.1,"min_rate":2.3031e-05,"max_rate":2.559e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516137900000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":13.123359580052494,"open_date":"2018-01-16 21:35:00+00:00","close_date":"2018-01-16 22:25:00+00:00","open_rate":7.62e-05,"close_rate":6.858e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.10448878,"profit_abs":-7.619999999999998e-06,"sell_reason":"stop_loss","initial_stop_loss_abs":6.858e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.858e-05,"stop_loss_ratio":0.1,"min_rate":6.858e-05,"max_rate":7.62e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516138500000.0,"close_timestamp":1516141500000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.4350777048780912,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:35:00+00:00","open_rate":0.00229844,"close_rate":0.002402129022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010368902255639091,"sell_reason":"roi","initial_stop_loss_abs":0.0020685960000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0020685960000000002,"stop_loss_ratio":0.1,"min_rate":0.00229844,"max_rate":0.002402129022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06622516556291391,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:40:00+00:00","open_rate":0.0151,"close_rate":0.015781203007518795,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":0.0006812030075187946,"sell_reason":"roi","initial_stop_loss_abs":0.013590000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.013590000000000001,"stop_loss_ratio":0.1,"min_rate":0.0151,"max_rate":0.015781203007518795,"is_open":false,"buy_tag":null,"open_timestamp":1516141800000.0,"close_timestamp":1516142400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.42431134269081283,"open_date":"2018-01-16 22:40:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":0.00235676,"close_rate":0.00246308,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":0.00010632000000000003,"sell_reason":"roi","initial_stop_loss_abs":0.002121084,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002121084,"stop_loss_ratio":0.1,"min_rate":0.00235676,"max_rate":0.00246308,"is_open":false,"buy_tag":null,"open_timestamp":1516142400000.0,"close_timestamp":1516142700000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01585559988076589,"open_date":"2018-01-16 22:45:00+00:00","close_date":"2018-01-16 23:05:00+00:00","open_rate":0.0630692,"close_rate":0.06464988170426066,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":0.0015806817042606502,"sell_reason":"roi","initial_stop_loss_abs":0.056762280000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.056762280000000005,"stop_loss_ratio":0.1,"min_rate":0.0630692,"max_rate":0.06464988170426066,"is_open":false,"buy_tag":null,"open_timestamp":1516142700000.0,"close_timestamp":1516143900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":45.45454545454545,"open_date":"2018-01-16 22:50:00+00:00","close_date":"2018-01-16 22:55:00+00:00","open_rate":2.2e-05,"close_rate":2.299248120300751e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":5,"profit_ratio":0.03990025,"profit_abs":9.924812030075114e-07,"sell_reason":"roi","initial_stop_loss_abs":1.98e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":1.98e-05,"stop_loss_ratio":0.1,"min_rate":2.2e-05,"max_rate":2.299248120300751e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516143000000.0,"close_timestamp":1516143300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.10454362685967,"open_date":"2018-01-17 03:30:00+00:00","close_date":"2018-01-17 04:00:00+00:00","open_rate":4.974e-05,"close_rate":5.048796992481203e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":7.479699248120277e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4766000000000005e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4766000000000005e-05,"stop_loss_ratio":0.1,"min_rate":4.974e-05,"max_rate":5.048796992481203e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516159800000.0,"close_timestamp":1516161600000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":14.068655036578503,"open_date":"2018-01-17 03:55:00+00:00","close_date":"2018-01-17 04:15:00+00:00","open_rate":7.108e-05,"close_rate":7.28614536340852e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":1.7814536340851996e-06,"sell_reason":"roi","initial_stop_loss_abs":6.3972e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":6.3972e-05,"stop_loss_ratio":0.1,"min_rate":7.108e-05,"max_rate":7.28614536340852e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516161300000.0,"close_timestamp":1516162500000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.0231107002542177,"open_date":"2018-01-17 09:35:00+00:00","close_date":"2018-01-17 10:15:00+00:00","open_rate":0.04327,"close_rate":0.04348689223057644,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002168922305764362,"sell_reason":"roi","initial_stop_loss_abs":0.038943000000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.038943000000000005,"stop_loss_ratio":0.1,"min_rate":0.04327,"max_rate":0.04348689223057644,"is_open":false,"buy_tag":null,"open_timestamp":1516181700000.0,"close_timestamp":1516184100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":20.012007204322593,"open_date":"2018-01-17 10:20:00+00:00","close_date":"2018-01-17 17:00:00+00:00","open_rate":4.997e-05,"close_rate":5.022047619047618e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":400,"profit_ratio":-0.0,"profit_abs":2.504761904761831e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4973e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4973e-05,"stop_loss_ratio":0.1,"min_rate":4.997e-05,"max_rate":5.022047619047618e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516184400000.0,"close_timestamp":1516208400000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014626687444363738,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:25:00+00:00","open_rate":0.06836818,"close_rate":0.06871087764411027,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":0.00034269764411026804,"sell_reason":"roi","initial_stop_loss_abs":0.061531362,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061531362,"stop_loss_ratio":0.1,"min_rate":0.06836818,"max_rate":0.06871087764411027,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516188300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":27.548209366391184,"open_date":"2018-01-17 10:30:00+00:00","close_date":"2018-01-17 11:10:00+00:00","open_rate":3.63e-05,"close_rate":3.648195488721804e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.8195488721804031e-07,"sell_reason":"roi","initial_stop_loss_abs":3.2670000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.2670000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.63e-05,"max_rate":3.648195488721804e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516185000000.0,"close_timestamp":1516187400000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03558718861209965,"open_date":"2018-01-17 12:30:00+00:00","close_date":"2018-01-17 22:05:00+00:00","open_rate":0.0281,"close_rate":0.02824085213032581,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":575,"profit_ratio":-0.0,"profit_abs":0.0001408521303258095,"sell_reason":"roi","initial_stop_loss_abs":0.02529,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.02529,"stop_loss_ratio":0.1,"min_rate":0.0281,"max_rate":0.02824085213032581,"is_open":false,"buy_tag":null,"open_timestamp":1516192200000.0,"close_timestamp":1516226700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011559355963546878,"open_date":"2018-01-17 12:35:00+00:00","close_date":"2018-01-17 16:55:00+00:00","open_rate":0.08651001,"close_rate":0.08694364413533832,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":-0.0,"profit_abs":0.00043363413533832607,"sell_reason":"roi","initial_stop_loss_abs":0.077859009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.077859009,"stop_loss_ratio":0.1,"min_rate":0.08651001,"max_rate":0.08694364413533832,"is_open":false,"buy_tag":null,"open_timestamp":1516192500000.0,"close_timestamp":1516208100000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.752529735487308,"open_date":"2018-01-18 05:00:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":5.633e-05,"close_rate":5.6612355889724306e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.8235588972430847e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0697e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0697e-05,"stop_loss_ratio":0.1,"min_rate":5.633e-05,"max_rate":5.6612355889724306e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516251600000.0,"close_timestamp":1516254900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01430923457900944,"open_date":"2018-01-18 05:20:00+00:00","close_date":"2018-01-18 05:55:00+00:00","open_rate":0.06988494,"close_rate":0.07093584135338346,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":35,"profit_ratio":0.00997506,"profit_abs":0.0010509013533834544,"sell_reason":"roi","initial_stop_loss_abs":0.06289644600000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06289644600000001,"stop_loss_ratio":0.1,"min_rate":0.06988494,"max_rate":0.07093584135338346,"is_open":false,"buy_tag":null,"open_timestamp":1516252800000.0,"close_timestamp":1516254900000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.034265103697024,"open_date":"2018-01-18 07:35:00+00:00","close_date":"2018-01-18 08:15:00+00:00","open_rate":5.545e-05,"close_rate":5.572794486215538e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.779448621553787e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9905e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9905e-05,"stop_loss_ratio":0.1,"min_rate":5.545e-05,"max_rate":5.572794486215538e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516260900000.0,"close_timestamp":1516263300000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06121723118136401,"open_date":"2018-01-18 09:00:00+00:00","close_date":"2018-01-18 09:40:00+00:00","open_rate":0.01633527,"close_rate":0.016417151052631574,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":8.188105263157511e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014701743,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014701743,"stop_loss_ratio":0.1,"min_rate":0.01633527,"max_rate":0.016417151052631574,"is_open":false,"buy_tag":null,"open_timestamp":1516266000000.0,"close_timestamp":1516268400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3707356136045141,"open_date":"2018-01-18 16:40:00+00:00","close_date":"2018-01-18 17:20:00+00:00","open_rate":0.00269734,"close_rate":0.002710860501253133,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.3520501253133123e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002427606,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002427606,"stop_loss_ratio":0.1,"min_rate":0.00269734,"max_rate":0.002710860501253133,"is_open":false,"buy_tag":null,"open_timestamp":1516293600000.0,"close_timestamp":1516296000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.346368715083802,"open_date":"2018-01-18 18:05:00+00:00","close_date":"2018-01-18 18:30:00+00:00","open_rate":4.475e-05,"close_rate":4.587155388471177e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":1.1215538847117757e-06,"sell_reason":"roi","initial_stop_loss_abs":4.0274999999999996e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.0274999999999996e-05,"stop_loss_ratio":0.1,"min_rate":4.475e-05,"max_rate":4.587155388471177e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516298700000.0,"close_timestamp":1516300200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":35.842293906810035,"open_date":"2018-01-18 18:25:00+00:00","close_date":"2018-01-18 18:55:00+00:00","open_rate":2.79e-05,"close_rate":2.8319548872180444e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.1954887218044365e-07,"sell_reason":"roi","initial_stop_loss_abs":2.5110000000000002e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.5110000000000002e-05,"stop_loss_ratio":0.1,"min_rate":2.79e-05,"max_rate":2.8319548872180444e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516299900000.0,"close_timestamp":1516301700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.022525942001105574,"open_date":"2018-01-18 20:10:00+00:00","close_date":"2018-01-18 20:50:00+00:00","open_rate":0.04439326,"close_rate":0.04461578260651629,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00022252260651629135,"sell_reason":"roi","initial_stop_loss_abs":0.039953933999999997,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039953933999999997,"stop_loss_ratio":0.1,"min_rate":0.04439326,"max_rate":0.04461578260651629,"is_open":false,"buy_tag":null,"open_timestamp":1516306200000.0,"close_timestamp":1516308600000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":22.271714922048996,"open_date":"2018-01-18 21:30:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.49e-05,"close_rate":4.51250626566416e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":185,"profit_ratio":0.0,"profit_abs":2.2506265664159932e-07,"sell_reason":"roi","initial_stop_loss_abs":4.041e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.041e-05,"stop_loss_ratio":0.1,"min_rate":4.49e-05,"max_rate":4.51250626566416e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516311000000.0,"close_timestamp":1516322100000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03502626970227671,"open_date":"2018-01-18 21:55:00+00:00","close_date":"2018-01-19 05:05:00+00:00","open_rate":0.02855,"close_rate":0.028693107769423555,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":430,"profit_ratio":-0.0,"profit_abs":0.00014310776942355607,"sell_reason":"roi","initial_stop_loss_abs":0.025695,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025695,"stop_loss_ratio":0.1,"min_rate":0.02855,"max_rate":0.028693107769423555,"is_open":false,"buy_tag":null,"open_timestamp":1516312500000.0,"close_timestamp":1516338300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.25327812284334,"open_date":"2018-01-18 22:10:00+00:00","close_date":"2018-01-18 22:50:00+00:00","open_rate":5.796e-05,"close_rate":5.8250526315789473e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.905263157894727e-07,"sell_reason":"roi","initial_stop_loss_abs":5.2164000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.2164000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.796e-05,"max_rate":5.8250526315789473e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516313400000.0,"close_timestamp":1516315800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02303975994413319,"open_date":"2018-01-18 23:50:00+00:00","close_date":"2018-01-19 00:30:00+00:00","open_rate":0.04340323,"close_rate":0.04362079005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.0002175600501253122,"sell_reason":"roi","initial_stop_loss_abs":0.039062907,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.039062907,"stop_loss_ratio":0.1,"min_rate":0.04340323,"max_rate":0.04362079005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516319400000.0,"close_timestamp":1516321800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02244943545282195,"open_date":"2018-01-19 16:45:00+00:00","close_date":"2018-01-19 17:35:00+00:00","open_rate":0.04454455,"close_rate":0.04476783095238095,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0002232809523809512,"sell_reason":"roi","initial_stop_loss_abs":0.040090095000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.040090095000000006,"stop_loss_ratio":0.1,"min_rate":0.04454455,"max_rate":0.04476783095238095,"is_open":false,"buy_tag":null,"open_timestamp":1516380300000.0,"close_timestamp":1516383300000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.793594306049823,"open_date":"2018-01-19 17:15:00+00:00","close_date":"2018-01-19 19:55:00+00:00","open_rate":5.62e-05,"close_rate":5.648170426065162e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":160,"profit_ratio":-0.0,"profit_abs":2.817042606516199e-07,"sell_reason":"roi","initial_stop_loss_abs":5.058e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.058e-05,"stop_loss_ratio":0.1,"min_rate":5.62e-05,"max_rate":5.648170426065162e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382100000.0,"close_timestamp":1516391700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.046784973496194,"open_date":"2018-01-19 17:20:00+00:00","close_date":"2018-01-19 20:15:00+00:00","open_rate":4.339e-05,"close_rate":4.360749373433584e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.174937343358337e-07,"sell_reason":"roi","initial_stop_loss_abs":3.9051e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.9051e-05,"stop_loss_ratio":0.1,"min_rate":4.339e-05,"max_rate":4.360749373433584e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516382400000.0,"close_timestamp":1516392900000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":9.910802775024777,"open_date":"2018-01-20 04:45:00+00:00","close_date":"2018-01-20 17:35:00+00:00","open_rate":0.0001009,"close_rate":0.00010140576441102755,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":770,"profit_ratio":0.0,"profit_abs":5.057644110275549e-07,"sell_reason":"roi","initial_stop_loss_abs":9.081e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":9.081e-05,"stop_loss_ratio":0.1,"min_rate":0.0001009,"max_rate":0.00010140576441102755,"is_open":false,"buy_tag":null,"open_timestamp":1516423500000.0,"close_timestamp":1516469700000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3696789338459548,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 15:15:00+00:00","open_rate":0.00270505,"close_rate":0.002718609147869674,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":625,"profit_ratio":-0.0,"profit_abs":1.3559147869673764e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002434545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002434545,"stop_loss_ratio":0.1,"min_rate":0.00270505,"max_rate":0.002718609147869674,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516461300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.033333311111125925,"open_date":"2018-01-20 04:50:00+00:00","close_date":"2018-01-20 07:00:00+00:00","open_rate":0.03000002,"close_rate":0.030150396040100245,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":0.00015037604010024672,"sell_reason":"roi","initial_stop_loss_abs":0.027000018,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027000018,"stop_loss_ratio":0.1,"min_rate":0.03000002,"max_rate":0.030150396040100245,"is_open":false,"buy_tag":null,"open_timestamp":1516423800000.0,"close_timestamp":1516431600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.315018315018317,"open_date":"2018-01-20 09:00:00+00:00","close_date":"2018-01-20 09:40:00+00:00","open_rate":5.46e-05,"close_rate":5.4873684210526304e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.736842105263053e-07,"sell_reason":"roi","initial_stop_loss_abs":4.914e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.914e-05,"stop_loss_ratio":0.1,"min_rate":5.46e-05,"max_rate":5.4873684210526304e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516438800000.0,"close_timestamp":1516441200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03244412634781012,"open_date":"2018-01-20 18:25:00+00:00","close_date":"2018-01-25 03:50:00+00:00","open_rate":0.03082222,"close_rate":0.027739998,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":6325,"profit_ratio":-0.10448878,"profit_abs":-0.0030822220000000025,"sell_reason":"stop_loss","initial_stop_loss_abs":0.027739998000000002,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.027739998000000002,"stop_loss_ratio":0.1,"min_rate":0.027739998,"max_rate":0.03082222,"is_open":false,"buy_tag":null,"open_timestamp":1516472700000.0,"close_timestamp":1516852200000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011148273260677065,"open_date":"2018-01-20 22:25:00+00:00","close_date":"2018-01-20 23:15:00+00:00","open_rate":0.08969999,"close_rate":0.09014961401002504,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":-0.0,"profit_abs":0.00044962401002504593,"sell_reason":"roi","initial_stop_loss_abs":0.080729991,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.080729991,"stop_loss_ratio":0.1,"min_rate":0.08969999,"max_rate":0.09014961401002504,"is_open":false,"buy_tag":null,"open_timestamp":1516487100000.0,"close_timestamp":1516490100000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06125570520324337,"open_date":"2018-01-21 02:50:00+00:00","close_date":"2018-01-21 14:30:00+00:00","open_rate":0.01632501,"close_rate":0.01640683962406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":700,"profit_ratio":0.0,"profit_abs":8.182962406014932e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014692509000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014692509000000001,"stop_loss_ratio":0.1,"min_rate":0.01632501,"max_rate":0.01640683962406015,"is_open":false,"buy_tag":null,"open_timestamp":1516503000000.0,"close_timestamp":1516545000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.01417675579120474,"open_date":"2018-01-21 10:20:00+00:00","close_date":"2018-01-21 11:00:00+00:00","open_rate":0.070538,"close_rate":0.07089157393483708,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00035357393483707866,"sell_reason":"roi","initial_stop_loss_abs":0.0634842,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0634842,"stop_loss_ratio":0.1,"min_rate":0.070538,"max_rate":0.07089157393483708,"is_open":false,"buy_tag":null,"open_timestamp":1516530000000.0,"close_timestamp":1516532400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.864365214110546,"open_date":"2018-01-21 15:50:00+00:00","close_date":"2018-01-21 18:45:00+00:00","open_rate":5.301e-05,"close_rate":5.327571428571427e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":175,"profit_ratio":-0.0,"profit_abs":2.657142857142672e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7709e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7709e-05,"stop_loss_ratio":0.1,"min_rate":5.301e-05,"max_rate":5.327571428571427e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516549800000.0,"close_timestamp":1516560300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.284450063211125,"open_date":"2018-01-21 16:20:00+00:00","close_date":"2018-01-21 17:00:00+00:00","open_rate":3.955e-05,"close_rate":3.9748245614035085e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.9824561403508552e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5595e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5595e-05,"stop_loss_ratio":0.1,"min_rate":3.955e-05,"max_rate":3.9748245614035085e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516551600000.0,"close_timestamp":1516554000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38683971296493297,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:45:00+00:00","open_rate":0.00258505,"close_rate":0.002623922932330827,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":3.8872932330826816e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326545,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326545,"stop_loss_ratio":0.1,"min_rate":0.00258505,"max_rate":0.002623922932330827,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":25.621316935690498,"open_date":"2018-01-21 21:15:00+00:00","close_date":"2018-01-21 21:55:00+00:00","open_rate":3.903e-05,"close_rate":3.922563909774435e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.9563909774435151e-07,"sell_reason":"roi","initial_stop_loss_abs":3.5127e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.5127e-05,"stop_loss_ratio":0.1,"min_rate":3.903e-05,"max_rate":3.922563909774435e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516569300000.0,"close_timestamp":1516571700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.098548510313215,"open_date":"2018-01-22 00:35:00+00:00","close_date":"2018-01-22 10:35:00+00:00","open_rate":5.236e-05,"close_rate":5.262245614035087e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":600,"profit_ratio":-0.0,"profit_abs":2.624561403508717e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7124e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7124e-05,"stop_loss_ratio":0.1,"min_rate":5.236e-05,"max_rate":5.262245614035087e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516581300000.0,"close_timestamp":1516617300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.076650420912717,"open_date":"2018-01-22 01:30:00+00:00","close_date":"2018-01-22 02:10:00+00:00","open_rate":9.028e-05,"close_rate":9.07325313283208e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":4.5253132832080657e-07,"sell_reason":"roi","initial_stop_loss_abs":8.1252e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.1252e-05,"stop_loss_ratio":0.1,"min_rate":9.028e-05,"max_rate":9.07325313283208e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516584600000.0,"close_timestamp":1516587000000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3721622627465575,"open_date":"2018-01-22 12:25:00+00:00","close_date":"2018-01-22 14:35:00+00:00","open_rate":0.002687,"close_rate":0.002700468671679198,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":130,"profit_ratio":-0.0,"profit_abs":1.3468671679197925e-05,"sell_reason":"roi","initial_stop_loss_abs":0.0024183000000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0024183000000000004,"stop_loss_ratio":0.1,"min_rate":0.002687,"max_rate":0.002700468671679198,"is_open":false,"buy_tag":null,"open_timestamp":1516623900000.0,"close_timestamp":1516631700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.99232245681382,"open_date":"2018-01-22 13:15:00+00:00","close_date":"2018-01-22 13:55:00+00:00","open_rate":4.168e-05,"close_rate":4.188892230576441e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.0892230576441054e-07,"sell_reason":"roi","initial_stop_loss_abs":3.7512e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.7512e-05,"stop_loss_ratio":0.1,"min_rate":4.168e-05,"max_rate":4.188892230576441e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516626900000.0,"close_timestamp":1516629300000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.336583153837434,"open_date":"2018-01-22 14:00:00+00:00","close_date":"2018-01-22 14:30:00+00:00","open_rate":8.821e-05,"close_rate":8.953646616541353e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":1.326466165413529e-06,"sell_reason":"roi","initial_stop_loss_abs":7.9389e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.9389e-05,"stop_loss_ratio":0.1,"min_rate":8.821e-05,"max_rate":8.953646616541353e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516629600000.0,"close_timestamp":1516631400000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.334880123743233,"open_date":"2018-01-22 15:55:00+00:00","close_date":"2018-01-22 16:40:00+00:00","open_rate":5.172e-05,"close_rate":5.1979248120300745e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.592481203007459e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6548e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6548e-05,"stop_loss_ratio":0.1,"min_rate":5.172e-05,"max_rate":5.1979248120300745e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516636500000.0,"close_timestamp":1516639200000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":33.04692663582287,"open_date":"2018-01-22 16:05:00+00:00","close_date":"2018-01-22 16:25:00+00:00","open_rate":3.026e-05,"close_rate":3.101839598997494e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":20,"profit_ratio":0.01995012,"profit_abs":7.5839598997494e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7234e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7234e-05,"stop_loss_ratio":0.1,"min_rate":3.026e-05,"max_rate":3.101839598997494e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516637100000.0,"close_timestamp":1516638300000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014156285390713478,"open_date":"2018-01-22 19:50:00+00:00","close_date":"2018-01-23 00:10:00+00:00","open_rate":0.07064,"close_rate":0.07099408521303258,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":260,"profit_ratio":0.0,"profit_abs":0.00035408521303258167,"sell_reason":"roi","initial_stop_loss_abs":0.063576,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.063576,"stop_loss_ratio":0.1,"min_rate":0.07064,"max_rate":0.07099408521303258,"is_open":false,"buy_tag":null,"open_timestamp":1516650600000.0,"close_timestamp":1516666200000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.06080938507725528,"open_date":"2018-01-22 21:25:00+00:00","close_date":"2018-01-22 22:05:00+00:00","open_rate":0.01644483,"close_rate":0.01652726022556391,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":8.243022556390922e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014800347,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014800347,"stop_loss_ratio":0.1,"min_rate":0.01644483,"max_rate":0.01652726022556391,"is_open":false,"buy_tag":null,"open_timestamp":1516656300000.0,"close_timestamp":1516658700000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":23.08935580697299,"open_date":"2018-01-23 00:05:00+00:00","close_date":"2018-01-23 00:35:00+00:00","open_rate":4.331e-05,"close_rate":4.3961278195488714e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":6.512781954887175e-07,"sell_reason":"roi","initial_stop_loss_abs":3.8979e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":3.8979e-05,"stop_loss_ratio":0.1,"min_rate":4.331e-05,"max_rate":4.3961278195488714e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516665900000.0,"close_timestamp":1516667700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.250000000000004,"open_date":"2018-01-23 01:50:00+00:00","close_date":"2018-01-23 02:15:00+00:00","open_rate":3.2e-05,"close_rate":3.2802005012531326e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":25,"profit_ratio":0.01995012,"profit_abs":8.020050125313278e-07,"sell_reason":"roi","initial_stop_loss_abs":2.88e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.88e-05,"stop_loss_ratio":0.1,"min_rate":3.2e-05,"max_rate":3.2802005012531326e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516672200000.0,"close_timestamp":1516673700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010907854156754155,"open_date":"2018-01-23 04:25:00+00:00","close_date":"2018-01-23 05:15:00+00:00","open_rate":0.09167706,"close_rate":0.09213659413533835,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":50,"profit_ratio":0.0,"profit_abs":0.0004595341353383492,"sell_reason":"roi","initial_stop_loss_abs":0.08250935400000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.08250935400000001,"stop_loss_ratio":0.1,"min_rate":0.09167706,"max_rate":0.09213659413533835,"is_open":false,"buy_tag":null,"open_timestamp":1516681500000.0,"close_timestamp":1516684500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014440474918339115,"open_date":"2018-01-23 07:35:00+00:00","close_date":"2018-01-23 09:00:00+00:00","open_rate":0.0692498,"close_rate":0.06959691679197995,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":0.0,"profit_abs":0.0003471167919799484,"sell_reason":"roi","initial_stop_loss_abs":0.06232482,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06232482,"stop_loss_ratio":0.1,"min_rate":0.0692498,"max_rate":0.06959691679197995,"is_open":false,"buy_tag":null,"open_timestamp":1516692900000.0,"close_timestamp":1516698000000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.426775612822127,"open_date":"2018-01-23 10:50:00+00:00","close_date":"2018-01-23 13:05:00+00:00","open_rate":3.182e-05,"close_rate":3.197949874686716e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":135,"profit_ratio":0.0,"profit_abs":1.594987468671663e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8638e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8638e-05,"stop_loss_ratio":0.1,"min_rate":3.182e-05,"max_rate":3.197949874686716e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516704600000.0,"close_timestamp":1516712700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024461839530332683,"open_date":"2018-01-23 11:05:00+00:00","close_date":"2018-01-23 16:05:00+00:00","open_rate":0.04088,"close_rate":0.04108491228070175,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":300,"profit_ratio":-0.0,"profit_abs":0.0002049122807017481,"sell_reason":"roi","initial_stop_loss_abs":0.036792,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036792,"stop_loss_ratio":0.1,"min_rate":0.04088,"max_rate":0.04108491228070175,"is_open":false,"buy_tag":null,"open_timestamp":1516705500000.0,"close_timestamp":1516723500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.417475728155342,"open_date":"2018-01-23 14:55:00+00:00","close_date":"2018-01-23 15:35:00+00:00","open_rate":5.15e-05,"close_rate":5.175814536340851e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5814536340851513e-07,"sell_reason":"roi","initial_stop_loss_abs":4.635e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.635e-05,"stop_loss_ratio":0.1,"min_rate":5.15e-05,"max_rate":5.175814536340851e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516719300000.0,"close_timestamp":1516721700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.011023294646713328,"open_date":"2018-01-23 16:35:00+00:00","close_date":"2018-01-24 00:05:00+00:00","open_rate":0.09071698,"close_rate":0.09117170170426064,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":450,"profit_ratio":0.0,"profit_abs":0.00045472170426064107,"sell_reason":"roi","initial_stop_loss_abs":0.081645282,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.081645282,"stop_loss_ratio":0.1,"min_rate":0.09071698,"max_rate":0.09117170170426064,"is_open":false,"buy_tag":null,"open_timestamp":1516725300000.0,"close_timestamp":1516752300000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":31.969309462915604,"open_date":"2018-01-23 17:25:00+00:00","close_date":"2018-01-23 18:45:00+00:00","open_rate":3.128e-05,"close_rate":3.1436791979949865e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":80,"profit_ratio":-0.0,"profit_abs":1.5679197994986587e-07,"sell_reason":"roi","initial_stop_loss_abs":2.8152e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.8152e-05,"stop_loss_ratio":0.1,"min_rate":3.128e-05,"max_rate":3.1436791979949865e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516728300000.0,"close_timestamp":1516733100000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.465724751439037,"open_date":"2018-01-23 20:15:00+00:00","close_date":"2018-01-23 22:00:00+00:00","open_rate":9.555e-05,"close_rate":9.602894736842104e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":4.789473684210343e-07,"sell_reason":"roi","initial_stop_loss_abs":8.5995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.5995e-05,"stop_loss_ratio":0.1,"min_rate":9.555e-05,"max_rate":9.602894736842104e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516738500000.0,"close_timestamp":1516744800000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02450979791426522,"open_date":"2018-01-23 22:30:00+00:00","close_date":"2018-01-23 23:10:00+00:00","open_rate":0.04080001,"close_rate":0.0410045213283208,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00020451132832080554,"sell_reason":"roi","initial_stop_loss_abs":0.036720009,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036720009,"stop_loss_ratio":0.1,"min_rate":0.04080001,"max_rate":0.0410045213283208,"is_open":false,"buy_tag":null,"open_timestamp":1516746600000.0,"close_timestamp":1516749000000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.368584156498162,"open_date":"2018-01-23 23:50:00+00:00","close_date":"2018-01-24 03:35:00+00:00","open_rate":5.163e-05,"close_rate":5.18887969924812e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":225,"profit_ratio":-0.0,"profit_abs":2.587969924812037e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6467e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6467e-05,"stop_loss_ratio":0.1,"min_rate":5.163e-05,"max_rate":5.18887969924812e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516751400000.0,"close_timestamp":1516764900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024747691102289384,"open_date":"2018-01-24 00:20:00+00:00","close_date":"2018-01-24 01:50:00+00:00","open_rate":0.04040781,"close_rate":0.04061035541353383,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":90,"profit_ratio":0.0,"profit_abs":0.0002025454135338306,"sell_reason":"roi","initial_stop_loss_abs":0.036367029,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036367029,"stop_loss_ratio":0.1,"min_rate":0.04040781,"max_rate":0.04061035541353383,"is_open":false,"buy_tag":null,"open_timestamp":1516753200000.0,"close_timestamp":1516758600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.485580670303975,"open_date":"2018-01-24 06:45:00+00:00","close_date":"2018-01-24 07:25:00+00:00","open_rate":5.132e-05,"close_rate":5.157724310776942e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":2.5724310776941724e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6188000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6188000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.132e-05,"max_rate":5.157724310776942e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516776300000.0,"close_timestamp":1516778700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":19.23816852635629,"open_date":"2018-01-24 14:15:00+00:00","close_date":"2018-01-24 14:25:00+00:00","open_rate":5.198e-05,"close_rate":5.432496240601503e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":10,"profit_ratio":0.03990025,"profit_abs":2.344962406015033e-06,"sell_reason":"roi","initial_stop_loss_abs":4.6782e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6782e-05,"stop_loss_ratio":0.1,"min_rate":5.198e-05,"max_rate":5.432496240601503e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516803300000.0,"close_timestamp":1516803900000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":32.74394237066143,"open_date":"2018-01-24 14:50:00+00:00","close_date":"2018-01-24 16:35:00+00:00","open_rate":3.054e-05,"close_rate":3.069308270676692e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":105,"profit_ratio":-0.0,"profit_abs":1.5308270676691466e-07,"sell_reason":"roi","initial_stop_loss_abs":2.7486000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.7486000000000004e-05,"stop_loss_ratio":0.1,"min_rate":3.054e-05,"max_rate":3.069308270676692e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516805400000.0,"close_timestamp":1516811700000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":10.795638562020944,"open_date":"2018-01-24 15:10:00+00:00","close_date":"2018-01-24 16:15:00+00:00","open_rate":9.263e-05,"close_rate":9.309431077694236e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":65,"profit_ratio":0.0,"profit_abs":4.6431077694236234e-07,"sell_reason":"roi","initial_stop_loss_abs":8.3367e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.3367e-05,"stop_loss_ratio":0.1,"min_rate":9.263e-05,"max_rate":9.309431077694236e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516806600000.0,"close_timestamp":1516810500000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":18.13565469713457,"open_date":"2018-01-24 22:40:00+00:00","close_date":"2018-01-24 23:25:00+00:00","open_rate":5.514e-05,"close_rate":5.54163909774436e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":2.7639097744360576e-07,"sell_reason":"roi","initial_stop_loss_abs":4.9625999999999995e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.9625999999999995e-05,"stop_loss_ratio":0.1,"min_rate":5.514e-05,"max_rate":5.54163909774436e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516833600000.0,"close_timestamp":1516836300000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":20.3210729526519,"open_date":"2018-01-25 00:50:00+00:00","close_date":"2018-01-25 01:30:00+00:00","open_rate":4.921e-05,"close_rate":4.9456666666666664e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":2.4666666666666543e-07,"sell_reason":"roi","initial_stop_loss_abs":4.4289e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.4289e-05,"stop_loss_ratio":0.1,"min_rate":4.921e-05,"max_rate":4.9456666666666664e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516841400000.0,"close_timestamp":1516843800000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38461538461538464,"open_date":"2018-01-25 08:15:00+00:00","close_date":"2018-01-25 12:15:00+00:00","open_rate":0.0026,"close_rate":0.002613032581453634,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":240,"profit_ratio":0.0,"profit_abs":1.3032581453634e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00234,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.00234,"stop_loss_ratio":0.1,"min_rate":0.0026,"max_rate":0.002613032581453634,"is_open":false,"buy_tag":null,"open_timestamp":1516868100000.0,"close_timestamp":1516882500000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03571593119825878,"open_date":"2018-01-25 10:25:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":0.02799871,"close_rate":0.028139054411027563,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":350,"profit_ratio":-0.0,"profit_abs":0.00014034441102756326,"sell_reason":"roi","initial_stop_loss_abs":0.025198839,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025198839,"stop_loss_ratio":0.1,"min_rate":0.02799871,"max_rate":0.028139054411027563,"is_open":false,"buy_tag":null,"open_timestamp":1516875900000.0,"close_timestamp":1516896900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024516401717913302,"open_date":"2018-01-25 11:00:00+00:00","close_date":"2018-01-25 11:45:00+00:00","open_rate":0.04078902,"close_rate":0.0409934762406015,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020445624060149575,"sell_reason":"roi","initial_stop_loss_abs":0.036710118,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.036710118,"stop_loss_ratio":0.1,"min_rate":0.04078902,"max_rate":0.0409934762406015,"is_open":false,"buy_tag":null,"open_timestamp":1516878000000.0,"close_timestamp":1516880700000.0},{"pair":"NXT/BTC","stake_amount":0.001,"amount":34.602076124567475,"open_date":"2018-01-25 13:05:00+00:00","close_date":"2018-01-25 13:45:00+00:00","open_rate":2.89e-05,"close_rate":2.904486215538847e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":1.4486215538846723e-07,"sell_reason":"roi","initial_stop_loss_abs":2.601e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":2.601e-05,"stop_loss_ratio":0.1,"min_rate":2.89e-05,"max_rate":2.904486215538847e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516885500000.0,"close_timestamp":1516887900000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02432912439481303,"open_date":"2018-01-25 13:20:00+00:00","close_date":"2018-01-25 14:05:00+00:00","open_rate":0.041103,"close_rate":0.04130903007518797,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00020603007518796984,"sell_reason":"roi","initial_stop_loss_abs":0.0369927,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0369927,"stop_loss_ratio":0.1,"min_rate":0.041103,"max_rate":0.04130903007518797,"is_open":false,"buy_tag":null,"open_timestamp":1516886400000.0,"close_timestamp":1516889100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.422991893883566,"open_date":"2018-01-25 15:45:00+00:00","close_date":"2018-01-25 16:15:00+00:00","open_rate":5.428e-05,"close_rate":5.509624060150376e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.162406015037611e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8852000000000006e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8852000000000006e-05,"stop_loss_ratio":0.1,"min_rate":5.428e-05,"max_rate":5.509624060150376e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516895100000.0,"close_timestamp":1516896900000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.47063169560399,"open_date":"2018-01-25 17:45:00+00:00","close_date":"2018-01-25 23:15:00+00:00","open_rate":5.414e-05,"close_rate":5.441137844611528e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":330,"profit_ratio":-0.0,"profit_abs":2.713784461152774e-07,"sell_reason":"roi","initial_stop_loss_abs":4.8726e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.8726e-05,"stop_loss_ratio":0.1,"min_rate":5.414e-05,"max_rate":5.441137844611528e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516902300000.0,"close_timestamp":1516922100000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024150056861308878,"open_date":"2018-01-25 21:15:00+00:00","close_date":"2018-01-25 21:55:00+00:00","open_rate":0.04140777,"close_rate":0.0416153277443609,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0002075577443608964,"sell_reason":"roi","initial_stop_loss_abs":0.037266993000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.037266993000000005,"stop_loss_ratio":0.1,"min_rate":0.04140777,"max_rate":0.0416153277443609,"is_open":false,"buy_tag":null,"open_timestamp":1516914900000.0,"close_timestamp":1516917300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3932224183965176,"open_date":"2018-01-26 02:05:00+00:00","close_date":"2018-01-26 02:45:00+00:00","open_rate":0.00254309,"close_rate":0.002555837318295739,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":1.2747318295739177e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002288781,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002288781,"stop_loss_ratio":0.1,"min_rate":0.00254309,"max_rate":0.002555837318295739,"is_open":false,"buy_tag":null,"open_timestamp":1516932300000.0,"close_timestamp":1516934700000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.834849295523455,"open_date":"2018-01-26 02:55:00+00:00","close_date":"2018-01-26 15:10:00+00:00","open_rate":5.607e-05,"close_rate":5.6351052631578935e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":735,"profit_ratio":-0.0,"profit_abs":2.810526315789381e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0463e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0463e-05,"stop_loss_ratio":0.1,"min_rate":5.607e-05,"max_rate":5.6351052631578935e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516935300000.0,"close_timestamp":1516979400000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.39400171784748983,"open_date":"2018-01-26 06:10:00+00:00","close_date":"2018-01-26 09:25:00+00:00","open_rate":0.00253806,"close_rate":0.0025507821052631577,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":195,"profit_ratio":0.0,"profit_abs":1.2722105263157733e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002284254,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002284254,"stop_loss_ratio":0.1,"min_rate":0.00253806,"max_rate":0.0025507821052631577,"is_open":false,"buy_tag":null,"open_timestamp":1516947000000.0,"close_timestamp":1516958700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.024096385542168672,"open_date":"2018-01-26 07:25:00+00:00","close_date":"2018-01-26 09:55:00+00:00","open_rate":0.0415,"close_rate":0.04170802005012531,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":150,"profit_ratio":-0.0,"profit_abs":0.00020802005012530989,"sell_reason":"roi","initial_stop_loss_abs":0.03735,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03735,"stop_loss_ratio":0.1,"min_rate":0.0415,"max_rate":0.04170802005012531,"is_open":false,"buy_tag":null,"open_timestamp":1516951500000.0,"close_timestamp":1516960500000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":18.793459875963165,"open_date":"2018-01-26 09:55:00+00:00","close_date":"2018-01-26 10:25:00+00:00","open_rate":5.321e-05,"close_rate":5.401015037593984e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":8.00150375939842e-07,"sell_reason":"roi","initial_stop_loss_abs":4.7889e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.7889e-05,"stop_loss_ratio":0.1,"min_rate":5.321e-05,"max_rate":5.401015037593984e-05,"is_open":false,"buy_tag":null,"open_timestamp":1516960500000.0,"close_timestamp":1516962300000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.036074437437185386,"open_date":"2018-01-26 16:05:00+00:00","close_date":"2018-01-26 16:45:00+00:00","open_rate":0.02772046,"close_rate":0.02785940967418546,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00013894967418546025,"sell_reason":"roi","initial_stop_loss_abs":0.024948414,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.024948414,"stop_loss_ratio":0.1,"min_rate":0.02772046,"max_rate":0.02785940967418546,"is_open":false,"buy_tag":null,"open_timestamp":1516982700000.0,"close_timestamp":1516985100000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010569326272036914,"open_date":"2018-01-26 23:35:00+00:00","close_date":"2018-01-27 00:15:00+00:00","open_rate":0.09461341,"close_rate":0.09508766268170424,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":0.0,"profit_abs":0.00047425268170424306,"sell_reason":"roi","initial_stop_loss_abs":0.085152069,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085152069,"stop_loss_ratio":0.1,"min_rate":0.09461341,"max_rate":0.09508766268170424,"is_open":false,"buy_tag":null,"open_timestamp":1517009700000.0,"close_timestamp":1517012100000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":17.809439002671414,"open_date":"2018-01-27 00:35:00+00:00","close_date":"2018-01-27 01:30:00+00:00","open_rate":5.615e-05,"close_rate":5.643145363408521e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":55,"profit_ratio":-0.0,"profit_abs":2.814536340852038e-07,"sell_reason":"roi","initial_stop_loss_abs":5.0535e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0535e-05,"stop_loss_ratio":0.1,"min_rate":5.615e-05,"max_rate":5.643145363408521e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013300000.0,"close_timestamp":1517016600000.0},{"pair":"ADA/BTC","stake_amount":0.001,"amount":17.998560115190784,"open_date":"2018-01-27 00:45:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.556e-05,"close_rate":5.144e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4560,"profit_ratio":-0.07877175,"profit_abs":-4.120000000000001e-06,"sell_reason":"force_sell","initial_stop_loss_abs":5.0004000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":5.0004000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.144e-05,"max_rate":5.556e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517013900000.0,"close_timestamp":1517287500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492751522789634,"open_date":"2018-01-27 02:30:00+00:00","close_date":"2018-01-27 11:25:00+00:00","open_rate":0.06900001,"close_rate":0.06934587471177944,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":535,"profit_ratio":-0.0,"profit_abs":0.0003458647117794422,"sell_reason":"roi","initial_stop_loss_abs":0.062100009000000005,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.062100009000000005,"stop_loss_ratio":0.1,"min_rate":0.06900001,"max_rate":0.06934587471177944,"is_open":false,"buy_tag":null,"open_timestamp":1517020200000.0,"close_timestamp":1517052300000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010582027378879437,"open_date":"2018-01-27 06:25:00+00:00","close_date":"2018-01-27 07:05:00+00:00","open_rate":0.09449985,"close_rate":0.0949735334586466,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0004736834586466093,"sell_reason":"roi","initial_stop_loss_abs":0.085049865,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.085049865,"stop_loss_ratio":0.1,"min_rate":0.09449985,"max_rate":0.0949735334586466,"is_open":false,"buy_tag":null,"open_timestamp":1517034300000.0,"close_timestamp":1517036700000.0},{"pair":"ZEC/BTC","stake_amount":0.001,"amount":0.02434885085598385,"open_date":"2018-01-27 09:40:00+00:00","close_date":"2018-01-30 04:40:00+00:00","open_rate":0.0410697,"close_rate":0.03928809,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":4020,"profit_ratio":-0.04815133,"profit_abs":-0.001781610000000003,"sell_reason":"force_sell","initial_stop_loss_abs":0.03696273,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.03696273,"stop_loss_ratio":0.1,"min_rate":0.03928809,"max_rate":0.0410697,"is_open":false,"buy_tag":null,"open_timestamp":1517046000000.0,"close_timestamp":1517287200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.03508771929824561,"open_date":"2018-01-27 11:45:00+00:00","close_date":"2018-01-27 12:30:00+00:00","open_rate":0.0285,"close_rate":0.02864285714285714,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":-0.0,"profit_abs":0.00014285714285713902,"sell_reason":"roi","initial_stop_loss_abs":0.025650000000000003,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025650000000000003,"stop_loss_ratio":0.1,"min_rate":0.0285,"max_rate":0.02864285714285714,"is_open":false,"buy_tag":null,"open_timestamp":1517053500000.0,"close_timestamp":1517056200000.0},{"pair":"XMR/BTC","stake_amount":0.001,"amount":0.034887307020861215,"open_date":"2018-01-27 12:35:00+00:00","close_date":"2018-01-27 15:25:00+00:00","open_rate":0.02866372,"close_rate":0.02880739779448621,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":170,"profit_ratio":-0.0,"profit_abs":0.00014367779448621124,"sell_reason":"roi","initial_stop_loss_abs":0.025797348,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.025797348,"stop_loss_ratio":0.1,"min_rate":0.02866372,"max_rate":0.02880739779448621,"is_open":false,"buy_tag":null,"open_timestamp":1517056500000.0,"close_timestamp":1517066700000.0},{"pair":"ETH/BTC","stake_amount":0.001,"amount":0.010484268355332824,"open_date":"2018-01-27 15:50:00+00:00","close_date":"2018-01-27 16:50:00+00:00","open_rate":0.095381,"close_rate":0.09585910025062656,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":-0.0,"profit_abs":0.00047810025062657024,"sell_reason":"roi","initial_stop_loss_abs":0.0858429,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.0858429,"stop_loss_ratio":0.1,"min_rate":0.095381,"max_rate":0.09585910025062656,"is_open":false,"buy_tag":null,"open_timestamp":1517068200000.0,"close_timestamp":1517071800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014794886650455417,"open_date":"2018-01-27 17:05:00+00:00","close_date":"2018-01-27 17:45:00+00:00","open_rate":0.06759092,"close_rate":0.06792972160401002,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00033880160401002224,"sell_reason":"roi","initial_stop_loss_abs":0.060831828,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060831828,"stop_loss_ratio":0.1,"min_rate":0.06759092,"max_rate":0.06792972160401002,"is_open":false,"buy_tag":null,"open_timestamp":1517072700000.0,"close_timestamp":1517075100000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.38684569885609726,"open_date":"2018-01-27 23:40:00+00:00","close_date":"2018-01-28 01:05:00+00:00","open_rate":0.00258501,"close_rate":0.002597967443609022,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":85,"profit_ratio":-0.0,"profit_abs":1.2957443609021985e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002326509,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002326509,"stop_loss_ratio":0.1,"min_rate":0.00258501,"max_rate":0.002597967443609022,"is_open":false,"buy_tag":null,"open_timestamp":1517096400000.0,"close_timestamp":1517101500000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014928710926711672,"open_date":"2018-01-28 02:25:00+00:00","close_date":"2018-01-28 08:10:00+00:00","open_rate":0.06698502,"close_rate":0.0673207845112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":345,"profit_ratio":-0.0,"profit_abs":0.00033576451127818874,"sell_reason":"roi","initial_stop_loss_abs":0.060286518000000004,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060286518000000004,"stop_loss_ratio":0.1,"min_rate":0.06698502,"max_rate":0.0673207845112782,"is_open":false,"buy_tag":null,"open_timestamp":1517106300000.0,"close_timestamp":1517127000000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014767187899175548,"open_date":"2018-01-28 10:25:00+00:00","close_date":"2018-01-28 16:30:00+00:00","open_rate":0.0677177,"close_rate":0.06805713709273183,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":365,"profit_ratio":-0.0,"profit_abs":0.0003394370927318202,"sell_reason":"roi","initial_stop_loss_abs":0.06094593000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06094593000000001,"stop_loss_ratio":0.1,"min_rate":0.0677177,"max_rate":0.06805713709273183,"is_open":false,"buy_tag":null,"open_timestamp":1517135100000.0,"close_timestamp":1517157000000.0},{"pair":"XLM/BTC","stake_amount":0.001,"amount":19.175455417066157,"open_date":"2018-01-28 20:35:00+00:00","close_date":"2018-01-28 21:35:00+00:00","open_rate":5.215e-05,"close_rate":5.2411403508771925e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":60,"profit_ratio":0.0,"profit_abs":2.6140350877192417e-07,"sell_reason":"roi","initial_stop_loss_abs":4.6935000000000004e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":4.6935000000000004e-05,"stop_loss_ratio":0.1,"min_rate":5.215e-05,"max_rate":5.2411403508771925e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517171700000.0,"close_timestamp":1517175300000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.36521808998243305,"open_date":"2018-01-28 22:00:00+00:00","close_date":"2018-01-28 22:30:00+00:00","open_rate":0.00273809,"close_rate":0.002779264285714285,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.117428571428529e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002464281,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002464281,"stop_loss_ratio":0.1,"min_rate":0.00273809,"max_rate":0.002779264285714285,"is_open":false,"buy_tag":null,"open_timestamp":1517176800000.0,"close_timestamp":1517178600000.0},{"pair":"ETC/BTC","stake_amount":0.001,"amount":0.3641236272539253,"open_date":"2018-01-29 00:00:00+00:00","close_date":"2018-01-29 00:30:00+00:00","open_rate":0.00274632,"close_rate":0.002787618045112782,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":30,"profit_ratio":0.00997506,"profit_abs":4.129804511278194e-05,"sell_reason":"roi","initial_stop_loss_abs":0.002471688,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.002471688,"stop_loss_ratio":0.1,"min_rate":0.00274632,"max_rate":0.002787618045112782,"is_open":false,"buy_tag":null,"open_timestamp":1517184000000.0,"close_timestamp":1517185800000.0},{"pair":"LTC/BTC","stake_amount":0.001,"amount":0.061634117689115045,"open_date":"2018-01-29 02:15:00+00:00","close_date":"2018-01-29 03:00:00+00:00","open_rate":0.01622478,"close_rate":0.016306107218045113,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":45,"profit_ratio":0.0,"profit_abs":8.132721804511231e-05,"sell_reason":"roi","initial_stop_loss_abs":0.014602302000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.014602302000000001,"stop_loss_ratio":0.1,"min_rate":0.01622478,"max_rate":0.016306107218045113,"is_open":false,"buy_tag":null,"open_timestamp":1517192100000.0,"close_timestamp":1517194800000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014492753623188404,"open_date":"2018-01-29 03:05:00+00:00","close_date":"2018-01-29 03:45:00+00:00","open_rate":0.069,"close_rate":0.06934586466165413,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.00034586466165412166,"sell_reason":"roi","initial_stop_loss_abs":0.06210000000000001,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.06210000000000001,"stop_loss_ratio":0.1,"min_rate":0.069,"max_rate":0.06934586466165413,"is_open":false,"buy_tag":null,"open_timestamp":1517195100000.0,"close_timestamp":1517197500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.42204454597373,"open_date":"2018-01-29 05:20:00+00:00","close_date":"2018-01-29 06:55:00+00:00","open_rate":8.755e-05,"close_rate":8.798884711779448e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":95,"profit_ratio":-0.0,"profit_abs":4.3884711779447504e-07,"sell_reason":"roi","initial_stop_loss_abs":7.879500000000001e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":7.879500000000001e-05,"stop_loss_ratio":0.1,"min_rate":8.755e-05,"max_rate":8.798884711779448e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517203200000.0,"close_timestamp":1517208900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014650376815016871,"open_date":"2018-01-29 07:00:00+00:00","close_date":"2018-01-29 19:25:00+00:00","open_rate":0.06825763,"close_rate":0.06859977350877192,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":745,"profit_ratio":-0.0,"profit_abs":0.00034214350877191657,"sell_reason":"roi","initial_stop_loss_abs":0.061431867,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.061431867,"stop_loss_ratio":0.1,"min_rate":0.06825763,"max_rate":0.06859977350877192,"is_open":false,"buy_tag":null,"open_timestamp":1517209200000.0,"close_timestamp":1517253900000.0},{"pair":"DASH/BTC","stake_amount":0.001,"amount":0.014894490408841846,"open_date":"2018-01-29 19:45:00+00:00","close_date":"2018-01-29 20:25:00+00:00","open_rate":0.06713892,"close_rate":0.06747545593984962,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":40,"profit_ratio":-0.0,"profit_abs":0.0003365359398496137,"sell_reason":"roi","initial_stop_loss_abs":0.060425028000000006,"initial_stop_loss_ratio":0.1,"stop_loss_abs":0.060425028000000006,"stop_loss_ratio":0.1,"min_rate":0.06713892,"max_rate":0.06747545593984962,"is_open":false,"buy_tag":null,"open_timestamp":1517255100000.0,"close_timestamp":1517257500000.0},{"pair":"TRX/BTC","stake_amount":0.001,"amount":11.193194537721066,"open_date":"2018-01-29 23:30:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":8.934e-05,"close_rate":8.8e-05,"fee_open":0.0025,"fee_close":0.0025,"trade_duration":315,"profit_ratio":-0.0199116,"profit_abs":-1.3399999999999973e-06,"sell_reason":"force_sell","initial_stop_loss_abs":8.0406e-05,"initial_stop_loss_ratio":0.1,"stop_loss_abs":8.0406e-05,"stop_loss_ratio":0.1,"min_rate":8.8e-05,"max_rate":8.934e-05,"is_open":false,"buy_tag":null,"open_timestamp":1517268600000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},"worst_pair":{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},"results_per_pair":[{"key":"ETH/BTC","trades":21,"profit_mean":0.0009500057142857142,"profit_mean_pct":0.09500057142857142,"profit_sum":0.01995012,"profit_sum_pct":2.0,"profit_total_abs":0.011505731278195264,"profit_total":1.1505731278195264,"profit_total_pct":115.06,"duration_avg":"2:17:00","wins":21,"draws":0,"losses":0},{"key":"DASH/BTC","trades":16,"profit_mean":0.0018703237499999997,"profit_mean_pct":0.18703237499999997,"profit_sum":0.029925179999999996,"profit_sum_pct":2.99,"profit_total_abs":0.007475052681704161,"profit_total":0.7475052681704161,"profit_total_pct":74.75,"duration_avg":"3:03:00","wins":16,"draws":0,"losses":0},{"key":"ZEC/BTC","trades":21,"profit_mean":-0.00039290904761904774,"profit_mean_pct":-0.03929090476190478,"profit_sum":-0.008251090000000003,"profit_sum_pct":-0.83,"profit_total_abs":0.004452605639097655,"profit_total":0.4452605639097655,"profit_total_pct":44.53,"duration_avg":"4:17:00","wins":20,"draws":0,"losses":1},{"key":"LTC/BTC","trades":8,"profit_mean":0.00748129625,"profit_mean_pct":0.748129625,"profit_sum":0.05985037,"profit_sum_pct":5.99,"profit_total_abs":0.0015944746365914707,"profit_total":0.15944746365914708,"profit_total_pct":15.94,"duration_avg":"1:59:00","wins":8,"draws":0,"losses":0},{"key":"XMR/BTC","trades":16,"profit_mean":-0.0027899012500000007,"profit_mean_pct":-0.2789901250000001,"profit_sum":-0.04463842000000001,"profit_sum_pct":-4.46,"profit_total_abs":0.0006671885263157366,"profit_total":0.06671885263157366,"profit_total_pct":6.67,"duration_avg":"8:41:00","wins":15,"draws":0,"losses":1},{"key":"ETC/BTC","trades":20,"profit_mean":0.0022568569999999997,"profit_mean_pct":0.22568569999999996,"profit_sum":0.04513713999999999,"profit_sum_pct":4.51,"profit_total_abs":0.00036538235338345404,"profit_total":0.0365382353383454,"profit_total_pct":3.65,"duration_avg":"1:45:00","wins":19,"draws":0,"losses":1},{"key":"TRX/BTC","trades":15,"profit_mean":0.0023467073333333323,"profit_mean_pct":0.23467073333333321,"profit_sum":0.035200609999999986,"profit_sum_pct":3.52,"profit_total_abs":1.1329523809523682e-05,"profit_total":0.0011329523809523682,"profit_total_pct":0.11,"duration_avg":"2:28:00","wins":13,"draws":0,"losses":2},{"key":"XLM/BTC","trades":21,"profit_mean":0.0026243899999999994,"profit_mean_pct":0.2624389999999999,"profit_sum":0.05511218999999999,"profit_sum_pct":5.51,"profit_total_abs":7.340779448621465e-06,"profit_total":0.0007340779448621465,"profit_total_pct":0.07,"duration_avg":"3:21:00","wins":20,"draws":0,"losses":1},{"key":"ADA/BTC","trades":29,"profit_mean":-0.0011598141379310352,"profit_mean_pct":-0.11598141379310352,"profit_sum":-0.03363461000000002,"profit_sum_pct":-3.36,"profit_total_abs":4.916634085212862e-06,"profit_total":0.0004916634085212862,"profit_total_pct":0.05,"duration_avg":"5:35:00","wins":27,"draws":0,"losses":2},{"key":"NXT/BTC","trades":12,"profit_mean":-0.0012261025000000006,"profit_mean_pct":-0.12261025000000006,"profit_sum":-0.014713230000000008,"profit_sum_pct":-1.47,"profit_total_abs":1.4774411027568458e-06,"profit_total":0.00014774411027568458,"profit_total_pct":0.01,"duration_avg":"0:57:00","wins":11,"draws":0,"losses":1},{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"results_per_buy_tag":[{"key":"TOTAL","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9}],"sell_reason_summary":[{"sell_reason":"roi","trades":170,"wins":170,"draws":0,"losses":0,"profit_mean":0.005398268352941177,"profit_mean_pct":0.54,"profit_sum":0.91770562,"profit_sum_pct":91.77,"profit_total_abs":0.031232837493733862,"profit_total":0.30590187333333335,"profit_total_pct":30.59},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.10448878000000002,"profit_mean_pct":-10.45,"profit_sum":-0.6269326800000001,"profit_sum_pct":-62.69,"profit_total_abs":-0.0033602680000000026,"profit_total":-0.20897756000000003,"profit_total_pct":-20.9},{"sell_reason":"force_sell","trades":3,"wins":0,"draws":0,"losses":3,"profit_mean":-0.04894489333333333,"profit_mean_pct":-4.89,"profit_sum":-0.14683468,"profit_sum_pct":-14.68,"profit_total_abs":-0.001787070000000003,"profit_total":-0.04894489333333333,"profit_total_pct":-4.89}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":179,"total_volume":0.17900000000000005,"avg_stake_amount":0.0010000000000000002,"profit_mean":0.0008041243575418989,"profit_median":0.0,"profit_total":2.6085499493733857,"profit_total_abs":0.026085499493733857,"backtest_start":"2018-01-10 07:15:00","backtest_start_ts":1515568500000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":"2020-10-01 18:00:00+00:00","backtest_run_end_ts":"2020-10-01 18:01:00+00:00","trades_per_day":9.42,"market_change":1.22,"pairlist":[],"stake_amount":0.001,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":0.01,"dry_run_wallet":0.01,"final_balance":0.03608549949373386,"rejected_signals":0,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timeframe_detail":"","timerange":"","enable_protections":false,"strategy_name":"StrategyTestV2","stoploss":0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":false,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.17955111999999998,"backtest_worst_day":-0.14683468,"backtest_best_day_abs":0.0071570099,"backtest_worst_day_abs":-0.0023093218,"winning_days":19,"draw_days":0,"losing_days":2,"daily_profit":[["2018-01-10",0.0025815306],["2018-01-11",0.0049356655],["2018-01-12",0.0006395218],["2018-01-13",0.0002574589],["2018-01-14",0.0010443828],["2018-01-15",0.0024030209],["2018-01-16",0.0071570099],["2018-01-17",0.001137038],["2018-01-18",0.0013712174],["2018-01-19",0.000584673],["2018-01-20",0.0006143386],["2018-01-21",0.0004749361],["2018-01-22",9.91669e-05],["2018-01-23",0.0015726664],["2018-01-24",0.0006610219],["2018-01-25",-0.0023093218],["2018-01-26",0.0003735204],["2018-01-27",0.0023975191],["2018-01-28",0.0007295947],["2018-01-29",0.0011476082],["2018-01-30",-0.00178707]],"wins":48,"losses":9,"draws":122,"holding_avg":"3:40:00","holding_avg_s":13200.0,"winner_holding_avg":"0:24:00","winner_holding_avg_s":1440.0,"loser_holding_avg":"1 day, 5:57:00","loser_holding_avg_s":107820.0,"max_drawdown":0.21142322000000008,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":0.0030822220000000025,"drawdown_start":"2018-01-25 01:30:00","drawdown_start_ts":1516843800000.0,"drawdown_end":"2018-01-25 03:50:00","drawdown_end_ts":1516852200000.0,"max_drawdown_low":0.02245167355388436,"max_drawdown_high":0.025533895553884363,"csum_min":0.01000434887218045,"csum_max":0.03608683949373386}},"strategy_comparison":[{"key":"StrategyTestV2","trades":179,"profit_mean":0.0008041243575418989,"profit_mean_pct":0.0804124357541899,"profit_sum":0.1439382599999999,"profit_sum_pct":14.39,"profit_total_abs":0.026085499493733857,"profit_total":2.6085499493733857,"profit_total_pct":260.85,"duration_avg":"3:40:00","wins":170,"draws":0,"losses":9,"max_drawdown_account":0.08674033488183289,"max_drawdown_abs":"0.00308222"}]} \ No newline at end of file diff --git a/tests/testdata/backtest-result_test.json b/tests/testdata/backtest-result_test.json deleted file mode 100644 index dce22acaf..000000000 --- a/tests/testdata/backtest-result_test.json +++ /dev/null @@ -1 +0,0 @@ -[["TRX/BTC",0.03990025,1515568500.0,1515568800.0,27,5,9.64e-05,0.00010074887218045112,false,"roi"],["ADA/BTC",0.03990025,1515568500.0,1515569400.0,27,15,4.756e-05,4.9705563909774425e-05,false,"roi"],["XLM/BTC",0.03990025,1515569100.0,1515569700.0,29,10,3.339e-05,3.489631578947368e-05,false,"roi"],["TRX/BTC",0.03990025,1515569100.0,1515570000.0,29,15,9.696e-05,0.00010133413533834584,false,"roi"],["ETH/BTC",-0.0,1515569700.0,1515573300.0,31,60,0.0943,0.09477268170426063,false,"roi"],["XMR/BTC",0.00997506,1515570000.0,1515571800.0,32,30,0.02719607,0.02760503345864661,false,"roi"],["ZEC/BTC",0.0,1515572100.0,1515578100.0,39,100,0.04634952,0.046581848421052625,false,"roi"],["NXT/BTC",-0.0,1515595500.0,1515599400.0,117,65,3.066e-05,3.081368421052631e-05,false,"roi"],["LTC/BTC",0.0,1515602100.0,1515604500.0,139,40,0.0168999,0.016984611278195488,false,"roi"],["ETH/BTC",-0.0,1515602400.0,1515604800.0,140,40,0.09132568,0.0917834528320802,false,"roi"],["ETH/BTC",-0.0,1515610200.0,1515613500.0,166,55,0.08898003,0.08942604518796991,false,"roi"],["ETH/BTC",0.0,1515622500.0,1515625200.0,207,45,0.08560008,0.08602915308270676,false,"roi"],["ETC/BTC",0.00997506,1515624600.0,1515626400.0,214,30,0.00249083,0.0025282860902255634,false,"roi"],["NXT/BTC",-0.0,1515626100.0,1515629700.0,219,60,3.022e-05,3.037147869674185e-05,false,"roi"],["ETC/BTC",0.01995012,1515627600.0,1515629100.0,224,25,0.002437,0.0024980776942355883,false,"roi"],["ZEC/BTC",0.00997506,1515628800.0,1515630900.0,228,35,0.04771803,0.04843559436090225,false,"roi"],["XLM/BTC",-0.10448878,1515642000.0,1515644700.0,272,45,3.651e-05,3.2859000000000005e-05,false,"stop_loss"],["ETH/BTC",0.00997506,1515642900.0,1515644700.0,275,30,0.08824105,0.08956798308270676,false,"roi"],["ETC/BTC",-0.0,1515643200.0,1515646200.0,276,50,0.00243,0.002442180451127819,false,"roi"],["ZEC/BTC",0.01995012,1515645000.0,1515646500.0,282,25,0.04545064,0.046589753784461146,false,"roi"],["XLM/BTC",0.01995012,1515645000.0,1515646200.0,282,20,3.372e-05,3.456511278195488e-05,false,"roi"],["XMR/BTC",0.01995012,1515646500.0,1515647700.0,287,20,0.02644,0.02710265664160401,false,"roi"],["ETH/BTC",-0.0,1515669600.0,1515672000.0,364,40,0.08812,0.08856170426065162,false,"roi"],["XMR/BTC",-0.0,1515670500.0,1515672900.0,367,40,0.02683577,0.026970285137844607,false,"roi"],["ADA/BTC",0.01995012,1515679200.0,1515680700.0,396,25,4.919e-05,5.04228320802005e-05,false,"roi"],["ETH/BTC",-0.0,1515698700.0,1515702900.0,461,70,0.08784896,0.08828930566416039,false,"roi"],["ADA/BTC",-0.0,1515710100.0,1515713400.0,499,55,5.105e-05,5.130588972431077e-05,false,"roi"],["XLM/BTC",0.00997506,1515711300.0,1515713100.0,503,30,3.96e-05,4.019548872180451e-05,false,"roi"],["NXT/BTC",-0.0,1515711300.0,1515713700.0,503,40,2.885e-05,2.899461152882205e-05,false,"roi"],["XMR/BTC",0.00997506,1515713400.0,1515715500.0,510,35,0.02645,0.026847744360902256,false,"roi"],["ZEC/BTC",-0.0,1515714900.0,1515719700.0,515,80,0.048,0.04824060150375939,false,"roi"],["XLM/BTC",0.01995012,1515791700.0,1515793200.0,771,25,4.692e-05,4.809593984962405e-05,false,"roi"],["ETC/BTC",-0.0,1515804900.0,1515824400.0,815,325,0.00256966,0.0025825405012531327,false,"roi"],["ADA/BTC",0.0,1515840900.0,1515843300.0,935,40,6.262e-05,6.293388471177944e-05,false,"roi"],["XLM/BTC",0.0,1515848700.0,1516025400.0,961,2945,4.73e-05,4.753709273182957e-05,false,"roi"],["ADA/BTC",-0.0,1515850200.0,1515854700.0,966,75,6.063e-05,6.0933909774436085e-05,false,"roi"],["TRX/BTC",-0.0,1515850800.0,1515886200.0,968,590,0.00011082,0.00011137548872180449,false,"roi"],["ADA/BTC",-0.0,1515856500.0,1515858900.0,987,40,5.93e-05,5.9597243107769415e-05,false,"roi"],["ZEC/BTC",-0.0,1515861000.0,1515863400.0,1002,40,0.04850003,0.04874313791979949,false,"roi"],["ETH/BTC",-0.0,1515881100.0,1515911100.0,1069,500,0.09825019,0.09874267215538847,false,"roi"],["ADA/BTC",0.0,1515889200.0,1515970500.0,1096,1355,6.018e-05,6.048165413533834e-05,false,"roi"],["ETH/BTC",-0.0,1515933900.0,1515936300.0,1245,40,0.09758999,0.0980791628822055,false,"roi"],["ETC/BTC",0.00997506,1515943800.0,1515945600.0,1278,30,0.00311,0.0031567669172932328,false,"roi"],["ETC/BTC",-0.0,1515962700.0,1515968100.0,1341,90,0.00312401,0.003139669197994987,false,"roi"],["LTC/BTC",0.0,1515972900.0,1515976200.0,1375,55,0.0174679,0.017555458395989976,false,"roi"],["DASH/BTC",-0.0,1515973500.0,1515975900.0,1377,40,0.07346846,0.07383672295739348,false,"roi"],["ETH/BTC",-0.0,1515983100.0,1515985500.0,1409,40,0.097994,0.09848519799498745,false,"roi"],["ETH/BTC",-0.0,1516000800.0,1516003200.0,1468,40,0.09659,0.09707416040100249,false,"roi"],["TRX/BTC",0.00997506,1516004400.0,1516006500.0,1480,35,9.987e-05,0.00010137180451127818,false,"roi"],["ETH/BTC",0.0,1516018200.0,1516071000.0,1526,880,0.0948969,0.09537257368421052,false,"roi"],["DASH/BTC",-0.0,1516025400.0,1516038000.0,1550,210,0.071,0.07135588972431077,false,"roi"],["ZEC/BTC",-0.0,1516026600.0,1516029000.0,1554,40,0.04600501,0.046235611553884705,false,"roi"],["TRX/BTC",-0.0,1516039800.0,1516044300.0,1598,75,9.438e-05,9.485308270676691e-05,false,"roi"],["XMR/BTC",-0.0,1516041300.0,1516043700.0,1603,40,0.03040001,0.030552391002506264,false,"roi"],["ADA/BTC",-0.10448878,1516047900.0,1516091100.0,1625,720,5.837e-05,5.2533e-05,false,"stop_loss"],["ZEC/BTC",-0.0,1516048800.0,1516053600.0,1628,80,0.046036,0.04626675689223057,false,"roi"],["ETC/BTC",-0.0,1516062600.0,1516065000.0,1674,40,0.0028685,0.0028828784461152877,false,"roi"],["DASH/BTC",0.0,1516065300.0,1516070100.0,1683,80,0.06731755,0.0676549813283208,false,"roi"],["ETH/BTC",0.0,1516088700.0,1516092000.0,1761,55,0.09217614,0.09263817578947368,false,"roi"],["LTC/BTC",0.01995012,1516091700.0,1516092900.0,1771,20,0.0165,0.016913533834586467,false,"roi"],["TRX/BTC",0.03990025,1516091700.0,1516092000.0,1771,5,7.953e-05,8.311781954887218e-05,false,"roi"],["ZEC/BTC",-0.0,1516092300.0,1516096200.0,1773,65,0.045202,0.04542857644110275,false,"roi"],["ADA/BTC",0.00997506,1516094100.0,1516095900.0,1779,30,5.248e-05,5.326917293233082e-05,false,"roi"],["XMR/BTC",0.0,1516094100.0,1516096500.0,1779,40,0.02892318,0.02906815834586466,false,"roi"],["ADA/BTC",0.01995012,1516096200.0,1516097400.0,1786,20,5.158e-05,5.287273182957392e-05,false,"roi"],["ZEC/BTC",0.00997506,1516097100.0,1516099200.0,1789,35,0.04357584,0.044231115789473675,false,"roi"],["XMR/BTC",0.00997506,1516097100.0,1516098900.0,1789,30,0.02828232,0.02870761804511278,false,"roi"],["ADA/BTC",0.00997506,1516110300.0,1516112400.0,1833,35,5.362e-05,5.4426315789473676e-05,false,"roi"],["ADA/BTC",-0.0,1516123800.0,1516127100.0,1878,55,5.302e-05,5.328576441102756e-05,false,"roi"],["ETH/BTC",0.00997506,1516126500.0,1516128300.0,1887,30,0.09129999,0.09267292218045112,false,"roi"],["XLM/BTC",0.01995012,1516126500.0,1516127700.0,1887,20,3.808e-05,3.903438596491228e-05,false,"roi"],["XMR/BTC",0.00997506,1516129200.0,1516131000.0,1896,30,0.02811012,0.028532828571428567,false,"roi"],["ETC/BTC",-0.10448878,1516137900.0,1516141500.0,1925,60,0.00258379,0.002325411,false,"stop_loss"],["NXT/BTC",-0.10448878,1516137900.0,1516142700.0,1925,80,2.559e-05,2.3031e-05,false,"stop_loss"],["TRX/BTC",-0.10448878,1516138500.0,1516141500.0,1927,50,7.62e-05,6.858e-05,false,"stop_loss"],["LTC/BTC",0.03990025,1516141800.0,1516142400.0,1938,10,0.0151,0.015781203007518795,false,"roi"],["ETC/BTC",0.03990025,1516141800.0,1516142100.0,1938,5,0.00229844,0.002402129022556391,false,"roi"],["ETC/BTC",0.03990025,1516142400.0,1516142700.0,1940,5,0.00235676,0.00246308,false,"roi"],["DASH/BTC",0.01995012,1516142700.0,1516143900.0,1941,20,0.0630692,0.06464988170426066,false,"roi"],["NXT/BTC",0.03990025,1516143000.0,1516143300.0,1942,5,2.2e-05,2.2992481203007514e-05,false,"roi"],["ADA/BTC",0.00997506,1516159800.0,1516161600.0,1998,30,4.974e-05,5.048796992481203e-05,false,"roi"],["TRX/BTC",0.01995012,1516161300.0,1516162500.0,2003,20,7.108e-05,7.28614536340852e-05,false,"roi"],["ZEC/BTC",-0.0,1516181700.0,1516184100.0,2071,40,0.04327,0.04348689223057644,false,"roi"],["ADA/BTC",-0.0,1516184400.0,1516208400.0,2080,400,4.997e-05,5.022047619047618e-05,false,"roi"],["DASH/BTC",-0.0,1516185000.0,1516188300.0,2082,55,0.06836818,0.06871087764411027,false,"roi"],["XLM/BTC",-0.0,1516185000.0,1516187400.0,2082,40,3.63e-05,3.648195488721804e-05,false,"roi"],["XMR/BTC",-0.0,1516192200.0,1516226700.0,2106,575,0.0281,0.02824085213032581,false,"roi"],["ETH/BTC",-0.0,1516192500.0,1516208100.0,2107,260,0.08651001,0.08694364413533832,false,"roi"],["ADA/BTC",-0.0,1516251600.0,1516254900.0,2304,55,5.633e-05,5.6612355889724306e-05,false,"roi"],["DASH/BTC",0.00997506,1516252800.0,1516254900.0,2308,35,0.06988494,0.07093584135338346,false,"roi"],["ADA/BTC",-0.0,1516260900.0,1516263300.0,2335,40,5.545e-05,5.572794486215538e-05,false,"roi"],["LTC/BTC",-0.0,1516266000.0,1516268400.0,2352,40,0.01633527,0.016417151052631574,false,"roi"],["ETC/BTC",-0.0,1516293600.0,1516296000.0,2444,40,0.00269734,0.0027108605012531326,false,"roi"],["XLM/BTC",0.01995012,1516298700.0,1516300200.0,2461,25,4.475e-05,4.587155388471177e-05,false,"roi"],["NXT/BTC",0.00997506,1516299900.0,1516301700.0,2465,30,2.79e-05,2.8319548872180444e-05,false,"roi"],["ZEC/BTC",0.0,1516306200.0,1516308600.0,2486,40,0.04439326,0.04461578260651629,false,"roi"],["XLM/BTC",0.0,1516311000.0,1516322100.0,2502,185,4.49e-05,4.51250626566416e-05,false,"roi"],["XMR/BTC",-0.0,1516312500.0,1516338300.0,2507,430,0.02855,0.028693107769423555,false,"roi"],["ADA/BTC",0.0,1516313400.0,1516315800.0,2510,40,5.796e-05,5.8250526315789473e-05,false,"roi"],["ZEC/BTC",0.0,1516319400.0,1516321800.0,2530,40,0.04340323,0.04362079005012531,false,"roi"],["ZEC/BTC",0.0,1516380300.0,1516383300.0,2733,50,0.04454455,0.04476783095238095,false,"roi"],["ADA/BTC",-0.0,1516382100.0,1516391700.0,2739,160,5.62e-05,5.648170426065162e-05,false,"roi"],["XLM/BTC",-0.0,1516382400.0,1516392900.0,2740,175,4.339e-05,4.360749373433584e-05,false,"roi"],["TRX/BTC",0.0,1516423500.0,1516469700.0,2877,770,0.0001009,0.00010140576441102757,false,"roi"],["ETC/BTC",-0.0,1516423800.0,1516461300.0,2878,625,0.00270505,0.002718609147869674,false,"roi"],["XMR/BTC",-0.0,1516423800.0,1516431600.0,2878,130,0.03000002,0.030150396040100245,false,"roi"],["ADA/BTC",-0.0,1516438800.0,1516441200.0,2928,40,5.46e-05,5.4873684210526304e-05,false,"roi"],["XMR/BTC",-0.10448878,1516472700.0,1516852200.0,3041,6325,0.03082222,0.027739998000000002,false,"stop_loss"],["ETH/BTC",-0.0,1516487100.0,1516490100.0,3089,50,0.08969999,0.09014961401002504,false,"roi"],["LTC/BTC",0.0,1516503000.0,1516545000.0,3142,700,0.01632501,0.01640683962406015,false,"roi"],["DASH/BTC",-0.0,1516530000.0,1516532400.0,3232,40,0.070538,0.07089157393483708,false,"roi"],["ADA/BTC",-0.0,1516549800.0,1516560300.0,3298,175,5.301e-05,5.3275714285714276e-05,false,"roi"],["XLM/BTC",0.0,1516551600.0,1516554000.0,3304,40,3.955e-05,3.9748245614035085e-05,false,"roi"],["ETC/BTC",0.00997506,1516569300.0,1516571100.0,3363,30,0.00258505,0.002623922932330827,false,"roi"],["XLM/BTC",-0.0,1516569300.0,1516571700.0,3363,40,3.903e-05,3.922563909774435e-05,false,"roi"],["ADA/BTC",-0.0,1516581300.0,1516617300.0,3403,600,5.236e-05,5.262245614035087e-05,false,"roi"],["TRX/BTC",0.0,1516584600.0,1516587000.0,3414,40,9.028e-05,9.073253132832079e-05,false,"roi"],["ETC/BTC",-0.0,1516623900.0,1516631700.0,3545,130,0.002687,0.002700468671679198,false,"roi"],["XLM/BTC",-0.0,1516626900.0,1516629300.0,3555,40,4.168e-05,4.1888922305764405e-05,false,"roi"],["TRX/BTC",0.00997506,1516629600.0,1516631400.0,3564,30,8.821e-05,8.953646616541353e-05,false,"roi"],["ADA/BTC",-0.0,1516636500.0,1516639200.0,3587,45,5.172e-05,5.1979248120300745e-05,false,"roi"],["NXT/BTC",0.01995012,1516637100.0,1516638300.0,3589,20,3.026e-05,3.101839598997494e-05,false,"roi"],["DASH/BTC",0.0,1516650600.0,1516666200.0,3634,260,0.07064,0.07099408521303258,false,"roi"],["LTC/BTC",0.0,1516656300.0,1516658700.0,3653,40,0.01644483,0.01652726022556391,false,"roi"],["XLM/BTC",0.00997506,1516665900.0,1516667700.0,3685,30,4.331e-05,4.3961278195488714e-05,false,"roi"],["NXT/BTC",0.01995012,1516672200.0,1516673700.0,3706,25,3.2e-05,3.2802005012531326e-05,false,"roi"],["ETH/BTC",0.0,1516681500.0,1516684500.0,3737,50,0.09167706,0.09213659413533835,false,"roi"],["DASH/BTC",0.0,1516692900.0,1516698000.0,3775,85,0.0692498,0.06959691679197995,false,"roi"],["NXT/BTC",0.0,1516704600.0,1516712700.0,3814,135,3.182e-05,3.197949874686716e-05,false,"roi"],["ZEC/BTC",-0.0,1516705500.0,1516723500.0,3817,300,0.04088,0.04108491228070175,false,"roi"],["ADA/BTC",-0.0,1516719300.0,1516721700.0,3863,40,5.15e-05,5.175814536340851e-05,false,"roi"],["ETH/BTC",0.0,1516725300.0,1516752300.0,3883,450,0.09071698,0.09117170170426064,false,"roi"],["NXT/BTC",-0.0,1516728300.0,1516733100.0,3893,80,3.128e-05,3.1436791979949865e-05,false,"roi"],["TRX/BTC",-0.0,1516738500.0,1516744800.0,3927,105,9.555e-05,9.602894736842104e-05,false,"roi"],["ZEC/BTC",-0.0,1516746600.0,1516749000.0,3954,40,0.04080001,0.041004521328320796,false,"roi"],["ADA/BTC",-0.0,1516751400.0,1516764900.0,3970,225,5.163e-05,5.1888796992481196e-05,false,"roi"],["ZEC/BTC",0.0,1516753200.0,1516758600.0,3976,90,0.04040781,0.04061035541353383,false,"roi"],["ADA/BTC",-0.0,1516776300.0,1516778700.0,4053,40,5.132e-05,5.157724310776942e-05,false,"roi"],["ADA/BTC",0.03990025,1516803300.0,1516803900.0,4143,10,5.198e-05,5.432496240601503e-05,false,"roi"],["NXT/BTC",-0.0,1516805400.0,1516811700.0,4150,105,3.054e-05,3.069308270676692e-05,false,"roi"],["TRX/BTC",0.0,1516806600.0,1516810500.0,4154,65,9.263e-05,9.309431077694235e-05,false,"roi"],["ADA/BTC",-0.0,1516833600.0,1516836300.0,4244,45,5.514e-05,5.5416390977443596e-05,false,"roi"],["XLM/BTC",0.0,1516841400.0,1516843800.0,4270,40,4.921e-05,4.9456666666666664e-05,false,"roi"],["ETC/BTC",0.0,1516868100.0,1516882500.0,4359,240,0.0026,0.002613032581453634,false,"roi"],["XMR/BTC",-0.0,1516875900.0,1516896900.0,4385,350,0.02799871,0.028139054411027563,false,"roi"],["ZEC/BTC",-0.0,1516878000.0,1516880700.0,4392,45,0.04078902,0.0409934762406015,false,"roi"],["NXT/BTC",-0.0,1516885500.0,1516887900.0,4417,40,2.89e-05,2.904486215538847e-05,false,"roi"],["ZEC/BTC",-0.0,1516886400.0,1516889100.0,4420,45,0.041103,0.041309030075187964,false,"roi"],["XLM/BTC",0.00997506,1516895100.0,1516896900.0,4449,30,5.428e-05,5.5096240601503756e-05,false,"roi"],["XLM/BTC",-0.0,1516902300.0,1516922100.0,4473,330,5.414e-05,5.441137844611528e-05,false,"roi"],["ZEC/BTC",-0.0,1516914900.0,1516917300.0,4515,40,0.04140777,0.0416153277443609,false,"roi"],["ETC/BTC",0.0,1516932300.0,1516934700.0,4573,40,0.00254309,0.002555837318295739,false,"roi"],["ADA/BTC",-0.0,1516935300.0,1516979400.0,4583,735,5.607e-05,5.6351052631578935e-05,false,"roi"],["ETC/BTC",0.0,1516947000.0,1516958700.0,4622,195,0.00253806,0.0025507821052631577,false,"roi"],["ZEC/BTC",-0.0,1516951500.0,1516960500.0,4637,150,0.0415,0.04170802005012531,false,"roi"],["XLM/BTC",0.00997506,1516960500.0,1516962300.0,4667,30,5.321e-05,5.401015037593984e-05,false,"roi"],["XMR/BTC",-0.0,1516982700.0,1516985100.0,4741,40,0.02772046,0.02785940967418546,false,"roi"],["ETH/BTC",0.0,1517009700.0,1517012100.0,4831,40,0.09461341,0.09508766268170425,false,"roi"],["XLM/BTC",-0.0,1517013300.0,1517016600.0,4843,55,5.615e-05,5.643145363408521e-05,false,"roi"],["ADA/BTC",-0.07877175,1517013900.0,1517287500.0,4845,4560,5.556e-05,5.144e-05,true,"force_sell"],["DASH/BTC",-0.0,1517020200.0,1517052300.0,4866,535,0.06900001,0.06934587471177944,false,"roi"],["ETH/BTC",-0.0,1517034300.0,1517036700.0,4913,40,0.09449985,0.09497353345864659,false,"roi"],["ZEC/BTC",-0.04815133,1517046000.0,1517287200.0,4952,4020,0.0410697,0.03928809,true,"force_sell"],["XMR/BTC",-0.0,1517053500.0,1517056200.0,4977,45,0.0285,0.02864285714285714,false,"roi"],["XMR/BTC",-0.0,1517056500.0,1517066700.0,4987,170,0.02866372,0.02880739779448621,false,"roi"],["ETH/BTC",-0.0,1517068200.0,1517071800.0,5026,60,0.095381,0.09585910025062655,false,"roi"],["DASH/BTC",-0.0,1517072700.0,1517075100.0,5041,40,0.06759092,0.06792972160401002,false,"roi"],["ETC/BTC",-0.0,1517096400.0,1517101500.0,5120,85,0.00258501,0.002597967443609022,false,"roi"],["DASH/BTC",-0.0,1517106300.0,1517127000.0,5153,345,0.06698502,0.0673207845112782,false,"roi"],["DASH/BTC",-0.0,1517135100.0,1517157000.0,5249,365,0.0677177,0.06805713709273183,false,"roi"],["XLM/BTC",0.0,1517171700.0,1517175300.0,5371,60,5.215e-05,5.2411403508771925e-05,false,"roi"],["ETC/BTC",0.00997506,1517176800.0,1517178600.0,5388,30,0.00273809,0.002779264285714285,false,"roi"],["ETC/BTC",0.00997506,1517184000.0,1517185800.0,5412,30,0.00274632,0.002787618045112782,false,"roi"],["LTC/BTC",0.0,1517192100.0,1517194800.0,5439,45,0.01622478,0.016306107218045113,false,"roi"],["DASH/BTC",-0.0,1517195100.0,1517197500.0,5449,40,0.069,0.06934586466165413,false,"roi"],["TRX/BTC",-0.0,1517203200.0,1517208900.0,5476,95,8.755e-05,8.798884711779448e-05,false,"roi"],["DASH/BTC",-0.0,1517209200.0,1517253900.0,5496,745,0.06825763,0.06859977350877192,false,"roi"],["DASH/BTC",-0.0,1517255100.0,1517257500.0,5649,40,0.06713892,0.06747545593984962,false,"roi"],["TRX/BTC",-0.0199116,1517268600.0,1517287500.0,5694,315,8.934e-05,8.8e-05,true,"force_sell"]]