diff --git a/docs/exchanges.md b/docs/exchanges.md index 647dfa868..1f3b80fd1 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -328,6 +328,22 @@ It's therefore required to pass the UID as well. !!! Warning "Necessary Verification" Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification. +## Bitget + +Bitget requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows: + +```json +"exchange": { + "name": "bitget", + "key": "your_exchange_key", + "secret": "your_exchange_secret", + "password": "your_exchange_api_key_password", + // ... +} +``` + +Bitget supports [time_in_force](configuration.md#understand-order_time_in_force). + ## Hyperliquid !!! Tip "Stoploss on Exchange" diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 36aadcad1..3ba603f33 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -6,6 +6,7 @@ from freqtrade.exchange.exchange import Exchange # isort: on from freqtrade.exchange.binance import Binance from freqtrade.exchange.bingx import Bingx +from freqtrade.exchange.bitget import Bitget from freqtrade.exchange.bitmart import Bitmart from freqtrade.exchange.bitpanda import Bitpanda from freqtrade.exchange.bitvavo import Bitvavo diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py new file mode 100644 index 000000000..89a549ad1 --- /dev/null +++ b/freqtrade/exchange/bitget.py @@ -0,0 +1,52 @@ +import logging +from datetime import timedelta + +from freqtrade.enums import CandleType +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_types import FtHas +from freqtrade.util.datetime_helpers import dt_now, dt_ts + + +logger = logging.getLogger(__name__) + + +class Bitget(Exchange): + """ + Bitget exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + + Please note that this exchange is not included in the list of exchanges + officially supported by the Freqtrade development team. So some features + may still not work as expected. + """ + + _ft_has: FtHas = { + "ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones. + "order_time_in_force": ["GTC", "FOK", "IOC", "PO"], + } + _ft_has_futures: FtHas = { + "mark_ohlcv_timeframe": "4h", + } + + def ohlcv_candle_limit( + self, timeframe: str, candle_type: CandleType, since_ms: int | None = None + ) -> int: + """ + Exchange ohlcv candle limit + bitget has the following behaviour: + * 1000 candles for up-to-date data + * 200 candles for historic data (prior to a certain date) + :param timeframe: Timeframe to check + :param candle_type: Candle-type + :param since_ms: Starting timestamp + :return: Candle limit as integer + """ + timeframe_map = self._api.options["fetchOHLCV"]["maxRecentDaysPerTimeframe"] + days = timeframe_map.get(timeframe, 30) + + if candle_type in (CandleType.FUTURES, CandleType.SPOT) and ( + not since_ms or dt_ts(dt_now() - timedelta(days=days)) < since_ms + ): + return 1000 + + return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index ffc25b0dc..2f181d610 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -408,6 +408,13 @@ EXCHANGES = { "candle_count": 200, "orderbook_max_entries": 50, }, + "bitget": { + "pair": "BTC/USDT", + "stake_currency": "USDT", + "hasQuoteVolume": True, + "timeframe": "1h", + "candle_count": 1000, + }, "htx": { "pair": "ETH/BTC", "stake_currency": "BTC", diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index baab85bae..578549a26 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -521,3 +521,35 @@ class TestCCXTExchange: exch, exchangename = exchange for method in EXCHANGES[exchangename].get("private_methods", []): assert hasattr(exch._api, method) + + def test_ccxt_bitget_ohlcv_candle_limit(self, exchange: EXCHANGE_FIXTURE_TYPE): + exch, exchangename = exchange + if exchangename != "bitget": + pytest.skip("This test is only for the Bitget exchange") + + timeframes = ("1m", "5m", "1h") + + for timeframe in timeframes: + assert exch.ohlcv_candle_limit(timeframe, CandleType.SPOT) == 1000 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES) == 1000 + assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK) == 200 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE) == 200 + + start_time = dt_ts(dt_now() - timedelta(days=17)) + assert exch.ohlcv_candle_limit(timeframe, CandleType.SPOT, start_time) == 1000 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == 1000 + assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == 200 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200 + start_time = dt_ts(dt_now() - timedelta(days=48)) + length = 200 if timeframe in ("1m", "5m") else 1000 + assert exch.ohlcv_candle_limit(timeframe, CandleType.SPOT, start_time) == length + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length + assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == 200 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200 + + start_time = dt_ts(dt_now() - timedelta(days=61)) + length = 200 + assert exch.ohlcv_candle_limit(timeframe, CandleType.SPOT, start_time) == length + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUTURES, start_time) == length + assert exch.ohlcv_candle_limit(timeframe, CandleType.MARK, start_time) == 200 + assert exch.ohlcv_candle_limit(timeframe, CandleType.FUNDING_RATE, start_time) == 200