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

freqtrade / freqtrade / 6181253459

08 Sep 2023 06:04AM UTC coverage: 94.614% (+0.06%) from 94.556%
6181253459

push

github-actions

web-flow
Merge pull request #9159 from stash86/fix-adjust

remove old codes when we only can do partial entries

2 of 2 new or added lines in 1 file covered. (100.0%)

19114 of 20202 relevant lines covered (94.61%)

0.95 hits per line

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

97.13
/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 custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
1✔
376
                        current_profit: float, after_fill: bool, **kwargs) -> Optional[float]:
377
        """
378
        Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
379
        e.g. returning -0.05 would create a stoploss 5% below current_rate.
380
        The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.
381

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

384
        When not implemented by a strategy, returns the initial stoploss value.
385
        Only called when use_custom_stoploss is set to True.
386

387
        :param pair: Pair that's currently analyzed
388
        :param trade: trade object.
389
        :param current_time: datetime object, containing the current datetime
390
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
391
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
392
        :param after_fill: True if the stoploss is called after the order was filled.
393
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
394
        :return float: New stoploss value, relative to the current_rate
395
        """
396
        return self.stoploss
1✔
397

398
    def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
1✔
399
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
400
        """
401
        Custom entry price logic, returning the new entry price.
402

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

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

407
        :param pair: Pair that's currently analyzed
408
        :param current_time: datetime object, containing the current datetime
409
        :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
410
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
411
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
412
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
413
        :return float: New entry price value if provided
414
        """
415
        return proposed_rate
1✔
416

417
    def custom_exit_price(self, pair: str, trade: Trade,
1✔
418
                          current_time: datetime, proposed_rate: float,
419
                          current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
420
        """
421
        Custom exit price logic, returning the new exit price.
422

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

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

427
        :param pair: Pair that's currently analyzed
428
        :param trade: trade object.
429
        :param current_time: datetime object, containing the current datetime
430
        :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing.
431
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
432
        :param exit_tag: Exit reason.
433
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
434
        :return float: New exit price value if provided
435
        """
436
        return proposed_rate
1✔
437

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

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

450
        Custom exit reason max length is 64. Exceeding characters will be removed.
451

452
        :param pair: Pair that's currently analyzed
453
        :param trade: trade object.
454
        :param current_time: datetime object, containing the current datetime
455
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
456
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
457
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
458
        :return: To execute exit, return a string with custom exit reason or True. Otherwise return
459
        None or False.
460
        """
461
        return None
1✔
462

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

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

474
        Custom exit reason max length is 64. Exceeding characters will be removed.
475

476
        :param pair: Pair that's currently analyzed
477
        :param trade: trade object.
478
        :param current_time: datetime object, containing the current datetime
479
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
480
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
481
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
482
        :return: To execute exit, return a string with custom exit reason or True. Otherwise return
483
        None or False.
484
        """
485
        return self.custom_sell(pair, trade, current_time, current_rate, current_profit, **kwargs)
1✔
486

487
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
1✔
488
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
489
                            leverage: float, entry_tag: Optional[str], side: str,
490
                            **kwargs) -> float:
491
        """
492
        Customize stake size for each new trade.
493

494
        :param pair: Pair that's currently analyzed
495
        :param current_time: datetime object, containing the current datetime
496
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
497
        :param proposed_stake: A stake amount proposed by the bot.
498
        :param min_stake: Minimal stake size allowed by exchange.
499
        :param max_stake: Balance available for trading.
500
        :param leverage: Leverage selected for this trade.
501
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
502
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
503
        :return: A stake size, which is between min_stake and max_stake.
504
        """
505
        return proposed_stake
1✔
506

507
    def adjust_trade_position(self, trade: Trade, current_time: datetime,
1✔
508
                              current_rate: float, current_profit: float,
509
                              min_stake: Optional[float], max_stake: float,
510
                              current_entry_rate: float, current_exit_rate: float,
511
                              current_entry_profit: float, current_exit_profit: float,
512
                              **kwargs) -> Optional[float]:
513
        """
514
        Custom trade adjustment logic, returning the stake amount that a trade should be
515
        increased or decreased.
516
        This means extra buy or sell orders with additional fees.
517
        Only called when `position_adjustment_enable` is set to True.
518

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

521
        When not implemented by a strategy, returns None
522

523
        :param trade: trade object.
524
        :param current_time: datetime object, containing the current datetime
525
        :param current_rate: Current buy rate.
526
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
527
        :param min_stake: Minimal stake size allowed by exchange (for both entries and exits)
528
        :param max_stake: Maximum stake allowed (either through balance, or by exchange limits).
529
        :param current_entry_rate: Current rate using entry pricing.
530
        :param current_exit_rate: Current rate using exit pricing.
531
        :param current_entry_profit: Current profit using entry pricing.
532
        :param current_exit_profit: Current profit using exit pricing.
533
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
534
        :return float: Stake amount to adjust your trade,
535
                       Positive values to increase position, Negative values to decrease position.
536
                       Return None for no action.
537
        """
538
        return None
×
539

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

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

550
        When not implemented by a strategy, returns current_order_rate as default.
551
        If current_order_rate is returned then the existing order is maintained.
552
        If None is returned then order gets canceled but not replaced by a new one.
553

554
        :param pair: Pair that's currently analyzed
555
        :param trade: Trade object.
556
        :param order: Order object
557
        :param current_time: datetime object, containing the current datetime
558
        :param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
559
        :param current_order_rate: Rate of the existing order in place.
560
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
561
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
562
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
563
        :return float: New entry price value if provided
564

565
        """
566
        return current_order_rate
1✔
567

568
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
1✔
569
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
570
                 side: str, **kwargs) -> float:
571
        """
572
        Customize leverage for each new trade. This method is only called in futures mode.
573

574
        :param pair: Pair that's currently analyzed
575
        :param current_time: datetime object, containing the current datetime
576
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
577
        :param proposed_leverage: A leverage proposed by the bot.
578
        :param max_leverage: Max leverage allowed on this pair
579
        :param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
580
        :param side: 'long' or 'short' - indicating the direction of the proposed trade
581
        :return: A leverage amount, which is between 1.0 and max_leverage.
582
        """
583
        return 1.0
1✔
584

585
    def informative_pairs(self) -> ListPairsWithTimeframes:
1✔
586
        """
587
        Define additional, informative pair/interval combinations to be cached from the exchange.
588
        These pair/interval combinations are non-tradable, unless they are part
589
        of the whitelist as well.
590
        For more information, please consult the documentation
591
        :return: List of tuples in the format (pair, interval)
592
            Sample: return [("ETH/USDT", "5m"),
593
                            ("BTC/USDT", "15m"),
594
                            ]
595
        """
596
        return []
1✔
597

598
    def version(self) -> Optional[str]:
1✔
599
        """
600
        Returns version of the strategy.
601
        """
602
        return None
1✔
603

604
    def populate_any_indicators(self, pair: str, df: DataFrame, tf: str,
1✔
605
                                informative: Optional[DataFrame] = None,
606
                                set_generalized_indicators: bool = False) -> DataFrame:
607
        """
608
        DEPRECATED - USE FEATURE ENGINEERING FUNCTIONS INSTEAD
609
        Function designed to automatically generate, name and merge features
610
        from user indicated timeframes in the configuration file. User can add
611
        additional features here, but must follow the naming convention.
612
        This method is *only* used in FreqaiDataKitchen class and therefore
613
        it is only called if FreqAI is active.
614
        :param pair: pair to be used as informative
615
        :param df: strategy dataframe which will receive merges from informatives
616
        :param tf: timeframe of the dataframe which will modify the feature names
617
        :param informative: the dataframe associated with the informative pair
618
        """
619
        return df
×
620

621
    def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
1✔
622
                                       metadata: Dict, **kwargs) -> DataFrame:
623
        """
624
        *Only functional with FreqAI enabled strategies*
625
        This function will automatically expand the defined features on the config defined
626
        `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and
627
        `include_corr_pairs`. In other words, a single feature defined in this function
628
        will automatically expand to a total of
629
        `indicator_periods_candles` * `include_timeframes` * `include_shifted_candles` *
630
        `include_corr_pairs` numbers of features added to the model.
631

632
        All features must be prepended with `%` to be recognized by FreqAI internals.
633

634
        More details on how these config defined parameters accelerate feature engineering
635
        in the documentation at:
636

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

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

641
        :param dataframe: strategy dataframe which will receive the features
642
        :param period: period of the indicator - usage example:
643
        :param metadata: metadata of current pair
644
        dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
645
        """
646
        return dataframe
×
647

648
    def feature_engineering_expand_basic(
1✔
649
            self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
650
        """
651
        *Only functional with FreqAI enabled strategies*
652
        This function will automatically expand the defined features on the config defined
653
        `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
654
        In other words, a single feature defined in this function
655
        will automatically expand to a total of
656
        `include_timeframes` * `include_shifted_candles` * `include_corr_pairs`
657
        numbers of features added to the model.
658

659
        Features defined here will *not* be automatically duplicated on user defined
660
        `indicator_periods_candles`
661

662
        All features must be prepended with `%` to be recognized by FreqAI internals.
663

664
        More details on how these config defined parameters accelerate feature engineering
665
        in the documentation at:
666

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

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

671
        :param dataframe: strategy dataframe which will receive the features
672
        :param metadata: metadata of current pair
673
        dataframe["%-pct-change"] = dataframe["close"].pct_change()
674
        dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
675
        """
676
        return dataframe
×
677

678
    def feature_engineering_standard(
1✔
679
            self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
680
        """
681
        *Only functional with FreqAI enabled strategies*
682
        This optional function will be called once with the dataframe of the base timeframe.
683
        This is the final function to be called, which means that the dataframe entering this
684
        function will contain all the features and columns created by all other
685
        freqai_feature_engineering_* functions.
686

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

691
        All features must be prepended with `%` to be recognized by FreqAI internals.
692

693
        More details about feature engineering available:
694

695
        https://www.freqtrade.io/en/latest/freqai-feature-engineering
696

697
        :param dataframe: strategy dataframe which will receive the features
698
        :param metadata: metadata of current pair
699
        usage example: dataframe["%-day_of_week"] = (dataframe["date"].dt.dayofweek + 1) / 7
700
        """
701
        return dataframe
×
702

703
    def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
1✔
704
        """
705
        *Only functional with FreqAI enabled strategies*
706
        Required function to set the targets for the model.
707
        All targets must be prepended with `&` to be recognized by the FreqAI internals.
708

709
        More details about feature engineering available:
710

711
        https://www.freqtrade.io/en/latest/freqai-feature-engineering
712

713
        :param dataframe: strategy dataframe which will receive the targets
714
        :param metadata: metadata of current pair
715
        usage example: dataframe["&-target"] = dataframe["close"].shift(-1) / dataframe["close"]
716
        """
717
        return dataframe
×
718

719
###
720
# END - Intended to be overridden by strategy
721
###
722

723
    _ft_stop_uses_after_fill = False
1✔
724

725
    def __informative_pairs_freqai(self) -> ListPairsWithTimeframes:
1✔
726
        """
727
        Create informative-pairs needed for FreqAI
728
        """
729
        if self.config.get('freqai', {}).get('enabled', False):
1✔
730
            whitelist_pairs = self.dp.current_whitelist()
1✔
731
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
732
            corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
1✔
733
            informative_pairs = []
1✔
734
            for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
1✔
735
                for pair in set(whitelist_pairs + corr_pairs):
1✔
736
                    informative_pairs.append((pair, tf, candle_type))
1✔
737
            return informative_pairs
1✔
738

739
        return []
1✔
740

741
    def gather_informative_pairs(self) -> ListPairsWithTimeframes:
1✔
742
        """
743
        Internal method which gathers all informative pairs (user or automatically defined).
744
        """
745
        informative_pairs = self.informative_pairs()
1✔
746
        # Compatibility code for 2 tuple informative pairs
747
        informative_pairs = [
1✔
748
            (p[0], p[1], CandleType.from_string(p[2]) if len(
749
                p) > 2 and p[2] != '' else self.config.get('candle_type_def', CandleType.SPOT))
750
            for p in informative_pairs]
751
        for inf_data, _ in self._ft_informative:
1✔
752
            # Get default candle type if not provided explicitly.
753
            candle_type = (inf_data.candle_type if inf_data.candle_type
1✔
754
                           else self.config.get('candle_type_def', CandleType.SPOT))
755
            if inf_data.asset:
1✔
756
                pair_tf = (
1✔
757
                    _format_pair_name(self.config, inf_data.asset),
758
                    inf_data.timeframe,
759
                    candle_type,
760
                )
761
                informative_pairs.append(pair_tf)
1✔
762
            else:
763
                for pair in self.dp.current_whitelist():
1✔
764
                    informative_pairs.append((pair, inf_data.timeframe, candle_type))
1✔
765
        informative_pairs.extend(self.__informative_pairs_freqai())
1✔
766
        return list(set(informative_pairs))
1✔
767

768
    def get_strategy_name(self) -> str:
1✔
769
        """
770
        Returns strategy class name
771
        """
772
        return self.__class__.__name__
1✔
773

774
    def lock_pair(self, pair: str, until: datetime,
1✔
775
                  reason: Optional[str] = None, side: str = '*') -> None:
776
        """
777
        Locks pair until a given timestamp happens.
778
        Locked pairs are not analyzed, and are prevented from opening new trades.
779
        Locks can only count up (allowing users to lock pairs for a longer period of time).
780
        To remove a lock from a pair, use `unlock_pair()`
781
        :param pair: Pair to lock
782
        :param until: datetime in UTC until the pair should be blocked from opening new trades.
783
                Needs to be timezone aware `datetime.now(timezone.utc)`
784
        :param reason: Optional string explaining why the pair was locked.
785
        :param side: Side to check, can be long, short or '*'
786
        """
787
        PairLocks.lock_pair(pair, until, reason, side=side)
1✔
788

789
    def unlock_pair(self, pair: str) -> None:
1✔
790
        """
791
        Unlocks a pair previously locked using lock_pair.
792
        Not used by freqtrade itself, but intended to be used if users lock pairs
793
        manually from within the strategy, to allow an easy way to unlock pairs.
794
        :param pair: Unlock pair to allow trading again
795
        """
796
        PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
1✔
797

798
    def unlock_reason(self, reason: str) -> None:
1✔
799
        """
800
        Unlocks all pairs previously locked using lock_pair with specified reason.
801
        Not used by freqtrade itself, but intended to be used if users lock pairs
802
        manually from within the strategy, to allow an easy way to unlock pairs.
803
        :param reason: Unlock pairs to allow trading again
804
        """
805
        PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
1✔
806

807
    def is_pair_locked(self, pair: str, *, candle_date: Optional[datetime] = None,
1✔
808
                       side: str = '*') -> bool:
809
        """
810
        Checks if a pair is currently locked
811
        The 2nd, optional parameter ensures that locks are applied until the new candle arrives,
812
        and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap
813
        of 2 seconds for an entry order to happen on an old signal.
814
        :param pair: "Pair to check"
815
        :param candle_date: Date of the last candle. Optional, defaults to current date
816
        :param side: Side to check, can be long, short or '*'
817
        :returns: locking state of the pair in question.
818
        """
819

820
        if not candle_date:
1✔
821
            # Simple call ...
822
            return PairLocks.is_pair_locked(pair, side=side)
1✔
823
        else:
824
            lock_time = timeframe_to_next_date(self.timeframe, candle_date)
1✔
825
            return PairLocks.is_pair_locked(pair, lock_time, side=side)
1✔
826

827
    def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
828
        """
829
        Parses the given candle (OHLCV) data and returns a populated DataFrame
830
        add several TA indicators and entry order signal to it
831
        Should only be used in live.
832
        :param dataframe: Dataframe containing data from exchange
833
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
834
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
835
        """
836
        logger.debug("TA Analysis Launched")
1✔
837
        dataframe = self.advise_indicators(dataframe, metadata)
1✔
838
        dataframe = self.advise_entry(dataframe, metadata)
1✔
839
        dataframe = self.advise_exit(dataframe, metadata)
1✔
840
        return dataframe
1✔
841

842
    def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
843
        """
844
        Parses the given candle (OHLCV) data and returns a populated DataFrame
845
        add several TA indicators and buy signal to it
846
        WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set.
847
        :param dataframe: Dataframe containing data from exchange
848
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
849
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
850
        """
851
        pair = str(metadata.get('pair'))
1✔
852

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

858
            # Defs that only make change on new candle data.
859
            dataframe = self.analyze_ticker(dataframe, metadata)
1✔
860

861
            self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
1✔
862

863
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
864
            self.dp._set_cached_df(pair, self.timeframe, dataframe, candle_type=candle_type)
1✔
865
            self.dp._emit_df((pair, self.timeframe, candle_type), dataframe, new_candle)
1✔
866

867
        else:
868
            logger.debug("Skipping TA Analysis for already analyzed candle")
1✔
869
            dataframe = remove_entry_exit_signals(dataframe)
1✔
870

871
        logger.debug("Loop Analysis Launched")
1✔
872

873
        return dataframe
1✔
874

875
    def analyze_pair(self, pair: str) -> None:
1✔
876
        """
877
        Fetch data for this pair from dataprovider and analyze.
878
        Stores the dataframe into the dataprovider.
879
        The analyzed dataframe is then accessible via `dp.get_analyzed_dataframe()`.
880
        :param pair: Pair to analyze.
881
        """
882
        dataframe = self.dp.ohlcv(
1✔
883
            pair, self.timeframe, candle_type=self.config.get('candle_type_def', CandleType.SPOT)
884
        )
885
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
886
            logger.warning('Empty candle (OHLCV) data for pair %s', pair)
1✔
887
            return
1✔
888

889
        try:
1✔
890
            df_len, df_close, df_date = self.preserve_df(dataframe)
1✔
891

892
            dataframe = strategy_safe_wrapper(
1✔
893
                self._analyze_ticker_internal, message=""
894
            )(dataframe, {'pair': pair})
895

896
            self.assert_df(dataframe, df_len, df_close, df_date)
1✔
897
        except StrategyError as error:
1✔
898
            logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
1✔
899
            return
1✔
900

901
        if dataframe.empty:
1✔
902
            logger.warning('Empty dataframe for pair %s', pair)
1✔
903
            return
1✔
904

905
    def analyze(self, pairs: List[str]) -> None:
1✔
906
        """
907
        Analyze all pairs using analyze_pair().
908
        :param pairs: List of pairs to analyze
909
        """
910
        for pair in pairs:
1✔
911
            self.analyze_pair(pair)
1✔
912

913
    @staticmethod
1✔
914
    def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]:
1✔
915
        """ keep some data for dataframes """
916
        return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1]
1✔
917

918
    def assert_df(self, dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime):
1✔
919
        """
920
        Ensure dataframe (length, last candle) was not modified, and has all elements we need.
921
        """
922
        message_template = "Dataframe returned from strategy has mismatching {}."
1✔
923
        message = ""
1✔
924
        if dataframe is None:
1✔
925
            message = "No dataframe returned (return statement missing?)."
1✔
926
        elif 'enter_long' not in dataframe:
1✔
927
            message = "enter_long/buy column not set."
1✔
928
        elif df_len != len(dataframe):
1✔
929
            message = message_template.format("length")
1✔
930
        elif df_close != dataframe["close"].iloc[-1]:
1✔
931
            message = message_template.format("last close price")
1✔
932
        elif df_date != dataframe["date"].iloc[-1]:
1✔
933
            message = message_template.format("last date")
1✔
934
        if message:
1✔
935
            if self.disable_dataframe_checks:
1✔
936
                logger.warning(message)
1✔
937
            else:
938
                raise StrategyError(message)
1✔
939

940
    def get_latest_candle(
1✔
941
        self,
942
        pair: str,
943
        timeframe: str,
944
        dataframe: DataFrame,
945
    ) -> Tuple[Optional[DataFrame], Optional[datetime]]:
946
        """
947
        Calculates current signal based based on the entry order or exit order
948
        columns of the dataframe.
949
        Used by Bot to get the signal to enter, or exit
950
        :param pair: pair in format ANT/BTC
951
        :param timeframe: timeframe to use
952
        :param dataframe: Analyzed dataframe to get signal from.
953
        :return: (None, None) or (Dataframe, latest_date) - corresponding to the last candle
954
        """
955
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
956
            logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
1✔
957
            return None, None
1✔
958

959
        latest_date = dataframe['date'].max()
1✔
960
        latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
1✔
961
        # Explicitly convert to datetime object to ensure the below comparison does not fail
962
        latest_date = latest_date.to_pydatetime()
1✔
963

964
        # Check if dataframe is out of date
965
        timeframe_minutes = timeframe_to_minutes(timeframe)
1✔
966
        offset = self.config.get('exchange', {}).get('outdated_offset', 5)
1✔
967
        if latest_date < (dt_now() - timedelta(minutes=timeframe_minutes * 2 + offset)):
1✔
968
            logger.warning(
1✔
969
                'Outdated history for pair %s. Last tick is %s minutes old',
970
                pair, int((dt_now() - latest_date).total_seconds() // 60)
971
            )
972
            return None, None
1✔
973
        return latest, latest_date
1✔
974

975
    def get_exit_signal(
1✔
976
        self,
977
        pair: str,
978
        timeframe: str,
979
        dataframe: DataFrame,
980
        is_short: Optional[bool] = None
981
    ) -> Tuple[bool, bool, Optional[str]]:
982
        """
983
        Calculates current exit signal based based on the dataframe
984
        columns of the dataframe.
985
        Used by Bot to get the signal to exit.
986
        depending on is_short, looks at "short" or "long" columns.
987
        :param pair: pair in format ANT/BTC
988
        :param timeframe: timeframe to use
989
        :param dataframe: Analyzed dataframe to get signal from.
990
        :param is_short: Indicating existing trade direction.
991
        :return: (enter, exit) A bool-tuple with enter / exit values.
992
        """
993
        latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
994
        if latest is None:
1✔
995
            return False, False, None
×
996

997
        if is_short:
1✔
998
            enter = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
999
            exit_ = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
1000

1001
        else:
1002
            enter = latest[SignalType.ENTER_LONG.value] == 1
1✔
1003
            exit_ = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
1004
        exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
1✔
1005
        # Tags can be None, which does not resolve to False.
1006
        exit_tag = exit_tag if isinstance(exit_tag, str) else None
1✔
1007

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

1011
        return enter, exit_, exit_tag
1✔
1012

1013
    def get_entry_signal(
1✔
1014
        self,
1015
        pair: str,
1016
        timeframe: str,
1017
        dataframe: DataFrame,
1018
    ) -> Tuple[Optional[SignalDirection], Optional[str]]:
1019
        """
1020
        Calculates current entry signal based based on the dataframe signals
1021
        columns of the dataframe.
1022
        Used by Bot to get the signal to enter trades.
1023
        :param pair: pair in format ANT/BTC
1024
        :param timeframe: timeframe to use
1025
        :param dataframe: Analyzed dataframe to get signal from.
1026
        :return: (SignalDirection, entry_tag)
1027
        """
1028
        latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
1029
        if latest is None or latest_date is None:
1✔
1030
            return None, None
1✔
1031

1032
        enter_long = latest[SignalType.ENTER_LONG.value] == 1
1✔
1033
        exit_long = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
1034
        enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
1035
        exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
1036

1037
        enter_signal: Optional[SignalDirection] = None
1✔
1038
        enter_tag_value: Optional[str] = None
1✔
1039
        if enter_long == 1 and not any([exit_long, enter_short]):
1✔
1040
            enter_signal = SignalDirection.LONG
1✔
1041
            enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
1042
        if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT
1✔
1043
                and self.can_short
1044
                and enter_short == 1 and not any([exit_short, enter_long])):
1045
            enter_signal = SignalDirection.SHORT
1✔
1046
            enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
1047

1048
        enter_tag_value = enter_tag_value if isinstance(enter_tag_value, str) else None
1✔
1049

1050
        timeframe_seconds = timeframe_to_seconds(timeframe)
1✔
1051

1052
        if self.ignore_expired_candle(
1✔
1053
            latest_date=latest_date,
1054
            current_time=dt_now(),
1055
            timeframe_seconds=timeframe_seconds,
1056
            enter=bool(enter_signal)
1057
        ):
1058
            return None, enter_tag_value
×
1059

1060
        logger.debug(f"entry trigger: {latest['date']} (pair={pair}) "
1✔
1061
                     f"enter={enter_long} enter_tag_value={enter_tag_value}")
1062
        return enter_signal, enter_tag_value
1✔
1063

1064
    def ignore_expired_candle(
1✔
1065
        self,
1066
        latest_date: datetime,
1067
        current_time: datetime,
1068
        timeframe_seconds: int,
1069
        enter: bool
1070
    ):
1071
        if self.ignore_buying_expired_candle_after and enter:
1✔
1072
            time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds))
1✔
1073
            return time_delta.total_seconds() > self.ignore_buying_expired_candle_after
1✔
1074
        else:
1075
            return False
1✔
1076

1077
    def should_exit(self, trade: Trade, rate: float, current_time: datetime, *,
1✔
1078
                    enter: bool, exit_: bool,
1079
                    low: Optional[float] = None, high: Optional[float] = None,
1080
                    force_stoploss: float = 0) -> List[ExitCheckTuple]:
1081
        """
1082
        This function evaluates if one of the conditions required to trigger an exit order
1083
        has been reached, which can either be a stop-loss, ROI or exit-signal.
1084
        :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI
1085
        :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI
1086
        :param force_stoploss: Externally provided stoploss
1087
        :return: List of exit reasons - or empty list.
1088
        """
1089
        exits: List[ExitCheckTuple] = []
1✔
1090
        current_rate = rate
1✔
1091
        current_profit = trade.calc_profit_ratio(current_rate)
1✔
1092
        current_profit_best = current_profit
1✔
1093
        if low is not None or high is not None:
1✔
1094
            # Set current rate to high for backtesting ROI exits
1095
            current_rate_best = (low if trade.is_short else high) or rate
1✔
1096
            current_profit_best = trade.calc_profit_ratio(current_rate_best)
1✔
1097

1098
        trade.adjust_min_max_rates(high or current_rate, low or current_rate)
1✔
1099

1100
        stoplossflag = self.ft_stoploss_reached(current_rate=current_rate, trade=trade,
1✔
1101
                                                current_time=current_time,
1102
                                                current_profit=current_profit,
1103
                                                force_stoploss=force_stoploss, low=low, high=high)
1104

1105
        # if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
1106
        roi_reached = (not (enter and self.ignore_roi_if_entry_signal)
1✔
1107
                       and self.min_roi_reached(trade=trade, current_profit=current_profit_best,
1108
                                                current_time=current_time))
1109

1110
        exit_signal = ExitType.NONE
1✔
1111
        custom_reason = ''
1✔
1112

1113
        if self.use_exit_signal:
1✔
1114
            if exit_ and not enter:
1✔
1115
                exit_signal = ExitType.EXIT_SIGNAL
1✔
1116
            else:
1117
                reason_cust = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
1✔
1118
                    pair=trade.pair, trade=trade, current_time=current_time,
1119
                    current_rate=current_rate, current_profit=current_profit)
1120
                if reason_cust:
1✔
1121
                    exit_signal = ExitType.CUSTOM_EXIT
1✔
1122
                    if isinstance(reason_cust, str):
1✔
1123
                        custom_reason = reason_cust
1✔
1124
                        if len(reason_cust) > CUSTOM_TAG_MAX_LENGTH:
1✔
1125
                            logger.warning(f'Custom exit reason returned from '
1✔
1126
                                           f'custom_exit is too long and was trimmed'
1127
                                           f'to {CUSTOM_TAG_MAX_LENGTH} characters.')
1128
                            custom_reason = reason_cust[:CUSTOM_TAG_MAX_LENGTH]
1✔
1129
                    else:
1130
                        custom_reason = ''
1✔
1131
            if (
1✔
1132
                exit_signal == ExitType.CUSTOM_EXIT
1133
                or (exit_signal == ExitType.EXIT_SIGNAL
1134
                    and (not self.exit_profit_only or current_profit > self.exit_profit_offset))
1135
            ):
1136
                logger.debug(f"{trade.pair} - Sell signal received. "
1✔
1137
                             f"exit_type=ExitType.{exit_signal.name}" +
1138
                             (f", custom_reason={custom_reason}" if custom_reason else ""))
1139
                exits.append(ExitCheckTuple(exit_type=exit_signal, exit_reason=custom_reason))
1✔
1140

1141
        # Sequence:
1142
        # Exit-signal
1143
        # Stoploss
1144
        # ROI
1145
        # Trailing stoploss
1146

1147
        if stoplossflag.exit_type in (ExitType.STOP_LOSS, ExitType.LIQUIDATION):
1✔
1148

1149
            logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}")
1✔
1150
            exits.append(stoplossflag)
1✔
1151

1152
        if roi_reached:
1✔
1153
            logger.debug(f"{trade.pair} - Required profit reached. exit_type=ExitType.ROI")
1✔
1154
            exits.append(ExitCheckTuple(exit_type=ExitType.ROI))
1✔
1155

1156
        if stoplossflag.exit_type == ExitType.TRAILING_STOP_LOSS:
1✔
1157

1158
            logger.debug(f"{trade.pair} - Trailing stoploss hit.")
1✔
1159
            exits.append(stoplossflag)
1✔
1160

1161
        return exits
1✔
1162

1163
    def ft_stoploss_adjust(self, current_rate: float, trade: Trade,
1✔
1164
                           current_time: datetime, current_profit: float,
1165
                           force_stoploss: float, low: Optional[float] = None,
1166
                           high: Optional[float] = None, after_fill: bool = False) -> None:
1167
        """
1168
        Adjust stop-loss dynamically if configured to do so.
1169
        :param current_profit: current profit as ratio
1170
        :param low: Low value of this candle, only set in backtesting
1171
        :param high: High value of this candle, only set in backtesting
1172
        """
1173
        if after_fill and not self._ft_stop_uses_after_fill:
1✔
1174
            # Skip if the strategy doesn't support after fill.
1175
            return
1✔
1176

1177
        stop_loss_value = force_stoploss if force_stoploss else self.stoploss
1✔
1178

1179
        # Initiate stoploss with open_rate. Does nothing if stoploss is already set.
1180
        trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
1✔
1181

1182
        dir_correct = (trade.stop_loss < (low or current_rate)
1✔
1183
                       if not trade.is_short else
1184
                       trade.stop_loss > (high or current_rate)
1185
                       )
1186

1187
        # Make sure current_profit is calculated using high for backtesting.
1188
        bound = (low if trade.is_short else high)
1✔
1189
        bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound)
1✔
1190
        if self.use_custom_stoploss and dir_correct:
1✔
1191
            stop_loss_value_custom = strategy_safe_wrapper(
1✔
1192
                self.custom_stoploss, default_retval=None, supress_error=True
1193
                    )(pair=trade.pair, trade=trade,
1194
                        current_time=current_time,
1195
                        current_rate=(bound or current_rate),
1196
                        current_profit=bound_profit,
1197
                        after_fill=after_fill)
1198
            # Sanity check - error cases will return None
1199
            if stop_loss_value_custom:
1✔
1200
                stop_loss_value = stop_loss_value_custom
1✔
1201
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value,
1✔
1202
                                       allow_refresh=after_fill)
1203
            else:
1204
                logger.debug("CustomStoploss function did not return valid stoploss")
1✔
1205

1206
        if self.trailing_stop and dir_correct:
1✔
1207
            # trailing stoploss handling
1208
            sl_offset = self.trailing_stop_positive_offset
1✔
1209
            # Make sure current_profit is calculated using high for backtesting.
1210

1211
            # Don't update stoploss if trailing_only_offset_is_reached is true.
1212
            if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset):
1✔
1213
                # Specific handling for trailing_stop_positive
1214
                if self.trailing_stop_positive is not None and bound_profit > sl_offset:
1✔
1215
                    stop_loss_value = self.trailing_stop_positive
1✔
1216
                    logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
1✔
1217
                                 f"offset: {sl_offset:.4g} profit: {bound_profit:.2%}")
1218

1219
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
1✔
1220

1221
    def ft_stoploss_reached(self, current_rate: float, trade: Trade,
1✔
1222
                            current_time: datetime, current_profit: float,
1223
                            force_stoploss: float, low: Optional[float] = None,
1224
                            high: Optional[float] = None) -> ExitCheckTuple:
1225
        """
1226
        Based on current profit of the trade and configured (trailing) stoploss,
1227
        decides to exit or not
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
        self.ft_stoploss_adjust(current_rate, trade, current_time, current_profit,
1✔
1233
                                force_stoploss, low, high)
1234

1235
        sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
1✔
1236
        sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
1✔
1237
        liq_higher_long = (trade.liquidation_price
1✔
1238
                           and trade.liquidation_price >= (low or current_rate)
1239
                           and not trade.is_short)
1240
        liq_lower_short = (trade.liquidation_price
1✔
1241
                           and trade.liquidation_price <= (high or current_rate)
1242
                           and trade.is_short)
1243

1244
        if (liq_higher_long or liq_lower_short):
1✔
1245
            logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
1✔
1246
            return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
1✔
1247

1248
        # evaluate if the stoploss was hit if stoploss is not on exchange
1249
        # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
1250
        # regular stoploss handling.
1251
        if ((sl_higher_long or sl_lower_short) and
1✔
1252
                (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):
1253

1254
            exit_type = ExitType.STOP_LOSS
1✔
1255

1256
            # If initial stoploss is not the same as current one then it is trailing.
1257
            if trade.is_stop_loss_trailing:
1✔
1258
                exit_type = ExitType.TRAILING_STOP_LOSS
1✔
1259
                logger.debug(
1✔
1260
                    f"{trade.pair} - HIT STOP: current price at "
1261
                    f"{((high if trade.is_short else low) or current_rate):.6f}, "
1262
                    f"stoploss is {trade.stop_loss:.6f}, "
1263
                    f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
1264
                    f"trade opened at {trade.open_rate:.6f}")
1265

1266
            return ExitCheckTuple(exit_type=exit_type)
1✔
1267

1268
        return ExitCheckTuple(exit_type=ExitType.NONE)
1✔
1269

1270
    def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:
1✔
1271
        """
1272
        Based on trade duration defines the ROI entry that may have been reached.
1273
        :param trade_dur: trade duration in minutes
1274
        :return: minimal ROI entry value or None if none proper ROI entry was found.
1275
        """
1276
        # Get highest entry in ROI dict where key <= trade-duration
1277
        roi_list = [x for x in self.minimal_roi.keys() if x <= trade_dur]
1✔
1278
        if not roi_list:
1✔
1279
            return None, None
1✔
1280
        roi_entry = max(roi_list)
1✔
1281
        return roi_entry, self.minimal_roi[roi_entry]
1✔
1282

1283
    def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
1✔
1284
        """
1285
        Based on trade duration, current profit of the trade and ROI configuration,
1286
        decides whether bot should exit.
1287
        :param current_profit: current profit as ratio
1288
        :return: True if bot should exit at current rate
1289
        """
1290
        # Check if time matches and current rate is above threshold
1291
        trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
1✔
1292
        _, roi = self.min_roi_reached_entry(trade_dur)
1✔
1293
        if roi is None:
1✔
1294
            return False
1✔
1295
        else:
1296
            return current_profit > roi
1✔
1297

1298
    def ft_check_timed_out(self, trade: Trade, order: Order,
1✔
1299
                           current_time: datetime) -> bool:
1300
        """
1301
        FT Internal method.
1302
        Check if timeout is active, and if the order is still open and timed out
1303
        """
1304
        side = 'entry' if order.ft_order_side == trade.entry_side else 'exit'
1✔
1305

1306
        timeout = self.config.get('unfilledtimeout', {}).get(side)
1✔
1307
        if timeout is not None:
1✔
1308
            timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
1✔
1309
            timeout_kwargs = {timeout_unit: -timeout}
1✔
1310
            timeout_threshold = current_time + timedelta(**timeout_kwargs)
1✔
1311
            timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold)
1✔
1312
            if timedout:
1✔
1313
                return True
1✔
1314
        time_method = (self.check_exit_timeout if order.ft_order_side == trade.exit_side
1✔
1315
                       else self.check_entry_timeout)
1316

1317
        return strategy_safe_wrapper(time_method,
1✔
1318
                                     default_retval=False)(
1319
                                        pair=trade.pair, trade=trade, order=order,
1320
                                        current_time=current_time)
1321

1322
    def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
1✔
1323
        """
1324
        Populates indicators for given candle (OHLCV) data (for multiple pairs)
1325
        Does not run advise_entry or advise_exit!
1326
        Used by optimize operations only, not during dry / live runs.
1327
        Using .copy() to get a fresh copy of the dataframe for every strategy run.
1328
        Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show.
1329
        Has positive effects on memory usage for whatever reason - also when
1330
        using only one strategy.
1331
        """
1332
        return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair}).copy()
1✔
1333
                for pair, pair_data in data.items()}
1334

1335
    def ft_advise_signals(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1336
        """
1337
        Call advise_entry and advise_exit and return the resulting dataframe.
1338
        :param dataframe: Dataframe containing data from exchange, as well as pre-calculated
1339
                          indicators
1340
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
1341
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
1342

1343
        """
1344

1345
        dataframe = self.advise_entry(dataframe, metadata)
1✔
1346
        dataframe = self.advise_exit(dataframe, metadata)
1✔
1347
        return dataframe
1✔
1348

1349
    def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1350
        """
1351
        Populate indicators that will be used in the Buy, Sell, short, exit_short strategy
1352
        This method should not be overridden.
1353
        :param dataframe: Dataframe with data from the exchange
1354
        :param metadata: Additional information, like the currently traded pair
1355
        :return: a Dataframe with all mandatory indicators for the strategies
1356
        """
1357
        logger.debug(f"Populating indicators for pair {metadata.get('pair')}.")
1✔
1358

1359
        # call populate_indicators_Nm() which were tagged with @informative decorator.
1360
        for inf_data, populate_fn in self._ft_informative:
1✔
1361
            dataframe = _create_and_merge_informative_pair(
1✔
1362
                self, dataframe, metadata, inf_data, populate_fn)
1363

1364
        return self.populate_indicators(dataframe, metadata)
1✔
1365

1366
    def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1367
        """
1368
        Based on TA indicators, populates the entry order signal for the given dataframe
1369
        This method should not be overridden.
1370
        :param dataframe: DataFrame
1371
        :param metadata: Additional information dictionary, with details like the
1372
            currently traded pair
1373
        :return: DataFrame with buy column
1374
        """
1375

1376
        logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.")
1✔
1377

1378
        df = self.populate_entry_trend(dataframe, metadata)
1✔
1379
        if 'enter_long' not in df.columns:
1✔
1380
            df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
1✔
1381

1382
        return df
1✔
1383

1384
    def advise_exit(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1385
        """
1386
        Based on TA indicators, populates the exit order signal for the given dataframe
1387
        This method should not be overridden.
1388
        :param dataframe: DataFrame
1389
        :param metadata: Additional information dictionary, with details like the
1390
            currently traded pair
1391
        :return: DataFrame with exit column
1392
        """
1393
        logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.")
1✔
1394
        df = self.populate_exit_trend(dataframe, metadata)
1✔
1395
        if 'exit_long' not in df.columns:
1✔
1396
            df = df.rename({'sell': 'exit_long'}, axis='columns')
1✔
1397
        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