conflict resolved0
This commit is contained in:
@@ -26,20 +26,21 @@ def log_has(line, logs):
|
||||
False)
|
||||
|
||||
|
||||
def patch_exchange(mocker, api_mock=None) -> None:
|
||||
def patch_exchange(mocker, api_mock=None, id='bittrex') -> 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"))
|
||||
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
|
||||
|
||||
if api_mock:
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
else:
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock())
|
||||
|
||||
|
||||
def get_patched_exchange(mocker, config, api_mock=None) -> Exchange:
|
||||
patch_exchange(mocker, api_mock)
|
||||
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
|
||||
patch_exchange(mocker, api_mock, id)
|
||||
exchange = Exchange(config)
|
||||
return exchange
|
||||
|
||||
|
@@ -362,18 +362,41 @@ def test_validate_order_types(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_timeframes', MagicMock())
|
||||
default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'}
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
|
||||
default_conf['order_types'] = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': False
|
||||
}
|
||||
|
||||
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'}
|
||||
default_conf['order_types'] = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'market',
|
||||
'stoploss_on_exchange': 'false'
|
||||
}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
Exchange(default_conf)
|
||||
|
||||
default_conf['order_types'] = {
|
||||
'buy': 'limit',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': True
|
||||
}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'On exchange stoploss is not supported for .*'):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_order_types_not_in_config(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
@@ -1122,3 +1145,85 @@ def test_get_fee(default_conf, mocker):
|
||||
|
||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
|
||||
'get_fee', 'calculate_fee')
|
||||
|
||||
|
||||
def test_stoploss_limit_order(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
order_type = 'stop_loss_limit'
|
||||
|
||||
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, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200)
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, 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] == 200
|
||||
assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220}
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||
|
||||
|
||||
def test_stoploss_limit_order_dry_run(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_type = 'stop_loss_limit'
|
||||
default_conf['dry_run'] = 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)
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200)
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
|
||||
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert 'type' in order
|
||||
|
||||
assert order['type'] == order_type
|
||||
assert order['price'] == 220
|
||||
assert order['amount'] == 1
|
||||
|
@@ -4,9 +4,10 @@ import arrow
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from freqtrade.constants import TICKER_INTERVAL_MINUTES
|
||||
|
||||
ticker_start_time = arrow.get(2018, 10, 3)
|
||||
ticker_interval_in_minute = 60
|
||||
tests_ticker_interval = "1h"
|
||||
|
||||
|
||||
class BTrade(NamedTuple):
|
||||
@@ -30,8 +31,8 @@ class BTContainer(NamedTuple):
|
||||
|
||||
|
||||
def _get_frame_time_from_offset(offset):
|
||||
return ticker_start_time.shift(
|
||||
minutes=(offset * ticker_interval_in_minute)).datetime.replace(tzinfo=None)
|
||||
return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])
|
||||
).datetime.replace(tzinfo=None)
|
||||
|
||||
|
||||
def _build_backtest_dataframe(ticker_with_signals):
|
||||
|
@@ -6,10 +6,11 @@ from pandas import DataFrame
|
||||
import pytest
|
||||
|
||||
|
||||
from freqtrade.optimize import get_timeframe
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
|
||||
_get_frame_time_from_offset)
|
||||
_get_frame_time_from_offset, tests_ticker_interval)
|
||||
from freqtrade.tests.conftest import patch_exchange
|
||||
|
||||
|
||||
@@ -147,6 +148,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
"""
|
||||
default_conf["stoploss"] = data.stop_loss
|
||||
default_conf["minimal_roi"] = {"0": data.roi}
|
||||
default_conf['ticker_interval'] = tests_ticker_interval
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0))
|
||||
patch_exchange(mocker)
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
@@ -158,29 +160,21 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
pair = 'UNITTEST/BTC'
|
||||
# Dummy data as we mock the analyze functions
|
||||
data_processed = {pair: DataFrame()}
|
||||
min_date, max_date = get_timeframe({pair: frame})
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'processed': data_processed,
|
||||
'max_open_trades': 10,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
)
|
||||
print(results.T)
|
||||
|
||||
assert len(results) == len(data.trades)
|
||||
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
|
||||
# if data.sell_r == SellType.STOP_LOSS:
|
||||
# assert log_has("Stop loss hit.", caplog.record_tuples)
|
||||
# else:
|
||||
# assert not log_has("Stop loss hit.", caplog.record_tuples)
|
||||
# log_test = (f'Force_selling still open trade UNITTEST/BTC with '
|
||||
# f'{results.iloc[-1].profit_percent} perc - {results.iloc[-1].profit_abs}')
|
||||
# if data.sell_r == SellType.FORCE_SELL:
|
||||
# assert log_has(log_test,
|
||||
# caplog.record_tuples)
|
||||
# else:
|
||||
# assert not log_has(log_test,
|
||||
# caplog.record_tuples)
|
||||
|
||||
for c, trade in enumerate(data.trades):
|
||||
res = results.iloc[c]
|
||||
assert res.sell_reason == trade.sell_reason
|
||||
|
@@ -13,6 +13,7 @@ from arrow import Arrow
|
||||
|
||||
from freqtrade import DependencyException, constants, optimize
|
||||
from freqtrade.arguments import Arguments, TimeRange
|
||||
from freqtrade.optimize import get_timeframe
|
||||
from freqtrade.optimize.backtesting import (Backtesting, setup_configuration,
|
||||
start)
|
||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||
@@ -86,17 +87,21 @@ def load_data_test(what):
|
||||
|
||||
def simple_backtest(config, contour, num_results, mocker) -> None:
|
||||
patch_exchange(mocker)
|
||||
config['ticker_interval'] = '1m'
|
||||
backtesting = Backtesting(config)
|
||||
|
||||
data = load_data_test(contour)
|
||||
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
min_date, max_date = get_timeframe(processed)
|
||||
assert isinstance(processed, dict)
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
'stake_amount': config['stake_amount'],
|
||||
'processed': processed,
|
||||
'max_open_trades': 1,
|
||||
'position_stacking': False
|
||||
'position_stacking': False,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
)
|
||||
# results :: <class 'pandas.core.frame.DataFrame'>
|
||||
@@ -123,12 +128,16 @@ def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
|
||||
data = trim_dictlist(data, -201)
|
||||
patch_exchange(mocker)
|
||||
backtesting = Backtesting(conf)
|
||||
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
min_date, max_date = get_timeframe(processed)
|
||||
return {
|
||||
'stake_amount': conf['stake_amount'],
|
||||
'processed': backtesting.strategy.tickerdata_to_dataframe(data),
|
||||
'processed': processed,
|
||||
'max_open_trades': 10,
|
||||
'position_stacking': False,
|
||||
'record': record
|
||||
'record': record,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
|
||||
|
||||
@@ -449,7 +458,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
)
|
||||
|
||||
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
default_conf['ticker_interval'] = "1m"
|
||||
default_conf['ticker_interval'] = '1m'
|
||||
default_conf['live'] = False
|
||||
default_conf['datadir'] = None
|
||||
default_conf['export'] = None
|
||||
@@ -505,12 +514,15 @@ def test_backtest(default_conf, fee, mocker) -> None:
|
||||
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
||||
data = trim_dictlist(data, -200)
|
||||
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
min_date, max_date = get_timeframe(data_processed)
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'processed': data_processed,
|
||||
'max_open_trades': 10,
|
||||
'position_stacking': False
|
||||
'position_stacking': False,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
)
|
||||
assert not results.empty
|
||||
@@ -554,12 +566,16 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
||||
# Run a backtesting for an exiting 5min ticker_interval
|
||||
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||
data = trim_dictlist(data, -200)
|
||||
processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
min_date, max_date = get_timeframe(processed)
|
||||
results = backtesting.backtest(
|
||||
{
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'processed': backtesting.strategy.tickerdata_to_dataframe(data),
|
||||
'processed': processed,
|
||||
'max_open_trades': 1,
|
||||
'position_stacking': False
|
||||
'position_stacking': False,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
)
|
||||
assert not results.empty
|
||||
@@ -583,25 +599,13 @@ def test_processed(default_conf, mocker) -> None:
|
||||
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
tests = [['raise', 18], ['lower', 0], ['sine', 19]]
|
||||
# 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)
|
||||
|
||||
|
||||
# Test backtest using offline data (testdata directory)
|
||||
def test_backtest_ticks(default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
ticks = [1, 5]
|
||||
fun = Backtesting(default_conf).advise_buy
|
||||
for _ in ticks:
|
||||
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.advise_buy = fun # Override
|
||||
backtesting.advise_sell = fun # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None, pair=None):
|
||||
@@ -636,14 +640,92 @@ 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}
|
||||
default_conf['ticker_interval'] = '1m'
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.advise_buy = _trend_alternate # Override
|
||||
backtesting.advise_sell = _trend_alternate # Override
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
backtesting._store_backtest_result("test_.json", results)
|
||||
assert len(results) == 4
|
||||
# 200 candles in backtest data
|
||||
# won't buy on first (shifted by 1)
|
||||
# 100 buys signals
|
||||
assert len(results) == 99
|
||||
# One trade was force-closed at the end
|
||||
assert len(results.loc[results.open_at_end]) == 1
|
||||
assert len(results.loc[results.open_at_end]) == 0
|
||||
|
||||
|
||||
def test_backtest_multi_pair(default_conf, fee, mocker):
|
||||
|
||||
def evaluate_result_multi(results, freq, max_open_trades):
|
||||
# Find overlapping trades by expanding each trade once per period
|
||||
# and then counting overlaps
|
||||
dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq))
|
||||
for row in results[['open_time', 'close_time']].iterrows()]
|
||||
deltas = [len(x) for x in dates]
|
||||
dates = pd.Series(pd.concat(dates).values, name='date')
|
||||
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
|
||||
|
||||
df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
|
||||
df2 = pd.concat([dates, df2], axis=1)
|
||||
df2 = df2.set_index('date')
|
||||
df_final = df2.resample(freq)[['pair']].count()
|
||||
return df_final[df_final['pair'] > max_open_trades]
|
||||
|
||||
def _trend_alternate_hold(dataframe=None, metadata=None):
|
||||
"""
|
||||
Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit)
|
||||
"""
|
||||
multi = 8
|
||||
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
|
||||
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
|
||||
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
|
||||
dataframe['buy'] = dataframe['buy'].shift(-4)
|
||||
dataframe['sell'] = dataframe['sell'].shift(-4)
|
||||
return dataframe
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
patch_exchange(mocker)
|
||||
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
|
||||
data = optimize.load_data(None, ticker_interval='5m', pairs=pairs)
|
||||
data = trim_dictlist(data, -500)
|
||||
# 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)
|
||||
backtesting.advise_buy = _trend_alternate_hold # Override
|
||||
backtesting.advise_sell = _trend_alternate_hold # Override
|
||||
|
||||
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
|
||||
min_date, max_date = get_timeframe(data_processed)
|
||||
backtest_conf = {
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'processed': data_processed,
|
||||
'max_open_trades': 3,
|
||||
'position_stacking': False,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
|
||||
# Make sure we have parallel trades
|
||||
assert len(evaluate_result_multi(results, '5min', 2)) > 0
|
||||
# make sure we don't have trades with more than configured max_open_trades
|
||||
assert len(evaluate_result_multi(results, '5min', 3)) == 0
|
||||
|
||||
backtest_conf = {
|
||||
'stake_amount': default_conf['stake_amount'],
|
||||
'processed': data_processed,
|
||||
'max_open_trades': 1,
|
||||
'position_stacking': False,
|
||||
'start_date': min_date,
|
||||
'end_date': max_date,
|
||||
}
|
||||
results = backtesting.backtest(backtest_conf)
|
||||
assert len(evaluate_result_multi(results, '5min', 1)) == 0
|
||||
|
||||
|
||||
def test_backtest_record(default_conf, fee, mocker):
|
||||
|
@@ -1,11 +1,12 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
from datetime import datetime
|
||||
import os
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from freqtrade.optimize.__init__ import load_tickerdata_file
|
||||
from freqtrade.optimize import load_tickerdata_file
|
||||
from freqtrade.optimize.hyperopt import Hyperopt, start
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
||||
@@ -293,6 +294,10 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
'freqtrade.optimize.hyperopt.Hyperopt.backtest',
|
||||
MagicMock(return_value=backtest_result)
|
||||
)
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.get_timeframe',
|
||||
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock())
|
||||
|
||||
|
@@ -113,3 +113,23 @@ def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
|
||||
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
|
||||
assert len(rpc_manager.registered_modules) == 1
|
||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
|
||||
|
||||
def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.startup_messages(default_conf)
|
||||
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
|
||||
telegram_mock.reset_mock()
|
||||
default_conf['dry_run'] = True
|
||||
default_conf['dynamic_whitelist'] = 20
|
||||
|
||||
rpc_manager.startup_messages(default_conf)
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "Dry run is enabled." in telegram_mock.call_args_list[0][0][0]['status']
|
||||
|
@@ -189,7 +189,8 @@ def test_strategy_override_order_types(caplog):
|
||||
order_types = {
|
||||
'buy': 'market',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit'
|
||||
'stoploss': 'limit',
|
||||
'stoploss_on_exchange': True,
|
||||
}
|
||||
|
||||
config = {
|
||||
@@ -199,13 +200,14 @@ def test_strategy_override_order_types(caplog):
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert resolver.strategy.order_types
|
||||
for method in ['buy', 'sell', 'stoploss']:
|
||||
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
|
||||
assert resolver.strategy.order_types[method] == order_types[method]
|
||||
|
||||
assert ('freqtrade.resolvers.strategy_resolver',
|
||||
logging.INFO,
|
||||
"Override strategy 'order_types' with value in config file:"
|
||||
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}."
|
||||
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
|
||||
" 'stoploss_on_exchange': True}."
|
||||
) in caplog.record_tuples
|
||||
|
||||
config = {
|
||||
@@ -263,13 +265,13 @@ def test_call_deprecated_function(result, monkeypatch):
|
||||
assert resolver.strategy._sell_fun_len == 2
|
||||
|
||||
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
|
||||
assert type(indicator_df) is DataFrame
|
||||
assert isinstance(indicator_df, DataFrame)
|
||||
assert 'adx' in indicator_df.columns
|
||||
|
||||
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
|
||||
assert type(buydf) is DataFrame
|
||||
assert isinstance(buydf, DataFrame)
|
||||
assert 'buy' in buydf.columns
|
||||
|
||||
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
|
||||
assert type(selldf) is DataFrame
|
||||
assert isinstance(selldf, DataFrame)
|
||||
assert 'sell' in selldf
|
||||
|
@@ -874,6 +874,100 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non
|
||||
assert call_args['amount'] == stake_amount / fix_price
|
||||
|
||||
|
||||
def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||
return_value=limit_buy_order['amount'])
|
||||
|
||||
stoploss_limit = MagicMock(return_value={'id': 13434334})
|
||||
mocker.patch('freqtrade.exchange.Exchange.stoploss_limit', stoploss_limit)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
trade.is_open = True
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
assert trade.stoploss_order_id == '13434334'
|
||||
assert stoploss_limit.call_count == 1
|
||||
assert trade.is_open is True
|
||||
|
||||
|
||||
def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
markets, limit_buy_order, limit_sell_order) -> None:
|
||||
stoploss_limit = MagicMock(return_value={'id': 13434334})
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock(return_value={
|
||||
'bid': 0.00001172,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee,
|
||||
get_markets=markets,
|
||||
stoploss_limit=stoploss_limit
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# First case: when stoploss is not yet set but the order is open
|
||||
# should get the stoploss order id immediately
|
||||
# and should return false as no trade actually happened
|
||||
trade = MagicMock()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = None
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert stoploss_limit.call_count == 1
|
||||
assert trade.stoploss_order_id == "13434334"
|
||||
|
||||
# Second case: when stoploss is set but it is not yet hit
|
||||
# should do nothing and return false
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
|
||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', hanging_stoploss_order)
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.stoploss_order_id == 100
|
||||
|
||||
# Third case: when stoploss is set and it is hit
|
||||
# should unset stoploss_order_id and return true
|
||||
# as a trade actually happened
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.stoploss_order_id = 100
|
||||
assert trade
|
||||
|
||||
stoploss_order_hit = MagicMock(return_value={
|
||||
'status': 'closed',
|
||||
'type': 'stop_loss_limit',
|
||||
'price': 3,
|
||||
'average': 2
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_order_hit)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||
assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog.record_tuples)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
@@ -1469,8 +1563,9 @@ def test_execute_sell_down_live(default_conf, ticker, fee,
|
||||
} == last_msg
|
||||
|
||||
|
||||
def test_execute_sell_down_dry_run(default_conf, ticker, fee,
|
||||
ticker_sell_down, markets, mocker) -> None:
|
||||
def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||
ticker_sell_down,
|
||||
markets, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@@ -1495,15 +1590,16 @@ def test_execute_sell_down_dry_run(default_conf, ticker, fee,
|
||||
)
|
||||
|
||||
default_conf['dry_run'] = True
|
||||
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
# Setting trade stoploss to 0.01
|
||||
trade.stop_loss = 0.00001099 * 0.99
|
||||
|
||||
trade.stop_loss = 0.00001099 * 0.99
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellType.STOP_LOSS)
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
@@ -1521,6 +1617,129 @@ def test_execute_sell_down_dry_run(default_conf, ticker, fee,
|
||||
} == last_msg
|
||||
|
||||
|
||||
def test_execute_sell_with_stoploss_on_exchange(default_conf,
|
||||
ticker, fee, ticker_sell_up,
|
||||
markets, mocker) -> None:
|
||||
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
_load_markets=MagicMock(return_value={}),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
get_markets=markets
|
||||
)
|
||||
|
||||
stoploss_limit = MagicMock(return_value={
|
||||
'id': 123,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# Create some test data
|
||||
freqtrade.create_trade()
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
|
||||
# Increase the price and sell it
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker_sell_up
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellType.SELL_SIGNAL)
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
assert cancel_order.call_count == 1
|
||||
assert rpc_mock.call_count == 2
|
||||
|
||||
|
||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf,
|
||||
ticker, fee,
|
||||
limit_buy_order,
|
||||
markets, mocker) -> None:
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
_load_markets=MagicMock(return_value={}),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
get_markets=markets
|
||||
)
|
||||
|
||||
stoploss_limit = MagicMock(return_value={
|
||||
'id': 123,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# Create some test data
|
||||
freqtrade.create_trade()
|
||||
trade = Trade.query.first()
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
assert trade
|
||||
assert trade.stoploss_order_id == '123'
|
||||
assert trade.open_order_id is None
|
||||
|
||||
# Assuming stoploss on exchnage is hit
|
||||
# stoploss_order_id should become None
|
||||
# and trade should be sold at the price of stoploss
|
||||
stoploss_limit_executed = MagicMock(return_value={
|
||||
"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": 99.0000000032274,
|
||||
"average": 1.08801,
|
||||
"filled": 90.99181074,
|
||||
"remaining": 0.0,
|
||||
"status": "closed",
|
||||
"fee": None,
|
||||
"trades": None
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_order', stoploss_limit_executed)
|
||||
|
||||
freqtrade.process_maybe_execute_sell(trade)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
print(trade.sell_reason)
|
||||
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
|
||||
assert rpc_mock.call_count == 1
|
||||
|
||||
|
||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
||||
ticker_sell_up, markets, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
|
@@ -426,6 +426,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
max_rate FLOAT,
|
||||
sell_reason VARCHAR,
|
||||
strategy VARCHAR,
|
||||
ticker_interval INTEGER,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
@@ -471,6 +472,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
assert trade.sell_reason is None
|
||||
assert trade.strategy is None
|
||||
assert trade.ticker_interval is None
|
||||
assert trade.stoploss_order_id is None
|
||||
assert log_has("trying trades_bak1", caplog.record_tuples)
|
||||
assert log_has("trying trades_bak2", caplog.record_tuples)
|
||||
assert log_has("Running database migration - backup available as trades_bak2",
|
||||
|
Reference in New Issue
Block a user