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

freqtrade / freqtrade / 6181253459

08 Sep 2023 06:04AM UTC coverage: 94.614% (+0.06%) from 94.556%
6181253459

push

github-actions

web-flow
Merge pull request #9159 from stash86/fix-adjust

remove old codes when we only can do partial entries

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

19114 of 20202 relevant lines covered (94.61%)

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

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

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

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

90
        except ccxt.BaseError as e:
1✔
91
            raise OperationalException(e) from e
1✔
92

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

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

122
    def funding_fee_cutoff(self, open_date: datetime):
1✔
123
        """
124
        :param open_date: The open date for a trade
125
        :return: The cutoff open time for when a funding fee is charged
126
        """
127
        return open_date.minute > 0 or (open_date.minute == 0 and open_date.second > 15)
1✔
128

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

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

158
        # * Only required for Cross
159
        :param mm_ex_1: (TMM)
160
            Cross-Margin Mode: Maintenance Margin of all other contracts, excluding Contract 1
161
            Isolated-Margin Mode: 0
162
        :param upnl_ex_1: (UPNL)
163
            Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
164
            Isolated-Margin Mode: 0
165
        """
166

167
        side_1 = -1 if is_short else 1
1✔
168
        cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
1✔
169

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

174
        if (maintenance_amt is None):
1✔
175
            raise OperationalException(
×
176
                "Parameter maintenance_amt is required by Binance.liquidation_price"
177
                f"for {self.trading_mode.value}"
178
            )
179

180
        if self.trading_mode == TradingMode.FUTURES:
1✔
181
            return (
1✔
182
                (
183
                    (wallet_balance + cross_vars + maintenance_amt) -
184
                    (side_1 * amount * open_rate)
185
                ) / (
186
                    (amount * mm_ratio) - (side_1 * amount)
187
                )
188
            )
189
        else:
190
            raise OperationalException(
×
191
                "Freqtrade only supports isolated futures for leverage trading")
192

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