Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: addressed rounding logic to aligne with next network upgrade #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
67 changes: 42 additions & 25 deletions examples/rounding.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""
This example demonstrates how to round numbers when placing orders.
Both Price (px) and Size (sz) have a maximum number of decimals that are accepted.
Prices can have up to 5 significant figures, but no more than MAX_DECIMALS - szDecimals decimal places where MAX_DECIMALS is 6 for perps and 8 for spot.
For non-integer prices, prices can have up to 5 significant figures, but no more than MAX_DECIMALS - szDecimals decimal places where MAX_DECIMALS is 6 for perps and 8 for spot.
For example, for perps, 1234.5 is valid but 1234.56 is not (too many significant figures).
0.001234 is valid, but 0.0012345 is not (more than 6 decimal places).
For spot, 0.0001234 is valid if szDecimals is 0 or 1, but not if szDecimals is greater than 2 (more than 8-2 decimal places).
Integer prices are always allowed, regardless of the number of significant figures. E.g. 123456.0 is a valid price even though 12345.6 is not.
Prices are precise to the lesser of 5 significant figures or 6 decimals.
Integer prices are always allowed, regardless of the number of significant figures. E.g. 123456 is a valid price.
Non-integer prices are precise to the lesser of 5 significant figures or 6 decimals.
You can find the szDecimals for an asset by making a meta request to the info endpoint
"""
import json
Expand All @@ -16,45 +16,62 @@
from hyperliquid.utils import constants


def demonstrate_price_rounding(px, coin, sz_decimals, max_decimals=6):
"""Helper function to demonstrate price rounding behavior."""
original_px = px

# Apply rounding logic
if abs(round(px) - px) < 1e-10:
px = round(px)
else:
px = round(float(f"{px:.5g}"), max_decimals - sz_decimals[coin])

print(f"Original price: {original_px}")
print(f"Rounded price: {px}")
print("---")
return px


def main():
address, info, exchange = example_utils.setup(constants.TESTNET_API_URL, skip_ws=True)

# Get the exchange's metadata and print it out
meta = info.meta()
print(json.dumps(meta, indent=2))

# create a szDecimals map
sz_decimals = {}
for asset_info in meta["universe"]:
sz_decimals[asset_info["name"]] = asset_info["szDecimals"]

# For demonstration purposes we'll start with a price and size that have too many digits
sz = 12.345678
px = 1.2345678
coin = "OP"
max_decimals = 6 # change to 8 for spot
coin = "BTC"
sz = 0.001 # Small size for testing

# If you use these directly, the exchange will return an error, so we round them.
# First we check if price is greater than 100k in which case we just need to round to an integer
if px > 100_000:
px = round(px)
# If not we round px to 5 significant figures and max_decimals - szDecimals decimals
else:
px = round(float(f"{px:.5g}"), max_decimals - sz_decimals[coin])
print("Testing price rounding behavior:")
print("\nTest Case 1: Integer price with more than 5 significant figures (now allowed)")
px1 = 123456 # 6 significant figures, integer
rounded_px1 = demonstrate_price_rounding(px1, coin, sz_decimals)

print("\nTest Case 2: Non-integer price with more than 5 significant figures (still not allowed)")
px2 = 12345.6 # 6 significant figures, non-integer
rounded_px2 = demonstrate_price_rounding(px2, coin, sz_decimals)

print("\nTest Case 3: Regular integer price")
px3 = 12345 # 5 significant figures, integer
rounded_px3 = demonstrate_price_rounding(px3, coin, sz_decimals)

# Next we round sz based on the sz_decimals map we created
sz = round(sz, sz_decimals[coin])
# Place orders to verify the behavior
print("\nPlacing orders to verify behavior:")

print(f"placing order with px {px} and sz {sz}")
order_result = exchange.order(coin, True, sz, px, {"limit": {"tif": "Gtc"}})
print(order_result)
# Place order with large integer price (new behavior allows this)
print(f"\nPlacing order with large integer price: {rounded_px1}")
order_result1 = exchange.order(coin, True, sz, rounded_px1, {"limit": {"tif": "Gtc"}})
print(f"Order result: {order_result1['status']}")

# Cancel the order
if order_result["status"] == "ok":
status = order_result["response"]["data"]["statuses"][0]
if order_result1["status"] == "ok":
status = order_result1["response"]["data"]["statuses"][0]
if "resting" in status:
cancel_result = exchange.cancel(coin, status["resting"]["oid"])
print(cancel_result)
print(f"Cancel result: {cancel_result['status']}")


if __name__ == "__main__":
Expand Down
150 changes: 150 additions & 0 deletions examples/setup_testnet_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""
This example demonstrates how to:
1. Create a new account for Hyperliquid testnet
2. Set up the config.json file with the account details
3. Guide users through requesting Hyperliquid testnet USDC
4. Provide verification functionality to check balance

Note: Automatic fund requesting is not currently supported
due to Hyperliquid testnet infrastructure limitations.
Users must request testnet USDC manually through the Hyperliquid
testnet interface.
"""

import json
import time
from pathlib import Path

from eth_account import Account

from hyperliquid.info import Info
from hyperliquid.utils import constants


def create_new_account():
"""Create a new Ethereum account for testing."""
Account.enable_unaudited_hdwallet_features()
account = Account.create()
return {"private_key": account.key.hex(), "address": account.address}


def setup_config(private_key, address):
"""Create or update config.json with the new account details."""
config = {
"comments": "Testnet account configuration",
"secret_key": private_key,
"account_address": address,
"multi_sig": {
"authorized_users": [
{"comment": "signer 1", "secret_key": "", "account_address": ""},
{"comment": "signer 2", "secret_key": "", "account_address": ""},
]
},
}

config_path = Path(__file__).parent / "config.json"
with open(config_path, "w") as f:
json.dump(config, f, indent=4)
print(f"\nConfig file created at: {config_path}")


def request_testnet_funds(address):
"""
TODO: Automatic testnet fund requesting

Currently, automatic fund requesting is not implemented because:
1. The testnet infrastructure doesn't provide a direct API endpoint for automated funding
2. Unlike local networks (e.g., Hardhat), we can't programmatically mint tokens
3. Users need to manually request funds through the web interface

For now, users should:
1. Visit https://app.hyperliquid-testnet.xyz/drip
2. Connect their wallet
3. Request funds manually
"""
message = """
Automatic fund requesting is not available.
Please visit: https://app.hyperliquid-testnet.xyz/drip
Connect your wallet and request funds manually.
"""
print(message)
return False


def check_balance(address):
"""Check the account balance on Hyperliquid testnet."""
info = Info(constants.TESTNET_API_URL)
try:
user_state = info.user_state(address)
if "error" in user_state:
print("\nAccount has no balance on Hyperliquid testnet")
return False

balance = float(user_state["marginSummary"]["accountValue"])
print(f"\nCurrent balance: {balance} USDC")
return balance > 0
except Exception as e:
print(f"\nError checking balance: {e}")
return False


def get_address_from_key(private_key):
"""Get Ethereum address from private key."""
if private_key.startswith("0x"):
private_key = private_key[2:]
account = Account.from_key(bytes.fromhex(private_key))
return account.address


def main():
print("\nSetting up a new testnet account...")

# Create new account
account = create_new_account()
account_info = f"""
New account created:
Address: {account['address']}
Private Key: {account['private_key']}
"""
print(account_info)

# Setup config.json
setup_config(account["private_key"], account["address"])

# Inform about manual funding process
next_steps = """
Next steps
----------
1. Visit Hyperliquid testnet and connect your wallet:
https://app.hyperliquid-testnet.xyz/drip
2. Request testnet funds manually
3. Run this script again with --verify flag once you have funds
"""
print(next_steps)


if __name__ == "__main__":
import sys

if len(sys.argv) > 1 and sys.argv[1] == "--verify":
# Load existing config and verify
config_path = Path(__file__).parent / "config.json"
with open(config_path) as f:
config = json.load(f)

address = config["account_address"]
if not address:
address = get_address_from_key(config["secret_key"])

print(f"Verifying account: {address}")
if check_balance(address):
print("\nSuccess! Account is ready for testing")
else:
print("\nAccount still needs to be funded")
print("Attempting to request funds again...")
if request_testnet_funds(address):
time.sleep(10)
if check_balance(address):
print("\nSuccess! Account is now funded and ready for testing")
else:
main()