Skip to content

Commit 9b1fc7a

Browse files
authored
Merge pull request freqtrade#12394 from freqtrade/feat/bitget_futures
Add support for bitget futures
2 parents 44ec500 + dcd9e2e commit 9b1fc7a

File tree

9 files changed

+239
-31
lines changed

9 files changed

+239
-31
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ hesitate to read the source code and understand the mechanism of this bot.
2727
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
2828

2929
- [X] [Binance](https://www.binance.com/)
30-
- [X] [Bitmart](https://bitmart.com/)
3130
- [X] [BingX](https://bingx.com/invite/0EM9RX)
31+
- [X] [Bitget](https://www.bitget.com/)
32+
- [X] [Bitmart](https://bitmart.com/)
3233
- [X] [Bybit](https://bybit.com/)
3334
- [X] [Gate.io](https://www.gate.io/ref/6266643)
3435
- [X] [HTX](https://www.htx.com/)
@@ -41,6 +42,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
4142
### Supported Futures Exchanges (experimental)
4243

4344
- [X] [Binance](https://www.binance.com/)
45+
- [X] [Bitget](https://www.bitget.com/)
4446
- [X] [Gate.io](https://www.gate.io/ref/6266643)
4547
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
4648
- [X] [OKX](https://okx.com/)

docs/exchanges.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,14 @@ Without these permissions, the bot will not start correctly and show errors like
298298

299299
Bybit supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "FOK" (full-or-cancel), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
300300

301-
Futures trading on bybit is currently supported for isolated futures mode.
301+
!!! Warning "Unified accounts"
302+
Freqtrade assumes accounts to be dedicated to the bot.
303+
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
304+
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
305+
306+
### Bybit Futures
307+
308+
Futures trading on bybit is supported for isolated futures mode.
302309

303310
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
304311

@@ -312,10 +319,6 @@ API Keys for live futures trading must have the following permissions:
312319

313320
We do strongly recommend to limit all API keys to the IP you're going to use it from.
314321

315-
!!! Warning "Unified accounts"
316-
Freqtrade assumes accounts to be dedicated to the bot.
317-
We therefore recommend the usage of one subaccount per bot. This is especially important when using unified accounts.
318-
Other configurations (multiple bots on one account, manual non-bot trades on the bot account) are not supported and may lead to unexpected behavior.
319322

320323
## Bitmart
321324

@@ -355,6 +358,12 @@ Bitget supports [time_in_force](configuration.md#understand-order_time_in_force)
355358
Bitget supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
356359
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used.
357360

361+
### Bitget Futures
362+
363+
Futures trading on bitget is supported for isolated futures mode.
364+
365+
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that manual changes to this setting may result in exceptions and errors.
366+
358367
## Hyperliquid
359368

360369
!!! Tip "Stoploss on Exchange"
@@ -478,3 +487,5 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
478487

479488
!!! Warning
480489
Please make sure to fully understand the impacts of these settings before modifying them.
490+
Using `_ft_has_params` overrides may lead to unexpected behavior, and may even break your bot.
491+
We will not be able to provide support for issues caused by custom settings in `_ft_has_params`.

docs/includes/exchange-features.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
| [Binance](exchanges.md#binance) | futures | isolated, cross | market, limit |
66
| [Bingx](exchanges.md#bingx) | spot | | market, limit |
77
| [Bitmart](exchanges.md#bitmart) | spot | | ❌ (not supported) |
8+
| [Bitget](exchanges.md#bitget) | spot | | market, limit |
9+
| [Bitget](exchanges.md#bitget) | futures | isolated | market, limit |
810
| [Bybit](exchanges.md#bybit) | spot | | ❌ (not supported) |
911
| [Bybit](exchanges.md#bybit) | futures | isolated | market, limit |
1012
| [Gate.io](exchanges.md#gateio) | spot | | limit |

docs/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
3939

4040
- [X] [Binance](https://www.binance.com/)
4141
- [X] [BingX](https://bingx.com/invite/0EM9RX)
42+
- [X] [Bitget](https://www.bitget.com/)
4243
- [X] [Bitmart](https://bitmart.com/)
4344
- [X] [Bybit](https://bybit.com/)
4445
- [X] [Gate.io](https://www.gate.io/ref/6266643)
@@ -52,6 +53,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
5253
### Supported Futures Exchanges (experimental)
5354

5455
- [X] [Binance](https://www.binance.com/)
56+
- [X] [Bitget](https://www.bitget.com/)
5557
- [X] [Bybit](https://bybit.com/)
5658
- [X] [Gate.io](https://www.gate.io/ref/6266643)
5759
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)

freqtrade/exchange/bitget.py

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import ccxt
55

6-
from freqtrade.enums import CandleType
6+
from freqtrade.constants import BuySell
7+
from freqtrade.enums import CandleType, MarginMode, TradingMode
78
from freqtrade.exceptions import (
89
DDosProtection,
910
OperationalException,
@@ -20,27 +21,30 @@
2021

2122

2223
class Bitget(Exchange):
23-
"""
24-
Bitget exchange class. Contains adjustments needed for Freqtrade to work
25-
with this exchange.
26-
27-
Please note that this exchange is not included in the list of exchanges
28-
officially supported by the Freqtrade development team. So some features
29-
may still not work as expected.
24+
"""Bitget exchange class.
25+
Contains adjustments needed for Freqtrade to work with this exchange.
3026
"""
3127

3228
_ft_has: FtHas = {
3329
"stoploss_on_exchange": True,
3430
"stop_price_param": "stopPrice",
3531
"stop_price_prop": "stopPrice",
32+
"stoploss_blocks_assets": False, # Stoploss orders do not block assets
3633
"stoploss_order_types": {"limit": "limit", "market": "market"},
3734
"ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones.
3835
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
3936
}
4037
_ft_has_futures: FtHas = {
4138
"mark_ohlcv_timeframe": "4h",
39+
"funding_fee_candle_limit": 100,
4240
}
4341

42+
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
43+
(TradingMode.SPOT, MarginMode.NONE),
44+
(TradingMode.FUTURES, MarginMode.ISOLATED),
45+
# (TradingMode.FUTURES, MarginMode.CROSS),
46+
]
47+
4448
def ohlcv_candle_limit(
4549
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
4650
) -> int:
@@ -126,3 +130,109 @@ def fetch_stoploss_order(
126130

127131
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
128132
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
133+
134+
@retrier
135+
def additional_exchange_init(self) -> None:
136+
"""
137+
Additional exchange initialization logic.
138+
.api will be available at this point.
139+
Must be overridden in child methods if required.
140+
"""
141+
try:
142+
if not self._config["dry_run"]:
143+
if self.trading_mode == TradingMode.FUTURES:
144+
position_mode = self._api.set_position_mode(False)
145+
self._log_exchange_response("set_position_mode", position_mode)
146+
except ccxt.DDoSProtection as e:
147+
raise DDosProtection(e) from e
148+
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
149+
raise TemporaryError(
150+
f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}"
151+
) from e
152+
except ccxt.BaseError as e:
153+
raise OperationalException(e) from e
154+
155+
def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
156+
if self.trading_mode != TradingMode.SPOT:
157+
# Explicitly setting margin_mode is not necessary as marginMode can be set per order.
158+
# self.set_margin_mode(pair, self.margin_mode, accept_fail)
159+
self._set_leverage(leverage, pair, accept_fail)
160+
161+
def _get_params(
162+
self,
163+
side: BuySell,
164+
ordertype: str,
165+
leverage: float,
166+
reduceOnly: bool,
167+
time_in_force: str = "GTC",
168+
) -> dict:
169+
params = super()._get_params(
170+
side=side,
171+
ordertype=ordertype,
172+
leverage=leverage,
173+
reduceOnly=reduceOnly,
174+
time_in_force=time_in_force,
175+
)
176+
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
177+
params["marginMode"] = self.margin_mode.value.lower()
178+
return params
179+
180+
def dry_run_liquidation_price(
181+
self,
182+
pair: str,
183+
open_rate: float,
184+
is_short: bool,
185+
amount: float,
186+
stake_amount: float,
187+
leverage: float,
188+
wallet_balance: float,
189+
open_trades: list,
190+
) -> float | None:
191+
"""
192+
Important: Must be fetching data from cached values as this is used by backtesting!
193+
194+
195+
https://www.bitget.com/support/articles/12560603808759
196+
MMR: Maintenance margin rate of the trading pair.
197+
198+
CoinMainIndexPrice: The index price for Coin-M futures. For USDT-M futures,
199+
the index price is: 1.
200+
201+
TakerFeeRatio: The fee rate applied when placing taker orders.
202+
203+
Position direction: The current position direction of the trading pair.
204+
1 indicates a long position, and -1 indicates a short position.
205+
206+
Formula:
207+
208+
Estimated liquidation price = [
209+
position margin - position size x average entry price x position direction
210+
] ÷ [position size x (MMR + TakerFeeRatio - position direction)]
211+
212+
:param pair: Pair to calculate liquidation price for
213+
:param open_rate: Entry price of position
214+
:param is_short: True if the trade is a short, false otherwise
215+
:param amount: Absolute value of position size incl. leverage (in base currency)
216+
:param stake_amount: Stake amount - Collateral in settle currency.
217+
:param leverage: Leverage used for this position.
218+
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
219+
Cross-Margin Mode: crossWalletBalance
220+
Isolated-Margin Mode: isolatedWalletBalance
221+
:param open_trades: List of other open trades in the same wallet
222+
"""
223+
market = self.markets[pair]
224+
taker_fee_rate = market["taker"] or self._api.describe().get("fees", {}).get(
225+
"trading", {}
226+
).get("taker", 0.001)
227+
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount)
228+
229+
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
230+
position_direction = -1 if is_short else 1
231+
232+
return (wallet_balance - (amount * open_rate * position_direction)) / (
233+
amount * (mm_ratio + taker_fee_rate - position_direction)
234+
)
235+
else:
236+
raise OperationalException(
237+
"Freqtrade currently only supports isolated futures for bitget"
238+
)

freqtrade/exchange/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def _get_logging_mixin():
5656
"binance",
5757
"bingx",
5858
"bitmart",
59+
"bitget",
5960
"bybit",
6061
"gate",
6162
"htx",

tests/exchange/test_bitget.py

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from datetime import timedelta
2-
from unittest.mock import MagicMock
2+
from unittest.mock import MagicMock, PropertyMock
33

44
import pytest
55

6-
from freqtrade.enums import CandleType
7-
from freqtrade.exceptions import RetryableOrderError
6+
from freqtrade.enums import CandleType, MarginMode, TradingMode
7+
from freqtrade.exceptions import OperationalException, RetryableOrderError
88
from freqtrade.exchange.common import API_RETRY_COUNT
99
from freqtrade.util import dt_now, dt_ts
1010
from tests.conftest import EXMS, get_patched_exchange
@@ -120,3 +120,76 @@ def test_bitget_ohlcv_candle_limit(mocker, default_conf_usdt):
120120
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length
121121
assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == length
122122
assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200
123+
124+
125+
def test_additional_exchange_init_bitget(default_conf, mocker):
126+
default_conf["dry_run"] = False
127+
default_conf["trading_mode"] = TradingMode.FUTURES
128+
default_conf["margin_mode"] = MarginMode.ISOLATED
129+
api_mock = MagicMock()
130+
api_mock.set_position_mode = MagicMock(return_value={})
131+
132+
get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
133+
assert api_mock.set_position_mode.call_count == 1
134+
135+
ccxt_exceptionhandlers(
136+
mocker, default_conf, api_mock, "bitget", "additional_exchange_init", "set_position_mode"
137+
)
138+
139+
140+
def test_dry_run_liquidation_price_cross_bitget(default_conf, mocker):
141+
default_conf["dry_run"] = True
142+
default_conf["trading_mode"] = TradingMode.FUTURES
143+
default_conf["margin_mode"] = MarginMode.CROSS
144+
api_mock = MagicMock()
145+
mocker.patch(f"{EXMS}.get_maintenance_ratio_and_amt", MagicMock(return_value=(0.005, 0.0)))
146+
exchange = get_patched_exchange(mocker, default_conf, exchange="bitget", api_mock=api_mock)
147+
148+
with pytest.raises(
149+
OperationalException, match="Freqtrade currently only supports isolated futures for bitget"
150+
):
151+
exchange.dry_run_liquidation_price(
152+
"ETH/USDT:USDT",
153+
100_000,
154+
False,
155+
0.1,
156+
100,
157+
10,
158+
100,
159+
[],
160+
)
161+
162+
163+
def test__lev_prep_bitget(default_conf, mocker):
164+
api_mock = MagicMock()
165+
api_mock.set_margin_mode = MagicMock()
166+
api_mock.set_leverage = MagicMock()
167+
type(api_mock).has = PropertyMock(return_value={"setMarginMode": True, "setLeverage": True})
168+
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
169+
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
170+
171+
assert api_mock.set_margin_mode.call_count == 0
172+
assert api_mock.set_leverage.call_count == 0
173+
174+
# test in futures mode
175+
api_mock.set_margin_mode.reset_mock()
176+
api_mock.set_leverage.reset_mock()
177+
default_conf["dry_run"] = False
178+
179+
default_conf["trading_mode"] = "futures"
180+
default_conf["margin_mode"] = "isolated"
181+
182+
exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget")
183+
exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy")
184+
185+
assert api_mock.set_margin_mode.call_count == 0
186+
assert api_mock.set_leverage.call_count == 1
187+
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=3.2)
188+
189+
api_mock.reset_mock()
190+
191+
exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell")
192+
193+
assert api_mock.set_margin_mode.call_count == 0
194+
assert api_mock.set_leverage.call_count == 1
195+
api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=19.99)

0 commit comments

Comments
 (0)