• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

freqtrade / freqtrade / 3645476382

pending completion
3645476382

push

github-actions

GitHub
Merge pull request #7665 from freqtrade/update_ci

15895 of 16625 relevant lines covered (95.61%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.74
/freqtrade/freqtradebot.py
1
"""
2
Freqtrade is the main module of this bot. It contains the class Freqtrade()
3
"""
4
import copy
1✔
5
import logging
1✔
6
import traceback
1✔
7
from datetime import datetime, time, timedelta, timezone
1✔
8
from math import isclose
1✔
9
from threading import Lock
1✔
10
from typing import Any, Dict, List, Optional, Tuple
1✔
11

12
from schedule import Scheduler
1✔
13

14
from freqtrade import constants
1✔
15
from freqtrade.configuration import validate_config_consistency
1✔
16
from freqtrade.constants import BuySell, Config, LongShort
1✔
17
from freqtrade.data.converter import order_book_to_dataframe
1✔
18
from freqtrade.data.dataprovider import DataProvider
1✔
19
from freqtrade.edge import Edge
1✔
20
from freqtrade.enums import (ExitCheckTuple, ExitType, RPCMessageType, RunMode, SignalDirection,
1✔
21
                             State, TradingMode)
22
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
1✔
23
                                  InvalidOrderException, PricingError)
24
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
1✔
25
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
1✔
26
from freqtrade.mixins import LoggingMixin
1✔
27
from freqtrade.persistence import Order, PairLocks, Trade, init_db
1✔
28
from freqtrade.plugins.pairlistmanager import PairListManager
1✔
29
from freqtrade.plugins.protectionmanager import ProtectionManager
1✔
30
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
1✔
31
from freqtrade.rpc import RPCManager
1✔
32
from freqtrade.rpc.external_message_consumer import ExternalMessageConsumer
1✔
33
from freqtrade.strategy.interface import IStrategy
1✔
34
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
1✔
35
from freqtrade.util import FtPrecise
1✔
36
from freqtrade.wallets import Wallets
1✔
37

38

39
logger = logging.getLogger(__name__)
1✔
40

41

42
class FreqtradeBot(LoggingMixin):
1✔
43
    """
44
    Freqtrade is the main class of the bot.
45
    This is from here the bot start its logic.
46
    """
47

48
    def __init__(self, config: Config) -> None:
1✔
49
        """
50
        Init all variables and objects the bot needs to work
51
        :param config: configuration dict, you can use Configuration.get_config()
52
        to get the config dict.
53
        """
54
        self.active_pair_whitelist: List[str] = []
1✔
55

56
        # Init bot state
57
        self.state = State.STOPPED
1✔
58

59
        # Init objects
60
        self.config = config
1✔
61

62
        self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
1✔
63

64
        # Check config consistency here since strategies can set certain options
65
        validate_config_consistency(config)
1✔
66

67
        self.exchange = ExchangeResolver.load_exchange(
1✔
68
            self.config['exchange']['name'], self.config, load_leverage_tiers=True)
69

70
        init_db(self.config['db_url'])
1✔
71

72
        self.wallets = Wallets(self.config, self.exchange)
1✔
73

74
        PairLocks.timeframe = self.config['timeframe']
1✔
75

76
        self.pairlists = PairListManager(self.exchange, self.config)
1✔
77

78
        # RPC runs in separate threads, can start handling external commands just after
79
        # initialization, even before Freqtradebot has a chance to start its throttling,
80
        # so anything in the Freqtradebot instance should be ready (initialized), including
81
        # the initial state of the bot.
82
        # Keep this at the end of this initialization method.
83
        self.rpc: RPCManager = RPCManager(self)
1✔
84

85
        self.dataprovider = DataProvider(self.config, self.exchange, rpc=self.rpc)
1✔
86
        self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider)
1✔
87

88
        self.dataprovider.add_pairlisthandler(self.pairlists)
1✔
89

90
        # Attach Dataprovider to strategy instance
91
        self.strategy.dp = self.dataprovider
1✔
92
        # Attach Wallets to strategy instance
93
        self.strategy.wallets = self.wallets
1✔
94

95
        # Initializing Edge only if enabled
96
        self.edge = Edge(self.config, self.exchange, self.strategy) if \
1✔
97
            self.config.get('edge', {}).get('enabled', False) else None
98

99
        # Init ExternalMessageConsumer if enabled
100
        self.emc = ExternalMessageConsumer(self.config, self.dataprovider) if \
1✔
101
            self.config.get('external_message_consumer', {}).get('enabled', False) else None
102

103
        self.active_pair_whitelist = self._refresh_active_whitelist()
1✔
104

105
        # Set initial bot state from config
106
        initial_state = self.config.get('initial_state')
1✔
107
        self.state = State[initial_state.upper()] if initial_state else State.STOPPED
1✔
108

109
        # Protect exit-logic from forcesell and vice versa
110
        self._exit_lock = Lock()
1✔
111
        LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
1✔
112

113
        self.trading_mode: TradingMode = self.config.get('trading_mode', TradingMode.SPOT)
1✔
114

115
        self._schedule = Scheduler()
1✔
116

117
        if self.trading_mode == TradingMode.FUTURES:
1✔
118

119
            def update():
1✔
120
                self.update_funding_fees()
1✔
121
                self.wallets.update()
1✔
122

123
            # TODO: This would be more efficient if scheduled in utc time, and performed at each
124
            # TODO: funding interval, specified by funding_fee_times on the exchange classes
125
            for time_slot in range(0, 24):
1✔
126
                for minutes in [0, 15, 30, 45]:
1✔
127
                    t = str(time(time_slot, minutes, 2))
1✔
128
                    self._schedule.every().day.at(t).do(update)
1✔
129
        self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc)
1✔
130

131
        self.strategy.ft_bot_start()
1✔
132
        # Initialize protections AFTER bot start - otherwise parameters are not loaded.
133
        self.protections = ProtectionManager(self.config, self.strategy.protections)
1✔
134

135
    def notify_status(self, msg: str) -> None:
1✔
136
        """
137
        Public method for users of this class (worker, etc.) to send notifications
138
        via RPC about changes in the bot status.
139
        """
140
        self.rpc.send_msg({
1✔
141
            'type': RPCMessageType.STATUS,
142
            'status': msg
143
        })
144

145
    def cleanup(self) -> None:
1✔
146
        """
147
        Cleanup pending resources on an already stopped bot
148
        :return: None
149
        """
150
        logger.info('Cleaning up modules ...')
1✔
151
        try:
1✔
152
            # Wrap db activities in shutdown to avoid problems if database is gone,
153
            # and raises further exceptions.
154
            if self.config['cancel_open_orders_on_exit']:
1✔
155
                self.cancel_all_open_orders()
1✔
156

157
            self.check_for_open_trades()
1✔
158

159
        finally:
160
            self.strategy.ft_bot_cleanup()
1✔
161

162
        self.rpc.cleanup()
1✔
163
        if self.emc:
1✔
164
            self.emc.shutdown()
×
165
        Trade.commit()
1✔
166
        self.exchange.close()
1✔
167

168
    def startup(self) -> None:
1✔
169
        """
170
        Called on startup and after reloading the bot - triggers notifications and
171
        performs startup tasks
172
        """
173
        self.rpc.startup_messages(self.config, self.pairlists, self.protections)
1✔
174
        # Update older trades with precision and precision mode
175
        self.startup_backpopulate_precision()
1✔
176
        if not self.edge:
1✔
177
            # Adjust stoploss if it was changed
178
            Trade.stoploss_reinitialization(self.strategy.stoploss)
1✔
179

180
        # Only update open orders on startup
181
        # This will update the database after the initial migration
182
        self.startup_update_open_orders()
1✔
183

184
    def process(self) -> None:
1✔
185
        """
186
        Queries the persistence layer for open trades and handles them,
187
        otherwise a new trade is created.
188
        :return: True if one or more trades has been created or closed, False otherwise
189
        """
190

191
        # Check whether markets have to be reloaded and reload them when it's needed
192
        self.exchange.reload_markets()
1✔
193

194
        self.update_closed_trades_without_assigned_fees()
1✔
195

196
        # Query trades from persistence layer
197
        trades = Trade.get_open_trades()
1✔
198

199
        self.active_pair_whitelist = self._refresh_active_whitelist(trades)
1✔
200

201
        # Refreshing candles
202
        self.dataprovider.refresh(self.pairlists.create_pair_list(self.active_pair_whitelist),
1✔
203
                                  self.strategy.gather_informative_pairs())
204

205
        strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
1✔
206

207
        self.strategy.analyze(self.active_pair_whitelist)
1✔
208

209
        with self._exit_lock:
1✔
210
            # Check for exchange cancelations, timeouts and user requested replace
211
            self.manage_open_orders()
1✔
212

213
        # Protect from collisions with force_exit.
214
        # Without this, freqtrade my try to recreate stoploss_on_exchange orders
215
        # while exiting is in process, since telegram messages arrive in an different thread.
216
        with self._exit_lock:
1✔
217
            trades = Trade.get_open_trades()
1✔
218
            # First process current opened trades (positions)
219
            self.exit_positions(trades)
1✔
220

221
        # Check if we need to adjust our current positions before attempting to buy new trades.
222
        if self.strategy.position_adjustment_enable:
1✔
223
            with self._exit_lock:
1✔
224
                self.process_open_trade_positions()
1✔
225

226
        # Then looking for buy opportunities
227
        if self.get_free_open_trades():
1✔
228
            self.enter_positions()
1✔
229
        if self.trading_mode == TradingMode.FUTURES:
1✔
230
            self._schedule.run_pending()
1✔
231
        Trade.commit()
1✔
232
        self.rpc.process_msg_queue(self.dataprovider._msg_queue)
1✔
233
        self.last_process = datetime.now(timezone.utc)
1✔
234

235
    def process_stopped(self) -> None:
1✔
236
        """
237
        Close all orders that were left open
238
        """
239
        if self.config['cancel_open_orders_on_exit']:
1✔
240
            self.cancel_all_open_orders()
1✔
241

242
    def check_for_open_trades(self):
1✔
243
        """
244
        Notify the user when the bot is stopped (not reloaded)
245
        and there are still open trades active.
246
        """
247
        open_trades = Trade.get_open_trades()
1✔
248

249
        if len(open_trades) != 0 and self.state != State.RELOAD_CONFIG:
1✔
250
            msg = {
1✔
251
                'type': RPCMessageType.WARNING,
252
                'status':
253
                    f"{len(open_trades)} open trades active.\n\n"
254
                    f"Handle these trades manually on {self.exchange.name}, "
255
                    f"or '/start' the bot again and use '/stopentry' "
256
                    f"to handle open trades gracefully. \n"
257
                    f"{'Note: Trades are simulated (dry run).' if self.config['dry_run'] else ''}",
258
            }
259
            self.rpc.send_msg(msg)
1✔
260

261
    def _refresh_active_whitelist(self, trades: List[Trade] = []) -> List[str]:
1✔
262
        """
263
        Refresh active whitelist from pairlist or edge and extend it with
264
        pairs that have open trades.
265
        """
266
        # Refresh whitelist
267
        _prev_whitelist = self.pairlists.whitelist
1✔
268
        self.pairlists.refresh_pairlist()
1✔
269
        _whitelist = self.pairlists.whitelist
1✔
270

271
        # Calculating Edge positioning
272
        if self.edge:
1✔
273
            self.edge.calculate(_whitelist)
1✔
274
            _whitelist = self.edge.adjust(_whitelist)
1✔
275

276
        if trades:
1✔
277
            # Extend active-pair whitelist with pairs of open trades
278
            # It ensures that candle (OHLCV) data are downloaded for open trades as well
279
            _whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist])
1✔
280

281
        # Called last to include the included pairs
282
        if _prev_whitelist != _whitelist:
1✔
283
            self.rpc.send_msg({'type': RPCMessageType.WHITELIST, 'data': _whitelist})
1✔
284

285
        return _whitelist
1✔
286

287
    def get_free_open_trades(self) -> int:
1✔
288
        """
289
        Return the number of free open trades slots or 0 if
290
        max number of open trades reached
291
        """
292
        open_trades = Trade.get_open_trade_count()
1✔
293
        return max(0, self.config['max_open_trades'] - open_trades)
1✔
294

295
    def update_funding_fees(self):
1✔
296
        if self.trading_mode == TradingMode.FUTURES:
1✔
297
            trades = Trade.get_open_trades()
1✔
298
            try:
1✔
299
                for trade in trades:
1✔
300
                    funding_fees = self.exchange.get_funding_fees(
1✔
301
                        pair=trade.pair,
302
                        amount=trade.amount,
303
                        is_short=trade.is_short,
304
                        open_date=trade.date_last_filled_utc
305
                    )
306
                    trade.funding_fees = funding_fees
1✔
307
            except ExchangeError:
×
308
                logger.warning("Could not update funding fees for open trades.")
×
309

310
    def startup_backpopulate_precision(self):
1✔
311

312
        trades = Trade.get_trades([Trade.contract_size.is_(None)])
1✔
313
        for trade in trades:
1✔
314
            if trade.exchange != self.exchange.id:
1✔
315
                continue
1✔
316
            trade.precision_mode = self.exchange.precisionMode
1✔
317
            trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
1✔
318
            trade.price_precision = self.exchange.get_precision_price(trade.pair)
1✔
319
            trade.contract_size = self.exchange.get_contract_size(trade.pair)
1✔
320
        Trade.commit()
1✔
321

322
    def startup_update_open_orders(self):
1✔
323
        """
324
        Updates open orders based on order list kept in the database.
325
        Mainly updates the state of orders - but may also close trades
326
        """
327
        if self.config['dry_run'] or self.config['exchange'].get('skip_open_order_update', False):
1✔
328
            # Updating open orders in dry-run does not make sense and will fail.
329
            return
1✔
330

331
        orders = Order.get_open_orders()
1✔
332
        logger.info(f"Updating {len(orders)} open orders.")
1✔
333
        for order in orders:
1✔
334
            try:
1✔
335
                fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
1✔
336
                                                                 order.ft_order_side == 'stoploss')
337

338
                self.update_trade_state(order.trade, order.order_id, fo,
1✔
339
                                        stoploss_order=(order.ft_order_side == 'stoploss'))
340

341
            except InvalidOrderException as e:
1✔
342
                logger.warning(f"Error updating Order {order.order_id} due to {e}.")
1✔
343
                if order.order_date_utc - timedelta(days=5) < datetime.now(timezone.utc):
1✔
344
                    logger.warning(
1✔
345
                        "Order is older than 5 days. Assuming order was fully cancelled.")
346
                    fo = order.to_ccxt_object()
1✔
347
                    fo['status'] = 'canceled'
1✔
348
                    self.handle_timedout_order(fo, order.trade)
1✔
349

350
            except ExchangeError as e:
1✔
351

352
                logger.warning(f"Error updating Order {order.order_id} due to {e}")
1✔
353

354
        if self.trading_mode == TradingMode.FUTURES:
1✔
355
            self._schedule.run_pending()
×
356

357
    def update_closed_trades_without_assigned_fees(self):
1✔
358
        """
359
        Update closed trades without close fees assigned.
360
        Only acts when Orders are in the database, otherwise the last order-id is unknown.
361
        """
362
        if self.config['dry_run']:
1✔
363
            # Updating open orders in dry-run does not make sense and will fail.
364
            return
1✔
365

366
        trades: List[Trade] = Trade.get_closed_trades_without_assigned_fees()
1✔
367
        for trade in trades:
1✔
368
            if not trade.is_open and not trade.fee_updated(trade.exit_side):
1✔
369
                # Get sell fee
370
                order = trade.select_order(trade.exit_side, False)
1✔
371
                if not order:
1✔
372
                    order = trade.select_order('stoploss', False)
×
373
                if order:
1✔
374
                    logger.info(
1✔
375
                        f"Updating {trade.exit_side}-fee on trade {trade}"
376
                        f"for order {order.order_id}."
377
                    )
378
                    self.update_trade_state(trade, order.order_id,
1✔
379
                                            stoploss_order=order.ft_order_side == 'stoploss',
380
                                            send_msg=False)
381

382
        trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
1✔
383
        for trade in trades:
1✔
384
            if trade.is_open and not trade.fee_updated(trade.entry_side):
1✔
385
                order = trade.select_order(trade.entry_side, False)
1✔
386
                open_order = trade.select_order(trade.entry_side, True)
1✔
387
                if order and open_order is None:
1✔
388
                    logger.info(
1✔
389
                        f"Updating {trade.entry_side}-fee on trade {trade}"
390
                        f"for order {order.order_id}."
391
                    )
392
                    self.update_trade_state(trade, order.order_id, send_msg=False)
1✔
393

394
    def handle_insufficient_funds(self, trade: Trade):
1✔
395
        """
396
        Try refinding a lost trade.
397
        Only used when InsufficientFunds appears on exit orders (stoploss or long sell/short buy).
398
        Tries to walk the stored orders and sell them off eventually.
399
        """
400
        logger.info(f"Trying to refind lost order for {trade}")
1✔
401
        for order in trade.orders:
1✔
402
            logger.info(f"Trying to refind {order}")
1✔
403
            fo = None
1✔
404
            if not order.ft_is_open:
1✔
405
                logger.debug(f"Order {order} is no longer open.")
1✔
406
                continue
1✔
407
            try:
1✔
408
                fo = self.exchange.fetch_order_or_stoploss_order(order.order_id, order.ft_pair,
1✔
409
                                                                 order.ft_order_side == 'stoploss')
410
                if order.ft_order_side == 'stoploss':
1✔
411
                    if fo and fo['status'] == 'open':
1✔
412
                        # Assume this as the open stoploss order
413
                        trade.stoploss_order_id = order.order_id
1✔
414
                elif order.ft_order_side == trade.exit_side:
1✔
415
                    if fo and fo['status'] == 'open':
1✔
416
                        # Assume this as the open order
417
                        trade.open_order_id = order.order_id
1✔
418
                elif order.ft_order_side == trade.entry_side:
1✔
419
                    if fo and fo['status'] == 'open':
1✔
420
                        trade.open_order_id = order.order_id
1✔
421
                if fo:
1✔
422
                    logger.info(f"Found {order} for trade {trade}.")
1✔
423
                    self.update_trade_state(trade, order.order_id, fo,
1✔
424
                                            stoploss_order=order.ft_order_side == 'stoploss')
425

426
            except ExchangeError:
1✔
427
                logger.warning(f"Error updating {order.order_id}.")
1✔
428

429
#
430
# BUY / enter positions / open trades logic and methods
431
#
432

433
    def enter_positions(self) -> int:
1✔
434
        """
435
        Tries to execute entry orders for new trades (positions)
436
        """
437
        trades_created = 0
1✔
438

439
        whitelist = copy.deepcopy(self.active_pair_whitelist)
1✔
440
        if not whitelist:
1✔
441
            self.log_once("Active pair whitelist is empty.", logger.info)
1✔
442
            return trades_created
1✔
443
        # Remove pairs for currently opened trades from the whitelist
444
        for trade in Trade.get_open_trades():
1✔
445
            if trade.pair in whitelist:
1✔
446
                whitelist.remove(trade.pair)
1✔
447
                logger.debug('Ignoring %s in pair whitelist', trade.pair)
1✔
448

449
        if not whitelist:
1✔
450
            self.log_once("No currency pair in active pair whitelist, "
1✔
451
                          "but checking to exit open trades.", logger.info)
452
            return trades_created
1✔
453
        if PairLocks.is_global_lock(side='*'):
1✔
454
            # This only checks for total locks (both sides).
455
            # per-side locks will be evaluated by `is_pair_locked` within create_trade,
456
            # once the direction for the trade is clear.
457
            lock = PairLocks.get_pair_longest_lock('*')
1✔
458
            if lock:
1✔
459
                self.log_once(f"Global pairlock active until "
1✔
460
                              f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)}. "
461
                              f"Not creating new trades, reason: {lock.reason}.", logger.info)
462
            else:
463
                self.log_once("Global pairlock active. Not creating new trades.", logger.info)
×
464
            return trades_created
1✔
465
        # Create entity and execute trade for each pair from whitelist
466
        for pair in whitelist:
1✔
467
            try:
1✔
468
                trades_created += self.create_trade(pair)
1✔
469
            except DependencyException as exception:
1✔
470
                logger.warning('Unable to create trade for %s: %s', pair, exception)
1✔
471

472
        if not trades_created:
1✔
473
            logger.debug("Found no enter signals for whitelisted currencies. Trying again...")
1✔
474

475
        return trades_created
1✔
476

477
    def create_trade(self, pair: str) -> bool:
1✔
478
        """
479
        Check the implemented trading strategy for buy signals.
480

481
        If the pair triggers the buy signal a new trade record gets created
482
        and the buy-order opening the trade gets issued towards the exchange.
483

484
        :return: True if a trade has been created.
485
        """
486
        logger.debug(f"create_trade for pair {pair}")
1✔
487

488
        analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
1✔
489
        nowtime = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None
1✔
490

491
        # get_free_open_trades is checked before create_trade is called
492
        # but it is still used here to prevent opening too many trades within one iteration
493
        if not self.get_free_open_trades():
1✔
494
            logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
1✔
495
            return False
1✔
496

497
        # running get_signal on historical data fetched
498
        (signal, enter_tag) = self.strategy.get_entry_signal(
1✔
499
            pair,
500
            self.strategy.timeframe,
501
            analyzed_df
502
        )
503

504
        if signal:
1✔
505
            if self.strategy.is_pair_locked(pair, candle_date=nowtime, side=signal):
1✔
506
                lock = PairLocks.get_pair_longest_lock(pair, nowtime, signal)
1✔
507
                if lock:
1✔
508
                    self.log_once(f"Pair {pair} {lock.side} is locked until "
1✔
509
                                  f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)} "
510
                                  f"due to {lock.reason}.",
511
                                  logger.info)
512
                else:
513
                    self.log_once(f"Pair {pair} is currently locked.", logger.info)
×
514
                return False
1✔
515
            stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
1✔
516

517
            bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {})
1✔
518
            if ((bid_check_dom.get('enabled', False)) and
1✔
519
                    (bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
520
                if self._check_depth_of_market(pair, bid_check_dom, side=signal):
1✔
521
                    return self.execute_entry(
1✔
522
                        pair,
523
                        stake_amount,
524
                        enter_tag=enter_tag,
525
                        is_short=(signal == SignalDirection.SHORT)
526
                    )
527
                else:
528
                    return False
1✔
529

530
            return self.execute_entry(
1✔
531
                pair,
532
                stake_amount,
533
                enter_tag=enter_tag,
534
                is_short=(signal == SignalDirection.SHORT)
535
            )
536
        else:
537
            return False
1✔
538

539
#
540
# BUY / increase positions / DCA logic and methods
541
#
542
    def process_open_trade_positions(self):
1✔
543
        """
544
        Tries to execute additional buy or sell orders for open trades (positions)
545
        """
546
        # Walk through each pair and check if it needs changes
547
        for trade in Trade.get_open_trades():
1✔
548
            # If there is any open orders, wait for them to finish.
549
            if trade.open_order_id is None:
1✔
550
                try:
1✔
551
                    self.check_and_call_adjust_trade_position(trade)
1✔
552
                except DependencyException as exception:
1✔
553
                    logger.warning(
1✔
554
                        f"Unable to adjust position of trade for {trade.pair}: {exception}")
555

556
    def check_and_call_adjust_trade_position(self, trade: Trade):
1✔
557
        """
558
        Check the implemented trading strategy for adjustment command.
559
        If the strategy triggers the adjustment, a new order gets issued.
560
        Once that completes, the existing trade is modified to match new data.
561
        """
562
        current_entry_rate, current_exit_rate = self.exchange.get_rates(
1✔
563
            trade.pair, True, trade.is_short)
564

565
        current_entry_profit = trade.calc_profit_ratio(current_entry_rate)
1✔
566
        current_exit_profit = trade.calc_profit_ratio(current_exit_rate)
1✔
567

568
        min_entry_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
1✔
569
                                                                  current_entry_rate,
570
                                                                  self.strategy.stoploss)
571
        min_exit_stake = self.exchange.get_min_pair_stake_amount(trade.pair,
1✔
572
                                                                 current_exit_rate,
573
                                                                 self.strategy.stoploss)
574
        max_entry_stake = self.exchange.get_max_pair_stake_amount(trade.pair, current_entry_rate)
1✔
575
        stake_available = self.wallets.get_available_stake_amount()
1✔
576
        logger.debug(f"Calling adjust_trade_position for pair {trade.pair}")
1✔
577
        stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position,
1✔
578
                                             default_retval=None)(
579
            trade=trade,
580
            current_time=datetime.now(timezone.utc), current_rate=current_entry_rate,
581
            current_profit=current_entry_profit, min_stake=min_entry_stake,
582
            max_stake=min(max_entry_stake, stake_available),
583
            current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate,
584
            current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit
585
        )
586

587
        if stake_amount is not None and stake_amount > 0.0:
1✔
588
            # We should increase our position
589
            if self.strategy.max_entry_position_adjustment > -1:
1✔
590
                count_of_entries = trade.nr_of_successful_entries
1✔
591
                if count_of_entries > self.strategy.max_entry_position_adjustment:
1✔
592
                    logger.debug(f"Max adjustment entries for {trade.pair} has been reached.")
1✔
593
                    return
1✔
594
                else:
595
                    logger.debug("Max adjustment entries is set to unlimited.")
×
596
            self.execute_entry(trade.pair, stake_amount, price=current_entry_rate,
1✔
597
                               trade=trade, is_short=trade.is_short)
598

599
        if stake_amount is not None and stake_amount < 0.0:
1✔
600
            # We should decrease our position
601
            amount = self.exchange.amount_to_contract_precision(
1✔
602
                trade.pair,
603
                abs(float(FtPrecise(stake_amount * trade.leverage) / FtPrecise(current_exit_rate))))
604
            if amount > trade.amount:
1✔
605
                # This is currently ineffective as remaining would become < min tradable
606
                # Fixing this would require checking for 0.0 there -
607
                # if we decide that this callback is allowed to "fully exit"
608
                logger.info(
1✔
609
                    f"Adjusting amount to trade.amount as it is higher. {amount} > {trade.amount}")
610
                amount = trade.amount
1✔
611

612
            if amount == 0.0:
1✔
613
                logger.info("Amount to exit is 0.0 due to exchange limits - not exiting.")
1✔
614
                return
1✔
615

616
            remaining = (trade.amount - amount) * current_exit_rate
1✔
617
            if remaining < min_exit_stake:
1✔
618
                logger.info(f"Remaining amount of {remaining} would be smaller "
1✔
619
                            f"than the minimum of {min_exit_stake}.")
620
                return
1✔
621

622
            self.execute_trade_exit(trade, current_exit_rate, exit_check=ExitCheckTuple(
1✔
623
                exit_type=ExitType.PARTIAL_EXIT), sub_trade_amt=amount)
624

625
    def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool:
1✔
626
        """
627
        Checks depth of market before executing a buy
628
        """
629
        conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0)
1✔
630
        logger.info(f"Checking depth of market for {pair} ...")
1✔
631
        order_book = self.exchange.fetch_l2_order_book(pair, 1000)
1✔
632
        order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks'])
1✔
633
        order_book_bids = order_book_data_frame['b_size'].sum()
1✔
634
        order_book_asks = order_book_data_frame['a_size'].sum()
1✔
635

636
        entry_side = order_book_bids if side == SignalDirection.LONG else order_book_asks
1✔
637
        exit_side = order_book_asks if side == SignalDirection.LONG else order_book_bids
1✔
638
        bids_ask_delta = entry_side / exit_side
1✔
639

640
        bids = f"Bids: {order_book_bids}"
1✔
641
        asks = f"Asks: {order_book_asks}"
1✔
642
        delta = f"Delta: {bids_ask_delta}"
1✔
643

644
        logger.info(
1✔
645
            f"{bids}, {asks}, {delta}, Direction: {side.value}"
646
            f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, "
647
            f"Immediate Bid Quantity: {order_book['bids'][0][1]}, "
648
            f"Immediate Ask Quantity: {order_book['asks'][0][1]}."
649
        )
650
        if bids_ask_delta >= conf_bids_to_ask_delta:
1✔
651
            logger.info(f"Bids to asks delta for {pair} DOES satisfy condition.")
1✔
652
            return True
1✔
653
        else:
654
            logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
1✔
655
            return False
1✔
656

657
    def execute_entry(
1✔
658
        self,
659
        pair: str,
660
        stake_amount: float,
661
        price: Optional[float] = None,
662
        *,
663
        is_short: bool = False,
664
        ordertype: Optional[str] = None,
665
        enter_tag: Optional[str] = None,
666
        trade: Optional[Trade] = None,
667
        order_adjust: bool = False,
668
        leverage_: Optional[float] = None,
669
    ) -> bool:
670
        """
671
        Executes a limit buy for the given pair
672
        :param pair: pair for which we want to create a LIMIT_BUY
673
        :param stake_amount: amount of stake-currency for the pair
674
        :return: True if a buy order is created, false if it fails.
675
        """
676
        time_in_force = self.strategy.order_time_in_force['entry']
1✔
677

678
        side: BuySell = 'sell' if is_short else 'buy'
1✔
679
        name = 'Short' if is_short else 'Long'
1✔
680
        trade_side: LongShort = 'short' if is_short else 'long'
1✔
681
        pos_adjust = trade is not None
1✔
682

683
        enter_limit_requested, stake_amount, leverage = self.get_valid_enter_price_and_stake(
1✔
684
            pair, price, stake_amount, trade_side, enter_tag, trade, order_adjust, leverage_)
685

686
        if not stake_amount:
1✔
687
            return False
1✔
688

689
        msg = (f"Position adjust: about to create a new order for {pair} with stake: "
1✔
690
               f"{stake_amount} for {trade}" if pos_adjust
691
               else
692
               f"{name} signal found: about create a new trade for {pair} with stake_amount: "
693
               f"{stake_amount} ...")
694
        logger.info(msg)
1✔
695
        amount = (stake_amount / enter_limit_requested) * leverage
1✔
696
        order_type = ordertype or self.strategy.order_types['entry']
1✔
697

698
        if not pos_adjust and not strategy_safe_wrapper(
1✔
699
                self.strategy.confirm_trade_entry, default_retval=True)(
700
                pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
701
                time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
702
                entry_tag=enter_tag, side=trade_side):
703
            logger.info(f"User denied entry for {pair}.")
1✔
704
            return False
1✔
705
        order = self.exchange.create_order(
1✔
706
            pair=pair,
707
            ordertype=order_type,
708
            side=side,
709
            amount=amount,
710
            rate=enter_limit_requested,
711
            reduceOnly=False,
712
            time_in_force=time_in_force,
713
            leverage=leverage
714
        )
715
        order_obj = Order.parse_from_ccxt_object(order, pair, side)
1✔
716
        order_id = order['id']
1✔
717
        order_status = order.get('status')
1✔
718
        logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.")
1✔
719

720
        # we assume the order is executed at the price requested
721
        enter_limit_filled_price = enter_limit_requested
1✔
722
        amount_requested = amount
1✔
723

724
        if order_status == 'expired' or order_status == 'rejected':
1✔
725

726
            # return false if the order is not filled
727
            if float(order['filled']) == 0:
1✔
728
                logger.warning(f'{name} {time_in_force} order with time in force {order_type} '
1✔
729
                               f'for {pair} is {order_status} by {self.exchange.name}.'
730
                               ' zero amount is fulfilled.')
731
                return False
1✔
732
            else:
733
                # the order is partially fulfilled
734
                # in case of IOC orders we can check immediately
735
                # if the order is fulfilled fully or partially
736
                logger.warning('%s %s order with time in force %s for %s is %s by %s.'
1✔
737
                               ' %s amount fulfilled out of %s (%s remaining which is canceled).',
738
                               name, time_in_force, order_type, pair, order_status,
739
                               self.exchange.name, order['filled'], order['amount'],
740
                               order['remaining']
741
                               )
742
                amount = safe_value_fallback(order, 'filled', 'amount')
1✔
743
                enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
1✔
744

745
        # in case of FOK the order may be filled immediately and fully
746
        elif order_status == 'closed':
1✔
747
            amount = safe_value_fallback(order, 'filled', 'amount')
1✔
748
            enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
1✔
749

750
        # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
751
        fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
1✔
752
        base_currency = self.exchange.get_pair_base_currency(pair)
1✔
753
        open_date = datetime.now(timezone.utc)
1✔
754

755
        # This is a new trade
756
        if trade is None:
1✔
757
            funding_fees = 0.0
1✔
758
            try:
1✔
759
                funding_fees = self.exchange.get_funding_fees(
1✔
760
                    pair=pair, amount=amount, is_short=is_short, open_date=open_date)
761
            except ExchangeError:
1✔
762
                logger.warning("Could not find funding fee.")
1✔
763

764
            trade = Trade(
1✔
765
                pair=pair,
766
                base_currency=base_currency,
767
                stake_currency=self.config['stake_currency'],
768
                stake_amount=stake_amount,
769
                amount=amount,
770
                is_open=True,
771
                amount_requested=amount_requested,
772
                fee_open=fee,
773
                fee_close=fee,
774
                open_rate=enter_limit_filled_price,
775
                open_rate_requested=enter_limit_requested,
776
                open_date=open_date,
777
                exchange=self.exchange.id,
778
                open_order_id=order_id,
779
                strategy=self.strategy.get_strategy_name(),
780
                enter_tag=enter_tag,
781
                timeframe=timeframe_to_minutes(self.config['timeframe']),
782
                leverage=leverage,
783
                is_short=is_short,
784
                trading_mode=self.trading_mode,
785
                funding_fees=funding_fees,
786
                amount_precision=self.exchange.get_precision_amount(pair),
787
                price_precision=self.exchange.get_precision_price(pair),
788
                precision_mode=self.exchange.precisionMode,
789
                contract_size=self.exchange.get_contract_size(pair),
790
            )
791
        else:
792
            # This is additional buy, we reset fee_open_currency so timeout checking can work
793
            trade.is_open = True
1✔
794
            trade.fee_open_currency = None
1✔
795
            trade.open_rate_requested = enter_limit_requested
1✔
796
            trade.open_order_id = order_id
1✔
797

798
        trade.orders.append(order_obj)
1✔
799
        trade.recalc_trade_from_orders()
1✔
800
        Trade.query.session.add(trade)
1✔
801
        Trade.commit()
1✔
802

803
        # Updating wallets
804
        self.wallets.update()
1✔
805

806
        self._notify_enter(trade, order_obj, order_type, sub_trade=pos_adjust)
1✔
807

808
        if pos_adjust:
1✔
809
            if order_status == 'closed':
1✔
810
                logger.info(f"DCA order closed, trade should be up to date: {trade}")
1✔
811
                trade = self.cancel_stoploss_on_exchange(trade)
1✔
812
            else:
813
                logger.info(f"DCA order {order_status}, will wait for resolution: {trade}")
1✔
814

815
        # Update fees if order is non-opened
816
        if order_status in constants.NON_OPEN_EXCHANGE_STATES:
1✔
817
            self.update_trade_state(trade, order_id, order)
1✔
818

819
        return True
1✔
820

821
    def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade:
1✔
822
        # First cancelling stoploss on exchange ...
823
        if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
1✔
824
            try:
1✔
825
                logger.info(f"Canceling stoploss on exchange for {trade}")
1✔
826
                co = self.exchange.cancel_stoploss_order_with_result(
1✔
827
                    trade.stoploss_order_id, trade.pair, trade.amount)
828
                trade.update_order(co)
1✔
829
            except InvalidOrderException:
1✔
830
                logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
1✔
831
        return trade
1✔
832

833
    def get_valid_enter_price_and_stake(
1✔
834
        self, pair: str, price: Optional[float], stake_amount: float,
835
        trade_side: LongShort,
836
        entry_tag: Optional[str],
837
        trade: Optional[Trade],
838
        order_adjust: bool,
839
        leverage_: Optional[float],
840
    ) -> Tuple[float, float, float]:
841

842
        if price:
1✔
843
            enter_limit_requested = price
1✔
844
        else:
845
            # Calculate price
846
            enter_limit_requested = self.exchange.get_rate(
1✔
847
                pair, side='entry', is_short=(trade_side == 'short'), refresh=True)
848
        if not order_adjust:
1✔
849
            # Don't call custom_entry_price in order-adjust scenario
850
            custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
1✔
851
                                                       default_retval=enter_limit_requested)(
852
                pair=pair, current_time=datetime.now(timezone.utc),
853
                proposed_rate=enter_limit_requested, entry_tag=entry_tag,
854
                side=trade_side,
855
            )
856

857
            enter_limit_requested = self.get_valid_price(custom_entry_price, enter_limit_requested)
1✔
858

859
        if not enter_limit_requested:
1✔
860
            raise PricingError('Could not determine entry price.')
1✔
861

862
        if self.trading_mode != TradingMode.SPOT and trade is None:
1✔
863
            max_leverage = self.exchange.get_max_leverage(pair, stake_amount)
1✔
864
            if leverage_:
1✔
865
                leverage = leverage_
1✔
866
            else:
867
                leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)(
1✔
868
                    pair=pair,
869
                    current_time=datetime.now(timezone.utc),
870
                    current_rate=enter_limit_requested,
871
                    proposed_leverage=1.0,
872
                    max_leverage=max_leverage,
873
                    side=trade_side, entry_tag=entry_tag,
874
                )
875
            # Cap leverage between 1.0 and max_leverage.
876
            leverage = min(max(leverage, 1.0), max_leverage)
1✔
877
        else:
878
            # Changing leverage currently not possible
879
            leverage = trade.leverage if trade else 1.0
1✔
880

881
        # Min-stake-amount should actually include Leverage - this way our "minimal"
882
        # stake- amount might be higher than necessary.
883
        # We do however also need min-stake to determine leverage, therefore this is ignored as
884
        # edge-case for now.
885
        min_stake_amount = self.exchange.get_min_pair_stake_amount(
1✔
886
            pair, enter_limit_requested, self.strategy.stoploss, leverage)
887
        max_stake_amount = self.exchange.get_max_pair_stake_amount(
1✔
888
            pair, enter_limit_requested, leverage)
889

890
        if not self.edge and trade is None:
1✔
891
            stake_available = self.wallets.get_available_stake_amount()
1✔
892
            stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
1✔
893
                                                 default_retval=stake_amount)(
894
                pair=pair, current_time=datetime.now(timezone.utc),
895
                current_rate=enter_limit_requested, proposed_stake=stake_amount,
896
                min_stake=min_stake_amount, max_stake=min(max_stake_amount, stake_available),
897
                leverage=leverage, entry_tag=entry_tag, side=trade_side
898
            )
899

900
        stake_amount = self.wallets.validate_stake_amount(
1✔
901
            pair=pair,
902
            stake_amount=stake_amount,
903
            min_stake_amount=min_stake_amount,
904
            max_stake_amount=max_stake_amount,
905
        )
906

907
        return enter_limit_requested, stake_amount, leverage
1✔
908

909
    def _notify_enter(self, trade: Trade, order: Order, order_type: Optional[str] = None,
1✔
910
                      fill: bool = False, sub_trade: bool = False) -> None:
911
        """
912
        Sends rpc notification when a entry order occurred.
913
        """
914
        msg_type = RPCMessageType.ENTRY_FILL if fill else RPCMessageType.ENTRY
1✔
915
        open_rate = order.safe_price
1✔
916

917
        if open_rate is None:
1✔
918
            open_rate = trade.open_rate
1✔
919

920
        current_rate = trade.open_rate_requested
1✔
921
        if self.dataprovider.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
1✔
922
            current_rate = self.exchange.get_rate(
1✔
923
                trade.pair, side='entry', is_short=trade.is_short, refresh=False)
924

925
        msg = {
1✔
926
            'trade_id': trade.id,
927
            'type': msg_type,
928
            'buy_tag': trade.enter_tag,
929
            'enter_tag': trade.enter_tag,
930
            'exchange': trade.exchange.capitalize(),
931
            'pair': trade.pair,
932
            'leverage': trade.leverage if trade.leverage else None,
933
            'direction': 'Short' if trade.is_short else 'Long',
934
            'limit': open_rate,  # Deprecated (?)
935
            'open_rate': open_rate,
936
            'order_type': order_type,
937
            'stake_amount': trade.stake_amount,
938
            'stake_currency': self.config['stake_currency'],
939
            'fiat_currency': self.config.get('fiat_display_currency', None),
940
            'amount': order.safe_amount_after_fee if fill else (order.amount or trade.amount),
941
            'open_date': trade.open_date or datetime.utcnow(),
942
            'current_rate': current_rate,
943
            'sub_trade': sub_trade,
944
        }
945

946
        # Send the message
947
        self.rpc.send_msg(msg)
1✔
948

949
    def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str,
1✔
950
                             sub_trade: bool = False) -> None:
951
        """
952
        Sends rpc notification when a entry order cancel occurred.
953
        """
954
        current_rate = self.exchange.get_rate(
1✔
955
            trade.pair, side='entry', is_short=trade.is_short, refresh=False)
956

957
        msg = {
1✔
958
            'trade_id': trade.id,
959
            'type': RPCMessageType.ENTRY_CANCEL,
960
            'buy_tag': trade.enter_tag,
961
            'enter_tag': trade.enter_tag,
962
            'exchange': trade.exchange.capitalize(),
963
            'pair': trade.pair,
964
            'leverage': trade.leverage,
965
            'direction': 'Short' if trade.is_short else 'Long',
966
            'limit': trade.open_rate,
967
            'order_type': order_type,
968
            'stake_amount': trade.stake_amount,
969
            'stake_currency': self.config['stake_currency'],
970
            'fiat_currency': self.config.get('fiat_display_currency', None),
971
            'amount': trade.amount,
972
            'open_date': trade.open_date,
973
            'current_rate': current_rate,
974
            'reason': reason,
975
            'sub_trade': sub_trade,
976
        }
977

978
        # Send the message
979
        self.rpc.send_msg(msg)
1✔
980

981
#
982
# SELL / exit positions / close trades logic and methods
983
#
984

985
    def exit_positions(self, trades: List[Any]) -> int:
1✔
986
        """
987
        Tries to execute exit orders for open trades (positions)
988
        """
989
        trades_closed = 0
1✔
990
        for trade in trades:
1✔
991
            try:
1✔
992

993
                if (self.strategy.order_types.get('stoploss_on_exchange') and
1✔
994
                        self.handle_stoploss_on_exchange(trade)):
995
                    trades_closed += 1
1✔
996
                    Trade.commit()
1✔
997
                    continue
1✔
998
                # Check if we can sell our current pair
999
                if trade.open_order_id is None and trade.is_open and self.handle_trade(trade):
1✔
1000
                    trades_closed += 1
1✔
1001

1002
            except DependencyException as exception:
1✔
1003
                logger.warning(f'Unable to exit trade {trade.pair}: {exception}')
1✔
1004

1005
        # Updating wallets if any trade occurred
1006
        if trades_closed:
1✔
1007
            self.wallets.update()
1✔
1008

1009
        return trades_closed
1✔
1010

1011
    def handle_trade(self, trade: Trade) -> bool:
1✔
1012
        """
1013
        Sells/exits_short the current pair if the threshold is reached and updates the trade record.
1014
        :return: True if trade has been sold/exited_short, False otherwise
1015
        """
1016
        if not trade.is_open:
1✔
1017
            raise DependencyException(f'Attempt to handle closed trade: {trade}')
1✔
1018

1019
        logger.debug('Handling %s ...', trade)
1✔
1020

1021
        (enter, exit_) = (False, False)
1✔
1022
        exit_tag = None
1✔
1023
        exit_signal_type = "exit_short" if trade.is_short else "exit_long"
1✔
1024

1025
        if (self.config.get('use_exit_signal', True) or
1✔
1026
                self.config.get('ignore_roi_if_entry_signal', False)):
1027
            analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
1✔
1028
                                                                      self.strategy.timeframe)
1029

1030
            (enter, exit_, exit_tag) = self.strategy.get_exit_signal(
1✔
1031
                trade.pair,
1032
                self.strategy.timeframe,
1033
                analyzed_df,
1034
                is_short=trade.is_short
1035
            )
1036

1037
        logger.debug('checking exit')
1✔
1038
        exit_rate = self.exchange.get_rate(
1✔
1039
            trade.pair, side='exit', is_short=trade.is_short, refresh=True)
1040
        if self._check_and_execute_exit(trade, exit_rate, enter, exit_, exit_tag):
1✔
1041
            return True
1✔
1042

1043
        logger.debug(f'Found no {exit_signal_type} signal for %s.', trade)
1✔
1044
        return False
1✔
1045

1046
    def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
1✔
1047
                                enter: bool, exit_: bool, exit_tag: Optional[str]) -> bool:
1048
        """
1049
        Check and execute trade exit
1050
        """
1051
        exits: List[ExitCheckTuple] = self.strategy.should_exit(
1✔
1052
            trade,
1053
            exit_rate,
1054
            datetime.now(timezone.utc),
1055
            enter=enter,
1056
            exit_=exit_,
1057
            force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
1058
        )
1059
        for should_exit in exits:
1✔
1060
            if should_exit.exit_flag:
1✔
1061
                exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None
1✔
1062
                logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}'
1✔
1063
                            f'{f" Tag: {exit_tag1}" if exit_tag1 is not None else ""}')
1064
                exited = self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag1)
1✔
1065
                if exited:
1✔
1066
                    return True
1✔
1067
        return False
1✔
1068

1069
    def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
1✔
1070
        """
1071
        Abstracts creating stoploss orders from the logic.
1072
        Handles errors and updates the trade database object.
1073
        Force-sells the pair (using EmergencySell reason) in case of Problems creating the order.
1074
        :return: True if the order succeeded, and False in case of problems.
1075
        """
1076
        try:
1✔
1077
            stoploss_order = self.exchange.stoploss(
1✔
1078
                pair=trade.pair,
1079
                amount=trade.amount,
1080
                stop_price=stop_price,
1081
                order_types=self.strategy.order_types,
1082
                side=trade.exit_side,
1083
                leverage=trade.leverage
1084
            )
1085

1086
            order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')
1✔
1087
            trade.orders.append(order_obj)
1✔
1088
            trade.stoploss_order_id = str(stoploss_order['id'])
1✔
1089
            trade.stoploss_last_update = datetime.now(timezone.utc)
1✔
1090
            return True
1✔
1091
        except InsufficientFundsError as e:
1✔
1092
            logger.warning(f"Unable to place stoploss order {e}.")
1✔
1093
            # Try to figure out what went wrong
1094
            self.handle_insufficient_funds(trade)
1✔
1095

1096
        except InvalidOrderException as e:
1✔
1097
            trade.stoploss_order_id = None
1✔
1098
            logger.error(f'Unable to place a stoploss order on exchange. {e}')
1✔
1099
            logger.warning('Exiting the trade forcefully')
1✔
1100
            self.execute_trade_exit(trade, stop_price, exit_check=ExitCheckTuple(
1✔
1101
                exit_type=ExitType.EMERGENCY_EXIT))
1102

1103
        except ExchangeError:
1✔
1104
            trade.stoploss_order_id = None
1✔
1105
            logger.exception('Unable to place a stoploss order on exchange.')
1✔
1106
        return False
1✔
1107

1108
    def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
1✔
1109
        """
1110
        Check if trade is fulfilled in which case the stoploss
1111
        on exchange should be added immediately if stoploss on exchange
1112
        is enabled.
1113
        # TODO: liquidation price always on exchange, even without stoploss_on_exchange
1114
        # Therefore fetching account liquidations for open pairs may make sense.
1115
        """
1116

1117
        logger.debug('Handling stoploss on exchange %s ...', trade)
1✔
1118

1119
        stoploss_order = None
1✔
1120

1121
        try:
1✔
1122
            # First we check if there is already a stoploss on exchange
1123
            stoploss_order = self.exchange.fetch_stoploss_order(
1✔
1124
                trade.stoploss_order_id, trade.pair) if trade.stoploss_order_id else None
1125
        except InvalidOrderException as exception:
1✔
1126
            logger.warning('Unable to fetch stoploss order: %s', exception)
1✔
1127

1128
        if stoploss_order:
1✔
1129
            trade.update_order(stoploss_order)
1✔
1130

1131
        # We check if stoploss order is fulfilled
1132
        if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
1✔
1133
            trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
1✔
1134
            self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
1✔
1135
                                    stoploss_order=True)
1136
            # Lock pair for one candle to prevent immediate rebuys
1137
            self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
1✔
1138
                                    reason='Auto lock')
1139
            self._notify_exit(trade, "stoploss", True)
1✔
1140
            return True
1✔
1141

1142
        if trade.open_order_id or not trade.is_open:
1✔
1143
            # Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case
1144
            # as the Amount on the exchange is tied up in another trade.
1145
            # The trade can be closed already (sell-order fill confirmation came in this iteration)
1146
            return False
1✔
1147

1148
        # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
1149
        if not stoploss_order:
1✔
1150
            stoploss = (
1✔
1151
                self.edge.stoploss(pair=trade.pair)
1152
                if self.edge else
1153
                self.strategy.stoploss / trade.leverage
1154
            )
1155
            if trade.is_short:
1✔
1156
                stop_price = trade.open_rate * (1 - stoploss)
1✔
1157
            else:
1158
                stop_price = trade.open_rate * (1 + stoploss)
1✔
1159

1160
            if self.create_stoploss_order(trade=trade, stop_price=stop_price):
1✔
1161
                # The above will return False if the placement failed and the trade was force-sold.
1162
                # in which case the trade will be closed - which we must check below.
1163
                return False
1✔
1164

1165
        # If stoploss order is canceled for some reason we add it again
1166
        if (trade.is_open
1✔
1167
                and stoploss_order
1168
                and stoploss_order['status'] in ('canceled', 'cancelled')):
1169
            if self.create_stoploss_order(trade=trade, stop_price=trade.stoploss_or_liquidation):
1✔
1170
                return False
1✔
1171
            else:
1172
                trade.stoploss_order_id = None
1✔
1173
                logger.warning('Stoploss order was cancelled, but unable to recreate one.')
1✔
1174

1175
        # Finally we check if stoploss on exchange should be moved up because of trailing.
1176
        # Triggered Orders are now real orders - so don't replace stoploss anymore
1177
        if (
1✔
1178
            trade.is_open and stoploss_order
1179
            and stoploss_order.get('status_stop') != 'triggered'
1180
            and (self.config.get('trailing_stop', False)
1181
                 or self.config.get('use_custom_stoploss', False))
1182
        ):
1183
            # if trailing stoploss is enabled we check if stoploss value has changed
1184
            # in which case we cancel stoploss order and put another one with new
1185
            # value immediately
1186
            self.handle_trailing_stoploss_on_exchange(trade, stoploss_order)
1✔
1187

1188
        return False
1✔
1189

1190
    def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: Dict) -> None:
1✔
1191
        """
1192
        Check to see if stoploss on exchange should be updated
1193
        in case of trailing stoploss on exchange
1194
        :param trade: Corresponding Trade
1195
        :param order: Current on exchange stoploss order
1196
        :return: None
1197
        """
1198
        stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stoploss_or_liquidation)
1✔
1199

1200
        if self.exchange.stoploss_adjust(stoploss_norm, order, side=trade.exit_side):
1✔
1201
            # we check if the update is necessary
1202
            update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
1✔
1203
            upd_req = datetime.now(timezone.utc) - timedelta(seconds=update_beat)
1✔
1204
            if trade.stoploss_last_update_utc and upd_req >= trade.stoploss_last_update_utc:
1✔
1205
                # cancelling the current stoploss on exchange first
1206
                logger.info(f"Cancelling current stoploss on exchange for pair {trade.pair} "
1✔
1207
                            f"(orderid:{order['id']}) in order to add another one ...")
1208
                try:
1✔
1209
                    co = self.exchange.cancel_stoploss_order_with_result(order['id'], trade.pair,
1✔
1210
                                                                         trade.amount)
1211
                    trade.update_order(co)
1✔
1212
                except InvalidOrderException:
1✔
1213
                    logger.exception(f"Could not cancel stoploss order {order['id']} "
1✔
1214
                                     f"for pair {trade.pair}")
1215

1216
                # Create new stoploss order
1217
                if not self.create_stoploss_order(trade=trade, stop_price=stoploss_norm):
1✔
1218
                    logger.warning(f"Could not create trailing stoploss order "
1✔
1219
                                   f"for pair {trade.pair}.")
1220

1221
    def manage_open_orders(self) -> None:
1✔
1222
        """
1223
        Management of open orders on exchange. Unfilled orders might be cancelled if timeout
1224
        was met or replaced if there's a new candle and user has requested it.
1225
        Timeout setting takes priority over limit order adjustment request.
1226
        :return: None
1227
        """
1228
        for trade in Trade.get_open_order_trades():
1✔
1229
            try:
1✔
1230
                if not trade.open_order_id:
1✔
1231
                    continue
×
1232
                order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
1✔
1233
            except (ExchangeError):
1✔
1234
                logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
1✔
1235
                continue
1✔
1236

1237
            fully_cancelled = self.update_trade_state(trade, trade.open_order_id, order)
1✔
1238
            not_closed = order['status'] == 'open' or fully_cancelled
1✔
1239
            order_obj = trade.select_order_by_order_id(trade.open_order_id)
1✔
1240

1241
            if not_closed:
1✔
1242
                if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out(
1✔
1243
                   trade, order_obj, datetime.now(timezone.utc))):
1244
                    self.handle_timedout_order(order, trade)
1✔
1245
                else:
1246
                    self.replace_order(order, order_obj, trade)
1✔
1247

1248
    def handle_timedout_order(self, order: Dict, trade: Trade) -> None:
1✔
1249
        """
1250
        Check if current analyzed order timed out and cancel if necessary.
1251
        :param order: Order dict grabbed with exchange.fetch_order()
1252
        :param trade: Trade object.
1253
        :return: None
1254
        """
1255
        if order['side'] == trade.entry_side:
1✔
1256
            self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
1✔
1257
        else:
1258
            canceled = self.handle_cancel_exit(
1✔
1259
                trade, order, constants.CANCEL_REASON['TIMEOUT'])
1260
            canceled_count = trade.get_exit_order_count()
1✔
1261
            max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0)
1✔
1262
            if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
1✔
1263
                logger.warning(f'Emergency exiting trade {trade}, as the exit order '
1✔
1264
                               f'timed out {max_timeouts} times.')
1265
                try:
1✔
1266
                    self.execute_trade_exit(
1✔
1267
                        trade, order['price'],
1268
                        exit_check=ExitCheckTuple(exit_type=ExitType.EMERGENCY_EXIT))
1269
                except DependencyException as exception:
1✔
1270
                    logger.warning(
1✔
1271
                        f'Unable to emergency sell trade {trade.pair}: {exception}')
1272

1273
    def replace_order(self, order: Dict, order_obj: Optional[Order], trade: Trade) -> None:
1✔
1274
        """
1275
        Check if current analyzed entry order should be replaced or simply cancelled.
1276
        To simply cancel the existing order(no replacement) adjust_entry_price() should return None
1277
        To maintain existing order adjust_entry_price() should return order_obj.price
1278
        To replace existing order adjust_entry_price() should return desired price for limit order
1279
        :param order: Order dict grabbed with exchange.fetch_order()
1280
        :param order_obj: Order object.
1281
        :param trade: Trade object.
1282
        :return: None
1283
        """
1284
        analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
1✔
1285
                                                                  self.strategy.timeframe)
1286
        latest_candle_open_date = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None
1✔
1287
        latest_candle_close_date = timeframe_to_next_date(self.strategy.timeframe,
1✔
1288
                                                          latest_candle_open_date)
1289
        # Check if new candle
1290
        if order_obj and latest_candle_close_date > order_obj.order_date_utc:
1✔
1291
            # New candle
1292
            proposed_rate = self.exchange.get_rate(
1✔
1293
                trade.pair, side='entry', is_short=trade.is_short, refresh=True)
1294
            adjusted_entry_price = strategy_safe_wrapper(self.strategy.adjust_entry_price,
1✔
1295
                                                         default_retval=order_obj.price)(
1296
                trade=trade, order=order_obj, pair=trade.pair,
1297
                current_time=datetime.now(timezone.utc), proposed_rate=proposed_rate,
1298
                current_order_rate=order_obj.price, entry_tag=trade.enter_tag,
1299
                side=trade.entry_side)
1300

1301
            replacing = True
1✔
1302
            cancel_reason = constants.CANCEL_REASON['REPLACE']
1✔
1303
            if not adjusted_entry_price:
1✔
1304
                replacing = False
1✔
1305
                cancel_reason = constants.CANCEL_REASON['USER_CANCEL']
1✔
1306
            if order_obj.price != adjusted_entry_price:
1✔
1307
                # cancel existing order if new price is supplied or None
1308
                self.handle_cancel_enter(trade, order, cancel_reason,
1✔
1309
                                         replacing=replacing)
1310
                if adjusted_entry_price:
1✔
1311
                    # place new order only if new price is supplied
1312
                    self.execute_entry(
1✔
1313
                        pair=trade.pair,
1314
                        stake_amount=(order_obj.remaining * order_obj.price / trade.leverage),
1315
                        price=adjusted_entry_price,
1316
                        trade=trade,
1317
                        is_short=trade.is_short,
1318
                        order_adjust=True,
1319
                    )
1320

1321
    def cancel_all_open_orders(self) -> None:
1✔
1322
        """
1323
        Cancel all orders that are currently open
1324
        :return: None
1325
        """
1326

1327
        for trade in Trade.get_open_order_trades():
1✔
1328
            try:
1✔
1329
                order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
1✔
1330
            except (ExchangeError):
1✔
1331
                logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
1✔
1332
                continue
1✔
1333

1334
            if order['side'] == trade.entry_side:
1✔
1335
                self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
1✔
1336

1337
            elif order['side'] == trade.exit_side:
1✔
1338
                self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
1✔
1339
        Trade.commit()
1✔
1340

1341
    def handle_cancel_enter(
1✔
1342
            self, trade: Trade, order: Dict, reason: str,
1343
            replacing: Optional[bool] = False
1344
    ) -> bool:
1345
        """
1346
        entry cancel - cancel order
1347
        :param replacing: Replacing order - prevent trade deletion.
1348
        :return: True if trade was fully cancelled
1349
        """
1350
        was_trade_fully_canceled = False
1✔
1351
        side = trade.entry_side.capitalize()
1✔
1352

1353
        # Cancelled orders may have the status of 'canceled' or 'closed'
1354
        if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
1✔
1355
            filled_val: float = order.get('filled', 0.0) or 0.0
1✔
1356
            filled_stake = filled_val * trade.open_rate
1✔
1357
            minstake = self.exchange.get_min_pair_stake_amount(
1✔
1358
                trade.pair, trade.open_rate, self.strategy.stoploss)
1359

1360
            if filled_val > 0 and minstake and filled_stake < minstake:
1✔
1361
                logger.warning(
1✔
1362
                    f"Order {trade.open_order_id} for {trade.pair} not cancelled, "
1363
                    f"as the filled amount of {filled_val} would result in an unexitable trade.")
1364
                return False
1✔
1365
            corder = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
1✔
1366
                                                            trade.amount)
1367
            # Avoid race condition where the order could not be cancelled coz its already filled.
1368
            # Simply bailing here is the only safe way - as this order will then be
1369
            # handled in the next iteration.
1370
            if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES:
1✔
1371
                logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
1✔
1372
                return False
1✔
1373
        else:
1374
            # Order was cancelled already, so we can reuse the existing dict
1375
            corder = order
1✔
1376
            reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
1✔
1377

1378
        logger.info('%s order %s for %s.', side, reason, trade)
1✔
1379

1380
        # Using filled to determine the filled amount
1381
        filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled')
1✔
1382
        if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
1✔
1383
            # if trade is not partially completed and it's the only order, just delete the trade
1384
            open_order_count = len([order for order in trade.orders if order.status == 'open'])
1✔
1385
            if open_order_count <= 1 and trade.nr_of_successful_entries == 0 and not replacing:
1✔
1386
                logger.info(f'{side} order fully cancelled. Removing {trade} from database.')
1✔
1387
                trade.delete()
1✔
1388
                was_trade_fully_canceled = True
1✔
1389
                reason += f", {constants.CANCEL_REASON['FULLY_CANCELLED']}"
1✔
1390
            else:
1391
                self.update_trade_state(trade, trade.open_order_id, corder)
1✔
1392
                trade.open_order_id = None
1✔
1393
                logger.info(f'{side} Order timeout for {trade}.')
1✔
1394
        else:
1395
            # update_trade_state (and subsequently recalc_trade_from_orders) will handle updates
1396
            # to the trade object
1397
            self.update_trade_state(trade, trade.open_order_id, corder)
1✔
1398
            trade.open_order_id = None
1✔
1399

1400
            logger.info(f'Partial {trade.entry_side} order timeout for {trade}.')
1✔
1401
            reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
1✔
1402

1403
        self.wallets.update()
1✔
1404
        self._notify_enter_cancel(trade, order_type=self.strategy.order_types['entry'],
1✔
1405
                                  reason=reason)
1406
        return was_trade_fully_canceled
1✔
1407

1408
    def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> bool:
1✔
1409
        """
1410
        exit order cancel - cancel order and update trade
1411
        :return: True if exit order was cancelled, false otherwise
1412
        """
1413
        cancelled = False
1✔
1414
        # Cancelled orders may have the status of 'canceled' or 'closed'
1415
        if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
1✔
1416
            filled_val: float = order.get('filled', 0.0) or 0.0
1✔
1417
            filled_rem_stake = trade.stake_amount - filled_val * trade.open_rate
1✔
1418
            minstake = self.exchange.get_min_pair_stake_amount(
1✔
1419
                trade.pair, trade.open_rate, self.strategy.stoploss)
1420
            # Double-check remaining amount
1421
            if filled_val > 0:
1✔
1422
                reason = constants.CANCEL_REASON['PARTIALLY_FILLED']
1✔
1423
                if minstake and filled_rem_stake < minstake:
1✔
1424
                    logger.warning(
1✔
1425
                        f"Order {trade.open_order_id} for {trade.pair} not cancelled, as "
1426
                        f"the filled amount of {filled_val} would result in an unexitable trade.")
1427
                    reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
1✔
1428

1429
                    self._notify_exit_cancel(
1✔
1430
                        trade,
1431
                        order_type=self.strategy.order_types['exit'],
1432
                        reason=reason, order_id=order['id'],
1433
                        sub_trade=trade.amount != order['amount']
1434
                    )
1435
                    return False
1✔
1436

1437
            try:
1✔
1438
                co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
1✔
1439
                                                            trade.amount)
1440
            except InvalidOrderException:
1✔
1441
                logger.exception(
1✔
1442
                    f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
1443
                return False
1✔
1444
            trade.close_rate = None
1✔
1445
            trade.close_rate_requested = None
1✔
1446
            trade.close_profit = None
1✔
1447
            trade.close_profit_abs = None
1✔
1448
            # Set exit_reason for fill message
1449
            exit_reason_prev = trade.exit_reason
1✔
1450
            trade.exit_reason = trade.exit_reason + f", {reason}" if trade.exit_reason else reason
1✔
1451
            self.update_trade_state(trade, trade.open_order_id, co)
1✔
1452
            # Order might be filled above in odd timing issues.
1453
            if co.get('status') in ('canceled', 'cancelled'):
1✔
1454
                trade.exit_reason = None
1✔
1455
                trade.open_order_id = None
1✔
1456
            else:
1457
                trade.exit_reason = exit_reason_prev
1✔
1458

1459
            logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
1✔
1460
            cancelled = True
1✔
1461
        else:
1462
            reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
1✔
1463
            logger.info(f'{trade.exit_side.capitalize()} order {reason} for {trade}.')
1✔
1464
            self.update_trade_state(trade, trade.open_order_id, order)
1✔
1465
            trade.open_order_id = None
1✔
1466

1467
        self._notify_exit_cancel(
1✔
1468
            trade,
1469
            order_type=self.strategy.order_types['exit'],
1470
            reason=reason, order_id=order['id'], sub_trade=trade.amount != order['amount']
1471
        )
1472
        return cancelled
1✔
1473

1474
    def _safe_exit_amount(self, trade: Trade, pair: str, amount: float) -> float:
1✔
1475
        """
1476
        Get sellable amount.
1477
        Should be trade.amount - but will fall back to the available amount if necessary.
1478
        This should cover cases where get_real_amount() was not able to update the amount
1479
        for whatever reason.
1480
        :param trade: Trade we're working with
1481
        :param pair: Pair we're trying to sell
1482
        :param amount: amount we expect to be available
1483
        :return: amount to sell
1484
        :raise: DependencyException: if available balance is not within 2% of the available amount.
1485
        """
1486
        # Update wallets to ensure amounts tied up in a stoploss is now free!
1487
        self.wallets.update()
1✔
1488
        if self.trading_mode == TradingMode.FUTURES:
1✔
1489
            return amount
1✔
1490

1491
        trade_base_currency = self.exchange.get_pair_base_currency(pair)
1✔
1492
        wallet_amount = self.wallets.get_free(trade_base_currency)
1✔
1493
        logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
1✔
1494
        if wallet_amount >= amount:
1✔
1495
            # A safe exit amount isn't needed for futures, you can just exit/close the position
1496
            return amount
1✔
1497
        elif wallet_amount > amount * 0.98:
1✔
1498
            logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.")
1✔
1499
            trade.amount = wallet_amount
1✔
1500
            return wallet_amount
1✔
1501
        else:
1502
            raise DependencyException(
1✔
1503
                f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}")
1504

1505
    def execute_trade_exit(
1✔
1506
            self,
1507
            trade: Trade,
1508
            limit: float,
1509
            exit_check: ExitCheckTuple,
1510
            *,
1511
            exit_tag: Optional[str] = None,
1512
            ordertype: Optional[str] = None,
1513
            sub_trade_amt: float = None,
1514
    ) -> bool:
1515
        """
1516
        Executes a trade exit for the given trade and limit
1517
        :param trade: Trade instance
1518
        :param limit: limit rate for the sell order
1519
        :param exit_check: CheckTuple with signal and reason
1520
        :return: True if it succeeds False
1521
        """
1522
        try:
1✔
1523
            trade.funding_fees = self.exchange.get_funding_fees(
1✔
1524
                pair=trade.pair,
1525
                amount=trade.amount,
1526
                is_short=trade.is_short,
1527
                open_date=trade.date_last_filled_utc,
1528
            )
1529
        except ExchangeError:
1✔
1530
            logger.warning("Could not update funding fee.")
1✔
1531

1532
        exit_type = 'exit'
1✔
1533
        exit_reason = exit_tag or exit_check.exit_reason
1✔
1534
        if exit_check.exit_type in (
1✔
1535
                ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS, ExitType.LIQUIDATION):
1536
            exit_type = 'stoploss'
1✔
1537

1538
        # set custom_exit_price if available
1539
        proposed_limit_rate = limit
1✔
1540
        current_profit = trade.calc_profit_ratio(limit)
1✔
1541
        custom_exit_price = strategy_safe_wrapper(self.strategy.custom_exit_price,
1✔
1542
                                                  default_retval=proposed_limit_rate)(
1543
            pair=trade.pair, trade=trade,
1544
            current_time=datetime.now(timezone.utc),
1545
            proposed_rate=proposed_limit_rate, current_profit=current_profit,
1546
            exit_tag=exit_reason)
1547

1548
        limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
1✔
1549

1550
        # First cancelling stoploss on exchange ...
1551
        trade = self.cancel_stoploss_on_exchange(trade)
1✔
1552

1553
        order_type = ordertype or self.strategy.order_types[exit_type]
1✔
1554
        if exit_check.exit_type == ExitType.EMERGENCY_EXIT:
1✔
1555
            # Emergency sells (default to market!)
1556
            order_type = self.strategy.order_types.get("emergency_exit", "market")
1✔
1557

1558
        amount = self._safe_exit_amount(trade, trade.pair, sub_trade_amt or trade.amount)
1✔
1559
        time_in_force = self.strategy.order_time_in_force['exit']
1✔
1560

1561
        if (exit_check.exit_type != ExitType.LIQUIDATION
1✔
1562
                and not sub_trade_amt
1563
                and not strategy_safe_wrapper(
1564
                    self.strategy.confirm_trade_exit, default_retval=True)(
1565
                    pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
1566
                    time_in_force=time_in_force, exit_reason=exit_reason,
1567
                    sell_reason=exit_reason,  # sellreason -> compatibility
1568
                    current_time=datetime.now(timezone.utc))):
1569
            logger.info(f"User denied exit for {trade.pair}.")
1✔
1570
            return False
1✔
1571

1572
        try:
1✔
1573
            # Execute sell and update trade record
1574
            order = self.exchange.create_order(
1✔
1575
                pair=trade.pair,
1576
                ordertype=order_type,
1577
                side=trade.exit_side,
1578
                amount=amount,
1579
                rate=limit,
1580
                leverage=trade.leverage,
1581
                reduceOnly=self.trading_mode == TradingMode.FUTURES,
1582
                time_in_force=time_in_force
1583
            )
1584
        except InsufficientFundsError as e:
1✔
1585
            logger.warning(f"Unable to place order {e}.")
1✔
1586
            # Try to figure out what went wrong
1587
            self.handle_insufficient_funds(trade)
1✔
1588
            return False
1✔
1589

1590
        order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side)
1✔
1591
        trade.orders.append(order_obj)
1✔
1592

1593
        trade.open_order_id = order['id']
1✔
1594
        trade.exit_order_status = ''
1✔
1595
        trade.close_rate_requested = limit
1✔
1596
        trade.exit_reason = exit_reason
1✔
1597

1598
        if not sub_trade_amt:
1✔
1599
            # Lock pair for one candle to prevent immediate re-trading
1600
            self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
1✔
1601
                                    reason='Auto lock')
1602

1603
        self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt), order=order_obj)
1✔
1604
        # In case of market sell orders the order can be closed immediately
1605
        if order.get('status', 'unknown') in ('closed', 'expired'):
1✔
1606
            self.update_trade_state(trade, trade.open_order_id, order)
1✔
1607
        Trade.commit()
1✔
1608

1609
        return True
1✔
1610

1611
    def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False,
1✔
1612
                     sub_trade: bool = False, order: Order = None) -> None:
1613
        """
1614
        Sends rpc notification when a sell occurred.
1615
        """
1616
        # Use cached rates here - it was updated seconds ago.
1617
        current_rate = self.exchange.get_rate(
1✔
1618
            trade.pair, side='exit', is_short=trade.is_short, refresh=False) if not fill else None
1619

1620
        # second condition is for mypy only; order will always be passed during sub trade
1621
        if sub_trade and order is not None:
1✔
1622
            amount = order.safe_filled if fill else order.amount
1✔
1623
            order_rate: float = order.safe_price
1✔
1624

1625
            profit = trade.calc_profit(rate=order_rate, amount=amount, open_rate=trade.open_rate)
1✔
1626
            profit_ratio = trade.calc_profit_ratio(order_rate, amount, trade.open_rate)
1✔
1627
        else:
1628
            order_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
1✔
1629
            profit = trade.calc_profit(rate=order_rate) + (0.0 if fill else trade.realized_profit)
1✔
1630
            profit_ratio = trade.calc_profit_ratio(order_rate)
1✔
1631
            amount = trade.amount
1✔
1632
        gain = "profit" if profit_ratio > 0 else "loss"
1✔
1633

1634
        msg = {
1✔
1635
            'type': (RPCMessageType.EXIT_FILL if fill
1636
                     else RPCMessageType.EXIT),
1637
            'trade_id': trade.id,
1638
            'exchange': trade.exchange.capitalize(),
1639
            'pair': trade.pair,
1640
            'leverage': trade.leverage,
1641
            'direction': 'Short' if trade.is_short else 'Long',
1642
            'gain': gain,
1643
            'limit': order_rate,  # Deprecated
1644
            'order_rate': order_rate,
1645
            'order_type': order_type,
1646
            'amount': amount,
1647
            'open_rate': trade.open_rate,
1648
            'close_rate': order_rate,
1649
            'current_rate': current_rate,
1650
            'profit_amount': profit,
1651
            'profit_ratio': profit_ratio,
1652
            'buy_tag': trade.enter_tag,
1653
            'enter_tag': trade.enter_tag,
1654
            'sell_reason': trade.exit_reason,  # Deprecated
1655
            'exit_reason': trade.exit_reason,
1656
            'open_date': trade.open_date,
1657
            'close_date': trade.close_date or datetime.utcnow(),
1658
            'stake_amount': trade.stake_amount,
1659
            'stake_currency': self.config['stake_currency'],
1660
            'fiat_currency': self.config.get('fiat_display_currency'),
1661
            'sub_trade': sub_trade,
1662
            'cumulative_profit': trade.realized_profit,
1663
        }
1664

1665
        # Send the message
1666
        self.rpc.send_msg(msg)
1✔
1667

1668
    def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str,
1✔
1669
                            order_id: str, sub_trade: bool = False) -> None:
1670
        """
1671
        Sends rpc notification when a sell cancel occurred.
1672
        """
1673
        if trade.exit_order_status == reason:
1✔
1674
            return
1✔
1675
        else:
1676
            trade.exit_order_status = reason
1✔
1677

1678
        order = trade.select_order_by_order_id(order_id)
1✔
1679
        if not order:
1✔
1680
            raise DependencyException(
×
1681
                f"Order_obj not found for {order_id}. This should not have happened.")
1682

1683
        profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
1✔
1684
        profit_trade = trade.calc_profit(rate=profit_rate)
1✔
1685
        current_rate = self.exchange.get_rate(
1✔
1686
            trade.pair, side='exit', is_short=trade.is_short, refresh=False)
1687
        profit_ratio = trade.calc_profit_ratio(profit_rate)
1✔
1688
        gain = "profit" if profit_ratio > 0 else "loss"
1✔
1689

1690
        msg = {
1✔
1691
            'type': RPCMessageType.EXIT_CANCEL,
1692
            'trade_id': trade.id,
1693
            'exchange': trade.exchange.capitalize(),
1694
            'pair': trade.pair,
1695
            'leverage': trade.leverage,
1696
            'direction': 'Short' if trade.is_short else 'Long',
1697
            'gain': gain,
1698
            'limit': profit_rate or 0,
1699
            'order_type': order_type,
1700
            'amount': order.safe_amount_after_fee,
1701
            'open_rate': trade.open_rate,
1702
            'current_rate': current_rate,
1703
            'profit_amount': profit_trade,
1704
            'profit_ratio': profit_ratio,
1705
            'buy_tag': trade.enter_tag,
1706
            'enter_tag': trade.enter_tag,
1707
            'sell_reason': trade.exit_reason,  # Deprecated
1708
            'exit_reason': trade.exit_reason,
1709
            'open_date': trade.open_date,
1710
            'close_date': trade.close_date or datetime.now(timezone.utc),
1711
            'stake_currency': self.config['stake_currency'],
1712
            'fiat_currency': self.config.get('fiat_display_currency', None),
1713
            'reason': reason,
1714
            'sub_trade': sub_trade,
1715
            'stake_amount': trade.stake_amount,
1716
        }
1717

1718
        # Send the message
1719
        self.rpc.send_msg(msg)
1✔
1720

1721
#
1722
# Common update trade state methods
1723
#
1724

1725
    def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None,
1✔
1726
                           stoploss_order: bool = False, send_msg: bool = True) -> bool:
1727
        """
1728
        Checks trades with open orders and updates the amount if necessary
1729
        Handles closing both buy and sell orders.
1730
        :param trade: Trade object of the trade we're analyzing
1731
        :param order_id: Order-id of the order we're analyzing
1732
        :param action_order: Already acquired order object
1733
        :param send_msg: Send notification - should always be True except in "recovery" methods
1734
        :return: True if order has been cancelled without being filled partially, False otherwise
1735
        """
1736
        if not order_id:
1✔
1737
            logger.warning(f'Orderid for trade {trade} is empty.')
1✔
1738
            return False
1✔
1739

1740
        # Update trade with order values
1741
        logger.info(f'Found open order for {trade}')
1✔
1742
        try:
1✔
1743
            order = action_order or self.exchange.fetch_order_or_stoploss_order(order_id,
1✔
1744
                                                                                trade.pair,
1745
                                                                                stoploss_order)
1746
        except InvalidOrderException as exception:
1✔
1747
            logger.warning('Unable to fetch order %s: %s', order_id, exception)
1✔
1748
            return False
1✔
1749

1750
        trade.update_order(order)
1✔
1751

1752
        if self.exchange.check_order_canceled_empty(order):
1✔
1753
            # Trade has been cancelled on exchange
1754
            # Handling of this will happen in check_handle_timedout.
1755
            return True
1✔
1756

1757
        order_obj = trade.select_order_by_order_id(order_id)
1✔
1758
        if not order_obj:
1✔
1759
            raise DependencyException(
×
1760
                f"Order_obj not found for {order_id}. This should not have happened.")
1761

1762
        self.handle_order_fee(trade, order_obj, order)
1✔
1763

1764
        trade.update_trade(order_obj)
1✔
1765

1766
        if order.get('status') in constants.NON_OPEN_EXCHANGE_STATES:
1✔
1767
            # If a entry order was closed, force update on stoploss on exchange
1768
            if order.get('side') == trade.entry_side:
1✔
1769
                trade = self.cancel_stoploss_on_exchange(trade)
1✔
1770
                if not self.edge:
1✔
1771
                    # TODO: should shorting/leverage be supported by Edge,
1772
                    # then this will need to be fixed.
1773
                    trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
1✔
1774
            if order.get('side') == trade.entry_side or trade.amount > 0:
1✔
1775
                # Must also run for partial exits
1776
                # TODO: Margin will need to use interest_rate as well.
1777
                # interest_rate = self.exchange.get_interest_rate()
1778
                trade.set_liquidation_price(self.exchange.get_liquidation_price(
1✔
1779
                    pair=trade.pair,
1780
                    open_rate=trade.open_rate,
1781
                    is_short=trade.is_short,
1782
                    amount=trade.amount,
1783
                    stake_amount=trade.stake_amount,
1784
                    wallet_balance=trade.stake_amount,
1785
                ))
1786

1787
            # Updating wallets when order is closed
1788
            self.wallets.update()
1✔
1789
        Trade.commit()
1✔
1790

1791
        self.order_close_notify(trade, order_obj, stoploss_order, send_msg)
1✔
1792

1793
        return False
1✔
1794

1795
    def order_close_notify(
1✔
1796
            self, trade: Trade, order: Order, stoploss_order: bool, send_msg: bool):
1797
        """send "fill" notifications"""
1798

1799
        sub_trade = not isclose(order.safe_amount_after_fee,
1✔
1800
                                trade.amount, abs_tol=constants.MATH_CLOSE_PREC)
1801
        if order.ft_order_side == trade.exit_side:
1✔
1802
            # Exit notification
1803
            if send_msg and not stoploss_order and not trade.open_order_id:
1✔
1804
                self._notify_exit(trade, '', fill=True, sub_trade=sub_trade, order=order)
1✔
1805
            if not trade.is_open:
1✔
1806
                self.handle_protections(trade.pair, trade.trade_direction)
1✔
1807
        elif send_msg and not trade.open_order_id and not stoploss_order:
1✔
1808
            # Enter fill
1809
            self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
1✔
1810

1811
    def handle_protections(self, pair: str, side: LongShort) -> None:
1✔
1812
        prot_trig = self.protections.stop_per_pair(pair, side=side)
1✔
1813
        if prot_trig:
1✔
1814
            msg = {'type': RPCMessageType.PROTECTION_TRIGGER, }
1✔
1815
            msg.update(prot_trig.to_json())
1✔
1816
            self.rpc.send_msg(msg)
1✔
1817

1818
        prot_trig_glb = self.protections.global_stop(side=side)
1✔
1819
        if prot_trig_glb:
1✔
1820
            msg = {'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, }
1✔
1821
            msg.update(prot_trig_glb.to_json())
1✔
1822
            self.rpc.send_msg(msg)
1✔
1823

1824
    def apply_fee_conditional(self, trade: Trade, trade_base_currency: str,
1✔
1825
                              amount: float, fee_abs: float, order_obj: Order) -> Optional[float]:
1826
        """
1827
        Applies the fee to amount (either from Order or from Trades).
1828
        Can eat into dust if more than the required asset is available.
1829
        Can't happen in Futures mode - where Fees are always in settlement currency,
1830
        never in base currency.
1831
        """
1832
        self.wallets.update()
1✔
1833
        amount_ = trade.amount
1✔
1834
        if order_obj.ft_order_side == trade.exit_side or order_obj.ft_order_side == 'stoploss':
1✔
1835
            # check against remaining amount!
1836
            amount_ = trade.amount - amount
1✔
1837

1838
        if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount_:
1✔
1839
            # Eat into dust if we own more than base currency
1840
            logger.info(f"Fee amount for {trade} was in base currency - "
1✔
1841
                        f"Eating Fee {fee_abs} into dust.")
1842
        elif fee_abs != 0:
1✔
1843
            logger.info(f"Applying fee on amount for {trade}, fee={fee_abs}.")
1✔
1844
            return fee_abs
1✔
1845
        return None
1✔
1846

1847
    def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None:
1✔
1848
        # Try update amount (binance-fix)
1849
        try:
1✔
1850
            fee_abs = self.get_real_amount(trade, order, order_obj)
1✔
1851
            if fee_abs is not None:
1✔
1852
                order_obj.ft_fee_base = fee_abs
1✔
1853
        except DependencyException as exception:
1✔
1854
            logger.warning("Could not update trade amount: %s", exception)
1✔
1855

1856
    def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> Optional[float]:
1✔
1857
        """
1858
        Detect and update trade fee.
1859
        Calls trade.update_fee() upon correct detection.
1860
        Returns modified amount if the fee was taken from the destination currency.
1861
        Necessary for exchanges which charge fees in base currency (e.g. binance)
1862
        :return: Absolute fee to apply for this order or None
1863
        """
1864
        # Init variables
1865
        order_amount = safe_value_fallback(order, 'filled', 'amount')
1✔
1866
        # Only run for closed orders
1867
        if trade.fee_updated(order.get('side', '')) or order['status'] == 'open':
1✔
1868
            return None
1✔
1869

1870
        trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
1✔
1871
        # use fee from order-dict if possible
1872
        if self.exchange.order_has_fee(order):
1✔
1873
            fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(
1✔
1874
                order['fee'], order['symbol'], order['cost'], order_obj.safe_filled)
1875
            logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: "
1✔
1876
                        f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
1877
            if fee_rate is None or fee_rate < 0.02:
1✔
1878
                # Reject all fees that report as > 2%.
1879
                # These are most likely caused by a parsing bug in ccxt
1880
                # due to multiple trades (https://github.com/ccxt/ccxt/issues/8025)
1881
                trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
1✔
1882
                if trade_base_currency == fee_currency:
1✔
1883
                    # Apply fee to amount
1884
                    return self.apply_fee_conditional(trade, trade_base_currency,
1✔
1885
                                                      amount=order_amount, fee_abs=fee_cost,
1886
                                                      order_obj=order_obj)
1887
                return None
1✔
1888
        return self.fee_detection_from_trades(
1✔
1889
            trade, order, order_obj, order_amount, order.get('trades', []))
1890

1891
    def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order,
1✔
1892
                                  order_amount: float, trades: List) -> Optional[float]:
1893
        """
1894
        fee-detection fallback to Trades.
1895
        Either uses provided trades list or the result of fetch_my_trades to get correct fee.
1896
        """
1897
        if not trades:
1✔
1898
            trades = self.exchange.get_trades_for_order(
1✔
1899
                self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date)
1900

1901
        if len(trades) == 0:
1✔
1902
            logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)
1✔
1903
            return None
1✔
1904
        fee_currency = None
1✔
1905
        amount = 0
1✔
1906
        fee_abs = 0.0
1✔
1907
        fee_cost = 0.0
1✔
1908
        trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
1✔
1909
        fee_rate_array: List[float] = []
1✔
1910
        for exectrade in trades:
1✔
1911
            amount += exectrade['amount']
1✔
1912
            if self.exchange.order_has_fee(exectrade):
1✔
1913
                # Prefer singular fee
1914
                fees = [exectrade['fee']]
1✔
1915
            else:
1916
                fees = exectrade.get('fees', [])
1✔
1917
            for fee in fees:
1✔
1918

1919
                fee_cost_, fee_currency, fee_rate_ = self.exchange.extract_cost_curr_rate(
1✔
1920
                    fee, exectrade['symbol'], exectrade['cost'], exectrade['amount']
1921
                )
1922
                fee_cost += fee_cost_
1✔
1923
                if fee_rate_ is not None:
1✔
1924
                    fee_rate_array.append(fee_rate_)
1✔
1925
                # only applies if fee is in quote currency!
1926
                if trade_base_currency == fee_currency:
1✔
1927
                    fee_abs += fee_cost_
1✔
1928
        # Ensure at least one trade was found:
1929
        if fee_currency:
1✔
1930
            # fee_rate should use mean
1931
            fee_rate = sum(fee_rate_array) / float(len(fee_rate_array)) if fee_rate_array else None
1✔
1932
            if fee_rate is not None and fee_rate < 0.02:
1✔
1933
                # Only update if fee-rate is < 2%
1934
                trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
1✔
1935
            else:
1936
                logger.warning(
1✔
1937
                    f"Not updating {order.get('side', '')}-fee - rate: {fee_rate}, {fee_currency}.")
1938

1939
        if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
1✔
1940
            # * Leverage could be a cause for this warning
1941
            logger.warning(f"Amount {amount} does not match amount {trade.amount}")
1✔
1942
            raise DependencyException("Half bought? Amounts don't match")
1✔
1943

1944
        if fee_abs != 0:
1✔
1945
            return self.apply_fee_conditional(
1✔
1946
                trade, trade_base_currency, amount=amount, fee_abs=fee_abs, order_obj=order_obj)
1947
        return None
1✔
1948

1949
    def get_valid_price(self, custom_price: float, proposed_price: float) -> float:
1✔
1950
        """
1951
        Return the valid price.
1952
        Check if the custom price is of the good type if not return proposed_price
1953
        :return: valid price for the order
1954
        """
1955
        if custom_price:
1✔
1956
            try:
1✔
1957
                valid_custom_price = float(custom_price)
1✔
1958
            except ValueError:
1✔
1959
                valid_custom_price = proposed_price
1✔
1960
        else:
1961
            valid_custom_price = proposed_price
1✔
1962

1963
        cust_p_max_dist_r = self.config.get('custom_price_max_distance_ratio', 0.02)
1✔
1964
        min_custom_price_allowed = proposed_price - (proposed_price * cust_p_max_dist_r)
1✔
1965
        max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r)
1✔
1966

1967
        # Bracket between min_custom_price_allowed and max_custom_price_allowed
1968
        return max(
1✔
1969
            min(valid_custom_price, max_custom_price_allowed),
1970
            min_custom_price_allowed)
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc