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

freqtrade / freqtrade / 3645510915

pending completion
3645510915

push

github-actions

Matthias
Add leverage to backtest results

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

15955 of 16697 relevant lines covered (95.56%)

0.96 hits per line

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

98.24
/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
import arrow
1✔
11
from pandas import DataFrame
1✔
12

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

28

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

32

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

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

50
    _ft_params_from_file: Dict
1✔
51
    # associated minimal roi
52
    minimal_roi: Dict = {"0": 10.0}
1✔
53

54
    # associated stoploss
55
    stoploss: float
1✔
56

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

64
    # Can this strategy go short?
65
    can_short: bool = False
1✔
66

67
    # associated timeframe
68
    timeframe: str
1✔
69

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

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

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

88
    use_exit_signal: bool
1✔
89
    exit_profit_only: bool
1✔
90
    exit_profit_offset: float
1✔
91
    ignore_roi_if_entry_signal: bool
1✔
92

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

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

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

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

106
    # Protections
107
    protections: List = []
1✔
108

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

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

122
    def __init__(self, config: Config) -> None:
1✔
123
        self.config = config
1✔
124
        # Dict to determine if analysis is necessary
125
        self._last_candle_seen_per_pair: Dict[str, datetime] = {}
1✔
126
        super().__init__(config)
1✔
127

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

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

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

172
                def shutdown(self, *args, **kwargs):
1✔
173
                    pass
1✔
174

175
            self.freqai = DummyClass()  # type: ignore
1✔
176

177
    def ft_bot_start(self, **kwargs) -> None:
1✔
178
        """
179
        Strategy init - runs after dataprovider has been added.
180
        Must call bot_start()
181
        """
182
        self.load_freqAI_model()
1✔
183

184
        strategy_safe_wrapper(self.bot_start)()
1✔
185

186
        self.ft_load_hyper_params(self.config.get('runmode') == RunMode.HYPEROPT)
1✔
187

188
    def ft_bot_cleanup(self) -> None:
1✔
189
        """
190
        Clean up FreqAI and child threads
191
        """
192
        self.freqai.shutdown()
1✔
193

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

204
    def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
205
        """
206
        DEPRECATED - please migrate to populate_entry_trend
207
        :param dataframe: DataFrame
208
        :param metadata: Additional information, like the currently traded pair
209
        :return: DataFrame with buy column
210
        """
211
        return dataframe
1✔
212

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

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

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

241
    def bot_start(self, **kwargs) -> None:
1✔
242
        """
243
        Called only once after bot instantiation.
244
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
245
        """
246
        pass
1✔
247

248
    def bot_loop_start(self, **kwargs) -> None:
1✔
249
        """
250
        Called at the start of the bot iteration (one loop).
251
        Might be used to perform pair-independent tasks
252
        (e.g. gather some remote resource for comparison)
253
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
254
        """
255
        pass
1✔
256

257
    def check_buy_timeout(self, pair: str, trade: Trade, order: Order,
1✔
258
                          current_time: datetime, **kwargs) -> bool:
259
        """
260
        DEPRECATED: Please use `check_entry_timeout` instead.
261
        """
262
        return False
1✔
263

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

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

285
    def check_sell_timeout(self, pair: str, trade: Trade, order: Order,
1✔
286
                           current_time: datetime, **kwargs) -> bool:
287
        """
288
        DEPRECATED: Please use `check_exit_timeout` instead.
289
        """
290
        return False
1✔
291

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

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

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

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

323
        When not implemented by a strategy, returns True (always confirming).
324

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

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

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

350
        When not implemented by a strategy, returns True (always confirming).
351

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

369
    def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
1✔
370
                        current_profit: float, **kwargs) -> float:
371
        """
372
        Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
373
        e.g. returning -0.05 would create a stoploss 5% below current_rate.
374
        The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.
375

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

378
        When not implemented by a strategy, returns the initial stoploss value
379
        Only called when use_custom_stoploss is set to True.
380

381
        :param pair: Pair that's currently analyzed
382
        :param trade: trade object.
383
        :param current_time: datetime object, containing the current datetime
384
        :param current_rate: Rate, calculated based on pricing settings in exit_pricing.
385
        :param current_profit: Current profit (as ratio), calculated based on current_rate.
386
        :param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
387
        :return float: New stoploss value, relative to the current_rate
388
        """
389
        return self.stoploss
1✔
390

391
    def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float,
1✔
392
                           entry_tag: Optional[str], side: str, **kwargs) -> float:
393
        """
394
        Custom entry price logic, returning the new entry price.
395

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

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

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

410
    def custom_exit_price(self, pair: str, trade: Trade,
1✔
411
                          current_time: datetime, proposed_rate: float,
412
                          current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
413
        """
414
        Custom exit price logic, returning the new exit price.
415

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

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

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

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

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

443
        Custom exit reason max length is 64. Exceeding characters will be removed.
444

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

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

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

467
        Custom exit reason max length is 64. Exceeding characters will be removed.
468

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

480
    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
1✔
481
                            proposed_stake: float, min_stake: Optional[float], max_stake: float,
482
                            leverage: float, entry_tag: Optional[str], side: str,
483
                            **kwargs) -> float:
484
        """
485
        Customize stake size for each new trade.
486

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

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

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

514
        When not implemented by a strategy, returns None
515

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

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

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

543
        When not implemented by a strategy, returns current_order_rate as default.
544
        If current_order_rate is returned then the existing order is maintained.
545
        If None is returned then order gets canceled but not replaced by a new one.
546

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

558
        """
559
        return current_order_rate
1✔
560

561
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
1✔
562
                 proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
563
                 side: str, **kwargs) -> float:
564
        """
565
        Customize leverage for each new trade. This method is only called in futures mode.
566

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

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

591
    def version(self) -> Optional[str]:
1✔
592
        """
593
        Returns version of the strategy.
594
        """
595
        return None
1✔
596

597
    def populate_any_indicators(self, pair: str, df: DataFrame, tf: str,
1✔
598
                                informative: DataFrame = None,
599
                                set_generalized_indicators: bool = False) -> DataFrame:
600
        """
601
        Function designed to automatically generate, name and merge features
602
        from user indicated timeframes in the configuration file. User can add
603
        additional features here, but must follow the naming convention.
604
        This method is *only* used in FreqaiDataKitchen class and therefore
605
        it is only called if FreqAI is active.
606
        :param pair: pair to be used as informative
607
        :param df: strategy dataframe which will receive merges from informatives
608
        :param tf: timeframe of the dataframe which will modify the feature names
609
        :param informative: the dataframe associated with the informative pair
610
        """
611
        return df
1✔
612

613
###
614
# END - Intended to be overridden by strategy
615
###
616

617
    def __informative_pairs_freqai(self) -> ListPairsWithTimeframes:
1✔
618
        """
619
        Create informative-pairs needed for FreqAI
620
        """
621
        if self.config.get('freqai', {}).get('enabled', False):
1✔
622
            whitelist_pairs = self.dp.current_whitelist()
1✔
623
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
624
            corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
1✔
625
            informative_pairs = []
1✔
626
            for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
1✔
627
                for pair in set(whitelist_pairs + corr_pairs):
1✔
628
                    informative_pairs.append((pair, tf, candle_type))
1✔
629
            return informative_pairs
1✔
630

631
        return []
1✔
632

633
    def gather_informative_pairs(self) -> ListPairsWithTimeframes:
1✔
634
        """
635
        Internal method which gathers all informative pairs (user or automatically defined).
636
        """
637
        informative_pairs = self.informative_pairs()
1✔
638
        # Compatibility code for 2 tuple informative pairs
639
        informative_pairs = [
1✔
640
            (p[0], p[1], CandleType.from_string(p[2]) if len(
641
                p) > 2 and p[2] != '' else self.config.get('candle_type_def', CandleType.SPOT))
642
            for p in informative_pairs]
643
        for inf_data, _ in self._ft_informative:
1✔
644
            # Get default candle type if not provided explicitly.
645
            candle_type = (inf_data.candle_type if inf_data.candle_type
1✔
646
                           else self.config.get('candle_type_def', CandleType.SPOT))
647
            if inf_data.asset:
1✔
648
                pair_tf = (
1✔
649
                    _format_pair_name(self.config, inf_data.asset),
650
                    inf_data.timeframe,
651
                    candle_type,
652
                )
653
                informative_pairs.append(pair_tf)
1✔
654
            else:
655
                for pair in self.dp.current_whitelist():
1✔
656
                    informative_pairs.append((pair, inf_data.timeframe, candle_type))
1✔
657
        informative_pairs.extend(self.__informative_pairs_freqai())
1✔
658
        return list(set(informative_pairs))
1✔
659

660
    def get_strategy_name(self) -> str:
1✔
661
        """
662
        Returns strategy class name
663
        """
664
        return self.__class__.__name__
1✔
665

666
    def lock_pair(self, pair: str, until: datetime, reason: str = None, side: str = '*') -> None:
1✔
667
        """
668
        Locks pair until a given timestamp happens.
669
        Locked pairs are not analyzed, and are prevented from opening new trades.
670
        Locks can only count up (allowing users to lock pairs for a longer period of time).
671
        To remove a lock from a pair, use `unlock_pair()`
672
        :param pair: Pair to lock
673
        :param until: datetime in UTC until the pair should be blocked from opening new trades.
674
                Needs to be timezone aware `datetime.now(timezone.utc)`
675
        :param reason: Optional string explaining why the pair was locked.
676
        :param side: Side to check, can be long, short or '*'
677
        """
678
        PairLocks.lock_pair(pair, until, reason, side=side)
1✔
679

680
    def unlock_pair(self, pair: str) -> None:
1✔
681
        """
682
        Unlocks a pair previously locked using lock_pair.
683
        Not used by freqtrade itself, but intended to be used if users lock pairs
684
        manually from within the strategy, to allow an easy way to unlock pairs.
685
        :param pair: Unlock pair to allow trading again
686
        """
687
        PairLocks.unlock_pair(pair, datetime.now(timezone.utc))
1✔
688

689
    def unlock_reason(self, reason: str) -> None:
1✔
690
        """
691
        Unlocks all pairs previously locked using lock_pair with specified reason.
692
        Not used by freqtrade itself, but intended to be used if users lock pairs
693
        manually from within the strategy, to allow an easy way to unlock pairs.
694
        :param reason: Unlock pairs to allow trading again
695
        """
696
        PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
1✔
697

698
    def is_pair_locked(self, pair: str, *, candle_date: datetime = None, side: str = '*') -> bool:
1✔
699
        """
700
        Checks if a pair is currently locked
701
        The 2nd, optional parameter ensures that locks are applied until the new candle arrives,
702
        and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap
703
        of 2 seconds for an entry order to happen on an old signal.
704
        :param pair: "Pair to check"
705
        :param candle_date: Date of the last candle. Optional, defaults to current date
706
        :param side: Side to check, can be long, short or '*'
707
        :returns: locking state of the pair in question.
708
        """
709

710
        if not candle_date:
1✔
711
            # Simple call ...
712
            return PairLocks.is_pair_locked(pair, side=side)
1✔
713
        else:
714
            lock_time = timeframe_to_next_date(self.timeframe, candle_date)
1✔
715
            return PairLocks.is_pair_locked(pair, lock_time, side=side)
1✔
716

717
    def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
718
        """
719
        Parses the given candle (OHLCV) data and returns a populated DataFrame
720
        add several TA indicators and entry order signal to it
721
        :param dataframe: Dataframe containing data from exchange
722
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
723
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
724
        """
725
        logger.debug("TA Analysis Launched")
1✔
726
        dataframe = self.advise_indicators(dataframe, metadata)
1✔
727
        dataframe = self.advise_entry(dataframe, metadata)
1✔
728
        dataframe = self.advise_exit(dataframe, metadata)
1✔
729
        return dataframe
1✔
730

731
    def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
732
        """
733
        Parses the given candle (OHLCV) data and returns a populated DataFrame
734
        add several TA indicators and buy signal to it
735
        WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set.
736
        :param dataframe: Dataframe containing data from exchange
737
        :param metadata: Metadata dictionary with additional data (e.g. 'pair')
738
        :return: DataFrame of candle (OHLCV) data with indicator data and signals added
739
        """
740
        pair = str(metadata.get('pair'))
1✔
741

742
        # Test if seen this pair and last candle before.
743
        # always run if process_only_new_candles is set to false
744
        if (not self.process_only_new_candles or
1✔
745
                self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']):
746

747
            # Defs that only make change on new candle data.
748
            dataframe = self.analyze_ticker(dataframe, metadata)
1✔
749

750
            self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
1✔
751

752
            candle_type = self.config.get('candle_type_def', CandleType.SPOT)
1✔
753
            self.dp._set_cached_df(pair, self.timeframe, dataframe, candle_type=candle_type)
1✔
754
            self.dp._emit_df((pair, self.timeframe, candle_type), dataframe)
1✔
755

756
        else:
757
            logger.debug("Skipping TA Analysis for already analyzed candle")
1✔
758
            dataframe = remove_entry_exit_signals(dataframe)
1✔
759

760
        logger.debug("Loop Analysis Launched")
1✔
761

762
        return dataframe
1✔
763

764
    def analyze_pair(self, pair: str) -> None:
1✔
765
        """
766
        Fetch data for this pair from dataprovider and analyze.
767
        Stores the dataframe into the dataprovider.
768
        The analyzed dataframe is then accessible via `dp.get_analyzed_dataframe()`.
769
        :param pair: Pair to analyze.
770
        """
771
        dataframe = self.dp.ohlcv(
1✔
772
            pair, self.timeframe, candle_type=self.config.get('candle_type_def', CandleType.SPOT)
773
        )
774
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
775
            logger.warning('Empty candle (OHLCV) data for pair %s', pair)
1✔
776
            return
1✔
777

778
        try:
1✔
779
            df_len, df_close, df_date = self.preserve_df(dataframe)
1✔
780

781
            dataframe = strategy_safe_wrapper(
1✔
782
                self._analyze_ticker_internal, message=""
783
            )(dataframe, {'pair': pair})
784

785
            self.assert_df(dataframe, df_len, df_close, df_date)
1✔
786
        except StrategyError as error:
1✔
787
            logger.warning(f"Unable to analyze candle (OHLCV) data for pair {pair}: {error}")
1✔
788
            return
1✔
789

790
        if dataframe.empty:
1✔
791
            logger.warning('Empty dataframe for pair %s', pair)
1✔
792
            return
1✔
793

794
    def analyze(self, pairs: List[str]) -> None:
1✔
795
        """
796
        Analyze all pairs using analyze_pair().
797
        :param pairs: List of pairs to analyze
798
        """
799
        for pair in pairs:
1✔
800
            self.analyze_pair(pair)
1✔
801

802
    @staticmethod
1✔
803
    def preserve_df(dataframe: DataFrame) -> Tuple[int, float, datetime]:
1✔
804
        """ keep some data for dataframes """
805
        return len(dataframe), dataframe["close"].iloc[-1], dataframe["date"].iloc[-1]
1✔
806

807
    def assert_df(self, dataframe: DataFrame, df_len: int, df_close: float, df_date: datetime):
1✔
808
        """
809
        Ensure dataframe (length, last candle) was not modified, and has all elements we need.
810
        """
811
        message_template = "Dataframe returned from strategy has mismatching {}."
1✔
812
        message = ""
1✔
813
        if dataframe is None:
1✔
814
            message = "No dataframe returned (return statement missing?)."
1✔
815
        elif 'enter_long' not in dataframe:
1✔
816
            message = "enter_long/buy column not set."
1✔
817
        elif df_len != len(dataframe):
1✔
818
            message = message_template.format("length")
1✔
819
        elif df_close != dataframe["close"].iloc[-1]:
1✔
820
            message = message_template.format("last close price")
1✔
821
        elif df_date != dataframe["date"].iloc[-1]:
1✔
822
            message = message_template.format("last date")
1✔
823
        if message:
1✔
824
            if self.disable_dataframe_checks:
1✔
825
                logger.warning(message)
1✔
826
            else:
827
                raise StrategyError(message)
1✔
828

829
    def get_latest_candle(
1✔
830
        self,
831
        pair: str,
832
        timeframe: str,
833
        dataframe: DataFrame,
834
    ) -> Tuple[Optional[DataFrame], Optional[arrow.Arrow]]:
835
        """
836
        Calculates current signal based based on the entry order or exit order
837
        columns of the dataframe.
838
        Used by Bot to get the signal to enter, or exit
839
        :param pair: pair in format ANT/BTC
840
        :param timeframe: timeframe to use
841
        :param dataframe: Analyzed dataframe to get signal from.
842
        :return: (None, None) or (Dataframe, latest_date) - corresponding to the last candle
843
        """
844
        if not isinstance(dataframe, DataFrame) or dataframe.empty:
1✔
845
            logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
1✔
846
            return None, None
1✔
847

848
        latest_date = dataframe['date'].max()
1✔
849
        latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
1✔
850
        # Explicitly convert to arrow object to ensure the below comparison does not fail
851
        latest_date = arrow.get(latest_date)
1✔
852

853
        # Check if dataframe is out of date
854
        timeframe_minutes = timeframe_to_minutes(timeframe)
1✔
855
        offset = self.config.get('exchange', {}).get('outdated_offset', 5)
1✔
856
        if latest_date < (arrow.utcnow().shift(minutes=-(timeframe_minutes * 2 + offset))):
1✔
857
            logger.warning(
1✔
858
                'Outdated history for pair %s. Last tick is %s minutes old',
859
                pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
860
            )
861
            return None, None
1✔
862
        return latest, latest_date
1✔
863

864
    def get_exit_signal(
1✔
865
        self,
866
        pair: str,
867
        timeframe: str,
868
        dataframe: DataFrame,
869
        is_short: bool = None
870
    ) -> Tuple[bool, bool, Optional[str]]:
871
        """
872
        Calculates current exit signal based based on the dataframe
873
        columns of the dataframe.
874
        Used by Bot to get the signal to exit.
875
        depending on is_short, looks at "short" or "long" columns.
876
        :param pair: pair in format ANT/BTC
877
        :param timeframe: timeframe to use
878
        :param dataframe: Analyzed dataframe to get signal from.
879
        :param is_short: Indicating existing trade direction.
880
        :return: (enter, exit) A bool-tuple with enter / exit values.
881
        """
882
        latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
883
        if latest is None:
1✔
884
            return False, False, None
×
885

886
        if is_short:
1✔
887
            enter = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
888
            exit_ = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
889

890
        else:
891
            enter = latest[SignalType.ENTER_LONG.value] == 1
1✔
892
            exit_ = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
893
        exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
1✔
894
        # Tags can be None, which does not resolve to False.
895
        exit_tag = exit_tag if isinstance(exit_tag, str) else None
1✔
896

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

900
        return enter, exit_, exit_tag
1✔
901

902
    def get_entry_signal(
1✔
903
        self,
904
        pair: str,
905
        timeframe: str,
906
        dataframe: DataFrame,
907
    ) -> Tuple[Optional[SignalDirection], Optional[str]]:
908
        """
909
        Calculates current entry signal based based on the dataframe signals
910
        columns of the dataframe.
911
        Used by Bot to get the signal to enter trades.
912
        :param pair: pair in format ANT/BTC
913
        :param timeframe: timeframe to use
914
        :param dataframe: Analyzed dataframe to get signal from.
915
        :return: (SignalDirection, entry_tag)
916
        """
917
        latest, latest_date = self.get_latest_candle(pair, timeframe, dataframe)
1✔
918
        if latest is None or latest_date is None:
1✔
919
            return None, None
1✔
920

921
        enter_long = latest[SignalType.ENTER_LONG.value] == 1
1✔
922
        exit_long = latest.get(SignalType.EXIT_LONG.value, 0) == 1
1✔
923
        enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
1✔
924
        exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
1✔
925

926
        enter_signal: Optional[SignalDirection] = None
1✔
927
        enter_tag_value: Optional[str] = None
1✔
928
        if enter_long == 1 and not any([exit_long, enter_short]):
1✔
929
            enter_signal = SignalDirection.LONG
1✔
930
            enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
931
        if (self.config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT
1✔
932
                and self.can_short
933
                and enter_short == 1 and not any([exit_short, enter_long])):
934
            enter_signal = SignalDirection.SHORT
1✔
935
            enter_tag_value = latest.get(SignalTagType.ENTER_TAG.value, None)
1✔
936

937
        enter_tag_value = enter_tag_value if isinstance(enter_tag_value, str) else None
1✔
938

939
        timeframe_seconds = timeframe_to_seconds(timeframe)
1✔
940

941
        if self.ignore_expired_candle(
1✔
942
            latest_date=latest_date.datetime,
943
            current_time=datetime.now(timezone.utc),
944
            timeframe_seconds=timeframe_seconds,
945
            enter=bool(enter_signal)
946
        ):
947
            return None, enter_tag_value
×
948

949
        logger.debug(f"entry trigger: {latest['date']} (pair={pair}) "
1✔
950
                     f"enter={enter_long} enter_tag_value={enter_tag_value}")
951
        return enter_signal, enter_tag_value
1✔
952

953
    def ignore_expired_candle(
1✔
954
        self,
955
        latest_date: datetime,
956
        current_time: datetime,
957
        timeframe_seconds: int,
958
        enter: bool
959
    ):
960
        if self.ignore_buying_expired_candle_after and enter:
1✔
961
            time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds))
1✔
962
            return time_delta.total_seconds() > self.ignore_buying_expired_candle_after
1✔
963
        else:
964
            return False
1✔
965

966
    def should_exit(self, trade: Trade, rate: float, current_time: datetime, *,
1✔
967
                    enter: bool, exit_: bool,
968
                    low: float = None, high: float = None,
969
                    force_stoploss: float = 0) -> List[ExitCheckTuple]:
970
        """
971
        This function evaluates if one of the conditions required to trigger an exit order
972
        has been reached, which can either be a stop-loss, ROI or exit-signal.
973
        :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI
974
        :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI
975
        :param force_stoploss: Externally provided stoploss
976
        :return: List of exit reasons - or empty list.
977
        """
978
        exits: List[ExitCheckTuple] = []
1✔
979
        current_rate = rate
1✔
980
        current_profit = trade.calc_profit_ratio(current_rate)
1✔
981

982
        trade.adjust_min_max_rates(high or current_rate, low or current_rate)
1✔
983

984
        stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
1✔
985
                                              current_time=current_time,
986
                                              current_profit=current_profit,
987
                                              force_stoploss=force_stoploss, low=low, high=high)
988

989
        # Set current rate to high for backtesting exits
990
        current_rate = (low if trade.is_short else high) or rate
1✔
991
        current_profit = trade.calc_profit_ratio(current_rate)
1✔
992

993
        # if enter signal and ignore_roi is set, we don't need to evaluate min_roi.
994
        roi_reached = (not (enter and self.ignore_roi_if_entry_signal)
1✔
995
                       and self.min_roi_reached(trade=trade, current_profit=current_profit,
996
                                                current_time=current_time))
997

998
        exit_signal = ExitType.NONE
1✔
999
        custom_reason = ''
1✔
1000
        # use provided rate in backtesting, not high/low.
1001
        current_rate = rate
1✔
1002
        current_profit = trade.calc_profit_ratio(current_rate)
1✔
1003

1004
        if self.use_exit_signal:
1✔
1005
            if exit_ and not enter:
1✔
1006
                exit_signal = ExitType.EXIT_SIGNAL
1✔
1007
            else:
1008
                reason_cust = strategy_safe_wrapper(self.custom_exit, default_retval=False)(
1✔
1009
                    pair=trade.pair, trade=trade, current_time=current_time,
1010
                    current_rate=current_rate, current_profit=current_profit)
1011
                if reason_cust:
1✔
1012
                    exit_signal = ExitType.CUSTOM_EXIT
1✔
1013
                    if isinstance(reason_cust, str):
1✔
1014
                        custom_reason = reason_cust
1✔
1015
                        if len(reason_cust) > CUSTOM_EXIT_MAX_LENGTH:
1✔
1016
                            logger.warning(f'Custom exit reason returned from '
1✔
1017
                                           f'custom_exit is too long and was trimmed'
1018
                                           f'to {CUSTOM_EXIT_MAX_LENGTH} characters.')
1019
                            custom_reason = reason_cust[:CUSTOM_EXIT_MAX_LENGTH]
1✔
1020
                    else:
1021
                        custom_reason = ''
1✔
1022
            if (
1✔
1023
                exit_signal == ExitType.CUSTOM_EXIT
1024
                or (exit_signal == ExitType.EXIT_SIGNAL
1025
                    and (not self.exit_profit_only or current_profit > self.exit_profit_offset))
1026
            ):
1027
                logger.debug(f"{trade.pair} - Sell signal received. "
1✔
1028
                             f"exit_type=ExitType.{exit_signal.name}" +
1029
                             (f", custom_reason={custom_reason}" if custom_reason else ""))
1030
                exits.append(ExitCheckTuple(exit_type=exit_signal, exit_reason=custom_reason))
1✔
1031

1032
        # Sequence:
1033
        # Exit-signal
1034
        # Stoploss
1035
        # ROI
1036
        # Trailing stoploss
1037

1038
        if stoplossflag.exit_type in (ExitType.STOP_LOSS, ExitType.LIQUIDATION):
1✔
1039

1040
            logger.debug(f"{trade.pair} - Stoploss hit. exit_type={stoplossflag.exit_type}")
1✔
1041
            exits.append(stoplossflag)
1✔
1042

1043
        if roi_reached:
1✔
1044
            logger.debug(f"{trade.pair} - Required profit reached. exit_type=ExitType.ROI")
1✔
1045
            exits.append(ExitCheckTuple(exit_type=ExitType.ROI))
1✔
1046

1047
        if stoplossflag.exit_type == ExitType.TRAILING_STOP_LOSS:
1✔
1048

1049
            logger.debug(f"{trade.pair} - Trailing stoploss hit.")
1✔
1050
            exits.append(stoplossflag)
1✔
1051

1052
        return exits
1✔
1053

1054
    def stop_loss_reached(self, current_rate: float, trade: Trade,
1✔
1055
                          current_time: datetime, current_profit: float,
1056
                          force_stoploss: float, low: float = None,
1057
                          high: float = None) -> ExitCheckTuple:
1058
        """
1059
        Based on current profit of the trade and configured (trailing) stoploss,
1060
        decides to exit or not
1061
        :param current_profit: current profit as ratio
1062
        :param low: Low value of this candle, only set in backtesting
1063
        :param high: High value of this candle, only set in backtesting
1064
        """
1065
        stop_loss_value = force_stoploss if force_stoploss else self.stoploss
1✔
1066

1067
        # Initiate stoploss with open_rate. Does nothing if stoploss is already set.
1068
        trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
1✔
1069

1070
        dir_correct = (trade.stop_loss < (low or current_rate)
1✔
1071
                       if not trade.is_short else
1072
                       trade.stop_loss > (high or current_rate)
1073
                       )
1074

1075
        # Make sure current_profit is calculated using high for backtesting.
1076
        bound = (low if trade.is_short else high)
1✔
1077
        bound_profit = current_profit if not bound else trade.calc_profit_ratio(bound)
1✔
1078
        if self.use_custom_stoploss and dir_correct:
1✔
1079
            stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
1✔
1080
                                                    )(pair=trade.pair, trade=trade,
1081
                                                      current_time=current_time,
1082
                                                      current_rate=(bound or current_rate),
1083
                                                      current_profit=bound_profit)
1084
            # Sanity check - error cases will return None
1085
            if stop_loss_value:
1✔
1086
                # logger.info(f"{trade.pair} {stop_loss_value=} {bound_profit=}")
1087
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
1✔
1088
            else:
1089
                logger.warning("CustomStoploss function did not return valid stoploss")
1✔
1090

1091
        if self.trailing_stop and dir_correct:
1✔
1092
            # trailing stoploss handling
1093
            sl_offset = self.trailing_stop_positive_offset
1✔
1094
            # Make sure current_profit is calculated using high for backtesting.
1095

1096
            # Don't update stoploss if trailing_only_offset_is_reached is true.
1097
            if not (self.trailing_only_offset_is_reached and bound_profit < sl_offset):
1✔
1098
                # Specific handling for trailing_stop_positive
1099
                if self.trailing_stop_positive is not None and bound_profit > sl_offset:
1✔
1100
                    stop_loss_value = self.trailing_stop_positive
1✔
1101
                    logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
1✔
1102
                                 f"offset: {sl_offset:.4g} profit: {bound_profit:.2%}")
1103

1104
                trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
1✔
1105

1106
        sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
1✔
1107
        sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
1✔
1108
        liq_higher_long = (trade.liquidation_price
1✔
1109
                           and trade.liquidation_price >= (low or current_rate)
1110
                           and not trade.is_short)
1111
        liq_lower_short = (trade.liquidation_price
1✔
1112
                           and trade.liquidation_price <= (high or current_rate)
1113
                           and trade.is_short)
1114

1115
        if (liq_higher_long or liq_lower_short):
1✔
1116
            logger.debug(f"{trade.pair} - Liquidation price hit. exit_type=ExitType.LIQUIDATION")
1✔
1117
            return ExitCheckTuple(exit_type=ExitType.LIQUIDATION)
1✔
1118

1119
        # evaluate if the stoploss was hit if stoploss is not on exchange
1120
        # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
1121
        # regular stoploss handling.
1122
        if ((sl_higher_long or sl_lower_short) and
1✔
1123
                (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):
1124

1125
            exit_type = ExitType.STOP_LOSS
1✔
1126

1127
            # If initial stoploss is not the same as current one then it is trailing.
1128
            if trade.initial_stop_loss != trade.stop_loss:
1✔
1129
                exit_type = ExitType.TRAILING_STOP_LOSS
1✔
1130
                logger.debug(
1✔
1131
                    f"{trade.pair} - HIT STOP: current price at "
1132
                    f"{((high if trade.is_short else low) or current_rate):.6f}, "
1133
                    f"stoploss is {trade.stop_loss:.6f}, "
1134
                    f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
1135
                    f"trade opened at {trade.open_rate:.6f}")
1136

1137
            return ExitCheckTuple(exit_type=exit_type)
1✔
1138

1139
        return ExitCheckTuple(exit_type=ExitType.NONE)
1✔
1140

1141
    def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:
1✔
1142
        """
1143
        Based on trade duration defines the ROI entry that may have been reached.
1144
        :param trade_dur: trade duration in minutes
1145
        :return: minimal ROI entry value or None if none proper ROI entry was found.
1146
        """
1147
        # Get highest entry in ROI dict where key <= trade-duration
1148
        roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys()))
1✔
1149
        if not roi_list:
1✔
1150
            return None, None
1✔
1151
        roi_entry = max(roi_list)
1✔
1152
        return roi_entry, self.minimal_roi[roi_entry]
1✔
1153

1154
    def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
1✔
1155
        """
1156
        Based on trade duration, current profit of the trade and ROI configuration,
1157
        decides whether bot should exit.
1158
        :param current_profit: current profit as ratio
1159
        :return: True if bot should exit at current rate
1160
        """
1161
        # Check if time matches and current rate is above threshold
1162
        trade_dur = int((current_time.timestamp() - trade.open_date_utc.timestamp()) // 60)
1✔
1163
        _, roi = self.min_roi_reached_entry(trade_dur)
1✔
1164
        if roi is None:
1✔
1165
            return False
1✔
1166
        else:
1167
            return current_profit > roi
1✔
1168

1169
    def ft_check_timed_out(self, trade: Trade, order: Order,
1✔
1170
                           current_time: datetime) -> bool:
1171
        """
1172
        FT Internal method.
1173
        Check if timeout is active, and if the order is still open and timed out
1174
        """
1175
        side = 'entry' if order.ft_order_side == trade.entry_side else 'exit'
1✔
1176

1177
        timeout = self.config.get('unfilledtimeout', {}).get(side)
1✔
1178
        if timeout is not None:
1✔
1179
            timeout_unit = self.config.get('unfilledtimeout', {}).get('unit', 'minutes')
1✔
1180
            timeout_kwargs = {timeout_unit: -timeout}
1✔
1181
            timeout_threshold = current_time + timedelta(**timeout_kwargs)
1✔
1182
            timedout = (order.status == 'open' and order.order_date_utc < timeout_threshold)
1✔
1183
            if timedout:
1✔
1184
                return True
1✔
1185
        time_method = (self.check_exit_timeout if order.side == trade.exit_side
1✔
1186
                       else self.check_entry_timeout)
1187

1188
        return strategy_safe_wrapper(time_method,
1✔
1189
                                     default_retval=False)(
1190
                                        pair=trade.pair, trade=trade, order=order,
1191
                                        current_time=current_time)
1192

1193
    def advise_all_indicators(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
1✔
1194
        """
1195
        Populates indicators for given candle (OHLCV) data (for multiple pairs)
1196
        Does not run advise_entry or advise_exit!
1197
        Used by optimize operations only, not during dry / live runs.
1198
        Using .copy() to get a fresh copy of the dataframe for every strategy run.
1199
        Also copy on output to avoid PerformanceWarnings pandas 1.3.0 started to show.
1200
        Has positive effects on memory usage for whatever reason - also when
1201
        using only one strategy.
1202
        """
1203
        return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair}).copy()
1✔
1204
                for pair, pair_data in data.items()}
1205

1206
    def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1207
        """
1208
        Populate indicators that will be used in the Buy, Sell, short, exit_short strategy
1209
        This method should not be overridden.
1210
        :param dataframe: Dataframe with data from the exchange
1211
        :param metadata: Additional information, like the currently traded pair
1212
        :return: a Dataframe with all mandatory indicators for the strategies
1213
        """
1214
        logger.debug(f"Populating indicators for pair {metadata.get('pair')}.")
1✔
1215

1216
        # call populate_indicators_Nm() which were tagged with @informative decorator.
1217
        for inf_data, populate_fn in self._ft_informative:
1✔
1218
            dataframe = _create_and_merge_informative_pair(
1✔
1219
                self, dataframe, metadata, inf_data, populate_fn)
1220

1221
        return self.populate_indicators(dataframe, metadata)
1✔
1222

1223
    def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1224
        """
1225
        Based on TA indicators, populates the entry order signal for the given dataframe
1226
        This method should not be overridden.
1227
        :param dataframe: DataFrame
1228
        :param metadata: Additional information dictionary, with details like the
1229
            currently traded pair
1230
        :return: DataFrame with buy column
1231
        """
1232

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

1235
        df = self.populate_entry_trend(dataframe, metadata)
1✔
1236
        if 'enter_long' not in df.columns:
1✔
1237
            df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns')
1✔
1238

1239
        return df
1✔
1240

1241
    def advise_exit(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
1✔
1242
        """
1243
        Based on TA indicators, populates the exit order signal for the given dataframe
1244
        This method should not be overridden.
1245
        :param dataframe: DataFrame
1246
        :param metadata: Additional information dictionary, with details like the
1247
            currently traded pair
1248
        :return: DataFrame with exit column
1249
        """
1250
        logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.")
1✔
1251
        df = self.populate_exit_trend(dataframe, metadata)
1✔
1252
        if 'exit_long' not in df.columns:
1✔
1253
            df = df.rename({'sell': 'exit_long'}, axis='columns')
1✔
1254
        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

© 2024 Coveralls, Inc