diff --git a/config_full.json.example b/config_full.json.example index 9dba8f539..b0719bcc6 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -33,6 +33,11 @@ "order_book_min": 1, "order_book_max": 9 }, + "order_types": { + "buy": "limit", + "sell": "limit", + "stoploss": "market" + }, "exchange": { "name": "bittrex", "key": "your_exchange_key", diff --git a/docs/configuration.md b/docs/configuration.md index f40c2c338..62559a41e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,6 +39,7 @@ The table below will list all configuration parameters. | `ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks. | `ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. +| `order_types` | None | No | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`). | `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). | `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode. | `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode. @@ -138,6 +139,22 @@ use the `last` price and values between those interpolate between ask and last price. Using `ask` price will guarantee quick success in bid, but bot will also end up paying more then would probably have been necessary. +### Understand order_types + +`order_types` contains a dict mapping order-types to market-types. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. +This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations. + +If this is configured, all 3 values (`"buy"`, `"sell"` and `"stoploss"`) need to be present, otherwise the bot warn about it and will fail to start. +The below is the default which is used if this is not configured in either Strategy or configuration. + +``` json + "order_types": { + "buy": "limit", + "sell": "limit", + "stoploss": "market" + }, +``` + ### What values for exchange.name? Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 2c753190e..aaab73ab4 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -12,6 +12,8 @@ DEFAULT_STRATEGY = 'DefaultStrategy' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' UNLIMITED_STAKE_AMOUNT = 'unlimited' +REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss'] +ORDERTYPE_POSSIBILITIES = ['limit', 'market'] TICKER_INTERVAL_MINUTES = { @@ -101,6 +103,15 @@ CONF_SCHEMA = { 'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50} } }, + 'order_types': { + 'type': 'object', + 'properties': { + 'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES}, + 'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES} + }, + 'required': ['buy', 'sell', 'stoploss'] + }, 'exchange': {'$ref': '#/definitions/exchange'}, 'edge': {'$ref': '#/definitions/edge'}, 'experimental': { diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 4af9db6db..ae07e36e9 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -102,7 +102,7 @@ class Exchange(object): self.markets = self._load_markets() # Check if all pairs are available self.validate_pairs(config['exchange']['pair_whitelist']) - + self.validate_ordertypes(config.get('order_types', {})) if config.get('ticker_interval'): # Check if timeframe is available self.validate_timeframes(config['ticker_interval']) @@ -218,6 +218,15 @@ class Exchange(object): raise OperationalException( f'Invalid ticker {timeframe}, this Exchange supports {timeframes}') + def validate_ordertypes(self, order_types: Dict) -> None: + """ + Checks if order-types configured in strategy/config are supported + """ + if any(v == 'market' for k, v in order_types.items()): + if not self.exchange_has('createMarketOrder'): + raise OperationalException( + f'Exchange {self.name} does not support market orders.') + def exchange_has(self, endpoint: str) -> bool: """ Checks if exchange implements a specific API endpoint. @@ -249,14 +258,14 @@ class Exchange(object): price = ceil(big_price) / pow(10, symbol_prec) return price - def buy(self, pair: str, rate: float, amount: float) -> Dict: + def buy(self, pair: str, ordertype: str, amount: float, rate: float) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'pair': pair, 'price': rate, 'amount': amount, - 'type': 'limit', + 'type': ordertype, 'side': 'buy', 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), @@ -268,9 +277,9 @@ class Exchange(object): try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) - rate = self.symbol_price_prec(pair, rate) + rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - return self._api.create_limit_buy_order(pair, amount, rate) + return self._api.create_order(pair, ordertype, 'buy', amount, rate) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create limit buy order on market {pair}.' @@ -287,14 +296,14 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) - def sell(self, pair: str, rate: float, amount: float) -> Dict: + def sell(self, pair: str, ordertype: str, amount: float, rate: float) -> Dict: if self._conf['dry_run']: order_id = f'dry_run_sell_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { 'pair': pair, 'price': rate, 'amount': amount, - 'type': 'limit', + 'type': ordertype, 'side': 'sell', 'remaining': 0.0, 'datetime': arrow.utcnow().isoformat(), @@ -305,9 +314,9 @@ class Exchange(object): try: # Set the precision for amount and price(rate) as accepted by the exchange amount = self.symbol_amount_prec(pair, amount) - rate = self.symbol_price_prec(pair, rate) + rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None - return self._api.create_limit_sell_order(pair, amount, rate) + return self._api.create_order(pair, ordertype, 'sell', amount, rate) except ccxt.InsufficientFunds as e: raise DependencyException( f'Insufficient funds to create limit sell order on market {pair}.' diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5ab78ab35..b6c18c2d9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -472,7 +472,8 @@ class FreqtradeBot(object): amount = stake_amount / buy_limit - order_id = self.exchange.buy(pair, buy_limit, amount)['id'] + order_id = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'], + amount=amount, rate=buy_limit)['id'] self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, @@ -759,8 +760,13 @@ class FreqtradeBot(object): :param sellreason: Reason the sell was triggered :return: None """ + sell_type = 'sell' + if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): + sell_type = 'stoploss' # Execute sell and update trade record - order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id'] + order_id = self.exchange.sell(pair=str(trade.pair), + ordertype=self.strategy.order_types[sell_type], + amount=trade.amount, rate=limit)['id'] trade.open_order_id = order_id trade.close_rate_requested = limit trade.sell_reason = sell_reason.value diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index f1646779b..b282a5938 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -28,6 +28,13 @@ class DefaultStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' + # Optional order type mapping + order_types = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit' + } + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 212559c8c..139bcd8be 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -70,6 +70,13 @@ class IStrategy(ABC): # associated ticker interval ticker_interval: str + # Optional order types + order_types: Dict = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit' + } + # run "populate_indicators" only for new candle process_only_new_candles: bool = False diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index aee47580c..3f25e4838 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -75,6 +75,19 @@ class StrategyResolver(object): else: config['process_only_new_candles'] = self.strategy.process_only_new_candles + if 'order_types' in config: + self.strategy.order_types = config['order_types'] + logger.info( + "Override strategy 'order_types' with value in config file: %s.", + config['order_types'] + ) + else: + config['order_types'] = self.strategy.order_types + + if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES): + raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. " + f"Order-types mapping is incomplete.") + # Sort and apply type conversions self.strategy.minimal_roi = OrderedDict(sorted( {int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(), diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 8a497725f..b6c022b45 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -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: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 788ef4518..207f14efe 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -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): diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index abc531689..a38050f24 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -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', diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4cba9e308..cef89c250 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -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: diff --git a/requirements.txt b/requirements.txt index 66593c264..a845ddeb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.498 +ccxt==1.17.518 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 7c3892b77..fd2e9ab75 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -48,6 +48,13 @@ class TestStrategy(IStrategy): # run "populate_indicators" only for new candle ta_on_candle = False + # Optional order type mapping + order_types = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'market' + } + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame