stable/freqtrade/tests/test_main.py

435 lines
18 KiB
Python
Raw Normal View History

2017-11-18 07:58:55 +00:00
# pragma pylint: disable=missing-docstring,C0103
import copy
2017-11-07 21:27:44 +00:00
from unittest.mock import MagicMock
import pytest
import requests
2017-12-27 10:41:11 +00:00
import logging
2017-11-09 22:45:22 +00:00
from sqlalchemy import create_engine
from freqtrade import DependencyException, OperationalException
2017-11-15 05:05:17 +00:00
from freqtrade.analyze import SignalType
2017-11-20 21:26:32 +00:00
from freqtrade.exchange import Exchanges
from freqtrade.main import create_trade, handle_trade, init, \
get_target_bid, _process, execute_sell
from freqtrade.misc import get_state, State
2017-09-28 21:26:28 +00:00
from freqtrade.persistence import Trade
def test_process_trade_creation(default_conf, ticker, health, mocker):
2017-11-07 21:27:44 +00:00
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_wallet_health=health,
2017-11-07 21:27:44 +00:00
buy=MagicMock(return_value='mocked_limit_buy'))
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-11-07 21:27:44 +00:00
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
2017-11-18 07:58:55 +00:00
assert not trades
2017-11-07 21:27:44 +00:00
result = _process()
assert result is True
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1
trade = trades[0]
assert trade is not None
assert trade.stake_amount == default_conf['stake_amount']
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == Exchanges.BITTREX.name
2017-12-17 21:07:56 +00:00
assert trade.open_rate == 0.00001099
assert trade.amount == 90.99181073703367
2017-11-07 21:27:44 +00:00
def test_process_exchange_failures(default_conf, ticker, health, mocker):
2017-11-07 21:27:44 +00:00
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
2017-11-07 21:27:44 +00:00
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_wallet_health=health,
2017-11-07 21:27:44 +00:00
buy=MagicMock(side_effect=requests.exceptions.RequestException))
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-11-07 21:27:44 +00:00
result = _process()
assert result is False
assert sleep_mock.has_calls()
def test_process_operational_exception(default_conf, ticker, health, mocker):
2017-11-07 21:27:44 +00:00
msg_mock = MagicMock()
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_wallet_health=health,
buy=MagicMock(side_effect=OperationalException))
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-11-07 21:27:44 +00:00
assert get_state() == State.RUNNING
result = _process()
assert result is False
assert get_state() == State.STOPPED
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
2017-11-07 21:27:44 +00:00
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker):
2017-11-07 21:27:44 +00:00
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-18 07:58:55 +00:00
mocker.patch('freqtrade.main.get_signal',
side_effect=lambda *args: False if args[1] == SignalType.SELL else True)
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_wallet_health=health,
2017-11-07 21:27:44 +00:00
buy=MagicMock(return_value='mocked_limit_buy'),
2017-11-08 21:44:32 +00:00
get_order=MagicMock(return_value=limit_buy_order))
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-11-07 21:27:44 +00:00
2017-11-08 21:44:32 +00:00
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
2017-11-18 07:58:55 +00:00
assert not trades
2017-11-07 21:27:44 +00:00
result = _process()
assert result is True
2017-11-08 21:44:32 +00:00
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
assert len(trades) == 1
2017-11-07 21:27:44 +00:00
result = _process()
assert result is False
2017-11-07 19:12:56 +00:00
def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
2017-10-01 21:28:09 +00:00
validate_pairs=MagicMock(),
2017-11-07 19:12:56 +00:00
get_ticker=ticker,
2017-10-31 23:26:32 +00:00
buy=MagicMock(return_value='mocked_limit_buy'))
# Save state of current whitelist
2017-11-07 19:12:56 +00:00
whitelist = copy.deepcopy(default_conf['exchange']['pair_whitelist'])
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-12-17 21:07:56 +00:00
create_trade(0.001)
trade = Trade.query.first()
2017-11-07 21:27:44 +00:00
assert trade is not None
2017-12-17 21:07:56 +00:00
assert trade.stake_amount == 0.001
2017-11-07 21:27:44 +00:00
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == Exchanges.BITTREX.name
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
2017-10-31 23:26:32 +00:00
2017-12-17 21:07:56 +00:00
assert trade.open_rate == 0.00001099
assert trade.amount == 90.99181073
2017-11-07 21:27:44 +00:00
assert whitelist == default_conf['exchange']['pair_whitelist']
2017-11-22 20:02:36 +00:00
def test_create_trade_minimal_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
2017-12-16 02:39:47 +00:00
buy_mock = mocker.patch(
'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy')
)
2017-11-22 20:02:36 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, create_engine('sqlite://'))
min_stake_amount = 0.0005
create_trade(min_stake_amount)
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
assert rate * amount >= min_stake_amount
2017-11-07 21:27:44 +00:00
def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5))
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
2017-11-07 21:27:44 +00:00
create_trade(default_conf['stake_amount'])
2017-11-07 19:12:56 +00:00
2017-10-31 23:26:32 +00:00
2017-11-07 21:27:44 +00:00
def test_create_trade_no_pairs(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
2017-11-07 21:27:44 +00:00
conf = copy.deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = []
mocker.patch.dict('freqtrade.main._CONF', conf)
create_trade(default_conf['stake_amount'])
2017-10-30 23:36:35 +00:00
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
with pytest.raises(DependencyException, match=r'.*No pair in whitelist.*'):
conf = copy.deepcopy(default_conf)
conf['exchange']['pair_whitelist'] = ["BTC_ETH"]
conf['exchange']['pair_blacklist'] = ["BTC_ETH"]
mocker.patch.dict('freqtrade.main._CONF', conf)
create_trade(default_conf['stake_amount'])
2017-11-07 21:27:44 +00:00
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
2017-11-07 19:12:56 +00:00
mocker.patch.dict('freqtrade.main._CONF', default_conf)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
2017-10-01 21:28:09 +00:00
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
2017-12-17 21:07:56 +00:00
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
}),
2017-11-07 21:27:44 +00:00
buy=MagicMock(return_value='mocked_limit_buy'),
2017-10-31 23:26:32 +00:00
sell=MagicMock(return_value='mocked_limit_sell'))
2017-12-25 07:51:41 +00:00
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-12-17 21:07:56 +00:00
create_trade(0.001)
trade = Trade.query.first()
assert trade
2017-10-31 23:26:32 +00:00
trade.update(limit_buy_order)
assert trade.is_open is True
handle_trade(trade)
2017-10-31 23:26:32 +00:00
assert trade.open_order_id == 'mocked_limit_sell'
# Simulate fulfilled LIMIT_SELL order for trade
2017-11-07 19:12:56 +00:00
trade.update(limit_sell_order)
2017-12-17 21:07:56 +00:00
assert trade.close_rate == 0.00001173
assert trade.close_profit == 0.06201057
assert trade.calc_profit() == 0.00006217
assert trade.close_date is not None
2017-12-29 08:40:24 +00:00
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
mocker.patch('freqtrade.main.min_roi_reached', return_value=True)
init(default_conf, create_engine('sqlite://'))
create_trade(0.001)
trade = Trade.query.first()
trade.is_open = True
# FIX: sniffing logs, suggest handle_trade should not execute_sell
# instead that responsibility should be moved out of handle_trade(),
# we might just want to check if we are in a sell condition without
# executing
# if ROI is reached we must sell
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False)
assert handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
# if ROI is reached we must sell even if sell-signal is not signalled
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
assert handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
2017-10-30 23:36:35 +00:00
2017-12-29 08:40:24 +00:00
2017-12-27 10:41:11 +00:00
def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
2017-12-27 10:41:11 +00:00
init(default_conf, create_engine('sqlite://'))
create_trade(0.001)
trade = Trade.query.first()
trade.is_open = True
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False)
value_returned = handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Checking sell_signal ...') in caplog.record_tuples
assert value_returned is False
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
assert handle_trade(trade)
2017-12-29 08:40:24 +00:00
s = 'Executing sell due to sell signal ...'
assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples
2017-12-27 10:41:11 +00:00
2017-11-07 21:27:44 +00:00
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker):
2017-11-07 19:12:56 +00:00
mocker.patch.dict('freqtrade.main._CONF', default_conf)
2017-11-14 17:06:03 +00:00
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
2017-11-07 21:27:44 +00:00
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
# Create trade and sell it
2017-11-09 22:45:22 +00:00
init(default_conf, create_engine('sqlite://'))
2017-12-17 21:07:56 +00:00
create_trade(0.001)
trade = Trade.query.first()
assert trade
trade.update(limit_buy_order)
trade.update(limit_sell_order)
assert trade.is_open is False
2017-11-07 21:27:44 +00:00
with pytest.raises(ValueError, match=r'.*closed trade.*'):
handle_trade(trade)
2017-10-30 23:36:35 +00:00
def test_balance_fully_ask_side(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}})
assert get_target_bid({'ask': 20, 'last': 10}) == 20
2017-10-30 23:36:35 +00:00
def test_balance_fully_last_side(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}})
assert get_target_bid({'ask': 20, 'last': 10}) == 10
2017-10-30 23:36:35 +00:00
2017-11-07 19:12:56 +00:00
def test_balance_bigger_last_ask(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}})
assert get_target_bid({'ask': 5, 'last': 10}) == 5
def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://'))
# Create some test data
create_trade(0.001)
trade = Trade.query.first()
assert trade
# Increase the price and sell it
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up)
execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0]
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
init=MagicMock(),
send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
init(default_conf, create_engine('sqlite://'))
# Create some test data
create_trade(0.001)
trade = Trade.query.first()
assert trade
# Decrease the price and sell it
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_down)
execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001044' in rpc_mock.call_args_list[-1][0][0]
assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0]
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True)
mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, create_engine('sqlite://'))
# Create some test data
create_trade(0.001)
trade = Trade.query.first()
assert trade
# Increase the price and sell it
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up)
mocker.patch('freqtrade.main._CONF', {})
execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
assert rpc_mock.call_count == 2
assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0]
assert '0.00001172' in rpc_mock.call_args_list[-1][0][0]
assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0]
assert 'USD' not in rpc_mock.call_args_list[-1][0][0]