@@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
from functools import reduce
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
@@ -10,6 +11,14 @@ from telegram import Chat, Message, Update
|
||||
from freqtrade.misc import CONF_SCHEMA
|
||||
|
||||
|
||||
def log_has(line, logs):
|
||||
# caplog mocker returns log as a tuple: ('freqtrade.analyze', logging.WARNING, 'foobar')
|
||||
# and we want to match line against foobar in the tuple
|
||||
return reduce(lambda a, b: a or b,
|
||||
filter(lambda x: x[2] == line, logs),
|
||||
False)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def default_conf():
|
||||
""" Returns validated configuration suitable for most tests """
|
||||
|
@@ -7,13 +7,23 @@ import pytest
|
||||
|
||||
from freqtrade import OperationalException
|
||||
from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get_balances, \
|
||||
get_ticker, cancel_order, get_name, get_fee
|
||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee
|
||||
import freqtrade.exchange as exchange
|
||||
|
||||
API_INIT = False
|
||||
|
||||
|
||||
def maybe_init_api(conf, mocker):
|
||||
global API_INIT
|
||||
if not API_INIT:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
init(config=conf)
|
||||
API_INIT = True
|
||||
|
||||
|
||||
def test_init(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
init(config=default_conf)
|
||||
maybe_init_api(default_conf, mocker)
|
||||
assert ('freqtrade.exchange',
|
||||
logging.INFO,
|
||||
'Instance is running with dry_run enabled'
|
||||
@@ -159,8 +169,10 @@ def test_get_balances_prod(default_conf, mocker):
|
||||
assert get_balances()[0]['Pending'] == 0.0
|
||||
|
||||
|
||||
def test_get_ticker(mocker, ticker):
|
||||
|
||||
# This test is somewhat redundant with
|
||||
# test_exchange_bittrex.py::test_exchange_bittrex_get_ticker
|
||||
def test_get_ticker(default_conf, mocker, ticker):
|
||||
maybe_init_api(default_conf, mocker)
|
||||
api_mock = MagicMock()
|
||||
tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
|
||||
api_mock.get_ticker = MagicMock(return_value=tick)
|
||||
@@ -177,6 +189,7 @@ def test_get_ticker(mocker, ticker):
|
||||
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
|
||||
|
||||
# if not caching the result we should get the same ticker
|
||||
# if not fetching a new result we should get the cached ticker
|
||||
ticker = get_ticker(pair='BTC_ETH', refresh=False)
|
||||
assert ticker['bid'] == 0.00001098
|
||||
assert ticker['ask'] == 0.00001099
|
||||
@@ -187,6 +200,26 @@ def test_get_ticker(mocker, ticker):
|
||||
assert ticker['ask'] == 1
|
||||
|
||||
|
||||
def test_get_ticker_history(default_conf, mocker, ticker):
|
||||
api_mock = MagicMock()
|
||||
tick = 123
|
||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# retrieve original ticker
|
||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
||||
assert ticks == 123
|
||||
|
||||
# change the ticker
|
||||
tick = 999
|
||||
api_mock.get_ticker_history = MagicMock(return_value=tick)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
|
||||
# ensure caching will still return the original ticker
|
||||
ticks = get_ticker_history('BTC_ETH', int(default_conf['ticker_interval']))
|
||||
assert ticks == 123
|
||||
|
||||
|
||||
def test_cancel_order_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
@@ -194,6 +227,33 @@ def test_cancel_order_dry_run(default_conf, mocker):
|
||||
assert cancel_order(order_id='123') is None
|
||||
|
||||
|
||||
# Ensure that if not dry_run, we should call API
|
||||
def test_cancel_order(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
api_mock = MagicMock()
|
||||
api_mock.cancel_order = MagicMock(return_value=123)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
assert cancel_order(order_id='_') == 123
|
||||
|
||||
|
||||
def test_get_order(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
order = MagicMock()
|
||||
order.myid = 123
|
||||
exchange._DRY_RUN_OPEN_ORDERS['X'] = order
|
||||
print(exchange.get_order('X'))
|
||||
assert exchange.get_order('X').myid == 123
|
||||
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
api_mock = MagicMock()
|
||||
api_mock.get_order = MagicMock(return_value=456)
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
assert 456 == exchange.get_order('X')
|
||||
|
||||
|
||||
def test_get_name(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.validate_pairs',
|
||||
side_effect=lambda s: True)
|
||||
@@ -209,3 +269,18 @@ def test_get_fee(default_conf, mocker):
|
||||
init(default_conf)
|
||||
|
||||
assert get_fee() == 0.0025
|
||||
|
||||
|
||||
def test_exchange_misc(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
exchange.get_markets()
|
||||
assert 1 == api_mock.get_markets.call_count
|
||||
exchange.get_market_summaries()
|
||||
assert 1 == api_mock.get_market_summaries.call_count
|
||||
api_mock.name = 123
|
||||
assert 123 == exchange.get_name()
|
||||
api_mock.fee = 456
|
||||
assert 456 == exchange.get_fee()
|
||||
exchange.get_wallet_health()
|
||||
assert 1 == api_mock.get_wallet_health.call_count
|
||||
|
@@ -143,7 +143,7 @@ def test_exchange_bittrex_fee():
|
||||
assert fee >= 0 and fee < 0.1 # Fee is 0-10 %
|
||||
|
||||
|
||||
def test_exchange_bittrex_buy_good(mocker):
|
||||
def test_exchange_bittrex_buy_good():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
uuid = wb.buy('BTC_ETH', 1, 1)
|
||||
@@ -154,7 +154,7 @@ def test_exchange_bittrex_buy_good(mocker):
|
||||
wb.buy('BAD', 1, 1)
|
||||
|
||||
|
||||
def test_exchange_bittrex_sell_good(mocker):
|
||||
def test_exchange_bittrex_sell_good():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
uuid = wb.sell('BTC_ETH', 1, 1)
|
||||
@@ -165,7 +165,7 @@ def test_exchange_bittrex_sell_good(mocker):
|
||||
uuid = wb.sell('BAD', 1, 1)
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_balance(mocker):
|
||||
def test_exchange_bittrex_get_balance():
|
||||
wb = make_wrap_bittrex()
|
||||
fb = FakeBittrex()
|
||||
bal = wb.get_balance('BTC_ETH')
|
||||
@@ -238,10 +238,12 @@ def test_exchange_bittrex_get_ticker_bad():
|
||||
wb.get_ticker('BTC_ETH')
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker_history_one():
|
||||
def test_exchange_bittrex_get_ticker_history_intervals():
|
||||
wb = make_wrap_bittrex()
|
||||
FakeBittrex()
|
||||
assert wb.get_ticker_history('BTC_ETH', 1)
|
||||
for tick_interval in [1, 5, 30, 60, 1440]:
|
||||
assert ([{'C': 0, 'V': 0, 'O': 0, 'H': 0, 'L': 0, 'T': 0}] ==
|
||||
wb.get_ticker_history('BTC_ETH', tick_interval))
|
||||
|
||||
|
||||
def test_exchange_bittrex_get_ticker_history():
|
||||
@@ -351,3 +353,8 @@ def test_validate_response_min_trade_requirement_not_met():
|
||||
}
|
||||
with pytest.raises(ContentDecodingError, match=r'.*MIN_TRADE_REQUIREMENT_NOT_MET.*'):
|
||||
Bittrex._validate_response(response)
|
||||
|
||||
|
||||
def test_custom_requests(mocker):
|
||||
mocker.patch('freqtrade.exchange.bittrex.requests', MagicMock())
|
||||
btx.custom_requests('http://', '')
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
import os
|
||||
import logging
|
||||
# from unittest.mock import MagicMock
|
||||
from shutil import copyfile
|
||||
from freqtrade import exchange, optimize
|
||||
from freqtrade.exchange import Bittrex
|
||||
@@ -198,12 +199,27 @@ def test_download_backtesting_testdata(default_conf, ticker_history, mocker):
|
||||
_clean_test_file(file2)
|
||||
|
||||
|
||||
def test_download_backtesting_testdata2(default_conf, mocker):
|
||||
tick = [{'T': 'bar'}, {'T': 'foo'}]
|
||||
mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
|
||||
assert download_backtesting_testdata(None, pair="BTC-UNITEST", interval=1)
|
||||
assert download_backtesting_testdata(None, pair="BTC-UNITEST", interval=3)
|
||||
|
||||
|
||||
def test_load_tickerdata_file():
|
||||
assert not load_tickerdata_file(None, 'BTC_UNITEST', 7)
|
||||
tickerdata = load_tickerdata_file(None, 'BTC_UNITEST', 1)
|
||||
assert _btc_unittest_length == len(tickerdata)
|
||||
|
||||
|
||||
def test_init(default_conf, mocker):
|
||||
conf = {'exchange': {'pair_whitelist': []}}
|
||||
mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf)
|
||||
assert {} == optimize.load_data('', pairs=[], refresh_pairs=True,
|
||||
ticker_interval=int(default_conf['ticker_interval']))
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe():
|
||||
timerange = ((None, 'line'), None, -100)
|
||||
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
|
||||
|
@@ -1,8 +1,10 @@
|
||||
# pragma pylint: disable=missing-docstring,W0621
|
||||
import json
|
||||
from unittest.mock import MagicMock
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
|
||||
import arrow
|
||||
import datetime
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
@@ -73,6 +75,41 @@ def test_returns_latest_sell_signal(mocker):
|
||||
assert get_signal('BTC-ETH', 5) == (True, False)
|
||||
|
||||
|
||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
||||
assert tt.log_has('Empty ticker history for pair foo',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
||||
side_effect=ValueError('xyz'))
|
||||
assert (False, False) == get_signal('foo', int(default_conf['ticker_interval']))
|
||||
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
||||
assert tt.log_has('Empty dataframe for pair xyz',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_signal_old_dataframe(default_conf, mocker, caplog):
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
|
||||
# FIX: The get_signal function has hardcoded 10, which we must inturn hardcode
|
||||
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
|
||||
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
||||
assert (False, False) == get_signal('xyz', int(default_conf['ticker_interval']))
|
||||
assert tt.log_has('Too old dataframe for pair xyz',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_get_signal_handles_exceptions(mocker):
|
||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
|
||||
mocker.patch('freqtrade.analyze.analyze_ticker',
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import copy
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
|
||||
import arrow
|
||||
import pytest
|
||||
@@ -20,11 +21,10 @@ from freqtrade.persistence import Trade
|
||||
def test_parse_args_backtesting(mocker):
|
||||
""" Test that main() can start backtesting or hyperopt.
|
||||
and also ensure we can pass some specific arguments
|
||||
argument parsing is done in test_misc.py """
|
||||
further argument parsing is done in test_misc.py """
|
||||
backtesting_mock = mocker.patch(
|
||||
'freqtrade.optimize.backtesting.start', MagicMock())
|
||||
with pytest.raises(SystemExit, match=r'0'):
|
||||
main.main(['backtesting'])
|
||||
main.main(['backtesting'])
|
||||
assert backtesting_mock.call_count == 1
|
||||
call_args = backtesting_mock.call_args[0][0]
|
||||
assert call_args.config == 'config.json'
|
||||
@@ -38,8 +38,7 @@ def test_parse_args_backtesting(mocker):
|
||||
def test_main_start_hyperopt(mocker):
|
||||
hyperopt_mock = mocker.patch(
|
||||
'freqtrade.optimize.hyperopt.start', MagicMock())
|
||||
with pytest.raises(SystemExit, match=r'0'):
|
||||
main.main(['hyperopt'])
|
||||
main.main(['hyperopt'])
|
||||
assert hyperopt_mock.call_count == 1
|
||||
call_args = hyperopt_mock.call_args[0][0]
|
||||
assert call_args.config == 'config.json'
|
||||
@@ -48,6 +47,34 @@ def test_main_start_hyperopt(mocker):
|
||||
assert call_args.func is not None
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(default_conf, mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch('freqtrade.main.create_trade', return_value=True)
|
||||
assert main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval']))
|
||||
mocker.patch('freqtrade.main.create_trade', return_value=False)
|
||||
assert not main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval']))
|
||||
|
||||
|
||||
def test_process_maybe_execute_sell(default_conf, mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch('freqtrade.main.handle_trade', return_value=True)
|
||||
mocker.patch('freqtrade.exchange.get_order', return_value=1)
|
||||
trade = MagicMock()
|
||||
trade.open_order_id = '123'
|
||||
assert not main.process_maybe_execute_sell(trade, int(default_conf['ticker_interval']))
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
# Assert we call handle_trade() if trade is feasible for execution
|
||||
assert main.process_maybe_execute_sell(trade, int(default_conf['ticker_interval']))
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch('freqtrade.main.create_trade', MagicMock(side_effect=DependencyException))
|
||||
main.process_maybe_execute_buy(default_conf, int(default_conf['ticker_interval']))
|
||||
tt.log_has('Unable to create trade:', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||
@@ -229,6 +256,20 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
|
||||
create_trade(default_conf['stake_amount'], int(default_conf['ticker_interval']))
|
||||
|
||||
|
||||
def test_create_trade_no_signal(default_conf, ticker, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch('freqtrade.main.get_signal', MagicMock(return_value=(False, False)))
|
||||
mocker.patch.multiple('freqtrade.exchange',
|
||||
get_ticker_history=MagicMock(return_value=20))
|
||||
mocker.patch.multiple('freqtrade.main.exchange',
|
||||
get_balance=MagicMock(return_value=20))
|
||||
stake_amount = 10
|
||||
Trade.query = MagicMock()
|
||||
Trade.query.filter = MagicMock()
|
||||
assert not create_trade(stake_amount, int(default_conf['ticker_interval']))
|
||||
|
||||
|
||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
|
||||
@@ -430,6 +471,20 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
|
||||
assert len(trades) == 0
|
||||
|
||||
|
||||
def test_handle_timedout_limit_buy(default_conf, mocker):
|
||||
cancel_order = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
|
||||
Trade.session = MagicMock()
|
||||
trade = MagicMock()
|
||||
order = {'remaining': 1,
|
||||
'amount': 1}
|
||||
assert main.handle_timedout_limit_buy(trade, order)
|
||||
assert cancel_order.call_count == 1
|
||||
order['amount'] = 2
|
||||
assert not main.handle_timedout_limit_buy(trade, order)
|
||||
assert cancel_order.call_count == 2
|
||||
|
||||
|
||||
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
cancel_order_mock = MagicMock()
|
||||
@@ -464,6 +519,20 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
||||
assert trade_sell.is_open is True
|
||||
|
||||
|
||||
def test_handle_timedout_limit_sell(default_conf, mocker):
|
||||
cancel_order = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.cancel_order', cancel_order)
|
||||
trade = MagicMock()
|
||||
order = {'remaining': 1,
|
||||
'amount': 1}
|
||||
assert main.handle_timedout_limit_sell(trade, order)
|
||||
assert cancel_order.call_count == 1
|
||||
order['amount'] = 2
|
||||
assert not main.handle_timedout_limit_sell(trade, order)
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order.call_count == 1
|
||||
|
||||
|
||||
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
||||
mocker):
|
||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||
|
Reference in New Issue
Block a user