Merge pull request #356 from kryofly/test_coverage

Test coverage
This commit is contained in:
Janne Sinivirta 2018-01-25 09:31:06 +02:00 committed by GitHub
commit 7dc63c06e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 345 additions and 80 deletions

View File

@ -82,35 +82,39 @@ def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
return dataframe
# FIX: Maybe return False, if an error has occured,
# Otherwise we might mask an error as an non-signal-scenario
def get_signal(pair: str, interval: int) -> (bool, bool):
"""
Calculates current signal based several technical analysis indicators
:param pair: pair in format BTC_ANT or BTC-ANT
:return: (True, False) if pair is good for buying and not for selling
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
"""
ticker_hist = get_ticker_history(pair, interval)
if not ticker_hist:
logger.warning('Empty ticker history for pair %s', pair)
return (False, False)
return (False, False) # return False ?
try:
dataframe = analyze_ticker(ticker_hist)
except ValueError as ex:
logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex))
return (False, False)
return (False, False) # return False ?
except Exception as ex:
logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex))
return (False, False)
return (False, False) # return False ?
if dataframe.empty:
return (False, False)
logger.warning('Empty dataframe for pair %s', pair)
return (False, False) # return False ?
latest = dataframe.iloc[-1]
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
if signal_date < arrow.now() - timedelta(minutes=10):
return (False, False)
logger.warning('Too old dataframe for pair %s', pair)
return (False, False) # return False ?
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell))

View File

@ -55,12 +55,49 @@ def refresh_whitelist(whitelist: List[str]) -> List[str]:
return final_list
def process_maybe_execute_buy(conf, interval):
"""
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']), interval):
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, interval):
"""
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, interval)
return False
def _process(interval: int, 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:
@ -78,33 +115,16 @@ def _process(interval: int, 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']), interval)
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, interval)
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, interval) or state_changed
state_changed |= process_maybe_execute_sell(trade, interval)
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...',
@ -121,6 +141,59 @@ def _process(interval: int, nb_assets: Optional[int] = 0) -> bool:
return state_changed
# FIX: 20180110, why is cancel.order unconditionally here, whereas
# it is conditionally called in the
# handle_timedout_limit_sell()?
def handle_timedout_limit_buy(trade: Trade, order: Dict) -> bool:
"""Buy timeout - cancel order
:return: True if order was fully cancelled
"""
exchange.cancel_order(trade.open_order_id)
if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
# FIX? do we really need to flush, caller of
# check_handle_timedout will flush afterwards
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
return True
else:
# if trade is partially complete, edit the stake details for the trade
# and close the order
trade.amount = order['amount'] - order['remaining']
trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
return False
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
def handle_timedout_limit_sell(trade: Trade, order: Dict) -> bool:
"""
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
exchange.cancel_order(trade.open_order_id)
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/')))
logger.info('Sell order timeout for %s.', trade)
return True
else:
# TODO: figure out how to handle partially complete sell orders
return False
def check_handle_timedout(timeoutvalue: int) -> None:
"""
Check if any orders are timed out and cancel if neccessary
@ -142,41 +215,9 @@ def check_handle_timedout(timeoutvalue: int) -> None:
continue
if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold:
# Buy timeout - cancel order
exchange.cancel_order(trade.open_order_id)
if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
else:
# if trade is partially complete, edit the stake details for the trade
# and close the order
trade.amount = order['amount'] - order['remaining']
trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
handle_timedout_limit_buy(trade, order)
elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold:
# Sell timeout - cancel order and update trade
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
exchange.cancel_order(trade.open_order_id)
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/')))
logger.info('Sell order timeout for %s.', trade)
return True
else:
# TODO: figure out how to handle partially complete sell orders
pass
handle_timedout_limit_sell(trade, order)
def execute_sell(trade: Trade, limit: float) -> None:
@ -435,7 +476,7 @@ def cleanup() -> None:
exit(0)
def main(sysargv=sys.argv[1:]) -> None:
def main(sysargv=sys.argv[1:]) -> int:
"""
Loads and validates the config and handles the main loop
:return: None
@ -447,7 +488,7 @@ def main(sysargv=sys.argv[1:]) -> None:
# A subcommand has been issued
if hasattr(args, 'func'):
args.func(args)
exit(0)
return 0
# Initialize logger
logging.basicConfig(
@ -508,7 +549,8 @@ def main(sysargv=sys.argv[1:]) -> None:
logger.exception('Got fatal exception!')
finally:
cleanup()
return 0
if __name__ == '__main__':
main()
main(sys.argv[1:])

View File

@ -109,6 +109,12 @@ def download_pairs(datadir, pairs: List[str], ticker_interval: int) -> bool:
return True
def file_dump_json(filename, data):
with open(filename, "wt") as fp:
json.dump(data, fp)
# FIX: 20180110, suggest rename interval to tick_interval
def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> bool:
"""
Download the latest 1 and 5 ticker intervals from Bittrex for the pairs passed in parameters

View File

@ -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 """

View File

@ -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

View File

@ -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://', '')

View File

@ -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)

View File

@ -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',

View File

@ -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)