|
| 1 | +from typing import Dict, Optional, Tuple |
| 2 | + |
1 | 3 | import requests
|
2 | 4 | import streamlit as st
|
3 | 5 |
|
| 6 | +HL_BASE_URL = "https://api.hyperliquid.xyz/info" |
| 7 | +DRIFT_BASE_URL = "https://dlob.drift.trade/l2" |
4 | 8 |
|
5 |
| -def fetch_orderbook_data(coin, size): |
6 |
| - post_url = "https://api.hyperliquid.xyz/info" |
7 |
| - payload = {"type": "metaAndAssetCtxs"} |
8 |
| - payload2 = {"type": "l2Book", "coin": coin} |
9 |
| - |
10 |
| - post_headers = {"Content-Type": "application/json"} |
11 | 9 |
|
12 |
| - results = {} |
| 10 | +def fetch_hyperliquid_data(coin: str) -> Tuple[Optional[Dict], Optional[Dict]]: |
| 11 | + meta = requests.post( |
| 12 | + HL_BASE_URL, |
| 13 | + json={"type": "metaAndAssetCtxs"}, |
| 14 | + headers={"Content-Type": "application/json"}, |
| 15 | + ).json() |
| 16 | + book = requests.post( |
| 17 | + HL_BASE_URL, |
| 18 | + json={"type": "l2Book", "coin": coin}, |
| 19 | + headers={"Content-Type": "application/json"}, |
| 20 | + ).json() |
| 21 | + return meta, book |
13 | 22 |
|
14 |
| - for nom, pay in [("hl_cxt", payload), ("hl_book", payload2)]: |
15 |
| - post_response = requests.post(post_url, json=pay, headers=post_headers) |
16 |
| - if post_response.status_code == 200: |
17 |
| - results[nom] = post_response.json() |
18 |
| - else: |
19 |
| - print("Error:", post_response.text, "\n") |
20 | 23 |
|
21 |
| - get_url = "https://dlob.drift.trade/l2" |
22 |
| - get_params = { |
23 |
| - "marketName": coin + "-PERP", |
24 |
| - "depth": 5, |
| 24 | +def fetch_drift_data(coin: str) -> Optional[Dict]: |
| 25 | + params = { |
| 26 | + "marketName": f"{coin}-PERP", |
| 27 | + "depth": 500, |
25 | 28 | "includeOracle": "true",
|
26 | 29 | "includeVamm": "true",
|
27 | 30 | }
|
| 31 | + return requests.get(DRIFT_BASE_URL, params=params).json() |
28 | 32 |
|
29 |
| - get_response = requests.get(get_url, params=get_params) |
30 |
| - if not get_response.status_code == 200: |
31 |
| - print("Error:", get_response.text) |
32 |
| - |
33 |
| - results["dr_book"] = get_response.json() |
34 |
| - |
35 |
| - def calculate_average_fill_price_dr(order_book, volume): |
36 |
| - volume = volume |
37 |
| - |
38 |
| - bids = order_book["bids"] |
39 |
| - asks = order_book["asks"] |
40 |
| - |
41 |
| - print(f'{float(bids[0]["price"])/1e6}/{float(asks[0]["price"])/1e6}') |
42 |
| - |
43 |
| - def average_price(levels, volume, is_buy): |
44 |
| - total_volume = 0 |
45 |
| - total_cost = 0.0 |
46 |
| - |
47 |
| - for level in levels: |
48 |
| - # Price is in 1e6 precision, size is in 1e9 precision |
49 |
| - price = float(level["price"]) / 1e6 |
50 |
| - size = float(level["size"]) / 1e9 |
51 | 33 |
|
52 |
| - if total_volume + size >= volume: |
53 |
| - # Only take the remaining required volume at this level |
54 |
| - remaining_volume = volume - total_volume |
55 |
| - total_cost += remaining_volume * price |
56 |
| - total_volume += remaining_volume |
57 |
| - break |
58 |
| - else: |
59 |
| - # Take the whole size at this level |
60 |
| - total_cost += size * price |
61 |
| - total_volume += size |
| 34 | +def calculate_avg_fill_price( |
| 35 | + levels: list, volume: float, is_drift: bool = False |
| 36 | +) -> float: |
| 37 | + total_volume = 0 |
| 38 | + total_cost = 0.0 |
62 | 39 |
|
63 |
| - if total_volume < volume: |
64 |
| - raise ValueError( |
65 |
| - "Insufficient volume in the order book to fill the order" |
66 |
| - ) |
67 |
| - |
68 |
| - return total_cost / volume |
69 |
| - |
70 |
| - try: |
71 |
| - buy_price = average_price(asks, volume, is_buy=True) |
72 |
| - sell_price = average_price(bids, volume, is_buy=False) |
73 |
| - except ValueError as e: |
74 |
| - return str(e) |
75 |
| - |
76 |
| - return {"average_buy_price": buy_price, "average_sell_price": sell_price} |
77 |
| - |
78 |
| - def calculate_average_fill_price_hl(order_book, volume): |
79 |
| - buy_levels = order_book["levels"][1] # Bids (lower prices first) |
80 |
| - sell_levels = order_book["levels"][0] # Asks (higher prices first) |
81 |
| - |
82 |
| - def average_price(levels, volume): |
83 |
| - total_volume = 0 |
84 |
| - total_cost = 0.0 |
| 40 | + for level in levels: |
| 41 | + if is_drift: |
| 42 | + price = float(level["price"]) / 1e6 |
| 43 | + size = float(level["size"]) / 1e9 |
| 44 | + else: |
| 45 | + price = float(level["px"]) |
| 46 | + size = float(level["sz"]) |
| 47 | + |
| 48 | + if total_volume + size >= volume: |
| 49 | + remaining_volume = volume - total_volume |
| 50 | + total_cost += remaining_volume * price |
| 51 | + total_volume += remaining_volume |
| 52 | + break |
| 53 | + else: |
| 54 | + total_cost += size * price |
| 55 | + total_volume += size |
85 | 56 |
|
86 |
| - for level in levels: |
87 |
| - px = float(level["px"]) |
88 |
| - sz = float(level["sz"]) |
| 57 | + if total_volume < volume: |
| 58 | + raise ValueError("Insufficient volume in the order book") |
89 | 59 |
|
90 |
| - if total_volume + sz >= volume: |
91 |
| - # Only take the remaining required volume at this level |
92 |
| - remaining_volume = volume - total_volume |
93 |
| - total_cost += remaining_volume * px |
94 |
| - total_volume += remaining_volume |
95 |
| - break |
96 |
| - else: |
97 |
| - # Take the whole size at this level |
98 |
| - total_cost += sz * px |
99 |
| - total_volume += sz |
| 60 | + return total_cost / volume |
100 | 61 |
|
101 |
| - if total_volume < volume: |
102 |
| - raise ValueError( |
103 |
| - "Insufficient volume in the order book to fill the order" |
104 |
| - ) |
105 | 62 |
|
106 |
| - return total_cost / volume |
| 63 | +def get_orderbook_data(coin: str, size: float) -> Tuple[Dict, Dict, float, Dict]: |
| 64 | + hl_meta, hl_book = fetch_hyperliquid_data(coin) |
| 65 | + drift_book = fetch_drift_data(coin) |
107 | 66 |
|
108 |
| - try: |
109 |
| - buy_price = average_price(buy_levels, volume) |
110 |
| - sell_price = average_price(sell_levels, volume) |
111 |
| - except ValueError as e: |
112 |
| - return str(e) |
| 67 | + try: |
| 68 | + hl_buy = calculate_avg_fill_price(hl_book["levels"][1], size) |
| 69 | + hl_sell = calculate_avg_fill_price(hl_book["levels"][0], size) |
| 70 | + hl_prices = {"average_buy_price": hl_buy, "average_sell_price": hl_sell} |
| 71 | + except (ValueError, KeyError) as e: |
| 72 | + hl_prices = str(e) |
113 | 73 |
|
114 |
| - return {"average_buy_price": buy_price, "average_sell_price": sell_price} |
| 74 | + try: |
| 75 | + drift_buy = calculate_avg_fill_price(drift_book["asks"], size, is_drift=True) |
| 76 | + drift_sell = calculate_avg_fill_price(drift_book["bids"], size, is_drift=True) |
| 77 | + drift_prices = { |
| 78 | + "average_buy_price": drift_buy, |
| 79 | + "average_sell_price": drift_sell, |
| 80 | + } |
| 81 | + except (ValueError, KeyError) as e: |
| 82 | + drift_prices = str(e) |
115 | 83 |
|
116 |
| - r = calculate_average_fill_price_hl(results["hl_book"], size) |
117 |
| - d = calculate_average_fill_price_dr(results["dr_book"], size) |
118 |
| - return (r, d, results["dr_book"]["oracle"] / 1e6, results["hl_cxt"]) |
| 84 | + return hl_prices, drift_prices, drift_book["oracle"] / 1e6, hl_meta |
119 | 85 |
|
120 | 86 |
|
121 | 87 | def orderbook_page():
|
122 |
| - s1, s2 = st.columns(2) |
| 88 | + st.title("Orderbook comparison") |
| 89 | + col1, col2 = st.columns(2) |
| 90 | + coin = col1.selectbox("Coin:", ["SOL", "BTC", "ETH"]) |
| 91 | + size = col2.number_input("Size:", min_value=0.1, value=1.0, help="in base units") |
| 92 | + |
| 93 | + hl_prices, drift_prices, drift_oracle, hl_meta = get_orderbook_data(coin, size) |
123 | 94 |
|
124 |
| - coin = s1.selectbox("coin:", ["SOL", "BTC", "ETH"]) |
125 |
| - size = s2.number_input("size:", min_value=0.1, value=1.0, help="in base units") |
126 |
| - hl, dr, dr_oracle, hl_ctx = fetch_orderbook_data(coin, size) |
| 95 | + coin_idx = next( |
| 96 | + i for i, x in enumerate(hl_meta[0]["universe"]) if coin == x["name"] |
| 97 | + ) |
| 98 | + col1, col2 = st.columns(2) |
127 | 99 |
|
128 |
| - uni_id = [i for (i, x) in enumerate(hl_ctx[0]["universe"]) if coin == x["name"]] |
| 100 | + with col1: |
| 101 | + st.header("Hyperliquid") |
| 102 | + st.write(float(hl_meta[1][coin_idx]["oraclePx"])) |
| 103 | + st.write(hl_prices) |
129 | 104 |
|
130 |
| - o1, o2 = st.columns(2) |
131 |
| - o1.header("hyperliquid") |
132 |
| - o1.write(float(hl_ctx[1][uni_id[0]]["oraclePx"])) |
133 |
| - o1.write(hl) |
| 105 | + with col2: |
| 106 | + st.header("Drift") |
| 107 | + st.write(drift_oracle) |
| 108 | + st.write(drift_prices) |
134 | 109 |
|
135 |
| - o2.header("drift") |
136 |
| - o2.write(dr_oracle) |
137 |
| - o2.write(dr) |
138 | 110 | if st.button("Refresh"):
|
139 | 111 | st.cache_data.clear()
|
0 commit comments