From 6a71f80a9e9aebf770227048c5efa9abef8714cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Nov 2018 06:58:24 +0100 Subject: [PATCH 01/19] Add support for different order types --- freqtrade/exchange/__init__.py | 8 ++++---- freqtrade/freqtradebot.py | 9 +++++++-- freqtrade/strategy/default_strategy.py | 7 +++++++ freqtrade/strategy/interface.py | 7 +++++++ freqtrade/tests/exchange/test_exchange.py | 20 ++++++++++---------- 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 4af9db6db..8e0116368 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -249,7 +249,7 @@ 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, rate: float, amount: float, ordertype: str = 'limt') -> Dict: if self._conf['dry_run']: order_id = f'dry_run_buy_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { @@ -270,7 +270,7 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) - 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,7 +287,7 @@ 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, rate: float, amount: float, ordertype: str = 'limt') -> Dict: if self._conf['dry_run']: order_id = f'dry_run_sell_{randint(0, 10**6)}' self._dry_run_open_orders[order_id] = { @@ -307,7 +307,7 @@ class Exchange(object): amount = self.symbol_amount_prec(pair, amount) rate = self.symbol_price_prec(pair, rate) - 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 eb92375ec..75f935c0c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -475,7 +475,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, rate=buy_limit, amount=amount, + ordertype=self.strategy.order_types['buy'])['id'] self.rpc.send_msg({ 'type': RPCMessageType.BUY_NOTIFICATION, @@ -762,8 +763,12 @@ 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), rate=limit, amount=trade.amount, + ordertype=self.strategy.order_types[sell_type])['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..458847636 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 types + 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 diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 212559c8c..9d6c5f098 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': 'market' + } + # run "populate_indicators" only for new candle process_only_new_candles: bool = False diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 788ef4518..fe37c6ac1 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -381,7 +381,7 @@ 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={ + api_mock.create_order = MagicMock(return_value={ 'id': order_id, 'info': { 'foo': 'bar' @@ -397,22 +397,22 @@ def test_buy_prod(default_conf, mocker): # 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) 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) 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) 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) @@ -429,7 +429,7 @@ 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={ + api_mock.create_order = MagicMock(return_value={ 'id': order_id, 'info': { 'foo': 'bar' @@ -446,22 +446,22 @@ def test_sell_prod(default_conf, mocker): # 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) 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) 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) 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) From e6baa9ccf2edad645f45bc10bc4b5d4673918a8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 15 Nov 2018 19:31:24 +0100 Subject: [PATCH 02/19] Switch tests to kwarguments --- freqtrade/tests/test_freqtradebot.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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: From b7abf7dda92bf78da1e075fc31a3b7e42327d5a0 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Fri, 16 Nov 2018 13:34:08 +0100 Subject: [PATCH 03/19] Update ccxt from 1.17.498 to 1.17.500 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 66593c264..6b356cd9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.498 +ccxt==1.17.500 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 24ed9a8b7d93f502ca2a12a3ef8db62628691a3e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 10:14:18 +0100 Subject: [PATCH 04/19] Add loading order_types from config file --- config_full.json.example | 5 +++++ freqtrade/strategy/resolver.py | 9 ++++++++ freqtrade/tests/strategy/test_strategy.py | 26 +++++++++++++++++++++++ 3 files changed, 40 insertions(+) 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/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index aee47580c..31bd21ec8 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -75,6 +75,15 @@ 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 + # 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/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index abc531689..e6204b5f5 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -182,6 +182,32 @@ 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 + + def test_deprecate_populate_indicators(result): default_location = path.join(path.dirname(path.realpath(__file__))) resolver = StrategyResolver({'strategy': 'TestStrategyLegacy', From 6e78efd9719f188f5d47d14db82309b9dddf02fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 10:24:42 +0100 Subject: [PATCH 05/19] Document "order_types" setting --- docs/configuration.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index d70a47b38..5a3f2b371 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 From 3ab0cf49af865f31d74db03e883b8c78dbb5532b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 10:26:15 +0100 Subject: [PATCH 06/19] Add order_types to sample strategy --- freqtrade/strategy/default_strategy.py | 2 +- user_data/strategies/test_strategy.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 458847636..59e280b6e 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -28,7 +28,7 @@ class DefaultStrategy(IStrategy): # Optimal ticker interval for the strategy ticker_interval = '5m' - # Optional order types + # Optional order type mapping order_types = { 'buy': 'limit', 'sell': 'limit', 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 From 54a86d72f28550e5904981c7b1fe909383dc91dc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 12:59:16 +0100 Subject: [PATCH 07/19] Raise error if one of the required ordertypes is not present --- freqtrade/constants.py | 1 + freqtrade/strategy/resolver.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b7c069c45..d5c23fe1d 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -12,6 +12,7 @@ DEFAULT_STRATEGY = 'DefaultStrategy' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' UNLIMITED_STAKE_AMOUNT = 'unlimited' +REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss'] TICKER_INTERVAL_MINUTES = { diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 31bd21ec8..3f25e4838 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -84,6 +84,10 @@ class StrategyResolver(object): 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(), From 9ba281c141eb2735798fd5298b654369f0b7876f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:05:35 +0100 Subject: [PATCH 08/19] add supported limit values --- freqtrade/constants.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index d5c23fe1d..fdbff886e 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -13,6 +13,7 @@ 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 = { @@ -102,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': { From e485aff597d68d9f649109976a8dd85517bf8235 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:12:11 +0100 Subject: [PATCH 09/19] Test failed load on invalid ordertypes --- freqtrade/tests/strategy/test_strategy.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index e6204b5f5..d1a87ecfa 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -207,6 +207,16 @@ def test_strategy_override_order_types(caplog): " {'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__))) From 543873263a711ccf54f90c3d0f8ed7f827b2adf1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:13:16 +0100 Subject: [PATCH 10/19] remove need for escaping quote --- freqtrade/tests/strategy/test_strategy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index d1a87ecfa..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={}) From ef1e20bfe8d2e3a009bad9bf46c12f41afe9af80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:23:13 +0100 Subject: [PATCH 11/19] Don't add default value for ordertype sort parameters to align with ccxt --- freqtrade/exchange/__init__.py | 8 ++++---- freqtrade/freqtradebot.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 8e0116368..57db4a125 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -249,14 +249,14 @@ class Exchange(object): price = ceil(big_price) / pow(10, symbol_prec) return price - def buy(self, pair: str, rate: float, amount: float, ordertype: str = 'limt') -> 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(), @@ -287,14 +287,14 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) - def sell(self, pair: str, rate: float, amount: float, ordertype: str = 'limt') -> 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(), diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 75f935c0c..c7532ead7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -475,8 +475,8 @@ class FreqtradeBot(object): amount = stake_amount / buy_limit - order_id = self.exchange.buy(pair=pair, rate=buy_limit, amount=amount, - ordertype=self.strategy.order_types['buy'])['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, @@ -767,8 +767,9 @@ class FreqtradeBot(object): 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(pair=str(trade.pair), rate=limit, amount=trade.amount, - ordertype=self.strategy.order_types[sell_type])['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 From a9a157af0fa58e1a0994747aeefd2985df9ac317 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:29:42 +0100 Subject: [PATCH 12/19] Align tests and test if ordertype is passed to ccxt correctly --- freqtrade/tests/exchange/test_exchange.py | 37 +++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index fe37c6ac1..12a8fbcf4 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -373,7 +373,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,6 +381,7 @@ 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)) + order_type = 'market' api_mock.create_order = MagicMock(return_value={ 'id': order_id, 'info': { @@ -390,38 +391,43 @@ def test_buy_prod(default_conf, mocker): default_conf['dry_run'] = False 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][1] == order_type + order_type = 'limit' + api_mock.create_order.reset_mock() + order = exchange.buy(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + assert api_mock.create_order.call_args[0][1] == order_type # test exception handling with pytest.raises(DependencyException): 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_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_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_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,6 +435,7 @@ 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)) + order_type = 'market' api_mock.create_order = MagicMock(return_value={ 'id': order_id, 'info': { @@ -439,31 +446,37 @@ def test_sell_prod(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock) - 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][1] == order_type + + order_type = 'limit' + api_mock.create_order.reset_mock() + order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) + assert api_mock.create_order.call_args[0][1] == order_type # test exception handling with pytest.raises(DependencyException): 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_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_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_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): From 681659f2d2f0f5fd7310f749e3fb28d762577987 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Sat, 17 Nov 2018 13:34:06 +0100 Subject: [PATCH 13/19] Update ccxt from 1.17.500 to 1.17.502 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6b356cd9e..56e0d080a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.500 +ccxt==1.17.502 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From 492868a966e352251dc15d74eaed4b133c8da475 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 13:34:23 +0100 Subject: [PATCH 14/19] Seperate different tests within one test clearer --- freqtrade/tests/exchange/test_exchange.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 12a8fbcf4..fd4512bd2 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -396,8 +396,9 @@ def test_buy_prod(default_conf, mocker): assert 'info' in order assert order['id'] == order_id assert api_mock.create_order.call_args[0][1] == order_type - order_type = 'limit' + 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][1] == order_type @@ -452,8 +453,8 @@ def test_sell_prod(default_conf, mocker): assert order['id'] == order_id assert api_mock.create_order.call_args[0][1] == order_type - order_type = 'limit' 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][1] == order_type From 968184ef0db70040eedb7be2b1734baa11e06e3c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 19:40:22 +0100 Subject: [PATCH 15/19] Swap default mode to all limit (defaults to how it was before) --- freqtrade/strategy/default_strategy.py | 2 +- freqtrade/strategy/interface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 59e280b6e..b282a5938 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -32,7 +32,7 @@ class DefaultStrategy(IStrategy): order_types = { 'buy': 'limit', 'sell': 'limit', - 'stoploss': 'market' + 'stoploss': 'limit' } def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 9d6c5f098..139bcd8be 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -74,7 +74,7 @@ class IStrategy(ABC): order_types: Dict = { 'buy': 'limit', 'sell': 'limit', - 'stoploss': 'market' + 'stoploss': 'limit' } # run "populate_indicators" only for new candle From c11984d943af8a59a548ddbee7a78d49fcb8bdde Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 19:54:55 +0100 Subject: [PATCH 16/19] Check if exchange supports all configured market types --- freqtrade/exchange/__init__.py | 11 ++++++++- freqtrade/tests/conftest.py | 1 + freqtrade/tests/exchange/test_exchange.py | 30 +++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 57db4a125..11836ed4e 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. 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 fd4512bd2..10644a9be 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') From b3e08831f7a233e9fccd67d6d7749d5577896e54 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Nov 2018 20:09:05 +0100 Subject: [PATCH 17/19] Remove rate for market orders --- freqtrade/exchange/__init__.py | 4 ++-- freqtrade/tests/exchange/test_exchange.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 11836ed4e..ae07e36e9 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -277,7 +277,7 @@ 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_order(pair, ordertype, 'buy', amount, rate) except ccxt.InsufficientFunds as e: @@ -314,7 +314,7 @@ 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_order(pair, ordertype, 'sell', amount, rate) except ccxt.InsufficientFunds as e: diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 10644a9be..207f14efe 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -419,18 +419,28 @@ def test_buy_prod(default_conf, mocker): } }) 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', 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): @@ -476,17 +486,27 @@ 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', 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): From cf2d68501c02c0cc96e95102f0ce5a78798fba79 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Mon, 19 Nov 2018 13:34:07 +0100 Subject: [PATCH 18/19] Update ccxt from 1.17.502 to 1.17.513 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 56e0d080a..9cb83d517 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.502 +ccxt==1.17.513 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 From ce092742dab519130c69e73ac85980a0868fec1c Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 20 Nov 2018 13:34:07 +0100 Subject: [PATCH 19/19] Update ccxt from 1.17.513 to 1.17.518 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9cb83d517..a845ddeb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.513 +ccxt==1.17.518 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1