• 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

96.59
/freqtrade/exchange/common.py
1
import asyncio
1✔
2
import logging
1✔
3
import time
1✔
4
from functools import wraps
1✔
5
from typing import Any, Callable, Dict, List, Optional, TypeVar, cast, overload
1✔
6

7
from freqtrade.constants import ExchangeConfig
1✔
8
from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError
1✔
9
from freqtrade.mixins import LoggingMixin
1✔
10

11

12
logger = logging.getLogger(__name__)
1✔
13
__logging_mixin = None
1✔
14

15

16
def _reset_logging_mixin():
1✔
17
    """
18
    Reset global logging mixin - used in tests only.
19
    """
20
    global __logging_mixin
21
    __logging_mixin = LoggingMixin(logger)
1✔
22

23

24
def _get_logging_mixin():
1✔
25
    # Logging-mixin to cache kucoin responses
26
    # Only to be used in retrier
27
    global __logging_mixin
28
    if not __logging_mixin:
1✔
29
        __logging_mixin = LoggingMixin(logger)
×
30
    return __logging_mixin
1✔
31

32

33
# Maximum default retry count.
34
# Functions are always called RETRY_COUNT + 1 times (for the original call)
35
API_RETRY_COUNT = 4
1✔
36
API_FETCH_ORDER_RETRY_COUNT = 5
1✔
37

38
BAD_EXCHANGES = {
1✔
39
    "bitmex": "Various reasons.",
40
    "phemex": "Does not provide history.",
41
    "probit": "Requires additional, regular calls to `signIn()`.",
42
    "poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
43
}
44

45
MAP_EXCHANGE_CHILDCLASS = {
1✔
46
    'binanceus': 'binance',
47
    'binanceje': 'binance',
48
    'binanceusdm': 'binance',
49
    'okex': 'okx',
50
    'gateio': 'gate',
51
    'huboi': 'htx',
52
}
53

54
SUPPORTED_EXCHANGES = [
1✔
55
    'binance',
56
    'bitmart',
57
    'gate',
58
    'htx',
59
    'kraken',
60
    'okx',
61
]
62

63
# either the main, or replacement methods (array) is required
64
EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = {
1✔
65
    # Required / private
66
    'fetchOrder': ['fetchOpenOrder', 'fetchClosedOrder'],
67
    'cancelOrder': [],
68
    'createOrder': [],
69
    'fetchBalance': [],
70

71
    # Public endpoints
72
    'fetchOHLCV': [],
73
}
74

75
EXCHANGE_HAS_OPTIONAL = [
1✔
76
    # Private
77
    'fetchMyTrades',  # Trades for order - fee detection
78
    'createLimitOrder', 'createMarketOrder',  # Either OR for orders
79
    # 'setLeverage',  # Margin/Futures trading
80
    # 'setMarginMode',  # Margin/Futures trading
81
    # 'fetchFundingHistory', # Futures trading
82
    # Public
83
    'fetchOrderBook', 'fetchL2OrderBook', 'fetchTicker',  # OR for pricing
84
    'fetchTickers',  # For volumepairlist?
85
    'fetchTrades',  # Downloading trades data
86
    # 'fetchFundingRateHistory',  # Futures trading
87
    # 'fetchPositions',  # Futures trading
88
    # 'fetchLeverageTiers',  # Futures initialization
89
    # 'fetchMarketLeverageTiers',  # Futures initialization
90
    # 'fetchOpenOrder', 'fetchClosedOrder',  # replacement for fetchOrder
91
    # 'fetchOpenOrders', 'fetchClosedOrders',  # 'fetchOrders',  # Refinding balance...
92
]
93

94

95
def remove_exchange_credentials(exchange_config: ExchangeConfig, dry_run: bool) -> None:
1✔
96
    """
97
    Removes exchange keys from the configuration and specifies dry-run
98
    Used for backtesting / hyperopt / edge and utils.
99
    Modifies the input dict!
100
    """
101
    if dry_run:
1✔
102
        exchange_config['key'] = ''
1✔
103
        exchange_config['apiKey'] = ''
1✔
104
        exchange_config['secret'] = ''
1✔
105
        exchange_config['password'] = ''
1✔
106
        exchange_config['uid'] = ''
1✔
107

108

109
def calculate_backoff(retrycount, max_retries):
1✔
110
    """
111
    Calculate backoff
112
    """
113
    return (max_retries - retrycount) ** 2 + 1
1✔
114

115

116
def retrier_async(f):
1✔
117
    async def wrapper(*args, **kwargs):
1✔
118
        count = kwargs.pop('count', API_RETRY_COUNT)
1✔
119
        kucoin = args[0].name == "KuCoin"  # Check if the exchange is KuCoin.
1✔
120
        try:
1✔
121
            return await f(*args, **kwargs)
1✔
122
        except TemporaryError as ex:
1✔
123
            msg = f'{f.__name__}() returned exception: "{ex}". '
1✔
124
            if count > 0:
1✔
125
                msg += f'Retrying still for {count} times.'
1✔
126
                count -= 1
1✔
127
                kwargs['count'] = count
1✔
128
                if isinstance(ex, DDosProtection):
1✔
129
                    if kucoin and "429000" in str(ex):
1✔
130
                        # Temporary fix for 429000 error on kucoin
131
                        # see https://github.com/freqtrade/freqtrade/issues/5700 for details.
132
                        _get_logging_mixin().log_once(
1✔
133
                            f"Kucoin 429 error, avoid triggering DDosProtection backoff delay. "
134
                            f"{count} tries left before giving up", logmethod=logger.warning)
135
                        # Reset msg to avoid logging too many times.
136
                        msg = ''
1✔
137
                    else:
138
                        backoff_delay = calculate_backoff(count + 1, API_RETRY_COUNT)
1✔
139
                        logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
1✔
140
                        await asyncio.sleep(backoff_delay)
1✔
141
                if msg:
1✔
142
                    logger.warning(msg)
1✔
143
                return await wrapper(*args, **kwargs)
1✔
144
            else:
145
                logger.warning(msg + 'Giving up.')
1✔
146
                raise ex
1✔
147
    return wrapper
1✔
148

149

150
F = TypeVar('F', bound=Callable[..., Any])
1✔
151

152

153
# Type shenanigans
154
@overload
1✔
155
def retrier(_func: F) -> F:
1✔
156
    ...
×
157

158

159
@overload
1✔
160
def retrier(*, retries=API_RETRY_COUNT) -> Callable[[F], F]:
1✔
161
    ...
×
162

163

164
def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
1✔
165
    def decorator(f: F) -> F:
1✔
166
        @wraps(f)
1✔
167
        def wrapper(*args, **kwargs):
1✔
168
            count = kwargs.pop('count', retries)
1✔
169
            try:
1✔
170
                return f(*args, **kwargs)
1✔
171
            except (TemporaryError, RetryableOrderError) as ex:
1✔
172
                msg = f'{f.__name__}() returned exception: "{ex}". '
1✔
173
                if count > 0:
1✔
174
                    logger.warning(msg + f'Retrying still for {count} times.')
1✔
175
                    count -= 1
1✔
176
                    kwargs.update({'count': count})
1✔
177
                    if isinstance(ex, (DDosProtection, RetryableOrderError)):
1✔
178
                        # increasing backoff
179
                        backoff_delay = calculate_backoff(count + 1, retries)
1✔
180
                        logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
1✔
181
                        time.sleep(backoff_delay)
1✔
182
                    return wrapper(*args, **kwargs)
1✔
183
                else:
184
                    logger.warning(msg + 'Giving up.')
1✔
185
                    raise ex
1✔
186
        return cast(F, wrapper)
1✔
187
    # Support both @retrier and @retrier(retries=2) syntax
188
    if _func is None:
1✔
189
        return decorator
1✔
190
    else:
191
        return decorator(_func)
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