|
|
|
@@ -143,6 +143,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.
|
|
|
|
@@ -154,8 +158,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]:
|
|
|
|
@@ -600,7 +602,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)):
|
|
|
|
@@ -860,30 +861,26 @@ 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')
|
|
|
|
|
or (self._check_timed_out('buy', order))):
|
|
|
|
|
if (order['side'] == 'buy' and (
|
|
|
|
|
trade_state_update
|
|
|
|
|
or self._check_timed_out('buy', order))):
|
|
|
|
|
self.handle_timedout_limit_buy(trade, order)
|
|
|
|
|
self.wallets.update()
|
|
|
|
|
order_type = self.strategy.order_types['buy']
|
|
|
|
|
self._notify_buy_cancel(trade, order_type)
|
|
|
|
|
|
|
|
|
|
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
|
|
|
|
|
or (self._check_timed_out('sell', order))):
|
|
|
|
|
self.handle_timedout_limit_sell(trade, order)
|
|
|
|
|
elif (order['side'] == 'sell' and (
|
|
|
|
|
trade_state_update
|
|
|
|
|
or self._check_timed_out('sell', 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:
|
|
|
|
|
"""
|
|
|
|
@@ -916,17 +913,7 @@ class FreqtradeBot:
|
|
|
|
|
# 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.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 if 'fee' in corder else order, trade.amount)
|
|
|
|
|
|
|
|
|
|
trade.open_order_id = None
|
|
|
|
|
logger.info('Partial buy order timeout for %s.', trade)
|
|
|
|
@@ -936,14 +923,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)
|
|
|
|
@@ -953,16 +940,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:
|
|
|
|
|
"""
|
|
|
|
@@ -1081,7 +1069,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.
|
|
|
|
|
"""
|
|
|
|
@@ -1108,6 +1096,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:
|
|
|
|
@@ -1122,9 +1111,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:
|
|
|
|
@@ -1134,25 +1126,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
|
|
|
|
|
del order['filled']
|
|
|
|
|
# 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
|
|
|
|
|