mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-14 11:51:19 +00:00
Merge pull request #1330 from freqtrade/feat/diff_order_types
Add support for different order types
This commit is contained in:
@@ -30,6 +30,7 @@ def log_has(line, logs):
|
||||
def patch_exchange(mocker, api_mock=None) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value="Bittrex"))
|
||||
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value="bittrex"))
|
||||
if api_mock:
|
||||
|
||||
@@ -355,6 +355,36 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_order_types(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
|
||||
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'}
|
||||
Exchange(default_conf)
|
||||
|
||||
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
|
||||
default_conf['order_types'] = {'buy': 'limit', 'sell': 'limit', 'stoploss': 'market'}
|
||||
|
||||
with pytest.raises(OperationalException,
|
||||
match=r'Exchange .* does not support market orders.'):
|
||||
Exchange(default_conf)
|
||||
|
||||
|
||||
def test_validate_order_types_not_in_config(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
|
||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
|
||||
|
||||
conf = copy.deepcopy(default_conf)
|
||||
Exchange(conf)
|
||||
|
||||
|
||||
def test_exchange_has(default_conf, mocker):
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
assert not exchange.exchange_has('ASDFASDF')
|
||||
@@ -373,7 +403,7 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_buy_' in order['id']
|
||||
|
||||
@@ -381,47 +411,64 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
def test_buy_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_buy_order = MagicMock(return_value={
|
||||
order_type = 'market'
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
}
|
||||
})
|
||||
default_conf['dry_run'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'buy'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
|
||||
def test_sell_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_sell_' in order['id']
|
||||
|
||||
@@ -429,7 +476,8 @@ def test_sell_dry_run(default_conf, mocker):
|
||||
def test_sell_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
|
||||
api_mock.create_limit_sell_order = MagicMock(return_value={
|
||||
order_type = 'market'
|
||||
api_mock.create_order = MagicMock(return_value={
|
||||
'id': order_id,
|
||||
'info': {
|
||||
'foo': 'bar'
|
||||
@@ -438,32 +486,48 @@ def test_sell_prod(default_conf, mocker):
|
||||
default_conf['dry_run'] = False
|
||||
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
|
||||
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
assert order['id'] == order_id
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] is None
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
assert api_mock.create_order.call_args[0][3] == 1
|
||||
assert api_mock.create_order.call_args[0][4] == 200
|
||||
|
||||
# test exception handling
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
|
||||
|
||||
def test_get_balance_dry_run(default_conf, mocker):
|
||||
|
||||
@@ -88,8 +88,8 @@ def test_load_strategy_invalid_directory(result, caplog):
|
||||
def test_load_not_found_strategy():
|
||||
strategy = StrategyResolver()
|
||||
with pytest.raises(ImportError,
|
||||
match=r'Impossible to load Strategy \'NotFoundStrategy\'.'
|
||||
r' This class does not exist or contains Python code errors'):
|
||||
match=r"Impossible to load Strategy 'NotFoundStrategy'."
|
||||
r" This class does not exist or contains Python code errors"):
|
||||
strategy._load_strategy(strategy_name='NotFoundStrategy', config={})
|
||||
|
||||
|
||||
@@ -182,6 +182,42 @@ def test_strategy_override_process_only_new_candles(caplog):
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_strategy_override_order_types(caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
order_types = {
|
||||
'buy': 'market',
|
||||
'sell': 'limit',
|
||||
'stoploss': 'limit'
|
||||
}
|
||||
|
||||
config = {
|
||||
'strategy': 'DefaultStrategy',
|
||||
'order_types': order_types
|
||||
}
|
||||
resolver = StrategyResolver(config)
|
||||
|
||||
assert resolver.strategy.order_types
|
||||
for method in ['buy', 'sell', 'stoploss']:
|
||||
assert resolver.strategy.order_types[method] == order_types[method]
|
||||
|
||||
assert ('freqtrade.strategy.resolver',
|
||||
logging.INFO,
|
||||
"Override strategy 'order_types' with value in config file:"
|
||||
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}."
|
||||
) in caplog.record_tuples
|
||||
|
||||
config = {
|
||||
'strategy': 'DefaultStrategy',
|
||||
'order_types': {'buy': 'market'}
|
||||
}
|
||||
# Raise error for invalid configuration
|
||||
with pytest.raises(ImportError,
|
||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
||||
r"Order-types mapping is incomplete."):
|
||||
StrategyResolver(config)
|
||||
|
||||
|
||||
def test_deprecate_populate_indicators(result):
|
||||
default_location = path.join(path.dirname(path.realpath(__file__)))
|
||||
resolver = StrategyResolver({'strategy': 'TestStrategyLegacy',
|
||||
|
||||
@@ -553,7 +553,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
freqtrade.create_trade()
|
||||
rate, amount = buy_mock.call_args[0][1], buy_mock.call_args[0][2]
|
||||
rate, amount = buy_mock.call_args[1]['rate'], buy_mock.call_args[1]['amount']
|
||||
assert rate * amount >= default_conf['stake_amount']
|
||||
|
||||
|
||||
@@ -863,10 +863,10 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
assert get_bid.call_count == 1
|
||||
assert buy_mm.call_count == 1
|
||||
call_args = buy_mm.call_args_list[0][0]
|
||||
assert call_args[0] == pair
|
||||
assert call_args[1] == bid
|
||||
assert call_args[2] == stake_amount / bid
|
||||
call_args = buy_mm.call_args_list[0][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == bid
|
||||
assert call_args['amount'] == stake_amount / bid
|
||||
|
||||
# Test calling with price
|
||||
fix_price = 0.06
|
||||
@@ -875,10 +875,10 @@ def test_execute_buy(mocker, default_conf, fee, markets, limit_buy_order) -> Non
|
||||
assert get_bid.call_count == 1
|
||||
|
||||
assert buy_mm.call_count == 2
|
||||
call_args = buy_mm.call_args_list[1][0]
|
||||
assert call_args[0] == pair
|
||||
assert call_args[1] == fix_price
|
||||
assert call_args[2] == stake_amount / fix_price
|
||||
call_args = buy_mm.call_args_list[1][1]
|
||||
assert call_args['pair'] == pair
|
||||
assert call_args['rate'] == fix_price
|
||||
assert call_args['amount'] == stake_amount / fix_price
|
||||
|
||||
|
||||
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
|
||||
|
||||
Reference in New Issue
Block a user