|
|
@ -66,6 +66,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
|
|
|
init_db(self.config.get('db_url', None), clean_open_orders=self.config['dry_run'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO-lev: Do anything with this?
|
|
|
|
self.wallets = Wallets(self.config, self.exchange)
|
|
|
|
self.wallets = Wallets(self.config, self.exchange)
|
|
|
|
|
|
|
|
|
|
|
|
PairLocks.timeframe = self.config['timeframe']
|
|
|
|
PairLocks.timeframe = self.config['timeframe']
|
|
|
@ -77,6 +78,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
# so anything in the Freqtradebot instance should be ready (initialized), including
|
|
|
|
# so anything in the Freqtradebot instance should be ready (initialized), including
|
|
|
|
# the initial state of the bot.
|
|
|
|
# the initial state of the bot.
|
|
|
|
# Keep this at the end of this initialization method.
|
|
|
|
# Keep this at the end of this initialization method.
|
|
|
|
|
|
|
|
# TODO-lev: Do I need to consider the rpc, pairlists or dataprovider?
|
|
|
|
self.rpc: RPCManager = RPCManager(self)
|
|
|
|
self.rpc: RPCManager = RPCManager(self)
|
|
|
|
|
|
|
|
|
|
|
|
self.pairlists = PairListManager(self.exchange, self.config)
|
|
|
|
self.pairlists = PairListManager(self.exchange, self.config)
|
|
|
@ -98,7 +100,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
initial_state = self.config.get('initial_state')
|
|
|
|
initial_state = self.config.get('initial_state')
|
|
|
|
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
|
|
|
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
|
|
|
|
|
|
|
|
|
|
|
# Protect sell-logic from forcesell and vice versa
|
|
|
|
# Protect exit-logic from forcesell and vice versa
|
|
|
|
self._sell_lock = Lock()
|
|
|
|
self._sell_lock = Lock()
|
|
|
|
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
|
|
|
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
|
|
|
|
|
|
|
|
|
|
@ -170,9 +172,9 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
# Check and handle any timed out open orders
|
|
|
|
# Check and handle any timed out open orders
|
|
|
|
self.check_handle_timedout()
|
|
|
|
self.check_handle_timedout()
|
|
|
|
|
|
|
|
|
|
|
|
# Protect from collisions with forcesell.
|
|
|
|
# Protect from collisions with forceexit.
|
|
|
|
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
|
|
|
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
|
|
|
# while selling is in process, since telegram messages arrive in an different thread.
|
|
|
|
# while exiting is in process, since telegram messages arrive in an different thread.
|
|
|
|
with self._sell_lock:
|
|
|
|
with self._sell_lock:
|
|
|
|
trades = Trade.get_open_trades()
|
|
|
|
trades = Trade.get_open_trades()
|
|
|
|
# First process current opened trades (positions)
|
|
|
|
# First process current opened trades (positions)
|
|
|
@ -289,8 +291,8 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def handle_insufficient_funds(self, trade: Trade):
|
|
|
|
def handle_insufficient_funds(self, trade: Trade):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Determine if we ever opened a sell order for this trade.
|
|
|
|
Determine if we ever opened a exiting order for this trade.
|
|
|
|
If not, try update buy fees - otherwise "refind" the open order we obviously lost.
|
|
|
|
If not, try update entering fees - otherwise "refind" the open order we obviously lost.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
sell_order = trade.select_order('sell', None)
|
|
|
|
sell_order = trade.select_order('sell', None)
|
|
|
|
if sell_order:
|
|
|
|
if sell_order:
|
|
|
@ -312,7 +314,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
def refind_lost_order(self, trade):
|
|
|
|
def refind_lost_order(self, trade):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Try refinding a lost trade.
|
|
|
|
Try refinding a lost trade.
|
|
|
|
Only used when InsufficientFunds appears on sell orders (stoploss or sell).
|
|
|
|
Only used when InsufficientFunds appears on exit orders (stoploss or long sell/short buy).
|
|
|
|
Tries to walk the stored orders and sell them off eventually.
|
|
|
|
Tries to walk the stored orders and sell them off eventually.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
logger.info(f"Trying to refind lost order for {trade}")
|
|
|
|
logger.info(f"Trying to refind lost order for {trade}")
|
|
|
@ -323,7 +325,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
logger.debug(f"Order {order} is no longer open.")
|
|
|
|
logger.debug(f"Order {order} is no longer open.")
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
if order.ft_order_side == 'buy':
|
|
|
|
if order.ft_order_side == 'buy':
|
|
|
|
# Skip buy side - this is handled by reupdate_buy_order_fees
|
|
|
|
# Skip buy side - this is handled by reupdate_enter_order_fees
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
|
|
|
fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
|
|
|
@ -350,7 +352,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def enter_positions(self) -> int:
|
|
|
|
def enter_positions(self) -> int:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Tries to execute buy orders for new trades (positions)
|
|
|
|
Tries to execute long buy/short sell orders for new trades (positions)
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
trades_created = 0
|
|
|
|
trades_created = 0
|
|
|
|
|
|
|
|
|
|
|
@ -366,7 +368,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
if not whitelist:
|
|
|
|
if not whitelist:
|
|
|
|
logger.info("No currency pair in active pair whitelist, "
|
|
|
|
logger.info("No currency pair in active pair whitelist, "
|
|
|
|
"but checking to sell open trades.")
|
|
|
|
"but checking to exit open trades.")
|
|
|
|
return trades_created
|
|
|
|
return trades_created
|
|
|
|
if PairLocks.is_global_lock():
|
|
|
|
if PairLocks.is_global_lock():
|
|
|
|
lock = PairLocks.get_pair_longest_lock('*')
|
|
|
|
lock = PairLocks.get_pair_longest_lock('*')
|
|
|
@ -512,7 +514,9 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
order_type = self.strategy.order_types['buy']
|
|
|
|
order_type = self.strategy.order_types['buy']
|
|
|
|
if forcebuy:
|
|
|
|
if forcebuy:
|
|
|
|
# Forcebuy can define a different ordertype
|
|
|
|
# Forcebuy can define a different ordertype
|
|
|
|
|
|
|
|
# TODO-lev: get a forceshort? What is this
|
|
|
|
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
|
|
|
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
|
|
|
|
|
|
|
# TODO-lev: Will this work for shorting?
|
|
|
|
|
|
|
|
|
|
|
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
|
|
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
|
|
|
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
|
|
|
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
|
|
@ -596,7 +600,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
|
|
|
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Sends rpc notification when a buy occurred.
|
|
|
|
Sends rpc notification when a buy/short occurred.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
msg = {
|
|
|
|
msg = {
|
|
|
|
'trade_id': trade.id,
|
|
|
|
'trade_id': trade.id,
|
|
|
@ -619,7 +623,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
|
|
|
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Sends rpc notification when a buy cancel occurred.
|
|
|
|
Sends rpc notification when a buy/short cancel occurred.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy")
|
|
|
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy")
|
|
|
|
|
|
|
|
|
|
|
@ -665,7 +669,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def exit_positions(self, trades: List[Any]) -> int:
|
|
|
|
def exit_positions(self, trades: List[Any]) -> int:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Tries to execute sell orders for open trades (positions)
|
|
|
|
Tries to execute sell/exit_short orders for open trades (positions)
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
trades_closed = 0
|
|
|
|
trades_closed = 0
|
|
|
|
for trade in trades:
|
|
|
|
for trade in trades:
|
|
|
@ -691,8 +695,8 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
def handle_trade(self, trade: Trade) -> bool:
|
|
|
|
def handle_trade(self, trade: Trade) -> bool:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Sells the current pair if the threshold is reached and updates the trade record.
|
|
|
|
Sells/exits_short the current pair if the threshold is reached and updates the trade record.
|
|
|
|
:return: True if trade has been sold, False otherwise
|
|
|
|
:return: True if trade has been sold/exited_short, False otherwise
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if not trade.is_open:
|
|
|
|
if not trade.is_open:
|
|
|
|
raise DependencyException(f'Attempt to handle closed trade: {trade}')
|
|
|
|
raise DependencyException(f'Attempt to handle closed trade: {trade}')
|
|
|
@ -700,7 +704,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
logger.debug('Handling %s ...', trade)
|
|
|
|
logger.debug('Handling %s ...', trade)
|
|
|
|
|
|
|
|
|
|
|
|
(buy, sell) = (False, False)
|
|
|
|
(buy, sell) = (False, False)
|
|
|
|
|
|
|
|
# TODO-lev: change to use_exit_signal, ignore_roi_if_enter_signal
|
|
|
|
if (self.config.get('use_sell_signal', True) or
|
|
|
|
if (self.config.get('use_sell_signal', True) or
|
|
|
|
self.config.get('ignore_roi_if_buy_signal', False)):
|
|
|
|
self.config.get('ignore_roi_if_buy_signal', False)):
|
|
|
|
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
|
|
|
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
|
|
@ -744,7 +748,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
except InvalidOrderException as e:
|
|
|
|
except InvalidOrderException as e:
|
|
|
|
trade.stoploss_order_id = None
|
|
|
|
trade.stoploss_order_id = None
|
|
|
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
|
|
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
|
|
|
logger.warning('Selling the trade forcefully')
|
|
|
|
logger.warning('Exiting the trade forcefully')
|
|
|
|
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
|
|
|
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
|
|
|
sell_type=SellType.EMERGENCY_SELL))
|
|
|
|
sell_type=SellType.EMERGENCY_SELL))
|
|
|
|
|
|
|
|
|
|
|
@ -758,6 +762,8 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
Check if trade is fulfilled in which case the stoploss
|
|
|
|
Check if trade is fulfilled in which case the stoploss
|
|
|
|
on exchange should be added immediately if stoploss on exchange
|
|
|
|
on exchange should be added immediately if stoploss on exchange
|
|
|
|
is enabled.
|
|
|
|
is enabled.
|
|
|
|
|
|
|
|
# TODO-lev: liquidation price will always be on exchange, even though
|
|
|
|
|
|
|
|
# TODO-lev: stoploss_on_exchange might not be enabled
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug('Handling stoploss on exchange %s ...', trade)
|
|
|
|
logger.debug('Handling stoploss on exchange %s ...', trade)
|
|
|
@ -776,6 +782,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
|
|
|
|
|
|
|
|
# We check if stoploss order is fulfilled
|
|
|
|
# We check if stoploss order is fulfilled
|
|
|
|
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
|
|
|
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
|
|
|
|
|
|
|
# TODO-lev: Update to exit reason
|
|
|
|
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
|
|
|
|
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
|
|
|
|
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
|
|
|
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
|
|
|
stoploss_order=True)
|
|
|
|
stoploss_order=True)
|
|
|
@ -791,7 +798,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
# The trade can be closed already (sell-order fill confirmation came in this iteration)
|
|
|
|
# The trade can be closed already (sell-order fill confirmation came in this iteration)
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
|
|
|
# If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
|
|
|
|
if not stoploss_order:
|
|
|
|
if not stoploss_order:
|
|
|
|
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
|
|
|
|
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
|
|
|
|
stop_price = trade.open_rate * (1 + stoploss)
|
|
|
|
stop_price = trade.open_rate * (1 + stoploss)
|
|
|
@ -942,6 +949,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
Buy cancel - cancel order
|
|
|
|
Buy cancel - cancel order
|
|
|
|
:return: True if order was fully cancelled
|
|
|
|
:return: True if order was fully cancelled
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO-lev: Pay back borrowed/interest and transfer back on leveraged trades
|
|
|
|
was_trade_fully_canceled = False
|
|
|
|
was_trade_fully_canceled = False
|
|
|
|
|
|
|
|
|
|
|
|
# Cancelled orders may have the status of 'canceled' or 'closed'
|
|
|
|
# Cancelled orders may have the status of 'canceled' or 'closed'
|
|
|
@ -986,6 +994,8 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
# to the order dict acquired before cancelling.
|
|
|
|
# to the order dict acquired before cancelling.
|
|
|
|
# we need to fall back to the values from order if corder does not contain these keys.
|
|
|
|
# we need to fall back to the values from order if corder does not contain these keys.
|
|
|
|
trade.amount = filled_amount
|
|
|
|
trade.amount = filled_amount
|
|
|
|
|
|
|
|
# TODO-lev: Check edge cases, we don't want to make leverage > 1.0 if we don't have to
|
|
|
|
|
|
|
|
|
|
|
|
trade.stake_amount = trade.amount * trade.open_rate
|
|
|
|
trade.stake_amount = trade.amount * trade.open_rate
|
|
|
|
self.update_trade_state(trade, trade.open_order_id, corder)
|
|
|
|
self.update_trade_state(trade, trade.open_order_id, corder)
|
|
|
|
|
|
|
|
|
|
|
@ -994,13 +1004,15 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
|
|
|
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
|
|
|
|
|
|
|
|
|
|
|
self.wallets.update()
|
|
|
|
self.wallets.update()
|
|
|
|
|
|
|
|
# TODO-lev: Should short and exit_short be an order type?
|
|
|
|
|
|
|
|
|
|
|
|
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
|
|
|
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
|
|
|
reason=reason)
|
|
|
|
reason=reason)
|
|
|
|
return was_trade_fully_canceled
|
|
|
|
return was_trade_fully_canceled
|
|
|
|
|
|
|
|
|
|
|
|
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
|
|
|
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Sell cancel - cancel order and update trade
|
|
|
|
Sell/exit_short cancel - cancel order and update trade
|
|
|
|
:return: Reason for cancel
|
|
|
|
:return: Reason for cancel
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# if trade is not partially completed, just cancel the order
|
|
|
|
# if trade is not partially completed, just cancel the order
|
|
|
@ -1050,6 +1062,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
:return: amount to sell
|
|
|
|
:return: amount to sell
|
|
|
|
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
|
|
|
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO-lev Maybe update?
|
|
|
|
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
|
|
|
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
|
|
|
self.wallets.update()
|
|
|
|
self.wallets.update()
|
|
|
|
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
|
|
|
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
|
|
@ -1072,7 +1085,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
:param sell_reason: Reason the sell was triggered
|
|
|
|
:param sell_reason: Reason the sell was triggered
|
|
|
|
:return: True if it succeeds (supported) False (not supported)
|
|
|
|
:return: True if it succeeds (supported) False (not supported)
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
sell_type = 'sell'
|
|
|
|
sell_type = 'sell' # TODO-lev: Update to exit
|
|
|
|
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
|
|
|
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
|
|
|
sell_type = 'stoploss'
|
|
|
|
sell_type = 'stoploss'
|
|
|
|
|
|
|
|
|
|
|
@ -1112,22 +1125,25 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
order_type = self.strategy.order_types.get("forcesell", order_type)
|
|
|
|
order_type = self.strategy.order_types.get("forcesell", order_type)
|
|
|
|
|
|
|
|
|
|
|
|
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
|
|
|
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
|
|
|
time_in_force = self.strategy.order_time_in_force['sell']
|
|
|
|
time_in_force = self.strategy.order_time_in_force['sell'] # TODO-lev update to exit
|
|
|
|
|
|
|
|
|
|
|
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
|
|
|
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
|
|
|
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
|
|
|
|
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
|
|
|
|
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason,
|
|
|
|
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason,
|
|
|
|
current_time=datetime.now(timezone.utc)):
|
|
|
|
current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit
|
|
|
|
logger.info(f"User requested abortion of selling {trade.pair}")
|
|
|
|
logger.info(f"User requested abortion of exiting {trade.pair}")
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# Execute sell and update trade record
|
|
|
|
# Execute sell and update trade record
|
|
|
|
order = self.exchange.create_order(pair=trade.pair,
|
|
|
|
order = self.exchange.create_order(
|
|
|
|
ordertype=order_type, side="sell",
|
|
|
|
pair=trade.pair,
|
|
|
|
amount=amount, rate=limit,
|
|
|
|
ordertype=order_type,
|
|
|
|
time_in_force=time_in_force
|
|
|
|
side="sell",
|
|
|
|
)
|
|
|
|
amount=amount,
|
|
|
|
|
|
|
|
rate=limit,
|
|
|
|
|
|
|
|
time_in_force=time_in_force
|
|
|
|
|
|
|
|
)
|
|
|
|
except InsufficientFundsError as e:
|
|
|
|
except InsufficientFundsError as e:
|
|
|
|
logger.warning(f"Unable to place order {e}.")
|
|
|
|
logger.warning(f"Unable to place order {e}.")
|
|
|
|
# Try to figure out what went wrong
|
|
|
|
# Try to figure out what went wrong
|
|
|
@ -1138,15 +1154,15 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
trade.orders.append(order_obj)
|
|
|
|
trade.orders.append(order_obj)
|
|
|
|
|
|
|
|
|
|
|
|
trade.open_order_id = order['id']
|
|
|
|
trade.open_order_id = order['id']
|
|
|
|
trade.sell_order_status = ''
|
|
|
|
trade.sell_order_status = '' # TODO-lev: Update to exit_order_status
|
|
|
|
trade.close_rate_requested = limit
|
|
|
|
trade.close_rate_requested = limit
|
|
|
|
trade.sell_reason = sell_reason.sell_reason
|
|
|
|
trade.sell_reason = sell_reason.sell_reason # TODO-lev: Update to exit_reason
|
|
|
|
# In case of market sell orders the order can be closed immediately
|
|
|
|
# In case of market sell orders the order can be closed immediately
|
|
|
|
if order.get('status', 'unknown') in ('closed', 'expired'):
|
|
|
|
if order.get('status', 'unknown') in ('closed', 'expired'):
|
|
|
|
self.update_trade_state(trade, trade.open_order_id, order)
|
|
|
|
self.update_trade_state(trade, trade.open_order_id, order)
|
|
|
|
Trade.commit()
|
|
|
|
Trade.commit()
|
|
|
|
|
|
|
|
|
|
|
|
# Lock pair for one candle to prevent immediate re-buys
|
|
|
|
# Lock pair for one candle to prevent immediate re-trading
|
|
|
|
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
|
|
|
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
|
|
|
reason='Auto lock')
|
|
|
|
reason='Auto lock')
|
|
|
|
|
|
|
|
|
|
|
@ -1181,7 +1197,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
'current_rate': current_rate,
|
|
|
|
'current_rate': current_rate,
|
|
|
|
'profit_amount': profit_trade,
|
|
|
|
'profit_amount': profit_trade,
|
|
|
|
'profit_ratio': profit_ratio,
|
|
|
|
'profit_ratio': profit_ratio,
|
|
|
|
'sell_reason': trade.sell_reason,
|
|
|
|
'sell_reason': trade.sell_reason, # TODO-lev: change to exit_reason
|
|
|
|
'open_date': trade.open_date,
|
|
|
|
'open_date': trade.open_date,
|
|
|
|
'close_date': trade.close_date or datetime.utcnow(),
|
|
|
|
'close_date': trade.close_date or datetime.utcnow(),
|
|
|
|
'stake_currency': self.config['stake_currency'],
|
|
|
|
'stake_currency': self.config['stake_currency'],
|
|
|
@ -1200,10 +1216,10 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Sends rpc notification when a sell cancel occurred.
|
|
|
|
Sends rpc notification when a sell cancel occurred.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
if trade.sell_order_status == reason:
|
|
|
|
if trade.sell_order_status == reason: # TODO-lev: Update to exit_order_status
|
|
|
|
return
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
trade.sell_order_status = reason
|
|
|
|
trade.sell_order_status = reason # TODO-lev: Update to exit_order_status
|
|
|
|
|
|
|
|
|
|
|
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
|
|
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
|
|
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
|
|
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
|
|
@ -1224,7 +1240,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
'current_rate': current_rate,
|
|
|
|
'current_rate': current_rate,
|
|
|
|
'profit_amount': profit_trade,
|
|
|
|
'profit_amount': profit_trade,
|
|
|
|
'profit_ratio': profit_ratio,
|
|
|
|
'profit_ratio': profit_ratio,
|
|
|
|
'sell_reason': trade.sell_reason,
|
|
|
|
'sell_reason': trade.sell_reason, # TODO-lev: trade to exit_reason
|
|
|
|
'open_date': trade.open_date,
|
|
|
|
'open_date': trade.open_date,
|
|
|
|
'close_date': trade.close_date,
|
|
|
|
'close_date': trade.close_date,
|
|
|
|
'stake_currency': self.config['stake_currency'],
|
|
|
|
'stake_currency': self.config['stake_currency'],
|
|
|
@ -1310,6 +1326,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
self.wallets.update()
|
|
|
|
self.wallets.update()
|
|
|
|
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
|
|
|
|
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
|
|
|
|
# Eat into dust if we own more than base currency
|
|
|
|
# Eat into dust if we own more than base currency
|
|
|
|
|
|
|
|
# TODO-lev: won't be in "base"(quote) currency for shorts
|
|
|
|
logger.info(f"Fee amount for {trade} was in base currency - "
|
|
|
|
logger.info(f"Fee amount for {trade} was in base currency - "
|
|
|
|
f"Eating Fee {fee_abs} into dust.")
|
|
|
|
f"Eating Fee {fee_abs} into dust.")
|
|
|
|
elif fee_abs != 0:
|
|
|
|
elif fee_abs != 0:
|
|
|
@ -1386,6 +1403,7 @@ class FreqtradeBot(LoggingMixin):
|
|
|
|
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
|
|
|
|
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
|
|
|
|
|
|
|
|
|
|
|
|
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
|
|
|
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
|
|
|
|
|
|
|
# TODO-lev: leverage?
|
|
|
|
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
|
|
|
|
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
|
|
|
|
raise DependencyException("Half bought? Amounts don't match")
|
|
|
|
raise DependencyException("Half bought? Amounts don't match")
|
|
|
|
|
|
|
|
|
|
|
|