* remove obsolete helper functions and make _state a public member. * remove function assertions * revert worker() changes * Update pytest from 3.4.2 to 3.5.0 * Adapt exchange functions to ccxt API Remove get_market_summaries and get_wallet_health, add exception handling * Add NetworkException * Change pair format in constants.py * Add tests for exchange functions that comply with ccxt * Remove bittrex tests * Remove Bittrex and Interface classes * Add retrier decorator * Remove cache from get_ticker * Remove unused and duplicate imports * Add keyword arguments for get_fee * Implement 'get_pair_detail_url' * Change get_ticker_history format to ccxt format * Fix exchange urls dict, don't need to initialize exchanges * Add "Using Exchange ..." logging line
		
			
				
	
	
		
			1276 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1276 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
 | |
| 
 | |
| """
 | |
| Unit test file for freqtradebot.py
 | |
| """
 | |
| 
 | |
| import logging
 | |
| import re
 | |
| import time
 | |
| from copy import deepcopy
 | |
| from typing import Dict, Optional
 | |
| from unittest.mock import MagicMock
 | |
| 
 | |
| import arrow
 | |
| import pytest
 | |
| import requests
 | |
| from sqlalchemy import create_engine
 | |
| 
 | |
| from freqtrade import DependencyException, OperationalException
 | |
| from freqtrade.freqtradebot import FreqtradeBot
 | |
| from freqtrade.persistence import Trade
 | |
| from freqtrade.state import State
 | |
| from freqtrade.tests.conftest import log_has
 | |
| 
 | |
| 
 | |
| # Functions for recurrent object patching
 | |
| def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
 | |
|     """
 | |
|     This function patch _init_modules() to not call dependencies
 | |
|     :param mocker: a Mocker object to apply patches
 | |
|     :param config: Config to pass to the bot
 | |
|     :return: None
 | |
|     """
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
 | |
|     mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
 | |
|     mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
 | |
|     mocker.patch('freqtrade.freqtradebot.exchange.init', MagicMock())
 | |
|     patch_coinmarketcap(mocker)
 | |
| 
 | |
|     return FreqtradeBot(config, create_engine('sqlite://'))
 | |
| 
 | |
| 
 | |
| def patch_get_signal(mocker, value=(True, False)) -> None:
 | |
|     """
 | |
| 
 | |
|     :param mocker: mocker to patch Analyze class
 | |
|     :param value: which value Analyze.get_signal() must return
 | |
|     :return: None
 | |
|     """
 | |
|     mocker.patch(
 | |
|         'freqtrade.freqtradebot.Analyze.get_signal',
 | |
|         side_effect=lambda s, t: value
 | |
|     )
 | |
| 
 | |
| 
 | |
| def patch_RPCManager(mocker) -> MagicMock:
 | |
|     """
 | |
|     This function mock RPC manager to avoid repeating this code in almost every tests
 | |
|     :param mocker: mocker to patch RPCManager class
 | |
|     :return: RPCManager.send_msg MagicMock to track if this method is called
 | |
|     """
 | |
|     mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
 | |
|     rpc_mock = mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
 | |
|     return rpc_mock
 | |
| 
 | |
| 
 | |
| def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
 | |
|     """
 | |
|     Mocker to coinmarketcap to speed up tests
 | |
|     :param mocker: mocker to patch coinmarketcap class
 | |
|     :return: None
 | |
|     """
 | |
|     mock = MagicMock()
 | |
| 
 | |
|     if value:
 | |
|         mock.ticker = {'price_usd': 12345.0}
 | |
| 
 | |
|     mocker.patch('freqtrade.fiat_convert.Market', mock)
 | |
| 
 | |
| 
 | |
| # Unit tests
 | |
| def test_freqtradebot_object() -> None:
 | |
|     """
 | |
|     Test the FreqtradeBot object has the mandatory public methods
 | |
|     """
 | |
|     assert hasattr(FreqtradeBot, 'worker')
 | |
|     assert hasattr(FreqtradeBot, 'clean')
 | |
|     assert hasattr(FreqtradeBot, 'create_trade')
 | |
|     assert hasattr(FreqtradeBot, 'get_target_bid')
 | |
|     assert hasattr(FreqtradeBot, 'process_maybe_execute_buy')
 | |
|     assert hasattr(FreqtradeBot, 'process_maybe_execute_sell')
 | |
|     assert hasattr(FreqtradeBot, 'handle_trade')
 | |
|     assert hasattr(FreqtradeBot, 'check_handle_timedout')
 | |
|     assert hasattr(FreqtradeBot, 'handle_timedout_limit_buy')
 | |
|     assert hasattr(FreqtradeBot, 'handle_timedout_limit_sell')
 | |
|     assert hasattr(FreqtradeBot, 'execute_sell')
 | |
| 
 | |
| 
 | |
| def test_freqtradebot(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test __init__, _init_modules, update_state, and get_state methods
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
|     assert freqtrade.state is State.RUNNING
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf.pop('initial_state')
 | |
|     freqtrade = FreqtradeBot(conf)
 | |
|     assert freqtrade.state is State.STOPPED
 | |
| 
 | |
| 
 | |
| def test_clean(mocker, default_conf, caplog) -> None:
 | |
|     """
 | |
|     Test clean() method
 | |
|     """
 | |
|     mock_cleanup = MagicMock()
 | |
|     mocker.patch('freqtrade.persistence.cleanup', mock_cleanup)
 | |
| 
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
|     assert freqtrade.state == State.RUNNING
 | |
| 
 | |
|     assert freqtrade.clean()
 | |
|     assert freqtrade.state == State.STOPPED
 | |
|     assert log_has('Stopping trader and cleaning up modules...', caplog.record_tuples)
 | |
|     assert mock_cleanup.call_count == 1
 | |
| 
 | |
| 
 | |
| def test_worker_running(mocker, default_conf, caplog) -> None:
 | |
|     """
 | |
|     Test worker() method. Test when we start the bot
 | |
|     """
 | |
|     mock_throttle = MagicMock()
 | |
|     mocker.patch('freqtrade.freqtradebot.FreqtradeBot._throttle', mock_throttle)
 | |
| 
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     state = freqtrade.worker(old_state=None)
 | |
|     assert state is State.RUNNING
 | |
|     assert log_has('Changing state to: RUNNING', caplog.record_tuples)
 | |
|     assert mock_throttle.call_count == 1
 | |
| 
 | |
| 
 | |
| def test_worker_stopped(mocker, default_conf, caplog) -> None:
 | |
|     """
 | |
|     Test worker() method. Test when we stop the bot
 | |
|     """
 | |
|     mock_throttle = MagicMock()
 | |
|     mocker.patch('freqtrade.freqtradebot.FreqtradeBot._throttle', mock_throttle)
 | |
|     mock_sleep = mocker.patch('time.sleep', return_value=None)
 | |
| 
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
|     freqtrade.state = State.STOPPED
 | |
|     state = freqtrade.worker(old_state=State.RUNNING)
 | |
|     assert state is State.STOPPED
 | |
|     assert log_has('Changing state to: STOPPED', caplog.record_tuples)
 | |
|     assert mock_throttle.call_count == 0
 | |
|     assert mock_sleep.call_count == 1
 | |
| 
 | |
| 
 | |
| def test_throttle(mocker, default_conf, caplog) -> None:
 | |
|     """
 | |
|     Test _throttle() method
 | |
|     """
 | |
|     def func():
 | |
|         """
 | |
|         Test function to throttle
 | |
|         """
 | |
|         return 42
 | |
| 
 | |
|     caplog.set_level(logging.DEBUG)
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     start = time.time()
 | |
|     result = freqtrade._throttle(func, min_secs=0.1)
 | |
|     end = time.time()
 | |
| 
 | |
|     assert result == 42
 | |
|     assert end - start > 0.1
 | |
|     assert log_has('Throttling func for 0.10 seconds', caplog.record_tuples)
 | |
| 
 | |
|     result = freqtrade._throttle(func, min_secs=-1)
 | |
|     assert result == 42
 | |
| 
 | |
| 
 | |
| def test_throttle_with_assets(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test _throttle() method when the function passed can have parameters
 | |
|     """
 | |
|     def func(nb_assets=-1):
 | |
|         """
 | |
|         Test function to throttle
 | |
|         """
 | |
|         return nb_assets
 | |
| 
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     result = freqtrade._throttle(func, min_secs=0.1, nb_assets=666)
 | |
|     assert result == 666
 | |
| 
 | |
|     result = freqtrade._throttle(func, min_secs=0.1)
 | |
|     assert result == -1
 | |
| 
 | |
| 
 | |
| def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) -> None:
 | |
|     """
 | |
|     Test _gen_pair_whitelist() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
|     mocker.patch(
 | |
|         'freqtrade.freqtradebot.exchange.get_market_summaries',
 | |
|         return_value=get_market_summaries_data
 | |
|     )
 | |
| 
 | |
|     # Test to retrieved BTC sorted on BaseVolume
 | |
|     whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
 | |
|     assert whitelist == ['ZCL/BTC', 'ZEC/BTC', 'XZC/BTC', 'XWC/BTC']
 | |
| 
 | |
|     # Test to retrieved BTC sorted on OpenBuyOrders
 | |
|     whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders')
 | |
|     assert whitelist == ['XWC/BTC', 'ZCL/BTC', 'ZEC/BTC', 'XZC/BTC']
 | |
| 
 | |
|     # Test with USDT sorted on BaseVolume
 | |
|     whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT')
 | |
|     assert whitelist == ['XRP/USDT', 'XVG/USDT', 'XMR/USDT', 'ZEC/USDT']
 | |
| 
 | |
|     # Test with ETH (our fixture does not have ETH, but Bittrex returns them)
 | |
|     whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH')
 | |
|     assert whitelist == []
 | |
| 
 | |
| 
 | |
| @pytest.mark.skip(reason="Test not implemented")
 | |
| def test_refresh_whitelist() -> None:
 | |
|     """
 | |
|     Test _refresh_whitelist() method
 | |
|     """
 | |
|     pass
 | |
| 
 | |
| 
 | |
| def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     # Save state of current whitelist
 | |
|     whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade is not None
 | |
|     assert trade.stake_amount == 0.001
 | |
|     assert trade.is_open
 | |
|     assert trade.open_date is not None
 | |
|     assert trade.exchange == 'bittrex'
 | |
| 
 | |
|     # Simulate fulfilled LIMIT_BUY order for trade
 | |
|     trade.update(limit_buy_order)
 | |
| 
 | |
|     assert trade.open_rate == 0.00001099
 | |
|     assert trade.amount == 90.99181073
 | |
| 
 | |
|     assert whitelist == default_conf['exchange']['pair_whitelist']
 | |
| 
 | |
| 
 | |
| def test_create_trade_minimal_amount(default_conf, ticker, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     buy_mock = MagicMock(return_value='mocked_limit_buy')
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=buy_mock
 | |
|     )
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['stake_amount'] = 0.0005
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
| 
 | |
|     freqtrade.create_trade()
 | |
|     rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
 | |
|     assert rate * amount >= conf['stake_amount']
 | |
| 
 | |
| 
 | |
| def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.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)
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     with pytest.raises(DependencyException, match=r'.*stake amount.*'):
 | |
|         freqtrade.create_trade()
 | |
| 
 | |
| 
 | |
| def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
 | |
|     conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
| 
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     with pytest.raises(DependencyException, match=r'.*No currency pairs in whitelist.*'):
 | |
|         freqtrade.create_trade()
 | |
| 
 | |
| 
 | |
| def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['exchange']['pair_whitelist'] = ["ETH/BTC"]
 | |
|     conf['exchange']['pair_blacklist'] = ["ETH/BTC"]
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
| 
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     with pytest.raises(DependencyException, match=r'.*No currency pairs in whitelist.*'):
 | |
|         freqtrade.create_trade()
 | |
| 
 | |
| 
 | |
| def test_create_trade_no_signal(default_conf, mocker) -> None:
 | |
|     """
 | |
|     Test create_trade() method
 | |
|     """
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['dry_run'] = True
 | |
| 
 | |
|     patch_get_signal(mocker, value=(False, False))
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker_history=MagicMock(return_value=20),
 | |
|         get_balance=MagicMock(return_value=20)
 | |
|     )
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['stake_amount'] = 10
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
| 
 | |
|     Trade.query = MagicMock()
 | |
|     Trade.query.filter = MagicMock()
 | |
|     assert not freqtrade.create_trade()
 | |
| 
 | |
| 
 | |
| def test_process_trade_creation(default_conf, ticker, limit_buy_order,
 | |
|                                 health, mocker, caplog) -> None:
 | |
|     """
 | |
|     Test the trade creation in _process() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_wallet_health=health,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy'),
 | |
|         get_order=MagicMock(return_value=limit_buy_order)
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trades = Trade.query.filter(Trade.is_open.is_(True)).all()
 | |
|     assert not trades
 | |
| 
 | |
|     result = freqtrade._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 == 'bittrex'
 | |
|     assert trade.open_rate == 0.00001099
 | |
|     assert trade.amount == 90.99181073703367
 | |
| 
 | |
|     assert log_has(
 | |
|         'Checking buy signals to create a new trade with stake_amount: 0.001000 ...',
 | |
|         caplog.record_tuples
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None:
 | |
|     """
 | |
|     Test _process() method when a RequestException happens
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_wallet_health=health,
 | |
|         buy=MagicMock(side_effect=requests.exceptions.RequestException)
 | |
|     )
 | |
|     sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
 | |
| 
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
|     result = freqtrade._process()
 | |
|     assert result is False
 | |
|     assert sleep_mock.has_calls()
 | |
| 
 | |
| 
 | |
| def test_process_operational_exception(default_conf, ticker, health, mocker) -> None:
 | |
|     """
 | |
|     Test _process() method when an OperationalException happens
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     msg_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_wallet_health=health,
 | |
|         buy=MagicMock(side_effect=OperationalException)
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
|     assert freqtrade.state == State.RUNNING
 | |
| 
 | |
|     result = freqtrade._process()
 | |
|     assert result is False
 | |
|     assert freqtrade.state == State.STOPPED
 | |
|     assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
 | |
| 
 | |
| 
 | |
| def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker) -> None:
 | |
|     """
 | |
|     Test _process()
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_wallet_health=health,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy'),
 | |
|         get_order=MagicMock(return_value=limit_buy_order)
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trades = Trade.query.filter(Trade.is_open.is_(True)).all()
 | |
|     assert not trades
 | |
|     result = freqtrade._process()
 | |
|     assert result is True
 | |
|     trades = Trade.query.filter(Trade.is_open.is_(True)).all()
 | |
|     assert len(trades) == 1
 | |
| 
 | |
|     result = freqtrade._process()
 | |
|     assert result is False
 | |
| 
 | |
| 
 | |
| def test_balance_fully_ask_side(mocker) -> None:
 | |
|     """
 | |
|     Test get_target_bid() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 0.0}})
 | |
| 
 | |
|     assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20
 | |
| 
 | |
| 
 | |
| def test_balance_fully_last_side(mocker) -> None:
 | |
|     """
 | |
|     Test get_target_bid() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}})
 | |
| 
 | |
|     assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10
 | |
| 
 | |
| 
 | |
| def test_balance_bigger_last_ask(mocker) -> None:
 | |
|     """
 | |
|     Test get_target_bid() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'ask_last_balance': 1.0}})
 | |
| 
 | |
|     assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5
 | |
| 
 | |
| 
 | |
| def test_process_maybe_execute_buy(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test process_maybe_execute_buy() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=True))
 | |
|     assert freqtrade.process_maybe_execute_buy()
 | |
| 
 | |
|     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.create_trade', MagicMock(return_value=False))
 | |
|     assert not freqtrade.process_maybe_execute_buy()
 | |
| 
 | |
| 
 | |
| def test_process_maybe_execute_buy_exception(mocker, default_conf, caplog) -> None:
 | |
|     """
 | |
|     Test exception on process_maybe_execute_buy() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     mocker.patch(
 | |
|         'freqtrade.freqtradebot.FreqtradeBot.create_trade',
 | |
|         MagicMock(side_effect=DependencyException)
 | |
|     )
 | |
|     freqtrade.process_maybe_execute_buy()
 | |
|     log_has('Unable to create trade:', caplog.record_tuples)
 | |
| 
 | |
| 
 | |
| def test_process_maybe_execute_sell(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test process_maybe_execute_sell() method
 | |
|     """
 | |
|     freqtrade = get_patched_freqtradebot(mocker, default_conf)
 | |
| 
 | |
|     mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
 | |
|     mocker.patch('freqtrade.freqtradebot.exchange.get_order', return_value=1)
 | |
| 
 | |
|     trade = MagicMock()
 | |
|     trade.open_order_id = '123'
 | |
|     assert not freqtrade.process_maybe_execute_sell(trade)
 | |
|     trade.is_open = True
 | |
|     trade.open_order_id = None
 | |
|     # Assert we call handle_trade() if trade is feasible for execution
 | |
|     assert freqtrade.process_maybe_execute_sell(trade)
 | |
| 
 | |
| 
 | |
| def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -> None:
 | |
|     """
 | |
|     Test check_handle() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=MagicMock(return_value={
 | |
|             'bid': 0.00001172,
 | |
|             'ask': 0.00001173,
 | |
|             'last': 0.00001172
 | |
|         }),
 | |
|         buy=MagicMock(return_value='mocked_limit_buy'),
 | |
|         sell=MagicMock(return_value='mocked_limit_sell')
 | |
|     )
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
 | |
| 
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     trade.update(limit_buy_order)
 | |
|     assert trade.is_open is True
 | |
| 
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade) is True
 | |
|     assert trade.open_order_id == 'mocked_limit_sell'
 | |
| 
 | |
|     # Simulate fulfilled LIMIT_SELL order for trade
 | |
|     trade.update(limit_sell_order)
 | |
| 
 | |
|     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
 | |
| 
 | |
| 
 | |
| def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
 | |
|     """
 | |
|     Test check_handle() method
 | |
|     """
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf.update({'experimental': {'use_sell_signal': True}})
 | |
| 
 | |
|     patch_get_signal(mocker, value=(True, True))
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
| 
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     # Buy and Sell triggering, so doing nothing ...
 | |
|     trades = Trade.query.all()
 | |
|     nb_trades = len(trades)
 | |
|     assert nb_trades == 0
 | |
| 
 | |
|     # Buy is triggering, so buying ...
 | |
|     patch_get_signal(mocker, value=(True, False))
 | |
|     freqtrade.create_trade()
 | |
|     trades = Trade.query.all()
 | |
|     nb_trades = len(trades)
 | |
|     assert nb_trades == 1
 | |
|     assert trades[0].is_open is True
 | |
| 
 | |
|     # Buy and Sell are not triggering, so doing nothing ...
 | |
|     patch_get_signal(mocker, value=(False, False))
 | |
|     assert freqtrade.handle_trade(trades[0]) is False
 | |
|     trades = Trade.query.all()
 | |
|     nb_trades = len(trades)
 | |
|     assert nb_trades == 1
 | |
|     assert trades[0].is_open is True
 | |
| 
 | |
|     # Buy and Sell are triggering, so doing nothing ...
 | |
|     patch_get_signal(mocker, value=(True, True))
 | |
|     assert freqtrade.handle_trade(trades[0]) is False
 | |
|     trades = Trade.query.all()
 | |
|     nb_trades = len(trades)
 | |
|     assert nb_trades == 1
 | |
|     assert trades[0].is_open is True
 | |
| 
 | |
|     # Sell is triggering, guess what : we are Selling!
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     trades = Trade.query.all()
 | |
|     assert freqtrade.handle_trade(trades[0]) is True
 | |
| 
 | |
| 
 | |
| def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
 | |
|     """
 | |
|     Test check_handle() method
 | |
|     """
 | |
|     caplog.set_level(logging.DEBUG)
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf.update({'experimental': {'use_sell_signal': True}})
 | |
| 
 | |
|     patch_get_signal(mocker, value=(True, False))
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     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
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade)
 | |
|     assert log_has('Required profit reached. Selling..', caplog.record_tuples)
 | |
| 
 | |
| 
 | |
| def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None:
 | |
|     """
 | |
|     Test check_handle() method
 | |
|     """
 | |
|     caplog.set_level(logging.DEBUG)
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf.update({'experimental': {'use_sell_signal': True}})
 | |
| 
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
| 
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     trade.is_open = True
 | |
| 
 | |
|     patch_get_signal(mocker, value=(False, False))
 | |
|     assert not freqtrade.handle_trade(trade)
 | |
| 
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade)
 | |
|     assert log_has('Sell signal received. Selling..', caplog.record_tuples)
 | |
| 
 | |
| 
 | |
| def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker) -> None:
 | |
|     """
 | |
|     Test check_handle() method
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     # Create trade and sell it
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     trade.update(limit_buy_order)
 | |
|     trade.update(limit_sell_order)
 | |
|     assert trade.is_open is False
 | |
| 
 | |
|     with pytest.raises(ValueError, match=r'.*closed trade.*'):
 | |
|         freqtrade.handle_trade(trade)
 | |
| 
 | |
| 
 | |
| def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker) -> None:
 | |
|     """
 | |
|     Test check_handle_timedout() method
 | |
|     """
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_order=MagicMock(return_value=limit_buy_order_old),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trade_buy = Trade(
 | |
|         pair='ETH/BTC',
 | |
|         open_rate=0.00001099,
 | |
|         exchange='BITTREX',
 | |
|         open_order_id='123456789',
 | |
|         amount=90.99181073,
 | |
|         fee=0.0,
 | |
|         stake_amount=1,
 | |
|         open_date=arrow.utcnow().shift(minutes=-601).datetime,
 | |
|         is_open=True
 | |
|     )
 | |
| 
 | |
|     Trade.session.add(trade_buy)
 | |
| 
 | |
|     # check it does cancel buy orders over the time limit
 | |
|     freqtrade.check_handle_timedout(600)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
|     assert rpc_mock.call_count == 1
 | |
|     trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
 | |
|     nb_trades = len(trades)
 | |
|     assert nb_trades == 0
 | |
| 
 | |
| 
 | |
| def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker) -> None:
 | |
|     """
 | |
|     Test check_handle_timedout() method
 | |
|     """
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_order=MagicMock(return_value=limit_sell_order_old),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trade_sell = Trade(
 | |
|         pair='ETH/BTC',
 | |
|         open_rate=0.00001099,
 | |
|         exchange='BITTREX',
 | |
|         open_order_id='123456789',
 | |
|         amount=90.99181073,
 | |
|         fee=0.0,
 | |
|         stake_amount=1,
 | |
|         open_date=arrow.utcnow().shift(hours=-5).datetime,
 | |
|         close_date=arrow.utcnow().shift(minutes=-601).datetime,
 | |
|         is_open=False
 | |
|     )
 | |
| 
 | |
|     Trade.session.add(trade_sell)
 | |
| 
 | |
|     # check it does cancel sell orders over the time limit
 | |
|     freqtrade.check_handle_timedout(600)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
|     assert rpc_mock.call_count == 1
 | |
|     assert trade_sell.is_open is True
 | |
| 
 | |
| 
 | |
| def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
 | |
|                                        mocker) -> None:
 | |
|     """
 | |
|     Test check_handle_timedout() method
 | |
|     """
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_order=MagicMock(return_value=limit_buy_order_old_partial),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trade_buy = Trade(
 | |
|         pair='ETH/BTC',
 | |
|         open_rate=0.00001099,
 | |
|         exchange='BITTREX',
 | |
|         open_order_id='123456789',
 | |
|         amount=90.99181073,
 | |
|         fee=0.0,
 | |
|         stake_amount=1,
 | |
|         open_date=arrow.utcnow().shift(minutes=-601).datetime,
 | |
|         is_open=True
 | |
|     )
 | |
| 
 | |
|     Trade.session.add(trade_buy)
 | |
| 
 | |
|     # check it does cancel buy orders over the time limit
 | |
|     # note this is for a partially-complete buy order
 | |
|     freqtrade.check_handle_timedout(600)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
|     assert rpc_mock.call_count == 1
 | |
|     trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
 | |
|     assert len(trades) == 1
 | |
|     assert trades[0].amount == 23.0
 | |
|     assert trades[0].stake_amount == trade_buy.open_rate * trades[0].amount
 | |
| 
 | |
| 
 | |
| def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -> None:
 | |
|     """
 | |
|     Test check_handle_timedout() method when get_order throw an exception
 | |
|     """
 | |
|     patch_RPCManager(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     patch_coinmarketcap(mocker)
 | |
| 
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.FreqtradeBot',
 | |
|         handle_timedout_limit_buy=MagicMock(),
 | |
|         handle_timedout_limit_sell=MagicMock(),
 | |
|     )
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker,
 | |
|         get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trade_buy = Trade(
 | |
|         pair='ETH/BTC',
 | |
|         open_rate=0.00001099,
 | |
|         exchange='BITTREX',
 | |
|         open_order_id='123456789',
 | |
|         amount=90.99181073,
 | |
|         fee=0.0,
 | |
|         stake_amount=1,
 | |
|         open_date=arrow.utcnow().shift(minutes=-601).datetime,
 | |
|         is_open=True
 | |
|     )
 | |
| 
 | |
|     Trade.session.add(trade_buy)
 | |
|     regexp = re.compile(
 | |
|         'Cannot query order for Trade(id=1, pair=ETH/BTC, amount=90.99181073, '
 | |
|         'open_rate=0.00001099, open_since=10 hours ago) due to Traceback (most '
 | |
|         'recent call last):\n.*'
 | |
|     )
 | |
| 
 | |
|     freqtrade.check_handle_timedout(600)
 | |
|     assert filter(regexp.match, caplog.record_tuples)
 | |
| 
 | |
| 
 | |
| def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test handle_timedout_limit_buy() method
 | |
|     """
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
| 
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     Trade.session = MagicMock()
 | |
|     trade = MagicMock()
 | |
|     order = {'remaining': 1,
 | |
|              'amount': 1}
 | |
|     assert freqtrade.handle_timedout_limit_buy(trade, order)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
|     order['amount'] = 2
 | |
|     assert not freqtrade.handle_timedout_limit_buy(trade, order)
 | |
|     assert cancel_order_mock.call_count == 2
 | |
| 
 | |
| 
 | |
| def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
 | |
|     """
 | |
|     Test handle_timedout_limit_sell() method
 | |
|     """
 | |
|     patch_RPCManager(mocker)
 | |
|     cancel_order_mock = MagicMock()
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         cancel_order=cancel_order_mock
 | |
|     )
 | |
| 
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     trade = MagicMock()
 | |
|     order = {'remaining': 1,
 | |
|              'amount': 1}
 | |
|     assert freqtrade.handle_timedout_limit_sell(trade, order)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
|     order['amount'] = 2
 | |
|     assert not freqtrade.handle_timedout_limit_sell(trade, order)
 | |
|     # Assert cancel_order was not called (callcount remains unchanged)
 | |
|     assert cancel_order_mock.call_count == 1
 | |
| 
 | |
| 
 | |
| def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
 | |
|     """
 | |
|     Test execute_sell() method with a ticker going UP
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker
 | |
|     )
 | |
|     mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     # Create some test data
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     # Increase the price and sell it
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker_sell_up
 | |
|     )
 | |
| 
 | |
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
 | |
| 
 | |
|     assert rpc_mock.call_count == 2
 | |
|     assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert 'Amount' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert 'Profit' 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) -> None:
 | |
|     """
 | |
|     Test execute_sell() method with a ticker going DOWN
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     # Create some test data
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     # Decrease the price and sell it
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker_sell_down
 | |
|     )
 | |
| 
 | |
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
 | |
| 
 | |
|     assert rpc_mock.call_count == 2
 | |
|     assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert 'Amount' 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_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
 | |
|     """
 | |
|     Test execute_sell() method with a ticker going DOWN and with a bot config empty
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     # Create some test data
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     # Increase the price and sell it
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker_sell_up
 | |
|     )
 | |
|     freqtrade.config = {}
 | |
| 
 | |
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'])
 | |
| 
 | |
|     assert rpc_mock.call_count == 2
 | |
|     assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert 'Amount' 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]
 | |
| 
 | |
| 
 | |
| def test_execute_sell_without_conf_sell_down(default_conf, ticker,
 | |
|                                              ticker_sell_down, mocker) -> None:
 | |
|     """
 | |
|     Test execute_sell() method with a ticker going DOWN and with a bot config empty
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     rpc_mock = patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker, value={'price_usd': 12345.0})
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker
 | |
|     )
 | |
|     freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
 | |
| 
 | |
|     # Create some test data
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     assert trade
 | |
| 
 | |
|     # Decrease the price and sell it
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=ticker_sell_down
 | |
|     )
 | |
| 
 | |
|     freqtrade.config = {}
 | |
|     freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'])
 | |
| 
 | |
|     assert rpc_mock.call_count == 2
 | |
|     assert 'Selling' in rpc_mock.call_args_list[-1][0][0]
 | |
|     assert '[ETH/BTC]' 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]
 | |
| 
 | |
| 
 | |
| def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -> None:
 | |
|     """
 | |
|     Test sell_profit_only feature when enabled
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=MagicMock(return_value={
 | |
|             'bid': 0.00002172,
 | |
|             'ask': 0.00002173,
 | |
|             'last': 0.00002172
 | |
|         }),
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['experimental'] = {
 | |
|         'use_sell_signal': True,
 | |
|         'sell_profit_only': True,
 | |
|     }
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     trade.update(limit_buy_order)
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade) is True
 | |
| 
 | |
| 
 | |
| def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker) -> None:
 | |
|     """
 | |
|     Test sell_profit_only feature when disabled
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=MagicMock(return_value={
 | |
|             'bid': 0.00002172,
 | |
|             'ask': 0.00002173,
 | |
|             'last': 0.00002172
 | |
|         }),
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['experimental'] = {
 | |
|         'use_sell_signal': True,
 | |
|         'sell_profit_only': False,
 | |
|     }
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     trade.update(limit_buy_order)
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade) is True
 | |
| 
 | |
| 
 | |
| def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) -> None:
 | |
|     """
 | |
|     Test sell_profit_only feature when enabled and we have a loss
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=MagicMock(return_value={
 | |
|             'bid': 0.00000172,
 | |
|             'ask': 0.00000173,
 | |
|             'last': 0.00000172
 | |
|         }),
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['experimental'] = {
 | |
|         'use_sell_signal': True,
 | |
|         'sell_profit_only': True,
 | |
|     }
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     trade.update(limit_buy_order)
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade) is False
 | |
| 
 | |
| 
 | |
| def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) -> None:
 | |
|     """
 | |
|     Test sell_profit_only feature when enabled and we have a loss
 | |
|     """
 | |
|     patch_get_signal(mocker)
 | |
|     patch_RPCManager(mocker)
 | |
|     patch_coinmarketcap(mocker)
 | |
|     mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
 | |
|     mocker.patch.multiple(
 | |
|         'freqtrade.freqtradebot.exchange',
 | |
|         validate_pairs=MagicMock(),
 | |
|         get_ticker=MagicMock(return_value={
 | |
|             'bid': 0.00000172,
 | |
|             'ask': 0.00000173,
 | |
|             'last': 0.00000172
 | |
|         }),
 | |
|         buy=MagicMock(return_value='mocked_limit_buy')
 | |
|     )
 | |
| 
 | |
|     conf = deepcopy(default_conf)
 | |
|     conf['experimental'] = {
 | |
|         'use_sell_signal': True,
 | |
|         'sell_profit_only': False,
 | |
|     }
 | |
| 
 | |
|     freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
 | |
|     freqtrade.create_trade()
 | |
| 
 | |
|     trade = Trade.query.first()
 | |
|     trade.update(limit_buy_order)
 | |
|     patch_get_signal(mocker, value=(False, True))
 | |
|     assert freqtrade.handle_trade(trade) is True
 |