commit
733217c4d2
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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': {
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user