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

freqtrade / freqtrade / 9394559170

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

push

github

xmatthias
Loader should be passed as kwarg for clarity

20280 of 21425 relevant lines covered (94.66%)

0.95 hits per line

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

98.44
/freqtrade/plugins/pairlist/AgeFilter.py
1
"""
2
Minimum age (days listed) pair list filter
3
"""
4
import logging
1✔
5
from copy import deepcopy
1✔
6
from datetime import timedelta
1✔
7
from typing import Any, Dict, List, Optional
1✔
8

9
from pandas import DataFrame
1✔
10

11
from freqtrade.constants import Config, ListPairsWithTimeframes
1✔
12
from freqtrade.exceptions import OperationalException
1✔
13
from freqtrade.exchange.types import Tickers
1✔
14
from freqtrade.misc import plural
1✔
15
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
1✔
16
from freqtrade.util import PeriodicCache, dt_floor_day, dt_now, dt_ts
1✔
17

18

19
logger = logging.getLogger(__name__)
1✔
20

21

22
class AgeFilter(IPairList):
1✔
23

24
    def __init__(self, exchange, pairlistmanager,
1✔
25
                 config: Config, pairlistconfig: Dict[str, Any],
26
                 pairlist_pos: int) -> None:
27
        super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
1✔
28

29
        # Checked symbols cache (dictionary of ticker symbol => timestamp)
30
        self._symbolsChecked: Dict[str, int] = {}
1✔
31
        self._symbolsCheckFailed = PeriodicCache(maxsize=1000, ttl=86_400)
1✔
32

33
        self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
1✔
34
        self._max_days_listed = pairlistconfig.get('max_days_listed')
1✔
35

36
        candle_limit = exchange.ohlcv_candle_limit('1d', self._config['candle_type_def'])
1✔
37
        if self._min_days_listed < 1:
1✔
38
            raise OperationalException("AgeFilter requires min_days_listed to be >= 1")
1✔
39
        if self._min_days_listed > candle_limit:
1✔
40
            raise OperationalException("AgeFilter requires min_days_listed to not exceed "
1✔
41
                                       "exchange max request size "
42
                                       f"({candle_limit})")
43
        if self._max_days_listed and self._max_days_listed <= self._min_days_listed:
1✔
44
            raise OperationalException("AgeFilter max_days_listed <= min_days_listed not permitted")
1✔
45
        if self._max_days_listed and self._max_days_listed > candle_limit:
1✔
46
            raise OperationalException("AgeFilter requires max_days_listed to not exceed "
×
47
                                       "exchange max request size "
48
                                       f"({candle_limit})")
49

50
    @property
1✔
51
    def needstickers(self) -> bool:
1✔
52
        """
53
        Boolean property defining if tickers are necessary.
54
        If no Pairlist requires tickers, an empty Dict is passed
55
        as tickers argument to filter_pairlist
56
        """
57
        return False
1✔
58

59
    def short_desc(self) -> str:
1✔
60
        """
61
        Short whitelist method description - used for startup-messages
62
        """
63
        return (
1✔
64
            f"{self.name} - Filtering pairs with age less than "
65
            f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}"
66
        ) + ((
67
            " or more than "
68
            f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}"
69
        ) if self._max_days_listed else '')
70

71
    @staticmethod
1✔
72
    def description() -> str:
1✔
73
        return "Filter pairs by age (days listed)."
1✔
74

75
    @staticmethod
1✔
76
    def available_parameters() -> Dict[str, PairlistParameter]:
1✔
77
        return {
1✔
78
            "min_days_listed": {
79
                "type": "number",
80
                "default": 10,
81
                "description": "Minimum Days Listed",
82
                "help": "Minimum number of days a pair must have been listed on the exchange.",
83
            },
84
            "max_days_listed": {
85
                "type": "number",
86
                "default": None,
87
                "description": "Maximum Days Listed",
88
                "help": "Maximum number of days a pair must have been listed on the exchange.",
89
            },
90
        }
91

92
    def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]:
1✔
93
        """
94
        :param pairlist: pairlist to filter or sort
95
        :param tickers: Tickers (from exchange.get_tickers). May be cached.
96
        :return: new allowlist
97
        """
98
        needed_pairs: ListPairsWithTimeframes = [
1✔
99
            (p, '1d', self._config['candle_type_def']) for p in pairlist
100
            if p not in self._symbolsChecked and p not in self._symbolsCheckFailed]
101
        if not needed_pairs:
1✔
102
            # Remove pairs that have been removed before
103
            return [p for p in pairlist if p not in self._symbolsCheckFailed]
1✔
104

105
        since_days = -(
1✔
106
            self._max_days_listed if self._max_days_listed else self._min_days_listed
107
        ) - 1
108
        since_ms = dt_ts(dt_floor_day(dt_now()) + timedelta(days=since_days))
1✔
109
        candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False)
1✔
110
        if self._enabled:
1✔
111
            for p in deepcopy(pairlist):
1✔
112
                daily_candles = candles[(p, '1d', self._config['candle_type_def'])] if (
1✔
113
                    p, '1d', self._config['candle_type_def']) in candles else None
114
                if not self._validate_pair_loc(p, daily_candles):
1✔
115
                    pairlist.remove(p)
1✔
116
        self.log_once(f"Validated {len(pairlist)} pairs.", logger.info)
1✔
117
        return pairlist
1✔
118

119
    def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
1✔
120
        """
121
        Validate age for the ticker
122
        :param pair: Pair that's currently validated
123
        :param daily_candles: Downloaded daily candles
124
        :return: True if the pair can stay, false if it should be removed
125
        """
126
        # Check symbol in cache
127
        if pair in self._symbolsChecked:
1✔
128
            return True
1✔
129

130
        if daily_candles is not None:
1✔
131
            if (
1✔
132
                len(daily_candles) >= self._min_days_listed
133
                and (not self._max_days_listed or len(daily_candles) <= self._max_days_listed)
134
            ):
135
                # We have fetched at least the minimum required number of daily candles
136
                # Add to cache, store the time we last checked this symbol
137
                self._symbolsChecked[pair] = dt_ts()
1✔
138
                return True
1✔
139
            else:
140
                self.log_once((
1✔
141
                    f"Removed {pair} from whitelist, because age "
142
                    f"{len(daily_candles)} is less than {self._min_days_listed} "
143
                    f"{plural(self._min_days_listed, 'day')}"
144
                ) + ((
145
                    " or more than "
146
                    f"{self._max_days_listed} {plural(self._max_days_listed, 'day')}"
147
                ) if self._max_days_listed else ''), logger.info)
148
                self._symbolsCheckFailed[pair] = dt_ts()
1✔
149
                return False
1✔
150
        return False
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