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

freqtrade / freqtrade / 9394559170

26 Apr 2024 06:36AM UTC coverage: 94.656% (-0.02%) from 94.674%
9394559170

push

github

xmatthias
Loader should be passed as kwarg for clarity

20280 of 21425 relevant lines covered (94.66%)

0.95 hits per line

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

97.48
/freqtrade/strategy/interface.py
1
"""
2
IStrategy interface
3
This module defines the interface to apply for strategies
4
"""
5
import logging
1✔
6
from abc import ABC, abstractmethod
1✔
7
from datetime import datetime, timedelta, timezone
1✔
8
from typing import Dict, List, Optional, Tuple, Union
1✔
9

10
from pandas import DataFrame
1✔
11

12
from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, Config, IntOrInf, ListPairsWithTimeframes
1✔
13
from freqtrade.data.dataprovider import DataProvider
1✔
14
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, RunMode,
1✔
15
                             SignalDirection, SignalTagType, SignalType, TradingMode)
16
from freqtrade.exceptions import OperationalException, StrategyError
1✔
17
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds
1✔
18
from freqtrade.misc import remove_entry_exit_signals
1✔
19
from freqtrade.persistence import Order, PairLocks, Trade
1✔
20
from freqtrade.strategy.hyper import HyperStrategyMixin
1✔
21
from freqtrade.strategy.informative_decorator import (InformativeData, PopulateIndicators,
1✔
22
                                                      _create_and_merge_informative_pair,
23
                                                      _format_pair_name)
24
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
1✔
25
from freqtrade.util import dt_now
1✔
26
from freqtrade.wallets import Wallets
1✔
27

28

29
logger = logging.getLogger(__name__)
1✔
30

31

32
class IStrategy(ABC, HyperStrategyMixin):
1✔
33
    """
34
    Interface for freqtrade strategies
35
    Defines the mandatory structure must follow any custom strategies
36

37
    Attributes you can use:
38
        minimal_roi -> Dict: Minimal ROI designed for the strategy
39
        stoploss -> float: optimal stoploss designed for the strategy
40
        timeframe -> str: value of the timeframe to use with the strategy
41
    """
42
    # Strategy interface version
43
    # Default to version 2
44
    # Version 1 is the initial interface without metadata dict - deprecated and no longer supported.
45
    # Version 2 populate_* include metadata dict
46
    # Version 3 - First version with short and leverage support
47
    INTERFACE_VERSION: int = 3
1✔
48

49
    _ft_params_from_file: Dict
1✔
50
    # associated minimal roi
51
    minimal_roi: Dict = {}
1✔
52

53
    # associated stoploss
54
    stoploss: float
1✔
55

56
    # max open trades for the strategy
57
    max_open_trades:  IntOrInf
1✔
58

59
    # trailing stoploss
60
    trailing_stop: bool = False
1✔
61
    trailing_stop_positive: Optional[float] = None
1✔
62
    trailing_stop_positive_offset: float = 0.0
1✔
63
    trailing_only_offset_is_reached = False
1✔
64
    use_custom_stoploss: bool = False
1✔
65

66
    # Can this strategy go short?
67
    can_short: bool = False
1✔
68

69
    # associated timeframe
70
    timeframe: str
1✔
71

72
    # Optional order types
73
    order_types: Dict = {
1✔
74
        'entry': 'limit',
75
        'exit': 'limit',
76
        'stoploss': 'limit',
77
        'stoploss_on_exchange': False,
78
        'stoploss_on_exchange_interval': 60,
79
    }
80

81
    # Optional time in force
82
    order_time_in_force: Dict = {
1✔
83
        'entry': 'GTC',
84
        'exit': 'GTC',
85
    }
86

87
    # run "populate_indicators" only for new candle
88
    process_only_new_candles: bool = True
1✔
89

90
    use_exit_signal: bool
1✔
91
    exit_profit_only: bool
1✔
92
    exit_profit_offset: float
1✔
93
    ignore_roi_if_entry_signal: bool
1✔
94

95
    # Position adjustment is disabled by default
96
    position_adjustment_enable: bool = False
1✔
97
    max_entry_position_adjustment: int = -1
1✔
98

99
    # Number of seconds after which the candle will no longer result in a buy on expired candles
100
    ignore_buying_expired_candle_after: int = 0
1✔
101

102
    # Disable checking the dataframe (converts the error into a warning message)
103
    disable_dataframe_checks: bool = False
1✔
104

105
    # Count of candles the strategy requires before producing valid signals
106
    startup_candle_count: int = 0
1✔
107

108
    # Protections
109
    protections: List = []
1✔
110

111
    # Class level variables (intentional) containing
112
    # the dataprovider (dp) (access to other candles, historic data, ...)
113
    # and wallets - access to the current balance.
114
    dp: DataProvider
1✔
115
    wallets: Optional[Wallets] = None
1✔
116
    # Filled from configuration
117
    stake_currency: str
1✔
118
    # container variable for strategy source code
119
    __source__: str = ''
1✔
120

121
    # Definition of plot_config. See plotting documentation for more details.
122
    plot_config: Dict = {}
1✔
123

124
    # A self set parameter that represents the market direction. filled from configuration
125
    market_direction: MarketDirection = MarketDirection.NONE
1✔
126

127
    def __init__(self, config: Config) -> None:
1✔
128
        self.config = config
1✔
129
        # Dict to determine if analysis is necessary
130
        self._last_candle_seen_per_pair: Dict[str, datetime] = {}
1✔
131
        super().__init__(config)
1✔
132

133
        # Gather informative pairs from @informative-decorated methods.
134
        self._ft_informative: List[Tuple[InformativeData, PopulateIndicators]] = []
1✔
135
        for attr_name in dir(self.__class__):
1✔
136
            cls_method = getattr(self.__class__, attr_name)
1✔
137
            if not callable(cls_method):
1✔
138
                continue
1✔
139
            informative_data_list = getattr(cls_method, '_ft_informative', None)
1✔
140
            if not isinstance(informative_data_list, list):
1✔
141
                # Type check is required because mocker would return a mock object that evaluates to
142
                # True, confusing this code.
143
                continue
1✔
144
            strategy_timeframe_minutes = timeframe_to_minutes(self.timeframe)
1✔
145
            for informative_data in informative_data_list:
1✔
146
                if timeframe_to_minutes(informative_data.timeframe) < strategy_timeframe_minutes:
1✔
147
                    raise OperationalException('Informative timeframe must be equal or higher than '
×
148
                                               'strategy timeframe!')
149
                if not informative_data.candle_type:
1✔
150
                    informative_data.candle_type = config['candle_type_def']
1✔
151
                self._ft_informative.append((informative_data, cls_method))
1✔
152

153
    def load_freqAI_model(self) -> None:
1✔
154
        if self.config.get('freqai', {}).get('enabled', False):
1✔
155
            # Import here to avoid importing this if freqAI is disabled
156
            from freqtrade.freqai.utils import download_all_data_for_training
1✔
157
            from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
1✔
158
            self.freqai = FreqaiModelResolver.load_freqaimodel(self.config)
1✔
159
            self.freqai_info = self.config["freqai"]
1✔
160

161
            # download the desired data in dry/live
162
            if self.config.get('runmode') in (RunMode.DRY_RUN, RunMode.LIVE):
1✔
163
                logger.info(
×
164
                    "Downloading all training data for all pairs in whitelist and "
165
                    "corr_pairlist, this may take a while if the data is not "
166
                    "already on disk."
167
                )
168
                download_all_data_for_training(self.dp, self.config)
×
169
        else:
170
            # Gracious failures if freqAI is disabled but "start" is called.
171
            class DummyClass:
1✔
172
                def start(self, *args, **kwargs):
1✔
173
                    raise OperationalException(
1✔
174
                        'freqAI is not enabled. '
175
                        'Please enable it in your config to use this strategy.')
176

177
                def shutdown(self, *args, **kwargs):
1✔
178
                    pass
1✔
179

180
            self.freqai = DummyClass()  # type: ignore
1✔
181

182
    def ft_bot_start(self, **kwargs) -> None:
1✔
183
        """
184
        Strategy init - runs after dataprovider has been added.
185
        Must call bot_start()
186
        """
187
        self.load_freqAI_model()
1✔
188

189
        strategy_safe_wrapper(self.bot_start)()
1✔
190

191
        self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT)
1✔
192

193
    def ft_bot_cleanup(self) -> None:
1✔
194
        """
195
        Clean up FreqAI and child threads
196
        """
197
        self.freqai.shutdown()
1✔
198

199
    @abstractmethod
1✔
200
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
201
        """
202
        Populate indicators that will be used in the Buy, Sell, Short, Exit_short strategy
203
        :param dataframe: DataFrame with data from the exchange
204
        :param metadata: Additional information, like the currently traded pair
205
        :return: a Dataframe with all mandatory indicators for the strategies
206
        """
207
        return dataframe
×
208

209
    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
210
        """
211
        DEPRECATED - please migrate to populate_entry_trend
212
        :param dataframe: DataFrame
213
        :param metadata: Additional information, like the currently traded pair
214
        :return: DataFrame with buy column
215
        """
216
        return dataframe
1✔
217

218
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
219
        """
220
        Based on TA indicators, populates the entry signal for the given dataframe
221
        :param dataframe: DataFrame
222
        :param metadata: Additional information, like the currently traded pair
223
        :return: DataFrame with entry columns populated
224
        """
225
        return self.populate_buy_trend(dataframe, metadata)
1✔
226

227
    def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
228
        """
229
        DEPRECATED - please migrate to populate_exit_trend
230
        Based on TA indicators, populates the sell signal for the given dataframe
231
        :param dataframe: DataFrame
232
        :param metadata: Additional information, like the currently traded pair
233
        :return: DataFrame with sell column
234
        """
235
        return dataframe
1✔
236

237
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
238
        """
239
        Based on TA indicators, populates the exit signal for the given dataframe
240
        :param dataframe: DataFrame
241
        :param metadata: Additional information, like the currently traded pair
242
        :return: DataFrame with exit columns populated
243
        """
244
        return self.populate_sell_trend(dataframe, metadata)
1✔
245

246
    def bot_start(self, **kwargs) -> None:
1✔
247
        """
248
        Called only once after bot instantiation.
249
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
250
        """
251
        pass
1✔
252

253
    def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
1✔
254
        """
255
        Called at the start of the bot iteration (one loop).
256
        Might be used to perform pair-independent tasks
257
        (e.g. gather some remote resource for comparison)
258
        :param current_time: datetime object, containing the current datetime
259
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
260
        """
261
        pass
1✔
262

263
    def check_buy_timeout(self, pair: str, trade: Trade, order: Order,
1✔
264
                          current_time: datetime, **kwargs) -> bool:
265
        """
266
        DEPRECATED: Please use `check_entry_timeout` instead.
267
        """
268
        return False
1✔
269

270
    def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
1✔
271
                            current_time: datetime, **kwargs) -> bool:
272
        """
273
        Check entry timeout function callback.
274
        This method can be used to override the entry-timeout.
275
        It is called whenever a limit entry order has been created,
276
        and is not yet fully filled.
277
        Configuration options in `unfilledtimeout` will be verified before this,
278
        so ensure to set these timeouts high enough.
279

280
        When not implemented by a strategy, this simply returns False.
281
        :param pair: Pair the trade is for
282
        :param trade: Trade object.
283
        :param order: Order object.
284
        :param current_time: datetime object, containing the current datetime
285
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
286
        :return bool: When True is returned, then the entry order is cancelled.
287
        """
288
        return self.check_buy_timeout(
1✔
289
            pair=pair, trade=trade, order=order, current_time=current_time)
290

291
    def check_sell_timeout(self, pair: str, trade: Trade, order: Order,
1✔
292
                           current_time: datetime, **kwargs) -> bool:
293
        """
294
        DEPRECATED: Please use `check_exit_timeout` instead.
295
        """
296
        return False
1✔
297

298
    def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
1✔
299
                           current_time: datetime, **kwargs) -> bool:
300
        """
301
        Check exit timeout function callback.
302
        This method can be used to override the exit-timeout.
303
        It is called whenever a limit exit order has been created,
304
        and is not yet fully filled.
305
        Configuration options in `unfilledtimeout` will be verified before this,
306
        so ensure to set these timeouts high enough.
307

308
        When not implemented by a strategy, this simply returns False.
309
        :param pair: Pair the trade is for
310
        :param trade: Trade object.
311
        :param order: Order object
312
        :param current_time: datetime object, containing the current datetime
313
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
314
        :return bool: When True is returned, then the exit-order is cancelled.
315
        """
316
        return self.check_sell_timeout(
1✔
317
            pair=pair, trade=trade, order=order, current_time=current_time)
318

319
    def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
1✔
320
                            time_in_force: str, current_time: datetime, entry_tag: Optional[str],
321
                            side: str, **kwargs) -> bool:
322
        """
323
        Called right before placing a entry order.
324
        Timing for this function is critical, so avoid doing heavy computations or
325
        network requests in this method.
326

327
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
328

329
        When not implemented by a strategy, returns True (always confirming).
330

331
        :param pair: Pair that's about to be bought/shorted.
332
        :param order_type: Order type (as configured in order_types). usually limit or market.
333
        :param amount: Amount in target (base) currency that's going to be traded.
334
        :param rate: Rate that's going to be used when using limit orders
335
                     or current rate for market orders.
336
        :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
337
        :param current_time: datetime object, containing the current datetime
338
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
339
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
340
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
341
        :return bool: When True is returned, then the buy-order is placed on the exchange.
342
            False aborts the process
343
        """
344
        return True
1✔
345

346
    def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
1✔
347
                           rate: float, time_in_force: str, exit_reason: str,
348
                           current_time: datetime, **kwargs) -> bool:
349
        """
350
        Called right before placing a regular exit order.
351
        Timing for this function is critical, so avoid doing heavy computations or
352
        network requests in this method.
353

354
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
355

356
        When not implemented by a strategy, returns True (always confirming).
357

358
        :param pair: Pair for trade that's about to be exited.
359
        :param trade: trade object.
360
        :param order_type: Order type (as configured in order_types). usually limit or market.
361
        :param amount: Amount in base currency.
362
        :param rate: Rate that's going to be used when using limit orders
363
                     or current rate for market orders.
364
        :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
365
        :param exit_reason: Exit reason.
366
            Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
367
                           'exit_signal', 'force_exit', 'emergency_exit']
368
        :param current_time: datetime object, containing the current datetime
369
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
370
        :return bool: When True, then the exit-order is placed on the exchange.
371
            False aborts the process
372
        """
373
        return True
1✔
374

375
    def order_filled(self, pair: str, trade: Trade, order: Order,
1✔
376
                     current_time: datetime, **kwargs) -> None:
377
        """
378
        Called right after an order fills.
379
        Will be called for all order types (entry, exit, stoploss, position adjustment).
380
        :param pair: Pair for trade
381
        :param trade: trade object.
382
        :param order: Order object.
383
        :param current_time: datetime object, containing the current datetime
384
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
385
        """
386
        pass
1✔
387

388
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
1✔
389
                        current_profit: float, after_fill: bool, **kwargs) -> Optional[float]:
390
        """
391
        Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
392
        e.g. returning -0.05 would create a stoploss 5% below current_rate.
393
        The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.
394

395
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
396

397
        When not implemented by a strategy, returns the initial stoploss value.
398
        Only called when use_custom_stoploss is set to True.
399

400
        :param pair: Pair that's currently analyzed
401
        :param trade: trade object.
402
        :param current_time: datetime object, containing the current datetime
403
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
404
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
405
        :param after_fill: True if the stoploss is called after the order was filled.
406
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
407
        :return float: New stoploss value, relative to the current_rate
408
        """
409
        return self.stoploss
1✔
410

411
    def custom_entry_price(self, pair: str, trade: Optional[Trade],
1✔
412
                           current_time: datetime, proposed_rate: float,
413
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
414
        """
415
        Custom entry price logic, returning the new entry price.
416

417
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
418

419
        When not implemented by a strategy, returns None, orderbook is used to set entry price
420

421
        :param pair: Pair that's currently analyzed
422
        :param trade: trade object (None for initial entries).
423
        :param current_time: datetime object, containing the current datetime
424
        :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
425
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
426
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
427
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
428
        :return float: New entry price value if provided
429
        """
430
        return proposed_rate
1✔
431

432
    def custom_exit_price(self, pair: str, trade: Trade,
1✔
433
                          current_time: datetime, proposed_rate: float,
434
                          current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
435
        """
436
        Custom exit price logic, returning the new exit price.
437

438
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
439

440
        When not implemented by a strategy, returns None, orderbook is used to set exit price
441

442
        :param pair: Pair that's currently analyzed
443
        :param trade: trade object.
444
        :param current_time: datetime object, containing the current datetime
445
        :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
446
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
447
        :param exit_tag: Exit reason.
448
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
449
        :return float: New exit price value if provided
450
        """
451
        return proposed_rate
1✔
452

453
    def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
1✔
454
                    current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
455
        """
456
        DEPRECATED - please use custom_exit instead.
457
        Custom exit signal logic indicating that specified position should be sold. Returning a
458
        string or True from this method is equal to setting exit signal on a candle at specified
459
        time. This method is not called when exit signal is set.
460

461
        This method should be overridden to create exit signals that depend on trade parameters. For
462
        example you could implement an exit relative to the candle when the trade was opened,
463
        or a custom 1:2 risk-reward ROI.
464

465
        Custom exit reason max length is 64. Exceeding characters will be removed.
466

467
        :param pair: Pair that's currently analyzed
468
        :param trade: trade object.
469
        :param current_time: datetime object, containing the current datetime
470
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
471
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
472
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
473
        :return: To execute exit, return a string with custom exit reason or True. Otherwise return
474
        None or False.
475
        """
476
        return None
1✔
477

478
    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
1✔
479
                    current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
480
        """
481
        Custom exit signal logic indicating that specified position should be sold. Returning a
482
        string or True from this method is equal to setting exit signal on a candle at specified
483
        time. This method is not called when exit signal is set.
484

485
        This method should be overridden to create exit signals that depend on trade parameters. For
486
        example you could implement an exit relative to the candle when the trade was opened,
487
        or a custom 1:2 risk-reward ROI.
488

489
        Custom exit reason max length is 64. Exceeding characters will be removed.
490

491
        :param pair: Pair that's currently analyzed
492
        :param trade: trade object.
493
        :param current_time: datetime object, containing the current datetime
494
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
495
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
496
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
497
        :return: To execute exit, return a string with custom exit reason or True. Otherwise return
498
        None or False.
499
        """
500
        return self.custom_sell(pair, trade, current_time, current_rate, current_profit, **kwargs)
1✔
501

502
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
1✔
503
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
504
                            leverage: float, entry_tag: Optional[str], side: str,
505
                            **kwargs) -> float:
506
        """
507
        Customize stake size for each new trade.
508

509
        :param pair: Pair that's currently analyzed
510
        :param current_time: datetime object, containing the current datetime
511
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
512
        :param proposed_stake: A stake amount proposed by the bot.
513
        :param min_stake: Minimal stake size allowed by exchange.
514
        :param max_stake: Balance available for trading.
515
        :param leverage: Leverage selected for this trade.
516
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
517
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
518
        :return: A stake size, which is between min_stake and max_stake.
519
        """
520
        return proposed_stake
1✔
521

522
    def adjust_trade_position(self, trade: Trade, current_time: datetime,
1✔
523
                              current_rate: float, current_profit: float,
524
                              min_stake: Optional[float], max_stake: float,
525
                              current_entry_rate: float, current_exit_rate: float,
526
                              current_entry_profit: float, current_exit_profit: float,
527
                              **kwargs
528
                              ) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
529
        """
530
        Custom trade adjustment logic, returning the stake amount that a trade should be
531
        increased or decreased.
532
        This means extra entry or exit orders with additional fees.
533
        Only called when `position_adjustment_enable` is set to True.
534

535
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
536

537
        When not implemented by a strategy, returns None
538

539
        :param trade: trade object.
540
        :param current_time: datetime object, containing the current datetime
541
        :param current_rate: Current entry rate (same as current_entry_profit)
542
        :param current_profit: Current profit (as ratio), calculated based on current_rate
543
                               (same as current_entry_profit).
544
        :param min_stake: Minimal stake size allowed by exchange (for both entries and exits)
545
        :param max_stake: Maximum stake allowed (either through balance, or by exchange limits).
546
        :param current_entry_rate: Current rate using entry pricing.
547
        :param current_exit_rate: Current rate using exit pricing.
548
        :param current_entry_profit: Current profit using entry pricing.
549
        :param current_exit_profit: Current profit using exit pricing.
550
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
551
        :return float: Stake amount to adjust your trade,
552
                       Positive values to increase position, Negative values to decrease position.
553
                       Return None for no action.
554
                       Optionally, return a tuple with a 2nd element with an order reason
555
        """
556
        return None
×
557

558
    def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
1✔
559
                           current_time: datetime, proposed_rate: float, current_order_rate: float,
560
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
561
        """
562
        Entry price re-adjustment logic, returning the user desired limit price.
563
        This only executes when a order was already placed, still open (unfilled fully or partially)
564
        and not timed out on subsequent candles after entry trigger.
565

566
        For full documentation please go to https://www.freqtrade.io/en/latest/strategy-callbacks/
567

568
        When not implemented by a strategy, returns current_order_rate as default.
569
        If current_order_rate is returned then the existing order is maintained.
570
        If None is returned then order gets canceled but not replaced by a new one.
571

572
        :param pair: Pair that's currently analyzed
573
        :param trade: Trade object.
574
        :param order: Order object
575
        :param current_time: datetime object, containing the current datetime
576
        :param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
577
        :param current_order_rate: Rate of the existing order in place.
578
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
579
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
580
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
581
        :return float: New entry price value if provided
582

583
        """
584
        return current_order_rate
1✔
585

586
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
1✔
587
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
588
                 side: str, **kwargs) -> float:
589
        """
590
        Customize leverage for each new trade. This method is only called in futures mode.
591

592
        :param pair: Pair that's currently analyzed
593
        :param current_time: datetime object, containing the current datetime
594
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
595
        :param proposed_leverage: A leverage proposed by the bot.
596
        :param max_leverage: Max leverage allowed on this pair
597
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
598
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
599
        :return: A leverage amount, which is between 1.0 and max_leverage.
600
        """
601
        return 1.0
1✔
602

603
    def informative_pairs(self) -> ListPairsWithTimeframes:
1✔
604
        """
605
        Define additional, informative pair/interval combinations to be cached from the exchange.
606
        These pair/interval combinations are non-tradable, unless they are part
607
        of the whitelist as well.
608
        For more information, please consult the documentation
609
        :return: List of tuples in the format (pair, interval)
610
            Sample: return [("ETH/USDT", "5m"),
611
                            ("BTC/USDT", "15m"),
612
                            ]
613
        """
614
        return []
1✔
615

616
    def version(self) -> Optional[str]:
1✔
617
        """
618
        Returns version of the strategy.
619
        """
620
        return None
1✔
621

622
    def populate_any_indicators(self, pair: str, df: DataFrame, tf: str,
1✔
623
                                informative: Optional[DataFrame] = None,
624
                                set_generalized_indicators: bool = False) -> DataFrame:
625
        """
626
        DEPRECATED - USE FEATURE ENGINEERING FUNCTIONS INSTEAD
627
        Function designed to automatically generate, name and merge features
628
        from user indicated timeframes in the configuration file. User can add
629
        additional features here, but must follow the naming convention.
630
        This method is *only* used in FreqaiDataKitchen class and therefore
631
        it is only called if FreqAI is active.
632
        :param pair: pair to be used as informative
633
        :param df: strategy dataframe which will receive merges from informatives
634
        :param tf: timeframe of the dataframe which will modify the feature names
635
        :param informative: the dataframe associated with the informative pair
636
        """
637
        return df
×
638

639
    def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
1✔
640
                                       metadata: Dict, **kwargs) -> DataFrame:
641
        """
642
        *Only functional with FreqAI enabled strategies*
643
        This function will automatically expand the defined features on the config defined
644
        `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and
645
        `include_corr_pairs`. In other words, a single feature defined in this function
646
        will automatically expand to a total of
647
        `indicator_periods_candles` * `include_timeframes` * `include_shifted_candles` *
648
        `include_corr_pairs` numbers of features added to the model.
649

650
        All features must be prepended with `%` to be recognized by FreqAI internals.
651

652
        More details on how these config defined parameters accelerate feature engineering
653
        in the documentation at:
654

655
        https://www.freqtrade.io/en/latest/freqai-parameter-table/#feature-parameters
656

657
        https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features
658

659
        :param dataframe: strategy dataframe which will receive the features
660
        :param period: period of the indicator - usage example:
661
        :param metadata: metadata of current pair
662
        dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
663
        """
664
        return dataframe
×
665

666
    def feature_engineering_expand_basic(
1✔
667
            self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
668
        """
669
        *Only functional with FreqAI enabled strategies*
670
        This function will automatically expand the defined features on the config defined
671
        `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
672
        In other words, a single feature defined in this function
673
        will automatically expand to a total of
674
        `include_timeframes` * `include_shifted_candles` * `include_corr_pairs`
675
        numbers of features added to the model.
676

677
        Features defined here will *not* be automatically duplicated on user defined
678
        `indicator_periods_candles`
679

680
        All features must be prepended with `%` to be recognized by FreqAI internals.
681

682
        More details on how these config defined parameters accelerate feature engineering
683
        in the documentation at:
684

685
        https://www.freqtrade.io/en/latest/freqai-parameter-table/#feature-parameters
686

687
        https://www.freqtrade.io/en/latest/freqai-feature-engineering/#defining-the-features
688

689
        :param dataframe: strategy dataframe which will receive the features
690
        :param metadata: metadata of current pair
691
        dataframe["%-pct-change"] = dataframe["close"].pct_change()
692
        dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
693
        """
694
        return dataframe
×
695

696
    def feature_engineering_standard(
1✔
697
            self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
698
        """
699
        *Only functional with FreqAI enabled strategies*
700
        This optional function will be called once with the dataframe of the base timeframe.
701
        This is the final function to be called, which means that the dataframe entering this
702
        function will contain all the features and columns created by all other
703
        freqai_feature_engineering_* functions.
704

705
        This function is a good place to do custom exotic feature extractions (e.g. tsfresh).
706
        This function is a good place for any feature that should not be auto-expanded upon
707
        (e.g. day of the week).
708

709
        All features must be prepended with `%` to be recognized by FreqAI internals.
710

711
        More details about feature engineering available:
712

713
        https://www.freqtrade.io/en/latest/freqai-feature-engineering
714

715
        :param dataframe: strategy dataframe which will receive the features
716
        :param metadata: metadata of current pair
717
        usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
718
        """
719
        return dataframe
×
720

721
    def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
1✔
722
        """
723
        *Only functional with FreqAI enabled strategies*
724
        Required function to set the targets for the model.
725
        All targets must be prepended with `&` to be recognized by the FreqAI internals.
726

727
        More details about feature engineering available:
728

729
        https://www.freqtrade.io/en/latest/freqai-feature-engineering
730

731
        :param dataframe: strategy dataframe which will receive the targets
732
        :param metadata: metadata of current pair
733
        usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"]
734
        """
735
        return dataframe
×
736

737
###
738
# END - Intended to be overridden by strategy
739
###
740

741
    _ft_stop_uses_after_fill = False
1✔
742

743
    def _adjust_trade_position_internal(
1✔
744
            self, trade: Trade, current_time: datetime,
745
            current_rate: float, current_profit: float,
746
            min_stake: Optional[float], max_stake: float,
747
            current_entry_rate: float, current_exit_rate: float,
748
            current_entry_profit: float, current_exit_profit: float,
749
            **kwargs
750
    ) -> Tuple[Optional[float], str]:
751
        """
752
        wrapper around adjust_trade_position to handle the return value
753
        """
754
        resp = strategy_safe_wrapper(self.adjust_trade_position,
1✔
755
                                     default_retval=(None, ''), supress_error=True)(
756
            trade=trade, current_time=current_time,
757
            current_rate=current_rate, current_profit=current_profit,
758
            min_stake=min_stake, max_stake=max_stake,
759
            current_entry_rate=current_entry_rate, current_exit_rate=current_exit_rate,
760
            current_entry_profit=current_entry_profit, current_exit_profit=current_exit_profit,
761
            **kwargs
762
        )
763
        order_tag = ''
1✔
764
        if isinstance(resp, tuple):
1✔
765
            if len(resp) >= 1:
1✔
766
                stake_amount = resp[0]
1✔
767
            if len(resp) > 1:
1✔
768
                order_tag = resp[1] or ''
1✔
769
        else:
770
            stake_amount = resp
1✔
771
        return stake_amount, order_tag
1✔
772

773
    def __informative_pairs_freqai(self) -> ListPairsWithTimeframes:
1✔
774
        """
775
        Create informative-pairs needed for FreqAI
776
        """
777
        if self.config.get('freqai', {}).get('enabled', False):
1✔
778
            whitelist_pairs = self.dp.current_whitelist()
1✔
779
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
780
            corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
1✔
781
            informative_pairs = []
1✔
782
            for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
1✔
783
                for pair in set(whitelist_pairs + corr_pairs):
1✔
784
                    informative_pairs.append((pair, tf, candle_type))
1✔
785
            return informative_pairs
1✔
786

787
        return []
1✔
788

789
    def gather_informative_pairs(self) -> ListPairsWithTimeframes:
1✔
790
        """
791
        Internal method which gathers all informative pairs (user or automatically defined).
792
        """
793
        informative_pairs = self.informative_pairs()
1✔
794
        # Compatibility code for 2 tuple informative pairs
795
        informative_pairs = [
1✔
796
            (p[0], p[1], CandleType.from_string(p[2]) if len(
797
                p) > 2 and p[2] != '' else self.config.get('candle_type_def', CandleType.SPOT))
798
            for p in informative_pairs]
799
        for inf_data, _ in self._ft_informative:
1✔
800
            # Get default candle type if not provided explicitly.
801
            candle_type = (inf_data.candle_type if inf_data.candle_type
1✔
802
                           else self.config.get('candle_type_def', CandleType.SPOT))
803
            if inf_data.asset:
1✔
804
                if any(s in inf_data.asset for s in ("{BASE}", "{base}")):
1✔
805
                    for pair in self.dp.current_whitelist():
1✔
806

807
                        pair_tf = (
1✔
808
                            _format_pair_name(self.config, inf_data.asset, self.dp.market(pair)),
809
                            inf_data.timeframe,
810
                            candle_type,
811
                        )
812
                        informative_pairs.append(pair_tf)
1✔
813

814
                else:
815
                    pair_tf = (
1✔
816
                        _format_pair_name(self.config, inf_data.asset),
817
                        inf_data.timeframe,
818
                        candle_type,
819
                    )
820
                    informative_pairs.append(pair_tf)
1✔
821
            else:
822
                for pair in self.dp.current_whitelist():
1✔
823
                    informative_pairs.append((pair, inf_data.timeframe, candle_type))
1✔
824
        informative_pairs.extend(self.__informative_pairs_freqai())
1✔
825
        return list(set(informative_pairs))
1✔
826

827
    def get_strategy_name(self) -> str:
1✔
828
        """
829
        Returns strategy class name
830
        """
831
        return self.__class__.__name__
1✔
832

833
    def lock_pair(self, pair: str, until: datetime,
1✔
834
                  reason: Optional[str] = None, side: str = '*') -> None:
835
        """
836
        Locks pair until a given timestamp happens.
837
        Locked pairs are not analyzed, and are prevented from opening new trades.
838
        Locks can only count up (allowing users to lock pairs for a longer period of time).
839
        To remove a lock from a pair, use `unlock_pair()`
840
        :param pair: Pair to lock
841
        :param until: datetime in UTC until the pair should be blocked from opening new trades.
842
                Needs to be timezone aware `datetime.now(timezone.utc)`
843
        :param reason: Optional string explaining why the pair was locked.
844
        :param side: Side to check, can be long, short or '*'
845
        """
846
        PairLocks.lock_pair(pair, until, reason, side=side)
1✔
847

848
    def unlock_pair(self, pair: str) -> None:
1✔
849
        """
850
        Unlocks a pair previously locked using lock_pair.
851
        Not used by freqtrade itself, but intended to be used if users lock pairs
852
        manually from within the strategy, to allow an easy way to unlock pairs.
853
        :param pair: Unlock pair to allow trading again
854
        """
855
        PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
1✔
856

857
    def unlock_reason(self, reason: str) -> None:
1✔
858
        """
859
        Unlocks all pairs previously locked using lock_pair with specified reason.
860
        Not used by freqtrade itself, but intended to be used if users lock pairs
861
        manually from within the strategy, to allow an easy way to unlock pairs.
862
        :param reason: Unlock pairs to allow trading again
863
        """
864
        PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
1✔
865

866
    def is_pair_locked(self, pair: str, *, candle_date: Optional[datetime] = None,
1✔
867
                       side: str = '*') -> bool:
868
        """
869
        Checks if a pair is currently locked
870
        The 2nd, optional parameter ensures that locks are applied until the new candle arrives,
871
        and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap
872
        of 2 seconds for an entry order to happen on an old signal.
873
        :param pair: "Pair to check"
874
        :param candle_date: Date of the last candle. Optional, defaults to current date
875
        :param side: Side to check, can be long, short or '*'
876
        :returns: locking state of the pair in question.
877
        """
878

879
        if not candle_date:
1✔
880
            # Simple call ...
881
            return PairLocks.is_pair_locked(pair, side=side)
1✔
882
        else:
883
            lock_time = timeframe_to_next_date(self.timeframe, candle_date)
1✔
884
            return PairLocks.is_pair_locked(pair, lock_time, side=side)
1✔
885

886
    def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
887
        """
888
        Parses the given candle (OHLCV) data and returns a populated DataFrame
889
        add several TA indicators and entry order signal to it
890
        Should only be used in live.
891
        :param dataframe: Dataframe containing data from exchange
892
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
893
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
894
        """
895
        logger.debug("TA Analysis Launched")
1✔
896
        dataframe = self.advise_indicators(dataframe, metadata)
1✔
897
        dataframe = self.advise_entry(dataframe, metadata)
1✔
898
        dataframe = self.advise_exit(dataframe, metadata)
1✔
899
        return dataframe
1✔
900

901
    def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
902
        """
903
        Parses the given candle (OHLCV) data and returns a populated DataFrame
904
        add several TA indicators and buy signal to it
905
        WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set.
906
        :param dataframe: Dataframe containing data from exchange
907
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
908
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
909
        """
910
        pair = str(metadata.get('pair'))
1✔
911

912
        new_candle = self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']
1✔
913
        # Test if seen this pair and last candle before.
914
        # always run if process_only_new_candles is set to false
915
        if not self.process_only_new_candles or new_candle:
1✔
916

917
            # Defs that only make change on new candle data.
918
            dataframe = self.analyze_ticker(dataframe, metadata)
1✔
919

920
            self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
1✔
921

922
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
923
            self.dp._set_cached_df(pair, self.timeframe, dataframe, candle_type=candle_type)
1✔
924
            self.dp._emit_df((pair, self.timeframe, candle_type), dataframe, new_candle)
1✔
925

926
        else:
927
            logger.debug("Skipping TA Analysis for already analyzed candle")
1✔
928
            dataframe = remove_entry_exit_signals(dataframe)
1✔
929

930
        logger.debug("Loop Analysis Launched")
1✔
931

932
        return dataframe
1✔
933

934
    def analyze_pair(self, pair: str) -> None:
1✔
935
        """
936
        Fetch data for this pair from dataprovider and analyze.
937
        Stores the dataframe into the dataprovider.
938
        The analyzed dataframe is then accessible via `dp.get_analyzed_dataframe()`.
939
        :param pair: Pair to analyze.
940
        """
941
        dataframe = self.dp.ohlcv(
1✔
942
            pair, self.timeframe, candle_type=self.config.get('candle_type_def', CandleType.SPOT)
943
        )
944
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
945
            logger.warning('Empty candle (OHLCV) data for pair %s', pair)
1✔
946
            return
1✔
947

948
        try:
1✔
949
            df_len, df_close, df_date = self.preserve_df(dataframe)
1✔
950

951
            dataframe = strategy_safe_wrapper(
1✔
952
                self._analyze_ticker_internal, message=""
953
            )(dataframe, {'pair': pair})
954

955
            self.assert_df(dataframe, df_len, df_close, df_date)
1✔
956
        except StrategyError as error:
1✔
957
            logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
1✔
958
            return
1✔
959

960
        if dataframe.empty:
1✔
961
            logger.warning('Empty dataframe for pair %s', pair)
1✔
962
            return
1✔
963

964
    def analyze(self, pairs: List[str]) -> None:
1✔
965
        """
966
        Analyze all pairs using analyze_pair().
967
        :param pairs: List of pairs to analyze
968
        """
969
        for pair in pairs:
1✔
970
            self.analyze_pair(pair)
1✔
971

972
    @staticmethod
1✔
973
    def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]:
1✔
974
        """ keep some data for dataframes """
975
        return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1]
1✔
976

977
    def assert_df(self, dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime):
1✔
978
        """
979
        Ensure dataframe (length, last candle) was not modified, and has all elements we need.
980
        """
981
        message_template = "Dataframe returned from strategy has mismatching {}."
1✔
982
        message = ""
1✔
983
        if dataframe is None:
1✔
984
            message = "No dataframe returned (return statement missing?)."
1✔
985
        elif 'enter_long' not in dataframe:
1✔
986
            message = "enter_long/buy column not set."
1✔
987
        elif df_len != len(dataframe):
1✔
988
            message = message_template.format("length")
1✔
989
        elif df_close != dataframe["close"].iloc[-1]:
1✔
990
            message = message_template.format("last close price")
1✔
991
        elif df_date != dataframe["date"].iloc[-1]:
1✔
992
            message = message_template.format("last date")
1✔
993
        if message:
1✔
994
            if self.disable_dataframe_checks:
1✔
995
                logger.warning(message)
1✔
996
            else:
997
                raise StrategyError(message)
1✔
998

999
    def get_latest_candle(
1✔
1000
        self,
1001
        pair: str,
1002
        timeframe: str,
1003
        dataframe: DataFrame,
1004
    ) -> Tuple[Optional[DataFrame], Optional[datetime]]:
1005
        """
1006
        Calculates current signal based based on the entry order or exit order
1007
        columns of the dataframe.
1008
        Used by Bot to get the signal to enter, or exit
1009
        :param pair: pair in format ANT/BTC
1010
        :param timeframe: timeframe to use
1011
        :param dataframe: Analyzed dataframe to get signal from.
1012
        :return: (None, None) or (Dataframe, latest_date) - corresponding to the last candle
1013
        """
1014
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
1015
            logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
1✔
1016
            return None, None
1✔
1017

1018
        latest_date = dataframe['date'].max()
1✔
1019
        latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
1✔
1020
        # Explicitly convert to datetime object to ensure the below comparison does not fail
1021
        latest_date = latest_date.to_pydatetime()
1✔
1022

1023
        # Check if dataframe is out of date
1024
        timeframe_minutes = timeframe_to_minutes(timeframe)
1✔
1025
        offset = self.config.get('exchange', {}).get('outdated_offset', 5)
1✔
1026
        if latest_date < (dt_now() - timedelta(minutes=timeframe_minutes * 2 + offset)):
1✔
1027
            logger.warning(
1✔
1028
                'Outdated history for pair %s. Last tick is %s minutes old',
1029
                pair, int((dt_now() - latest_date).total_seconds() // 60)
1030
            )
1031
            return None, None
1✔
1032
        return latest, latest_date
1✔
1033

1034
    def get_exit_signal(
1✔
1035
        self,
1036
        pair: str,
1037
        timeframe: str,
1038
        dataframe: DataFrame,
1039
        is_short: Optional[bool] = None
1040
    ) -> Tuple[bool, bool, Optional[str]]:
1041
        """
1042
        Calculates current exit signal based based on the dataframe
1043
        columns of the dataframe.
1044
        Used by Bot to get the signal to exit.
1045
        depending on is_short, looks at "short" or "long" columns.
1046
        :param pair: pair in format ANT/BTC
1047
        :param timeframe: timeframe to use
1048
        :param dataframe: Analyzed dataframe to get signal from.
1049
        :param is_short: Indicating existing trade direction.
1050
        :return: (enter, exit) A bool-tuple with enter / exit values.
1051
        """
1052
        latest, _latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
1053
        if latest is None:
1✔
1054
            return False, False, None
1✔
1055

1056
        if is_short:
1✔
1057
            enter = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
1058
            exit_ = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
1059

1060
        else:
1061
            enter = latest[SignalType.ENTER_LONG.value] == 1
1✔
1062
            exit_ = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
1063
        exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
1✔
1064
        # Tags can be None, which does not resolve to False.
1065
        exit_tag = exit_tag if isinstance(exit_tag, str) and exit_tag != 'nan' else None
1✔
1066

1067
        logger.debug(f"exit-trigger: {latest['date']} (pair={pair}) "
1✔
1068
                     f"enter={enter} exit={exit_}")
1069

1070
        return enter, exit_, exit_tag
1✔
1071

1072
    def get_entry_signal(
1✔
1073
        self,
1074
        pair: str,
1075
        timeframe: str,
1076
        dataframe: DataFrame,
1077
    ) -> Tuple[Optional[SignalDirection], Optional[str]]:
1078
        """
1079
        Calculates current entry signal based based on the dataframe signals
1080
        columns of the dataframe.
1081
        Used by Bot to get the signal to enter trades.
1082
        :param pair: pair in format ANT/BTC
1083
        :param timeframe: timeframe to use
1084
        :param dataframe: Analyzed dataframe to get signal from.
1085
        :return: (SignalDirection, entry_tag)
1086
        """
1087
        latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
1088
        if latest is None or latest_date is None:
1✔
1089
            return None, None
1✔
1090

1091
        enter_long = latest[SignalType.ENTER_LONG.value] == 1
1✔
1092
        exit_long = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
1093
        enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
1094
        exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
1095

1096
        enter_signal: Optional[SignalDirection] = None
1✔
1097
        enter_tag: Optional[str] = None
1✔
1098
        if enter_long == 1 and not any([exit_long, enter_short]):
1✔
1099
            enter_signal = SignalDirection.LONG
1✔
1100
            enter_tag = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
1101
        if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT
1✔
1102
                and self.can_short
1103
                and enter_short == 1 and not any([exit_short, enter_long])):
1104
            enter_signal = SignalDirection.SHORT
1✔
1105
            enter_tag = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
1106

1107
        enter_tag = enter_tag if isinstance(enter_tag, str) and enter_tag != 'nan' else None
1✔
1108

1109
        timeframe_seconds = timeframe_to_seconds(timeframe)
1✔
1110

1111
        if self.ignore_expired_candle(
1✔
1112
            latest_date=latest_date,
1113
            current_time=dt_now(),
1114
            timeframe_seconds=timeframe_seconds,
1115
            enter=bool(enter_signal)
1116
        ):
1117
            return None, enter_tag
×
1118

1119
        logger.debug(f"entry trigger: {latest['date']} (pair={pair}) "
1✔
1120
                     f"enter={enter_long} enter_tag_value={enter_tag}")
1121
        return enter_signal, enter_tag
1✔
1122

1123
    def ignore_expired_candle(
1✔
1124
        self,
1125
        latest_date: datetime,
1126
        current_time: datetime,
1127
        timeframe_seconds: int,
1128
        enter: bool
1129
    ):
1130
        if self.ignore_buying_expired_candle_after and enter:
1✔
1131
            time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds))
1✔
1132
            return time_delta.total_seconds() > self.ignore_buying_expired_candle_after
1✔
1133
        else:
1134
            return False
1✔
1135

1136
    def should_exit(self, trade: Trade, rate: float, current_time: datetime, *,
1✔
1137
                    enter: bool, exit_: bool,
1138
                    low: Optional[float] = None, high: Optional[float] = None,
1139
                    force_stoploss: float = 0) -> List[ExitCheckTuple]:
1140
        """
1141
        This function evaluates if one of the conditions required to trigger an exit order
1142
        has been reached, which can either be a stop-loss, ROI or exit-signal.
1143
        :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI
1144
        :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI
1145
        :param force_stoploss: Externally provided stoploss
1146
        :return: List of exit reasons - or empty list.
1147
        """
1148
        exits: List[ExitCheckTuple] = []
1✔
1149
        current_rate = rate
1✔
1150
        current_profit = trade.calc_profit_ratio(current_rate)
1✔
1151
        current_profit_best = current_profit
1✔
1152
        if low is not None or high is not None:
1✔
1153
            # Set current rate to high for backtesting ROI exits
1154
            current_rate_best = (low if trade.is_short else high) or rate
1✔
1155
            current_profit_best = trade.calc_profit_ratio(current_rate_best)
1✔
1156

1157
        trade.adjust_min_max_rates(high or current_rate, low or current_rate)
1✔
1158

1159
        stoplossflag = self.ft_stoploss_reached(current_rate=current_rate, trade=trade,
1✔
1160
                                                current_time=current_time,
1161
                                                current_profit=current_profit,
1162
                                                force_stoploss=force_stoploss, low=low, high=high)
1163

1164
        # if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
1165
        roi_reached = (not (enter and self.ignore_roi_if_entry_signal)
1✔
1166
                       and self.min_roi_reached(trade=trade, current_profit=current_profit_best,
1167
                                                current_time=current_time))
1168

1169
        exit_signal = ExitType.NONE
1✔
1170
        custom_reason = ''
1✔
1171

1172
        if self.use_exit_signal:
1✔
1173
            if exit_ and not enter:
1✔
1174
                exit_signal = ExitType.EXIT_SIGNAL
1✔
1175
            else:
1176
                reason_cust = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
1✔
1177
                    pair=trade.pair, trade=trade, current_time=current_time,
1178
                    current_rate=current_rate, current_profit=current_profit)
1179
                if reason_cust:
1✔
1180
                    exit_signal = ExitType.CUSTOM_EXIT
1✔
1181
                    if isinstance(reason_cust, str):
1✔
1182
                        custom_reason = reason_cust
1✔
1183
                        if len(reason_cust) > CUSTOM_TAG_MAX_LENGTH:
1✔
1184
                            logger.warning(f'Custom exit reason returned from '
1✔
1185
                                           f'custom_exit is too long and was trimmed'
1186
                                           f'to {CUSTOM_TAG_MAX_LENGTH} characters.')
1187
                            custom_reason = reason_cust[:CUSTOM_TAG_MAX_LENGTH]
1✔
1188
                    else:
1189
                        custom_reason = ''
1✔
1190
            if (
1✔
1191
                exit_signal == ExitType.CUSTOM_EXIT
1192
                or (exit_signal == ExitType.EXIT_SIGNAL
1193
                    and (not self.exit_profit_only or current_profit > self.exit_profit_offset))
1194
            ):
1195
                logger.debug(f"{trade.pair} - Sell signal received. "
1✔
1196
                             f"exit_type=ExitType.{exit_signal.name}" +
1197
                             (f", custom_reason={custom_reason}" if custom_reason else ""))
1198
                exits.append(ExitCheckTuple(exit_type=exit_signal, exit_reason=custom_reason))
1✔
1199

1200
        # Sequence:
1201
        # Exit-signal
1202
        # Stoploss
1203
        # ROI
1204
        # Trailing stoploss
1205

1206
        if stoplossflag.exit_type in (ExitType.STOP_LOSS, ExitType.LIQUIDATION):
1✔
1207

1208
            logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}")
1✔
1209
            exits.append(stoplossflag)
1✔
1210

1211
        if roi_reached:
1✔
1212
            logger.debug(f"{trade.pair} - Required profit reached. exit_type=ExitType.ROI")
1✔
1213
            exits.append(ExitCheckTuple(exit_type=ExitType.ROI))
1✔
1214

1215
        if stoplossflag.exit_type == ExitType.TRAILING_STOP_LOSS:
1✔
1216

1217
            logger.debug(f"{trade.pair} - Trailing stoploss hit.")
1✔
1218
            exits.append(stoplossflag)
1✔
1219

1220
        return exits
1✔
1221

1222
    def ft_stoploss_adjust(self, current_rate: float, trade: Trade,
1✔
1223
                           current_time: datetime, current_profit: float,
1224
                           force_stoploss: float, low: Optional[float] = None,
1225
                           high: Optional[float] = None, after_fill: bool = False) -> None:
1226
        """
1227
        Adjust stop-loss dynamically if configured to do so.
1228
        :param current_profit: current profit as ratio
1229
        :param low: Low value of this candle, only set in backtesting
1230
        :param high: High value of this candle, only set in backtesting
1231
        """
1232
        if after_fill and not self._ft_stop_uses_after_fill:
1✔
1233
            # Skip if the strategy doesn't support after fill.
1234
            return
1✔
1235

1236
        stop_loss_value = force_stoploss if force_stoploss else self.stoploss
1✔
1237

1238
        # Initiate stoploss with open_rate. Does nothing if stoploss is already set.
1239
        trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
1✔
1240

1241
        dir_correct = (trade.stop_loss < (low or current_rate)
1✔
1242
                       if not trade.is_short else
1243
                       trade.stop_loss > (high or current_rate)
1244
                       )
1245

1246
        # Make sure current_profit is calculated using high for backtesting.
1247
        bound = (low if trade.is_short else high)
1✔
1248
        bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound)
1✔
1249
        if self.use_custom_stoploss and dir_correct:
1✔
1250
            stop_loss_value_custom = strategy_safe_wrapper(
1✔
1251
                self.custom_stoploss, default_retval=None, supress_error=True
1252
                    )(pair=trade.pair, trade=trade,
1253
                        current_time=current_time,
1254
                        current_rate=(bound or current_rate),
1255
                        current_profit=bound_profit,
1256
                        after_fill=after_fill)
1257
            # Sanity check - error cases will return None
1258
            if stop_loss_value_custom:
1✔
1259
                stop_loss_value = stop_loss_value_custom
1✔
1260
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value,
1✔
1261
                                       allow_refresh=after_fill)
1262
            else:
1263
                logger.debug("CustomStoploss function did not return valid stoploss")
1✔
1264

1265
        if self.trailing_stop and dir_correct:
1✔
1266
            # trailing stoploss handling
1267
            sl_offset = self.trailing_stop_positive_offset
1✔
1268
            # Make sure current_profit is calculated using high for backtesting.
1269

1270
            # Don't update stoploss if trailing_only_offset_is_reached is true.
1271
            if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset):
1✔
1272
                # Specific handling for trailing_stop_positive
1273
                if self.trailing_stop_positive is not None and bound_profit > sl_offset:
1✔
1274
                    stop_loss_value = self.trailing_stop_positive
1✔
1275
                    logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
1✔
1276
                                 f"offset: {sl_offset:.4g} profit: {bound_profit:.2%}")
1277

1278
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
1✔
1279

1280
    def ft_stoploss_reached(self, current_rate: float, trade: Trade,
1✔
1281
                            current_time: datetime, current_profit: float,
1282
                            force_stoploss: float, low: Optional[float] = None,
1283
                            high: Optional[float] = None) -> ExitCheckTuple:
1284
        """
1285
        Based on current profit of the trade and configured (trailing) stoploss,
1286
        decides to exit or not
1287
        :param current_profit: current profit as ratio
1288
        :param low: Low value of this candle, only set in backtesting
1289
        :param high: High value of this candle, only set in backtesting
1290
        """
1291
        self.ft_stoploss_adjust(current_rate, trade, current_time, current_profit,
1✔
1292
                                force_stoploss, low, high)
1293

1294
        sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
1✔
1295
        sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
1✔
1296
        liq_higher_long = (trade.liquidation_price
1✔
1297
                           and trade.liquidation_price >= (low or current_rate)
1298
                           and not trade.is_short)
1299
        liq_lower_short = (trade.liquidation_price
1✔
1300
                           and trade.liquidation_price <= (high or current_rate)
1301
                           and trade.is_short)
1302

1303
        # evaluate if the stoploss was hit if stoploss is not on exchange
1304
        # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
1305
        # regular stoploss handling.
1306
        if ((sl_higher_long or sl_lower_short) and
1✔
1307
                (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):
1308

1309
            exit_type = ExitType.STOP_LOSS
1✔
1310

1311
            # If initial stoploss is not the same as current one then it is trailing.
1312
            if trade.is_stop_loss_trailing:
1✔
1313
                exit_type = ExitType.TRAILING_STOP_LOSS
1✔
1314
                logger.debug(
1✔
1315
                    f"{trade.pair} - HIT STOP: current price at "
1316
                    f"{((high if trade.is_short else low) or current_rate):.6f}, "
1317
                    f"stoploss is {trade.stop_loss:.6f}, "
1318
                    f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
1319
                    f"trade opened at {trade.open_rate:.6f}")
1320

1321
            return ExitCheckTuple(exit_type=exit_type)
1✔
1322

1323
        if (liq_higher_long or liq_lower_short):
1✔
1324
            logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
1✔
1325
            return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
1✔
1326

1327
        return ExitCheckTuple(exit_type=ExitType.NONE)
1✔
1328

1329
    def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:
1✔
1330
        """
1331
        Based on trade duration defines the ROI entry that may have been reached.
1332
        :param trade_dur: trade duration in minutes
1333
        :return: minimal ROI entry value or None if none proper ROI entry was found.
1334
        """
1335
        # Get highest entry in ROI dict where key <= trade-duration
1336
        roi_list = [x for x in self.minimal_roi.keys() if x <= trade_dur]
1✔
1337
        if not roi_list:
1✔
1338
            return None, None
1✔
1339
        roi_entry = max(roi_list)
1✔
1340
        return roi_entry, self.minimal_roi[roi_entry]
1✔
1341

1342
    def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
1✔
1343
        """
1344
        Based on trade duration, current profit of the trade and ROI configuration,
1345
        decides whether bot should exit.
1346
        :param current_profit: current profit as ratio
1347
        :return: True if bot should exit at current rate
1348
        """
1349
        # Check if time matches and current rate is above threshold
1350
        trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
1✔
1351
        _, roi = self.min_roi_reached_entry(trade_dur)
1✔
1352
        if roi is None:
1✔
1353
            return False
1✔
1354
        else:
1355
            return current_profit > roi
1✔
1356

1357
    def ft_check_timed_out(self, trade: Trade, order: Order,
1✔
1358
                           current_time: datetime) -> bool:
1359
        """
1360
        FT Internal method.
1361
        Check if timeout is active, and if the order is still open and timed out
1362
        """
1363
        side = 'entry' if order.ft_order_side == trade.entry_side else 'exit'
1✔
1364

1365
        timeout = self.config.get('unfilledtimeout', {}).get(side)
1✔
1366
        if timeout is not None:
1✔
1367
            timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
1✔
1368
            timeout_kwargs = {timeout_unit: -timeout}
1✔
1369
            timeout_threshold = current_time + timedelta(**timeout_kwargs)
1✔
1370
            timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold)
1✔
1371
            if timedout:
1✔
1372
                return True
1✔
1373
        time_method = (self.check_exit_timeout if order.ft_order_side == trade.exit_side
1✔
1374
                       else self.check_entry_timeout)
1375

1376
        return strategy_safe_wrapper(time_method,
1✔
1377
                                     default_retval=False)(
1378
                                        pair=trade.pair, trade=trade, order=order,
1379
                                        current_time=current_time)
1380

1381
    def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
1✔
1382
        """
1383
        Populates indicators for given candle (OHLCV) data (for multiple pairs)
1384
        Does not run advise_entry or advise_exit!
1385
        Used by optimize operations only, not during dry / live runs.
1386
        Using .copy() to get a fresh copy of the dataframe for every strategy run.
1387
        Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show.
1388
        Has positive effects on memory usage for whatever reason - also when
1389
        using only one strategy.
1390
        """
1391
        return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair}).copy()
1✔
1392
                for pair, pair_data in data.items()}
1393

1394
    def ft_advise_signals(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1395
        """
1396
        Call advise_entry and advise_exit and return the resulting dataframe.
1397
        :param dataframe: Dataframe containing data from exchange, as well as pre-calculated
1398
                          indicators
1399
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
1400
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
1401

1402
        """
1403

1404
        dataframe = self.advise_entry(dataframe, metadata)
1✔
1405
        dataframe = self.advise_exit(dataframe, metadata)
1✔
1406
        return dataframe
1✔
1407

1408
    def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1409
        """
1410
        Populate indicators that will be used in the Buy, Sell, short, exit_short strategy
1411
        This method should not be overridden.
1412
        :param dataframe: Dataframe with data from the exchange
1413
        :param metadata: Additional information, like the currently traded pair
1414
        :return: a Dataframe with all mandatory indicators for the strategies
1415
        """
1416
        logger.debug(f"Populating indicators for pair {metadata.get('pair')}.")
1✔
1417

1418
        # call populate_indicators_Nm() which were tagged with @informative decorator.
1419
        for inf_data, populate_fn in self._ft_informative:
1✔
1420
            dataframe = _create_and_merge_informative_pair(
1✔
1421
                self, dataframe, metadata, inf_data, populate_fn)
1422

1423
        return self.populate_indicators(dataframe, metadata)
1✔
1424

1425
    def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1426
        """
1427
        Based on TA indicators, populates the entry order signal for the given dataframe
1428
        This method should not be overridden.
1429
        :param dataframe: DataFrame
1430
        :param metadata: Additional information dictionary, with details like the
1431
            currently traded pair
1432
        :return: DataFrame with buy column
1433
        """
1434

1435
        logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.")
1✔
1436
        # Initialize column to work around Pandas bug #56503.
1437
        dataframe.loc[:, 'enter_tag'] = ''
1✔
1438
        df = self.populate_entry_trend(dataframe, metadata)
1✔
1439
        if 'enter_long' not in df.columns:
1✔
1440
            df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
1✔
1441

1442
        return df
1✔
1443

1444
    def advise_exit(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1445
        """
1446
        Based on TA indicators, populates the exit order signal for the given dataframe
1447
        This method should not be overridden.
1448
        :param dataframe: DataFrame
1449
        :param metadata: Additional information dictionary, with details like the
1450
            currently traded pair
1451
        :return: DataFrame with exit column
1452
        """
1453
        # Initialize column to work around Pandas bug #56503.
1454
        dataframe.loc[:, 'exit_tag'] = ''
1✔
1455
        logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.")
1✔
1456
        df = self.populate_exit_trend(dataframe, metadata)
1✔
1457
        if 'exit_long' not in df.columns:
1✔
1458
            df = df.rename({'sell': 'exit_long'}, axis='columns')
1✔
1459
        return df
1✔
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

© 2025 Coveralls, Inc