From 5ed7008933752548480fb47775973391553c98a3 Mon Sep 17 00:00:00 2001 From: Nullart Date: Thu, 14 Jun 2018 09:32:52 +0800 Subject: [PATCH 1/3] separating unfulfilled timeouts for buy and sell --- config.json.example | 5 ++++- config_full.json.example | 5 ++++- freqtrade/constants.py | 11 +++++++++-- freqtrade/freqtradebot.py | 15 +++++++++------ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/config.json.example b/config.json.example index d133cd4df..7060e9881 100644 --- a/config.json.example +++ b/config.json.example @@ -5,7 +5,10 @@ "fiat_display_currency": "USD", "ticker_interval" : "5m", "dry_run": false, - "unfilledtimeout": 600, + "unfilledtimeout": { + "buy":10, + "sell":30 + } "bid_strategy": { "ask_last_balance": 0.0, "use_book_order": true, diff --git a/config_full.json.example b/config_full.json.example index 2b0930049..5fdf7b036 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -12,7 +12,10 @@ "0": 0.04 }, "stoploss": -0.10, - "unfilledtimeout": 600, + "unfilledtimeout": { + "buy":10, + "sell":30 + } "bid_strategy": { "ask_last_balance": 0.0, "use_book_order": true, diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 9cfa21565..ba7baab57 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -55,7 +55,14 @@ CONF_SCHEMA = { 'minProperties': 1 }, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, - 'unfilledtimeout': {'type': 'integer', 'minimum': 0}, + 'unfilledtimeout': { + 'type': 'object', + 'properties': { + 'use_book_order': {'type': 'boolean'}, + 'buy': {'type': 'number', 'minimum':3}, + 'sell': {'type': 'number', 'minimum':10} + } + }, 'bid_strategy': { 'type': 'object', 'properties': { @@ -76,7 +83,7 @@ CONF_SCHEMA = { 'use_book_order': {'type': 'boolean'}, 'book_order_min': {'type': 'number', 'minimum':1}, 'book_order_max': {'type': 'number', 'minimum':1} - }, + } }, 'exchange': {'$ref': '#/definitions/exchange'}, 'experimental': { diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 378c8af91..d36bbefca 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -164,7 +164,7 @@ class FreqtradeBot(object): if 'unfilledtimeout' in self.config: # Check and handle any timed out open orders - self.check_handle_timedout(self.config['unfilledtimeout']) + self.check_handle_timedout() Trade.session.flush() except TemporaryError as error: @@ -461,19 +461,22 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ return True return False - def check_handle_timedout(self, timeoutvalue: int) -> None: + def check_handle_timedout(self) -> None: """ Check if any orders are timed out and cancel if neccessary :param timeoutvalue: Number of minutes until order is considered timed out :return: None """ - timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime + buy_timeout = self.config['unfilledtimeout']['buy'] + sell_timeout = self.config['unfilledtimeout']['sell'] + buy_timeoutthreashold = arrow.utcnow().shift(minutes=-buy_timeout).datetime + sell_timeoutthreashold = arrow.utcnow().shift(minutes=-sell_timeout).datetime for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all(): try: # FIXME: Somehow the query above returns results # where the open_order_id is in fact None. - # This is probably because the record got + # This is probably because the record got # updated via /forcesell in a different thread. if not trade.open_order_id: continue @@ -488,9 +491,9 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ # Check if trade is still actually open if (int(order['filled']) == 0) and (order['status']=='open'): - if order['side'] == 'buy' and ordertime < timeoutthreashold: + if order['side'] == 'buy' and ordertime < buy_timeoutthreashold: self.handle_timedout_limit_buy(trade, order) - elif order['side'] == 'sell' and ordertime < timeoutthreashold: + elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold: self.handle_timedout_limit_sell(trade, order) # FIX: 20180110, why is cancel.order unconditionally here, whereas From dc03b41c68df7ec0816e79a7dcd3dd9aa448b6c9 Mon Sep 17 00:00:00 2001 From: Nullart Date: Thu, 14 Jun 2018 09:57:14 +0800 Subject: [PATCH 2/3] added: disable_buy feature, this will only tell bot to sell whatever is needed to sell added: order book buy and sell will check ticker first; for buying, if ticker is lower than buy order price, use ticker; for selling, if ticker is higher than sell order price, use ticker --- config.json.example | 1 + config_full.json.example | 1 + freqtrade/exchange/__init__.py | 4 ++-- freqtrade/freqtradebot.py | 30 +++++++++++++++++++--------- freqtrade/tests/conftest.py | 18 ++++++++++++----- freqtrade/tests/test_freqtradebot.py | 8 ++++---- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/config.json.example b/config.json.example index 7060e9881..ac4324aba 100644 --- a/config.json.example +++ b/config.json.example @@ -5,6 +5,7 @@ "fiat_display_currency": "USD", "ticker_interval" : "5m", "dry_run": false, + "disable_buy" : true, "unfilledtimeout": { "buy":10, "sell":30 diff --git a/config_full.json.example b/config_full.json.example index 5fdf7b036..299060489 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -4,6 +4,7 @@ "stake_amount": 0.05, "fiat_display_currency": "USD", "dry_run": false, + "disable_buy" : true, "ticker_interval": "5m", "minimal_roi": { "40": 0.0, diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 26e828db3..6bbc55f66 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -240,9 +240,9 @@ def get_balances() -> dict: raise OperationalException(e) @retrier -def get_order_book(pair: str, refresh: Optional[bool] = True) -> dict: +def get_order_book(pair: str, limit: Optional[int] = 1000) -> dict: try: - return _API.fetch_order_book(pair) + return _API.fetch_order_book(pair, limit) except ccxt.NotSupported as e: raise OperationalException( f'Exchange {_API.name} does not support fetching order book.' diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d36bbefca..02a3834e1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -159,8 +159,11 @@ class FreqtradeBot(object): state_changed |= self.process_maybe_execute_sell(trade) # Then looking for buy opportunities - if len(trades) < self.config['max_open_trades']: - state_changed = self.process_maybe_execute_buy() + if (self.config['disable_buy']): + logger.info('Buy disabled...') + else: + if len(trades) < self.config['max_open_trades']: + state_changed = self.process_maybe_execute_buy() if 'unfilledtimeout' in self.config: # Check and handle any timed out open orders @@ -244,17 +247,23 @@ class FreqtradeBot(object): :return: float: Price """ + ticker = exchange.get_ticker(pair); + if ticker['ask'] < ticker['last']: + return ticker['ask'] + balance = self.config['bid_strategy']['ask_last_balance'] + ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + if self.config['bid_strategy']['use_book_order']: logger.info('Getting price from Order Book') orderBook = exchange.get_order_book(pair) - return orderBook['bids'][self.config['bid_strategy']['book_order_top']][0] + orderBook_rate = orderBook['bids'][self.config['bid_strategy']['book_order_top']][0] + # if ticker has lower rate, then use ticker ( usefull if down trending ) + if ticker_rate < orderBook_rate: + return ticker_rate + return orderBook_rate else: logger.info('Using Ask / Last Price') - ticker = exchange.get_ticker(pair); - if ticker['ask'] < ticker['last']: - return ticker['ask'] - balance = self.config['bid_strategy']['ask_last_balance'] - return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) + return ticker_rate def create_trade(self) -> bool: @@ -444,7 +453,10 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ orderBook = exchange.get_order_book(trade.pair) # logger.debug('Order book %s',orderBook) for i in range(self.config['ask_strategy']['book_order_min'],self.config['ask_strategy']['book_order_max']+1): - sell_rate = orderBook['asks'][i-1][0] + orderBook_rate = orderBook['asks'][i-1][0] + # if orderbook has higher rate (high profit), use orderbook, otherwise just use sell rate + if (sell_rate < orderBook_rate): + sell_rate = orderBook_rate if self.check_sell(trade, sell_rate, buy, sell): return True break diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index fd4421d22..04145d531 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -85,7 +85,11 @@ def default_conf(): "0": 0.04 }, "stoploss": -0.10, - "unfilledtimeout": 600, + "disable_buy": False, + "unfilledtimeout": { + "buy":10, + "sell":30 + }, "bid_strategy": { "use_book_order": False, "book_order_top": 6, @@ -248,7 +252,8 @@ def limit_buy_order(): 'price': 0.00001099, 'amount': 90.99181073, 'remaining': 0.0, - 'status': 'closed' + 'status': 'closed', + 'filled':0.0 } @@ -263,7 +268,8 @@ def limit_buy_order_old(): 'price': 0.00001099, 'amount': 90.99181073, 'remaining': 90.99181073, - 'status': 'open' + 'status': 'open', + 'filled':0.0 } @@ -278,7 +284,8 @@ def limit_sell_order_old(): 'price': 0.00001099, 'amount': 90.99181073, 'remaining': 90.99181073, - 'status': 'open' + 'status': 'open', + 'filled':0.0 } @@ -293,7 +300,8 @@ def limit_buy_order_old_partial(): 'price': 0.00001099, 'amount': 90.99181073, 'remaining': 67.99181073, - 'status': 'open' + 'status': 'open', + 'filled':0.0 } diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index a2fd5260a..4ebc7bfeb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -852,7 +852,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe Trade.session.add(trade_buy) # check it does cancel buy orders over the time limit - freqtrade.check_handle_timedout(600) + freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all() @@ -893,7 +893,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, Trade.session.add(trade_sell) # check it does cancel sell orders over the time limit - freqtrade.check_handle_timedout(600) + freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 assert trade_sell.is_open is True @@ -933,7 +933,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old # check it does cancel buy orders over the time limit # note this is for a partially-complete buy order - freqtrade.check_handle_timedout(600) + freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 assert rpc_mock.call_count == 1 trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all() @@ -984,7 +984,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - 'recent call last):\n.*' ) - freqtrade.check_handle_timedout(600) + freqtrade.check_handle_timedout() assert filter(regexp.match, caplog.record_tuples) From 79dd0eb104cb57a53b5ec9c9407b363bc528558c Mon Sep 17 00:00:00 2001 From: Nullart Date: Thu, 14 Jun 2018 12:37:44 +0800 Subject: [PATCH 3/3] flake8 compliance --- freqtrade/constants.py | 10 +++++----- freqtrade/exchange/__init__.py | 2 ++ freqtrade/freqtradebot.py | 20 +++++++++++--------- freqtrade/tests/conftest.py | 12 ++++++------ freqtrade/tests/test_freqtradebot.py | 9 ++++++--- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index ba7baab57..bc8a7223c 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -59,8 +59,8 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'use_book_order': {'type': 'boolean'}, - 'buy': {'type': 'number', 'minimum':3}, - 'sell': {'type': 'number', 'minimum':10} + 'buy': {'type': 'number', 'minimum': 3}, + 'sell': {'type': 'number', 'minimum': 10} } }, 'bid_strategy': { @@ -73,7 +73,7 @@ CONF_SCHEMA = { 'exclusiveMaximum': False }, 'use_book_order': {'type': 'boolean'}, - 'book_order_top': {'type': 'number', 'maximum':20,'minimum':1} + 'book_order_top': {'type': 'number', 'maximum': 20, 'minimum': 1} }, 'required': ['ask_last_balance'] }, @@ -81,8 +81,8 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'use_book_order': {'type': 'boolean'}, - 'book_order_min': {'type': 'number', 'minimum':1}, - 'book_order_max': {'type': 'number', 'minimum':1} + 'book_order_min': {'type': 'number', 'minimum': 1}, + 'book_order_max': {'type': 'number', 'minimum': 1} } }, 'exchange': {'$ref': '#/definitions/exchange'}, diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 6bbc55f66..c0fc32633 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -239,6 +239,7 @@ def get_balances() -> dict: except ccxt.BaseError as e: raise OperationalException(e) + @retrier def get_order_book(pair: str, limit: Optional[int] = 1000) -> dict: try: @@ -253,6 +254,7 @@ def get_order_book(pair: str, limit: Optional[int] = 1000) -> dict: except ccxt.BaseError as e: raise OperationalException(e) + @retrier def get_tickers() -> Dict: try: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 02a3834e1..e91f631d4 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -247,7 +247,7 @@ class FreqtradeBot(object): :return: float: Price """ - ticker = exchange.get_ticker(pair); + ticker = exchange.get_ticker(pair) if ticker['ask'] < ticker['last']: return ticker['ask'] balance = self.config['bid_strategy']['ask_last_balance'] @@ -265,7 +265,6 @@ class FreqtradeBot(object): logger.info('Using Ask / Last Price') return ticker_rate - def create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, @@ -452,22 +451,25 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ logger.info('Using order book for selling...') orderBook = exchange.get_order_book(trade.pair) # logger.debug('Order book %s',orderBook) - for i in range(self.config['ask_strategy']['book_order_min'],self.config['ask_strategy']['book_order_max']+1): + orderBook_min = self.config['ask_strategy']['book_order_min'] + orderBook_max = self.config['ask_strategy']['book_order_max'] + for i in range(orderBook_min, orderBook_max+1): orderBook_rate = orderBook['asks'][i-1][0] - # if orderbook has higher rate (high profit), use orderbook, otherwise just use sell rate + # if orderbook has higher rate (high profit), + # use orderbook, otherwise just use sell rate if (sell_rate < orderBook_rate): - sell_rate = orderBook_rate + sell_rate = orderBook_rate if self.check_sell(trade, sell_rate, buy, sell): return True break else: 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: + def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool: if self.analyze.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell): self.execute_sell(trade, sell_rate) return True @@ -488,7 +490,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ try: # FIXME: Somehow the query above returns results # where the open_order_id is in fact None. - # This is probably because the record got + # This is probably because the record get_trades_for_order # updated via /forcesell in a different thread. if not trade.open_order_id: continue @@ -502,7 +504,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ ordertime = arrow.get(order['datetime']).datetime # Check if trade is still actually open - if (int(order['filled']) == 0) and (order['status']=='open'): + if (int(order['filled']) == 0) and (order['status'] == 'open'): if order['side'] == 'buy' and ordertime < buy_timeoutthreashold: self.handle_timedout_limit_buy(trade, order) elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold: diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 04145d531..746fb4f16 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -87,8 +87,8 @@ def default_conf(): "stoploss": -0.10, "disable_buy": False, "unfilledtimeout": { - "buy":10, - "sell":30 + "buy": 10, + "sell": 30 }, "bid_strategy": { "use_book_order": False, @@ -253,7 +253,7 @@ def limit_buy_order(): 'amount': 90.99181073, 'remaining': 0.0, 'status': 'closed', - 'filled':0.0 + 'filled': 0.0 } @@ -269,7 +269,7 @@ def limit_buy_order_old(): 'amount': 90.99181073, 'remaining': 90.99181073, 'status': 'open', - 'filled':0.0 + 'filled': 0.0 } @@ -285,7 +285,7 @@ def limit_sell_order_old(): 'amount': 90.99181073, 'remaining': 90.99181073, 'status': 'open', - 'filled':0.0 + 'filled': 0.0 } @@ -301,7 +301,7 @@ def limit_buy_order_old_partial(): 'amount': 90.99181073, 'remaining': 67.99181073, 'status': 'open', - 'filled':0.0 + 'filled': 0.0 } diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index 4ebc7bfeb..3cc78d3bb 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -502,7 +502,8 @@ def test_balance_fully_ask_side(mocker) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'use_book_order':False,'book_order_top':6,'ask_last_balance': 0.0}}) + param = {'use_book_order': False, 'book_order_top': 6, 'ask_last_balance': 0.0} + freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': param}) assert freqtrade.get_target_bid('ETH/BTC') >= 0.07 @@ -511,7 +512,8 @@ def test_balance_fully_last_side(mocker) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'use_book_order':False,'book_order_top':6,'ask_last_balance': 1.0}}) + param = {'use_book_order': False, 'book_order_top': 6, 'ask_last_balance': 0.0} + freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': param}) assert freqtrade.get_target_bid('ETH/BTC') >= 0.07 @@ -520,7 +522,8 @@ def test_balance_bigger_last_ask(mocker) -> None: """ Test get_target_bid() method """ - freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': {'use_book_order':False,'book_order_top':6,'ask_last_balance': 1.0}}) + param = {'use_book_order': False, 'book_order_top': 6, 'ask_last_balance': 0.0} + freqtrade = get_patched_freqtradebot(mocker, {'bid_strategy': param}) assert freqtrade.get_target_bid('ETH/BTC') >= 0.07