Vault Development · Keeper Architecture · 5/7
Position Management
Reading Positions
Before making any trading decision, the keeper needs to know the current state. In the examples below, exchange is an Exchange created with vault_address=vault_address, so every action is signed for the vault — the SDK's order methods do not take a vault_address argument. Run them on testnet first.
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
def get_positions(info: Info, vault_address: str) -> dict:
"""Get all open positions for the vault."""
state = info.user_state(vault_address)
positions = {}
for ap in state["assetPositions"]:
p = ap["position"]
coin = p["coin"]
positions[coin] = {
"size": float(p["szi"]), # Positive = long, negative = short
"entry_price": float(p["entryPx"]),
"mark_price": float(p["positionValue"]) / abs(float(p["szi"])) if float(p["szi"]) != 0 else 0,
"unrealized_pnl": float(p["unrealizedPnl"]),
"leverage": float(p.get("leverage", {}).get("value", 1)),
"liquidation_px": float(p.get("liquidationPx", 0) or 0),
}
return positions
Opening Positions
def open_position(exchange: Exchange, vault_address: str,
coin: str, side: str, size: float,
leverage: int = 10) -> dict:
"""Open a new position for the vault."""
# Set leverage first
exchange.update_leverage(leverage, coin, is_cross=True)
# Place market order (an aggressive IOC limit order with 5% default slippage)
is_buy = side == "long"
result = exchange.market_open(coin, is_buy, size)
return result
Closing Positions
def close_position(exchange: Exchange, vault_address: str,
coin: str, size: float) -> dict:
"""Close an existing position."""
# market_close reads the vault's position, picks the closing side,
# and sends a reduce-only IOC order.
result = exchange.market_close(coin, sz=abs(size))
return result
Adjusting Position Size
Sometimes you need to partially close or add to a position:
def adjust_position(exchange: Exchange, info: Info,
vault_address: str, coin: str,
target_size: float) -> dict | None:
"""Adjust position to target size."""
positions = get_positions(info, vault_address)
current = positions.get(coin, {}).get("size", 0.0)
diff = target_size - current
if abs(diff) < 0.001: # Already at target (within tolerance)
return None
is_buy = diff > 0
result = exchange.market_open(coin, is_buy, abs(diff))
return result
Stop-Loss Management
def set_stop_loss(exchange: Exchange, vault_address: str,
coin: str, size: float, stop_price: float) -> dict:
"""Set a stop-loss order for an open position."""
is_buy = size < 0 # Stop for shorts = buy, for longs = sell
result = exchange.order(
coin,
is_buy,
abs(size),
stop_price,
{"trigger": {
"triggerPx": stop_price,
"isMarket": True,
"tpsl": "sl",
}},
reduce_only=True,
)
return result
Key Position Management Rules
- Always know your position — Read before writing
- Use reduce_only — For closing orders, use
market_closeor set reduce_only=True to prevent accidental flips - Check margin before opening — Don't exceed your risk limits
- Handle partial fills — Market orders may partially fill in low liquidity
- Log everything — Every order, fill, and error should be logged for debugging