← Back to All Tutorials

Tutorial 01: HIP-3 Liquidation Trading Bot Advanced

Table of Contents
  1. What Does This Bot Do?
  2. Key Concepts You Need to Understand
  3. How the Bot Works (Step by Step)
  4. Code Walkthrough - Each Function Explained
  5. Configuration Variables
  6. Dependencies You Need
  7. Glossary

1. What Does This Bot Do?

This bot watches for liquidation events on the Hyperliquid exchange's HIP-3 markets and automatically trades them. HIP-3 markets are special "tokenized asset perpetuals" - these are futures contracts for real-world assets like crude oil, gold, and stocks that trade on a crypto exchange.

Simple Analogy: Imagine you're watching a marketplace where people trade oil contracts. Sometimes traders borrowed too much money and get force-sold (liquidated). When many traders get liquidated at once, it creates a cascade - like dominos falling. This bot detects those cascades and jumps in to trade the momentum.

The bot runs in a loop, checking every 30 seconds for:

2. Key Concepts You Need to Understand

Hyperliquid

A decentralized exchange (DEX) for trading perpetual futures contracts. Unlike centralized exchanges (like Binance), it runs on a blockchain and you keep custody of your own funds.

HIP-3 (Hyperliquid Improvement Proposal 3)

A special feature that lets you trade real-world assets (oil, gold, stocks) as perpetual futures on Hyperliquid. These are called "tokenized asset perps."

Liquidation

When a trader's position loses too much value, the exchange automatically closes it to prevent the trader from owing more than they deposited. This is called a liquidation.

Why it matters: When many positions get liquidated at once, it creates a sudden wave of buying or selling that pushes the price further - creating a trading opportunity.

Perpetual Futures (Perps)

A type of futures contract that never expires. You can hold it as long as you want. Unlike traditional futures that settle on a specific date, perps use a "funding rate" mechanism to keep the contract price close to the underlying asset price.

Long vs Short

3. How the Bot Works (Step by Step)

1. Check for HIP-3
Liquidation Data


2. Find Positions
Near Liquidation


3. Read Market
Momentum (1h candles)


4. Choose Trade
Direction (Long/Short)


5. Place Entry Order
with TP/SL


6. Manage Position
(adjust or close)


Wait 30 seconds
then repeat

4. Code Walkthrough - Each Function Explained

1 get_h3_position() - Check if you already have a position

def get_h3_position(coin, account, dex='xyz'):
    info = get_h3_info(dex=dex, skip_ws=True)
    symbol = resolve_h3_symbol(coin, dex=dex, info=info)
    user_state = info.user_state(account.address, dex=dex)

    for asset in user_state.get('assetPositions', []):
        position = asset.get('position', {})
        if position.get('coin') == symbol and float(position.get('szi', 0)) != 0:
            return position, True
    return None, False

What it does: Asks Hyperliquid "do I already have an open position for this coin?" It looks through all your positions and checks if any match the coin you want to trade. Returns the position data and True if found, or None, False if you have no position.

Why it matters: The bot needs to know if you're already in a trade before placing a new one. You don't want to accidentally double your exposure.

2 h3_price_to_order_price() - Format the price correctly

def h3_price_to_order_price(coin, px, dex='xyz', info=None):
    # Gets the number of decimal places allowed for this asset
    info = info or get_h3_info(dex=dex, skip_ws=True)
    symbol = resolve_h3_symbol(coin, dex=dex, info=info)
    asset = info.name_to_asset(symbol)
    sz_decimals = info.asset_to_sz_decimals[asset]
    return round(float(f"{float(px):.5g}"), 6 - sz_decimals)

What it does: Different assets have different precision rules. Oil might need 2 decimal places, while a stock might need 4. This function rounds the price to the correct number of decimal places that the exchange expects.

Think of it like: You can't buy gas at $3.123456 per gallon - the pump only shows $3.12. Exchanges have similar rounding rules.

3 h3_limit_order() - Place an order on the exchange

def h3_limit_order(coin, is_buy, sz, limit_px, reduce_only, account, dex='xyz'):
    # Connects to exchange, formats the order, and sends it
    info = get_h3_info(dex=dex, skip_ws=True)
    exchange = get_h3_exchange(account, dex=dex)
    symbol = resolve_h3_symbol(coin, dex=dex, info=info)

    # Round size to proper decimals
    sz = round(float(sz), sz_decimals)
    # Round price to proper decimals
    limit_px = h3_price_to_order_price(symbol, limit_px, ...)

    return exchange.order(
        symbol, is_buy, sz, float(limit_px),
        {"limit": {"tif": "Gtc"}},  # Good-til-Cancelled
        reduce_only=reduce_only,
    )

What it does: This is the function that actually places a trade order on Hyperliquid. It takes:

The "tif": "Gtc" means "Good-til-Cancelled" - the order stays open until it fills or you cancel it.

4 cancel_all_h3_orders() - Cancel open orders

def cancel_all_h3_orders(account, dex='xyz', coin=None):
    orders = get_h3_open_orders(account, ...)
    for order in orders:
        exchange.cancel(symbol, order['oid'])  # Cancel by order ID

What it does: Cancels all your open orders (or just orders for a specific coin). This is important for risk management - if market conditions change, you want to pull your orders quickly.

5 close_h3_position() - Close an existing trade

def close_h3_position(coin, account, dex='xyz', slippage=0.05):
    # Uses market order to immediately close the position
    return exchange.market_close(symbol, slippage=slippage)

What it does: Closes your position immediately at the current market price. The slippage parameter (default 5%) is the maximum price difference you're willing to accept. This prevents you from getting a terrible price if the market is moving fast.

5. Configuration Variables

VariableWhat It ControlsBeginner Tip
Poll intervalHow often the bot checks for signals30 seconds is typical. Faster = more API calls.
Take Profit (TP)At what profit % to close the tradeStart with smaller TP for more frequent wins.
Stop Loss (SL)At what loss % to close the tradeAlways set this! It limits your maximum loss.
Position sizeHow much money to risk per tradeStart very small while testing.

6. Dependencies You Need

Before running this bot, install these Python packages:

pip install hyperliquid-python-sdk ccxt pandas python-dotenv colorama eth-account requests

You also need a .env file with your keys:

PRIVATE_KEY=your_private_key_here
HYPER_LIQUID_KEY=your_hyperliquid_key_here
Security Warning: Never share your private keys with anyone. Never commit your .env file to a public repository. These keys control your money - anyone who has them can steal your funds.

7. Glossary

TermMeaning
LiquidationWhen the exchange force-closes your position because you lost too much
Liquidation CascadeA chain reaction where one liquidation causes price moves that trigger more liquidations
HIP-3Hyperliquid's system for trading real-world assets (oil, gold, stocks) as crypto perps
Perpetual (Perp)A futures contract that never expires
Limit OrderAn order that only fills at your specified price or better
Market OrderAn order that fills immediately at whatever the current price is
SlippageThe difference between the price you expected and what you actually got
TP (Take Profit)Automatically close the trade when you've made enough profit
SL (Stop Loss)Automatically close the trade to prevent further losses
LongBetting the price will go up
ShortBetting the price will go down
DEXDecentralized Exchange - a trading platform without a central authority
OI (Open Interest)Total value of all outstanding (unclosed) positions

8. Full Code: Python to Pseudo-Code Translation

Below is every significant section of the bot's code translated line-by-line into plain English pseudo-code so you can trace exactly what the program does.

Configuration & Setup

# --- PYTHON ---
DEX = os.getenv("H3_BOT_DEX", "xyz")
SYMBOL = os.getenv("H3_BOT_SYMBOL", "CL")
LEVERAGE = int(os.getenv("H3_BOT_LEVERAGE", "3"))
POSITION_SIZE_USD = float(os.getenv("H3_BOT_POSITION_USD", "15"))
TAKE_PROFIT_PERCENT = float(os.getenv("H3_BOT_TP_PERCENT", "1.5"))
STOP_LOSS_PERCENT = float(os.getenv("H3_BOT_SL_PERCENT", "-1.0"))
MAX_HOLD_HOURS = float(os.getenv("H3_BOT_MAX_HOLD_HOURS", "2"))
LOOP_INTERVAL_SECONDS = int(os.getenv("H3_BOT_LOOP_SECONDS", "30"))
HYPER_LIQUID_KEY = os.getenv("HYPER_LIQUID_KEY")
account = eth_account.Account.from_key(HYPER_LIQUID_KEY)

# --- PSEUDO-CODE ---
SET DEX to the value of environment variable "H3_BOT_DEX", default "xyz"
SET SYMBOL to the value of environment variable "H3_BOT_SYMBOL", default "CL" (crude oil)
SET LEVERAGE to the integer value of env var "H3_BOT_LEVERAGE", default 3
SET POSITION_SIZE_USD to the decimal value of env var "H3_BOT_POSITION_USD", default $15
SET TAKE_PROFIT_PERCENT to the decimal value of env var "H3_BOT_TP_PERCENT", default 1.5%
SET STOP_LOSS_PERCENT to the decimal value of env var "H3_BOT_SL_PERCENT", default -1.0%
SET MAX_HOLD_HOURS to the decimal value of env var "H3_BOT_MAX_HOLD_HOURS", default 2 hours
SET LOOP_INTERVAL_SECONDS to the integer value of env var "H3_BOT_LOOP_SECONDS", default 30 seconds
READ HYPER_LIQUID_KEY from the environment variables (your private key)
CREATE an account object from that private key (this authenticates you on Hyperliquid)

get_h3_position() - Check Existing Position

# --- PYTHON ---
def get_h3_position(coin, account, dex='xyz'):
    info = get_h3_info(dex=dex, skip_ws=True)
    symbol = resolve_h3_symbol(coin, dex=dex, info=info)
    user_state = info.user_state(account.address, dex=dex)
    for asset in user_state.get('assetPositions', []):
        position = asset.get('position', {})
        if position.get('coin') == symbol and float(position.get('szi', 0) or 0) != 0:
            return position, True
    return None, False

# --- PSEUDO-CODE ---
FUNCTION get_h3_position(coin, account, dex):
    CONNECT to Hyperliquid's HIP-3 info service
    RESOLVE the coin name (e.g. "CL") to the full symbol name used on this DEX
    ASK Hyperliquid: "What are all positions for this wallet address?"
    FOR EACH position in the response:
        GET the position details
        IF the position's coin matches our symbol AND the size is NOT zero:
            RETURN this position data and TRUE (yes, we have a position)
    RETURN None and FALSE (no position found)

h3_limit_order() - Place a Trade Order

# --- PYTHON ---
def h3_limit_order(coin, is_buy, sz, limit_px, reduce_only, account, dex='xyz'):
    info = get_h3_info(dex=dex, skip_ws=True)
    exchange = get_h3_exchange(account, dex=dex)
    symbol = resolve_h3_symbol(coin, dex=dex, info=info)
    asset = info.name_to_asset(symbol)
    sz_decimals = info.asset_to_sz_decimals[asset]
    sz = round(float(sz), sz_decimals)
    limit_px = h3_price_to_order_price(symbol, limit_px, dex=dex, info=info)
    return exchange.order(symbol, is_buy, sz, float(limit_px),
                          {"limit": {"tif": "Gtc"}}, reduce_only=reduce_only)

# --- PSEUDO-CODE ---
FUNCTION h3_limit_order(coin, is_buy, size, limit_price, reduce_only, account, dex):
    CONNECT to Hyperliquid's HIP-3 info service
    CREATE an exchange connection using the account's private key
    RESOLVE the coin name to the full exchange symbol
    FIND how many decimal places are allowed for the size (e.g. 2 decimals)
    ROUND the order size to the allowed number of decimal places
    ROUND the limit price to the correct number of decimal places
    SEND the order to the exchange with:
        - the symbol to trade
        - whether this is a BUY or SELL
        - the size of the order
        - the limit price (maximum price to pay for a buy, minimum to accept for a sell)
        - instruction: "Good-til-Cancelled" (keep the order open until it fills or is cancelled)
        - whether this order should only reduce an existing position (not open new)
    RETURN the exchange's response

get_signal_context() - Gather All Signal Data

# --- PYTHON ---
def get_signal_context():
    near_liq = get_moondev_hip3_near_liq_positions(SYMBOL)
    liq_stats = get_moondev_hip3_liquidations(LIQUIDATION_TIMEFRAME, symbol=SYMBOL)
    momentum = get_momentum_context()
    longs = near_liq.get("longs", [])
    shorts = near_liq.get("shorts", [])
    total_long_value = float(near_liq.get("total_long_value", 0))
    total_short_value = float(near_liq.get("total_short_value", 0))
    longs_in_range = [pos for pos in longs
                      if float(pos.get("distance_pct", 999)) <= TRIGGER_DISTANCE_PCT]
    shorts_in_range = [pos for pos in shorts
                       if float(pos.get("distance_pct", 999)) <= TRIGGER_DISTANCE_PCT]
    near_long_value = sum(float(pos.get("value", 0)) for pos in longs_in_range)
    near_short_value = sum(float(pos.get("value", 0)) for pos in shorts_in_range)
    return { ... }

# --- PSEUDO-CODE ---
FUNCTION get_signal_context():
    CALL Moon Dev API to get positions that are NEAR liquidation for our asset
    CALL Moon Dev API to get historical liquidation stats for our asset
    GET the current market momentum (is price going up or down based on 1h candles?)

    EXTRACT the list of near-liquidation LONG positions
    EXTRACT the list of near-liquidation SHORT positions
    CALCULATE total USD value of all near-liquidation longs
    CALCULATE total USD value of all near-liquidation shorts

    FILTER: keep only longs that are within TRIGGER_DISTANCE_PCT of their liquidation price
    FILTER: keep only shorts that are within TRIGGER_DISTANCE_PCT of their liquidation price

    SUM the USD value of longs that are very close to liquidation
    SUM the USD value of shorts that are very close to liquidation

    RETURN a dictionary with all this data:
        - near-liquidation positions data
        - historical liquidation statistics
        - current momentum direction
        - total value of longs and shorts near liquidation
        - the closest long and closest short to being liquidated

choose_trade() - Decide Whether to Trade

# --- PYTHON ---
def choose_trade(context):
    closest_long = context["closest_long"]
    closest_short = context["closest_short"]
    near_long_value = context["near_long_value"]
    near_short_value = context["near_short_value"]
    momentum = context["momentum"]
    if (near_long_value >= NEAR_LIQ_MIN_TOTAL_USD
        and long_distance <= TRIGGER_DISTANCE_PCT
        and momentum["bearish"]):
        return {"should_trade": True, "is_buy": False, "reason": "Long cascade incoming"}
    if (near_short_value >= NEAR_LIQ_MIN_TOTAL_USD
        and short_distance <= TRIGGER_DISTANCE_PCT
        and momentum["bullish"]):
        return {"should_trade": True, "is_buy": True, "reason": "Short squeeze incoming"}
    return {"should_trade": False, "is_buy": None, "reason": "No setup"}

# --- PSEUDO-CODE ---
FUNCTION choose_trade(context):
    GET the closest long position to being liquidated
    GET the closest short position to being liquidated
    GET the total value of positions near liquidation (both sides)
    GET the current market momentum direction

    IF there is a LOT of money in longs about to be liquidated
       AND the closest long is VERY close to its liquidation price
       AND the market momentum is BEARISH (price going down):
        THEN: return "YES, trade! Go SHORT (sell)"
              Reason: "A cascade of long liquidations is about to push price down"

    IF there is a LOT of money in shorts about to be liquidated
       AND the closest short is VERY close to its liquidation price
       AND the market momentum is BULLISH (price going up):
        THEN: return "YES, trade! Go LONG (buy)"
              Reason: "A short squeeze is about to push price up"

    OTHERWISE: return "No, don't trade. No setup detected."

manage_open_position() - Watch an Active Trade

# --- PYTHON ---
def manage_open_position():
    global position_entry_time
    state = get_h3_position_state()
    if not state["in_position"]:
        position_entry_time = None
        return False
    if position_entry_time is None:
        position_entry_time = datetime.now()
    elapsed_hours = (datetime.now() - position_entry_time).total_seconds() / 3600
    if state["pnl_perc"] >= TAKE_PROFIT_PERCENT:
        close_position(f"take profit hit ({state['pnl_perc']:.2f}%)")
        return True
    if state["pnl_perc"] <= STOP_LOSS_PERCENT:
        close_position(f"stop loss hit ({state['pnl_perc']:.2f}%)")
        return True
    if elapsed_hours >= MAX_HOLD_HOURS:
        close_position(f"max hold reached ({elapsed_hours:.2f}h)")
        return True
    return True

# --- PSEUDO-CODE ---
FUNCTION manage_open_position():
    CHECK: do we currently have an open position?
    IF no position:
        CLEAR the entry time tracker
        RETURN False (nothing to manage)

    IF we have a position but no recorded entry time:
        RECORD the current time as the entry time

    CALCULATE how many hours we've been in this position

    IF our profit is at or above the TAKE PROFIT threshold (e.g. +1.5%):
        CLOSE the position immediately
        RETURN True (position was managed)

    IF our loss is at or below the STOP LOSS threshold (e.g. -1.0%):
        CLOSE the position immediately
        RETURN True (position was managed)

    IF we've been in this position longer than MAX HOLD HOURS (e.g. 2 hours):
        CLOSE the position regardless of P&L
        RETURN True (position was managed)

    RETURN True (position is still open, being monitored)

bot() - The Main Loop

# --- PYTHON ---
def bot():
    if manage_open_position():
        return
    context = get_signal_context()
    trade = choose_trade(context)
    if not trade["should_trade"]:
        return
    place_entry(trade["is_buy"], trade["reason"])

if __name__ == "__main__":
    try:
        bot()
        while True:
            time.sleep(LOOP_INTERVAL_SECONDS)
            bot()
    except KeyboardInterrupt:
        print("Stopped by user.")

# --- PSEUDO-CODE ---
FUNCTION bot():
    STEP 1: Check if we already have an open position
        IF yes: manage it (check TP, SL, max hold time), then STOP here
        IF no: continue to step 2

    STEP 2: Gather all signal data
        Get near-liquidation positions from Moon Dev API
        Get historical liquidation stats
        Get current market momentum
        Calculate how much money is near liquidation on each side

    STEP 3: Decide whether to trade
        Based on the signal data, determine if there's a trading opportunity
        IF no opportunity: STOP here, wait for next loop

    STEP 4: Place the trade
        Calculate the correct order size and price
        Cancel any leftover orders
        Send the limit order to the exchange
        Record the entry time

MAIN PROGRAM:
    Run the bot function once
    THEN loop forever:
        Wait 30 seconds
        Run the bot function again
    IF the user presses Ctrl+C:
        Stop cleanly