From 184b5ca3fc5ad3d102be4846db52a5b1a469b1b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Aug 2018 19:59:56 +0200 Subject: [PATCH 01/23] cleanup root dir and create build_helpers --- .../install_ta-lib.sh | 0 build_helpers/publish_docker.sh | 53 ++++++++++++++++++ .../ta-lib-0.4.0-src.tar.gz | Bin 3 files changed, 53 insertions(+) rename install_ta-lib.sh => build_helpers/install_ta-lib.sh (100%) create mode 100755 build_helpers/publish_docker.sh rename ta-lib-0.4.0-src.tar.gz => build_helpers/ta-lib-0.4.0-src.tar.gz (100%) diff --git a/install_ta-lib.sh b/build_helpers/install_ta-lib.sh similarity index 100% rename from install_ta-lib.sh rename to build_helpers/install_ta-lib.sh diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh new file mode 100755 index 000000000..a398a8719 --- /dev/null +++ b/build_helpers/publish_docker.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# Tag with travis build +TAG=$TRAVIS_BUILD_NUMBER +# - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` +# Replace / with _ to create a valid tag +TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/") + + +# Pull last build to avoid rebuilding the whole image +docker pull ${REPO}:${TAG} + +docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} . +if [ $? -ne 0 ]; then + echo "failed building image" + return 1 +fi + +# Run backtest +docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro freqtrade:${TAG} --datadir freqtrade/tests/testdata backtesting + +if [ $? -ne 0 ]; then + echo "failed running backtest" + return 1 +fi + +# Tag image for upload +docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG +if [ $? -ne 0 ]; then + echo "failed tagging image" + return 1 +fi + +# Tag as latest for develop builds +if [ "${TRAVIS_BRANCH}" == "develop" ]; then + docker tag freqtrade:$TAG ${IMAGE_NAME}:latest +fi + +# Login +echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin + +if [ $? -ne 0 ]; then + echo "failed login" + return 1 +fi + +# Show all available images +docker images + +docker push ${IMAGE_NAME} +if [ $? -ne 0 ]; then + echo "failed pushing repo" + return 1 +fi diff --git a/ta-lib-0.4.0-src.tar.gz b/build_helpers/ta-lib-0.4.0-src.tar.gz similarity index 100% rename from ta-lib-0.4.0-src.tar.gz rename to build_helpers/ta-lib-0.4.0-src.tar.gz From 98738c482a7e52ecc9d7d55929e21b54ff2558c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Aug 2018 20:01:57 +0200 Subject: [PATCH 02/23] modify install-ta-lib script to support running in docker --- build_helpers/install_ta-lib.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/build_helpers/install_ta-lib.sh b/build_helpers/install_ta-lib.sh index d8ae2eeaa..4d4f37c17 100755 --- a/build_helpers/install_ta-lib.sh +++ b/build_helpers/install_ta-lib.sh @@ -1,7 +1,13 @@ if [ ! -f "ta-lib/CHANGELOG.TXT" ]; then tar zxvf ta-lib-0.4.0-src.tar.gz - cd ta-lib && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h && ./configure && make && sudo make install && cd .. + cd ta-lib \ + && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \ + && ./configure \ + && make \ + && which sudo && sudo make install || make install \ + && cd .. else echo "TA-lib already installed, skipping download and build." cd ta-lib && sudo make install && cd .. + fi From 907761f9941e73f931e3b8e97e8d1366539f90ca Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Aug 2018 20:03:20 +0200 Subject: [PATCH 03/23] Install ta-lib in Docker with script works for travis, works for Docker --- Dockerfile | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2506665ab..24cce0049 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,20 @@ FROM python:3.7.0-slim-stretch -# Install TA-lib -RUN apt-get update && apt-get -y install curl build-essential && apt-get clean -RUN curl -L http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz | \ - tar xzvf - && \ - cd ta-lib && \ - sed -i "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h && \ - ./configure && make && make install && \ - cd .. && rm -rf ta-lib -ENV LD_LIBRARY_PATH /usr/local/lib +RUN apt-get update \ + && apt-get -y install curl build-essential \ + && apt-get clean \ + && pip install --upgrade pip # Prepare environment RUN mkdir /freqtrade WORKDIR /freqtrade +# Install TA-lib +COPY build_helpers/* /tmp/ +RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib* + +ENV LD_LIBRARY_PATH /usr/local/lib + # Install dependencies COPY requirements.txt /freqtrade/ RUN pip install numpy --no-cache-dir \ From af7283017b331f6c5f396b7757f7c84674796a3e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Aug 2018 20:04:25 +0200 Subject: [PATCH 04/23] modify travis to build and push docker * name steps * only build for master / develop and this branch (for now) --- .travis.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 981eedcf8..f1192e80c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,15 @@ sudo: true os: - linux +dist: trusty language: python python: - 3.6 +services: + - docker +env: + global: + - IMAGE_NAME=freqtradeorg/freqtrade addons: apt: packages: @@ -11,24 +17,38 @@ addons: - libdw-dev - binutils-dev install: -- ./install_ta-lib.sh +- ./build_helpers/install_ta-lib.sh - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - pip install --upgrade flake8 coveralls pytest-random-order pytest-asyncio mypy - pip install -r requirements.txt - pip install -e . jobs: include: - - script: + - stage: tests + script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - coveralls + name: pytest - script: - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting + name: backtest - script: - cp config.json.example config.json - python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5 + name: hyperopt - script: flake8 freqtrade + name: flake8 - script: mypy freqtrade + name: mypy + + - stage: docker + if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) + script: + - build_helpers/publish_docker.sh + name: "Build and test and push docker image" + + notifications: slack: secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= From 0535660db722342d11e3a7a2da4d3aca6880025c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Aug 2018 09:44:21 +0200 Subject: [PATCH 05/23] build technical image --- Dockerfile.technical | 6 ++++++ build_helpers/publish_docker.sh | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 Dockerfile.technical diff --git a/Dockerfile.technical b/Dockerfile.technical new file mode 100644 index 000000000..5339eb232 --- /dev/null +++ b/Dockerfile.technical @@ -0,0 +1,6 @@ +FROM freqtradeorg/freqtrade:develop + +RUN apt-get update \ + && apt-get -y install git \ + && apt-get clean \ + && pip install git+https://github.com/berlinguyinca/technical diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index a398a8719..95aae0f4f 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -1,10 +1,8 @@ #!/bin/sh -# Tag with travis build -TAG=$TRAVIS_BUILD_NUMBER # - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` # Replace / with _ to create a valid tag TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/") - +TAG_TECH="${TAG}_technical" # Pull last build to avoid rebuilding the whole image docker pull ${REPO}:${TAG} @@ -23,6 +21,10 @@ if [ $? -ne 0 ]; then return 1 fi +# build technical image +sed -i Dockerfile.technical -e "s/FROM freqtradeorg\/freqtrade:develop/FROM freqtradeorg\/freqtrade:${TAG}/" +docker build --cache-from freqtrade:${TAG} -t ${IMAGE_NAME}:${TAG_TECH} -f Dockerfile.technical . + # Tag image for upload docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG if [ $? -ne 0 ]; then @@ -31,7 +33,7 @@ if [ $? -ne 0 ]; then fi # Tag as latest for develop builds -if [ "${TRAVIS_BRANCH}" == "develop" ]; then +if [ "${TRAVIS_BRANCH}" = "develop" ]; then docker tag freqtrade:$TAG ${IMAGE_NAME}:latest fi From 7301d76cff46c23a0e9b7c7aee04d83ca124bb27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 21 Oct 2018 13:20:51 +0200 Subject: [PATCH 06/23] Remove autobuild for technical as it's not versioned as it's not versioned and installed from github, we cannot guarantee which version is in the image. --- build_helpers/publish_docker.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index 95aae0f4f..5e0809fcf 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -21,10 +21,6 @@ if [ $? -ne 0 ]; then return 1 fi -# build technical image -sed -i Dockerfile.technical -e "s/FROM freqtradeorg\/freqtrade:develop/FROM freqtradeorg\/freqtrade:${TAG}/" -docker build --cache-from freqtrade:${TAG} -t ${IMAGE_NAME}:${TAG_TECH} -f Dockerfile.technical . - # Tag image for upload docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG if [ $? -ne 0 ]; then From 39efda19f4df040576ce55f33e60699a5e1dbe73 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 21 Oct 2018 13:33:23 +0200 Subject: [PATCH 07/23] Add freqtradeorg/freqtrade docker images to the documentation --- docs/installation.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 1000600e6..15cfb1467 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -109,7 +109,25 @@ Dry-Run touch tradesv3.dryrun.sqlite ``` -### 2. Build the Docker image +### 2. Download or build the docker image + +Either use the prebuilt image from docker hub - or build the image yourself if you would like more control on which version is used. + +Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/). + +#### 2.1. Download the docker image + +Pull the image from docker hub and (optionally) change the name of the image + +```bash +docker pull freqtradeorg/freqtrade:develop +# Optionally tag the repository so the run-commands remain shorter +docker tag freqtradeorg/freqtrade:develop freqtrade +``` + +To update the image, simply run the above commands again and restart your running container. + +#### 2.2. Build the Docker image ```bash cd freqtrade From 530d521d78bc9305face56e2fb8308c6b58f328c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 21 Oct 2018 14:00:01 +0200 Subject: [PATCH 08/23] Rebuild complete image on "cron" events --- build_helpers/publish_docker.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/build_helpers/publish_docker.sh b/build_helpers/publish_docker.sh index 5e0809fcf..c2b40ba81 100755 --- a/build_helpers/publish_docker.sh +++ b/build_helpers/publish_docker.sh @@ -2,12 +2,18 @@ # - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` # Replace / with _ to create a valid tag TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/") -TAG_TECH="${TAG}_technical" -# Pull last build to avoid rebuilding the whole image -docker pull ${REPO}:${TAG} -docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} . +if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then + echo "event ${TRAVIS_EVENT_TYPE}: full rebuild - skipping cache" + docker build -t freqtrade:${TAG} . +else + echo "event ${TRAVIS_EVENT_TYPE}: building with cache" + # Pull last build to avoid rebuilding the whole image + docker pull ${REPO}:${TAG} + docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} . +fi + if [ $? -ne 0 ]; then echo "failed building image" return 1 @@ -29,7 +35,7 @@ if [ $? -ne 0 ]; then fi # Tag as latest for develop builds -if [ "${TRAVIS_BRANCH}" = "develop" ]; then +if [ "${TRAVIS_BRANCH}" = "develop" ]; then docker tag freqtrade:$TAG ${IMAGE_NAME}:latest fi From fd7184718baa99cd328c4f0dacac98c7f79cd3c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Nov 2018 14:31:46 +0100 Subject: [PATCH 09/23] replace lambda with Magicmock in test --- freqtrade/tests/test_freqtradebot.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a36ae2cd8..eb5336c61 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -292,7 +292,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, freqtrade = FreqtradeBot(edge_conf) freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() trade.update(limit_buy_order) @@ -332,7 +332,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets, freqtrade = FreqtradeBot(edge_conf) freqtrade.active_pair_whitelist = ['NEO/BTC'] patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() trade.update(limit_buy_order) @@ -1010,7 +1010,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade, value=(True, True)) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() @@ -1066,7 +1066,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade, value=(True, False)) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True + freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.create_trade() @@ -1099,7 +1099,7 @@ def test_handle_trade_experimental( freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() @@ -1580,7 +1580,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() @@ -1612,7 +1612,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() @@ -1674,7 +1674,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() @@ -1704,7 +1704,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True + freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.create_trade() @@ -1736,7 +1736,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, default_conf['trailing_stop'] = True freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() @@ -1771,7 +1771,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets default_conf['trailing_stop_positive'] = 0.01 freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() @@ -1831,7 +1831,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, default_conf['trailing_stop_positive_offset'] = 0.011 freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False + freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.create_trade() trade = Trade.query.first() @@ -1890,7 +1890,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, } freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) - freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True + freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) freqtrade.create_trade() From 317eba2139f7fcc352b97c1de333a76ef18d27eb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Nov 2018 14:36:02 +0100 Subject: [PATCH 10/23] Remove dual instanciation of pairinfo named tuple --- freqtrade/tests/conftest.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 63655126c..f7fe697b8 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -4,7 +4,6 @@ import logging from datetime import datetime from functools import reduce from typing import Dict, Optional -from collections import namedtuple from unittest.mock import MagicMock, PropertyMock import arrow @@ -13,7 +12,7 @@ from telegram import Chat, Message, Update from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe from freqtrade.exchange import Exchange -from freqtrade.edge import Edge +from freqtrade.edge import Edge, PairInfo from freqtrade.freqtradebot import FreqtradeBot logging.getLogger('').setLevel(logging.INFO) @@ -56,13 +55,11 @@ def patch_edge(mocker) -> None: # "LTC/BTC", # "XRP/BTC", # "NEO/BTC" - pair_info = namedtuple( - 'pair_info', - 'stoploss, winrate, risk_reward_ratio, required_risk_reward, expectancy') + mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( return_value={ - 'NEO/BTC': pair_info(-0.20, 0.66, 3.71, 0.50, 1.71), - 'LTC/BTC': pair_info(-0.21, 0.66, 3.71, 0.50, 1.71), + 'NEO/BTC': PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25), + 'LTC/BTC': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20), } )) mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20)) From 745a5177958ad16e220dd0ba2d8dc982fddfc016 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Nov 2018 14:40:21 +0100 Subject: [PATCH 11/23] Fix comment pointing to wrong column --- freqtrade/exchange/exchange_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange_helpers.py b/freqtrade/exchange/exchange_helpers.py index 8f4b03daf..84e68d4bb 100644 --- a/freqtrade/exchange/exchange_helpers.py +++ b/freqtrade/exchange/exchange_helpers.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) def parse_ticker_dataframe(ticker: list) -> DataFrame: """ Analyses the trend for the given ticker history - :param ticker: See exchange.get_candle_history + :param ticker: ticker list, as returned by exchange.async_get_candle_history :return: DataFrame """ cols = ['date', 'open', 'high', 'low', 'close', 'volume'] From 8a4361199266c3f558e6eb6b98947155ace77d57 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Nov 2018 14:48:15 +0100 Subject: [PATCH 12/23] Remove get_candle_history (it's now async) convert sort-test to async --- freqtrade/exchange/__init__.py | 45 ------------- freqtrade/tests/exchange/test_exchange.py | 78 ++++------------------- 2 files changed, 12 insertions(+), 111 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index ae07e36e9..0f8d8895c 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -500,51 +500,6 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(f'Could not fetch ticker data. Msg: {e}') - @retrier - def get_candle_history(self, pair: str, tick_interval: str, - since_ms: Optional[int] = None) -> List[Dict]: - try: - # last item should be in the time interval [now - tick_interval, now] - till_time_ms = arrow.utcnow().shift( - minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval] - ).timestamp * 1000 - # it looks as if some exchanges return cached data - # and they update it one in several minute, so 10 mins interval - # is necessary to skeep downloading of an empty array when all - # chached data was already downloaded - till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) - - data: List[Dict[Any, Any]] = [] - while not since_ms or since_ms < till_time_ms: - data_part = self._api.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) - - # Because some exchange sort Tickers ASC and other DESC. - # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) - # when GDAX returns a list of tickers DESC (newest first, oldest last) - data_part = sorted(data_part, key=lambda x: x[0]) - - if not data_part: - break - - logger.debug('Downloaded data for %s time range [%s, %s]', - pair, - arrow.get(data_part[0][0] / 1000).format(), - arrow.get(data_part[-1][0] / 1000).format()) - - data.extend(data_part) - since_ms = data[-1][0] + 1 - - return data - except ccxt.NotSupported as e: - raise OperationalException( - f'Exchange {self._api.name} does not support fetching historical candlestick data.' - f'Message: {e}') - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') - except ccxt.BaseError as e: - raise OperationalException(f'Could not fetch ticker data. Msg: {e}') - @retrier def cancel_order(self, order_id: str, pair: str) -> None: if self._conf['dry_run']: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 207f14efe..7fadb4cc1 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -875,64 +875,8 @@ def make_fetch_ohlcv_mock(data): return fetch_ohlcv_mock -def test_get_candle_history(default_conf, mocker): - api_mock = MagicMock() - tick = [ - [ - 1511686200000, # unix timestamp ms - 1, # open - 2, # high - 3, # low - 4, # close - 5, # volume (in quote currency) - ] - ] - type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) - api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - - # retrieve original ticker - ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) - assert ticks[0][0] == 1511686200000 - assert ticks[0][1] == 1 - assert ticks[0][2] == 2 - assert ticks[0][3] == 3 - assert ticks[0][4] == 4 - assert ticks[0][5] == 5 - - # change ticker and ensure tick changes - new_tick = [ - [ - 1511686210000, # unix timestamp ms - 6, # open - 7, # high - 8, # low - 9, # close - 10, # volume (in quote currency) - ] - ] - api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick)) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - - ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) - assert ticks[0][0] == 1511686210000 - assert ticks[0][1] == 6 - assert ticks[0][2] == 7 - assert ticks[0][3] == 8 - assert ticks[0][4] == 9 - assert ticks[0][5] == 10 - - ccxt_exceptionhandlers(mocker, default_conf, api_mock, - "get_candle_history", "fetch_ohlcv", - pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) - - with pytest.raises(OperationalException, match=r'Exchange .* does not support.*'): - api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported) - exchange = get_patched_exchange(mocker, default_conf, api_mock) - exchange.get_candle_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) - - -def test_get_candle_history_sort(default_conf, mocker): +@pytest.mark.asyncio +async def test___async_get_candle_history_sort(default_conf, mocker): api_mock = MagicMock() # GDAX use-case (real data from GDAX) @@ -949,13 +893,14 @@ def test_get_candle_history_sort(default_conf, mocker): [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] ] - type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange = get_patched_exchange(mocker, default_conf) + exchange._api_async.fetch_ohlcv = get_mock_coro(tick) # Test the ticker history sort - ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) + res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) + assert res[0] == 'ETH/BTC' + ticks = res[1] assert ticks[0][0] == 1527830400000 assert ticks[0][1] == 0.07649 assert ticks[0][2] == 0.07651 @@ -984,11 +929,12 @@ def test_get_candle_history_sort(default_conf, mocker): [1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244], [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783] ] - type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) - api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) - exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange._api_async.fetch_ohlcv = get_mock_coro(tick) # Test the ticker history sort - ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) + res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) + assert res[0] == 'ETH/BTC' + ticks = res[1] + assert ticks[0][0] == 1527827700000 assert ticks[0][1] == 0.07659999 assert ticks[0][2] == 0.0766 From ebaf58b0fe465be049d46ef891c57746946ed4a0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Nov 2018 15:00:50 +0100 Subject: [PATCH 13/23] Only sort data if necessary --- freqtrade/exchange/__init__.py | 4 +++- freqtrade/tests/exchange/test_exchange.py | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 0f8d8895c..350c730a4 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -478,7 +478,9 @@ class Exchange(object): # Because some exchange sort Tickers ASC and other DESC. # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) # when GDAX returns a list of tickers DESC (newest first, oldest last) - data = sorted(data, key=lambda x: x[0]) + # Only sort if necessary to save computing time + if data and data[0][0] > data[-1][0]: + data = sorted(data, key=lambda x: x[0]) # keeping last candle time as last refreshed time of the pair if data: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 7fadb4cc1..dbb8d4ec2 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -877,7 +877,8 @@ def make_fetch_ohlcv_mock(data): @pytest.mark.asyncio async def test___async_get_candle_history_sort(default_conf, mocker): - api_mock = MagicMock() + def sort_data(data, key): + return sorted(data, key=key) # GDAX use-case (real data from GDAX) # This ticker history is ordered DESC (newest first, oldest last) @@ -893,14 +894,15 @@ async def test___async_get_candle_history_sort(default_conf, mocker): [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] ] - api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) exchange = get_patched_exchange(mocker, default_conf) exchange._api_async.fetch_ohlcv = get_mock_coro(tick) - + sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the ticker history sort res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) assert res[0] == 'ETH/BTC' ticks = res[1] + + assert sort_mock.call_count == 1 assert ticks[0][0] == 1527830400000 assert ticks[0][1] == 0.07649 assert ticks[0][2] == 0.07651 @@ -930,11 +932,14 @@ async def test___async_get_candle_history_sort(default_conf, mocker): [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783] ] exchange._api_async.fetch_ohlcv = get_mock_coro(tick) + # Reset sort mock + sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) # Test the ticker history sort res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) assert res[0] == 'ETH/BTC' ticks = res[1] - + # Sorted not called again - data is already in order + assert sort_mock.call_count == 0 assert ticks[0][0] == 1527827700000 assert ticks[0][1] == 0.07659999 assert ticks[0][2] == 0.0766 From 16eec078d78fcdc8e5948d45998dac2536e9a902 Mon Sep 17 00:00:00 2001 From: Pan Long Date: Mon, 26 Nov 2018 09:18:29 +0800 Subject: [PATCH 14/23] Use dot to access attribute in NamedTuple This should fix the crash in #1359 --- freqtrade/wallets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index b8b37907d..bf6f8b027 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -35,8 +35,8 @@ class Wallets(object): return 999.9 balance = self.wallets.get(currency) - if balance and balance['free']: - return balance['free'] + if balance and balance.free: + return balance.free else: return 0 From ad8592f3168c6d4fa908e3d9fae615f9490f9ae8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Nov 2018 06:22:53 +0100 Subject: [PATCH 15/23] Test live mode of get_free --- freqtrade/tests/test_wallets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/tests/test_wallets.py b/freqtrade/tests/test_wallets.py index e6a17ecbf..88366a869 100644 --- a/freqtrade/tests/test_wallets.py +++ b/freqtrade/tests/test_wallets.py @@ -30,6 +30,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert freqtrade.wallets.wallets['GAS'].free == 0.260739 assert freqtrade.wallets.wallets['GAS'].used == 0.0 assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert freqtrade.wallets.get_free('BNT') == 1.0 mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -56,6 +57,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): assert freqtrade.wallets.wallets['GAS'].free == 0.270739 assert freqtrade.wallets.wallets['GAS'].used == 0.1 assert freqtrade.wallets.wallets['GAS'].total == 0.260439 + assert freqtrade.wallets.get_free('GAS') == 0.270739 def test_sync_wallet_missing_data(mocker, default_conf): @@ -84,3 +86,4 @@ def test_sync_wallet_missing_data(mocker, default_conf): assert freqtrade.wallets.wallets['GAS'].free == 0.260739 assert freqtrade.wallets.wallets['GAS'].used is None assert freqtrade.wallets.wallets['GAS'].total == 0.260739 + assert freqtrade.wallets.get_free('GAS') == 0.260739 From d3712c6e4035552e62490e31d8e422944a36abc6 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 26 Nov 2018 13:34:05 +0100 Subject: [PATCH 16/23] Update ccxt from 1.17.536 to 1.17.539 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fa550195d..6f969be06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.536 +ccxt==1.17.539 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 7832fe7074a403fa7417f7cc38c5156ed1fa64a4 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 27 Nov 2018 13:34:08 +0100 Subject: [PATCH 17/23] Update ccxt from 1.17.539 to 1.17.543 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6f969be06..12c6f5cda 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.539 +ccxt==1.17.543 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 50a384130fe7830cd5c3edc3ab2ecda5dd030f0a Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Wed, 28 Nov 2018 13:34:07 +0100 Subject: [PATCH 18/23] Update ccxt from 1.17.543 to 1.17.545 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12c6f5cda..15c7d1858 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.543 +ccxt==1.17.545 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 38592c6fa60f0892d0d32e450b1036830bab2c3c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Nov 2018 07:07:47 +0100 Subject: [PATCH 19/23] Add binance config sample, improve invalid pair message --- config_binance.json.example | 83 ++++++++++++++++++++++++++++++++++ freqtrade/exchange/__init__.py | 3 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 config_binance.json.example diff --git a/config_binance.json.example b/config_binance.json.example new file mode 100644 index 000000000..da000efa7 --- /dev/null +++ b/config_binance.json.example @@ -0,0 +1,83 @@ +{ + "max_open_trades": 3, + "stake_currency": "BTC", + "stake_amount": 0.05, + "fiat_display_currency": "USD", + "ticker_interval" : "5m", + "dry_run": true, + "trailing_stop": false, + "unfilledtimeout": { + "buy": 10, + "sell": 30 + }, + "bid_strategy": { + "ask_last_balance": 0.0, + "use_order_book": false, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 + }, + "exchange": { + "name": "binance", + "key": "your_exchange_key", + "secret": "your_exchange_secret", + "ccxt_config": {"enableRateLimit": true}, + "ccxt_async_config": { + "enableRateLimit": false + }, + "pair_whitelist": [ + "AST/BTC", + "ETC/BTC", + "ETH/BTC", + "EOS/BTC", + "IOTA/BTC", + "LTC/BTC", + "MTH/BTC", + "NCASH/BTC", + "TNT/BTC", + "XMR/BTC", + "XLM/BTC", + "XRP/BTC" + ], + "pair_blacklist": [ + "DOGE/BTC" + ] + }, + "experimental": { + "use_sell_signal": false, + "sell_profit_only": false, + "ignore_roi_if_buy_signal": false + }, + "edge": { + "enabled": false, + "process_throttle_secs": 3600, + "calculate_since_number_of_days": 7, + "total_capital_in_stake_currency": 0.5, + "allowed_risk": 0.01, + "stoploss_range_min": -0.01, + "stoploss_range_max": -0.1, + "stoploss_range_step": -0.01, + "minimum_winrate": 0.60, + "minimum_expectancy": 0.20, + "min_trade_number": 10, + "max_trade_duration_minute": 1440, + "remove_pumps": false + }, + "telegram": { + "enabled": false, + "token": "your_telegram_token", + "chat_id": "your_telegram_chat_id" + }, + "initial_state": "running", + "forcebuy_enable": false, + "internals": { + "process_throttle_secs": 5 + } +} diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 350c730a4..4573e461c 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -207,7 +207,8 @@ class Exchange(object): f'Pair {pair} not compatible with stake_currency: {stake_cur}') if self.markets and pair not in self.markets: raise OperationalException( - f'Pair {pair} is not available at {self.name}') + f'Pair {pair} is not available at {self.name}' + f'Please remove {pair} from your whitelist.') def validate_timeframes(self, timeframe: List[str]) -> None: """ From cb9104fd8a1fb4a9862ae042baae9e5b69f608d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Nov 2018 07:36:37 +0100 Subject: [PATCH 20/23] Add BNB as blacklist to align to documentation --- config_binance.json.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_binance.json.example b/config_binance.json.example index da000efa7..7773a8c39 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -47,7 +47,7 @@ "XRP/BTC" ], "pair_blacklist": [ - "DOGE/BTC" + "BNB/BTC" ] }, "experimental": { From bc2f6d3b71722f9703a5423488759a44669fba79 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Thu, 29 Nov 2018 13:34:07 +0100 Subject: [PATCH 21/23] Update ccxt from 1.17.545 to 1.17.556 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 15c7d1858..e2a1332ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.545 +ccxt==1.17.556 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 3d37c5d76725e0b56aaf0b2591781e88e408cba8 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 29 Nov 2018 18:31:08 +0100 Subject: [PATCH 22/23] edge non existing stoploss fixed. solves #1370 --- freqtrade/edge/__init__.py | 8 +++++++- freqtrade/tests/edge/test_edge.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 009b80664..2b1849d19 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -157,7 +157,13 @@ class Edge(): return position_size def stoploss(self, pair: str) -> float: - return self._cached_pairs[pair].stoploss + if pair in self._cached_pairs: + return self._cached_pairs[pair].stoploss + else: + logger.warning('tried to access stoploss of a non-existing pair, ' + 'strategy stoploss is returned instead.') + return self.strategy.stoploss + def adjust(self, pairs) -> list: """ diff --git a/freqtrade/tests/edge/test_edge.py b/freqtrade/tests/edge/test_edge.py index fac055c17..d2b1f8030 100644 --- a/freqtrade/tests/edge/test_edge.py +++ b/freqtrade/tests/edge/test_edge.py @@ -152,6 +152,17 @@ def test_stoploss(mocker, default_conf): assert edge.stoploss('E/F') == -0.01 +def test_nonexisting_stoploss(mocker, default_conf): + freqtrade = get_patched_freqtradebot(mocker, default_conf) + edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy) + mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( + return_value={ + 'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + } + )) + + assert edge.stoploss('N/O') == -0.1 + def _validate_ohlc(buy_ohlc_sell_matrice): for index, ohlc in enumerate(buy_ohlc_sell_matrice): # if not high < open < low or not high < close < low From 74ca34f2dee9982235478c19e7a86ce74fa89c37 Mon Sep 17 00:00:00 2001 From: misagh Date: Thu, 29 Nov 2018 18:45:37 +0100 Subject: [PATCH 23/23] flaking8 --- freqtrade/edge/__init__.py | 1 - freqtrade/tests/edge/test_edge.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/edge/__init__.py b/freqtrade/edge/__init__.py index 2b1849d19..4cb0dbc31 100644 --- a/freqtrade/edge/__init__.py +++ b/freqtrade/edge/__init__.py @@ -164,7 +164,6 @@ class Edge(): 'strategy stoploss is returned instead.') return self.strategy.stoploss - def adjust(self, pairs) -> list: """ Filters out and sorts "pairs" according to Edge calculated pairs diff --git a/freqtrade/tests/edge/test_edge.py b/freqtrade/tests/edge/test_edge.py index d2b1f8030..50c4ade3d 100644 --- a/freqtrade/tests/edge/test_edge.py +++ b/freqtrade/tests/edge/test_edge.py @@ -163,6 +163,7 @@ def test_nonexisting_stoploss(mocker, default_conf): assert edge.stoploss('N/O') == -0.1 + def _validate_ohlc(buy_ohlc_sell_matrice): for index, ohlc in enumerate(buy_ohlc_sell_matrice): # if not high < open < low or not high < close < low