small refactor splitting the _process()
This commit is contained in:
parent
ad2328bbd8
commit
f8cc08e2a1
@ -72,12 +72,49 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]:
|
|||||||
return final_list
|
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:
|
def _process(nb_assets: Optional[int] = 0) -> bool:
|
||||||
"""
|
"""
|
||||||
Queries the persistence layer for open trades and handles them,
|
Queries the persistence layer for open trades and handles them,
|
||||||
otherwise a new trade is created.
|
otherwise a new trade is created.
|
||||||
:param: nb_assets: the maximum number of pairs to be traded at the same time
|
: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
|
state_changed = False
|
||||||
try:
|
try:
|
||||||
@ -95,33 +132,16 @@ def _process(nb_assets: Optional[int] = 0) -> bool:
|
|||||||
# Query trades from persistence layer
|
# Query trades from persistence layer
|
||||||
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
|
||||||
if len(trades) < _CONF['max_open_trades']:
|
if len(trades) < _CONF['max_open_trades']:
|
||||||
try:
|
state_changed = process_maybe_execute_buy(_CONF)
|
||||||
# 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)
|
|
||||||
|
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
# Get order details for actual price per unit
|
state_changed = process_maybe_execute_sell(trade) or state_changed
|
||||||
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
|
|
||||||
|
|
||||||
if 'unfilledtimeout' in _CONF:
|
if 'unfilledtimeout' in _CONF:
|
||||||
# Check and handle any timed out open orders
|
# Check and handle any timed out open orders
|
||||||
check_handle_timedout(_CONF['unfilledtimeout'])
|
check_handle_timedout(_CONF['unfilledtimeout'])
|
||||||
|
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Got %s in _process(), retrying in 30 seconds...',
|
'Got %s in _process(), retrying in 30 seconds...',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring
|
# pragma pylint: disable=missing-docstring
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import arrow
|
import arrow
|
||||||
@ -10,6 +11,14 @@ from telegram import Message, Chat, Update
|
|||||||
from freqtrade.misc import CONF_SCHEMA
|
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")
|
@pytest.fixture(scope="module")
|
||||||
def default_conf():
|
def default_conf():
|
||||||
""" Returns validated configuration suitable for most tests """
|
""" Returns validated configuration suitable for most tests """
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0621
|
# pragma pylint: disable=missing-docstring,W0621
|
||||||
import json
|
import json
|
||||||
from functools import reduce
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import datetime
|
import datetime
|
||||||
@ -12,14 +12,6 @@ from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, popula
|
|||||||
get_signal, SignalType, populate_sell_trend
|
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
|
@pytest.fixture
|
||||||
def result():
|
def result():
|
||||||
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
|
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):
|
def test_get_signal_empty(mocker, caplog):
|
||||||
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
|
||||||
assert not get_signal('foo', SignalType.BUY)
|
assert not get_signal('foo', SignalType.BUY)
|
||||||
assert log_has('Empty ticker history for pair foo',
|
assert tt.log_has('Empty ticker history for pair foo',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_execption_valueerror(mocker, caplog):
|
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',
|
mocker.patch('freqtrade.analyze.analyze_ticker',
|
||||||
side_effect=ValueError('xyz'))
|
side_effect=ValueError('xyz'))
|
||||||
assert not get_signal('foo', SignalType.BUY)
|
assert not get_signal('foo', SignalType.BUY)
|
||||||
assert log_has('Unable to analyze ticker for pair foo: xyz',
|
assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
# This error should never occur becase analyze_ticker is run first,
|
# 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.get_ticker_history', return_value=1)
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
|
||||||
assert not get_signal('xyz', SignalType.BUY)
|
assert not get_signal('xyz', SignalType.BUY)
|
||||||
assert log_has('Empty dataframe for pair xyz',
|
assert tt.log_has('Empty dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_old_dataframe(mocker, caplog):
|
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}])
|
ticks = DataFrame([{'buy': 1, 'date': oldtime}])
|
||||||
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
|
||||||
assert not get_signal('xyz', SignalType.BUY)
|
assert not get_signal('xyz', SignalType.BUY)
|
||||||
assert log_has('Too old dataframe for pair xyz',
|
assert tt.log_has('Too old dataframe for pair xyz',
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_get_signal_handles_exceptions(mocker):
|
def test_get_signal_handles_exceptions(mocker):
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring,C0103
|
# pragma pylint: disable=missing-docstring,C0103
|
||||||
import copy
|
import copy
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
import freqtrade.tests.conftest as tt # test tools
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
@ -50,6 +51,34 @@ def test_main_start_hyperopt(mocker):
|
|||||||
assert call_args.func is not None
|
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):
|
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
|
||||||
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
mocker.patch.dict('freqtrade.main._CONF', default_conf)
|
||||||
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
|
||||||
|
Loading…
Reference in New Issue
Block a user