|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Sell ALL TSLA perp on Hyperliquid + transfer USDT0 back + swap to USDC |
| 4 | +# |
| 5 | +# Uses hyperliquid --json API for all commands. Output is always JSON. |
| 6 | +# |
| 7 | +# Workflow: |
| 8 | +# 1. Get positions, find TSLA |
| 9 | +# 2. Get TSLA price from quote |
| 10 | +# 3. Sell TSLA perp with close flag |
| 11 | +# 4. Get wallet address via address command |
| 12 | +# 5. Query cash dex withdrawable via HL API (curl) |
| 13 | +# 6. Transfer USDT0 from cash dex to spot |
| 14 | +# 7. Check spot USDT0 balance |
| 15 | +# 8. Sell USDT0 for USDC on spot |
| 16 | +# |
| 17 | +# Usage: ./tests/hyperliquid/sell_tsla.sh |
| 18 | +# |
| 19 | +set -uo pipefail |
| 20 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 21 | +source "$SCRIPT_DIR/../helpers.sh" |
| 22 | +ensure_built |
| 23 | + |
| 24 | +ft() { $HYPERLIQUID --json "$1" 2>/dev/null; } |
| 25 | + |
| 26 | +log "Sell ALL TSLA perp on Hyperliquid (JSON API)" |
| 27 | + |
| 28 | +# ── Step 1: Get positions ──────────────────────────────────────────── |
| 29 | +info "Fetching positions to find TSLA size..." |
| 30 | +POSITIONS=$(ft '{"command":"positions"}') |
| 31 | + |
| 32 | +if [[ -z "$POSITIONS" ]]; then |
| 33 | + fail "Failed to fetch positions" |
| 34 | + exit 1 |
| 35 | +fi |
| 36 | + |
| 37 | +TSLA_SIZE=$(echo "$POSITIONS" | jq -r ' |
| 38 | + [.[] | .position // .] | |
| 39 | + map(select(.coin == "TSLA" or .coin == "cash:TSLA")) | |
| 40 | + .[0].szi // empty |
| 41 | +' 2>/dev/null || true) |
| 42 | + |
| 43 | +if [[ -z "$TSLA_SIZE" || "$TSLA_SIZE" == "null" ]]; then |
| 44 | + info "No TSLA position found. Checking open orders..." |
| 45 | + ORDERS=$(ft '{"command":"orders"}') |
| 46 | + if [[ -n "$ORDERS" ]]; then |
| 47 | + OPEN_COUNT=$(echo "$ORDERS" | jq -r 'length' 2>/dev/null || echo "0") |
| 48 | + info "Open orders: $OPEN_COUNT" |
| 49 | + fi |
| 50 | + done_step |
| 51 | + warn "No TSLA position to sell -- order may not have filled" |
| 52 | + exit 0 |
| 53 | +fi |
| 54 | + |
| 55 | +SELL_SIZE=$(echo "$TSLA_SIZE" | sed 's/^-//') |
| 56 | +info "TSLA position size: $TSLA_SIZE shares (selling $SELL_SIZE)" |
| 57 | + |
| 58 | +# ── Step 2: Get TSLA price ─────────────────────────────────────────── |
| 59 | +info "Fetching TSLA mark price..." |
| 60 | +QUOTE=$(ft '{"command":"quote","symbol":"TSLA"}') |
| 61 | + |
| 62 | +if [[ -z "$QUOTE" ]]; then |
| 63 | + fail "TSLA quote failed" |
| 64 | + exit 1 |
| 65 | +fi |
| 66 | + |
| 67 | +SELL_PRICE=$(echo "$QUOTE" | jq -r '.markPx') |
| 68 | +SELL_LIMIT=$(echo "$SELL_PRICE" | awk '{printf "%.2f", $1 * 0.995}') |
| 69 | + |
| 70 | +info "Current mark: \$$SELL_PRICE" |
| 71 | +info "Sell limit: \$$SELL_LIMIT (-0.5% buffer)" |
| 72 | + |
| 73 | +# ── Step 3: Sell TSLA perp with close flag ─────────────────────────── |
| 74 | +RESULT=$(ft "{\"command\":\"perp_sell\",\"symbol\":\"TSLA\",\"amount\":$SELL_SIZE,\"price\":$SELL_LIMIT,\"close\":true}") |
| 75 | + |
| 76 | +if [[ -z "$RESULT" ]]; then |
| 77 | + fail "TSLA perp sell failed" |
| 78 | + warn "Position may still be open -- check manually with 'hyperliquid positions'" |
| 79 | + exit 1 |
| 80 | +fi |
| 81 | + |
| 82 | +FILL=$(echo "$RESULT" | jq -r '.fillStatus // empty') |
| 83 | + |
| 84 | +done_step |
| 85 | +info "Sold: $SELL_SIZE shares" |
| 86 | +info "Limit: \$$SELL_LIMIT" |
| 87 | +info "Fill status: $FILL" |
| 88 | +ok "TSLA perp sell order placed -- $SELL_SIZE shares at \$$SELL_LIMIT" |
| 89 | + |
| 90 | +# ── Step 4: Get wallet address ─────────────────────────────────────── |
| 91 | +info "Transferring USDT0 from cash dex back to spot..." |
| 92 | +sleep 2 |
| 93 | + |
| 94 | +ADDR_JSON=$(ft '{"command":"address"}') |
| 95 | +USER_ADDR=$(echo "$ADDR_JSON" | jq -r '.address // empty') |
| 96 | + |
| 97 | +# ── Step 5: Query cash dex withdrawable via HL API ─────────────────── |
| 98 | +CASH_WITHDRAWABLE="0" |
| 99 | +if [[ -n "$USER_ADDR" ]]; then |
| 100 | + CASH_STATE=$(curl -s -X POST https://api.hyperliquid.xyz/info \ |
| 101 | + -H 'Content-Type: application/json' \ |
| 102 | + -d "{\"type\":\"clearinghouseState\",\"user\":\"$USER_ADDR\",\"dex\":\"cash\"}" 2>/dev/null) |
| 103 | + CASH_WITHDRAWABLE=$(echo "$CASH_STATE" | jq -r '.withdrawable // "0"' 2>/dev/null || echo "0") |
| 104 | + info "Cash dex withdrawable: $CASH_WITHDRAWABLE USDT0" |
| 105 | +fi |
| 106 | + |
| 107 | +# ── Step 6: Transfer USDT0 from cash to spot ───────────────────────── |
| 108 | +TRANSFER_AMT=$(echo "$CASH_WITHDRAWABLE" | awk '{v = int($1 * 100) / 100; if (v > 0) printf "%.2f", v; else print "0"}') |
| 109 | + |
| 110 | +if [[ "$TRANSFER_AMT" != "0" && "$TRANSFER_AMT" != "0.00" ]]; then |
| 111 | + info "Transferring $TRANSFER_AMT USDT0 from cash dex to spot..." |
| 112 | + RESULT=$(ft "{\"command\":\"transfer\",\"asset\":\"USDT0\",\"amount\":$TRANSFER_AMT,\"from\":\"cash\",\"to\":\"spot\"}") |
| 113 | + if [[ -z "$RESULT" ]]; then |
| 114 | + warn "USDT0 transfer from cash dex failed" |
| 115 | + warn "USDT0 may still be in cash dex. Use: hyperliquid transfer USDT0 --amount <amount> --from cash --to spot" |
| 116 | + else |
| 117 | + ok "Transferred $TRANSFER_AMT USDT0 from cash dex to spot" |
| 118 | + fi |
| 119 | + sleep 1 |
| 120 | +else |
| 121 | + info "No withdrawable USDT0 in cash dex" |
| 122 | +fi |
| 123 | + |
| 124 | +# ── Step 7: Check spot USDT0 balance ───────────────────────────────── |
| 125 | +info "Checking spot USDT0 balance..." |
| 126 | +BALANCE=$(ft '{"command":"balance"}') |
| 127 | +SPOT_USDT0=$(echo "$BALANCE" | jq -r '.spot.balances[]? | select(.coin == "USDT0") | .total // "0"' 2>/dev/null || echo "0") |
| 128 | +USDT0_HOLD=$(echo "$BALANCE" | jq -r '.spot.balances[]? | select(.coin == "USDT0") | .hold // "0"' 2>/dev/null || echo "0") |
| 129 | + |
| 130 | +# ── Step 8: Sell USDT0 for USDC ────────────────────────────────────── |
| 131 | +SELL_AMT=$(echo "$SPOT_USDT0 $USDT0_HOLD" | awk '{v = int(($1 - $2) * 100) / 100; if (v > 0) printf "%.2f", v; else print "0"}') |
| 132 | + |
| 133 | +if [[ "$SELL_AMT" != "0" && "$SELL_AMT" != "0.00" ]]; then |
| 134 | + info "Swapping $SELL_AMT USDT0 -> USDC on spot..." |
| 135 | + RESULT=$(ft "{\"command\":\"sell\",\"symbol\":\"USDT0\",\"amount\":$SELL_AMT,\"price\":0.998}") |
| 136 | + if [[ -z "$RESULT" ]]; then |
| 137 | + warn "USDT0 -> USDC spot swap failed" |
| 138 | + warn "USDT0 still in spot. Sell manually: hyperliquid sell USDT0 --amount $SELL_AMT --price 0.998" |
| 139 | + else |
| 140 | + SWAP_FILL=$(echo "$RESULT" | jq -r '.fillStatus // empty') |
| 141 | + info "USDT0->USDC swap fill: $SWAP_FILL" |
| 142 | + ok "Swapped $SELL_AMT USDT0 -> USDC" |
| 143 | + fi |
| 144 | +else |
| 145 | + info "No USDT0 available to swap back to USDC" |
| 146 | +fi |
0 commit comments