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

freqtrade / freqtrade / 3645494098

pending completion
3645494098

push

github-actions

GitHub
Merge pull request #7843 from smarmau/develop

79 of 79 new or added lines in 7 files covered. (100.0%)

16593 of 17440 relevant lines covered (95.14%)

0.95 hits per line

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

97.8
/freqtrade/exchange/binance.py
1
""" Binance exchange subclass """
2
import logging
1✔
3
from datetime import datetime
1✔
4
from pathlib import Path
1✔
5
from typing import Dict, List, Optional, Tuple
1✔
6

7
import arrow
1✔
8
import ccxt
1✔
9

10
from freqtrade.enums import CandleType, MarginMode, TradingMode
1✔
11
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
1✔
12
from freqtrade.exchange import Exchange
1✔
13
from freqtrade.exchange.common import retrier
1✔
14
from freqtrade.exchange.types import Tickers
1✔
15
from freqtrade.misc import deep_merge_dicts, json_load
1✔
16

17

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

20

21
class Binance(Exchange):
1✔
22

23
    _ft_has: Dict = {
1✔
24
        "stoploss_on_exchange": True,
25
        "stoploss_order_types": {"limit": "stop_loss_limit"},
26
        "order_time_in_force": ['GTC', 'FOK', 'IOC'],
27
        "ohlcv_candle_limit": 1000,
28
        "trades_pagination": "id",
29
        "trades_pagination_arg": "fromId",
30
        "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
31
        "ccxt_futures_name": "future"
32
    }
33
    _ft_has_futures: Dict = {
1✔
34
        "stoploss_order_types": {"limit": "limit", "market": "market"},
35
        "tickers_have_price": False,
36
    }
37

38
    _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
1✔
39
        # TradingMode.SPOT always supported and not required in this list
40
        # (TradingMode.MARGIN, MarginMode.CROSS),
41
        # (TradingMode.FUTURES, MarginMode.CROSS),
42
        (TradingMode.FUTURES, MarginMode.ISOLATED)
43
    ]
44

45
    def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers:
1✔
46
        tickers = super().get_tickers(symbols=symbols, cached=cached)
1✔
47
        if self.trading_mode == TradingMode.FUTURES:
1✔
48
            # Binance's future result has no bid/ask values.
49
            # Therefore we must fetch that from fetch_bids_asks and combine the two results.
50
            bidsasks = self.fetch_bids_asks(symbols, cached)
1✔
51
            tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False)
1✔
52
        return tickers
1✔
53

54
    @retrier
1✔
55
    def additional_exchange_init(self) -> None:
1✔
56
        """
57
        Additional exchange initialization logic.
58
        .api will be available at this point.
59
        Must be overridden in child methods if required.
60
        """
61
        try:
1✔
62
            if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
1✔
63
                position_side = self._api.fapiPrivateGetPositionsideDual()
1✔
64
                self._log_exchange_response('position_side_setting', position_side)
1✔
65
                assets_margin = self._api.fapiPrivateGetMultiAssetsMargin()
1✔
66
                self._log_exchange_response('multi_asset_margin', assets_margin)
1✔
67
                msg = ""
1✔
68
                if position_side.get('dualSidePosition') is True:
1✔
69
                    msg += (
1✔
70
                        "\nHedge Mode is not supported by freqtrade. "
71
                        "Please change 'Position Mode' on your binance futures account.")
72
                if assets_margin.get('multiAssetsMargin') is True:
1✔
73
                    msg += ("\nMulti-Asset Mode is not supported by freqtrade. "
1✔
74
                            "Please change 'Asset Mode' on your binance futures account.")
75
                if msg:
1✔
76
                    raise OperationalException(msg)
1✔
77
        except ccxt.DDoSProtection as e:
1✔
78
            raise DDosProtection(e) from e
1✔
79
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
1✔
80
            raise TemporaryError(
1✔
81
                f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
82
        except ccxt.BaseError as e:
1✔
83
            raise OperationalException(e) from e
1✔
84

85
    @retrier
1✔
86
    def _set_leverage(
1✔
87
        self,
88
        leverage: float,
89
        pair: Optional[str] = None,
90
        trading_mode: Optional[TradingMode] = None
91
    ):
92
        """
93
        Set's the leverage before making a trade, in order to not
94
        have the same leverage on every trade
95
        """
96
        trading_mode = trading_mode or self.trading_mode
1✔
97

98
        if self._config['dry_run'] or trading_mode != TradingMode.FUTURES:
1✔
99
            return
1✔
100

101
        try:
1✔
102
            self._api.set_leverage(symbol=pair, leverage=round(leverage))
1✔
103
        except ccxt.DDoSProtection as e:
1✔
104
            raise DDosProtection(e) from e
1✔
105
        except (ccxt.NetworkError, ccxt.ExchangeError) as e:
1✔
106
            raise TemporaryError(
1✔
107
                f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
108
        except ccxt.BaseError as e:
1✔
109
            raise OperationalException(e) from e
1✔
110

111
    async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
1✔
112
                                        since_ms: int, candle_type: CandleType,
113
                                        is_new_pair: bool = False, raise_: bool = False,
114
                                        until_ms: Optional[int] = None
115
                                        ) -> Tuple[str, str, str, List]:
116
        """
117
        Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
118
        Does not work for other exchanges, which don't return the earliest data when called with "0"
119
        :param candle_type: Any of the enum CandleType (must match trading mode!)
120
        """
121
        if is_new_pair:
1✔
122
            x = await self._async_get_candle_history(pair, timeframe, candle_type, 0)
1✔
123
            if x and x[3] and x[3][0] and x[3][0][0] > since_ms:
1✔
124
                # Set starting date to first available candle.
125
                since_ms = x[3][0][0]
1✔
126
                logger.info(f"Candle-data for {pair} available starting with "
1✔
127
                            f"{arrow.get(since_ms // 1000).isoformat()}.")
128

129
        return await super()._async_get_historic_ohlcv(
1✔
130
            pair=pair,
131
            timeframe=timeframe,
132
            since_ms=since_ms,
133
            is_new_pair=is_new_pair,
134
            raise_=raise_,
135
            candle_type=candle_type,
136
            until_ms=until_ms,
137
        )
138

139
    def funding_fee_cutoff(self, open_date: datetime):
1✔
140
        """
141
        :param open_date: The open date for a trade
142
        :return: The cutoff open time for when a funding fee is charged
143
        """
144
        return open_date.minute > 0 or (open_date.minute == 0 and open_date.second > 15)
1✔
145

146
    def dry_run_liquidation_price(
1✔
147
        self,
148
        pair: str,
149
        open_rate: float,   # Entry price of position
150
        is_short: bool,
151
        amount: float,
152
        stake_amount: float,
153
        wallet_balance: float,  # Or margin balance
154
        mm_ex_1: float = 0.0,  # (Binance) Cross only
155
        upnl_ex_1: float = 0.0,  # (Binance) Cross only
156
    ) -> Optional[float]:
157
        """
158
        Important: Must be fetching data from cached values as this is used by backtesting!
159
        MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
160
        PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
161

162
        :param exchange_name:
163
        :param open_rate: Entry price of position
164
        :param is_short: True if the trade is a short, false otherwise
165
        :param amount: Absolute value of position size incl. leverage (in base currency)
166
        :param stake_amount: Stake amount - Collateral in settle currency.
167
        :param trading_mode: SPOT, MARGIN, FUTURES, etc.
168
        :param margin_mode: Either ISOLATED or CROSS
169
        :param wallet_balance: Amount of margin_mode in the wallet being used to trade
170
            Cross-Margin Mode: crossWalletBalance
171
            Isolated-Margin Mode: isolatedWalletBalance
172

173
        # * Only required for Cross
174
        :param mm_ex_1: (TMM)
175
            Cross-Margin Mode: Maintenance Margin of all other contracts, excluding Contract 1
176
            Isolated-Margin Mode: 0
177
        :param upnl_ex_1: (UPNL)
178
            Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
179
            Isolated-Margin Mode: 0
180
        """
181

182
        side_1 = -1 if is_short else 1
1✔
183
        cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
1✔
184

185
        # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
186
        # maintenance_amt: (CUM) Maintenance Amount of position
187
        mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount)
1✔
188

189
        if (maintenance_amt is None):
1✔
190
            raise OperationalException(
×
191
                "Parameter maintenance_amt is required by Binance.liquidation_price"
192
                f"for {self.trading_mode.value}"
193
            )
194

195
        if self.trading_mode == TradingMode.FUTURES:
1✔
196
            return (
1✔
197
                (
198
                    (wallet_balance + cross_vars + maintenance_amt) -
199
                    (side_1 * amount * open_rate)
200
                ) / (
201
                    (amount * mm_ratio) - (side_1 * amount)
202
                )
203
            )
204
        else:
205
            raise OperationalException(
×
206
                "Freqtrade only supports isolated futures for leverage trading")
207

208
    @retrier
1✔
209
    def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
1✔
210
        if self.trading_mode == TradingMode.FUTURES:
1✔
211
            if self._config['dry_run']:
1✔
212
                leverage_tiers_path = (
1✔
213
                    Path(__file__).parent / 'binance_leverage_tiers.json'
214
                )
215
                with open(leverage_tiers_path) as json_file:
1✔
216
                    return json_load(json_file)
1✔
217
            else:
218
                try:
1✔
219
                    return self._api.fetch_leverage_tiers()
1✔
220
                except ccxt.DDoSProtection as e:
1✔
221
                    raise DDosProtection(e) from e
1✔
222
                except (ccxt.NetworkError, ccxt.ExchangeError) as e:
1✔
223
                    raise TemporaryError(f'Could not fetch leverage amounts due to'
1✔
224
                                         f'{e.__class__.__name__}. Message: {e}') from e
225
                except ccxt.BaseError as e:
1✔
226
                    raise OperationalException(e) from e
1✔
227
        else:
228
            return {}
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