Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ hyperliquid-*.tar

# Temporary files, for example, from tests.
/tmp/

.idea
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def spot_ctxs, do: Cache.get(:spot_ctxs)
You may also note some commonly used util methods in the Cache which can be used like this:

```elixir
Hyperliquid.Cache.asset_from_coin("SOL")
Hyperliquid.Cache.asset_name_to_index("SOL")
5

Hyperliquid.Cache.decimals_from_coin("SOL")
Expand Down
114 changes: 92 additions & 22 deletions lib/hyperliquid/cache/cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ defmodule Hyperliquid.Cache do
spot_pairs = Map.get(spot_meta, "universe")
tokens = Map.get(spot_meta, "tokens")

asset_map = Map.merge(
create_asset_map(meta),
create_asset_map(spot_meta, 10_000)
)
assets_name_to_coin_map = Map.merge(
create_perp_assets_name_to_coin_map(meta),
create_spot_assets_name_to_coin_map(spot_meta)
)

assets_name_to_index_map = Map.merge(
create_perp_assets_name_to_index_map(meta),
create_spot_assets_name_to_index_map(spot_meta)
)

decimal_map = Map.merge(
create_decimal_map(meta),
Expand All @@ -46,34 +51,71 @@ defmodule Hyperliquid.Cache do
Cachex.put!(@cache, :meta, meta)
Cachex.put!(@cache, :spot_meta, spot_meta)
Cachex.put!(@cache, :all_mids, all_mids)
Cachex.put!(@cache, :asset_map, asset_map)
Cachex.put!(@cache, :decimal_map, decimal_map)
Cachex.put!(@cache, :perps, perps)
Cachex.put!(@cache, :spot_pairs, spot_pairs)
Cachex.put!(@cache, :tokens, tokens)
Cachex.put!(@cache, :ctxs, ctxs)
Cachex.put!(@cache, :spot_ctxs, spot_ctxs)
Cachex.put!(@cache, :assets_name_to_index_map, assets_name_to_index_map)
Cachex.put!(@cache, :assets_name_to_coin_map, assets_name_to_coin_map)
end

def meta, do: Cache.get(:meta)
def spot_meta, do: Cache.get(:spot_meta)
def all_mids, do: Cache.get(:all_mids)
def asset_map, do: Cache.get(:asset_map)
def decimal_map, do: Cache.get(:decimal_map)
def perps, do: Cache.get(:perps)
def spot_pairs, do: Cache.get(:spot_pairs)
def tokens, do: Cache.get(:tokens)
def ctxs, do: Cache.get(:ctxs)
def spot_ctxs, do: Cache.get(:spot_ctxs)
def meta, do: Cache.get(:meta)
def spot_meta, do: Cache.get(:spot_meta)
def all_mids, do: Cache.get(:all_mids)
def decimal_map, do: Cache.get(:decimal_map)
def perps, do: Cache.get(:perps)
def spot_pairs, do: Cache.get(:spot_pairs)
def tokens, do: Cache.get(:tokens)
def ctxs, do: Cache.get(:ctxs)
def spot_ctxs, do: Cache.get(:spot_ctxs)
def assets_name_to_index_map, do: Cache.get(:assets_name_to_index_map)
def assets_name_to_coin_map, do: Cache.get(:assets_name_to_coin_map)

###### Setters ######
defp create_asset_map(data, buffer \\ 0) do
defp create_perp_assets_name_to_index_map(data) do
perp_buffer = 0

data
|> Map.get("universe")
|> Enum.with_index(&{&1["name"], &2 + buffer})
|> Enum.with_index(&{&1["name"], &2 + perp_buffer})
|> Enum.into(%{})
end

defp create_spot_assets_name_to_index_map(data) do
spot_buffer = 10_000
tokens = Map.get(data, "tokens")

data
|> Map.get("universe")
|> Enum.reduce(%{}, fn spot_asset, spot_assets_index ->
[base, quote] = spot_asset["tokens"]
spot_asset_name = "#{Enum.at(tokens, base)["name"]}/#{Enum.at(tokens, quote)["name"]}"
Map.put(spot_assets_index, spot_asset_name, spot_asset["index"] + spot_buffer)
end)
end

defp create_perp_assets_name_to_coin_map(data) do
data
|> Map.get("universe")
|> Enum.reduce(%{}, fn perp_assets, perp_assets_name_to_coin_map ->
Map.put(perp_assets_name_to_coin_map, perp_assets["name"], perp_assets["name"])
end)
end

defp create_spot_assets_name_to_coin_map(data) do
tokens = Map.get(data, "tokens")

data
|> Map.get("universe")
|> Enum.reduce(%{}, fn spot_asset, spot_assets_name_to_coin_map ->
[base, quote] = spot_asset["tokens"]
spot_asset_name = "#{Enum.at(tokens, base)["name"]}/#{Enum.at(tokens, quote)["name"]}"
Map.put(spot_assets_name_to_coin_map, spot_asset_name, spot_asset["name"])
end)
end

defp create_decimal_map(data) do
data
|> Map.get("universe")
Expand All @@ -91,24 +133,52 @@ defmodule Hyperliquid.Cache do
###### Helpers ######

@doc """
Retrieves the asset index for a given coin symbol.
Retrieves the asset index for a given asset name.

## Parameters

- `coin`: The coin symbol (e.g., "BTC", "ETH")
- `name`: The asset name (e.g., "BTC", "ETH" for perp assets or "PURR/USDC", "HFUN/USDC" for spot assets)

## Returns

The asset index corresponding to the given coin symbol, or nil if not found.
The asset index corresponding to the given asset name, or nil if not found.

## Example

iex> Hyperliquid.Cache.asset_from_coin("SOL")
iex> Hyperliquid.Cache.asset_name_to_index("SOL")
5

iex> Hyperliquid.Cache.asset_name_to_index("PURR/USDC")
10000
"""
def asset_from_coin(coin), do: Cache.get(:asset_map)[coin]
def asset_name_to_index(name), do: Cache.get(:assets_name_to_index_map)[name]
def decimals_from_coin(coin), do: Cache.get(:decimal_map)[coin]


@doc """
Retrieves the coin symbol for a given asset name.

## Parameters

- `name`: The asset name (e.g., "BTC", "ETH" for perp assets or "PURR/USDC", "HFUN/USDC" for spot assets)

## Returns

The coin symbol corresponding to the given asset name, or nil if not found.

## Example

iex> Hyperliquid.Cache.asset_name_to_coin("SOL")
SOL

iex> Hyperliquid.Cache.asset_name_to_coin("PURR/USDC")
PURR/USDC

iex> Hyperliquid.Cache.asset_name_to_coin("HFUN/USDC")
@1
"""
def asset_name_to_coin(name), do: Cache.get(:assets_name_to_coin_map)[name]

def get_token_by_index(index), do:
Cache.get(:tokens)
|> Enum.find(& &1["index"] == index)
Expand Down
6 changes: 3 additions & 3 deletions lib/hyperliquid/orders/order_wire.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ defmodule Hyperliquid.Orders.OrderWire do
The OrderWire struct is typically used within order placement functions in the Orders module. Here's an example
of how it is used for a `limit_order` function:

def limit_order(coin, sz, is_buy?, px, tif \\ "gtc", reduce? \\ false, vault_address \\ nil) do
def limit_order(asset_name, sz, is_buy?, px, tif \\ "gtc", reduce? \\ false, vault_address \\ nil) do
trigger = trigger_from_order_type(tif)
asset = Cache.asset_from_coin(coin)
asset_index = Cache.asset_name_to_index(asset_name)

OrderWire.new(asset, is_buy?, px, sz, reduce?, trigger)
OrderWire.new(asset_index, is_buy?, px, sz, reduce?, trigger)
|> OrderWire.purify()
|> Exchange.place_order("na", vault_address)
end
Expand Down
38 changes: 21 additions & 17 deletions lib/hyperliquid/orders/orders.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ defmodule Hyperliquid.Orders do

Usage examples:

# Retrieve mid-price for a coin
mid_price = Hyperliquid.Orders.get_midprice("SOL")
135.545
# Retrieve mid-price for an asset
mid_price = Hyperliquid.Orders.get_midprice("SOL")
135.545

mid_price = Hyperliquid.Orders.get_midprice("PURR/USDC")
0.18546

# Place a market buy order
Hyperliquid.Orders.market_buy("ETH", 1.0, "0x123...")
Expand Down Expand Up @@ -84,7 +87,8 @@ defmodule Hyperliquid.Orders do

@default_slippage 0.05

def get_midprice(coin) do
def get_midprice(asset_name) do
coin = Cache.asset_name_to_coin(asset_name)
mids = Cache.all_mids() || fetch_mids()
coin = String.to_existing_atom(coin)

Expand All @@ -101,8 +105,8 @@ defmodule Hyperliquid.Orders do
end
end

def slippage_price(coin, buy?, slippage \\ @default_slippage, px \\ nil) do
px = px || get_midprice(coin)
def slippage_price(asset_name, buy?, slippage \\ @default_slippage, px \\ nil) do
px = px || get_midprice(asset_name)
px = if buy?, do: px * (1 + slippage), else: px * (1 - slippage)

case PriceConverter.convert_price(px, :perp) do
Expand All @@ -124,27 +128,27 @@ defmodule Hyperliquid.Orders do

def trigger_from_order_type(type) when is_map(type), do: %{trigger: type}

def market_buy(coin, sz, vault_address \\ nil), do:
market_order(coin, sz, true, false, vault_address, nil, @default_slippage)
def market_buy(asset_name, sz, vault_address \\ nil), do:
market_order(asset_name, sz, true, false, vault_address, nil, @default_slippage)

def market_sell(coin, sz, vault_address \\ nil), do:
market_order(coin, sz, false, false, vault_address, nil, @default_slippage)
def market_sell(asset_name, sz, vault_address \\ nil), do:
market_order(asset_name, sz, false, false, vault_address, nil, @default_slippage)

def market_order(coin, sz, buy?, reduce?, vault_address \\ nil, px \\ nil, slippage \\ @default_slippage) do
px = slippage_price(coin, buy?, slippage, px)
def market_order(asset_name, sz, buy?, reduce?, vault_address \\ nil, px \\ nil, slippage \\ @default_slippage) do
px = slippage_price(asset_name, buy?, slippage, px)
trigger = trigger_from_order_type("ioc")
asset = Cache.asset_from_coin(coin)
asset_index = Cache.asset_name_to_index(asset_name)

OrderWire.new(asset, buy?, px, sz, reduce?, trigger)
OrderWire.new(asset_index, buy?, px, sz, reduce?, trigger)
|> OrderWire.purify()
|> Exchange.place_order("na", vault_address)
end

def limit_order(coin, sz, buy?, px, tif \\ "gtc", reduce? \\ false, vault_address \\ nil) do
def limit_order(asset_name, sz, buy?, px, tif \\ "gtc", reduce? \\ false, vault_address \\ nil) do
trigger = trigger_from_order_type(tif)
asset = Cache.asset_from_coin(coin)
asset_index = Cache.asset_name_to_index(asset_name)

OrderWire.new(asset, buy?, px, sz, reduce?, trigger)
OrderWire.new(asset_index, buy?, px, sz, reduce?, trigger)
|> OrderWire.purify()
|> Exchange.place_order("na", vault_address)
end
Expand Down