From f8cc08e2a15c004a23f186004d70c88d7eec21af Mon Sep 17 00:00:00 2001 From: kryofly Date: Wed, 10 Jan 2018 09:09:33 +0100 Subject: [PATCH] small refactor splitting the _process() --- freqtrade/main.py | 62 ++++++++++++++++++++++----------- freqtrade/tests/conftest.py | 9 +++++ freqtrade/tests/test_analyze.py | 26 +++++--------- freqtrade/tests/test_main.py | 29 +++++++++++++++ 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index efd0d7c44..4e69e572d 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -72,12 +72,49 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]: return final_list +def process_maybe_execute_buy(conf): + """ + Tries to execute a buy trade in a safe way + :return: True if executed + """ + try: + # Create entity and execute trade + if create_trade(float(conf['stake_amount'])): + return True + else: + logger.info( + 'Checked all whitelisted currencies. ' + 'Found no suitable entry positions for buying. Will keep looking ...' + ) + return False + except DependencyException as exception: + logger.warning('Unable to create trade: %s', exception) + return False + + +def process_maybe_execute_sell(trade): + """ + Tries to execute a sell trade + :return: True if executed + """ + # Get order details for actual price per unit + if trade.open_order_id: + # Update trade with order values + logger.info('Got open order for %s', trade) + trade.update(exchange.get_order(trade.open_order_id)) + + if trade.is_open and trade.open_order_id is None: + # Check if we can sell our current pair + return handle_trade(trade) + return False + + def _process(nb_assets: Optional[int] = 0) -> bool: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. :param: nb_assets: the maximum number of pairs to be traded at the same time - :return: True if a trade has been created or closed, False otherwise + :return: True if one or more trades has been created or closed, False otherwise """ state_changed = False try: @@ -95,33 +132,16 @@ def _process(nb_assets: Optional[int] = 0) -> bool: # Query trades from persistence layer trades = Trade.query.filter(Trade.is_open.is_(True)).all() if len(trades) < _CONF['max_open_trades']: - try: - # Create entity and execute trade - state_changed = create_trade(float(_CONF['stake_amount'])) - if not state_changed: - logger.info( - 'Checked all whitelisted currencies. ' - 'Found no suitable entry positions for buying. Will keep looking ...' - ) - except DependencyException as exception: - logger.warning('Unable to create trade: %s', exception) + state_changed = process_maybe_execute_buy(_CONF) for trade in trades: - # Get order details for actual price per unit - if trade.open_order_id: - # Update trade with order values - logger.info('Got open order for %s', trade) - trade.update(exchange.get_order(trade.open_order_id)) - - if trade.is_open and trade.open_order_id is None: - # Check if we can sell our current pair - state_changed = handle_trade(trade) or state_changed + state_changed = process_maybe_execute_sell(trade) or state_changed if 'unfilledtimeout' in _CONF: # Check and handle any timed out open orders check_handle_timedout(_CONF['unfilledtimeout']) - Trade.session.flush() + except (requests.exceptions.RequestException, json.JSONDecodeError) as error: logger.warning( 'Got %s in _process(), retrying in 30 seconds...', diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 788585345..c93830dd6 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring from datetime import datetime from unittest.mock import MagicMock +from functools import reduce import pytest import arrow @@ -10,6 +11,14 @@ from telegram import Message, Chat, 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 """ diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 67a5a54f5..17f76ac8e 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -1,7 +1,7 @@ # pragma pylint: disable=missing-docstring,W0621 import json -from functools import reduce from unittest.mock import MagicMock +import freqtrade.tests.conftest as tt # test tools import arrow import datetime @@ -12,14 +12,6 @@ from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, popula get_signal, SignalType, populate_sell_trend -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 def result(): with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file: @@ -78,8 +70,8 @@ def test_returns_latest_sell_signal(mocker): def test_get_signal_empty(mocker, caplog): mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) assert not get_signal('foo', SignalType.BUY) - assert log_has('Empty ticker history for pair foo', - caplog.record_tuples) + assert tt.log_has('Empty ticker history for pair foo', + caplog.record_tuples) def test_get_signal_execption_valueerror(mocker, caplog): @@ -87,8 +79,8 @@ def test_get_signal_execption_valueerror(mocker, caplog): mocker.patch('freqtrade.analyze.analyze_ticker', side_effect=ValueError('xyz')) assert not get_signal('foo', SignalType.BUY) - assert log_has('Unable to analyze ticker for pair foo: xyz', - caplog.record_tuples) + assert tt.log_has('Unable to analyze ticker for pair foo: xyz', + caplog.record_tuples) # This error should never occur becase analyze_ticker is run first, @@ -97,8 +89,8 @@ def test_get_signal_empty_dataframe(mocker, caplog): mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([])) assert not get_signal('xyz', SignalType.BUY) - assert log_has('Empty dataframe for pair xyz', - caplog.record_tuples) + assert tt.log_has('Empty dataframe for pair xyz', + caplog.record_tuples) def test_get_signal_old_dataframe(mocker, caplog): @@ -108,8 +100,8 @@ def test_get_signal_old_dataframe(mocker, caplog): ticks = DataFrame([{'buy': 1, 'date': oldtime}]) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks)) assert not get_signal('xyz', SignalType.BUY) - assert log_has('Too old dataframe for pair xyz', - caplog.record_tuples) + assert tt.log_has('Too old dataframe for pair xyz', + caplog.record_tuples) def test_get_signal_handles_exceptions(mocker): diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index cceb555f7..110c40862 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -1,6 +1,7 @@ # pragma pylint: disable=missing-docstring,C0103 import copy from unittest.mock import MagicMock +import freqtrade.tests.conftest as tt # test tools import pytest import requests @@ -50,6 +51,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) + mocker.patch('freqtrade.main.create_trade', return_value=False) + assert not main.process_maybe_execute_buy(default_conf) + + +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) + 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) + + +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) + 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())