From b88db55db3b9a8490fe81574a0b3a4102459059f Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Fri, 25 Oct 2024 17:24:59 +0200 Subject: [PATCH 01/30] Initial implementation of hyperliquid. - Spot - Swap (long, short, leverage, stoploss_on_exchange) - dry_run_liquidation_price() --- freqtrade/exchange/exchange.py | 3 + freqtrade/exchange/exchange_types.py | 2 + freqtrade/exchange/hyperliquid.py | 147 +++++++++++++++++++++++++-- requirements.txt | 2 +- 4 files changed, 144 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f7fb8a9c7..aacfb3163 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -144,6 +144,7 @@ class Exchange: "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", "trades_has_history": False, + "create_order_has_all_data": True, # Set to False if create_order doesn't return all data "l2_limit_range": None, "l2_limit_range_required": True, # Allow Empty L2 limit (kucoin) "mark_ohlcv_price": "mark", @@ -1274,6 +1275,8 @@ class Exchange: rate_for_order, params, ) + if not self._ft_has.get("create_order_has_all_data"): + order = self._api.fetch_order(order['id'], pair) if order.get("status") is None: # Map empty status to open. order["status"] = "open" diff --git a/freqtrade/exchange/exchange_types.py b/freqtrade/exchange/exchange_types.py index e9c58ec38..6bf5cb323 100644 --- a/freqtrade/exchange/exchange_types.py +++ b/freqtrade/exchange/exchange_types.py @@ -34,6 +34,8 @@ class FtHas(TypedDict, total=False): trades_pagination_arg: str trades_has_history: bool trades_pagination_overlap: bool + # Create order + create_order_has_all_data: bool # Orderbook l2_limit_range: Optional[list[int]] l2_limit_range_required: bool diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 144edbf3a..2a25c73f6 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -1,10 +1,14 @@ """Hyperliquid exchange subclass""" import logging +from typing import Optional -from freqtrade.enums import TradingMode from freqtrade.exchange import Exchange -from freqtrade.exchange.exchange_types import FtHas +from typing import List, Tuple, Dict +from freqtrade.enums import MarginMode, TradingMode, CandleType +from freqtrade.exceptions import OperationalException, ExchangeError +from freqtrade.constants import BuySell +from datetime import datetime logger = logging.getLogger(__name__) @@ -15,21 +19,146 @@ class Hyperliquid(Exchange): Contains adjustments needed for Freqtrade to work with this exchange. """ - _ft_has: FtHas = { - # Only the most recent 5000 candles are available according to the - # exchange's API documentation. + _ft_has: Dict = { "ohlcv_has_history": False, "ohlcv_candle_limit": 5000, - "trades_has_history": False, # Trades endpoint doesn't seem available. + "orderbook_max_entries": 20, + "l2_limit_range": [20], + "trades_has_history": False, + "tickers_have_bid_ask": False, + "stoploss_on_exchange": True, "exchange_has_overrides": {"fetchTrades": False}, + "stoploss_order_types": {"limit": "limit"}, + "funding_fee_timeframe": "1h", + "create_order_has_all_data": False } + _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + (TradingMode.FUTURES, MarginMode.ISOLATED) + ] + @property - def _ccxt_config(self) -> dict: - # Parameters to add directly to ccxt sync/async initialization. - # ccxt defaults to swap mode. + def _ccxt_config(self) -> Dict: + # ccxt Hyperliquid defaults to swap config = {} if self.trading_mode == TradingMode.SPOT: config.update({"options": {"defaultType": "spot"}}) config.update(super()._ccxt_config) return config + + def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float: + # There are no leverage tiers + if self.trading_mode == TradingMode.FUTURES: + return self.markets[pair]['limits']['leverage']['max'] + else: + return 1.0 + + def ohlcv_candle_limit( + self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None + ) -> int: + # Funding rate candles have a different limit + if candle_type in CandleType.FUNDING_RATE: + return 500 + + return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) + + def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): + if self.trading_mode != TradingMode.SPOT: + # Hyperliquid expects leverage to be an int + leverage = int(leverage) + # Hyperliquid needs the parameter leverage. + # Don't use set_leverage(), as this sets margin back to cross + self.set_margin_mode(pair, self.margin_mode, params={"leverage": leverage}) + + def dry_run_liquidation_price( + self, + pair: str, + open_rate: float, # Entry price of position + is_short: bool, + amount: float, + stake_amount: float, + leverage: float, + wallet_balance: float, # Or margin balance + mm_ex_1: float = 0.0, # (Binance) Cross only + upnl_ex_1: float = 0.0, # (Binance) Cross only + ) -> Optional[float]: + """ + Optimized + Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/liquidations + Below can be done in fewer lines of code, but like this it matches the documentation. + + Tested with 196 unique ccxt fetch_positions() position outputs + - Only first output per position where pnl=0.0 + - Compare against returned liquidation price + Positions: 197 Average deviation: 0.00028980% Max deviation: 0.01309453% + Positions info: + {'leverage': {1.0: 23, 2.0: 155, 3.0: 8, 4.0: 7, 5.0: 4}, + 'side': {'long': 133, 'short': 64}, + 'symbol': {'BTC/USDC:USDC': 81, + 'DOGE/USDC:USDC': 20, + 'ETH/USDC:USDC': 53, + 'SOL/USDC:USDC': 43}} + """ + # Defining/renaming variables to match the documentation + isolated_margin = stake_amount + position_size = amount + price = open_rate + position_value = price * position_size + max_leverage = self.markets[pair]['limits']['leverage']['max'] + + # Docs: The maintenance margin is half of the initial margin at max leverage, + # which varies from 3-50x. In other words, the maintenance margin is between 1% + # (for 50x max leverage assets) and 16.7% (for 3x max leverage assets) + # depending on the asset + # The key thing here is 'Half of the initial margin at max leverage'. + # A bit ambiguous, but this interpretation leads to accurate results: + # 1. Start from the position value + # 2. Assume max leverage, calculate the initial margin by deviding the position value + # by the max leverage + # 3. Divide this by 2 + maintenance_margin_required = position_value / max_leverage / 2 + + # Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required + margin_available = isolated_margin - maintenance_margin_required + + # Docs: The maintenance margin is half of the initial margin at max leverage + # The docs don't explicitly specify maintenance leverage, but this works. + # Double because of the statement 'half of the initial margin at max leverage' + maintenance_leverage = max_leverage * 2 + + # Docs: l = 1 / MAINTENANCE_LEVERAGE (Using 'll' to comply with PEP8: E741) + ll = 1 / maintenance_leverage + + # Docs: side = 1 for long and -1 for short + side = -1 if is_short else 1 + + # Docs: liq_price = price - side * margin_available / position_size / (1 - l * side) + liq_price = price - side * margin_available / position_size / (1 - ll * side) + + if self.trading_mode == TradingMode.FUTURES: + return liq_price + else: + raise OperationalException( + "Freqtrade only supports isolated futures for leverage trading" + ) + + def get_funding_fees( + self, pair: str, amount: float, is_short: bool, open_date: datetime + ) -> float: + """ + Fetch funding fees, either from the exchange (live) or calculates them + based on funding rate/mark price history + :param pair: The quote/base pair of the trade + :param is_short: trade direction + :param amount: Trade amount + :param open_date: Open date of the trade + :return: funding fee since open_date + :raises: ExchangeError if something goes wrong. + """ + # Bybit does not provide "applied" funding fees per position. + if self.trading_mode == TradingMode.FUTURES: + try: + return self._fetch_and_calculate_funding_fees(pair, amount, is_short, open_date) + except ExchangeError: + logger.warning(f"Could not update funding fees for {pair}.") + return 0.0 diff --git a/requirements.txt b/requirements.txt index 4f0fc2532..5b1d2defb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.4.20 +ccxt==4.4.22 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.3; platform_machine != 'armv7l' aiohttp==3.10.10 From 4c78b8c6d115d97125f0b352944051edc8291c2d Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Sat, 26 Oct 2024 14:13:57 +0200 Subject: [PATCH 02/30] updated dry_run_liquidation_price to match new signature fixed comment --- freqtrade/exchange/hyperliquid.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 2a25c73f6..31656d2d8 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -79,8 +79,7 @@ class Hyperliquid(Exchange): stake_amount: float, leverage: float, wallet_balance: float, # Or margin balance - mm_ex_1: float = 0.0, # (Binance) Cross only - upnl_ex_1: float = 0.0, # (Binance) Cross only + open_trades: list, ) -> Optional[float]: """ Optimized @@ -155,7 +154,7 @@ class Hyperliquid(Exchange): :return: funding fee since open_date :raises: ExchangeError if something goes wrong. """ - # Bybit does not provide "applied" funding fees per position. + # Hyperliquid does not have fetchFundingHistory if self.trading_mode == TradingMode.FUTURES: try: return self._fetch_and_calculate_funding_fees(pair, amount, is_short, open_date) From 7d1976ad0512dba314d8009382e914db061849ab Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Sun, 27 Oct 2024 12:09:20 +0100 Subject: [PATCH 03/30] fix ohlcv_candle_limit funding_rate candles Co-authored-by: Matthias --- freqtrade/exchange/hyperliquid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 31656d2d8..60925578f 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -57,7 +57,7 @@ class Hyperliquid(Exchange): self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None ) -> int: # Funding rate candles have a different limit - if candle_type in CandleType.FUNDING_RATE: + if candle_type == CandleType.FUNDING_RATE: return 500 return super().ohlcv_candle_limit(timeframe, candle_type, since_ms) From a7681fc7124de042a20f1fedee026da24757ebfd Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Sun, 27 Oct 2024 12:27:54 +0100 Subject: [PATCH 04/30] revert ccxt version bump --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5b1d2defb..4f0fc2532 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.4.22 +ccxt==4.4.20 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.3; platform_machine != 'armv7l' aiohttp==3.10.10 From 493fa8541bb4917437984bfe169d37322e473279 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Sun, 27 Oct 2024 17:06:56 +0100 Subject: [PATCH 05/30] fixes related to ruff and mypy --- freqtrade/exchange/exchange.py | 2 +- freqtrade/exchange/hyperliquid.py | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index aacfb3163..3209b693f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1276,7 +1276,7 @@ class Exchange: params, ) if not self._ft_has.get("create_order_has_all_data"): - order = self._api.fetch_order(order['id'], pair) + order = self._api.fetch_order(order["id"], pair) if order.get("status") is None: # Map empty status to open. order["status"] = "open" diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 60925578f..3218d2dc1 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -1,14 +1,14 @@ """Hyperliquid exchange subclass""" import logging +from datetime import datetime from typing import Optional -from freqtrade.exchange import Exchange -from typing import List, Tuple, Dict -from freqtrade.enums import MarginMode, TradingMode, CandleType -from freqtrade.exceptions import OperationalException, ExchangeError from freqtrade.constants import BuySell -from datetime import datetime +from freqtrade.enums import CandleType, MarginMode, TradingMode +from freqtrade.exceptions import ExchangeError, OperationalException +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_types import FtHas logger = logging.getLogger(__name__) @@ -19,10 +19,9 @@ class Hyperliquid(Exchange): Contains adjustments needed for Freqtrade to work with this exchange. """ - _ft_has: Dict = { + _ft_has: FtHas = { "ohlcv_has_history": False, "ohlcv_candle_limit": 5000, - "orderbook_max_entries": 20, "l2_limit_range": [20], "trades_has_history": False, "tickers_have_bid_ask": False, @@ -30,15 +29,15 @@ class Hyperliquid(Exchange): "exchange_has_overrides": {"fetchTrades": False}, "stoploss_order_types": {"limit": "limit"}, "funding_fee_timeframe": "1h", - "create_order_has_all_data": False + "create_order_has_all_data": False, } - _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ + _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ (TradingMode.FUTURES, MarginMode.ISOLATED) ] @property - def _ccxt_config(self) -> Dict: + def _ccxt_config(self) -> dict: # ccxt Hyperliquid defaults to swap config = {} if self.trading_mode == TradingMode.SPOT: @@ -49,7 +48,7 @@ class Hyperliquid(Exchange): def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float: # There are no leverage tiers if self.trading_mode == TradingMode.FUTURES: - return self.markets[pair]['limits']['leverage']['max'] + return self.markets[pair]["limits"]["leverage"]["max"] else: return 1.0 @@ -103,7 +102,7 @@ class Hyperliquid(Exchange): position_size = amount price = open_rate position_value = price * position_size - max_leverage = self.markets[pair]['limits']['leverage']['max'] + max_leverage = self.markets[pair]["limits"]["leverage"]["max"] # Docs: The maintenance margin is half of the initial margin at max leverage, # which varies from 3-50x. In other words, the maintenance margin is between 1% From fd619de1d36d4185136e4c38b335819c7ad2cc5c Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Sun, 27 Oct 2024 22:00:52 +0100 Subject: [PATCH 06/30] typo --- freqtrade/exchange/hyperliquid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 3218d2dc1..d7c64e462 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -111,7 +111,7 @@ class Hyperliquid(Exchange): # The key thing here is 'Half of the initial margin at max leverage'. # A bit ambiguous, but this interpretation leads to accurate results: # 1. Start from the position value - # 2. Assume max leverage, calculate the initial margin by deviding the position value + # 2. Assume max leverage, calculate the initial margin by dividing the position value # by the max leverage # 3. Divide this by 2 maintenance_margin_required = position_value / max_leverage / 2 From 3913b450b685aa50c87cde8b2df48197a680971e Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Mon, 28 Oct 2024 19:30:59 +0100 Subject: [PATCH 07/30] check for tickers_have_bid_ask --- tests/exchange_online/test_ccxt_compat.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index 32133d8e5..a64b8d310 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -118,9 +118,10 @@ class TestCCXTExchange: tickers = exch.get_tickers() assert pair in tickers assert "ask" in tickers[pair] - assert tickers[pair]["ask"] is not None assert "bid" in tickers[pair] - assert tickers[pair]["bid"] is not None + if EXCHANGES[exchangename].get("tickers_have_bid_ask"): + assert tickers[pair]["bid"] is not None + assert tickers[pair]["ask"] is not None assert "quoteVolume" in tickers[pair] if EXCHANGES[exchangename].get("hasQuoteVolume"): assert tickers[pair]["quoteVolume"] is not None @@ -150,9 +151,10 @@ class TestCCXTExchange: ticker = exch.fetch_ticker(pair) assert "ask" in ticker - assert ticker["ask"] is not None assert "bid" in ticker - assert ticker["bid"] is not None + if EXCHANGES[exchangename].get("tickers_have_bid_ask"): + assert ticker["ask"] is not None + assert ticker["bid"] is not None assert "quoteVolume" in ticker if EXCHANGES[exchangename].get("hasQuoteVolume"): assert ticker["quoteVolume"] is not None From 67a0040732748fd29c0fa675af24a6030dd1f5ba Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Mon, 28 Oct 2024 19:32:43 +0100 Subject: [PATCH 08/30] added hyperliquid to conftest --- tests/exchange_online/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index e9c594299..b815babec 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -339,6 +339,18 @@ EXCHANGES = { }, ], }, + "hyperliquid": { + "pair": "PURR/USDC", + "stake_currency": "USDC", + "hasQuoteVolume": False, + "timeframe": "1h", + "futures": True, + "orderbook_max_entries": 20, + "futures_pair": "BTC/USDC:USDC", + "hasQuoteVolumeFutures": True, + "leverage_tiers_public": False, + "leverage_in_spot_market": False, + }, } From cfa527e9af1bbc9bbb57ed6955f0833d20372ef1 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Mon, 28 Oct 2024 20:34:24 +0100 Subject: [PATCH 09/30] added tests for hyperliquid --- tests/exchange/test_hyperliquid.py | 319 +++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 tests/exchange/test_hyperliquid.py diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py new file mode 100644 index 000000000..b2f8fdd49 --- /dev/null +++ b/tests/exchange/test_hyperliquid.py @@ -0,0 +1,319 @@ +from datetime import datetime, timezone +from unittest.mock import MagicMock + +from tests.conftest import get_mock_coro, get_patched_exchange + + +def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): + # test if liq price calculated by dry_run_liquidation_price() is close to ccxt liq price + # testing different pairs with large/small prices, different leverages, long, short + markets = { + "BTC/USDC:USDC": {"limits": {"leverage": {"max": 50}}}, + "ETH/USDC:USDC": {"limits": {"leverage": {"max": 50}}}, + "SOL/USDC:USDC": {"limits": {"leverage": {"max": 20}}}, + "DOGE/USDC:USDC": {"limits": {"leverage": {"max": 20}}}, + } + positions = [ + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2458.5, + "side": "long", + "contracts": 0.015, + "collateral": 36.864593, + "leverage": 1.0, + "liquidationPrice": 0.86915825, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 63287.0, + "side": "long", + "contracts": 0.00039, + "collateral": 24.673292, + "leverage": 1.0, + "liquidationPrice": 22.37166537, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 146.82, + "side": "long", + "contracts": 0.16, + "collateral": 23.482979, + "leverage": 1.0, + "liquidationPrice": 0.05269872, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 145.83, + "side": "long", + "contracts": 0.33, + "collateral": 24.045107, + "leverage": 2.0, + "liquidationPrice": 74.83696193, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2459.5, + "side": "long", + "contracts": 0.0199, + "collateral": 24.454895, + "leverage": 2.0, + "liquidationPrice": 1243.0411908, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 62739.0, + "side": "long", + "contracts": 0.00077, + "collateral": 24.137992, + "leverage": 2.0, + "liquidationPrice": 31708.03843631, + }, + { + "symbol": "DOGE/USDC:USDC", + "entryPrice": 0.11586, + "side": "long", + "contracts": 437.0, + "collateral": 25.29769, + "leverage": 2.0, + "liquidationPrice": 0.05945697, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2642.8, + "side": "short", + "contracts": 0.019, + "collateral": 25.091876, + "leverage": 2.0, + "liquidationPrice": 3924.18322043, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 155.89, + "side": "short", + "contracts": 0.32, + "collateral": 24.924941, + "leverage": 2.0, + "liquidationPrice": 228.07847866, + }, + { + "symbol": "DOGE/USDC:USDC", + "entryPrice": 0.14333, + "side": "short", + "contracts": 351.0, + "collateral": 25.136807, + "leverage": 2.0, + "liquidationPrice": 0.20970228, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 68595.0, + "side": "short", + "contracts": 0.00069, + "collateral": 23.64871, + "leverage": 2.0, + "liquidationPrice": 101849.99354283, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 65536.0, + "side": "short", + "contracts": 0.00099, + "collateral": 21.604172, + "leverage": 3.0, + "liquidationPrice": 86493.46174617, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 173.06, + "side": "long", + "contracts": 0.6, + "collateral": 20.735658, + "leverage": 5.0, + "liquidationPrice": 142.05186667, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2545.5, + "side": "long", + "contracts": 0.0329, + "collateral": 20.909894, + "leverage": 4.0, + "liquidationPrice": 1929.23322895, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 67400.0, + "side": "short", + "contracts": 0.00031, + "collateral": 20.887308, + "leverage": 1.0, + "liquidationPrice": 133443.97317151, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2552.0, + "side": "short", + "contracts": 0.0327, + "collateral": 20.833393, + "leverage": 4.0, + "liquidationPrice": 3157.53150453, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 66930.0, + "side": "long", + "contracts": 0.0015, + "collateral": 20.043862, + "leverage": 5.0, + "liquidationPrice": 54108.51043771, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 67033.0, + "side": "long", + "contracts": 0.00121, + "collateral": 20.251817, + "leverage": 4.0, + "liquidationPrice": 50804.00091827, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2521.9, + "side": "long", + "contracts": 0.0237, + "collateral": 19.902091, + "leverage": 3.0, + "liquidationPrice": 1699.14071943, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 68139.0, + "side": "short", + "contracts": 0.00145, + "collateral": 19.72573, + "leverage": 5.0, + "liquidationPrice": 80933.61590987, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 178.29, + "side": "short", + "contracts": 0.11, + "collateral": 19.605036, + "leverage": 1.0, + "liquidationPrice": 347.82205322, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 176.23, + "side": "long", + "contracts": 0.33, + "collateral": 19.364946, + "leverage": 3.0, + "liquidationPrice": 120.56240404, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 173.08, + "side": "short", + "contracts": 0.33, + "collateral": 19.01881, + "leverage": 3.0, + "liquidationPrice": 225.08561715, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 68240.0, + "side": "short", + "contracts": 0.00105, + "collateral": 17.887922, + "leverage": 4.0, + "liquidationPrice": 84431.79820839, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2518.4, + "side": "short", + "contracts": 0.007, + "collateral": 17.62263, + "leverage": 1.0, + "liquidationPrice": 4986.05799151, + }, + { + "symbol": "ETH/USDC:USDC", + "entryPrice": 2533.2, + "side": "long", + "contracts": 0.0347, + "collateral": 17.555195, + "leverage": 5.0, + "liquidationPrice": 2047.7642302, + }, + { + "symbol": "DOGE/USDC:USDC", + "entryPrice": 0.13284, + "side": "long", + "contracts": 360.0, + "collateral": 15.943218, + "leverage": 3.0, + "liquidationPrice": 0.09082388, + }, + { + "symbol": "SOL/USDC:USDC", + "entryPrice": 163.11, + "side": "short", + "contracts": 0.48, + "collateral": 15.650731, + "leverage": 5.0, + "liquidationPrice": 190.94213618, + }, + { + "symbol": "BTC/USDC:USDC", + "entryPrice": 67141.0, + "side": "long", + "contracts": 0.00067, + "collateral": 14.979079, + "leverage": 3.0, + "liquidationPrice": 45236.52992613, + }, + ] + + api_mock = MagicMock() + get_mock_coro(return_value=markets) + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + default_conf["stake_currency"] = "USDC" + api_mock.load_markets = get_mock_coro(return_value=markets) + exchange = get_patched_exchange( + mocker, default_conf, api_mock, exchange="hyperliquid", mock_markets=False + ) + + for position in positions: + is_short = True if position["side"] == "short" else False + liq_price_returned = position["liquidationPrice"] + liq_price_calculated = exchange.dry_run_liquidation_price( + position["symbol"], + position["entryPrice"], + is_short, + position["contracts"], + position["collateral"], + position["leverage"], + position["collateral"], + [], + ) + assert abs(liq_price_calculated - liq_price_returned) / liq_price_returned < 0.0001 + + +def test_hyperliquid_get_funding_fees(default_conf, mocker): + now = datetime.now(timezone.utc) + exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid") + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees("BTC/USDC:USDC", 1, False, now) + assert exchange._fetch_and_calculate_funding_fees.call_count == 0 + + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid") + exchange._fetch_and_calculate_funding_fees = MagicMock() + exchange.get_funding_fees("BTC/USDC:USDC", 1, False, now) + + assert exchange._fetch_and_calculate_funding_fees.call_count == 1 From c4312fbb311fb28903eba6f729bb7e01265eb1ca Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Fri, 1 Nov 2024 19:14:37 +0100 Subject: [PATCH 10/30] _lev_prep: fixed typo. dry_run_liquidation_price: use wallet_balance instead of stake_amount --- freqtrade/exchange/hyperliquid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index d7c64e462..f37b9dc84 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -66,7 +66,7 @@ class Hyperliquid(Exchange): # Hyperliquid expects leverage to be an int leverage = int(leverage) # Hyperliquid needs the parameter leverage. - # Don't use set_leverage(), as this sets margin back to cross + # Don't use _set_leverage(), as this sets margin back to cross self.set_margin_mode(pair, self.margin_mode, params={"leverage": leverage}) def dry_run_liquidation_price( @@ -98,7 +98,7 @@ class Hyperliquid(Exchange): 'SOL/USDC:USDC': 43}} """ # Defining/renaming variables to match the documentation - isolated_margin = stake_amount + isolated_margin = wallet_balance position_size = amount price = open_rate position_value = price * position_size From bdb54f56d9744c9e35f9f3f88c83d3f6ab48fe9e Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Tue, 5 Nov 2024 21:21:11 +0100 Subject: [PATCH 11/30] Update exchanges.md - add Hyperliquid --- docs/exchanges.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index f55c45919..166e48a15 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -295,6 +295,45 @@ 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. +## Hyperliquid + +Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. This needs to be configured like this: + +```json +"exchange": { + "name": "hyperliquid", + "walletAddress": "your_eth_wallet_address", + "privateKey": "your_private_key", + // ... +} +``` +walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. + +privateKey also must be in hex format: `0x<64 hex characters>`. + +If needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your pivate key in python. First install eth_account: +```shell +$ pip3 install eth_account +``` + +Then run this in python (replace with your actual mnemonic phrase) : +```python +from eth_account import Account +Account.enable_unaudited_hdwallet_features() +words = "" +print(f"0x{Account.from_mnemonic(words)._private_key.hex()}") +``` + +Some general best practices (non exhaustive): +* Don't ever run this conversion online. Online tools 'facilitating' this conversion are likely scams and will steal your funds. +* Always keep your mnemonic phrase and private key (basically the same thing) private. +* Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. +* Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. +* Remember that if someone hacks the host you use for trading, or any other host you stored your private key / mnemonic on, you will lose the funds protected by that private key. That means the funds on that wallet and the funds deposited on Hyperliquid. +* If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet. + +Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed. + ## All exchanges Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. From 38eb4eed97bfc408e6324503d02e98d65c0f359e Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Tue, 5 Nov 2024 22:37:12 +0100 Subject: [PATCH 12/30] Reverted changes related to create_order_has_all_data --- freqtrade/exchange/exchange.py | 3 --- freqtrade/exchange/exchange_types.py | 2 -- freqtrade/exchange/hyperliquid.py | 1 - 3 files changed, 6 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3209b693f..f7fb8a9c7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -144,7 +144,6 @@ class Exchange: "trades_pagination": "time", # Possible are "time" or "id" "trades_pagination_arg": "since", "trades_has_history": False, - "create_order_has_all_data": True, # Set to False if create_order doesn't return all data "l2_limit_range": None, "l2_limit_range_required": True, # Allow Empty L2 limit (kucoin) "mark_ohlcv_price": "mark", @@ -1275,8 +1274,6 @@ class Exchange: rate_for_order, params, ) - if not self._ft_has.get("create_order_has_all_data"): - order = self._api.fetch_order(order["id"], pair) if order.get("status") is None: # Map empty status to open. order["status"] = "open" diff --git a/freqtrade/exchange/exchange_types.py b/freqtrade/exchange/exchange_types.py index 6bf5cb323..e9c58ec38 100644 --- a/freqtrade/exchange/exchange_types.py +++ b/freqtrade/exchange/exchange_types.py @@ -34,8 +34,6 @@ class FtHas(TypedDict, total=False): trades_pagination_arg: str trades_has_history: bool trades_pagination_overlap: bool - # Create order - create_order_has_all_data: bool # Orderbook l2_limit_range: Optional[list[int]] l2_limit_range_required: bool diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index f37b9dc84..1e2b7ab89 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -29,7 +29,6 @@ class Hyperliquid(Exchange): "exchange_has_overrides": {"fetchTrades": False}, "stoploss_order_types": {"limit": "limit"}, "funding_fee_timeframe": "1h", - "create_order_has_all_data": False, } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ From 0b6bf701ef451a09f382e51a31cf276d37c5116a Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Wed, 6 Nov 2024 10:29:23 +0100 Subject: [PATCH 13/30] Update exchanges.md - typo --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 166e48a15..14cad5f5e 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -311,7 +311,7 @@ walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily privateKey also must be in hex format: `0x<64 hex characters>`. -If needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your pivate key in python. First install eth_account: +If needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your private key in python. First install eth_account: ```shell $ pip3 install eth_account ``` From a71ab6e2aeeb60344419705da29f266752d82972 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Wed, 6 Nov 2024 21:39:56 +0100 Subject: [PATCH 14/30] Update exchanges.md - add hyperliquid stoploss_on_exchange note --- docs/exchanges.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 14cad5f5e..fc5658216 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -297,7 +297,11 @@ It's therefore required to pass the UID as well. ## Hyperliquid -Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. This needs to be configured like this: +!!! Tip "Stoploss on Exchange" + Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it. + +Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. +This needs to be configured like this: ```json "exchange": { From 2de4f425ff50786a08eb7ad86dd4578c43a4e496 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Wed, 6 Nov 2024 21:41:59 +0100 Subject: [PATCH 15/30] Update stoploss.md - add hyperliquid --- docs/stoploss.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index e0353d4da..024ead07b 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -36,6 +36,7 @@ The Order-type will be ignored if only one mode is available. | Gate | limit | | Okx | limit | | Kucoin | stop-limit, stop-market| +| Hyperliquid | limit | !!! Note "Tight stoploss" Do not set too low/tight stoploss value when using stop loss on exchange! From 96354ee1d8505219d17ae75395aa55500278aa72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Nov 2024 06:45:45 +0100 Subject: [PATCH 16/30] docs: fix hyperliquid docs formatting --- docs/exchanges.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index fc5658216..c4c9cf450 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -299,7 +299,7 @@ It's therefore required to pass the UID as well. !!! Tip "Stoploss on Exchange" Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it. - + Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. This needs to be configured like this: @@ -311,16 +311,19 @@ This needs to be configured like this: // ... } ``` -walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. + +walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. privateKey also must be in hex format: `0x<64 hex characters>`. If needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your private key in python. First install eth_account: + ```shell $ pip3 install eth_account ``` Then run this in python (replace with your actual mnemonic phrase) : + ```python from eth_account import Account Account.enable_unaudited_hdwallet_features() @@ -328,7 +331,8 @@ words = "" print(f"0x{Account.from_mnemonic(words)._private_key.hex()}") ``` -Some general best practices (non exhaustive): +Some general best practices (non exhaustive): + * Don't ever run this conversion online. Online tools 'facilitating' this conversion are likely scams and will steal your funds. * Always keep your mnemonic phrase and private key (basically the same thing) private. * Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. @@ -336,7 +340,7 @@ Some general best practices (non exhaustive): * Remember that if someone hacks the host you use for trading, or any other host you stored your private key / mnemonic on, you will lose the funds protected by that private key. That means the funds on that wallet and the funds deposited on Hyperliquid. * If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet. -Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed. +Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed. ## All exchanges From 5b7f08137f7bddb8a6162edec4ab2ab481f485aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Nov 2024 07:22:16 +0100 Subject: [PATCH 17/30] tests: improve hyperliquid tests --- tests/exchange/test_hyperliquid.py | 33 ++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index b2f8fdd49..e8e06af53 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -1,7 +1,9 @@ from datetime import datetime, timezone -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock -from tests.conftest import get_mock_coro, get_patched_exchange +import pytest + +from tests.conftest import EXMS, get_mock_coro, get_patched_exchange def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): @@ -278,7 +280,6 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): ] api_mock = MagicMock() - get_mock_coro(return_value=markets) default_conf["trading_mode"] = "futures" default_conf["margin_mode"] = "isolated" default_conf["stake_currency"] = "USDC" @@ -300,7 +301,7 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): position["collateral"], [], ) - assert abs(liq_price_calculated - liq_price_returned) / liq_price_returned < 0.0001 + assert pytest.approx(liq_price_returned, rel=0.0001) == liq_price_calculated def test_hyperliquid_get_funding_fees(default_conf, mocker): @@ -317,3 +318,27 @@ def test_hyperliquid_get_funding_fees(default_conf, mocker): exchange.get_funding_fees("BTC/USDC:USDC", 1, False, now) assert exchange._fetch_and_calculate_funding_fees.call_count == 1 + + +def test_hyperliquid_get_max_leverage(default_conf, mocker): + markets = { + "BTC/USDC:USDC": {"limits": {"leverage": {"max": 50}}}, + "ETH/USDC:USDC": {"limits": {"leverage": {"max": 50}}}, + "SOL/USDC:USDC": {"limits": {"leverage": {"max": 20}}}, + "DOGE/USDC:USDC": {"limits": {"leverage": {"max": 20}}}, + } + exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid") + assert exchange.get_max_leverage("BTC/USDC:USDC", 1) == 1.0 + + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + exchange = get_patched_exchange(mocker, default_conf, exchange="hyperliquid") + mocker.patch.multiple( + EXMS, + markets=PropertyMock(return_value=markets), + ) + + assert exchange.get_max_leverage("BTC/USDC:USDC", 1) == 50 + assert exchange.get_max_leverage("ETH/USDC:USDC", 20) == 50 + assert exchange.get_max_leverage("SOL/USDC:USDC", 50) == 20 + assert exchange.get_max_leverage("DOGE/USDC:USDC", 3) == 20 From 3cc97690a413f90569a7a0d9379a5bab86cf0305 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Nov 2024 08:11:20 +0100 Subject: [PATCH 18/30] tests: add hyperliquid lev_prep test --- tests/exchange/test_hyperliquid.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index e8e06af53..4605a8669 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -2,6 +2,7 @@ from datetime import datetime, timezone from unittest.mock import MagicMock, PropertyMock import pytest +from fastapi import params from tests.conftest import EXMS, get_mock_coro, get_patched_exchange @@ -342,3 +343,33 @@ def test_hyperliquid_get_max_leverage(default_conf, mocker): assert exchange.get_max_leverage("ETH/USDC:USDC", 20) == 50 assert exchange.get_max_leverage("SOL/USDC:USDC", 50) == 20 assert exchange.get_max_leverage("DOGE/USDC:USDC", 3) == 20 + + +def test_hyperliquid__lev_prep(default_conf, mocker): + api_mock = MagicMock() + api_mock.set_margin_mode = MagicMock() + type(api_mock).has = PropertyMock(return_value={"setMarginMode": True}) + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="hyperliquid") + exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy") + + assert api_mock.set_margin_mode.call_count == 0 + + # test in futures mode + api_mock.set_margin_mode.reset_mock() + default_conf["dry_run"] = False + + default_conf["trading_mode"] = "futures" + default_conf["margin_mode"] = "isolated" + + exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="hyperliquid") + exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy") + + assert api_mock.set_margin_mode.call_count == 1 + api_mock.set_margin_mode.assert_called_with("isolated", "BTC/USDC:USDC", {"leverage": 3}) + + api_mock.reset_mock() + + exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell") + + assert api_mock.set_margin_mode.call_count == 1 + api_mock.set_margin_mode.assert_called_with("isolated", "BTC/USDC:USDC", {"leverage": 19}) From 9b3764212f786c38635abbff298f931845aeb16b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2024 06:30:55 +0100 Subject: [PATCH 19/30] chore: fix bad import --- tests/exchange/test_hyperliquid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index 4605a8669..b754685f8 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -2,7 +2,6 @@ from datetime import datetime, timezone from unittest.mock import MagicMock, PropertyMock import pytest -from fastapi import params from tests.conftest import EXMS, get_mock_coro, get_patched_exchange From 0028df25646b839b923dc9209957e60339415263 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2024 06:58:21 +0100 Subject: [PATCH 20/30] feat(hyperliquid): fix a few settings to make spot work properly --- freqtrade/exchange/hyperliquid.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 1e2b7ab89..39ec41669 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -25,10 +25,15 @@ class Hyperliquid(Exchange): "l2_limit_range": [20], "trades_has_history": False, "tickers_have_bid_ask": False, - "stoploss_on_exchange": True, + "stoploss_on_exchange": False, "exchange_has_overrides": {"fetchTrades": False}, - "stoploss_order_types": {"limit": "limit"}, "funding_fee_timeframe": "1h", + "marketOrderRequiresPrice": True, + } + _ft_has_futures: FtHas = { + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "limit"}, + "marketOrderRequiresPrice": False, } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ From 31814e8bf91751bcad05bb299357373a6395cc87 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Tue, 12 Nov 2024 17:55:12 +0100 Subject: [PATCH 21/30] Update docs/stoploss.md - clarify that stoploss_on_exchange is only supported for hyperliquid futures Co-authored-by: Matthias --- docs/stoploss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stoploss.md b/docs/stoploss.md index 024ead07b..7442484de 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -36,7 +36,7 @@ The Order-type will be ignored if only one mode is available. | Gate | limit | | Okx | limit | | Kucoin | stop-limit, stop-market| -| Hyperliquid | limit | +| Hyperliquid (futures only) | limit | !!! Note "Tight stoploss" Do not set too low/tight stoploss value when using stop loss on exchange! From ca00871362382d44e18839bb3093a9708cde6df6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2024 18:05:51 +0100 Subject: [PATCH 22/30] chore: adopt hyperopt class to new ruff formatting --- freqtrade/exchange/hyperliquid.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 39ec41669..b1e5c28ce 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -2,7 +2,6 @@ import logging from datetime import datetime -from typing import Optional from freqtrade.constants import BuySell from freqtrade.enums import CandleType, MarginMode, TradingMode @@ -49,7 +48,7 @@ class Hyperliquid(Exchange): config.update(super()._ccxt_config) return config - def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float: + def get_max_leverage(self, pair: str, stake_amount: float | None) -> float: # There are no leverage tiers if self.trading_mode == TradingMode.FUTURES: return self.markets[pair]["limits"]["leverage"]["max"] @@ -57,7 +56,7 @@ class Hyperliquid(Exchange): return 1.0 def ohlcv_candle_limit( - self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None + self, timeframe: str, candle_type: CandleType, since_ms: int | None = None ) -> int: # Funding rate candles have a different limit if candle_type == CandleType.FUNDING_RATE: @@ -83,7 +82,7 @@ class Hyperliquid(Exchange): leverage: float, wallet_balance: float, # Or margin balance open_trades: list, - ) -> Optional[float]: + ) -> float | None: """ Optimized Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/liquidations From 8f946ea1b2effbb085894a745ec8a44b2aba288a Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Tue, 12 Nov 2024 18:13:31 +0100 Subject: [PATCH 23/30] Update exchanges.md - further clarify private key best practices --- docs/exchanges.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 34bab4c22..596e9e360 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -324,7 +324,7 @@ walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily privateKey also must be in hex format: `0x<64 hex characters>`. -If needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your private key in python. First install eth_account: +Some wallets, like metamask, support exporting the private key directly. However, if needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your private key in python. First install eth_account: ```shell $ pip3 install eth_account @@ -341,7 +341,10 @@ print(f"0x{Account.from_mnemonic(words)._private_key.hex()}") Some general best practices (non exhaustive): -* Don't ever run this conversion online. Online tools 'facilitating' this conversion are likely scams and will steal your funds. +* If you have to run the mnemonic -> private key conversion: + * Don't ever run the mnemonic -> private key conversion online. Online tools 'facilitating' this conversion are likely scams and will steal your funds. + * Beware of supplychainattacks, pip package poisoning etcetera. Make sure your python environment, including the eth_account module, are safe to use. Use at your own risk. +* Interact as little with the private key as possible. Store it in a seperate file from the config.json (secrets.json for example) that you never have to touch, and secure it. * Always keep your mnemonic phrase and private key (basically the same thing) private. * Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. * Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. From 702b50e7e912f71c594a21e84582983180e0d723 Mon Sep 17 00:00:00 2001 From: gaardiolor Date: Tue, 12 Nov 2024 18:22:19 +0100 Subject: [PATCH 24/30] Update exchanges.md - less details on the private key conversion --- docs/exchanges.md | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 596e9e360..c66ec4b69 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -320,32 +320,14 @@ This needs to be configured like this: } ``` -walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. - -privateKey also must be in hex format: `0x<64 hex characters>`. - -Some wallets, like metamask, support exporting the private key directly. However, if needed you can use your mnemonic phrase (the 12 or 24 words you had to write down when creating your wallet) to generate your private key in python. First install eth_account: - -```shell -$ pip3 install eth_account -``` - -Then run this in python (replace with your actual mnemonic phrase) : - -```python -from eth_account import Account -Account.enable_unaudited_hdwallet_features() -words = "" -print(f"0x{Account.from_mnemonic(words)._private_key.hex()}") -``` +* walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. +* privateKey also must be in hex format: `0x<64 hex characters>`, and can either be exported from your wallet or regenerated using your mnemonic phrase. Some general best practices (non exhaustive): -* If you have to run the mnemonic -> private key conversion: - * Don't ever run the mnemonic -> private key conversion online. Online tools 'facilitating' this conversion are likely scams and will steal your funds. - * Beware of supplychainattacks, pip package poisoning etcetera. Make sure your python environment, including the eth_account module, are safe to use. Use at your own risk. +* Beware of supplychainattacks, like pip package poisoning etcetera. However you export or (re-)generate your private key, make sure your environment is safe. * Interact as little with the private key as possible. Store it in a seperate file from the config.json (secrets.json for example) that you never have to touch, and secure it. -* Always keep your mnemonic phrase and private key (basically the same thing) private. +* Always keep your mnemonic phrase and private key private. * Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. * Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. * Remember that if someone hacks the host you use for trading, or any other host you stored your private key / mnemonic on, you will lose the funds protected by that private key. That means the funds on that wallet and the funds deposited on Hyperliquid. From 9360c109a9a9a6c93386be7a9bc5ef359572eef7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Nov 2024 19:32:42 +0100 Subject: [PATCH 25/30] docs: fix spelling --- docs/exchanges.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index c66ec4b69..abff4f01e 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -325,8 +325,8 @@ This needs to be configured like this: Some general best practices (non exhaustive): -* Beware of supplychainattacks, like pip package poisoning etcetera. However you export or (re-)generate your private key, make sure your environment is safe. -* Interact as little with the private key as possible. Store it in a seperate file from the config.json (secrets.json for example) that you never have to touch, and secure it. +* Beware of supply chain attacks, like pip package poisoning etcetera. However you export or (re-)generate your private key, make sure your environment is safe. +* Interact as little with the private key as possible. Store it in a separate file from the config.json (secrets.json for example) that you never have to touch, and secure it. * Always keep your mnemonic phrase and private key private. * Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. * Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. From c039a45def72f7e49b408fd6936361ee8bfe2b38 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2024 07:04:39 +0100 Subject: [PATCH 26/30] fix: hyperliquid market orders don't exist and always require price --- freqtrade/exchange/hyperliquid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index b1e5c28ce..2807f1be6 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -32,7 +32,6 @@ class Hyperliquid(Exchange): _ft_has_futures: FtHas = { "stoploss_on_exchange": True, "stoploss_order_types": {"limit": "limit"}, - "marketOrderRequiresPrice": False, } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ From a3a94e6ea43eaadb3bdfba907f74fcc947c3ec69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2024 07:06:09 +0100 Subject: [PATCH 27/30] chore: reorder hyperliquid docs, add note about order types --- docs/exchanges.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index abff4f01e..4b324346f 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -308,7 +308,7 @@ It's therefore required to pass the UID as well. !!! Tip "Stoploss on Exchange" Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it. -Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. +Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet. This needs to be configured like this: ```json @@ -323,17 +323,21 @@ This needs to be configured like this: * walletAddress must be in hex format: `0x<40 hex characters>`, and can be easily copied from your wallet. * privateKey also must be in hex format: `0x<64 hex characters>`, and can either be exported from your wallet or regenerated using your mnemonic phrase. -Some general best practices (non exhaustive): +Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed. -* Beware of supply chain attacks, like pip package poisoning etcetera. However you export or (re-)generate your private key, make sure your environment is safe. -* Interact as little with the private key as possible. Store it in a separate file from the config.json (secrets.json for example) that you never have to touch, and secure it. -* Always keep your mnemonic phrase and private key private. -* Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. -* Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. -* Remember that if someone hacks the host you use for trading, or any other host you stored your private key / mnemonic on, you will lose the funds protected by that private key. That means the funds on that wallet and the funds deposited on Hyperliquid. -* If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet. +!!! Note "Hyperliquid general usage Notes" + Hyperliquid does not support market orders, however ccxt will simulate market orders by placing limit orders with a maximum slippage of 5%. + Unfortunately, hyperliquid only offers 5000 historic candles, so backtesting will either need to build candles historically (by waiting and downloading the data incrementally over time) - or will be limited to the last 5000 candles. + +!!! Info "Some general best practices (non exhaustive)" + * Beware of supply chain attacks, like pip package poisoning etcetera. However you export or (re-)generate your private key, make sure your environment is safe. + * Interact as little with the private key as possible. Store it in a separate file from the config.json (secrets.json for example) that you never have to touch, and secure it. + * Always keep your mnemonic phrase and private key private. + * Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet. + * Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet / private key to trade on Hyperliquid. + * Remember that if someone hacks the host you use for trading, or any other host you stored your private key / mnemonic on, you will lose the funds protected by that private key. That means the funds on that wallet and the funds deposited on Hyperliquid. + * If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet. -Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed. ## All exchanges From a02f63dcc3ca058e50af15c36144c2851d5e2b3a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2024 07:06:43 +0100 Subject: [PATCH 28/30] chore: add hyperliquid to supported exchange list --- freqtrade/exchange/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py index cd451062f..4fd9cea63 100644 --- a/freqtrade/exchange/common.py +++ b/freqtrade/exchange/common.py @@ -58,6 +58,7 @@ SUPPORTED_EXCHANGES = [ "bybit", "gate", "htx", + "hyperliquid", "kraken", "okx", ] From 5c4b9e59d1ca8c9ac0d47ddd541f7205fb0311dc Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Nov 2024 07:08:27 +0100 Subject: [PATCH 29/30] chore: add Hyperliquid as supported exchange to docs --- README.md | 2 ++ docs/index.md | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9f4fc51fb..5a4330bb9 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [Bybit](https://bybit.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [HTX](https://www.htx.com/) (Former Huobi) +- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX) - [X] [Kraken](https://kraken.com/) - [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ @@ -41,6 +42,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [Binance](https://www.binance.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX) - [X] [OKX](https://okx.com/) - [X] [Bybit](https://bybit.com/) diff --git a/docs/index.md b/docs/index.md index d6dca4880..87b76b6ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -40,11 +40,12 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange. - [X] [Binance](https://www.binance.com/) -- [X] [Bitmart](https://bitmart.com/) - [X] [BingX](https://bingx.com/invite/0EM9RX) +- [X] [Bitmart](https://bitmart.com/) - [X] [Bybit](https://bybit.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [HTX](https://www.htx.com/) (Former Huobi) +- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX) - [X] [Kraken](https://kraken.com/) - [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others through ccxt](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ @@ -52,9 +53,10 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, ### Supported Futures Exchanges (experimental) - [X] [Binance](https://www.binance.com/) +- [X] [Bybit](https://bybit.com/) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX) - [X] [OKX](https://okx.com/) -- [X] [Bybit](https://bybit.com/) Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in. From 46f2e693bcaefcc29348926d071deaa0998c5e4a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Nov 2024 21:57:58 +0100 Subject: [PATCH 30/30] chore: bump ccxt to 4.4.31 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c27ab0201..b80d8f7e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ bottleneck==1.4.2 numexpr==2.10.1 pandas-ta==0.3.14b -ccxt==4.4.29 +ccxt==4.4.31 cryptography==42.0.8; platform_machine == 'armv7l' cryptography==43.0.3; platform_machine != 'armv7l' aiohttp==3.10.10