Skip to main content
Scaffold this example locally and test it with the CLI:
circuit new --name my-hyperliquid-agent --language python --template hyperliquid
cd my-hyperliquid-agent
circuit run    # execute a run cycle
circuit unwind # test the unwind logic

circuit.toml

circuit.toml
name = "Example ETH Perp Agent"
tagline = "Buys ETH perps each cycle"
walletType = "ethereum"
allowedExecutionModes = ["auto", "manual"]
imageUrl = "https://cdn.circuit.org/agents/default"
runtimeIntervalMinutes = 60
version = "0.0.1"

[startingAsset]
network = "hypercore:perp" # Hyperliquid Perpetuals
address = "USDC" # USDC on Hyperliquid
minimumAmount = "2000000000" # 20 USDC (8 decimals; Circuit raw units for hypercore:perp USDC)

Example

from agent_sdk import Agent, AgentContext
from agent_sdk.agent_context import CurrentPosition

BUY_SIZE = 0.01  # ETH to buy each cycle

def run(agent: AgentContext) -> None:
    # Check available balance
    balances = agent.platforms.hyperliquid.balances()
    if not balances.success or not balances.data:
        agent.log(balances.error or "Failed to fetch balances", error=True)
        return

    withdrawable = float(balances.data.perp.withdrawable)
    agent.log(f"Withdrawable balance: ${withdrawable:.2f}")

    if withdrawable < 20:
        agent.log("Not enough balance to place order")
        return

    # Check existing positions
    # Optional: pass a builder DEX name like "xyz", "cash", or "vntl" to scope positions to that venue.
    positions = agent.platforms.hyperliquid.positions()
    if positions.success and positions.data:
        for pos in positions.data:
            agent.log(f"Open: {pos.symbol} {pos.side} {pos.size} @ {pos.entryPrice} (PnL: {pos.unrealizedPnl})")

    # Place a market buy for ETH perps
    # price acts as slippage limit for market orders — set above current market price
    order = agent.platforms.hyperliquid.place_order({
        "symbol": "ETH",
        "side": "buy",
        "size": BUY_SIZE,
        "price": 10000,  # Max price willing to pay (slippage limit)
        "market": "perp",
        "type": "market",
    })

    if order.success and order.data:
        agent.log(f"Order placed: {order.data.orderId} ({order.data.status})")

        # Track total buys in memory
        prev = agent.memory.get("totalBuys")
        count = int(prev.data.value) + 1 if prev.success and prev.data else 1
        agent.memory.set("totalBuys", str(count))
        agent.log(f"Total buy orders placed: {count}")
    else:
        agent.log(order.error or "Order failed", error=True)

def unwind(agent: AgentContext, positions: list[CurrentPosition]) -> None:
    # Close all open perp positions
    # Optional: pass a builder DEX name like "xyz", "cash", or "vntl" to close positions for that venue only.
    open_positions = agent.platforms.hyperliquid.positions()
    if not open_positions.success or not open_positions.data or len(open_positions.data) == 0:
        agent.log("No open positions to close")
        return

    for pos in open_positions.data:
        close_side = "sell" if pos.side == "long" else "buy"
        # Use entry price with 50% slippage — must stay within 95% of reference price
        entry = float(pos.entryPrice)
        slippage_price = entry * 0.5 if close_side == "sell" else entry * 1.5
        close_order = agent.platforms.hyperliquid.place_order({
            "symbol": pos.symbol,
            "side": close_side,
            "size": float(pos.size),
            "price": slippage_price,
            "market": "perp",
            "type": "market",
            "reduceOnly": True,
        })

        if close_order.success and close_order.data:
            agent.log(f"Closed {pos.symbol} {pos.side}: {close_order.data.status}")
        else:
            agent.log(f"Failed to close {pos.symbol}: {close_order.error}", error=True)

agent = Agent(run_function=run, unwind_function=unwind)

handler = agent.get_handler()

if __name__ == "__main__":
    agent.run()

Sample Output

Agent run started
Withdrawable balance: $96.67
Open: BTC long 0.00031 @ 68237.0 (PnL: -0.26629)
Open: ETH long 0.021 @ 1951.33 (PnL: -0.175046)
Open: SOL long 0.13 @ 84.979 (PnL: -0.24856)
Order placed: 327024162229 (filled)
Total buy orders placed: 1
Agent run completed
Agent unwind started
Closed BTC long: filled
Closed ETH long: filled
Closed SOL long: filled
Agent unwind completed

How It Works

  1. Check balance: Reads withdrawable USDC from the perp account
  2. Log positions: Shows any existing open positions with PnL
  3. Place order: Buys a small ETH perp position with a market order
  4. Track state: Uses memory to count total buy orders across cycles
  5. Unwind: Closes all open positions with reduce-only market orders

Notes

  • The price field on market orders acts as a slippage limit — set it above current market for buys, below for sells.
  • reduceOnly: true ensures the close order can only reduce an existing position, not open a new one.
  • Hyperliquid amounts are formatted values (e.g., 0.01 ETH), not raw units like EVM chains.
  • See Hyperliquid tick and lot sizes for minimum order sizes per symbol.