Merge pull request #31 from nullart/nullartHFT

Nullart HFT PR
This commit is contained in:
Gert Wohlgemuth 2018-06-13 21:50:10 -07:00 committed by GitHub
commit 733217c4d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 42 deletions

View File

@ -5,6 +5,11 @@
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"ticker_interval" : "5m", "ticker_interval" : "5m",
"dry_run": false, "dry_run": false,
"disable_buy" : true,
"unfilledtimeout": {
"buy":10,
"sell":30
}
"trailing_stop": { "trailing_stop": {
"positive" : 0.005 "positive" : 0.005
}, },

View File

@ -4,6 +4,7 @@
"stake_amount": 0.05, "stake_amount": 0.05,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"dry_run": false, "dry_run": false,
"disable_buy" : true,
"ticker_interval": "5m", "ticker_interval": "5m",
"trailing_stop": true, "trailing_stop": true,
"minimal_roi": { "minimal_roi": {
@ -13,7 +14,10 @@
"0": 0.04 "0": 0.04
}, },
"stoploss": -0.10, "stoploss": -0.10,
"unfilledtimeout": 600, "unfilledtimeout": {
"buy":10,
"sell":30
}
"bid_strategy": { "bid_strategy": {
"ask_last_balance": 0.0, "ask_last_balance": 0.0,
"use_book_order": true, "use_book_order": true,

View File

@ -55,7 +55,14 @@ CONF_SCHEMA = {
'minProperties': 1 'minProperties': 1
}, },
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, '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': { 'bid_strategy': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@ -66,7 +73,7 @@ CONF_SCHEMA = {
'exclusiveMaximum': False 'exclusiveMaximum': False
}, },
'use_book_order': {'type': 'boolean'}, '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'] 'required': ['ask_last_balance']
}, },
@ -74,9 +81,9 @@ CONF_SCHEMA = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'use_book_order': {'type': 'boolean'}, 'use_book_order': {'type': 'boolean'},
'book_order_min': {'type': 'number', 'minimum':1}, 'book_order_min': {'type': 'number', 'minimum': 1},
'book_order_max': {'type': 'number', 'minimum':1} 'book_order_max': {'type': 'number', 'minimum': 1}
}, }
}, },
'exchange': {'$ref': '#/definitions/exchange'}, 'exchange': {'$ref': '#/definitions/exchange'},
'experimental': { 'experimental': {

View File

@ -239,10 +239,11 @@ def get_balances() -> dict:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@retrier @retrier
def get_order_book(pair: str, refresh: Optional[bool] = True) -> dict: def get_order_book(pair: str, limit: Optional[int] = 1000) -> dict:
try: try:
return _API.fetch_order_book(pair) return _API.fetch_order_book(pair, limit)
except ccxt.NotSupported as e: except ccxt.NotSupported as e:
raise OperationalException( raise OperationalException(
f'Exchange {_API.name} does not support fetching order book.' f'Exchange {_API.name} does not support fetching order book.'
@ -253,6 +254,7 @@ def get_order_book(pair: str, refresh: Optional[bool] = True) -> dict:
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) raise OperationalException(e)
@retrier @retrier
def get_tickers() -> Dict: def get_tickers() -> Dict:
try: try:

View File

@ -156,12 +156,15 @@ class FreqtradeBot(object):
state_changed |= self.process_maybe_execute_sell(trade) state_changed |= self.process_maybe_execute_sell(trade)
# Then looking for buy opportunities # Then looking for buy opportunities
if (self.config['disable_buy']):
logger.info('Buy disabled...')
else:
if len(trades) < self.config['max_open_trades']: if len(trades) < self.config['max_open_trades']:
state_changed = self.process_maybe_execute_buy() state_changed = self.process_maybe_execute_buy()
if 'unfilledtimeout' in self.config: if 'unfilledtimeout' in self.config:
# Check and handle any timed out open orders # Check and handle any timed out open orders
self.check_handle_timedout(self.config['unfilledtimeout']) self.check_handle_timedout()
Trade.session.flush() Trade.session.flush()
except TemporaryError as error: except TemporaryError as error:
@ -241,17 +244,23 @@ class FreqtradeBot(object):
:return: float: Price :return: float: Price
""" """
if self.config['bid_strategy']['use_book_order']: ticker = exchange.get_ticker(pair)
logger.info('Getting price from Order Book')
orderBook = exchange.get_order_book(pair)
return orderBook['bids'][self.config['bid_strategy']['book_order_top']][0]
else:
logger.info('Using Ask / Last Price')
ticker = exchange.get_ticker(pair);
if ticker['ask'] < ticker['last']: if ticker['ask'] < ticker['last']:
return ticker['ask'] return ticker['ask']
balance = self.config['bid_strategy']['ask_last_balance'] balance = self.config['bid_strategy']['ask_last_balance']
return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) 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)
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')
return ticker_rate
def create_trade(self) -> bool: def create_trade(self) -> bool:
""" """
@ -439,9 +448,15 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
logger.info('Using order book for selling...') logger.info('Using order book for selling...')
orderBook = exchange.get_order_book(trade.pair) orderBook = exchange.get_order_book(trade.pair)
# logger.debug('Order book %s',orderBook) # logger.debug('Order book %s',orderBook)
for i in range(self.config['ask_strategy']['book_order_min'], orderBook_min = self.config['ask_strategy']['book_order_min']
self.config['ask_strategy']['book_order_max'] + 1): orderBook_max = self.config['ask_strategy']['book_order_max']
sell_rate = orderBook['asks'][i - 1][0] 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 (sell_rate < orderBook_rate):
sell_rate = orderBook_rate
if self.check_sell(trade, sell_rate, buy, sell): if self.check_sell(trade, sell_rate, buy, sell):
return True return True
break break
@ -458,19 +473,22 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
return True return True
return False 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 Check if any orders are timed out and cancel if neccessary
:param timeoutvalue: Number of minutes until order is considered timed out :param timeoutvalue: Number of minutes until order is considered timed out
:return: None :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(): for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
try: try:
# FIXME: Somehow the query above returns results # FIXME: Somehow the query above returns results
# where the open_order_id is in fact None. # 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. # updated via /forcesell in a different thread.
if not trade.open_order_id: if not trade.open_order_id:
continue continue
@ -485,12 +503,10 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
print(order) print(order)
# Check if trade is still actually open # Check if trade is still actually open
# this makes no real sense and causes errors! if (int(order['filled']) == 0) and (order['status'] == 'open'):
# if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
# if (filled(int(order['filled']) == 0) and (order['status'] == 'open'):
if order['side'] == 'buy' and ordertime < timeoutthreashold:
self.handle_timedout_limit_buy(trade, order) 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) self.handle_timedout_limit_sell(trade, order)
# FIX: 20180110, why is cancel.order unconditionally here, whereas # FIX: 20180110, why is cancel.order unconditionally here, whereas

View File

@ -89,7 +89,11 @@ def default_conf():
"0": 0.04 "0": 0.04
}, },
"stoploss": -0.10, "stoploss": -0.10,
"unfilledtimeout": 600, "disable_buy": False,
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": { "bid_strategy": {
"use_book_order": False, "use_book_order": False,
"book_order_top": 6, "book_order_top": 6,
@ -252,7 +256,8 @@ def limit_buy_order():
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'remaining': 0.0, 'remaining': 0.0,
'status': 'closed' 'status': 'closed',
'filled': 0.0
} }
@ -267,7 +272,8 @@ def limit_buy_order_old():
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'remaining': 90.99181073, 'remaining': 90.99181073,
'status': 'open' 'status': 'open',
'filled': 0.0
} }
@ -282,7 +288,8 @@ def limit_sell_order_old():
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'remaining': 90.99181073, 'remaining': 90.99181073,
'status': 'open' 'status': 'open',
'filled': 0.0
} }
@ -297,7 +304,8 @@ def limit_buy_order_old_partial():
'price': 0.00001099, 'price': 0.00001099,
'amount': 90.99181073, 'amount': 90.99181073,
'remaining': 67.99181073, 'remaining': 67.99181073,
'status': 'open' 'status': 'open',
'filled': 0.0
} }

View File

@ -499,7 +499,8 @@ def test_balance_fully_ask_side(mocker) -> None:
""" """
Test get_target_bid() method 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 assert freqtrade.get_target_bid('ETH/BTC') >= 0.07
@ -508,7 +509,8 @@ def test_balance_fully_last_side(mocker) -> None:
""" """
Test get_target_bid() method 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 assert freqtrade.get_target_bid('ETH/BTC') >= 0.07
@ -517,7 +519,8 @@ def test_balance_bigger_last_ask(mocker) -> None:
""" """
Test get_target_bid() method 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 assert freqtrade.get_target_bid('ETH/BTC') >= 0.07
@ -849,7 +852,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
Trade.session.add(trade_buy) Trade.session.add(trade_buy)
# check it does cancel buy orders over the time limit # 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 cancel_order_mock.call_count == 1
assert rpc_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() trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
@ -890,7 +893,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
Trade.session.add(trade_sell) Trade.session.add(trade_sell)
# check it does cancel sell orders over the time limit # 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 cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1 assert rpc_mock.call_count == 1
assert trade_sell.is_open is True assert trade_sell.is_open is True
@ -930,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 # check it does cancel buy orders over the time limit
# note this is for a partially-complete buy order # 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 cancel_order_mock.call_count == 1
assert rpc_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() trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
@ -981,7 +984,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
'recent call last):\n.*' 'recent call last):\n.*'
) )
freqtrade.check_handle_timedout(600) freqtrade.check_handle_timedout()
assert filter(regexp.match, caplog.record_tuples) assert filter(regexp.match, caplog.record_tuples)