mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-01-26 08:50:47 +00:00
@@ -33,6 +33,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
|
|||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/) (Former OKEX)
|
||||||
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [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] [Binance](https://www.binance.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [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] [OKX](https://okx.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,42 @@ It's therefore required to pass the UID as well.
|
|||||||
!!! Warning "Necessary Verification"
|
!!! 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.
|
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
|
||||||
|
|
||||||
|
!!! 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": {
|
||||||
|
"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>`, and can either be exported from your wallet or regenerated using your mnemonic phrase.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
!!! 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.
|
||||||
|
|
||||||
|
|
||||||
## All exchanges
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -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.
|
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] [Binance](https://www.binance.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
|
||||||
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||||
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
- [X] [Bybit](https://bybit.com/)
|
- [X] [Bybit](https://bybit.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
||||||
|
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/) (Former OKEX)
|
||||||
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](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)
|
### Supported Futures Exchanges (experimental)
|
||||||
|
|
||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
|
||||||
- [X] [OKX](https://okx.com/)
|
|
||||||
- [X] [Bybit](https://bybit.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/)
|
||||||
|
|
||||||
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
|
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ The Order-type will be ignored if only one mode is available.
|
|||||||
| Gate | limit |
|
| Gate | limit |
|
||||||
| Okx | limit |
|
| Okx | limit |
|
||||||
| Kucoin | stop-limit, stop-market|
|
| Kucoin | stop-limit, stop-market|
|
||||||
|
| Hyperliquid (futures only) | limit |
|
||||||
|
|
||||||
!!! Note "Tight stoploss"
|
!!! Note "Tight stoploss"
|
||||||
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
|
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ SUPPORTED_EXCHANGES = [
|
|||||||
"bybit",
|
"bybit",
|
||||||
"gate",
|
"gate",
|
||||||
"htx",
|
"htx",
|
||||||
|
"hyperliquid",
|
||||||
"kraken",
|
"kraken",
|
||||||
"okx",
|
"okx",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
"""Hyperliquid exchange subclass"""
|
"""Hyperliquid exchange subclass"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from freqtrade.enums import TradingMode
|
from freqtrade.constants import BuySell
|
||||||
|
from freqtrade.enums import CandleType, MarginMode, TradingMode
|
||||||
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.exchange.exchange_types import FtHas
|
from freqtrade.exchange.exchange_types import FtHas
|
||||||
|
|
||||||
@@ -16,20 +19,146 @@ class Hyperliquid(Exchange):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_ft_has: FtHas = {
|
_ft_has: FtHas = {
|
||||||
# Only the most recent 5000 candles are available according to the
|
|
||||||
# exchange's API documentation.
|
|
||||||
"ohlcv_has_history": False,
|
"ohlcv_has_history": False,
|
||||||
"ohlcv_candle_limit": 5000,
|
"ohlcv_candle_limit": 5000,
|
||||||
"trades_has_history": False, # Trades endpoint doesn't seem available.
|
"l2_limit_range": [20],
|
||||||
|
"trades_has_history": False,
|
||||||
|
"tickers_have_bid_ask": False,
|
||||||
|
"stoploss_on_exchange": False,
|
||||||
"exchange_has_overrides": {"fetchTrades": False},
|
"exchange_has_overrides": {"fetchTrades": False},
|
||||||
|
"funding_fee_timeframe": "1h",
|
||||||
|
"marketOrderRequiresPrice": True,
|
||||||
}
|
}
|
||||||
|
_ft_has_futures: FtHas = {
|
||||||
|
"stoploss_on_exchange": True,
|
||||||
|
"stoploss_order_types": {"limit": "limit"},
|
||||||
|
}
|
||||||
|
|
||||||
|
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||||
|
(TradingMode.FUTURES, MarginMode.ISOLATED)
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _ccxt_config(self) -> dict:
|
def _ccxt_config(self) -> dict:
|
||||||
# Parameters to add directly to ccxt sync/async initialization.
|
# ccxt Hyperliquid defaults to swap
|
||||||
# ccxt defaults to swap mode.
|
|
||||||
config = {}
|
config = {}
|
||||||
if self.trading_mode == TradingMode.SPOT:
|
if self.trading_mode == TradingMode.SPOT:
|
||||||
config.update({"options": {"defaultType": "spot"}})
|
config.update({"options": {"defaultType": "spot"}})
|
||||||
config.update(super()._ccxt_config)
|
config.update(super()._ccxt_config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
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"]
|
||||||
|
else:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
def ohlcv_candle_limit(
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
open_trades: list,
|
||||||
|
) -> float | None:
|
||||||
|
"""
|
||||||
|
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 = wallet_balance
|
||||||
|
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 dividing 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.
|
||||||
|
"""
|
||||||
|
# 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)
|
||||||
|
except ExchangeError:
|
||||||
|
logger.warning(f"Could not update funding fees for {pair}.")
|
||||||
|
return 0.0
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ bottleneck==1.4.2
|
|||||||
numexpr==2.10.1
|
numexpr==2.10.1
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.4.29
|
ccxt==4.4.31
|
||||||
cryptography==42.0.8; platform_machine == 'armv7l'
|
cryptography==42.0.8; platform_machine == 'armv7l'
|
||||||
cryptography==43.0.3; platform_machine != 'armv7l'
|
cryptography==43.0.3; platform_machine != 'armv7l'
|
||||||
aiohttp==3.10.10
|
aiohttp==3.10.10
|
||||||
|
|||||||
374
tests/exchange/test_hyperliquid.py
Normal file
374
tests/exchange/test_hyperliquid.py
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
from datetime import datetime, timezone
|
||||||
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tests.conftest import EXMS, 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()
|
||||||
|
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 pytest.approx(liq_price_returned, rel=0.0001) == liq_price_calculated
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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})
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -118,9 +118,10 @@ class TestCCXTExchange:
|
|||||||
tickers = exch.get_tickers()
|
tickers = exch.get_tickers()
|
||||||
assert pair in tickers
|
assert pair in tickers
|
||||||
assert "ask" in tickers[pair]
|
assert "ask" in tickers[pair]
|
||||||
assert tickers[pair]["ask"] is not None
|
|
||||||
assert "bid" in tickers[pair]
|
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]
|
assert "quoteVolume" in tickers[pair]
|
||||||
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
||||||
assert tickers[pair]["quoteVolume"] is not None
|
assert tickers[pair]["quoteVolume"] is not None
|
||||||
@@ -150,9 +151,10 @@ class TestCCXTExchange:
|
|||||||
|
|
||||||
ticker = exch.fetch_ticker(pair)
|
ticker = exch.fetch_ticker(pair)
|
||||||
assert "ask" in ticker
|
assert "ask" in ticker
|
||||||
assert ticker["ask"] is not None
|
|
||||||
assert "bid" in ticker
|
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
|
assert "quoteVolume" in ticker
|
||||||
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
if EXCHANGES[exchangename].get("hasQuoteVolume"):
|
||||||
assert ticker["quoteVolume"] is not None
|
assert ticker["quoteVolume"] is not None
|
||||||
|
|||||||
Reference in New Issue
Block a user