Merge branch 'develop' into interface_ordertimeoutcallback

This commit is contained in:
Matthias
2020-04-19 06:58:44 +02:00
57 changed files with 1241 additions and 487 deletions

View File

@@ -20,6 +20,7 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
from freqtrade.misc import safe_value_fallback
from freqtrade.pairlist.pairlistmanager import PairListManager
from freqtrade.persistence import Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
@@ -144,6 +145,10 @@ class FreqtradeBot:
self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist),
self.strategy.informative_pairs())
with self._sell_lock:
# Check and handle any timed out open orders
self.check_handle_timedout()
# Protect from collisions with forcesell.
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
# while selling is in process, since telegram messages arrive in an different thread.
@@ -155,8 +160,6 @@ class FreqtradeBot:
if self.get_free_open_trades():
self.enter_positions()
# Check and handle any timed out open orders
self.check_handle_timedout()
Trade.session.flush()
def _refresh_whitelist(self, trades: List[Trade] = []) -> List[str]:
@@ -395,16 +398,18 @@ class FreqtradeBot:
logger.info(f"Pair {pair} is currently locked.")
return False
# get_free_open_trades is checked before create_trade is called
# but it is still used here to prevent opening too many trades within one iteration
if not self.get_free_open_trades():
logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
return False
# running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal(
pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
if buy and not sell:
if not self.get_free_open_trades():
logger.debug("Can't open a new trade: max number of trades is reached.")
return False
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
@@ -599,7 +604,6 @@ class FreqtradeBot:
trades_closed = 0
for trade in trades:
try:
self.update_trade_state(trade)
if (self.strategy.order_types.get('stoploss_on_exchange') and
self.handle_stoploss_on_exchange(trade)):
@@ -859,19 +863,13 @@ class FreqtradeBot:
continue
order = self.exchange.get_order(trade.open_order_id, trade.pair)
except (RequestException, DependencyException, InvalidOrderException):
logger.info(
'Cannot query order for %s due to %s',
trade,
traceback.format_exc())
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
continue
# Check if trade is still actually open
if float(order.get('remaining', 0.0)) == 0.0:
self.wallets.update()
continue
trade_state_update = self.update_trade_state(trade, order)
if (order['side'] == 'buy' and (
order['status'] == 'canceled'
trade_state_update
or self._check_timed_out('buy', order)
or strategy_safe_wrapper(self.strategy.check_buy_timeout,
default_retval=False)(pair=trade.pair,
@@ -884,16 +882,16 @@ class FreqtradeBot:
self._notify_buy_cancel(trade, order_type)
elif (order['side'] == 'sell' and (
order['status'] == 'canceled'
trade_state_update
or self._check_timed_out('sell', order)
or strategy_safe_wrapper(self.strategy.check_sell_timeout,
default_retval=False)(pair=trade.pair,
trade=trade,
order=order))):
self.handle_timedout_limit_sell(trade, order)
reason = self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
order_type = self.strategy.order_types['sell']
self._notify_sell_cancel(trade, order_type)
self._notify_sell_cancel(trade, order_type, reason)
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""
@@ -902,15 +900,17 @@ class FreqtradeBot:
"""
if order['status'] != 'canceled':
reason = "cancelled due to timeout"
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Buy order %s for %s.', reason, trade)
corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
trade.amount)
else:
# Order was cancelled already, so we can reuse the existing dict
corder = order
reason = "cancelled on exchange"
logger.info('Buy order %s for %s.', reason, trade)
if corder.get('remaining', order['remaining']) == order['amount']:
logger.info('Buy order %s for %s.', reason, trade)
if safe_value_fallback(corder, order, 'remaining', 'remaining') == order['amount']:
logger.info('Buy order fully cancelled. Removing %s from database.', trade)
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
Trade.session.flush()
@@ -921,19 +921,10 @@ class FreqtradeBot:
# cancel_order may not contain the full order dict, so we need to fallback
# to the order dict aquired before cancelling.
# we need to fall back to the values from order if corder does not contain these keys.
trade.amount = order['amount'] - corder.get('remaining', order['remaining'])
trade.amount = order['amount'] - safe_value_fallback(corder, order,
'remaining', 'remaining')
trade.stake_amount = trade.amount * trade.open_rate
# verify if fees were taken from amount to avoid problems during selling
try:
new_amount = self.get_real_amount(trade, corder if 'fee' in corder else order,
trade.amount)
if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
trade.amount = new_amount
# Fee was applied, so set to 0
trade.fee_open = 0
trade.recalc_open_trade_price()
except DependencyException as e:
logger.warning("Could not update trade amount: %s", e)
self.update_trade_state(trade, corder, trade.amount)
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
@@ -943,14 +934,14 @@ class FreqtradeBot:
})
return False
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> str:
"""
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
:return: Reason for cancel
"""
# if trade is not partially completed, just cancel the trade
if order['remaining'] == order['amount']:
if order["status"] != "canceled":
if order['remaining'] == order['amount'] or order.get('filled') == 0.0:
if not self.exchange.check_order_canceled_empty(order):
reason = "cancelled due to timeout"
# if trade is not partially completed, just delete the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
@@ -960,15 +951,17 @@ class FreqtradeBot:
logger.info('Sell order %s for %s.', reason, trade)
trade.close_rate = None
trade.close_rate_requested = None
trade.close_profit = None
trade.close_profit_abs = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
return True
return reason
# TODO: figure out how to handle partially complete sell orders
return False
return 'partially filled - keeping order open'
def _safe_sell_amount(self, pair: str, amount: float) -> float:
"""
@@ -1087,7 +1080,7 @@ class FreqtradeBot:
# Send the message
self.rpc.send_msg(msg)
def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None:
def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
"""
Sends rpc notification when a sell cancel occured.
"""
@@ -1114,6 +1107,7 @@ class FreqtradeBot:
'close_date': trade.close_date,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'reason': reason,
}
if 'fiat_display_currency' in self.config:
@@ -1128,9 +1122,12 @@ class FreqtradeBot:
# Common update trade state methods
#
def update_trade_state(self, trade: Trade, action_order: dict = None) -> None:
def update_trade_state(self, trade: Trade, action_order: dict = None,
order_amount: float = None) -> bool:
"""
Checks trades with open orders and updates the amount if necessary
Handles closing both buy and sell orders.
:return: True if order has been cancelled without being filled partially, False otherwise
"""
# Get order details for actual price per unit
if trade.open_order_id:
@@ -1140,25 +1137,31 @@ class FreqtradeBot:
order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair)
except InvalidOrderException as exception:
logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception)
return
return False
# Try update amount (binance-fix)
try:
new_amount = self.get_real_amount(trade, order)
new_amount = self.get_real_amount(trade, order, order_amount)
if not isclose(order['amount'], new_amount, abs_tol=constants.MATH_CLOSE_PREC):
order['amount'] = new_amount
order.pop('filled', None)
# Fee was applied, so set to 0
trade.fee_open = 0
trade.recalc_open_trade_price()
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
if self.exchange.check_order_canceled_empty(order):
# Trade has been cancelled on exchange
# Handling of this will happen in check_handle_timeout.
return True
trade.update(order)
# Updating wallets when order is closed
if not trade.is_open:
self.wallets.update()
return False
def get_real_amount(self, trade: Trade, order: Dict, order_amount: float = None) -> float:
"""
Get real amount for the trade