From 4a9bf78770624357d91de4675a10c541fc488a74 Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Sun, 5 Aug 2018 12:41:06 +0800 Subject: [PATCH 01/10] Order Book with tests --- config.json.example | 16 +++- config_full.json.example | 16 +++- docs/configuration.md | 7 ++ freqtrade/constants.py | 25 ++++- freqtrade/exchange/__init__.py | 23 +++++ freqtrade/exchange/exchange_helpers.py | 24 +++++ freqtrade/freqtradebot.py | 111 ++++++++++++++++++++-- freqtrade/tests/conftest.py | 16 ++++ freqtrade/tests/exchange/test_exchange.py | 10 ++ freqtrade/tests/test_freqtradebot.py | 39 +++++++- 10 files changed, 271 insertions(+), 16 deletions(-) diff --git a/config.json.example b/config.json.example index 8bd3942e6..c3dc6b5b6 100644 --- a/config.json.example +++ b/config.json.example @@ -37,7 +37,21 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "ignore_roi_if_buy_signal": false, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + }, + "bid_strategy": { + "use_order_book": false, + "order_book_top": 2, + "percent_from_top": 0 + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 + } }, "telegram": { "enabled": true, diff --git a/config_full.json.example b/config_full.json.example index cc3b3d630..5a364a93b 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -46,7 +46,21 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "ignore_roi_if_buy_signal": false + "ignore_roi_if_buy_signal": false, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + }, + "bid_strategy": { + "use_order_book": false, + "order_book_top": 2, + "percent_from_top": 0 + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 + }s }, "telegram": { "enabled": true, diff --git a/docs/configuration.md b/docs/configuration.md index ff5ce118c..5ffe24556 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,6 +39,13 @@ The table below will list all configuration parameters. | `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`. | `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision. | `experimental.ignore_roi_if_buy_signal` | false | No | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal` +| `experimental.check_depth_of_market` | false | No | Does not sell if the % difference of buy orders and sell orders is met in Order Book. +| `experimental.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. +| `experimental.bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids. +| `experimental.bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. +| `experimental.ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks. +| `experimental.ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. +| `experimental.ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `telegram.enabled` | true | Yes | Enable or not the usage of Telegram. | `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 87e354455..b7431af3c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -89,7 +89,30 @@ CONF_SCHEMA = { 'properties': { 'use_sell_signal': {'type': 'boolean'}, 'sell_profit_only': {'type': 'boolean'}, - "ignore_roi_if_buy_signal_true": {'type': 'boolean'} + 'ignore_roi_if_buy_signal_true': {'type': 'boolean'}, + 'check_depth_of_market': { + 'type': 'object', + 'properties': { + 'enabled': {'type': 'boolean'}, + 'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, + } + }, + 'bid_strategy': { + 'type': 'object', + 'properties': { + 'percent_from_top': {'type': 'number', 'minimum': 0}, + 'use_order_book': {'type': 'boolean'}, + 'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1} + } + }, + 'ask_strategy': { + 'type': 'object', + 'properties': { + 'use_order_book': {'type': 'boolean'}, + 'order_book_min': {'type': 'number', 'minimum': 1}, + 'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50} + } + } } }, 'telegram': { diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 810957902..18b95b604 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -409,6 +409,29 @@ class Exchange(object): except ccxt.BaseError as e: raise OperationalException(e) + @retrier + def get_order_book(self, pair: str, limit: int = 100) -> dict: + try: + # 20180619: bittrex doesnt support limits -.- + # 20180619: binance support limits but only on specific range + if self._api.name == 'Binance': + limit_range = [5, 10, 20, 50, 100, 500, 1000] + for limitx in limit_range: + if limit <= limitx: + limit = limitx + break + + return self._api.fetch_l2_order_book(pair, limit) + except ccxt.NotSupported as e: + raise OperationalException( + f'Exchange {self._api.name} does not support fetching order book.' + f'Message: {e}') + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not get order book due to {e.__class__.__name__}. Message: {e}') + except ccxt.BaseError as e: + raise OperationalException(e) + @retrier def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: if self._conf['dry_run']: diff --git a/freqtrade/exchange/exchange_helpers.py b/freqtrade/exchange/exchange_helpers.py index 254c16309..6574f5a53 100644 --- a/freqtrade/exchange/exchange_helpers.py +++ b/freqtrade/exchange/exchange_helpers.py @@ -2,6 +2,7 @@ Functions to analyze ticker data with indicators and produce buy and sell signals """ import logging +import pandas as pd from pandas import DataFrame, to_datetime logger = logging.getLogger(__name__) @@ -31,3 +32,26 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame: }) frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle return frame + + +def order_book_to_dataframe(data: list) -> DataFrame: + """ + Gets order book list, returns dataframe with below format per suggested by creslin + ------------------------------------------------------------------- + b_sum b_size bids asks a_size a_sum + ------------------------------------------------------------------- + """ + cols = ['bids', 'b_size'] + bids_frame = DataFrame(data['bids'], columns=cols) + # add cumulative sum column + bids_frame['b_sum'] = bids_frame['b_size'].cumsum() + cols2 = ['asks', 'a_size'] + asks_frame = DataFrame(data['asks'], columns=cols2) + # add cumulative sum column + asks_frame['a_sum'] = asks_frame['a_size'].cumsum() + + frame = pd.concat([bids_frame['b_sum'], bids_frame['b_size'], bids_frame['bids'], + asks_frame['asks'], asks_frame['a_size'], asks_frame['a_sum']], axis=1, + keys=['b_sum', 'b_size', 'bids', 'asks', 'a_size', 'a_sum']) + # logger.info('order book %s', frame ) + return frame diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 46fbb3a38..0c4670971 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,6 +21,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.state import State from freqtrade.strategy.interface import SellType from freqtrade.strategy.resolver import IStrategy, StrategyResolver +from freqtrade.exchange.exchange_helpers import order_book_to_dataframe logger = logging.getLogger(__name__) @@ -233,16 +234,47 @@ class FreqtradeBot(object): return final_list - def get_target_bid(self, ticker: Dict[str, float]) -> float: + def get_target_bid(self, pair: str, ticker: Dict[str, float]) -> float: """ Calculates bid target between current ask price and last price :param ticker: Ticker to use for getting Ask and Last Price :return: float: Price """ if ticker['ask'] < ticker['last']: - return ticker['ask'] - balance = self.config['bid_strategy']['ask_last_balance'] - return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + ticker_rate = ticker['ask'] + else: + balance = self.config['bid_strategy']['ask_last_balance'] + ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + + used_rate = ticker_rate + experimental_bid_strategy = self.config.get('experimental', {}).get('bid_strategy', {}) + if 'use_order_book' in experimental_bid_strategy and\ + experimental_bid_strategy.get('use_order_book', False): + logger.info('Getting price from order book') + order_book_top = experimental_bid_strategy.get('order_book_top', 1) + order_book = self.exchange.get_order_book(pair, order_book_top) + # top 1 = index 0 + order_book_rate = order_book['bids'][order_book_top - 1][0] + # if ticker has lower rate, then use ticker ( usefull if down trending ) + logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate) + if ticker_rate < order_book_rate: + logger.info('...using ticker rate instead %0.8f', ticker_rate) + used_rate = ticker_rate + used_rate = order_book_rate + else: + logger.info('Using Last Ask / Last Price') + used_rate = ticker_rate + percent_from_top = self.config.get('bid_strategy', {}).get('percent_from_top', 0) + if percent_from_top > 0: + used_rate = used_rate - (used_rate * percent_from_top) + used_rate = self._trunc_num(used_rate, 8) + logger.info('...percent_from_top enabled, new buy rate %0.8f', used_rate) + + return used_rate + + def _trunc_num(self, f, n): + import math + return math.floor(f * 10 ** n) / 10 ** n def _get_trade_stake_amount(self) -> Optional[float]: """ @@ -334,9 +366,37 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(_pair, interval, thistory) if buy and not sell: + experimental_check_depth_of_market = self.config.get('experimental', {}).\ + get('check_depth_of_market', {}) + if (experimental_check_depth_of_market.get('enabled', False)) and\ + (experimental_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): + if self._check_depth_of_market_buy(_pair): + return self.execute_buy(_pair, stake_amount) + else: + return False return self.execute_buy(_pair, stake_amount) return False + def _check_depth_of_market_buy(self, pair: str, ) -> bool: + """ + Checks depth of market before executing a buy + """ + experimental_check_depth_of_market = self.config.get('experimental', {}).\ + get('check_depth_of_market', {}) + conf_bids_to_ask_delta = experimental_check_depth_of_market.get('bids_to_ask_delta', 0) + logger.info('checking depth of market for %s', pair) + order_book = self.exchange.get_order_book(pair, 1000) + order_book_data_frame = order_book_to_dataframe(order_book) + order_book_bids = order_book_data_frame['b_size'].sum() + order_book_asks = order_book_data_frame['a_size'].sum() + bids_ask_delta = order_book_bids / order_book_asks + logger.info('bids: %s, asks: %s, delta: %s', order_book_bids, + order_book_asks, + order_book_bids / order_book_asks) + if bids_ask_delta >= conf_bids_to_ask_delta: + return True + return False + def execute_buy(self, pair: str, stake_amount: float) -> bool: """ Executes a limit buy for the given pair @@ -349,7 +409,7 @@ class FreqtradeBot(object): fiat_currency = self.config.get('fiat_display_currency', None) # Calculate amount - buy_limit = self.get_target_bid(self.exchange.get_ticker(pair)) + buy_limit = self.get_target_bid(pair, self.exchange.get_ticker(pair)) min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit) if min_stake_amount is not None and min_stake_amount > stake_amount: @@ -492,7 +552,7 @@ class FreqtradeBot(object): raise ValueError(f'attempt to handle closed trade: {trade}') logger.debug('Handling %s ...', trade) - current_rate = self.exchange.get_ticker(trade.pair)['bid'] + sell_rate = self.exchange.get_ticker(trade.pair)['bid'] (buy, sell) = (False, False) experimental = self.config.get('experimental', {}) @@ -501,13 +561,44 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval, ticker) - should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell) - if should_sell.sell_flag: - self.execute_sell(trade, current_rate, should_sell.sell_type) - return True + experimental_ask_strategy = self.config.get('experimental', {}).get('ask_strategy', {}) + if 'use_order_book' in experimental_ask_strategy and\ + experimental_ask_strategy.get('use_order_book', False): + logger.info('Using order book for selling...') + # logger.debug('Order book %s',orderBook) + order_book_min = experimental_ask_strategy.get('order_book_min', 1) + order_book_max = experimental_ask_strategy.get('order_book_max', 1) + + order_book = self.exchange.get_order_book(trade.pair, order_book_max) + + for i in range(order_book_min, order_book_max + 1): + order_book_rate = order_book['asks'][i - 1][0] + + # if orderbook has higher rate (high profit), + # use orderbook, otherwise just use bids rate + logger.info(' order book asks top %s: %0.8f', i, order_book_rate) + if sell_rate < order_book_rate: + sell_rate = order_book_rate + + if self.check_sell(trade, sell_rate, buy, sell): + return True + break + else: + logger.info('checking sell') + if self.check_sell(trade, sell_rate, buy, sell): + return True + logger.info('Found no sell signals for whitelisted currencies. Trying again..') return False + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: + should_sell = self.strategy.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell) + if should_sell.sell_flag: + self.execute_sell(trade, sell_rate, should_sell.sell_type) + logger.info('excuted sell') + return True + return False + def check_handle_timedout(self) -> None: """ Check if any orders are timed out and cancel if neccessary diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index d18016e16..d7f7e96d9 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -116,6 +116,22 @@ def default_conf(): "NEO/BTC" ] }, + "experimental": { + "check_depth_of_market": { + "enabled": False, + "bids_to_ask_delta": 1 + }, + "bid_strategy": { + "percent_from_top": 0, + "use_order_book": False, + "order_book_top": 1 + }, + "ask_strategy": { + "use_order_book": False, + "order_book_min": 1, + "order_book_max": 1 + } + }, "telegram": { "enabled": True, "token": "token", diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index d327b97c7..d0917bda2 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -515,6 +515,16 @@ def test_get_ticker(default_conf, mocker): exchange.get_ticker(pair='ETH/BTC', refresh=True) +def test_get_order_book(default_conf, mocker): + default_conf['exchange']['name'] = 'binance' + exchange = Exchange(default_conf) + order_book = exchange.get_order_book(pair='ETH/BTC', limit=50) + assert 'bids' in order_book + assert 'asks' in order_book + assert len(order_book['bids']) == 50 + assert len(order_book['asks']) == 50 + + def make_fetch_ohlcv_mock(data): def fetch_ohlcv_mock(pair, timeframe, since): if since: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 69f349107..4718ab0fa 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -664,21 +664,21 @@ def test_balance_fully_ask_side(mocker, default_conf) -> None: default_conf['bid_strategy']['ask_last_balance'] = 0.0 freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20 + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 20 def test_balance_fully_last_side(mocker, default_conf) -> None: default_conf['bid_strategy']['ask_last_balance'] = 1.0 freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10 + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 10 def test_balance_bigger_last_ask(mocker, default_conf) -> None: default_conf['bid_strategy']['ask_last_balance'] = 1.0 freqtrade = get_patched_freqtradebot(mocker, default_conf) - assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5 + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 5, 'last': 10}) == 5 def test_process_maybe_execute_buy(mocker, default_conf) -> None: @@ -1876,3 +1876,36 @@ def test_get_real_amount_open_trade(default_conf, mocker): freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) assert freqtrade.get_real_amount(trade, order) == amount + + +def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker): + default_conf['experimental']['check_depth_of_market']['enabled'] = True + default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 0.1 + patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + get_markets=markets + ) + + # Save state of current whitelist + whitelist = deepcopy(default_conf['exchange']['pair_whitelist']) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade is not None + assert trade.stake_amount == 0.001 + assert trade.is_open + assert trade.open_date is not None + assert trade.exchange == 'bittrex' + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + assert trade.open_rate == 0.00001099 + assert whitelist == default_conf['exchange']['pair_whitelist'] From 26d591ea43dfc8a9799ab80bbc0e7bdbdd51f724 Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Sun, 5 Aug 2018 21:08:07 +0800 Subject: [PATCH 02/10] mypy fix --- freqtrade/exchange/__init__.py | 2 +- freqtrade/exchange/exchange_helpers.py | 7 ++++--- freqtrade/freqtradebot.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 18b95b604..d5be25ba0 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -116,7 +116,7 @@ class Exchange(object): api.urls['api'] = api.urls['test'] logger.info("Enabled Sandbox API on %s", name) else: - logger.warning(self, "No Sandbox URL in CCXT, exiting. " + logger.warning(name, "No Sandbox URL in CCXT, exiting. " "Please check your config.json") raise OperationalException(f'Exchange {name} does not provide a sandbox api') diff --git a/freqtrade/exchange/exchange_helpers.py b/freqtrade/exchange/exchange_helpers.py index 6574f5a53..ac729190a 100644 --- a/freqtrade/exchange/exchange_helpers.py +++ b/freqtrade/exchange/exchange_helpers.py @@ -34,7 +34,7 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame: return frame -def order_book_to_dataframe(data: list) -> DataFrame: +def order_book_to_dataframe(bids: list, asks: list) -> DataFrame: """ Gets order book list, returns dataframe with below format per suggested by creslin ------------------------------------------------------------------- @@ -42,11 +42,12 @@ def order_book_to_dataframe(data: list) -> DataFrame: ------------------------------------------------------------------- """ cols = ['bids', 'b_size'] - bids_frame = DataFrame(data['bids'], columns=cols) + + bids_frame = DataFrame(bids, columns=cols) # add cumulative sum column bids_frame['b_sum'] = bids_frame['b_size'].cumsum() cols2 = ['asks', 'a_size'] - asks_frame = DataFrame(data['asks'], columns=cols2) + asks_frame = DataFrame(asks, columns=cols2) # add cumulative sum column asks_frame['a_sum'] = asks_frame['a_size'].cumsum() diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0c4670971..b91cb1bd2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -386,7 +386,7 @@ class FreqtradeBot(object): conf_bids_to_ask_delta = experimental_check_depth_of_market.get('bids_to_ask_delta', 0) logger.info('checking depth of market for %s', pair) order_book = self.exchange.get_order_book(pair, 1000) - order_book_data_frame = order_book_to_dataframe(order_book) + order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks']) order_book_bids = order_book_data_frame['b_size'].sum() order_book_asks = order_book_data_frame['a_size'].sum() bids_ask_delta = order_book_bids / order_book_asks From 7143b64fb73411744686381770a3adbf1897da6c Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Sun, 5 Aug 2018 22:41:58 +0800 Subject: [PATCH 03/10] tests for coverage --- freqtrade/freqtradebot.py | 7 +- freqtrade/tests/exchange/test_exchange.py | 16 +++++ freqtrade/tests/test_freqtradebot.py | 81 +++++++++++++++++++++++ 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b91cb1bd2..f1955a9d7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -264,11 +264,6 @@ class FreqtradeBot(object): else: logger.info('Using Last Ask / Last Price') used_rate = ticker_rate - percent_from_top = self.config.get('bid_strategy', {}).get('percent_from_top', 0) - if percent_from_top > 0: - used_rate = used_rate - (used_rate * percent_from_top) - used_rate = self._trunc_num(used_rate, 8) - logger.info('...percent_from_top enabled, new buy rate %0.8f', used_rate) return used_rate @@ -377,7 +372,7 @@ class FreqtradeBot(object): return self.execute_buy(_pair, stake_amount) return False - def _check_depth_of_market_buy(self, pair: str, ) -> bool: + def _check_depth_of_market_buy(self, pair: str) -> bool: """ Checks depth of market before executing a buy """ diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index d0917bda2..60f51553a 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -525,6 +525,22 @@ def test_get_order_book(default_conf, mocker): assert len(order_book['asks']) == 50 +def test_get_order_book_exception(default_conf, mocker): + api_mock = MagicMock() + with pytest.raises(OperationalException): + api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_order_book(pair='ETH/BTC', limit=50) + with pytest.raises(TemporaryError): + api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_order_book(pair='ETH/BTC', limit=50) + with pytest.raises(OperationalException): + api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + exchange.get_order_book(pair='ETH/BTC', limit=50) + + def make_fetch_ohlcv_mock(data): def fetch_ohlcv_mock(pair, timeframe, since): if since: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4718ab0fa..563f9961b 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1909,3 +1909,84 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, assert trade.open_rate == 0.00001099 assert whitelist == default_conf['exchange']['pair_whitelist'] + + +def test_order_book_depth_of_market_high_delta(default_conf, ticker, + limit_buy_order, fee, markets, mocker): + default_conf['experimental']['check_depth_of_market']['enabled'] = True + default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 + patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=ticker, + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + get_fee=fee, + get_markets=markets + ) + + # Save state of current whitelist + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade is None + + +def test_order_book_bid_strategy(default_conf) -> None: + default_conf['exchange']['name'] = 'binance' + default_conf['experimental']['bid_strategy']['use_order_book'] = True + default_conf['experimental']['bid_strategy']['order_book_top'] = 2 + default_conf['telegram']['enabled'] = False + + freqtrade = FreqtradeBot(default_conf) + + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) != 20 + + +def test_trunc_num(default_conf) -> None: + default_conf['telegram']['enabled'] = False + freqtrade = FreqtradeBot(default_conf) + + assert freqtrade._trunc_num(10.1111, 2) == 10.11 + + +def test_check_depth_of_market_buy(default_conf) -> None: + default_conf['telegram']['enabled'] = False + default_conf['exchange']['name'] = 'binance' + default_conf['experimental']['check_depth_of_market']['enabled'] = True + default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 + freqtrade = FreqtradeBot(default_conf) + + assert freqtrade._check_depth_of_market_buy('ETH/BTC') is False + + +def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order, + fee, markets, mocker) -> None: + default_conf['exchange']['name'] = 'binance' + default_conf['experimental']['ask_strategy']['use_order_book'] = True + default_conf['experimental']['ask_strategy']['order_book_min'] = 1 + default_conf['experimental']['ask_strategy']['order_book_max'] = 2 + default_conf['telegram']['enabled'] = False + patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_ticker=MagicMock(return_value={ + 'bid': 0.00001172, + 'ask': 0.00001173, + 'last': 0.00001172 + }), + buy=MagicMock(return_value={'id': limit_buy_order['id']}), + sell=MagicMock(return_value={'id': limit_sell_order['id']}), + get_fee=fee, + get_markets=markets + ) + freqtrade = FreqtradeBot(default_conf) + patch_get_signal(freqtrade) + + freqtrade.create_trade() + + trade = Trade.query.first() + assert trade From 1309c2b14f50c55acdbdeaaf217aba34dfd7656b Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Sun, 5 Aug 2018 22:56:14 +0800 Subject: [PATCH 04/10] tests update --- freqtrade/freqtradebot.py | 6 +++--- freqtrade/tests/test_freqtradebot.py | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f1955a9d7..496ef1168 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -260,7 +260,8 @@ class FreqtradeBot(object): if ticker_rate < order_book_rate: logger.info('...using ticker rate instead %0.8f', ticker_rate) used_rate = ticker_rate - used_rate = order_book_rate + else: + used_rate = order_book_rate else: logger.info('Using Last Ask / Last Price') used_rate = ticker_rate @@ -557,8 +558,7 @@ class FreqtradeBot(object): ticker) experimental_ask_strategy = self.config.get('experimental', {}).get('ask_strategy', {}) - if 'use_order_book' in experimental_ask_strategy and\ - experimental_ask_strategy.get('use_order_book', False): + if experimental_ask_strategy.get('use_order_book', False): logger.info('Using order book for selling...') # logger.debug('Order book %s',orderBook) order_book_min = experimental_ask_strategy.get('order_book_min', 1) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 563f9961b..6282720d6 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1938,11 +1938,12 @@ def test_order_book_bid_strategy(default_conf) -> None: default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 2 + default_conf['bid_strategy']['ask_last_balance'] = 0 default_conf['telegram']['enabled'] = False freqtrade = FreqtradeBot(default_conf) - assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) != 20 + assert freqtrade.get_target_bid('BTC/USDT', {'ask': 2, 'last': 2}) == 2 def test_trunc_num(default_conf) -> None: @@ -1990,3 +1991,10 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order trade = Trade.query.first() assert trade + + time.sleep(0.01) # Race condition fix + trade.update(limit_buy_order) + assert trade.is_open is True + + patch_get_signal(freqtrade, value=(False, True)) + assert freqtrade.handle_trade(trade) is True From c9c0e108ab249df607699c4b8ea066517e664e24 Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Tue, 7 Aug 2018 18:29:37 +0800 Subject: [PATCH 05/10] refactor --- freqtrade/exchange/__init__.py | 20 +++++++++---- freqtrade/freqtradebot.py | 15 +++------- freqtrade/tests/test_freqtradebot.py | 43 +++++++++++++++++++++------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index d5be25ba0..4b78283ea 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -411,15 +411,23 @@ class Exchange(object): @retrier def get_order_book(self, pair: str, limit: int = 100) -> dict: + """ + get order book level 2 from exchange + + Notes: + 20180619: bittrex doesnt support limits -.- + 20180619: binance support limits but only on specific range + """ try: - # 20180619: bittrex doesnt support limits -.- - # 20180619: binance support limits but only on specific range if self._api.name == 'Binance': limit_range = [5, 10, 20, 50, 100, 500, 1000] - for limitx in limit_range: - if limit <= limitx: - limit = limitx - break + # get next-higher step in the limit_range list + limit = min(list(filter(lambda x: limit <= x, limit_range))) + # above script works like loop below (but with slightly better performance): + # for limitx in limit_range: + # if limit <= limitx: + # limit = limitx + # break return self._api.fetch_l2_order_book(pair, limit) except ccxt.NotSupported as e: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 496ef1168..55b9c577f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -268,10 +268,6 @@ class FreqtradeBot(object): return used_rate - def _trunc_num(self, f, n): - import math - return math.floor(f * 10 ** n) / 10 ** n - def _get_trade_stake_amount(self) -> Optional[float]: """ Check if stake amount can be fulfilled with the available balance @@ -366,20 +362,18 @@ class FreqtradeBot(object): get('check_depth_of_market', {}) if (experimental_check_depth_of_market.get('enabled', False)) and\ (experimental_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): - if self._check_depth_of_market_buy(_pair): + if self._check_depth_of_market_buy(_pair, experimental_check_depth_of_market): return self.execute_buy(_pair, stake_amount) else: return False return self.execute_buy(_pair, stake_amount) return False - def _check_depth_of_market_buy(self, pair: str) -> bool: + def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: """ Checks depth of market before executing a buy """ - experimental_check_depth_of_market = self.config.get('experimental', {}).\ - get('check_depth_of_market', {}) - conf_bids_to_ask_delta = experimental_check_depth_of_market.get('bids_to_ask_delta', 0) + conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0) logger.info('checking depth of market for %s', pair) order_book = self.exchange.get_order_book(pair, 1000) order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks']) @@ -387,8 +381,7 @@ class FreqtradeBot(object): order_book_asks = order_book_data_frame['a_size'].sum() bids_ask_delta = order_book_bids / order_book_asks logger.info('bids: %s, asks: %s, delta: %s', order_book_bids, - order_book_asks, - order_book_bids / order_book_asks) + order_book_asks, bids_ask_delta) if bids_ask_delta >= conf_bids_to_ask_delta: return True return False diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 6282720d6..4357b573a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -159,6 +159,15 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None: assert whitelist == [] +def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: + freqtrade = get_patched_freqtradebot(mocker, default_conf) + mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers) + mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False)) + + with pytest.raises(OperationalException): + freqtrade._gen_pair_whitelist(base_currency='BTC') + + @pytest.mark.skip(reason="Test not implemented") def test_refresh_whitelist() -> None: pass @@ -1914,6 +1923,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order, fee, markets, mocker): default_conf['experimental']['check_depth_of_market']['enabled'] = True + # delta is 100 which is impossible to reach. hence check_depth_of_market will return false default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 patch_RPCManager(mocker) mocker.patch.multiple( @@ -1924,7 +1934,6 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, get_fee=fee, get_markets=markets ) - # Save state of current whitelist freqtrade = FreqtradeBot(default_conf) patch_get_signal(freqtrade) @@ -1934,33 +1943,47 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, assert trade is None -def test_order_book_bid_strategy(default_conf) -> None: +def test_order_book_bid_strategy1(default_conf) -> None: + """ + test if function get_target_bid will return the order book price + instead of the ask rate + """ default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 2 default_conf['bid_strategy']['ask_last_balance'] = 0 default_conf['telegram']['enabled'] = False + freqtrade = FreqtradeBot(default_conf) + assert freqtrade.get_target_bid('BTC/USDT', {'ask': 200000, 'last': 200000}) != 200000 + + +def test_order_book_bid_strategy2(default_conf) -> None: + """ + test if function get_target_bid will return ask rate instead + of the order book rate + """ + default_conf['exchange']['name'] = 'binance' + default_conf['experimental']['bid_strategy']['use_order_book'] = True + default_conf['experimental']['bid_strategy']['order_book_top'] = 1 + default_conf['bid_strategy']['ask_last_balance'] = 0 + default_conf['telegram']['enabled'] = False + freqtrade = FreqtradeBot(default_conf) assert freqtrade.get_target_bid('BTC/USDT', {'ask': 2, 'last': 2}) == 2 -def test_trunc_num(default_conf) -> None: - default_conf['telegram']['enabled'] = False - freqtrade = FreqtradeBot(default_conf) - - assert freqtrade._trunc_num(10.1111, 2) == 10.11 - - def test_check_depth_of_market_buy(default_conf) -> None: default_conf['telegram']['enabled'] = False default_conf['exchange']['name'] = 'binance' default_conf['experimental']['check_depth_of_market']['enabled'] = True + # delta is 100 which is impossible to reach. hence function will return false default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 freqtrade = FreqtradeBot(default_conf) - assert freqtrade._check_depth_of_market_buy('ETH/BTC') is False + conf = default_conf['experimental']['check_depth_of_market'] + assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order, From 78610bb47f84007718b8dd6dfe1a7672bb1adb8c Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Tue, 14 Aug 2018 18:12:44 +0800 Subject: [PATCH 06/10] mock order_book and additional test --- freqtrade/freqtradebot.py | 1 + freqtrade/tests/conftest.py | 33 +++++++++++++++++++ freqtrade/tests/test_freqtradebot.py | 47 ++++++++++++++++++++++------ 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 55b9c577f..715cbce80 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -253,6 +253,7 @@ class FreqtradeBot(object): logger.info('Getting price from order book') order_book_top = experimental_bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) + logger.debug('order_book %s', order_book) # top 1 = index 0 order_book_rate = order_book['bids'][order_book_top - 1][0] # if ticker has lower rate, then use ticker ( usefull if down trending ) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index d7f7e96d9..8a40397f3 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -419,6 +419,39 @@ def limit_sell_order(): } +@pytest.fixture +def order_book_l2(): + return MagicMock(return_value={ + 'bids': [ + [0.043936, 10.442], + [0.043935, 31.865], + [0.043933, 11.212], + [0.043928, 0.088], + [0.043925, 10.0], + [0.043921, 10.0], + [0.04392, 37.64], + [0.043899, 0.066], + [0.043885, 0.676], + [0.04387, 22.758] + ], + 'asks': [ + [0.043949, 0.346], + [0.04395, 0.608], + [0.043951, 3.948], + [0.043954, 0.288], + [0.043958, 9.277], + [0.043995, 1.566], + [0.044, 0.588], + [0.044002, 0.992], + [0.044003, 0.095], + [0.04402, 37.64] + ], + 'timestamp': None, + 'datetime': None, + 'nonce': 288004540 + }) + + @pytest.fixture def ticker_history(): return [ diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4357b573a..85f393b9d 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1887,10 +1887,12 @@ def test_get_real_amount_open_trade(default_conf, mocker): assert freqtrade.get_real_amount(trade, order) == amount -def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker): +def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker, + order_book_l2): default_conf['experimental']['check_depth_of_market']['enabled'] = True default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 0.1 patch_RPCManager(mocker) + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1920,12 +1922,13 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, assert whitelist == default_conf['exchange']['pair_whitelist'] -def test_order_book_depth_of_market_high_delta(default_conf, ticker, - limit_buy_order, fee, markets, mocker): +def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order, + fee, markets, mocker, order_book_l2): default_conf['experimental']['check_depth_of_market']['enabled'] = True # delta is 100 which is impossible to reach. hence check_depth_of_market will return false default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 patch_RPCManager(mocker) + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), @@ -1943,11 +1946,12 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, assert trade is None -def test_order_book_bid_strategy1(default_conf) -> None: +def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None: """ test if function get_target_bid will return the order book price instead of the ask rate """ + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 2 @@ -1955,14 +1959,31 @@ def test_order_book_bid_strategy1(default_conf) -> None: default_conf['telegram']['enabled'] = False freqtrade = FreqtradeBot(default_conf) - assert freqtrade.get_target_bid('BTC/USDT', {'ask': 200000, 'last': 200000}) != 200000 + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.045, 'last': 0.046}) == 0.043935 -def test_order_book_bid_strategy2(default_conf) -> None: +def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None: + """ + test if function get_target_bid will return the ask rate (since its value is lower) + instead of the order book rate (even if enabled) + """ + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) + default_conf['exchange']['name'] = 'binance' + default_conf['experimental']['bid_strategy']['use_order_book'] = True + default_conf['experimental']['bid_strategy']['order_book_top'] = 2 + default_conf['bid_strategy']['ask_last_balance'] = 0 + default_conf['telegram']['enabled'] = False + + freqtrade = FreqtradeBot(default_conf) + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.042, 'last': 0.046}) == 0.042 + + +def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2) -> None: """ test if function get_target_bid will return ask rate instead of the order book rate """ + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 1 @@ -1971,10 +1992,14 @@ def test_order_book_bid_strategy2(default_conf) -> None: freqtrade = FreqtradeBot(default_conf) - assert freqtrade.get_target_bid('BTC/USDT', {'ask': 2, 'last': 2}) == 2 + assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.03, 'last': 0.029}) == 0.03 -def test_check_depth_of_market_buy(default_conf) -> None: +def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None: + """ + test check depth of market + """ + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) default_conf['telegram']['enabled'] = False default_conf['exchange']['name'] = 'binance' default_conf['experimental']['check_depth_of_market']['enabled'] = True @@ -1987,7 +2012,11 @@ def test_check_depth_of_market_buy(default_conf) -> None: def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order, - fee, markets, mocker) -> None: + fee, markets, mocker, order_book_l2) -> None: + """ + test order book ask strategy + """ + mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['ask_strategy']['use_order_book'] = True default_conf['experimental']['ask_strategy']['order_book_min'] = 1 From a489a044adca228a68e2d856d2a4af30b909be1e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Aug 2018 13:17:07 +0200 Subject: [PATCH 07/10] Mock Exchange results to avoid random test-failures --- freqtrade/tests/exchange/test_exchange.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 60f51553a..4686176c4 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -515,14 +515,17 @@ def test_get_ticker(default_conf, mocker): exchange.get_ticker(pair='ETH/BTC', refresh=True) -def test_get_order_book(default_conf, mocker): +def test_get_order_book(default_conf, mocker, order_book_l2): default_conf['exchange']['name'] = 'binance' - exchange = Exchange(default_conf) - order_book = exchange.get_order_book(pair='ETH/BTC', limit=50) + api_mock = MagicMock() + + api_mock.fetch_l2_order_book = order_book_l2 + exchange = get_patched_exchange(mocker, default_conf, api_mock) + order_book = exchange.get_order_book(pair='ETH/BTC', limit=10) assert 'bids' in order_book assert 'asks' in order_book - assert len(order_book['bids']) == 50 - assert len(order_book['asks']) == 50 + assert len(order_book['bids']) == 10 + assert len(order_book['asks']) == 10 def test_get_order_book_exception(default_conf, mocker): From 42587741dd0b6b91d4e837ca546ca3fde256d6d0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Aug 2018 13:21:10 +0200 Subject: [PATCH 08/10] mock exchange to avoid random failures --- freqtrade/tests/test_freqtradebot.py | 36 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 85f393b9d..43ab587ec 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1946,12 +1946,17 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o assert trade is None -def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None: +def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) -> None: """ test if function get_target_bid will return the order book price instead of the ask rate """ - mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_markets=markets, + get_order_book=order_book_l2 + ) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 2 @@ -1962,12 +1967,17 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None: assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.045, 'last': 0.046}) == 0.043935 -def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None: +def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) -> None: """ test if function get_target_bid will return the ask rate (since its value is lower) instead of the order book rate (even if enabled) """ - mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_markets=markets, + get_order_book=order_book_l2 + ) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 2 @@ -1978,12 +1988,17 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None: assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.042, 'last': 0.046}) == 0.042 -def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2) -> None: +def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2, markets) -> None: """ test if function get_target_bid will return ask rate instead of the order book rate """ - mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_markets=markets, + get_order_book=order_book_l2 + ) default_conf['exchange']['name'] = 'binance' default_conf['experimental']['bid_strategy']['use_order_book'] = True default_conf['experimental']['bid_strategy']['order_book_top'] = 1 @@ -1995,11 +2010,16 @@ def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2) -> None: assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.03, 'last': 0.029}) == 0.03 -def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2) -> None: +def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) -> None: """ test check depth of market """ - mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + validate_pairs=MagicMock(), + get_markets=markets, + get_order_book=order_book_l2 + ) default_conf['telegram']['enabled'] = False default_conf['exchange']['name'] = 'binance' default_conf['experimental']['check_depth_of_market']['enabled'] = True From b6b89a464fd388b3955990a59f54a5100665bb77 Mon Sep 17 00:00:00 2001 From: Nullart2 Date: Wed, 29 Aug 2018 17:38:43 +0800 Subject: [PATCH 09/10] move order_book config out of experimental --- config.json.example | 29 ++++++++---------- config_full.json.example | 29 ++++++++---------- docs/configuration.md | 14 ++++----- freqtrade/constants.py | 44 ++++++++++++---------------- freqtrade/freqtradebot.py | 24 +++++++-------- freqtrade/tests/conftest.py | 29 ++++++++---------- freqtrade/tests/test_freqtradebot.py | 32 ++++++++++---------- 7 files changed, 92 insertions(+), 109 deletions(-) diff --git a/config.json.example b/config.json.example index c3dc6b5b6..7a0bb6b9b 100644 --- a/config.json.example +++ b/config.json.example @@ -11,7 +11,18 @@ "sell": 30 }, "bid_strategy": { - "ask_last_balance": 0.0 + "ask_last_balance": 0.0, + "use_order_book": false, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 }, "exchange": { "name": "bittrex", @@ -37,21 +48,7 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "ignore_roi_if_buy_signal": false, - "check_depth_of_market": { - "enabled": false, - "bids_to_ask_delta": 1 - }, - "bid_strategy": { - "use_order_book": false, - "order_book_top": 2, - "percent_from_top": 0 - }, - "ask_strategy":{ - "use_order_book": false, - "order_book_min": 1, - "order_book_max": 9 - } + "ignore_roi_if_buy_signal": false }, "telegram": { "enabled": true, diff --git a/config_full.json.example b/config_full.json.example index 5a364a93b..717f3c7df 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -20,7 +20,18 @@ "sell": 30 }, "bid_strategy": { - "ask_last_balance": 0.0 + "ask_last_balance": 0.0, + "use_order_book": false, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy":{ + "use_order_book": false, + "order_book_min": 1, + "order_book_max": 9 }, "exchange": { "name": "bittrex", @@ -46,21 +57,7 @@ "experimental": { "use_sell_signal": false, "sell_profit_only": false, - "ignore_roi_if_buy_signal": false, - "check_depth_of_market": { - "enabled": false, - "bids_to_ask_delta": 1 - }, - "bid_strategy": { - "use_order_book": false, - "order_book_top": 2, - "percent_from_top": 0 - }, - "ask_strategy":{ - "use_order_book": false, - "order_book_min": 1, - "order_book_max": 9 - }s + "ignore_roi_if_buy_signal": false }, "telegram": { "enabled": true, diff --git a/docs/configuration.md b/docs/configuration.md index 5ffe24556..edc376f90 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -31,6 +31,13 @@ The table below will list all configuration parameters. | `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. | `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. +| `bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids. +| `bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. +| `bid_strategy.check_depth_of_market.enabled` | false | No | Does not buy if the % difference of buy orders and sell orders is met in Order Book. +| `experimental.check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. +| `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. | `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. @@ -39,13 +46,6 @@ The table below will list all configuration parameters. | `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`. | `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision. | `experimental.ignore_roi_if_buy_signal` | false | No | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal` -| `experimental.check_depth_of_market` | false | No | Does not sell if the % difference of buy orders and sell orders is met in Order Book. -| `experimental.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. -| `experimental.bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids. -| `experimental.bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. -| `experimental.ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks. -| `experimental.ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. -| `experimental.ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. | `telegram.enabled` | true | Yes | Enable or not the usage of Telegram. | `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index b7431af3c..59b1c3ccf 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -78,41 +78,35 @@ CONF_SCHEMA = { 'type': 'number', 'minimum': 0, 'maximum': 1, - 'exclusiveMaximum': False + 'exclusiveMaximum': False, + 'use_order_book': {'type': 'boolean'}, + 'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1}, + 'check_depth_of_market': { + 'type': 'object', + 'properties': { + 'enabled': {'type': 'boolean'}, + 'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, + } + }, }, }, 'required': ['ask_last_balance'] }, + 'ask_strategy': { + 'type': 'object', + 'properties': { + 'use_order_book': {'type': 'boolean'}, + 'order_book_min': {'type': 'number', 'minimum': 1}, + 'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50} + } + }, 'exchange': {'$ref': '#/definitions/exchange'}, 'experimental': { 'type': 'object', 'properties': { 'use_sell_signal': {'type': 'boolean'}, 'sell_profit_only': {'type': 'boolean'}, - 'ignore_roi_if_buy_signal_true': {'type': 'boolean'}, - 'check_depth_of_market': { - 'type': 'object', - 'properties': { - 'enabled': {'type': 'boolean'}, - 'bids_to_ask_delta': {'type': 'number', 'minimum': 0}, - } - }, - 'bid_strategy': { - 'type': 'object', - 'properties': { - 'percent_from_top': {'type': 'number', 'minimum': 0}, - 'use_order_book': {'type': 'boolean'}, - 'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1} - } - }, - 'ask_strategy': { - 'type': 'object', - 'properties': { - 'use_order_book': {'type': 'boolean'}, - 'order_book_min': {'type': 'number', 'minimum': 1}, - 'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50} - } - } + 'ignore_roi_if_buy_signal_true': {'type': 'boolean'} } }, 'telegram': { diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 715cbce80..1f25fb0e4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -247,11 +247,11 @@ class FreqtradeBot(object): ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask']) used_rate = ticker_rate - experimental_bid_strategy = self.config.get('experimental', {}).get('bid_strategy', {}) - if 'use_order_book' in experimental_bid_strategy and\ - experimental_bid_strategy.get('use_order_book', False): + config_bid_strategy = self.config.get('bid_strategy', {}) + if 'use_order_book' in config_bid_strategy and\ + config_bid_strategy.get('use_order_book', False): logger.info('Getting price from order book') - order_book_top = experimental_bid_strategy.get('order_book_top', 1) + order_book_top = config_bid_strategy.get('order_book_top', 1) order_book = self.exchange.get_order_book(pair, order_book_top) logger.debug('order_book %s', order_book) # top 1 = index 0 @@ -359,11 +359,11 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(_pair, interval, thistory) if buy and not sell: - experimental_check_depth_of_market = self.config.get('experimental', {}).\ + bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\ get('check_depth_of_market', {}) - if (experimental_check_depth_of_market.get('enabled', False)) and\ - (experimental_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): - if self._check_depth_of_market_buy(_pair, experimental_check_depth_of_market): + if (bidstrat_check_depth_of_market.get('enabled', False)) and\ + (bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0): + if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market): return self.execute_buy(_pair, stake_amount) else: return False @@ -551,12 +551,12 @@ class FreqtradeBot(object): (buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval, ticker) - experimental_ask_strategy = self.config.get('experimental', {}).get('ask_strategy', {}) - if experimental_ask_strategy.get('use_order_book', False): + config_ask_strategy = self.config.get('ask_strategy', {}) + if config_ask_strategy.get('use_order_book', False): logger.info('Using order book for selling...') # logger.debug('Order book %s',orderBook) - order_book_min = experimental_ask_strategy.get('order_book_min', 1) - order_book_max = experimental_ask_strategy.get('order_book_max', 1) + order_book_min = config_ask_strategy.get('order_book_min', 1) + order_book_max = config_ask_strategy.get('order_book_max', 1) order_book = self.exchange.get_order_book(trade.pair, order_book_max) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 8a40397f3..af9062cab 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -102,7 +102,18 @@ def default_conf(): "sell": 30 }, "bid_strategy": { - "ask_last_balance": 0.0 + "ask_last_balance": 0.0, + "use_order_book": False, + "order_book_top": 1, + "check_depth_of_market": { + "enabled": False, + "bids_to_ask_delta": 1 + } + }, + "ask_strategy": { + "use_order_book": False, + "order_book_min": 1, + "order_book_max": 1 }, "exchange": { "name": "bittrex", @@ -116,22 +127,6 @@ def default_conf(): "NEO/BTC" ] }, - "experimental": { - "check_depth_of_market": { - "enabled": False, - "bids_to_ask_delta": 1 - }, - "bid_strategy": { - "percent_from_top": 0, - "use_order_book": False, - "order_book_top": 1 - }, - "ask_strategy": { - "use_order_book": False, - "order_book_min": 1, - "order_book_max": 1 - } - }, "telegram": { "enabled": True, "token": "token", diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 43ab587ec..389215f6f 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -1889,8 +1889,8 @@ def test_get_real_amount_open_trade(default_conf, mocker): def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker, order_book_l2): - default_conf['experimental']['check_depth_of_market']['enabled'] = True - default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 0.1 + default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True + default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 0.1 patch_RPCManager(mocker) mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) mocker.patch.multiple( @@ -1924,9 +1924,9 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order, fee, markets, mocker, order_book_l2): - default_conf['experimental']['check_depth_of_market']['enabled'] = True + default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True # delta is 100 which is impossible to reach. hence check_depth_of_market will return false - default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 + default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100 patch_RPCManager(mocker) mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) mocker.patch.multiple( @@ -1958,8 +1958,8 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) get_order_book=order_book_l2 ) default_conf['exchange']['name'] = 'binance' - default_conf['experimental']['bid_strategy']['use_order_book'] = True - default_conf['experimental']['bid_strategy']['order_book_top'] = 2 + default_conf['bid_strategy']['use_order_book'] = True + default_conf['bid_strategy']['order_book_top'] = 2 default_conf['bid_strategy']['ask_last_balance'] = 0 default_conf['telegram']['enabled'] = False @@ -1979,8 +1979,8 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) get_order_book=order_book_l2 ) default_conf['exchange']['name'] = 'binance' - default_conf['experimental']['bid_strategy']['use_order_book'] = True - default_conf['experimental']['bid_strategy']['order_book_top'] = 2 + default_conf['bid_strategy']['use_order_book'] = True + default_conf['bid_strategy']['order_book_top'] = 2 default_conf['bid_strategy']['ask_last_balance'] = 0 default_conf['telegram']['enabled'] = False @@ -2000,8 +2000,8 @@ def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2, markets) get_order_book=order_book_l2 ) default_conf['exchange']['name'] = 'binance' - default_conf['experimental']['bid_strategy']['use_order_book'] = True - default_conf['experimental']['bid_strategy']['order_book_top'] = 1 + default_conf['bid_strategy']['use_order_book'] = True + default_conf['bid_strategy']['order_book_top'] = 1 default_conf['bid_strategy']['ask_last_balance'] = 0 default_conf['telegram']['enabled'] = False @@ -2022,12 +2022,12 @@ def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) ) default_conf['telegram']['enabled'] = False default_conf['exchange']['name'] = 'binance' - default_conf['experimental']['check_depth_of_market']['enabled'] = True + default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True # delta is 100 which is impossible to reach. hence function will return false - default_conf['experimental']['check_depth_of_market']['bids_to_ask_delta'] = 100 + default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100 freqtrade = FreqtradeBot(default_conf) - conf = default_conf['experimental']['check_depth_of_market'] + conf = default_conf['bid_strategy']['check_depth_of_market'] assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False @@ -2038,9 +2038,9 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order """ mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2) default_conf['exchange']['name'] = 'binance' - default_conf['experimental']['ask_strategy']['use_order_book'] = True - default_conf['experimental']['ask_strategy']['order_book_min'] = 1 - default_conf['experimental']['ask_strategy']['order_book_max'] = 2 + default_conf['ask_strategy']['use_order_book'] = True + default_conf['ask_strategy']['order_book_min'] = 1 + default_conf['ask_strategy']['order_book_max'] = 2 default_conf['telegram']['enabled'] = False patch_RPCManager(mocker) mocker.patch.multiple( From f7b67cec5b50a93bf0bad4e30656b408caf8a47b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Aug 2018 19:16:41 +0200 Subject: [PATCH 10/10] Fix missing docstring --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index edc376f90..757310957 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -34,7 +34,7 @@ The table below will list all configuration parameters. | `bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids. | `bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids. | `bid_strategy.check_depth_of_market.enabled` | false | No | Does not buy if the % difference of buy orders and sell orders is met in Order Book. -| `experimental.check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. +| `bid_strategy.check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher. | `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.