• 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

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

7
import ccxt
1✔
8

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

16

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

19

20
class Binance(Exchange):
1✔
21

22
    _ft_has: Dict = {
1✔
23
        "stoploss_on_exchange": True,
24
        "stop_price_param": "stopPrice",
25
        "stop_price_prop": "stopPrice",
26
        "stoploss_order_types": {"limit": "stop_loss_limit"},
27
        "order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
28
        "ohlcv_candle_limit": 1000,
29
        "trades_pagination": "id",
30
        "trades_pagination_arg": "fromId",
31
        "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
32
    }
33
    _ft_has_futures: Dict = {
1✔
34
        "stoploss_order_types": {"limit": "stop", "market": "stop_market"},
35
        "order_time_in_force": ["GTC", "FOK", "IOC"],
36
        "tickers_have_price": False,
37
        "floor_leverage": True,
38
        "stop_price_type_field": "workingType",
39
        "order_props_in_contracts": ['amount', 'cost', 'filled', 'remaining'],
40
        "stop_price_type_value_mapping": {
41
            PriceType.LAST: "CONTRACT_PRICE",
42
            PriceType.MARK: "MARK_PRICE",
43
        },
44
    }
45

46
    _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
1✔
47
        # TradingMode.SPOT always supported and not required in this list
48
        # (TradingMode.MARGIN, MarginMode.CROSS),
49
        # (TradingMode.FUTURES, MarginMode.CROSS),
50
        (TradingMode.FUTURES, MarginMode.ISOLATED)
51
    ]
52

53
    def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers:
1✔
54
        tickers = super().get_tickers(symbols=symbols, cached=cached)
1✔
55
        if self.trading_mode == TradingMode.FUTURES:
1✔
56
            # Binance's future result has no bid/ask values.
57
            # Therefore we must fetch that from fetch_bids_asks and combine the two results.
58
            bidsasks = self.fetch_bids_asks(symbols, cached)
1✔
59
            tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False)
1✔
60
        return tickers
1✔
61

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

92
        except ccxt.BaseError as e:
1✔
93
            raise OperationalException(e) from e
1✔
94

95
    async def _async_get_historic_ohlcv(self, pair: str, timeframe: str,
1✔
96
                                        since_ms: int, candle_type: CandleType,
97
                                        is_new_pair: bool = False, raise_: bool = False,
98
                                        until_ms: Optional[int] = None
99
                                        ) -> OHLCVResponse:
100
        """
101
        Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
102
        Does not work for other exchanges, which don't return the earliest data when called with "0"
103
        :param candle_type: Any of the enum CandleType (must match trading mode!)
104
        """
105
        if is_new_pair:
1✔
106
            x = await self._async_get_candle_history(pair, timeframe, candle_type, 0)
1✔
107
            if x and x[3] and x[3][0] and x[3][0][0] > since_ms:
1✔
108
                # Set starting date to first available candle.
109
                since_ms = x[3][0][0]
1✔
110
                logger.info(
1✔
111
                    f"Candle-data for {pair} available starting with "
112
                    f"{datetime.fromtimestamp(since_ms // 1000, tz=timezone.utc).isoformat()}.")
113

114
        return await super()._async_get_historic_ohlcv(
1✔
115
            pair=pair,
116
            timeframe=timeframe,
117
            since_ms=since_ms,
118
            is_new_pair=is_new_pair,
119
            raise_=raise_,
120
            candle_type=candle_type,
121
            until_ms=until_ms,
122
        )
123

124
    def funding_fee_cutoff(self, open_date: datetime):
1✔
125
        """
126
        Funding fees are only charged at full hours (usually every 4-8h).
127
        Therefore a trade opening at 10:00:01 will not be charged a funding fee until the next hour.
128
        On binance, this cutoff is 15s.
129
        https://github.com/freqtrade/freqtrade/pull/5779#discussion_r740175931
130
        :param open_date: The open date for a trade
131
        :return: True if the date falls on a full hour, False otherwise
132
        """
133
        return open_date.minute == 0 and open_date.second < 15
1✔
134

135
    def dry_run_liquidation_price(
1✔
136
        self,
137
        pair: str,
138
        open_rate: float,   # Entry price of position
139
        is_short: bool,
140
        amount: float,
141
        stake_amount: float,
142
        leverage: float,
143
        wallet_balance: float,  # Or margin balance
144
        mm_ex_1: float = 0.0,  # (Binance) Cross only
145
        upnl_ex_1: float = 0.0,  # (Binance) Cross only
146
    ) -> Optional[float]:
147
        """
148
        Important: Must be fetching data from cached values as this is used by backtesting!
149
        MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
150
        PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
151

152
        :param pair: Pair to calculate liquidation price for
153
        :param open_rate: Entry price of position
154
        :param is_short: True if the trade is a short, false otherwise
155
        :param amount: Absolute value of position size incl. leverage (in base currency)
156
        :param stake_amount: Stake amount - Collateral in settle currency.
157
        :param leverage: Leverage used for this position.
158
        :param trading_mode: SPOT, MARGIN, FUTURES, etc.
159
        :param margin_mode: Either ISOLATED or CROSS
160
        :param wallet_balance: Amount of margin_mode in the wallet being used to trade
161
            Cross-Margin Mode: crossWalletBalance
162
            Isolated-Margin Mode: isolatedWalletBalance
163

164
        # * Only required for Cross
165
        :param mm_ex_1: (TMM)
166
            Cross-Margin Mode: Maintenance Margin of all other contracts, excluding Contract 1
167
            Isolated-Margin Mode: 0
168
        :param upnl_ex_1: (UPNL)
169
            Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
170
            Isolated-Margin Mode: 0
171
        """
172

173
        side_1 = -1 if is_short else 1
1✔
174
        cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
1✔
175

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

180
        if (maintenance_amt is None):
1✔
181
            raise OperationalException(
×
182
                "Parameter maintenance_amt is required by Binance.liquidation_price"
183
                f"for {self.trading_mode.value}"
184
            )
185

186
        if self.trading_mode == TradingMode.FUTURES:
1✔
187
            return (
1✔
188
                (
189
                    (wallet_balance + cross_vars + maintenance_amt) -
190
                    (side_1 * amount * open_rate)
191
                ) / (
192
                    (amount * mm_ratio) - (side_1 * amount)
193
                )
194
            )
195
        else:
196
            raise OperationalException(
×
197
                "Freqtrade only supports isolated futures for leverage trading")
198

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

© 2025 Coveralls, Inc