Merge branch 'develop' into feat/new_args_system
This commit is contained in:
@@ -103,11 +103,6 @@
|
||||
"max_trade_duration_minute": 1440,
|
||||
"remove_pumps": false
|
||||
},
|
||||
"experimental": {
|
||||
"use_sell_signal": false,
|
||||
"sell_profit_only": false,
|
||||
"ignore_roi_if_buy_signal": false
|
||||
},
|
||||
"telegram": {
|
||||
// We can now comment out some settings
|
||||
// "enabled": true,
|
||||
|
@@ -9,8 +9,8 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
import numpy as np
|
||||
import pytest
|
||||
from telegram import Chat, Message, Update
|
||||
|
||||
from freqtrade import constants, persistence
|
||||
@@ -19,10 +19,10 @@ from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.resolvers import ExchangeResolver
|
||||
from freqtrade.worker import Worker
|
||||
|
||||
|
||||
logging.getLogger('').setLevel(logging.INFO)
|
||||
|
||||
|
||||
@@ -609,6 +609,14 @@ def limit_buy_order_old_partial():
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def limit_buy_order_old_partial_canceled(limit_buy_order_old_partial):
|
||||
res = deepcopy(limit_buy_order_old_partial)
|
||||
res['status'] = 'canceled'
|
||||
res['fee'] = {'cost': 0.0001, 'currency': 'ETH'}
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def limit_sell_order():
|
||||
return {
|
||||
@@ -897,12 +905,6 @@ def result(testdatadir):
|
||||
return parse_ticker_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)
|
||||
|
||||
# FIX:
|
||||
# Create an fixture/function
|
||||
# that inserts a trade of some type and open-status
|
||||
# return the open-order-id
|
||||
# See tests in rpc/main that could use this
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_for_order():
|
||||
@@ -929,6 +931,110 @@ def trades_for_order():
|
||||
'fee': {'cost': 0.008, 'currency': 'LTC'}}]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_history():
|
||||
return [{'info': {'a': 126181329,
|
||||
'p': '0.01962700',
|
||||
'q': '0.04000000',
|
||||
'f': 138604155,
|
||||
'l': 138604155,
|
||||
'T': 1565798399463,
|
||||
'm': False,
|
||||
'M': True},
|
||||
'timestamp': 1565798399463,
|
||||
'datetime': '2019-08-14T15:59:59.463Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
'id': '126181329',
|
||||
'order': None,
|
||||
'type': None,
|
||||
'takerOrMaker': None,
|
||||
'side': 'buy',
|
||||
'price': 0.019627,
|
||||
'amount': 0.04,
|
||||
'cost': 0.00078508,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181330,
|
||||
'p': '0.01962700',
|
||||
'q': '0.24400000',
|
||||
'f': 138604156,
|
||||
'l': 138604156,
|
||||
'T': 1565798399629,
|
||||
'm': False,
|
||||
'M': True},
|
||||
'timestamp': 1565798399629,
|
||||
'datetime': '2019-08-14T15:59:59.629Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
'id': '126181330',
|
||||
'order': None,
|
||||
'type': None,
|
||||
'takerOrMaker': None,
|
||||
'side': 'buy',
|
||||
'price': 0.019627,
|
||||
'amount': 0.244,
|
||||
'cost': 0.004788987999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181331,
|
||||
'p': '0.01962600',
|
||||
'q': '0.01100000',
|
||||
'f': 138604157,
|
||||
'l': 138604157,
|
||||
'T': 1565798399752,
|
||||
'm': True,
|
||||
'M': True},
|
||||
'timestamp': 1565798399752,
|
||||
'datetime': '2019-08-14T15:59:59.752Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
'id': '126181331',
|
||||
'order': None,
|
||||
'type': None,
|
||||
'takerOrMaker': None,
|
||||
'side': 'sell',
|
||||
'price': 0.019626,
|
||||
'amount': 0.011,
|
||||
'cost': 0.00021588599999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181332,
|
||||
'p': '0.01962600',
|
||||
'q': '0.01100000',
|
||||
'f': 138604158,
|
||||
'l': 138604158,
|
||||
'T': 1565798399862,
|
||||
'm': True,
|
||||
'M': True},
|
||||
'timestamp': 1565798399862,
|
||||
'datetime': '2019-08-14T15:59:59.862Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
'id': '126181332',
|
||||
'order': None,
|
||||
'type': None,
|
||||
'takerOrMaker': None,
|
||||
'side': 'sell',
|
||||
'price': 0.019626,
|
||||
'amount': 0.011,
|
||||
'cost': 0.00021588599999999999,
|
||||
'fee': None},
|
||||
{'info': {'a': 126181333,
|
||||
'p': '0.01952600',
|
||||
'q': '0.01200000',
|
||||
'f': 138604158,
|
||||
'l': 138604158,
|
||||
'T': 1565798399872,
|
||||
'm': True,
|
||||
'M': True},
|
||||
'timestamp': 1565798399872,
|
||||
'datetime': '2019-08-14T15:59:59.872Z',
|
||||
'symbol': 'ETH/BTC',
|
||||
'id': '126181333',
|
||||
'order': None,
|
||||
'type': None,
|
||||
'takerOrMaker': None,
|
||||
'side': 'sell',
|
||||
'price': 0.019626,
|
||||
'amount': 0.011,
|
||||
'cost': 0.00021588599999999999,
|
||||
'fee': None}]
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def trades_for_order2():
|
||||
return [{'info': {'id': 34567,
|
||||
@@ -1076,3 +1182,19 @@ def import_fails() -> None:
|
||||
|
||||
# restore previous importfunction
|
||||
builtins.__import__ = realimport
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def open_trade():
|
||||
return Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
@@ -53,12 +53,12 @@ def test_load_trades_db(default_conf, fee, mocker):
|
||||
|
||||
def test_extract_trades_of_period(testdatadir):
|
||||
pair = "UNITTEST/BTC"
|
||||
timerange = TimeRange(None, 'line', 0, -1000)
|
||||
# 2018-11-14 06:07:00
|
||||
timerange = TimeRange('date', None, 1510639620, 0)
|
||||
|
||||
data = load_pair_history(pair=pair, ticker_interval='1m',
|
||||
datadir=testdatadir, timerange=timerange)
|
||||
|
||||
# timerange = 2017-11-14 06:07 - 2017-11-14 22:58:00
|
||||
trades = DataFrame(
|
||||
{'pair': [pair, pair, pair, pair],
|
||||
'profit_percent': [0.0, 0.1, -0.2, -0.5],
|
||||
@@ -108,7 +108,7 @@ def test_load_trades(default_conf, mocker):
|
||||
|
||||
|
||||
def test_combine_tickers_with_mean(testdatadir):
|
||||
pairs = ["ETH/BTC", "XLM/BTC"]
|
||||
pairs = ["ETH/BTC", "ADA/BTC"]
|
||||
tickers = load_data(datadir=testdatadir,
|
||||
pairs=pairs,
|
||||
ticker_interval='5m'
|
||||
@@ -116,7 +116,7 @@ def test_combine_tickers_with_mean(testdatadir):
|
||||
df = combine_tickers_with_mean(tickers)
|
||||
assert isinstance(df, DataFrame)
|
||||
assert "ETH/BTC" in df.columns
|
||||
assert "XLM/BTC" in df.columns
|
||||
assert "ADA/BTC" in df.columns
|
||||
assert "mean" in df.columns
|
||||
|
||||
|
||||
|
@@ -120,3 +120,35 @@ def test_refresh(mocker, default_conf, ticker_history):
|
||||
assert len(refresh_mock.call_args[0]) == 1
|
||||
assert len(refresh_mock.call_args[0][0]) == len(pairs) + len(pairs_non_trad)
|
||||
assert refresh_mock.call_args[0][0] == pairs + pairs_non_trad
|
||||
|
||||
|
||||
def test_orderbook(mocker, default_conf, order_book_l2):
|
||||
api_mock = MagicMock()
|
||||
api_mock.fetch_l2_order_book = order_book_l2
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock)
|
||||
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
res = dp.orderbook('ETH/BTC', 5)
|
||||
assert order_book_l2.call_count == 1
|
||||
assert order_book_l2.call_args_list[0][0][0] == 'ETH/BTC'
|
||||
assert order_book_l2.call_args_list[0][0][1] == 5
|
||||
|
||||
assert type(res) is dict
|
||||
assert 'bids' in res
|
||||
assert 'asks' in res
|
||||
|
||||
|
||||
def test_market(mocker, default_conf, markets):
|
||||
api_mock = MagicMock()
|
||||
api_mock.markets = markets
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock=api_mock)
|
||||
|
||||
dp = DataProvider(default_conf, exchange)
|
||||
res = dp.market('ETH/BTC')
|
||||
|
||||
assert type(res) is dict
|
||||
assert 'symbol' in res
|
||||
assert res['symbol'] == 'ETH/BTC'
|
||||
|
||||
res = dp.market('UNITTEST/BTC')
|
||||
assert res is None
|
||||
|
@@ -1,7 +1,6 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
@@ -14,49 +13,54 @@ from pandas import DataFrame
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.history import (download_pair_history,
|
||||
load_cached_data_for_updating,
|
||||
load_tickerdata_file,
|
||||
from freqtrade.data.history import (_load_cached_data_for_updating,
|
||||
convert_trades_to_ohlcv,
|
||||
download_pair_history,
|
||||
download_trades_history,
|
||||
load_tickerdata_file, pair_data_filename,
|
||||
pair_trades_filename,
|
||||
refresh_backtest_ohlcv_data,
|
||||
refresh_backtest_trades_data,
|
||||
trim_tickerlist)
|
||||
from freqtrade.exchange import timeframe_to_minutes
|
||||
from freqtrade.misc import file_dump_json
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||
from tests.conftest import get_patched_exchange, log_has, log_has_re, patch_exchange
|
||||
from tests.conftest import (get_patched_exchange, log_has, log_has_re,
|
||||
patch_exchange)
|
||||
|
||||
# Change this if modifying UNITTEST/BTC testdatafile
|
||||
_BTC_UNITTEST_LENGTH = 13681
|
||||
|
||||
|
||||
def _backup_file(file: str, copy_file: bool = False) -> None:
|
||||
def _backup_file(file: Path, copy_file: bool = False) -> None:
|
||||
"""
|
||||
Backup existing file to avoid deleting the user file
|
||||
:param file: complete path to the file
|
||||
:param touch_file: create an empty file in replacement
|
||||
:return: None
|
||||
"""
|
||||
file_swp = file + '.swp'
|
||||
if os.path.isfile(file):
|
||||
os.rename(file, file_swp)
|
||||
file_swp = str(file) + '.swp'
|
||||
if file.is_file():
|
||||
file.rename(file_swp)
|
||||
|
||||
if copy_file:
|
||||
copyfile(file_swp, file)
|
||||
|
||||
|
||||
def _clean_test_file(file: str) -> None:
|
||||
def _clean_test_file(file: Path) -> None:
|
||||
"""
|
||||
Backup existing file to avoid deleting the user file
|
||||
:param file: complete path to the file
|
||||
:return: None
|
||||
"""
|
||||
file_swp = file + '.swp'
|
||||
file_swp = Path(str(file) + '.swp')
|
||||
# 1. Delete file from the test
|
||||
if os.path.isfile(file):
|
||||
os.remove(file)
|
||||
if file.is_file():
|
||||
file.unlink()
|
||||
|
||||
# 2. Rollback to the initial file
|
||||
if os.path.isfile(file_swp):
|
||||
os.rename(file_swp, file)
|
||||
if file_swp.is_file():
|
||||
file_swp.rename(file)
|
||||
|
||||
|
||||
def test_load_data_30min_ticker(mocker, caplog, default_conf, testdatadir) -> None:
|
||||
@@ -80,10 +84,10 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf, testdatadir) -> Non
|
||||
|
||||
def test_load_data_1min_ticker(ticker_history, mocker, caplog, testdatadir) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history)
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
|
||||
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
||||
_backup_file(file, copy_file=True)
|
||||
history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||
assert os.path.isfile(file) is True
|
||||
assert file.is_file()
|
||||
assert not log_has(
|
||||
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
|
||||
'and store in None.', caplog
|
||||
@@ -98,14 +102,14 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog,
|
||||
"""
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
file = testdatadir / 'MEME_BTC-1m.json'
|
||||
|
||||
_backup_file(file)
|
||||
# do not download a new pair if refresh_pairs isn't set
|
||||
history.load_pair_history(datadir=testdatadir,
|
||||
ticker_interval='1m',
|
||||
pair='MEME/BTC')
|
||||
assert os.path.isfile(file) is False
|
||||
assert not file.is_file()
|
||||
assert log_has(
|
||||
'No history data for pair: "MEME/BTC", interval: 1m. '
|
||||
'Use `freqtrade download-data` to download the data', caplog
|
||||
@@ -117,7 +121,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog,
|
||||
refresh_pairs=True,
|
||||
exchange=exchange,
|
||||
pair='MEME/BTC')
|
||||
assert os.path.isfile(file) is True
|
||||
assert file.is_file()
|
||||
assert log_has_re(
|
||||
'Download history data for pair: "MEME/BTC", interval: 1m '
|
||||
'and store in .*', caplog
|
||||
@@ -135,6 +139,18 @@ def test_testdata_path(testdatadir) -> None:
|
||||
assert str(Path('tests') / 'testdata') in str(testdatadir)
|
||||
|
||||
|
||||
def test_pair_data_filename():
|
||||
fn = pair_data_filename(Path('freqtrade/hello/world'), 'ETH/BTC', '5m')
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path('freqtrade/hello/world/ETH_BTC-5m.json')
|
||||
|
||||
|
||||
def test_pair_trades_filename():
|
||||
fn = pair_trades_filename(Path('freqtrade/hello/world'), 'ETH/BTC')
|
||||
assert isinstance(fn, Path)
|
||||
assert fn == Path('freqtrade/hello/world/ETH_BTC-trades.json.gz')
|
||||
|
||||
|
||||
def test_load_cached_data_for_updating(mocker) -> None:
|
||||
datadir = Path(__file__).parent.parent.joinpath('testdata')
|
||||
|
||||
@@ -151,43 +167,43 @@ def test_load_cached_data_for_updating(mocker) -> None:
|
||||
# timeframe starts earlier than the cached data
|
||||
# should fully update data
|
||||
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
assert start_ts == test_data[0][0] - 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m',
|
||||
TimeRange(None, 'line', 0, -num_lines))
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m',
|
||||
TimeRange(None, 'line', 0, -num_lines))
|
||||
assert data == []
|
||||
assert start_ts < test_data[0][0] - 1
|
||||
|
||||
# timeframe starts in the center of the cached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# timeframe starts after the chached data
|
||||
# should return the chached data w/o the last item
|
||||
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# Try loading last 30 lines.
|
||||
# Not supported by load_cached_data_for_updating, we always need to get the full data.
|
||||
# Not supported by _load_cached_data_for_updating, we always need to get the full data.
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
@@ -195,27 +211,27 @@ def test_load_cached_data_for_updating(mocker) -> None:
|
||||
# should return the chached data w/o the last item
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
|
||||
assert data == test_data[:-1]
|
||||
assert test_data[-2][0] < start_ts < test_data[-1][0]
|
||||
|
||||
# no datafile exist
|
||||
# should return timestamp start time
|
||||
timerange = TimeRange('date', None, now_ts - 10000, 0)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
assert start_ts == (now_ts - 10000) * 1000
|
||||
|
||||
# same with 'line' timeframe
|
||||
num_lines = 30
|
||||
timerange = TimeRange(None, 'line', 0, -num_lines)
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
|
||||
assert data == []
|
||||
assert start_ts == (now_ts - num_lines * 60) * 1000
|
||||
|
||||
# no datafile exist, no timeframe is set
|
||||
# should return an empty array and None
|
||||
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', None)
|
||||
data, start_ts = _load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', None)
|
||||
assert data == []
|
||||
assert start_ts is None
|
||||
|
||||
@@ -223,18 +239,18 @@ def test_load_cached_data_for_updating(mocker) -> None:
|
||||
def test_download_pair_history(ticker_history_list, mocker, default_conf, testdatadir) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
|
||||
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
|
||||
file2_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-5m.json')
|
||||
file1_1 = testdatadir / 'MEME_BTC-1m.json'
|
||||
file1_5 = testdatadir / 'MEME_BTC-5m.json'
|
||||
file2_1 = testdatadir / 'CFI_BTC-1m.json'
|
||||
file2_5 = testdatadir / 'CFI_BTC-5m.json'
|
||||
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
_backup_file(file2_1)
|
||||
_backup_file(file2_5)
|
||||
|
||||
assert os.path.isfile(file1_1) is False
|
||||
assert os.path.isfile(file2_1) is False
|
||||
assert not file1_1.is_file()
|
||||
assert not file2_1.is_file()
|
||||
|
||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
pair='MEME/BTC',
|
||||
@@ -243,15 +259,15 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda
|
||||
pair='CFI/BTC',
|
||||
ticker_interval='1m')
|
||||
assert not exchange._pairs_last_refresh_time
|
||||
assert os.path.isfile(file1_1) is True
|
||||
assert os.path.isfile(file2_1) is True
|
||||
assert file1_1.is_file()
|
||||
assert file2_1.is_file()
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_1)
|
||||
_clean_test_file(file2_1)
|
||||
|
||||
assert os.path.isfile(file1_5) is False
|
||||
assert os.path.isfile(file2_5) is False
|
||||
assert not file1_5.is_file()
|
||||
assert not file2_5.is_file()
|
||||
|
||||
assert download_pair_history(datadir=testdatadir, exchange=exchange,
|
||||
pair='MEME/BTC',
|
||||
@@ -260,8 +276,8 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf, testda
|
||||
pair='CFI/BTC',
|
||||
ticker_interval='5m')
|
||||
assert not exchange._pairs_last_refresh_time
|
||||
assert os.path.isfile(file1_5) is True
|
||||
assert os.path.isfile(file2_5) is True
|
||||
assert file1_5.is_file()
|
||||
assert file2_5.is_file()
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_5)
|
||||
@@ -288,8 +304,8 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog,
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
|
||||
file1_1 = testdatadir / 'MEME_BTC-1m.json'
|
||||
file1_5 = testdatadir / 'MEME_BTC-5m.json'
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
|
||||
@@ -359,43 +375,12 @@ def test_init(default_conf, mocker) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_trim_tickerlist() -> None:
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
|
||||
def test_trim_tickerlist(testdatadir) -> None:
|
||||
file = testdatadir / 'UNITTEST_BTC-1m.json'
|
||||
with open(file) as data_file:
|
||||
ticker_list = json.load(data_file)
|
||||
ticker_list_len = len(ticker_list)
|
||||
|
||||
# Test the pattern ^(-\d+)$
|
||||
# This pattern uses the latest N elements
|
||||
timerange = TimeRange(None, 'line', 0, -5)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is not ticker[0] # The first element should be different
|
||||
assert ticker_list[-1] is ticker[-1] # The last element must be the same
|
||||
|
||||
# Test the pattern ^(\d+)-$
|
||||
# This pattern keep X element from the end
|
||||
timerange = TimeRange('line', None, 5, 0)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is ticker[0] # The first element must be the same
|
||||
assert ticker_list[-1] is not ticker[-1] # The last element should be different
|
||||
|
||||
# Test the pattern ^(\d+)-(\d+)$
|
||||
# This pattern extract a window
|
||||
timerange = TimeRange('index', 'index', 5, 10)
|
||||
ticker = trim_tickerlist(ticker_list, timerange)
|
||||
ticker_len = len(ticker)
|
||||
|
||||
assert ticker_len == 5
|
||||
assert ticker_list[0] is not ticker[0] # The first element should be different
|
||||
assert ticker_list[5] is ticker[0] # The list starts at the index 5
|
||||
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
|
||||
|
||||
# Test the pattern ^(\d{8})-(\d{8})$
|
||||
# This pattern extract a window between the dates
|
||||
timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
|
||||
@@ -435,13 +420,6 @@ def test_trim_tickerlist() -> None:
|
||||
|
||||
assert ticker_list_len == ticker_len
|
||||
|
||||
# Test invalid timerange (start after stop)
|
||||
timerange = TimeRange('index', 'index', 10, 5)
|
||||
with pytest.raises(ValueError, match=r'The timerange .* is incorrect'):
|
||||
trim_tickerlist(ticker_list, timerange)
|
||||
|
||||
assert ticker_list_len == ticker_len
|
||||
|
||||
# passing empty list
|
||||
timerange = TimeRange(None, None, None, 5)
|
||||
ticker = trim_tickerlist([], timerange)
|
||||
@@ -449,22 +427,21 @@ def test_trim_tickerlist() -> None:
|
||||
assert not ticker
|
||||
|
||||
|
||||
def test_file_dump_json_tofile() -> None:
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
||||
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
||||
def test_file_dump_json_tofile(testdatadir) -> None:
|
||||
file = testdatadir / 'test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
data = {'bar': 'foo'}
|
||||
|
||||
# check the file we will create does not exist
|
||||
assert os.path.isfile(file) is False
|
||||
assert not file.is_file()
|
||||
|
||||
# Create the Json file
|
||||
file_dump_json(file, data)
|
||||
|
||||
# Check the file was create
|
||||
assert os.path.isfile(file) is True
|
||||
assert file.is_file()
|
||||
|
||||
# Open the Json file created and test the data is in it
|
||||
with open(file) as data_file:
|
||||
with file.open() as data_file:
|
||||
json_from_file = json.load(data_file)
|
||||
|
||||
assert 'bar' in json_from_file
|
||||
@@ -571,3 +548,92 @@ def test_download_data_no_markets(mocker, default_conf, caplog, testdatadir):
|
||||
assert "ETH/BTC" in unav_pairs
|
||||
assert "XRP/BTC" in unav_pairs
|
||||
assert log_has("Skipping pair ETH/BTC...", caplog)
|
||||
|
||||
|
||||
def test_refresh_backtest_trades_data(mocker, default_conf, markets, caplog, testdatadir):
|
||||
dl_mock = mocker.patch('freqtrade.data.history.download_trades_history', MagicMock())
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets)
|
||||
)
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
timerange = TimeRange.parse_timerange("20190101-20190102")
|
||||
unavailable_pairs = refresh_backtest_trades_data(exchange=ex,
|
||||
pairs=["ETH/BTC", "XRP/BTC", "XRP/ETH"],
|
||||
datadir=testdatadir,
|
||||
timerange=timerange, erase=True
|
||||
)
|
||||
|
||||
assert dl_mock.call_count == 2
|
||||
assert dl_mock.call_args[1]['timerange'].starttype == 'date'
|
||||
|
||||
assert log_has("Downloading trades for pair ETH/BTC.", caplog)
|
||||
assert unavailable_pairs == ["XRP/ETH"]
|
||||
assert log_has("Skipping pair XRP/ETH...", caplog)
|
||||
|
||||
|
||||
def test_download_trades_history(trades_history, mocker, default_conf, testdatadir, caplog) -> None:
|
||||
|
||||
ght_mock = MagicMock(side_effect=lambda pair, *args, **kwargs: (pair, trades_history))
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
ght_mock)
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
file1 = testdatadir / 'ETH_BTC-trades.json.gz'
|
||||
|
||||
_backup_file(file1)
|
||||
|
||||
assert not file1.is_file()
|
||||
|
||||
assert download_trades_history(datadir=testdatadir, exchange=exchange,
|
||||
pair='ETH/BTC')
|
||||
assert log_has("New Amount of trades: 5", caplog)
|
||||
assert file1.is_file()
|
||||
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1)
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
|
||||
MagicMock(side_effect=ValueError))
|
||||
|
||||
assert not download_trades_history(datadir=testdatadir, exchange=exchange,
|
||||
pair='ETH/BTC')
|
||||
assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog)
|
||||
|
||||
|
||||
def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||
|
||||
pair = 'XRP/ETH'
|
||||
file1 = testdatadir / 'XRP_ETH-1m.json'
|
||||
file5 = testdatadir / 'XRP_ETH-5m.json'
|
||||
# Compare downloaded dataset with converted dataset
|
||||
dfbak_1m = history.load_pair_history(datadir=testdatadir,
|
||||
ticker_interval="1m",
|
||||
pair=pair)
|
||||
dfbak_5m = history.load_pair_history(datadir=testdatadir,
|
||||
ticker_interval="5m",
|
||||
pair=pair)
|
||||
|
||||
_backup_file(file1, copy_file=True)
|
||||
_backup_file(file5)
|
||||
|
||||
tr = TimeRange.parse_timerange('20191011-20191012')
|
||||
|
||||
convert_trades_to_ohlcv([pair], timeframes=['1m', '5m'],
|
||||
datadir=testdatadir, timerange=tr, erase=True)
|
||||
|
||||
assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog)
|
||||
# Load new data
|
||||
df_1m = history.load_pair_history(datadir=testdatadir,
|
||||
ticker_interval="1m",
|
||||
pair=pair)
|
||||
df_5m = history.load_pair_history(datadir=testdatadir,
|
||||
ticker_interval="5m",
|
||||
pair=pair)
|
||||
|
||||
assert df_1m.equals(dfbak_1m)
|
||||
assert df_5m.equals(dfbak_5m)
|
||||
|
||||
_clean_test_file(file1)
|
||||
_clean_test_file(file5)
|
||||
|
@@ -79,7 +79,7 @@ tc0 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 1]], # enter trade (signal on last candle)
|
||||
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
|
||||
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
||||
trades=[]
|
||||
)
|
||||
|
||||
@@ -94,7 +94,7 @@ tc1 = BTContainer(data=[
|
||||
[5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action
|
||||
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
|
||||
],
|
||||
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
|
||||
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
|
||||
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
|
||||
)
|
||||
@@ -106,7 +106,7 @@ tc2 = BTContainer(data=[
|
||||
[1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit
|
||||
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||
],
|
||||
stop_loss=-0.01, roi=float('inf'), profit_perc=-0.01,
|
||||
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ tc3 = BTContainer(data=[
|
||||
[1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit
|
||||
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||
],
|
||||
stop_loss=-0.03, roi=float('inf'), profit_perc=-0.03,
|
||||
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ tc4 = BTContainer(data=[
|
||||
[1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal
|
||||
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||
],
|
||||
stop_loss=-0.03, roi=float('inf'), profit_perc=-0.03,
|
||||
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
|
@@ -142,6 +142,12 @@ def test_exchange_resolver(default_conf, mocker, caplog):
|
||||
assert not log_has_re(r"No .* specific subclass found. Using the generic class instead.",
|
||||
caplog)
|
||||
|
||||
# Test mapping
|
||||
exchange = ExchangeResolver('binanceus', default_conf).exchange
|
||||
assert isinstance(exchange, Exchange)
|
||||
assert isinstance(exchange, Binance)
|
||||
assert not isinstance(exchange, Kraken)
|
||||
|
||||
|
||||
def test_validate_order_time_in_force(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
@@ -409,7 +415,8 @@ def test_validate_timeframes_failed(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
|
||||
with pytest.raises(OperationalException, match=r'Invalid ticker 3m, this Exchange supports.*'):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"Invalid ticker interval '3m'. This exchange supports.*"):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
@@ -1135,6 +1142,13 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
|
||||
await exchange._async_get_candle_history(pair, "5m",
|
||||
(arrow.utcnow().timestamp - 2000) * 1000)
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical candlestick data\..*'):
|
||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_get_candle_history(pair, "5m",
|
||||
(arrow.utcnow().timestamp - 2000) * 1000)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
|
||||
@@ -1306,6 +1320,196 @@ async def test___async_get_candle_history_sort(default_conf, mocker, exchange_na
|
||||
assert ticks[9][5] == 2.31452783
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_fetch_trades(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
# Monkey-patch async function
|
||||
exchange._api_async.fetch_trades = get_mock_coro(trades_history)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params=None)
|
||||
assert type(res) is list
|
||||
assert isinstance(res[0], dict)
|
||||
assert isinstance(res[1], dict)
|
||||
|
||||
assert exchange._api_async.fetch_trades.call_count == 1
|
||||
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
|
||||
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, since .*", caplog)
|
||||
caplog.clear()
|
||||
exchange._api_async.fetch_trades.reset_mock()
|
||||
res = await exchange._async_fetch_trades(pair, since=None, params={'from': '123'})
|
||||
assert exchange._api_async.fetch_trades.call_count == 1
|
||||
assert exchange._api_async.fetch_trades.call_args[0][0] == pair
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['limit'] == 1000
|
||||
assert exchange._api_async.fetch_trades.call_args[1]['params'] == {'from': '123'}
|
||||
assert log_has_re(f"Fetching trades for pair {pair}, params: .*", caplog)
|
||||
|
||||
exchange = Exchange(default_conf)
|
||||
await async_ccxt_exception(mocker, default_conf, MagicMock(),
|
||||
"_async_fetch_trades", "fetch_trades",
|
||||
pair='ABCD/BTC', since=None)
|
||||
|
||||
api_mock = MagicMock()
|
||||
with pytest.raises(OperationalException, match=r'Could not fetch trade data*'):
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000)
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Exchange.* does not support fetching '
|
||||
r'historical trade data\..*'):
|
||||
api_mock.fetch_trades = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
await exchange._async_fetch_trades(pair, since=(arrow.utcnow().timestamp - 2000) * 1000)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_trade_history_id(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
pagination_arg = exchange._trades_pagination_arg
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if 'since' in kwargs:
|
||||
# Return first 3
|
||||
return trades_history[:-2]
|
||||
elif kwargs.get('params', {}).get(pagination_arg) == trades_history[-3]['id']:
|
||||
# Return 2
|
||||
return trades_history[-3:-1]
|
||||
else:
|
||||
# Return last 2
|
||||
return trades_history[-2:]
|
||||
# Monkey-patch async function
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_id(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
assert len(ret[1]) == len(trades_history)
|
||||
assert exchange._async_fetch_trades.call_count == 3
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
|
||||
# 2nd call
|
||||
assert fetch_trades_cal[1][0][0] == pair
|
||||
assert 'params' in fetch_trades_cal[1][1]
|
||||
assert exchange._ft_has['trades_pagination_arg'] in fetch_trades_cal[1][1]['params']
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_trade_history_time(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if kwargs['since'] == trades_history[0]["timestamp"]:
|
||||
return trades_history[:-1]
|
||||
else:
|
||||
return trades_history[-1:]
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
# Monkey-patch async function
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
assert len(ret[1]) == len(trades_history)
|
||||
assert exchange._async_fetch_trades.call_count == 2
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
|
||||
# 2nd call
|
||||
assert fetch_trades_cal[1][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
assert log_has_re(r"Stopping because until was reached.*", caplog)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
|
||||
async def mock_get_trade_hist(pair, *args, **kwargs):
|
||||
if kwargs['since'] == trades_history[0]["timestamp"]:
|
||||
return trades_history[:-1]
|
||||
else:
|
||||
return []
|
||||
|
||||
caplog.set_level(logging.DEBUG)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
# Monkey-patch async function
|
||||
exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist)
|
||||
pair = 'ETH/BTC'
|
||||
ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"]-1)
|
||||
assert type(ret) is tuple
|
||||
assert ret[0] == pair
|
||||
assert type(ret[1]) is list
|
||||
assert len(ret[1]) == len(trades_history) - 1
|
||||
assert exchange._async_fetch_trades.call_count == 2
|
||||
fetch_trades_cal = exchange._async_fetch_trades.call_args_list
|
||||
# first call (using since, not fromId)
|
||||
assert fetch_trades_cal[0][0][0] == pair
|
||||
assert fetch_trades_cal[0][1]['since'] == trades_history[0]["timestamp"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_historic_trades(default_conf, mocker, caplog, exchange_name, trades_history):
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
exchange._async_get_trade_history_id = get_mock_coro((pair, trades_history))
|
||||
exchange._async_get_trade_history_time = get_mock_coro((pair, trades_history))
|
||||
ret = exchange.get_historic_trades(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"])
|
||||
|
||||
# Depending on the exchange, one or the other method should be called
|
||||
assert sum([exchange._async_get_trade_history_id.call_count,
|
||||
exchange._async_get_trade_history_time.call_count]) == 1
|
||||
|
||||
assert len(ret) == 2
|
||||
assert ret[0] == pair
|
||||
assert len(ret[1]) == len(trades_history)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange_name,
|
||||
trades_history):
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=False)
|
||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match="This exchange does not suport downloading Trades."):
|
||||
exchange.get_historic_trades(pair, since=trades_history[0]["timestamp"],
|
||||
until=trades_history[-1]["timestamp"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||
default_conf['dry_run'] = True
|
||||
@@ -1452,13 +1656,17 @@ def test_merge_ft_has_dict(default_conf, mocker):
|
||||
assert ex._ft_has == Exchange._ft_has_default
|
||||
|
||||
ex = Kraken(default_conf)
|
||||
assert ex._ft_has == Exchange._ft_has_default
|
||||
assert ex._ft_has != Exchange._ft_has_default
|
||||
assert ex._ft_has['trades_pagination'] == 'id'
|
||||
assert ex._ft_has['trades_pagination_arg'] == 'since'
|
||||
|
||||
# Binance defines different values
|
||||
ex = Binance(default_conf)
|
||||
assert ex._ft_has != Exchange._ft_has_default
|
||||
assert ex._ft_has['stoploss_on_exchange']
|
||||
assert ex._ft_has['order_time_in_force'] == ['gtc', 'fok', 'ioc']
|
||||
assert ex._ft_has['trades_pagination'] == 'id'
|
||||
assert ex._ft_has['trades_pagination_arg'] == 'fromId'
|
||||
|
||||
conf = copy.deepcopy(default_conf)
|
||||
conf['exchange']['_ft_has_params'] = {"DeadBeef": 20,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
from typing import NamedTuple, List
|
||||
from typing import Dict, List, NamedTuple
|
||||
|
||||
import arrow
|
||||
from pandas import DataFrame
|
||||
@@ -25,7 +25,7 @@ class BTContainer(NamedTuple):
|
||||
"""
|
||||
data: List[float]
|
||||
stop_loss: float
|
||||
roi: float
|
||||
roi: Dict[str, float]
|
||||
trades: List[BTrade]
|
||||
profit_perc: float
|
||||
trailing_stop: bool = False
|
||||
|
@@ -22,7 +22,7 @@ tc0 = BTContainer(data=[
|
||||
[3, 5010, 5000, 4980, 5010, 6172, 0, 1],
|
||||
[4, 5010, 4987, 4977, 4995, 6172, 0, 0],
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi=1, profit_perc=0.002, use_sell_signal=True,
|
||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
|
||||
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ tc1 = BTContainer(data=[
|
||||
[3, 4975, 5000, 4980, 4977, 6172, 0, 0],
|
||||
[4, 4977, 4987, 4977, 4995, 6172, 0, 0],
|
||||
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi=1, profit_perc=-0.01,
|
||||
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
@@ -51,7 +51,7 @@ tc2 = BTContainer(data=[
|
||||
[3, 4975, 5000, 4800, 4962, 6172, 0, 0], # exit with stoploss hit
|
||||
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.03, roi=1, profit_perc=-0.03,
|
||||
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ tc3 = BTContainer(data=[
|
||||
[4, 4975, 5000, 4950, 4962, 6172, 0, 0], # enter trade 2 (signal on last candle)
|
||||
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
|
||||
[6, 4950, 4975, 4975, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.02, roi=1, profit_perc=-0.04,
|
||||
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
|
||||
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
|
||||
)
|
||||
@@ -88,7 +88,7 @@ tc4 = BTContainer(data=[
|
||||
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
|
||||
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.02, roi=0.06, profit_perc=-0.02,
|
||||
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ tc5 = BTContainer(data=[
|
||||
[3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI
|
||||
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.01, roi=0.03, profit_perc=0.03,
|
||||
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ tc6 = BTContainer(data=[
|
||||
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
|
||||
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.02, roi=0.05, profit_perc=-0.02,
|
||||
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
@@ -130,7 +130,7 @@ tc7 = BTContainer(data=[
|
||||
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
|
||||
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.02, roi=0.03, profit_perc=0.03,
|
||||
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
||||
)
|
||||
|
||||
@@ -144,7 +144,7 @@ tc8 = BTContainer(data=[
|
||||
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True,
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True,
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
@@ -158,7 +158,7 @@ tc9 = BTContainer(data=[
|
||||
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
|
||||
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True,
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True,
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ tc10 = BTContainer(data=[
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.10, profit_perc=-0.1, trailing_stop=True,
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
|
||||
@@ -188,7 +188,7 @@ tc11 = BTContainer(data=[
|
||||
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.10, profit_perc=0.019, trailing_stop=True,
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
@@ -204,7 +204,7 @@ tc12 = BTContainer(data=[
|
||||
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.10, profit_perc=0.019, trailing_stop=True,
|
||||
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
|
||||
trailing_stop_positive=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
|
||||
@@ -219,7 +219,7 @@ tc13 = BTContainer(data=[
|
||||
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4850, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4850, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi=0.01, profit_perc=0.01,
|
||||
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
@@ -232,7 +232,7 @@ tc14 = BTContainer(data=[
|
||||
[2, 5100, 5251, 4850, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4850, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.05, roi=0.10, profit_perc=-0.05,
|
||||
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
@@ -246,11 +246,26 @@ tc15 = BTContainer(data=[
|
||||
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
|
||||
[3, 4850, 5050, 4850, 4750, 6172, 0, 0],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
|
||||
stop_loss=-0.05, roi=0.01, profit_perc=-0.04,
|
||||
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1),
|
||||
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)]
|
||||
)
|
||||
|
||||
# Test 16: Buy, hold for 65 mins, then forcesell using roi=-1
|
||||
# Causes negative profit even though sell-reason is ROI.
|
||||
# stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration)
|
||||
tc16 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
|
||||
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
|
||||
[3, 4975, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1)
|
||||
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
@@ -268,6 +283,7 @@ TESTS = [
|
||||
tc13,
|
||||
tc14,
|
||||
tc15,
|
||||
tc16,
|
||||
]
|
||||
|
||||
|
||||
@@ -277,7 +293,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
run functional tests
|
||||
"""
|
||||
default_conf["stoploss"] = data.stop_loss
|
||||
default_conf["minimal_roi"] = {"0": data.roi}
|
||||
default_conf["minimal_roi"] = data.roi
|
||||
default_conf["ticker_interval"] = tests_ticker_interval
|
||||
default_conf["trailing_stop"] = data.trailing_stop
|
||||
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
||||
@@ -285,7 +301,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
if data.trailing_stop_positive:
|
||||
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
||||
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
||||
default_conf["experimental"] = {"use_sell_signal": data.use_sell_signal}
|
||||
default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal}
|
||||
|
||||
mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0))
|
||||
patch_exchange(mocker)
|
||||
|
@@ -26,6 +26,21 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
|
||||
ORDER_TYPES = [
|
||||
{
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False
|
||||
},
|
||||
{
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': True
|
||||
}]
|
||||
|
||||
|
||||
def trim_dictlist(dict_list, num):
|
||||
new = {}
|
||||
for pair, pair_data in dict_list.items():
|
||||
@@ -34,7 +49,7 @@ def trim_dictlist(dict_list, num):
|
||||
|
||||
|
||||
def load_data_test(what, testdatadir):
|
||||
timerange = TimeRange(None, 'line', 0, -101)
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
pair = history.load_tickerdata_file(testdatadir, ticker_interval='1m',
|
||||
pair='UNITTEST/BTC', timerange=timerange)
|
||||
datalen = len(pair)
|
||||
@@ -211,7 +226,8 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
'--disable-max-market-positions',
|
||||
'--timerange', ':100',
|
||||
'--export', '/bar/foo',
|
||||
'--export-filename', 'foo_bar.json'
|
||||
'--export-filename', 'foo_bar.json',
|
||||
'--fee', '0',
|
||||
]
|
||||
|
||||
config = setup_configuration(get_args(args), RunMode.BACKTEST)
|
||||
@@ -243,6 +259,9 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
assert 'exportfilename' in config
|
||||
assert log_has('Storing backtest results to {} ...'.format(config['exportfilename']), caplog)
|
||||
|
||||
assert 'fee' in config
|
||||
assert log_has('Parameter --fee detected, setting fee to: {} ...'.format(config['fee']), caplog)
|
||||
|
||||
|
||||
def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None:
|
||||
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
|
||||
@@ -277,21 +296,6 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
|
||||
ORDER_TYPES = [
|
||||
{
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': False
|
||||
},
|
||||
{
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': True
|
||||
}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("order_types", ORDER_TYPES)
|
||||
def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
||||
"""
|
||||
@@ -314,10 +318,6 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
||||
|
||||
|
||||
def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> None:
|
||||
"""
|
||||
Check that stoploss_on_exchange is set to False while backtesting
|
||||
since backtesting assumes a perfect stoploss anyway.
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
del default_conf['ticker_interval']
|
||||
default_conf['strategy_list'] = ['DefaultStrategy',
|
||||
@@ -330,9 +330,20 @@ def test_backtesting_init_no_ticker_interval(mocker, default_conf, caplog) -> No
|
||||
"or as cli argument `--ticker-interval 5m`", caplog)
|
||||
|
||||
|
||||
def test_tickerdata_with_fee(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
default_conf['fee'] = 0.1234
|
||||
|
||||
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||
backtesting = Backtesting(default_conf)
|
||||
assert backtesting.fee == 0.1234
|
||||
assert fee_mock.call_count == 0
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||
patch_exchange(mocker)
|
||||
timerange = TimeRange(None, 'line', 0, -100)
|
||||
# timerange = TimeRange(None, 'line', 0, -100)
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
tick = history.load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
@@ -464,7 +475,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
default_conf['ticker_interval'] = '1m'
|
||||
default_conf['datadir'] = testdatadir
|
||||
default_conf['export'] = None
|
||||
default_conf['timerange'] = '-100'
|
||||
default_conf['timerange'] = '-1510694220'
|
||||
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.start()
|
||||
@@ -507,11 +518,12 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
|
||||
|
||||
|
||||
def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
pair = 'UNITTEST/BTC'
|
||||
timerange = TimeRange(None, 'line', 0, -201)
|
||||
timerange = TimeRange('date', None, 1517227800, 0)
|
||||
data = history.load_data(datadir=testdatadir, ticker_interval='5m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
@@ -561,12 +573,13 @@ def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
|
||||
|
||||
|
||||
def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -> None:
|
||||
default_conf['ask_strategy']['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(default_conf)
|
||||
|
||||
# Run a backtesting for an exiting 1min ticker_interval
|
||||
timerange = TimeRange(None, 'line', 0, -200)
|
||||
timerange = TimeRange.parse_timerange('1510688220-1510700340')
|
||||
data = history.load_data(datadir=testdatadir, ticker_interval='1m', pairs=['UNITTEST/BTC'],
|
||||
timerange=timerange)
|
||||
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
@@ -603,8 +616,6 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir) -> None:
|
||||
# TODO: Evaluate usefullness of this, the patterns and buy-signls are unrealistic
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
tests = [['raise', 19], ['lower', 0], ['sine', 35]]
|
||||
# We need to enable sell-signal - otherwise it sells on ROI!!
|
||||
default_conf['experimental'] = {"use_sell_signal": True}
|
||||
|
||||
for [contour, numres] in tests:
|
||||
simple_backtest(default_conf, contour, numres, mocker, testdatadir)
|
||||
@@ -645,8 +656,6 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
mocker.patch('freqtrade.optimize.backtesting.file_dump_json', MagicMock())
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf,
|
||||
pair='UNITTEST/BTC', datadir=testdatadir)
|
||||
# We need to enable sell-signal - otherwise it sells on ROI!!
|
||||
default_conf['experimental'] = {"use_sell_signal": True}
|
||||
default_conf['ticker_interval'] = '1m'
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.strategy.advise_buy = _trend_alternate # Override
|
||||
@@ -687,8 +696,6 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
|
||||
# Remove data for one pair from the beginning of the data
|
||||
data[pair] = data[pair][tres:].reset_index()
|
||||
# We need to enable sell-signal - otherwise it sells on ROI!!
|
||||
default_conf['experimental'] = {"use_sell_signal": True}
|
||||
default_conf['ticker_interval'] = '5m'
|
||||
|
||||
backtesting = Backtesting(default_conf)
|
||||
@@ -817,7 +824,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--datadir', str(testdatadir),
|
||||
'--ticker-interval', '1m',
|
||||
'--timerange', '-100',
|
||||
'--timerange', '1510694220-1510700340',
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions'
|
||||
]
|
||||
@@ -827,7 +834,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
exists = [
|
||||
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
|
||||
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
|
||||
'Parameter --timerange detected: -100 ...',
|
||||
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
||||
f'Using data directory: {testdatadir} ...',
|
||||
'Using stake_currency: BTC ...',
|
||||
'Using stake_amount: 0.001 ...',
|
||||
@@ -863,7 +870,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'--config', 'config.json',
|
||||
'--datadir', str(testdatadir),
|
||||
'--ticker-interval', '1m',
|
||||
'--timerange', '-100',
|
||||
'--timerange', '1510694220-1510700340',
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--strategy-list',
|
||||
@@ -881,7 +888,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
exists = [
|
||||
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
|
||||
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
|
||||
'Parameter --timerange detected: -100 ...',
|
||||
'Parameter --timerange detected: 1510694220-1510700340 ...',
|
||||
f'Using data directory: {testdatadir} ...',
|
||||
'Using stake_currency: BTC ...',
|
||||
'Using stake_amount: 0.001 ...',
|
||||
|
@@ -98,6 +98,16 @@ def test_edge_init(mocker, edge_conf) -> None:
|
||||
assert callable(edge_cli.edge.calculate)
|
||||
|
||||
|
||||
def test_edge_init_fee(mocker, edge_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
edge_conf['fee'] = 0.1234
|
||||
edge_conf['stake_amount'] = 20
|
||||
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||
edge_cli = EdgeCli(edge_conf)
|
||||
assert edge_cli.edge.fee == 0.1234
|
||||
assert fee_mock.call_count == 0
|
||||
|
||||
|
||||
def test_generate_edge_table(edge_conf, mocker):
|
||||
patch_exchange(mocker)
|
||||
edge_cli = EdgeCli(edge_conf)
|
||||
|
@@ -12,7 +12,7 @@ from freqtrade import OperationalException
|
||||
from freqtrade.data.converter import parse_ticker_dataframe
|
||||
from freqtrade.data.history import load_tickerdata_file
|
||||
from freqtrade.optimize import setup_configuration, start_hyperopt
|
||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
|
||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpt
|
||||
from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss
|
||||
from freqtrade.optimize.hyperopt import Hyperopt
|
||||
from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver,
|
||||
@@ -153,12 +153,12 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
hyperopts = DefaultHyperOpts
|
||||
delattr(hyperopts, 'populate_buy_trend')
|
||||
delattr(hyperopts, 'populate_sell_trend')
|
||||
hyperopt = DefaultHyperOpt
|
||||
delattr(hyperopt, 'populate_buy_trend')
|
||||
delattr(hyperopt, 'populate_sell_trend')
|
||||
mocker.patch(
|
||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
|
||||
MagicMock(return_value=hyperopts(default_conf))
|
||||
MagicMock(return_value=hyperopt(default_conf))
|
||||
)
|
||||
default_conf.update({'hyperopt': 'DefaultHyperOpts'})
|
||||
x = HyperOptResolver(default_conf).hyperopt
|
||||
|
@@ -6,8 +6,6 @@ from pandas import DataFrame
|
||||
|
||||
# Add your lib to import here
|
||||
import talib.abstract as ta
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
import numpy # noqa
|
||||
|
||||
|
||||
# This class is a sample. Feel free to customize it.
|
||||
@@ -17,7 +15,6 @@ class TestStrategyLegacy(IStrategy):
|
||||
removed in a future update.
|
||||
Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py
|
||||
for a uptodate version of this template.
|
||||
|
||||
"""
|
||||
|
||||
# Minimal ROI designed for the strategy.
|
||||
@@ -51,156 +48,9 @@ class TestStrategyLegacy(IStrategy):
|
||||
# ADX
|
||||
dataframe['adx'] = ta.ADX(dataframe)
|
||||
|
||||
"""
|
||||
# Awesome oscillator
|
||||
dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
|
||||
|
||||
# Commodity Channel Index: values Oversold:<-100, Overbought:>100
|
||||
dataframe['cci'] = ta.CCI(dataframe)
|
||||
|
||||
# MACD
|
||||
macd = ta.MACD(dataframe)
|
||||
dataframe['macd'] = macd['macd']
|
||||
dataframe['macdsignal'] = macd['macdsignal']
|
||||
dataframe['macdhist'] = macd['macdhist']
|
||||
|
||||
# MFI
|
||||
dataframe['mfi'] = ta.MFI(dataframe)
|
||||
|
||||
# Minus Directional Indicator / Movement
|
||||
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
|
||||
# Plus Directional Indicator / Movement
|
||||
dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
|
||||
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
|
||||
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
|
||||
|
||||
# ROC
|
||||
dataframe['roc'] = ta.ROC(dataframe)
|
||||
|
||||
# RSI
|
||||
dataframe['rsi'] = ta.RSI(dataframe)
|
||||
|
||||
# Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy)
|
||||
rsi = 0.1 * (dataframe['rsi'] - 50)
|
||||
dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1)
|
||||
|
||||
# Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy)
|
||||
dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
|
||||
|
||||
# Stoch
|
||||
stoch = ta.STOCH(dataframe)
|
||||
dataframe['slowd'] = stoch['slowd']
|
||||
dataframe['slowk'] = stoch['slowk']
|
||||
|
||||
# Stoch fast
|
||||
stoch_fast = ta.STOCHF(dataframe)
|
||||
dataframe['fastd'] = stoch_fast['fastd']
|
||||
dataframe['fastk'] = stoch_fast['fastk']
|
||||
|
||||
# Stoch RSI
|
||||
stoch_rsi = ta.STOCHRSI(dataframe)
|
||||
dataframe['fastd_rsi'] = stoch_rsi['fastd']
|
||||
dataframe['fastk_rsi'] = stoch_rsi['fastk']
|
||||
"""
|
||||
|
||||
# Overlap Studies
|
||||
# ------------------------------------
|
||||
|
||||
# Bollinger bands
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
||||
dataframe['bb_lowerband'] = bollinger['lower']
|
||||
dataframe['bb_middleband'] = bollinger['mid']
|
||||
dataframe['bb_upperband'] = bollinger['upper']
|
||||
|
||||
"""
|
||||
# EMA - Exponential Moving Average
|
||||
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
|
||||
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
|
||||
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
|
||||
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
|
||||
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
|
||||
|
||||
# SAR Parabol
|
||||
dataframe['sar'] = ta.SAR(dataframe)
|
||||
|
||||
# SMA - Simple Moving Average
|
||||
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
|
||||
"""
|
||||
|
||||
# TEMA - Triple Exponential Moving Average
|
||||
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
|
||||
|
||||
# Cycle Indicator
|
||||
# ------------------------------------
|
||||
# Hilbert Transform Indicator - SineWave
|
||||
hilbert = ta.HT_SINE(dataframe)
|
||||
dataframe['htsine'] = hilbert['sine']
|
||||
dataframe['htleadsine'] = hilbert['leadsine']
|
||||
|
||||
# Pattern Recognition - Bullish candlestick patterns
|
||||
# ------------------------------------
|
||||
"""
|
||||
# Hammer: values [0, 100]
|
||||
dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
|
||||
# Inverted Hammer: values [0, 100]
|
||||
dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
|
||||
# Dragonfly Doji: values [0, 100]
|
||||
dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
|
||||
# Piercing Line: values [0, 100]
|
||||
dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
|
||||
# Morningstar: values [0, 100]
|
||||
dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
|
||||
# Three White Soldiers: values [0, 100]
|
||||
dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
|
||||
"""
|
||||
|
||||
# Pattern Recognition - Bearish candlestick patterns
|
||||
# ------------------------------------
|
||||
"""
|
||||
# Hanging Man: values [0, 100]
|
||||
dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
|
||||
# Shooting Star: values [0, 100]
|
||||
dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
|
||||
# Gravestone Doji: values [0, 100]
|
||||
dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
|
||||
# Dark Cloud Cover: values [0, 100]
|
||||
dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
|
||||
# Evening Doji Star: values [0, 100]
|
||||
dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
|
||||
# Evening Star: values [0, 100]
|
||||
dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
|
||||
"""
|
||||
|
||||
# Pattern Recognition - Bullish/Bearish candlestick patterns
|
||||
# ------------------------------------
|
||||
"""
|
||||
# Three Line Strike: values [0, -100, 100]
|
||||
dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
|
||||
# Spinning Top: values [0, -100, 100]
|
||||
dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
|
||||
# Engulfing: values [0, -100, 100]
|
||||
dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
|
||||
# Harami: values [0, -100, 100]
|
||||
dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
|
||||
# Three Outside Up/Down: values [0, -100, 100]
|
||||
dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
|
||||
# Three Inside Up/Down: values [0, -100, 100]
|
||||
dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
|
||||
"""
|
||||
|
||||
# Chart type
|
||||
# ------------------------------------
|
||||
"""
|
||||
# Heikinashi stategy
|
||||
heikinashi = qtpylib.heikinashi(dataframe)
|
||||
dataframe['ha_open'] = heikinashi['open']
|
||||
dataframe['ha_close'] = heikinashi['close']
|
||||
dataframe['ha_high'] = heikinashi['high']
|
||||
dataframe['ha_low'] = heikinashi['low']
|
||||
"""
|
||||
|
||||
return dataframe
|
||||
|
||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
@@ -212,8 +62,8 @@ class TestStrategyLegacy(IStrategy):
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['adx'] > 30) &
|
||||
(dataframe['tema'] <= dataframe['bb_middleband']) &
|
||||
(dataframe['tema'] > dataframe['tema'].shift(1))
|
||||
(dataframe['tema'] > dataframe['tema'].shift(1)) &
|
||||
(dataframe['volume'] > 0)
|
||||
),
|
||||
'buy'] = 1
|
||||
|
||||
@@ -228,8 +78,8 @@ class TestStrategyLegacy(IStrategy):
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['adx'] > 70) &
|
||||
(dataframe['tema'] > dataframe['bb_middleband']) &
|
||||
(dataframe['tema'] < dataframe['tema'].shift(1))
|
||||
(dataframe['tema'] < dataframe['tema'].shift(1)) &
|
||||
(dataframe['volume'] > 0)
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
|
@@ -106,7 +106,7 @@ def test_get_signal_handles_exceptions(mocker, default_conf):
|
||||
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None:
|
||||
strategy = DefaultStrategy(default_conf)
|
||||
|
||||
timerange = TimeRange(None, 'line', 0, -100)
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
|
||||
fill_missing=True)}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
||||
import logging
|
||||
import tempfile
|
||||
import warnings
|
||||
from base64 import urlsafe_b64encode
|
||||
from os import path
|
||||
@@ -39,7 +38,7 @@ def test_search_strategy():
|
||||
def test_load_strategy(default_conf, result):
|
||||
default_conf.update({'strategy': 'SampleStrategy'})
|
||||
resolver = StrategyResolver(default_conf)
|
||||
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
|
||||
|
||||
def test_load_strategy_base64(result, caplog, default_conf):
|
||||
@@ -48,10 +47,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
|
||||
default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
|
||||
|
||||
resolver = StrategyResolver(default_conf)
|
||||
assert 'adx' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
assert 'rsi' in resolver.strategy.advise_indicators(result, {'pair': 'ETH/BTC'})
|
||||
# Make sure strategy was loaded from base64 (using temp directory)!!
|
||||
assert log_has_re(r"Using resolved strategy SampleStrategy from '"
|
||||
+ tempfile.gettempdir() + r"/.*/SampleStrategy\.py'\.\.\.", caplog)
|
||||
r".*(/|\\).*(/|\\)SampleStrategy\.py'\.\.\.", caplog)
|
||||
|
||||
|
||||
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
||||
@@ -265,23 +264,23 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||
'strategy': 'DefaultStrategy',
|
||||
})
|
||||
resolver = StrategyResolver(default_conf)
|
||||
assert not resolver.strategy.use_sell_signal
|
||||
assert resolver.strategy.use_sell_signal
|
||||
assert isinstance(resolver.strategy.use_sell_signal, bool)
|
||||
# must be inserted to configuration
|
||||
assert 'use_sell_signal' in default_conf['experimental']
|
||||
assert not default_conf['experimental']['use_sell_signal']
|
||||
assert 'use_sell_signal' in default_conf['ask_strategy']
|
||||
assert default_conf['ask_strategy']['use_sell_signal']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'experimental': {
|
||||
'use_sell_signal': True,
|
||||
'ask_strategy': {
|
||||
'use_sell_signal': False,
|
||||
},
|
||||
})
|
||||
resolver = StrategyResolver(default_conf)
|
||||
|
||||
assert resolver.strategy.use_sell_signal
|
||||
assert not resolver.strategy.use_sell_signal
|
||||
assert isinstance(resolver.strategy.use_sell_signal, bool)
|
||||
assert log_has("Override strategy 'use_sell_signal' with value in config file: True.", caplog)
|
||||
assert log_has("Override strategy 'use_sell_signal' with value in config file: False.", caplog)
|
||||
|
||||
|
||||
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
@@ -293,12 +292,12 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
assert not resolver.strategy.sell_profit_only
|
||||
assert isinstance(resolver.strategy.sell_profit_only, bool)
|
||||
# must be inserted to configuration
|
||||
assert 'sell_profit_only' in default_conf['experimental']
|
||||
assert not default_conf['experimental']['sell_profit_only']
|
||||
assert 'sell_profit_only' in default_conf['ask_strategy']
|
||||
assert not default_conf['ask_strategy']['sell_profit_only']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'experimental': {
|
||||
'ask_strategy': {
|
||||
'sell_profit_only': True,
|
||||
},
|
||||
})
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -177,6 +179,44 @@ def test_plot_profit_options() -> None:
|
||||
assert pargs["db_url"] == "sqlite:///whatever.sqlite"
|
||||
|
||||
|
||||
def test_config_notallowed(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
|
||||
args = [
|
||||
'create-userdir',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["config"] is None
|
||||
|
||||
# When file exists:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
args = [
|
||||
'create-userdir',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
# config is not added even if it exists, since create-userdir is in the notallowed list
|
||||
assert pargs["config"] is None
|
||||
|
||||
|
||||
def test_config_notrequired(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
|
||||
args = [
|
||||
'download-data',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["config"] is None
|
||||
|
||||
# When file exists:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
args = [
|
||||
'download-data',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
# config is added if it exists
|
||||
assert pargs["config"] == ['config.json']
|
||||
|
||||
|
||||
def test_check_int_positive() -> None:
|
||||
assert check_int_positive("3") == 3
|
||||
assert check_int_positive("1") == 1
|
||||
|
@@ -14,6 +14,9 @@ from freqtrade.configuration import (Arguments, Configuration,
|
||||
validate_config_consistency)
|
||||
from freqtrade.configuration.check_exchange import check_exchange
|
||||
from freqtrade.configuration.config_validation import validate_config_schema
|
||||
from freqtrade.configuration.deprecated_settings import (check_conflicting_settings,
|
||||
process_deprecated_setting,
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.directory_operations import (create_datadir,
|
||||
create_userdata_dir)
|
||||
from freqtrade.configuration.load_config import load_config_file
|
||||
@@ -418,14 +421,14 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
assert 'pair_whitelist' in config['exchange']
|
||||
assert 'datadir' in config
|
||||
assert log_has('Using data directory: {} ...'.format("/foo/bar"), caplog)
|
||||
assert log_has('Using user-data directory: {} ...'.format("/tmp/freqtrade"), caplog)
|
||||
assert log_has('Using user-data directory: {} ...'.format(Path("/tmp/freqtrade")), caplog)
|
||||
assert 'user_data_dir' in config
|
||||
|
||||
assert 'ticker_interval' in config
|
||||
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
|
||||
caplog)
|
||||
|
||||
assert 'position_stacking'in config
|
||||
assert 'position_stacking' in config
|
||||
assert log_has('Parameter --enable-position-stacking detected ...', caplog)
|
||||
|
||||
assert 'use_max_market_positions' in config
|
||||
@@ -528,7 +531,8 @@ def test_check_exchange(default_conf, caplog) -> None:
|
||||
# Test an available exchange, supported by ccxt
|
||||
default_conf.get('exchange').update({'name': 'huobipro'})
|
||||
assert check_exchange(default_conf)
|
||||
assert log_has_re(r"Exchange .* is supported by ccxt and .* not officially supported "
|
||||
assert log_has_re(r"Exchange .* is known to the the ccxt library, available for the bot, "
|
||||
r"but not officially supported "
|
||||
r"by the Freqtrade development team\. .*", caplog)
|
||||
caplog.clear()
|
||||
|
||||
@@ -542,16 +546,16 @@ def test_check_exchange(default_conf, caplog) -> None:
|
||||
# Test a 'bad' exchange with check_for_bad=False
|
||||
default_conf.get('exchange').update({'name': 'bitmex'})
|
||||
assert check_exchange(default_conf, False)
|
||||
assert log_has_re(r"Exchange .* is supported by ccxt and .* not officially supported "
|
||||
assert log_has_re(r"Exchange .* is known to the the ccxt library, available for the bot, "
|
||||
r"but not officially supported "
|
||||
r"by the Freqtrade development team\. .*", caplog)
|
||||
caplog.clear()
|
||||
|
||||
# Test an invalid exchange
|
||||
default_conf.get('exchange').update({'name': 'unknown_exchange'})
|
||||
|
||||
with pytest.raises(
|
||||
OperationalException,
|
||||
match=r'.*Exchange "unknown_exchange" is not supported by ccxt '
|
||||
match=r'Exchange "unknown_exchange" is not known to the ccxt library '
|
||||
r'and therefore not available for the bot.*'
|
||||
):
|
||||
check_exchange(default_conf)
|
||||
@@ -670,9 +674,9 @@ def test_create_userdata_dir(mocker, default_conf, caplog) -> None:
|
||||
x = create_userdata_dir('/tmp/bar', create_dir=True)
|
||||
assert md.call_count == 7
|
||||
assert md.call_args[1]['parents'] is False
|
||||
assert log_has('Created user-data directory: /tmp/bar', caplog)
|
||||
assert log_has(f'Created user-data directory: {Path("/tmp/bar")}', caplog)
|
||||
assert isinstance(x, Path)
|
||||
assert str(x) == "/tmp/bar"
|
||||
assert str(x) == str(Path("/tmp/bar"))
|
||||
|
||||
|
||||
def test_create_userdata_dir_exists(mocker, default_conf, caplog) -> None:
|
||||
@@ -687,7 +691,8 @@ def test_create_userdata_dir_exists_exception(mocker, default_conf, caplog) -> N
|
||||
mocker.patch.object(Path, "is_dir", MagicMock(return_value=False))
|
||||
md = mocker.patch.object(Path, 'mkdir', MagicMock())
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Directory `/tmp/bar` does not exist.*'):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Directory `.{1,2}tmp.{1,2}bar` does not exist.*'):
|
||||
create_userdata_dir('/tmp/bar', create_dir=False)
|
||||
assert md.call_count == 0
|
||||
|
||||
@@ -918,3 +923,126 @@ def test_pairlist_resolving_fallback(mocker):
|
||||
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert config['exchange']['name'] == 'binance'
|
||||
assert config['datadir'] == str(Path.cwd() / "user_data/data/binance")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("setting", [
|
||||
("ask_strategy", "use_sell_signal", True,
|
||||
"experimental", "use_sell_signal", False),
|
||||
("ask_strategy", "sell_profit_only", False,
|
||||
"experimental", "sell_profit_only", True),
|
||||
("ask_strategy", "ignore_roi_if_buy_signal", False,
|
||||
"experimental", "ignore_roi_if_buy_signal", True),
|
||||
])
|
||||
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
# Create sections for new and deprecated settings
|
||||
# (they may not exist in the config)
|
||||
default_conf[setting[0]] = {}
|
||||
default_conf[setting[3]] = {}
|
||||
# Assign new setting
|
||||
default_conf[setting[0]][setting[1]] = setting[2]
|
||||
# Assign deprecated setting
|
||||
default_conf[setting[3]][setting[4]] = setting[5]
|
||||
|
||||
# New and deprecated settings are conflicting ones
|
||||
with pytest.raises(OperationalException, match=r'DEPRECATED'):
|
||||
process_temporary_deprecated_settings(default_conf)
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Delete new setting
|
||||
del default_conf[setting[0]][setting[1]]
|
||||
|
||||
process_temporary_deprecated_settings(default_conf)
|
||||
assert log_has_re('DEPRECATED', caplog)
|
||||
# The value of the new setting shall have been set to the
|
||||
# value of the deprecated one
|
||||
assert default_conf[setting[0]][setting[1]] == setting[5]
|
||||
|
||||
|
||||
def test_check_conflicting_settings(mocker, default_conf, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
# Create sections for new and deprecated settings
|
||||
# (they may not exist in the config)
|
||||
default_conf['sectionA'] = {}
|
||||
default_conf['sectionB'] = {}
|
||||
# Assign new setting
|
||||
default_conf['sectionA']['new_setting'] = 'valA'
|
||||
# Assign deprecated setting
|
||||
default_conf['sectionB']['deprecated_setting'] = 'valB'
|
||||
|
||||
# New and deprecated settings are conflicting ones
|
||||
with pytest.raises(OperationalException, match=r'DEPRECATED'):
|
||||
check_conflicting_settings(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Delete new setting (deprecated exists)
|
||||
del default_conf['sectionA']['new_setting']
|
||||
check_conflicting_settings(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
assert not log_has_re('DEPRECATED', caplog)
|
||||
assert 'new_setting' not in default_conf['sectionA']
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Assign new setting
|
||||
default_conf['sectionA']['new_setting'] = 'valA'
|
||||
# Delete deprecated setting
|
||||
del default_conf['sectionB']['deprecated_setting']
|
||||
check_conflicting_settings(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
assert not log_has_re('DEPRECATED', caplog)
|
||||
assert default_conf['sectionA']['new_setting'] == 'valA'
|
||||
|
||||
|
||||
def test_process_deprecated_setting(mocker, default_conf, caplog):
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
# Create sections for new and deprecated settings
|
||||
# (they may not exist in the config)
|
||||
default_conf['sectionA'] = {}
|
||||
default_conf['sectionB'] = {}
|
||||
# Assign new setting
|
||||
default_conf['sectionA']['new_setting'] = 'valA'
|
||||
# Assign deprecated setting
|
||||
default_conf['sectionB']['deprecated_setting'] = 'valB'
|
||||
|
||||
# Both new and deprecated settings exists
|
||||
process_deprecated_setting(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
assert log_has_re('DEPRECATED', caplog)
|
||||
# The value of the new setting shall have been set to the
|
||||
# value of the deprecated one
|
||||
assert default_conf['sectionA']['new_setting'] == 'valB'
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Delete new setting (deprecated exists)
|
||||
del default_conf['sectionA']['new_setting']
|
||||
process_deprecated_setting(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
assert log_has_re('DEPRECATED', caplog)
|
||||
# The value of the new setting shall have been set to the
|
||||
# value of the deprecated one
|
||||
assert default_conf['sectionA']['new_setting'] == 'valB'
|
||||
|
||||
caplog.clear()
|
||||
|
||||
# Assign new setting
|
||||
default_conf['sectionA']['new_setting'] = 'valA'
|
||||
# Delete deprecated setting
|
||||
del default_conf['sectionB']['deprecated_setting']
|
||||
process_deprecated_setting(default_conf,
|
||||
'sectionA', 'new_setting',
|
||||
'sectionB', 'deprecated_setting')
|
||||
assert not log_has_re('DEPRECATED', caplog)
|
||||
assert default_conf['sectionA']['new_setting'] == 'valA'
|
||||
|
@@ -655,7 +655,8 @@ def test_create_trades_no_pairs_let(default_conf, ticker, limit_buy_order, fee,
|
||||
|
||||
assert freqtrade.create_trades()
|
||||
assert not freqtrade.create_trades()
|
||||
assert log_has("No currency pair in whitelist, but checking to sell open trades.", caplog)
|
||||
assert log_has("No currency pair in active pair whitelist, "
|
||||
"but checking to sell open trades.", caplog)
|
||||
|
||||
|
||||
def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_order, fee,
|
||||
@@ -674,7 +675,7 @@ def test_create_trades_no_pairs_in_whitelist(default_conf, ticker, limit_buy_ord
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert not freqtrade.create_trades()
|
||||
assert log_has("Whitelist is empty.", caplog)
|
||||
assert log_has("Active pair whitelist is empty.", caplog)
|
||||
|
||||
|
||||
def test_create_trades_no_signal(default_conf, fee, mocker) -> None:
|
||||
@@ -1057,8 +1058,9 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
trade.is_open = True
|
||||
trades = [trade]
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
assert trade.stoploss_order_id == '13434334'
|
||||
assert stoploss_limit.call_count == 1
|
||||
assert trade.is_open is True
|
||||
@@ -1447,7 +1449,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
||||
# setting stoploss
|
||||
freqtrade.strategy.stoploss = -0.02
|
||||
|
||||
# setting stoploss_on_exchange_interval to 0 second
|
||||
# setting stoploss_on_exchange_interval to 0 seconds
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
|
||||
|
||||
patch_get_signal(freqtrade)
|
||||
@@ -1518,26 +1520,26 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
||||
stop_price=0.00002344 * 0.99)
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(mocker, default_conf, caplog) -> None:
|
||||
def test_process_maybe_execute_buys(mocker, default_conf, caplog) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trades', MagicMock(return_value=False))
|
||||
freqtrade.process_maybe_execute_buy()
|
||||
freqtrade.process_maybe_execute_buys()
|
||||
assert log_has('Found no buy signals for whitelisted currencies. Trying again...', caplog)
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None:
|
||||
def test_process_maybe_execute_buys_exception(mocker, default_conf, caplog) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.FreqtradeBot.create_trades',
|
||||
MagicMock(side_effect=DependencyException)
|
||||
)
|
||||
freqtrade.process_maybe_execute_buy()
|
||||
freqtrade.process_maybe_execute_buys()
|
||||
assert log_has('Unable to create trade: ', caplog)
|
||||
|
||||
|
||||
def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||
def test_process_maybe_execute_sells(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
@@ -1549,7 +1551,8 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
trade.open_fee = 0.001
|
||||
assert not freqtrade.process_maybe_execute_sell(trade)
|
||||
trades = [trade]
|
||||
assert not freqtrade.process_maybe_execute_sells(trades)
|
||||
# Test amount not modified by fee-logic
|
||||
assert not log_has(
|
||||
'Applying fee to amount for Trade {} from 90.99181073 to 90.81'.format(trade), caplog
|
||||
@@ -1557,24 +1560,25 @@ def test_process_maybe_execute_sell(mocker, default_conf, limit_buy_order, caplo
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount', return_value=90.81)
|
||||
# test amount modified by fee-logic
|
||||
assert not freqtrade.process_maybe_execute_sell(trade)
|
||||
assert not freqtrade.process_maybe_execute_sells(trades)
|
||||
|
||||
|
||||
def test_process_maybe_execute_sell_exception(mocker, default_conf,
|
||||
limit_buy_order, caplog) -> None:
|
||||
def test_process_maybe_execute_sells_exception(mocker, default_conf,
|
||||
limit_buy_order, caplog) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
||||
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
trade.open_fee = 0.001
|
||||
trades = [trade]
|
||||
|
||||
# Test raise of DependencyException exception
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.FreqtradeBot.update_trade_state',
|
||||
side_effect=DependencyException()
|
||||
)
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
assert log_has('Unable to sell trade: ', caplog)
|
||||
|
||||
|
||||
@@ -1674,7 +1678,7 @@ def test_update_trade_state_exception(mocker, default_conf,
|
||||
# Test raise of OperationalException exception
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
side_effect=OperationalException()
|
||||
side_effect=DependencyException()
|
||||
)
|
||||
freqtrade.update_trade_state(trade)
|
||||
assert log_has('Could not update trade amount: ', caplog)
|
||||
@@ -1768,8 +1772,6 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
|
||||
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
|
||||
fee, markets, mocker) -> None:
|
||||
default_conf.update({'experimental': {'use_sell_signal': True}})
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -1824,7 +1826,6 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
|
||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
|
||||
fee, mocker, markets, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf.update({'experimental': {'use_sell_signal': True}})
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
@@ -1856,10 +1857,10 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_trade_experimental(
|
||||
def test_handle_trade_use_sell_signal(
|
||||
default_conf, ticker, limit_buy_order, fee, mocker, markets, caplog) -> None:
|
||||
# use_sell_signal is True buy default
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf.update({'experimental': {'use_sell_signal': True}})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@@ -1911,11 +1912,12 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
|
||||
trade.update(limit_sell_order)
|
||||
assert trade.is_open is False
|
||||
|
||||
with pytest.raises(ValueError, match=r'.*closed trade.*'):
|
||||
with pytest.raises(DependencyException, match=r'.*closed trade.*'):
|
||||
freqtrade.handle_trade(trade)
|
||||
|
||||
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fee, mocker) -> None:
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, open_trade,
|
||||
fee, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
patch_exchange(mocker)
|
||||
@@ -1928,31 +1930,18 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
|
||||
|
||||
def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old,
|
||||
def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, open_trade,
|
||||
fee, mocker, caplog) -> None:
|
||||
""" Handle Buy order cancelled on exchange"""
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@@ -1968,32 +1957,19 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 0
|
||||
assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old,
|
||||
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
|
||||
fee, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -2008,31 +1984,19 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 0
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
nb_trades = len(trades)
|
||||
assert nb_trades == 1
|
||||
|
||||
|
||||
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker) -> None:
|
||||
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker,
|
||||
open_trade) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
patch_exchange(mocker)
|
||||
@@ -2044,30 +2008,20 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_sell = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(hours=-5).datetime,
|
||||
close_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=False
|
||||
)
|
||||
open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
|
||||
open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
|
||||
open_trade.is_open = False
|
||||
|
||||
Trade.session.add(trade_sell)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel sell orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
assert trade_sell.is_open is True
|
||||
assert open_trade.is_open is True
|
||||
|
||||
|
||||
def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
||||
def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old, open_trade,
|
||||
mocker, caplog) -> None:
|
||||
""" Handle sell order cancelled on exchange"""
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@@ -2082,34 +2036,24 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_sell = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(hours=-5).datetime,
|
||||
close_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=False
|
||||
)
|
||||
open_trade.open_date = arrow.utcnow().shift(hours=-5).datetime
|
||||
open_trade.close_date = arrow.utcnow().shift(minutes=-601).datetime
|
||||
open_trade.is_open = False
|
||||
|
||||
Trade.session.add(trade_sell)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel sell orders over the time limit
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert rpc_mock.call_count == 1
|
||||
assert trade_sell.is_open is True
|
||||
assert open_trade.is_open is True
|
||||
assert log_has_re("Sell order canceled on exchange for Trade.*", caplog)
|
||||
|
||||
|
||||
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
||||
mocker) -> None:
|
||||
open_trade, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -2119,33 +2063,97 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=arrow.utcnow().shift(minutes=-601).datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
# check it does cancel buy orders over the time limit
|
||||
# note this is for a partially-complete buy order
|
||||
freqtrade.check_handle_timedout()
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
assert trades[0].amount == 23.0
|
||||
assert trades[0].stake_amount == trade_buy.open_rate * trades[0].amount
|
||||
assert trades[0].stake_amount == open_trade.open_rate * trades[0].amount
|
||||
|
||||
|
||||
def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -> None:
|
||||
def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, caplog, fee,
|
||||
limit_buy_order_old_partial, trades_for_order,
|
||||
limit_buy_order_old_partial_canceled, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_order=MagicMock(return_value=limit_buy_order_old_partial),
|
||||
cancel_order=cancel_order_mock,
|
||||
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
assert open_trade.amount == limit_buy_order_old_partial['amount']
|
||||
|
||||
open_trade.fee_open = fee()
|
||||
open_trade.fee_close = fee()
|
||||
Trade.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
|
||||
assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
|
||||
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that tradehas been updated
|
||||
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
|
||||
limit_buy_order_old_partial['remaining']) - 0.0001
|
||||
assert trades[0].open_order_id is None
|
||||
assert trades[0].fee_open == 0
|
||||
|
||||
|
||||
def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, caplog, fee,
|
||||
limit_buy_order_old_partial, trades_for_order,
|
||||
limit_buy_order_old_partial_canceled, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order_old_partial_canceled)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_order=MagicMock(return_value=limit_buy_order_old_partial),
|
||||
cancel_order=cancel_order_mock,
|
||||
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
MagicMock(side_effect=DependencyException))
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
assert open_trade.amount == limit_buy_order_old_partial['amount']
|
||||
|
||||
open_trade.fee_open = fee()
|
||||
open_trade.fee_close = fee()
|
||||
Trade.session.add(open_trade)
|
||||
# cancelling a half-filled order should update the amount to the bought amount
|
||||
# and apply fees if necessary.
|
||||
freqtrade.check_handle_timedout()
|
||||
|
||||
assert log_has_re(r"Could not update trade amount: .*", caplog)
|
||||
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert rpc_mock.call_count == 1
|
||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||
assert len(trades) == 1
|
||||
# Verify that tradehas been updated
|
||||
|
||||
assert trades[0].amount == (limit_buy_order_old_partial['amount'] -
|
||||
limit_buy_order_old_partial['remaining'])
|
||||
assert trades[0].open_order_id is None
|
||||
assert trades[0].fee_open == fee()
|
||||
|
||||
|
||||
def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocker, caplog) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -2163,34 +2171,20 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
open_date = arrow.utcnow().shift(minutes=-601)
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee_open=0.0,
|
||||
fee_close=0.0,
|
||||
stake_amount=1,
|
||||
open_date=open_date.datetime,
|
||||
is_open=True
|
||||
)
|
||||
|
||||
Trade.session.add(trade_buy)
|
||||
Trade.session.add(open_trade)
|
||||
|
||||
freqtrade.check_handle_timedout()
|
||||
assert log_has_re(r"Cannot query order for Trade\(id=1, pair=ETH/BTC, amount=90.99181073, "
|
||||
r"open_rate=0.00001099, open_since="
|
||||
f"{open_date.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
f"{open_trade.open_date.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
r"\) due to Traceback \(most recent call last\):\n*",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
|
||||
def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
cancel_order_mock = MagicMock(return_value=limit_buy_order)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
cancel_order=cancel_order_mock
|
||||
@@ -2200,13 +2194,14 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
|
||||
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
order = {'remaining': 1,
|
||||
'amount': 1}
|
||||
assert freqtrade.handle_timedout_limit_buy(trade, order)
|
||||
limit_buy_order['remaining'] = limit_buy_order['amount']
|
||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['amount'] = 2
|
||||
assert not freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
order['amount'] = 2
|
||||
assert not freqtrade.handle_timedout_limit_buy(trade, order)
|
||||
assert cancel_order_mock.call_count == 2
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
@@ -2418,13 +2413,6 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf,
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
stoploss_limit = MagicMock(return_value={
|
||||
'id': 123,
|
||||
'info': {
|
||||
@@ -2433,11 +2421,16 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf,
|
||||
})
|
||||
|
||||
cancel_order = MagicMock(return_value=True)
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order', cancel_order)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
symbol_amount_prec=lambda s, x, y: y,
|
||||
symbol_price_prec=lambda s, x, y: y,
|
||||
stoploss_limit=stoploss_limit,
|
||||
cancel_order=cancel_order,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
@@ -2448,8 +2441,9 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf,
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
trades = [trade]
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
|
||||
# Increase the price and sell it
|
||||
mocker.patch.multiple(
|
||||
@@ -2477,7 +2471,9 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
markets=PropertyMock(return_value=markets),
|
||||
symbol_amount_prec=lambda s, x, y: y,
|
||||
symbol_price_prec=lambda s, x, y: y,
|
||||
)
|
||||
|
||||
stoploss_limit = MagicMock(return_value={
|
||||
@@ -2487,8 +2483,6 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
|
||||
}
|
||||
})
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@@ -2498,7 +2492,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
|
||||
# Create some test data
|
||||
freqtrade.create_trades()
|
||||
trade = Trade.query.first()
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
trades = [trade]
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
assert trade
|
||||
assert trade.stoploss_order_id == '123'
|
||||
assert trade.open_order_id is None
|
||||
@@ -2526,13 +2521,122 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed)
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
assert rpc_mock.call_count == 2
|
||||
|
||||
|
||||
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf,
|
||||
ticker, fee,
|
||||
limit_buy_order,
|
||||
markets, mocker) -> None:
|
||||
"""
|
||||
Tests workflow of selling stoploss_on_exchange.
|
||||
Sells
|
||||
* first trade as stoploss
|
||||
* 2nd trade is kept
|
||||
* 3rd trade is sold via sell-signal
|
||||
"""
|
||||
default_conf['max_open_trades'] = 3
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
|
||||
stoploss_limit = {
|
||||
'id': 123,
|
||||
'info': {}
|
||||
}
|
||||
stoploss_order_open = {
|
||||
"id": "123",
|
||||
"timestamp": 1542707426845,
|
||||
"datetime": "2018-11-20T09:50:26.845Z",
|
||||
"lastTradeTimestamp": None,
|
||||
"symbol": "BTC/USDT",
|
||||
"type": "stop_loss_limit",
|
||||
"side": "sell",
|
||||
"price": 1.08801,
|
||||
"amount": 90.99181074,
|
||||
"cost": 0.0,
|
||||
"average": 0.0,
|
||||
"filled": 0.0,
|
||||
"remaining": 0.0,
|
||||
"status": "open",
|
||||
"fee": None,
|
||||
"trades": None
|
||||
}
|
||||
stoploss_order_closed = stoploss_order_open.copy()
|
||||
stoploss_order_closed['status'] = 'closed'
|
||||
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
|
||||
stoploss_order_mock = MagicMock(
|
||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||
# Sell 3rd trade (not called for the first trade)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
|
||||
SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)]
|
||||
)
|
||||
cancel_order_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Binance.stoploss_limit', stoploss_limit)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
symbol_amount_prec=lambda s, x, y: y,
|
||||
symbol_price_prec=lambda s, x, y: y,
|
||||
get_order=stoploss_order_mock,
|
||||
cancel_order=cancel_order_mock,
|
||||
)
|
||||
|
||||
wallets_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
update_trade_state=MagicMock(),
|
||||
_notify_sell=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
mocker.patch("freqtrade.wallets.Wallets.update", wallets_mock)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
# Switch ordertype to market to close trade immediately
|
||||
freqtrade.strategy.order_types['sell'] = 'market'
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# Create some test data
|
||||
freqtrade.create_trades()
|
||||
wallets_mock.reset_mock()
|
||||
Trade.session = MagicMock()
|
||||
|
||||
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
|
||||
trade.open_order_id = None
|
||||
|
||||
freqtrade.process_maybe_execute_sells(trades)
|
||||
assert should_sell_mock.call_count == 2
|
||||
|
||||
# Only order for 3rd trade needs to be cancelled
|
||||
assert cancel_order_mock.call_count == 1
|
||||
# Wallets should only be called once per sell cycle
|
||||
assert wallets_mock.call_count == 1
|
||||
|
||||
trade = trades[0]
|
||||
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
assert not trade.is_open
|
||||
|
||||
trade = trades[1]
|
||||
assert not trade.sell_reason
|
||||
assert trade.is_open
|
||||
|
||||
trade = trades[2]
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
assert not trade.is_open
|
||||
|
||||
|
||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
ticker_sell_up, markets, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@@ -2600,7 +2704,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'use_sell_signal': True,
|
||||
'sell_profit_only': True,
|
||||
}
|
||||
@@ -2632,7 +2736,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'use_sell_signal': True,
|
||||
'sell_profit_only': False,
|
||||
}
|
||||
@@ -2662,7 +2766,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'use_sell_signal': True,
|
||||
'sell_profit_only': True,
|
||||
}
|
||||
@@ -2692,7 +2796,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'use_sell_signal': True,
|
||||
'sell_profit_only': False,
|
||||
}
|
||||
@@ -2761,7 +2865,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'ignore_roi_if_buy_signal': True
|
||||
}
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@@ -3029,7 +3133,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
default_conf['experimental'] = {
|
||||
default_conf['ask_strategy'] = {
|
||||
'ignore_roi_if_buy_signal': False
|
||||
}
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@@ -3251,7 +3355,7 @@ def test_get_real_amount_wrong_amount(default_conf, trades_for_order, buy_order_
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# Amount does not change
|
||||
with pytest.raises(OperationalException, match=r"Half bought\? Amounts don't match"):
|
||||
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
|
||||
freqtrade.get_real_amount(trade, limit_buy_order)
|
||||
|
||||
|
||||
|
@@ -1,15 +0,0 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from freqtrade.indicator_helpers import went_down, went_up
|
||||
|
||||
|
||||
def test_went_up():
|
||||
series = pd.Series([1, 2, 3, 1])
|
||||
assert went_up(series).equals(pd.Series([False, True, True, False]))
|
||||
|
||||
|
||||
def test_went_down():
|
||||
series = pd.Series([1, 2, 3, 1])
|
||||
assert went_down(series).equals(pd.Series([False, False, False, True]))
|
@@ -53,11 +53,11 @@ def test_init_plotscript(default_conf, mocker, testdatadir):
|
||||
assert "trades" in ret
|
||||
assert "pairs" in ret
|
||||
|
||||
default_conf['pairs'] = ["POWR/BTC", "XLM/BTC"]
|
||||
default_conf['pairs'] = ["POWR/BTC", "ADA/BTC"]
|
||||
ret = init_plotscript(default_conf)
|
||||
assert "tickers" in ret
|
||||
assert "POWR/BTC" in ret["tickers"]
|
||||
assert "XLM/BTC" in ret["tickers"]
|
||||
assert "ADA/BTC" in ret["tickers"]
|
||||
|
||||
|
||||
def test_add_indicators(default_conf, testdatadir, caplog):
|
||||
@@ -197,8 +197,7 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
||||
# All buy-signals should be plotted
|
||||
assert int(data.sell.sum()) == len(sell.x)
|
||||
|
||||
assert find_trace_in_fig_data(figure.data, "BB lower")
|
||||
assert find_trace_in_fig_data(figure.data, "BB upper")
|
||||
assert find_trace_in_fig_data(figure.data, "Bollinger Band")
|
||||
|
||||
assert row_mock.call_count == 2
|
||||
assert trades_mock.call_count == 1
|
||||
@@ -215,11 +214,12 @@ def test_generate_plot_file(mocker, caplog):
|
||||
store_plot_file(fig, filename="freqtrade-plot-UNITTEST_BTC-5m.html",
|
||||
directory=Path("user_data/plots"))
|
||||
|
||||
expected_fn = str(Path("user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html"))
|
||||
assert plot_mock.call_count == 1
|
||||
assert plot_mock.call_args[0][0] == fig
|
||||
assert (plot_mock.call_args_list[0][1]['filename']
|
||||
== "user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html")
|
||||
assert log_has("Stored plot as user_data/plots/freqtrade-plot-UNITTEST_BTC-5m.html",
|
||||
== expected_fn)
|
||||
assert log_has(f"Stored plot as {expected_fn}",
|
||||
caplog)
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ def test_add_profit(testdatadir):
|
||||
fig1 = add_profit(fig, row=2, data=cum_profits, column='cum_profits', name='Profits')
|
||||
figure = fig1.layout.figure
|
||||
profits = find_trace_in_fig_data(figure.data, "Profits")
|
||||
assert isinstance(profits, go.Scattergl)
|
||||
assert isinstance(profits, go.Scatter)
|
||||
assert profits.yaxis == "y2"
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ def test_generate_profit_graph(testdatadir):
|
||||
filename = testdatadir / "backtest-result_test.json"
|
||||
trades = load_backtest_data(filename)
|
||||
timerange = TimeRange.parse_timerange("20180110-20180112")
|
||||
pairs = ["POWR/BTC", "XLM/BTC"]
|
||||
pairs = ["POWR/BTC", "ADA/BTC"]
|
||||
|
||||
tickers = history.load_data(datadir=testdatadir,
|
||||
pairs=pairs,
|
||||
@@ -268,14 +268,14 @@ def test_generate_profit_graph(testdatadir):
|
||||
assert len(figure.data) == 4
|
||||
|
||||
avgclose = find_trace_in_fig_data(figure.data, "Avg close price")
|
||||
assert isinstance(avgclose, go.Scattergl)
|
||||
assert isinstance(avgclose, go.Scatter)
|
||||
|
||||
profit = find_trace_in_fig_data(figure.data, "Profit")
|
||||
assert isinstance(profit, go.Scattergl)
|
||||
assert isinstance(profit, go.Scatter)
|
||||
|
||||
for pair in pairs:
|
||||
profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}")
|
||||
assert isinstance(profit_pair, go.Scattergl)
|
||||
assert isinstance(profit_pair, go.Scatter)
|
||||
|
||||
|
||||
def test_start_plot_dataframe(mocker):
|
||||
|
@@ -5,9 +5,6 @@ from freqtrade.configuration import TimeRange
|
||||
|
||||
|
||||
def test_parse_timerange_incorrect() -> None:
|
||||
assert TimeRange(None, 'line', 0, -200) == TimeRange.parse_timerange('-200')
|
||||
assert TimeRange('line', None, 200, 0) == TimeRange.parse_timerange('200-')
|
||||
assert TimeRange('index', 'index', 200, 500) == TimeRange.parse_timerange('200-500')
|
||||
|
||||
assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-')
|
||||
assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522')
|
||||
@@ -20,9 +17,14 @@ def test_parse_timerange_incorrect() -> None:
|
||||
timerange = TimeRange.parse_timerange('1231006505-1233360000')
|
||||
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
||||
|
||||
# TODO: Find solution for the following case (passing timestamp in ms)
|
||||
timerange = TimeRange.parse_timerange('1231006505000-1233360000000')
|
||||
assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange
|
||||
assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange
|
||||
|
||||
timerange = TimeRange.parse_timerange('1231006505000-')
|
||||
assert TimeRange('date', None, 1231006505, 0) == timerange
|
||||
|
||||
timerange = TimeRange.parse_timerange('-1231006505000')
|
||||
assert TimeRange(None, 'date', 0, 1231006505) == timerange
|
||||
|
||||
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
|
||||
TimeRange.parse_timerange('-')
|
||||
|
@@ -7,7 +7,8 @@ import pytest
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.state import RunMode
|
||||
from freqtrade.utils import (setup_utils_configuration, start_create_userdir,
|
||||
start_download_data, start_list_exchanges)
|
||||
start_download_data, start_list_exchanges,
|
||||
start_list_timeframes)
|
||||
from tests.conftest import get_args, log_has, patch_exchange
|
||||
|
||||
|
||||
@@ -31,7 +32,7 @@ def test_list_exchanges(capsys):
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"Exchanges supported by ccxt and available.*", captured.out)
|
||||
assert re.match(r"Exchanges available for Freqtrade.*", captured.out)
|
||||
assert re.match(r".*binance,.*", captured.out)
|
||||
assert re.match(r".*bittrex,.*", captured.out)
|
||||
|
||||
@@ -43,10 +44,125 @@ def test_list_exchanges(capsys):
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert not re.match(r"Exchanges supported by ccxt and available.*", captured.out)
|
||||
assert re.search(r"^binance$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
|
||||
|
||||
# Test with --all
|
||||
args = [
|
||||
"list-exchanges",
|
||||
"--all",
|
||||
]
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match(r"All exchanges supported by the ccxt library.*", captured.out)
|
||||
assert re.match(r".*binance,.*", captured.out)
|
||||
assert re.match(r".*bittrex,.*", captured.out)
|
||||
assert re.match(r".*bitmex,.*", captured.out)
|
||||
|
||||
# Test with --one-column --all
|
||||
args = [
|
||||
"list-exchanges",
|
||||
"--one-column",
|
||||
"--all",
|
||||
]
|
||||
|
||||
start_list_exchanges(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^binance$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bittrex$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^bitmex$", captured.out, re.MULTILINE)
|
||||
|
||||
|
||||
def test_list_timeframes(mocker, capsys):
|
||||
|
||||
api_mock = MagicMock()
|
||||
api_mock.timeframes = {'1m': 'oneMin',
|
||||
'5m': 'fiveMin',
|
||||
'30m': 'thirtyMin',
|
||||
'1h': 'hour',
|
||||
'1d': 'day',
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock)
|
||||
args = [
|
||||
"list-timeframes",
|
||||
]
|
||||
pargs = get_args(args)
|
||||
pargs['config'] = None
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"This command requires a configured exchange.*"):
|
||||
start_list_timeframes(pargs)
|
||||
|
||||
# Test with --config config.json.example
|
||||
args = [
|
||||
'--config', 'config.json.example',
|
||||
"list-timeframes",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match("Timeframes available for the exchange `bittrex`: "
|
||||
"1m, 5m, 30m, 1h, 1d",
|
||||
captured.out)
|
||||
|
||||
# Test with --exchange bittrex
|
||||
args = [
|
||||
"list-timeframes",
|
||||
"--exchange", "bittrex",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match("Timeframes available for the exchange `bittrex`: "
|
||||
"1m, 5m, 30m, 1h, 1d",
|
||||
captured.out)
|
||||
|
||||
api_mock.timeframes = {'1m': '1m',
|
||||
'5m': '5m',
|
||||
'15m': '15m',
|
||||
'30m': '30m',
|
||||
'1h': '1h',
|
||||
'6h': '6h',
|
||||
'12h': '12h',
|
||||
'1d': '1d',
|
||||
'3d': '3d',
|
||||
}
|
||||
patch_exchange(mocker, api_mock=api_mock)
|
||||
# Test with --exchange binance
|
||||
args = [
|
||||
"list-timeframes",
|
||||
"--exchange", "binance",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.match("Timeframes available for the exchange `binance`: "
|
||||
"1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 3d",
|
||||
captured.out)
|
||||
|
||||
# Test with --one-column
|
||||
args = [
|
||||
'--config', 'config.json.example',
|
||||
"list-timeframes",
|
||||
"--one-column",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^1m$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^5m$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^1h$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^1d$", captured.out, re.MULTILINE)
|
||||
|
||||
# Test with --exchange binance --one-column
|
||||
args = [
|
||||
"list-timeframes",
|
||||
"--exchange", "binance",
|
||||
"--one-column",
|
||||
]
|
||||
start_list_timeframes(get_args(args))
|
||||
captured = capsys.readouterr()
|
||||
assert re.search(r"^1m$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^5m$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^1h$", captured.out, re.MULTILINE)
|
||||
assert re.search(r"^1d$", captured.out, re.MULTILINE)
|
||||
|
||||
|
||||
def test_create_datadir_failed(caplog):
|
||||
|
||||
@@ -144,3 +260,25 @@ def test_download_data_no_pairs(mocker, caplog):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"Downloading data requires a list of pairs\..*"):
|
||||
start_download_data(pargs)
|
||||
|
||||
|
||||
def test_download_data_trades(mocker, caplog):
|
||||
dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_trades_data',
|
||||
MagicMock(return_value=[]))
|
||||
convert_mock = mocker.patch('freqtrade.utils.convert_trades_to_ohlcv',
|
||||
MagicMock(return_value=[]))
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={})
|
||||
)
|
||||
args = [
|
||||
"download-data",
|
||||
"--exchange", "kraken",
|
||||
"--pairs", "ETH/BTC", "XRP/BTC",
|
||||
"--days", "20",
|
||||
"--dl-trades"
|
||||
]
|
||||
start_download_data(get_args(args))
|
||||
assert dl_mock.call_args[1]['timerange'].starttype == "date"
|
||||
assert dl_mock.call_count == 1
|
||||
assert convert_mock.call_count == 1
|
||||
|
1
tests/testdata/ADA_BTC-1m.json
vendored
1
tests/testdata/ADA_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/DASH_BTC-1m.json
vendored
1
tests/testdata/DASH_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/ETC_BTC-1m.json
vendored
1
tests/testdata/ETC_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/ETH_BTC-1m.json
vendored
1
tests/testdata/ETH_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/LTC_BTC-1m.json
vendored
1
tests/testdata/LTC_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/NXT_BTC-1m.json
vendored
1
tests/testdata/NXT_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/POWR_BTC-1m.json
vendored
1
tests/testdata/POWR_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/XLM_BTC-1m.json
vendored
1
tests/testdata/XLM_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/XMR_BTC-1m.json
vendored
1
tests/testdata/XMR_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
1
tests/testdata/XRP_ETH-1m.json
vendored
Normal file
1
tests/testdata/XRP_ETH-1m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
tests/testdata/XRP_ETH-5m.json
vendored
Normal file
1
tests/testdata/XRP_ETH-5m.json
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
tests/testdata/XRP_ETH-trades.json.gz
vendored
Normal file
BIN
tests/testdata/XRP_ETH-trades.json.gz
vendored
Normal file
Binary file not shown.
1
tests/testdata/ZEC_BTC-1m.json
vendored
1
tests/testdata/ZEC_BTC-1m.json
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user