Skip to content

Commit 25a1c0f

Browse files
juntaoclaude
andcommitted
feat: add sell_tsla.sh test script
Closes TSLA perp position, transfers USDT0 from cash dex back to spot, and swaps USDT0 back to USDC. Mirrors sell_silver.sh workflow. Signed-off-by: Michael Yuan <michael@secondstate.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent afb5fd8 commit 25a1c0f

1 file changed

Lines changed: 146 additions & 0 deletions

File tree

tests/hyperliquid/sell_tsla.sh

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
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

Comments
 (0)