Merge branch 'develop' into fix/backtest_toomanyopen
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from typing import Dict, Optional
|
||||
from collections import namedtuple
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
import arrow
|
||||
@@ -12,6 +13,7 @@ from telegram import Chat, Message, Update
|
||||
|
||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.edge import Edge
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
|
||||
logging.getLogger('').setLevel(logging.INFO)
|
||||
@@ -28,6 +30,7 @@ def log_has(line, logs):
|
||||
def patch_exchange(mocker, api_mock=None) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value="Bittrex"))
|
||||
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value="bittrex"))
|
||||
if api_mock:
|
||||
@@ -42,7 +45,32 @@ def get_patched_exchange(mocker, config, api_mock=None) -> Exchange:
|
||||
return exchange
|
||||
|
||||
|
||||
def patch_edge(mocker) -> None:
|
||||
# "ETH/BTC",
|
||||
# "LTC/BTC",
|
||||
# "XRP/BTC",
|
||||
# "NEO/BTC"
|
||||
pair_info = namedtuple(
|
||||
'pair_info',
|
||||
'stoploss, winrate, risk_reward_ratio, required_risk_reward, expectancy')
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={
|
||||
'NEO/BTC': pair_info(-0.20, 0.66, 3.71, 0.50, 1.71),
|
||||
'LTC/BTC': pair_info(-0.21, 0.66, 3.71, 0.50, 1.71),
|
||||
}
|
||||
))
|
||||
mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20))
|
||||
mocker.patch('freqtrade.edge.Edge.calculate', MagicMock(return_value=True))
|
||||
|
||||
|
||||
def get_patched_edge(mocker, config) -> Edge:
|
||||
patch_edge(mocker)
|
||||
edge = Edge(config)
|
||||
return edge
|
||||
|
||||
# Functions for recurrent object patching
|
||||
|
||||
|
||||
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
|
||||
"""
|
||||
This function patch _init_modules() to not call dependencies
|
||||
@@ -752,3 +780,23 @@ def buy_order_fee():
|
||||
'status': 'closed',
|
||||
'fee': None
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def edge_conf(default_conf):
|
||||
default_conf['edge'] = {
|
||||
"enabled": True,
|
||||
"process_throttle_secs": 1800,
|
||||
"calculate_since_number_of_days": 14,
|
||||
"allowed_risk": 0.01,
|
||||
"stoploss_range_min": -0.01,
|
||||
"stoploss_range_max": -0.1,
|
||||
"stoploss_range_step": -0.01,
|
||||
"maximum_winrate": 0.80,
|
||||
"minimum_expectancy": 0.20,
|
||||
"min_trade_number": 15,
|
||||
"max_trade_duration_minute": 1440,
|
||||
"remove_pumps": False
|
||||
}
|
||||
|
||||
return default_conf
|
||||
|
0
freqtrade/tests/edge/__init__.py
Normal file
0
freqtrade/tests/edge/__init__.py
Normal file
310
freqtrade/tests/edge/test_edge.py
Normal file
310
freqtrade/tests/edge/test_edge.py
Normal file
@@ -0,0 +1,310 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, C0330
|
||||
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
||||
|
||||
import pytest
|
||||
import logging
|
||||
from freqtrade.tests.conftest import get_patched_freqtradebot
|
||||
from freqtrade.edge import Edge, PairInfo
|
||||
from pandas import DataFrame, to_datetime
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
|
||||
_get_frame_time_from_offset)
|
||||
import arrow
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Cases to be tested:
|
||||
# 1) Open trade should be removed from the end
|
||||
# 2) Two complete trades within dataframe (with sell hit for all)
|
||||
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
||||
# 4) Entered, sl 3%, candle drops 4%, recovers to 1% => Trade closed, 3% loss
|
||||
# 5) Stoploss and sell are hit. should sell on stoploss
|
||||
####################################################################
|
||||
|
||||
ticker_start_time = arrow.get(2018, 10, 3)
|
||||
ticker_interval_in_minute = 60
|
||||
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
||||
|
||||
|
||||
# Open trade should be removed from the end
|
||||
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,
|
||||
trades=[]
|
||||
)
|
||||
|
||||
# Two complete trades within dataframe(with sell hit for all)
|
||||
tc1 = 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)
|
||||
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open
|
||||
[3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action
|
||||
[4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade
|
||||
[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,
|
||||
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)]
|
||||
)
|
||||
|
||||
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
|
||||
tc2 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[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,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
|
||||
tc3 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[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,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
# 5) Stoploss and sell are hit. should sell on stoploss
|
||||
tc4 = BTContainer(data=[
|
||||
# D O H L C V B S
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[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,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
tc2,
|
||||
tc3,
|
||||
tc4
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", TESTS)
|
||||
def test_edge_results(edge_conf, mocker, caplog, data) -> None:
|
||||
"""
|
||||
run functional tests
|
||||
"""
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
caplog.set_level(logging.DEBUG)
|
||||
edge.fee = 0
|
||||
|
||||
trades = edge._find_trades_for_stoploss_range(frame, 'TEST/BTC', [data.stop_loss])
|
||||
results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
|
||||
|
||||
print(results)
|
||||
|
||||
assert len(trades) == len(data.trades)
|
||||
|
||||
if not results.empty:
|
||||
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
|
||||
|
||||
for c, trade in enumerate(data.trades):
|
||||
res = results.iloc[c]
|
||||
assert res.exit_type == trade.sell_reason
|
||||
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
|
||||
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
|
||||
|
||||
|
||||
def test_adjust(mocker, default_conf):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={
|
||||
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
||||
}
|
||||
))
|
||||
|
||||
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
|
||||
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
|
||||
|
||||
|
||||
def test_stoploss(mocker, default_conf):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={
|
||||
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
|
||||
}
|
||||
))
|
||||
|
||||
assert edge.stoploss('E/F') == -0.01
|
||||
|
||||
|
||||
def _validate_ohlc(buy_ohlc_sell_matrice):
|
||||
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
||||
# if not high < open < low or not high < close < low
|
||||
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
|
||||
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
|
||||
return True
|
||||
|
||||
|
||||
def _build_dataframe(buy_ohlc_sell_matrice):
|
||||
_validate_ohlc(buy_ohlc_sell_matrice)
|
||||
tickers = []
|
||||
for ohlc in buy_ohlc_sell_matrice:
|
||||
ticker = {
|
||||
'date': ticker_start_time.shift(
|
||||
minutes=(
|
||||
ohlc[0] *
|
||||
ticker_interval_in_minute)).timestamp *
|
||||
1000,
|
||||
'buy': ohlc[1],
|
||||
'open': ohlc[2],
|
||||
'high': ohlc[3],
|
||||
'low': ohlc[4],
|
||||
'close': ohlc[5],
|
||||
'sell': ohlc[6]}
|
||||
tickers.append(ticker)
|
||||
|
||||
frame = DataFrame(tickers)
|
||||
frame['date'] = to_datetime(frame['date'],
|
||||
unit='ms',
|
||||
utc=True,
|
||||
infer_datetime_format=True)
|
||||
|
||||
return frame
|
||||
|
||||
|
||||
def _time_on_candle(number):
|
||||
return np.datetime64(ticker_start_time.shift(
|
||||
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
||||
|
||||
|
||||
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
heartbeat = edge_conf['edge']['process_throttle_secs']
|
||||
|
||||
# should not recalculate if heartbeat not reached
|
||||
edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1
|
||||
|
||||
assert edge.calculate() is False
|
||||
|
||||
|
||||
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
|
||||
timerange=None, exchange=None):
|
||||
hz = 0.1
|
||||
base = 0.001
|
||||
|
||||
ETHBTC = [
|
||||
[
|
||||
ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
123.45
|
||||
] for x in range(0, 500)]
|
||||
|
||||
hz = 0.2
|
||||
base = 0.002
|
||||
LTCBTC = [
|
||||
[
|
||||
ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
123.45
|
||||
] for x in range(0, 500)]
|
||||
|
||||
pairdata = {'NEO/BTC': ETHBTC, 'LTC/BTC': LTCBTC}
|
||||
return pairdata
|
||||
|
||||
|
||||
def test_edge_process_downloaded_data(mocker, default_conf):
|
||||
default_conf['datadir'] = None
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
||||
edge = Edge(default_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
|
||||
assert edge.calculate()
|
||||
assert len(edge._cached_pairs) == 2
|
||||
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
||||
|
||||
|
||||
def test_process_expectancy(mocker, edge_conf):
|
||||
edge_conf['edge']['min_trade_number'] = 2
|
||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||
|
||||
def get_fee():
|
||||
return 0.001
|
||||
|
||||
freqtrade.exchange.get_fee = get_fee
|
||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||
|
||||
trades = [
|
||||
{'pair': 'TEST/BTC',
|
||||
'stoploss': -0.9,
|
||||
'profit_percent': '',
|
||||
'profit_abs': '',
|
||||
'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
|
||||
'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
|
||||
'open_index': 1,
|
||||
'close_index': 1,
|
||||
'trade_duration': '',
|
||||
'open_rate': 17,
|
||||
'close_rate': 17,
|
||||
'exit_type': 'sell_signal'},
|
||||
|
||||
{'pair': 'TEST/BTC',
|
||||
'stoploss': -0.9,
|
||||
'profit_percent': '',
|
||||
'profit_abs': '',
|
||||
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
|
||||
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
|
||||
'open_index': 4,
|
||||
'close_index': 4,
|
||||
'trade_duration': '',
|
||||
'open_rate': 20,
|
||||
'close_rate': 20,
|
||||
'exit_type': 'sell_signal'},
|
||||
|
||||
{'pair': 'TEST/BTC',
|
||||
'stoploss': -0.9,
|
||||
'profit_percent': '',
|
||||
'profit_abs': '',
|
||||
'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
|
||||
'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
|
||||
'open_index': 6,
|
||||
'close_index': 7,
|
||||
'trade_duration': '',
|
||||
'open_rate': 26,
|
||||
'close_rate': 34,
|
||||
'exit_type': 'sell_signal'}
|
||||
]
|
||||
|
||||
trades_df = DataFrame(trades)
|
||||
trades_df = edge._fill_calculable_fields(trades_df)
|
||||
final = edge._process_expectancy(trades_df)
|
||||
assert len(final) == 1
|
||||
|
||||
assert 'TEST/BTC' in final
|
||||
assert final['TEST/BTC'].stoploss == -0.9
|
||||
assert round(final['TEST/BTC'].winrate, 10) == 0.3333333333
|
||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
@@ -355,6 +355,36 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_order_types(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
|
||||
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
|
||||
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_timeframes', MagicMock())
|
||||
default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'}
|
||||
Exchange(default_conf)
|
||||
|
||||
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
|
||||
default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_order_types_not_in_config(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
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_timeframes', MagicMock())
|
||||
|
||||
conf = copy.deepcopy(default_conf)
|
||||
Exchange(conf)
|
||||
|
||||
|
||||
def test_exchange_has(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
assert not exchange.exchange_has('ASDFASDF')
|
||||
@@ -373,7 +403,7 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_buy_' in order['id']
|
||||
|
||||
@@ -381,47 +411,64 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
def test_buy_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_buy_order = MagicMock(return_value={
|
||||
order_type = 'market'
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
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)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
|
||||
def test_sell_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_sell_' in order['id']
|
||||
|
||||
@@ -429,7 +476,8 @@ def test_sell_dry_run(default_conf, mocker):
|
||||
def test_sell_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_sell_order = MagicMock(return_value={
|
||||
order_type = 'market'
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
@@ -438,32 +486,48 @@ def test_sell_prod(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
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)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
|
||||
def test_get_balance_dry_run(default_conf, mocker):
|
||||
|
@@ -31,8 +31,8 @@ class BTContainer(NamedTuple):
|
||||
|
||||
|
||||
def _get_frame_time_from_offset(offset):
|
||||
return ticker_start_time.shift(
|
||||
minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])).datetime
|
||||
return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])
|
||||
).datetime.replace(tzinfo=None)
|
||||
|
||||
|
||||
def _build_backtest_dataframe(ticker_with_signals):
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -35,15 +35,15 @@ tc0 = BTContainer(data=[
|
||||
# TC2: Stop-Loss Triggered 3% Loss
|
||||
tc1 = 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], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4962, 4975, 6172, 0, 0],
|
||||
[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]],
|
||||
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
|
||||
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4987, 5012, 4962, 4975, 6172, 0, 0],
|
||||
[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,
|
||||
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Test 3 Candle drops 4%, Recovers 1%.
|
||||
@@ -128,7 +128,7 @@ tc6 = BTContainer(data=[
|
||||
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
|
||||
stop_loss=-0.02, roi=0.03, profit_perc=0.03,
|
||||
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
|
||||
)
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
|
@@ -638,6 +638,7 @@ def test_backtest_only_sell(mocker, default_conf):
|
||||
|
||||
def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
mocker.patch('freqtrade.optimize.backtesting.file_dump_json', MagicMock())
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
||||
# We need to enable sell-signal - otherwise it sells on ROI!!
|
||||
default_conf['experimental'] = {"use_sell_signal": True}
|
||||
|
131
freqtrade/tests/optimize/test_edge_cli.py
Normal file
131
freqtrade/tests/optimize/test_edge_cli.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, C0330
|
||||
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
import json
|
||||
from typing import List
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.optimize.edge_cli import (EdgeCli, setup_configuration, start)
|
||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||
|
||||
|
||||
def get_args(args) -> List[str]:
|
||||
return Arguments(args, '').get_parsed_arg()
|
||||
|
||||
|
||||
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
args = [
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'edge'
|
||||
]
|
||||
|
||||
config = setup_configuration(get_args(args))
|
||||
assert 'max_open_trades' in config
|
||||
assert 'stake_currency' in config
|
||||
assert 'stake_amount' in config
|
||||
assert 'exchange' in config
|
||||
assert 'pair_whitelist' in config['exchange']
|
||||
assert 'datadir' in config
|
||||
assert log_has(
|
||||
'Using data folder: {} ...'.format(config['datadir']),
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert 'ticker_interval' in config
|
||||
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||
|
||||
assert 'refresh_pairs' not in config
|
||||
assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||
|
||||
assert 'timerange' not in config
|
||||
assert 'stoploss_range' not in config
|
||||
|
||||
|
||||
def test_setup_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(edge_conf)
|
||||
))
|
||||
|
||||
args = [
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--datadir', '/foo/bar',
|
||||
'edge',
|
||||
'--ticker-interval', '1m',
|
||||
'--refresh-pairs-cached',
|
||||
'--timerange', ':100',
|
||||
'--stoplosses=-0.01,-0.10,-0.001'
|
||||
]
|
||||
|
||||
config = setup_configuration(get_args(args))
|
||||
assert 'max_open_trades' in config
|
||||
assert 'stake_currency' in config
|
||||
assert 'stake_amount' in config
|
||||
assert 'exchange' in config
|
||||
assert 'pair_whitelist' in config['exchange']
|
||||
assert 'datadir' in config
|
||||
assert log_has(
|
||||
'Using data folder: {} ...'.format(config['datadir']),
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert 'ticker_interval' in config
|
||||
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
|
||||
assert log_has(
|
||||
'Using ticker_interval: 1m ...',
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
assert 'refresh_pairs' in config
|
||||
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||
assert 'timerange' in config
|
||||
assert log_has(
|
||||
'Parameter --timerange detected: {} ...'.format(config['timerange']),
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
|
||||
def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||
start_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock)
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(edge_conf)
|
||||
))
|
||||
args = [
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'edge'
|
||||
]
|
||||
args = get_args(args)
|
||||
start(args)
|
||||
assert log_has(
|
||||
'Starting freqtrade in Edge mode',
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
|
||||
def test_edge_init(mocker, edge_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
edge_cli = EdgeCli(edge_conf)
|
||||
assert edge_cli.config == edge_conf
|
||||
assert callable(edge_cli.edge.calculate)
|
||||
|
||||
|
||||
def test_generate_edge_table(edge_conf, mocker):
|
||||
patch_exchange(mocker)
|
||||
edge_cli = EdgeCli(edge_conf)
|
||||
|
||||
results = {}
|
||||
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
|
||||
|
||||
assert edge_cli._generate_edge_table(results).count(':|') == 7
|
||||
assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1
|
||||
assert edge_cli._generate_edge_table(results).count(
|
||||
'| risk reward ratio | required risk reward | expectancy |') == 1
|
@@ -176,7 +176,7 @@ def test_roi_table_generation(hyperopt) -> None:
|
||||
'roi_p3': 3,
|
||||
}
|
||||
|
||||
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||
|
||||
|
||||
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
|
||||
@@ -244,7 +244,8 @@ def test_populate_indicators(hyperopt) -> None:
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
|
||||
# Check if some indicators are generated. We will not test all of them
|
||||
assert 'adx' in dataframe
|
||||
@@ -256,9 +257,10 @@ def test_buy_strategy_generator(hyperopt) -> None:
|
||||
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
|
||||
tickerlist = {'UNITTEST/BTC': tick}
|
||||
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
|
||||
dataframe = hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
|
||||
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
|
||||
{'pair': 'UNITTEST/BTC'})
|
||||
|
||||
populate_buy_trend = hyperopt.buy_strategy_generator(
|
||||
populate_buy_trend = hyperopt.custom_hyperopt.buy_strategy_generator(
|
||||
{
|
||||
'adx-value': 20,
|
||||
'fastd-value': 20,
|
||||
|
@@ -645,3 +645,28 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||
pair = 'ETH/BTC'
|
||||
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
|
||||
rpc._rpc_forcebuy(pair, None)
|
||||
|
||||
|
||||
def test_rpc_whitelist(mocker, default_conf) -> None:
|
||||
patch_coinmarketcap(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_whitelist()
|
||||
assert ret['method'] == 'static'
|
||||
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
|
||||
patch_coinmarketcap(mocker)
|
||||
patch_exchange(mocker)
|
||||
default_conf['dynamic_whitelist'] = 4
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_whitelist()
|
||||
assert ret['method'] == 4
|
||||
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
|
||||
|
@@ -72,7 +72,8 @@ def test_init(default_conf, mocker, caplog) -> None:
|
||||
|
||||
message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \
|
||||
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \
|
||||
"['performance'], ['daily'], ['count'], ['reload_conf'], ['help'], ['version']]"
|
||||
"['performance'], ['daily'], ['count'], ['reload_conf'], " \
|
||||
"['whitelist'], ['help'], ['version']]"
|
||||
|
||||
assert log_has(message_str, caplog.record_tuples)
|
||||
|
||||
@@ -1006,6 +1007,43 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
|
||||
assert msg in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.telegram.Telegram',
|
||||
_init=MagicMock(),
|
||||
_send_msg=msg_mock
|
||||
)
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
telegram = Telegram(freqtradebot)
|
||||
|
||||
telegram._whitelist(bot=MagicMock(), update=update)
|
||||
assert msg_mock.call_count == 1
|
||||
assert ('Using static whitelist with `4` pairs \n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`'
|
||||
in msg_mock.call_args_list[0][0][0])
|
||||
|
||||
|
||||
def test_whitelist_dynamic(default_conf, update, mocker) -> None:
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.telegram.Telegram',
|
||||
_init=MagicMock(),
|
||||
_send_msg=msg_mock
|
||||
)
|
||||
default_conf['dynamic_whitelist'] = 4
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
telegram = Telegram(freqtradebot)
|
||||
|
||||
telegram._whitelist(bot=MagicMock(), update=update)
|
||||
assert msg_mock.call_count == 1
|
||||
assert ('Dynamic whitelist with `4` pairs\n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`'
|
||||
in msg_mock.call_args_list[0][0][0])
|
||||
|
||||
|
||||
def test_help_handle(default_conf, update, mocker) -> None:
|
||||
patch_coinmarketcap(mocker)
|
||||
msg_mock = MagicMock()
|
||||
|
@@ -88,8 +88,8 @@ def test_load_strategy_invalid_directory(result, caplog):
|
||||
def test_load_not_found_strategy():
|
||||
strategy = StrategyResolver()
|
||||
with pytest.raises(ImportError,
|
||||
match=r'Impossible to load Strategy \'NotFoundStrategy\'.'
|
||||
r' This class does not exist or contains Python code errors'):
|
||||
match=r"Impossible to load Strategy 'NotFoundStrategy'."
|
||||
r" This class does not exist or contains Python code errors"):
|
||||
strategy._load_strategy(strategy_name='NotFoundStrategy', config={})
|
||||
|
||||
|
||||
@@ -182,6 +182,42 @@ def test_strategy_override_process_only_new_candles(caplog):
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_strategy_override_order_types(caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
order_types = {
|
||||
'buy': 'market',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit'
|
||||
}
|
||||
|
||||
config = {
|
||||
'strategy': 'DefaultStrategy',
|
||||
'order_types': order_types
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert resolver.strategy.order_types
|
||||
for method in ['buy', 'sell', 'stoploss']:
|
||||
assert resolver.strategy.order_types[method] == order_types[method]
|
||||
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
"Override strategy 'order_types' with value in config file:"
|
||||
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}."
|
||||
) in caplog.record_tuples
|
||||
|
||||
config = {
|
||||
'strategy': 'DefaultStrategy',
|
||||
'order_types': {'buy': 'market'}
|
||||
}
|
||||
# Raise error for invalid configuration
|
||||
with pytest.raises(ImportError,
|
||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
||||
r"Order-types mapping is incomplete."):
|
||||
StrategyResolver(config)
|
||||
|
||||
|
||||
def test_deprecate_populate_indicators(result):
|
||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||
|
@@ -64,6 +64,22 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
|
||||
assert log_has('Validating configuration ...', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> None:
|
||||
default_conf['max_open_trades'] = -1
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
|
||||
args = Arguments([], '').get_parsed_arg()
|
||||
configuration = Configuration(args)
|
||||
validated_conf = configuration.load_config()
|
||||
print(validated_conf)
|
||||
|
||||
assert validated_conf['max_open_trades'] > 999999999
|
||||
assert validated_conf['max_open_trades'] == float('inf')
|
||||
assert log_has('Validating configuration ...', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_load_config_file_exception(mocker) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.configuration.open',
|
||||
|
@@ -18,7 +18,7 @@ from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc import RPCMessageType
|
||||
from freqtrade.state import State
|
||||
from freqtrade.strategy.interface import SellType, SellCheckTuple
|
||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||
from freqtrade.tests.conftest import log_has, patch_exchange, patch_edge
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
@@ -177,7 +177,7 @@ def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mock
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
result = freqtrade._get_trade_stake_amount()
|
||||
result = freqtrade._get_trade_stake_amount('ETH/BTC')
|
||||
assert result == default_conf['stake_amount']
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf,
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||
freqtrade._get_trade_stake_amount()
|
||||
freqtrade._get_trade_stake_amount('ETH/BTC')
|
||||
|
||||
|
||||
def test_get_trade_stake_amount_unlimited_amount(default_conf,
|
||||
@@ -224,28 +224,131 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# no open trades, order amount should be 'balance / max_open_trades'
|
||||
result = freqtrade._get_trade_stake_amount()
|
||||
result = freqtrade._get_trade_stake_amount('ETH/BTC')
|
||||
assert result == default_conf['stake_amount'] / conf['max_open_trades']
|
||||
|
||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||
freqtrade.create_trade()
|
||||
|
||||
result = freqtrade._get_trade_stake_amount()
|
||||
result = freqtrade._get_trade_stake_amount('LTC/BTC')
|
||||
assert result == default_conf['stake_amount'] / (conf['max_open_trades'] - 1)
|
||||
|
||||
# create 2 trades, order amount should be None
|
||||
freqtrade.create_trade()
|
||||
|
||||
result = freqtrade._get_trade_stake_amount()
|
||||
result = freqtrade._get_trade_stake_amount('XRP/BTC')
|
||||
assert result is None
|
||||
|
||||
# set max_open_trades = None, so do not trade
|
||||
conf['max_open_trades'] = 0
|
||||
freqtrade = FreqtradeBot(conf)
|
||||
result = freqtrade._get_trade_stake_amount()
|
||||
result = freqtrade._get_trade_stake_amount('NEO/BTC')
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_edge_called_in_process(mocker, edge_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_edge(mocker)
|
||||
|
||||
def _refresh_whitelist(list):
|
||||
return ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC']
|
||||
|
||||
patch_exchange(mocker)
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
freqtrade._refresh_whitelist = _refresh_whitelist
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade._process()
|
||||
assert freqtrade.active_pair_whitelist == ['NEO/BTC', 'LTC/BTC']
|
||||
|
||||
|
||||
def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_edge(mocker)
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
|
||||
assert freqtrade._get_trade_stake_amount('NEO/BTC') == (0.001 * 0.01) / 0.20
|
||||
assert freqtrade._get_trade_stake_amount('LTC/BTC') == (0.001 * 0.01) / 0.20
|
||||
|
||||
|
||||
def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, edge_conf) -> None:
|
||||
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_edge(mocker)
|
||||
|
||||
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
|
||||
# Thus, if price falls 21%, stoploss should be triggered
|
||||
#
|
||||
# mocking the ticker: price is falling ...
|
||||
buy_price = limit_buy_order['price']
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': buy_price * 0.79,
|
||||
'ask': buy_price * 0.79,
|
||||
'last': buy_price * 0.79
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
get_markets=markets,
|
||||
)
|
||||
#############################################
|
||||
|
||||
# Create a trade with "limit_buy_order" price
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
freqtrade.active_pair_whitelist = ['NEO/BTC']
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
#############################################
|
||||
|
||||
# stoploss shoud be hit
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog.record_tuples)
|
||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||
|
||||
|
||||
def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets,
|
||||
mocker, edge_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
patch_edge(mocker)
|
||||
|
||||
# Strategy stoploss is -0.1 but Edge imposes a stoploss at -0.2
|
||||
# Thus, if price falls 15%, stoploss should not be triggered
|
||||
#
|
||||
# mocking the ticker: price is falling ...
|
||||
buy_price = limit_buy_order['price']
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': buy_price * 0.85,
|
||||
'ask': buy_price * 0.85,
|
||||
'last': buy_price * 0.85
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
get_markets=markets,
|
||||
)
|
||||
#############################################
|
||||
|
||||
# Create a trade with "limit_buy_order" price
|
||||
freqtrade = FreqtradeBot(edge_conf)
|
||||
freqtrade.active_pair_whitelist = ['NEO/BTC']
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
#############################################
|
||||
|
||||
# stoploss shoud not be hit
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
@@ -450,7 +553,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
freqtrade.create_trade()
|
||||
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
||||
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
|
||||
assert rate * amount >= default_conf['stake_amount']
|
||||
|
||||
|
||||
@@ -494,7 +597,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
assert freqtrade.create_trade() is False
|
||||
assert freqtrade._get_trade_stake_amount() is None
|
||||
assert freqtrade._get_trade_stake_amount('ETH/BTC') is None
|
||||
|
||||
|
||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
|
||||
@@ -593,7 +696,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
assert trade.amount == 90.99181073703367
|
||||
|
||||
assert log_has(
|
||||
'Checking buy signals to create a new trade with stake_amount: 0.001000 ...',
|
||||
'Buy signal found: about create a new trade with stake_amount: 0.001000 ...',
|
||||
caplog.record_tuples
|
||||
)
|
||||
|
||||
@@ -760,10 +863,10 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
assert get_bid.call_count == 1
|
||||
assert buy_mm.call_count == 1
|
||||
call_args = buy_mm.call_args_list[0][0]
|
||||
assert call_args[0] == pair
|
||||
assert call_args[1] == bid
|
||||
assert call_args[2] == stake_amount / bid
|
||||
call_args = buy_mm.call_args_list[0][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == bid
|
||||
assert call_args['amount'] == stake_amount / bid
|
||||
|
||||
# Test calling with price
|
||||
fix_price = 0.06
|
||||
@@ -772,10 +875,10 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non
|
||||
assert get_bid.call_count == 1
|
||||
|
||||
assert buy_mm.call_count == 2
|
||||
call_args = buy_mm.call_args_list[1][0]
|
||||
assert call_args[0] == pair
|
||||
assert call_args[1] == fix_price
|
||||
assert call_args[2] == stake_amount / fix_price
|
||||
call_args = buy_mm.call_args_list[1][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == fix_price
|
||||
assert call_args['amount'] == stake_amount / fix_price
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
|
||||
@@ -1547,7 +1650,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
freqtrade.strategy.stop_loss_reached = \
|
||||
lambda current_rate, trade, current_time, current_profit: SellCheckTuple(
|
||||
lambda current_rate, trade, current_time, force_stoploss, current_profit: SellCheckTuple(
|
||||
sell_flag=False, sell_type=SellType.NONE)
|
||||
freqtrade.create_trade()
|
||||
|
||||
@@ -1821,7 +1924,7 @@ def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, ca
|
||||
exchange='binance',
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456"
|
||||
)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
@@ -2097,9 +2200,9 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets)
|
||||
"""
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_markets=markets,
|
||||
get_order_book=order_book_l2
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_markets=markets,
|
||||
get_order_book=order_book_l2
|
||||
)
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
default_conf['bid_strategy']['use_order_book'] = True
|
||||
|
84
freqtrade/tests/test_wallets.py
Normal file
84
freqtrade/tests/test_wallets.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
from freqtrade.tests.conftest import get_patched_freqtradebot
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def test_sync_wallet_at_boot(mocker, default_conf):
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value={
|
||||
"BNT": {
|
||||
"free": 1.0,
|
||||
"used": 2.0,
|
||||
"total": 3.0
|
||||
},
|
||||
"GAS": {
|
||||
"free": 0.260739,
|
||||
"used": 0.0,
|
||||
"total": 0.260739
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
assert len(freqtrade.wallets.wallets) == 2
|
||||
assert freqtrade.wallets.wallets['BNT'].free == 1.0
|
||||
assert freqtrade.wallets.wallets['BNT'].used == 2.0
|
||||
assert freqtrade.wallets.wallets['BNT'].total == 3.0
|
||||
assert freqtrade.wallets.wallets['GAS'].free == 0.260739
|
||||
assert freqtrade.wallets.wallets['GAS'].used == 0.0
|
||||
assert freqtrade.wallets.wallets['GAS'].total == 0.260739
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value={
|
||||
"BNT": {
|
||||
"free": 1.2,
|
||||
"used": 1.9,
|
||||
"total": 3.5
|
||||
},
|
||||
"GAS": {
|
||||
"free": 0.270739,
|
||||
"used": 0.1,
|
||||
"total": 0.260439
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
freqtrade.wallets.update()
|
||||
|
||||
assert len(freqtrade.wallets.wallets) == 2
|
||||
assert freqtrade.wallets.wallets['BNT'].free == 1.2
|
||||
assert freqtrade.wallets.wallets['BNT'].used == 1.9
|
||||
assert freqtrade.wallets.wallets['BNT'].total == 3.5
|
||||
assert freqtrade.wallets.wallets['GAS'].free == 0.270739
|
||||
assert freqtrade.wallets.wallets['GAS'].used == 0.1
|
||||
assert freqtrade.wallets.wallets['GAS'].total == 0.260439
|
||||
|
||||
|
||||
def test_sync_wallet_missing_data(mocker, default_conf):
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value={
|
||||
"BNT": {
|
||||
"free": 1.0,
|
||||
"used": 2.0,
|
||||
"total": 3.0
|
||||
},
|
||||
"GAS": {
|
||||
"free": 0.260739,
|
||||
"total": 0.260739
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
assert len(freqtrade.wallets.wallets) == 2
|
||||
assert freqtrade.wallets.wallets['BNT'].free == 1.0
|
||||
assert freqtrade.wallets.wallets['BNT'].used == 2.0
|
||||
assert freqtrade.wallets.wallets['BNT'].total == 3.0
|
||||
assert freqtrade.wallets.wallets['GAS'].free == 0.260739
|
||||
assert freqtrade.wallets.wallets['GAS'].used is None
|
||||
assert freqtrade.wallets.wallets['GAS'].total == 0.260739
|
Reference in New Issue
Block a user