perp-cli 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +129 -535
- package/README.zh.md +243 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +2 -2
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/funding-arb-strategy.js +10 -3
- package/dist/bot/strategies/funding-arb-utils.d.ts +14 -2
- package/dist/bot/strategies/funding-arb-utils.js +48 -15
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +4 -2
- package/dist/bot/strategies/spot-perp-executor.js +2 -2
- package/dist/cli-program.js +1 -1
- package/dist/commands/account.js +168 -24
- package/dist/commands/arb/index.js +19 -77
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +155 -81
- package/dist/commands/arb.d.ts +8 -0
- package/dist/commands/arb.js +49 -23
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +241 -78
- package/dist/commands/options.d.ts +5 -0
- package/dist/commands/options.js +20 -3
- package/dist/commands/pacifica-earn.js +8 -21
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +51 -8
- package/dist/commands/trade.js +4 -4
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +2 -2
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +10 -0
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +10 -6
- package/dist/exchanges/arcus/adapter.js +33 -6
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/aster.d.ts +27 -7
- package/dist/exchanges/aster.js +124 -42
- package/dist/exchanges/canonical-symbols.d.ts +7 -0
- package/dist/exchanges/canonical-symbols.js +20 -0
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/options.d.ts +5 -0
- package/dist/exchanges/derive/options.js +20 -1
- package/dist/exchanges/extended/adapter.d.ts +16 -2
- package/dist/exchanges/extended/adapter.js +96 -34
- package/dist/exchanges/extended/public.d.ts +3 -0
- package/dist/exchanges/extended/public.js +9 -2
- package/dist/exchanges/grvt/adapter.d.ts +16 -2
- package/dist/exchanges/grvt/adapter.js +98 -21
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +8 -0
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +180 -13
- package/dist/exchanges/hyperliquid.js +487 -185
- package/dist/exchanges/interface.d.ts +64 -8
- package/dist/exchanges/interface.js +31 -0
- package/dist/exchanges/lighter-public.d.ts +66 -11
- package/dist/exchanges/lighter-public.js +249 -81
- package/dist/exchanges/lighter-spot.d.ts +7 -1
- package/dist/exchanges/lighter-spot.js +99 -90
- package/dist/exchanges/lighter.d.ts +70 -11
- package/dist/exchanges/lighter.js +285 -92
- package/dist/exchanges/ondo/adapter.d.ts +34 -8
- package/dist/exchanges/ondo/adapter.js +73 -16
- package/dist/exchanges/ondo/public.d.ts +27 -2
- package/dist/exchanges/ondo/public.js +63 -31
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-spot.d.ts +6 -0
- package/dist/exchanges/pacifica-spot.js +23 -15
- package/dist/exchanges/pacifica-vaults.d.ts +9 -0
- package/dist/exchanges/pacifica-vaults.js +14 -1
- package/dist/exchanges/pacifica.d.ts +77 -6
- package/dist/exchanges/pacifica.js +321 -130
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +52 -9
- package/dist/exchanges/qfex/adapter.js +74 -9
- package/dist/exchanges/qfex/public.d.ts +62 -9
- package/dist/exchanges/qfex/public.js +128 -22
- package/dist/exchanges/qfex/ws-transport.d.ts +1 -1
- package/dist/exchanges/risex/adapter.d.ts +19 -7
- package/dist/exchanges/risex/adapter.js +58 -12
- package/dist/exchanges/spot-factory.js +1 -1
- package/dist/exchanges/txflow/adapter.d.ts +14 -7
- package/dist/exchanges/txflow/adapter.js +51 -19
- package/dist/exchanges/txflow/client.js +3 -1
- package/dist/exchanges/txflow/public.d.ts +12 -1
- package/dist/exchanges/txflow/public.js +82 -11
- package/dist/exchanges/unknown-symbol.d.ts +6 -1
- package/dist/exchanges/unknown-symbol.js +7 -2
- package/dist/exchanges/variational/adapter.d.ts +2 -6
- package/dist/exchanges/venue-types.d.ts +46 -3
- package/dist/exchanges/venue-types.js +80 -9
- package/dist/exchanges/venue.d.ts +99 -3
- package/dist/exchanges/venue.js +72 -0
- package/dist/exchanges/venues/hyperliquid.js +9 -0
- package/dist/exchanges/venues/lighter-rh.js +13 -3
- package/dist/exchanges/venues/lighter.js +13 -3
- package/dist/exchanges/venues/pacifica.js +9 -0
- package/dist/exchanges/venues/risex.js +15 -0
- package/dist/funding/legacy-public.d.ts +5 -3
- package/dist/funding/legacy-public.js +33 -37
- package/dist/funding/rates.d.ts +54 -5
- package/dist/funding/rates.js +49 -13
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.js +11 -5
- package/dist/mcp-server.js +104 -15
- package/dist/pacifica/client.d.ts +2 -1
- package/dist/pacifica/client.js +11 -0
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/strategies/funding-arb.js +35 -62
- package/dist/utils.d.ts +11 -1
- package/dist/venue-health.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +30 -9
- package/dist/ws/index.js +180 -15
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +30 -9
- package/dist/ws/node.js +204 -16
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/package.json +15 -2
- package/skills/perp-cli/SKILL.md +97 -33
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +15 -12
- package/skills/perp-cli/references/commands.md +67 -12
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +55 -24
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
|
@@ -1,56 +1,70 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# Scan spot+perp funding rate arbitrage opportunities
|
|
3
|
-
# Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10]
|
|
4
|
-
# Combines
|
|
2
|
+
# Scan spot+perp (and perp-perp) funding rate arbitrage opportunities
|
|
3
|
+
# Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10] [-n mainnet|testnet] [--no-portfolio]
|
|
4
|
+
# Combines the scan with balances (portfolio) and venue health (perp health).
|
|
5
|
+
# --no-portfolio: skip the balance read (no wallet needed); the output says it was skipped.
|
|
6
|
+
# Spot-perp rows: only direction == "long-spot-short-perp" is executable with `arb exec <SYM> spot:<venue> <perp>`.
|
|
7
|
+
# Testnet funding / spreads are not meaningful — use mainnet (the default) for yield analysis.
|
|
5
8
|
|
|
6
9
|
set -euo pipefail
|
|
7
10
|
|
|
8
|
-
#
|
|
9
|
-
if command -v perp &>/dev/null; then
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# perp must be installed (no npx / auto-install path: the user decides what gets installed).
|
|
12
|
+
if ! command -v perp &>/dev/null; then
|
|
13
|
+
echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
|
|
14
|
+
echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
|
|
15
|
+
exit 1
|
|
13
16
|
fi
|
|
17
|
+
PERP="perp"
|
|
14
18
|
|
|
15
19
|
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
16
|
-
# (never append a fallback document); empty output (crash
|
|
17
|
-
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
20
|
+
# (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
|
|
21
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
|
|
18
22
|
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
19
23
|
|
|
20
24
|
JSON_MODE=false
|
|
21
25
|
MODE="all"
|
|
22
26
|
MIN_SPREAD=10
|
|
27
|
+
NETWORK="mainnet"
|
|
28
|
+
WITH_PORTFOLIO=true
|
|
23
29
|
while [[ $# -gt 0 ]]; do
|
|
24
30
|
case "$1" in
|
|
25
31
|
--json) JSON_MODE=true; shift ;;
|
|
26
32
|
--mode) MODE="$2"; shift 2 ;;
|
|
27
33
|
--min) MIN_SPREAD="$2"; shift 2 ;;
|
|
28
|
-
|
|
34
|
+
-n|--network) NETWORK="$2"; shift 2 ;;
|
|
35
|
+
--no-portfolio) WITH_PORTFOLIO=false; shift ;;
|
|
36
|
+
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
29
37
|
esac
|
|
30
38
|
done
|
|
39
|
+
PERP="perp -n $NETWORK"
|
|
31
40
|
|
|
32
|
-
# 1. Portfolio
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if $
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
# 1. Portfolio (balances). Explicitly skipped with --no-portfolio; a failed read aborts.
|
|
42
|
+
if $WITH_PORTFOLIO; then
|
|
43
|
+
PORTFOLIO=$(pj --json portfolio)
|
|
44
|
+
if ! isok "$PORTFOLIO"; then
|
|
45
|
+
if $JSON_MODE; then
|
|
46
|
+
jq -c '{ok:false, error:{code:.error.code, message:.error.message, source:"perp --json portfolio", remediation:"Check the wallet setup, or rerun with --no-portfolio to scan without balances"}}' <<<"$PORTFOLIO"
|
|
47
|
+
else
|
|
48
|
+
echo "ERROR: portfolio failed ($(jq -r '.error.code' <<<"$PORTFOLIO")). Check the wallet setup, or rerun with --no-portfolio." >&2
|
|
49
|
+
fi
|
|
50
|
+
exit 1
|
|
39
51
|
fi
|
|
40
|
-
|
|
52
|
+
else
|
|
53
|
+
PORTFOLIO='{"skipped":true,"reason":"--no-portfolio"}'
|
|
41
54
|
fi
|
|
42
55
|
|
|
43
56
|
# 2. Run appropriate scan
|
|
44
57
|
SCAN_RESULT=$(pj --json arb scan --mode "$MODE" --min "$MIN_SPREAD")
|
|
45
58
|
|
|
46
|
-
# 3.
|
|
47
|
-
HEALTH=$(pj --json
|
|
59
|
+
# 3. Venue health (data.healthy = every official-API venue is up)
|
|
60
|
+
HEALTH=$(pj --json health)
|
|
48
61
|
|
|
49
62
|
if $JSON_MODE; then
|
|
50
63
|
cat <<EOF
|
|
51
64
|
{
|
|
52
65
|
"ok": true,
|
|
53
66
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
67
|
+
"network": "$NETWORK",
|
|
54
68
|
"mode": "$MODE",
|
|
55
69
|
"minSpread": $MIN_SPREAD,
|
|
56
70
|
"portfolio": $PORTFOLIO,
|
|
@@ -60,29 +74,35 @@ if $JSON_MODE; then
|
|
|
60
74
|
EOF
|
|
61
75
|
else
|
|
62
76
|
echo "=== Spot+Perp Arb Scanner ==="
|
|
63
|
-
echo "Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
|
|
77
|
+
echo "Network: $NETWORK | Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
|
|
64
78
|
echo ""
|
|
65
79
|
|
|
66
|
-
# Show balances
|
|
67
80
|
echo "--- Balances ---"
|
|
68
|
-
|
|
69
|
-
.data.exchanges
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
if $WITH_PORTFOLIO; then
|
|
82
|
+
jq -r '.data.exchanges[] | " \(.name): equity $\(.perp.equity) available $\(.perp.available)"' <<<"$PORTFOLIO"
|
|
83
|
+
else
|
|
84
|
+
echo " (skipped: --no-portfolio)"
|
|
85
|
+
fi
|
|
72
86
|
echo ""
|
|
73
87
|
|
|
74
|
-
# Show opportunities
|
|
75
88
|
echo "--- Opportunities ---"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
if isok "$SCAN_RESULT"; then
|
|
90
|
+
jq -r '.data[] |
|
|
91
|
+
if .mode == "spot-perp" then
|
|
92
|
+
" \(.symbol) | spot-perp | net \(.netSpread)%/yr | \(.direction) spot:\(.spotExchanges | join("/")) perp:\(.perpExchange)"
|
|
93
|
+
+ (if .direction == "long-spot-short-perp" then "" else " [NOT executable with arb exec]" end)
|
|
94
|
+
else
|
|
95
|
+
" \(.symbol) | perp-perp | net \(.netSpread)%/yr | long \(.longExch) / short \(.shortExch)"
|
|
96
|
+
end' <<<"$SCAN_RESULT"
|
|
97
|
+
else
|
|
98
|
+
echo " ERROR: arb scan failed: $(jq -r '"\(.error.code): \(.error.message)"' <<<"$SCAN_RESULT")"
|
|
99
|
+
fi
|
|
80
100
|
|
|
81
|
-
# Show exchange health
|
|
82
101
|
echo ""
|
|
83
|
-
echo "---
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
echo "--- Venue Health ---"
|
|
103
|
+
if isok "$HEALTH"; then
|
|
104
|
+
jq -r '" healthy (official venues): \(.data.healthy)", (.data.exchanges[] | " \(.exchange): \(.status)")' <<<"$HEALTH"
|
|
105
|
+
else
|
|
106
|
+
echo " ERROR: perp health failed: $(jq -r '.error.code' <<<"$HEALTH")"
|
|
107
|
+
fi
|
|
88
108
|
fi
|
|
@@ -3,20 +3,22 @@
|
|
|
3
3
|
# Usage: ./validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]
|
|
4
4
|
# Performs: price check, liquidity check, balance check, risk check, dry-run via 'arb exec'
|
|
5
5
|
# Returns JSON validation report
|
|
6
|
-
# NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl)
|
|
6
|
+
# NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl) — only for a scan row with
|
|
7
|
+
# direction == "long-spot-short-perp" (arb exec spot: always buys spot + shorts the perp).
|
|
7
8
|
|
|
8
9
|
set -euo pipefail
|
|
9
10
|
|
|
10
|
-
#
|
|
11
|
-
if command -v perp &>/dev/null; then
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
# perp must be installed (no npx / auto-install path: the user decides what gets installed).
|
|
12
|
+
if ! command -v perp &>/dev/null; then
|
|
13
|
+
echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
|
|
14
|
+
echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
|
|
15
|
+
exit 1
|
|
15
16
|
fi
|
|
17
|
+
PERP="perp"
|
|
16
18
|
|
|
17
19
|
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
18
|
-
# (never append a fallback document); empty output (crash
|
|
19
|
-
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
20
|
+
# (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
|
|
21
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
|
|
20
22
|
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
21
23
|
|
|
22
24
|
SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
|
|
@@ -42,24 +44,23 @@ add_result() {
|
|
|
42
44
|
if [[ "$ok" == "false" ]]; then pass=false; fi
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
# Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below
|
|
47
|
+
# Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below.
|
|
48
|
+
# Aliases (hl, pac, lt, lrh, ast, …) are accepted everywhere, portfolio included.
|
|
46
49
|
LONG_VENUE="${LONG_EX#spot:}"
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
|
|
60
|
-
add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
|
|
50
|
+
SPOT_MODE=false
|
|
51
|
+
[[ "$LONG_EX" == spot:* ]] && SPOT_MODE=true
|
|
52
|
+
|
|
53
|
+
# 1. Price comparison — both exchanges must report similar prices.
|
|
54
|
+
# `market mid` reads the PERP book; there is no spot price read — in spot mode the long price
|
|
55
|
+
# is the spot venue's perp mid (arb exec itself compares the spot and perp books and refuses
|
|
56
|
+
# an empty book or a > 5% gap).
|
|
57
|
+
LONG_MID=$(pj --json -e "$LONG_VENUE" market mid "$SYM")
|
|
58
|
+
SHORT_MID=$(pj --json -e "$SHORT_EX" market mid "$SYM")
|
|
59
|
+
if ! isok "$LONG_MID" || ! isok "$SHORT_MID"; then
|
|
60
|
+
add_result "price" "false" "Cannot fetch mid: long=$(jq -r '.error.code // .data.mid' <<<"$LONG_MID") short=$(jq -r '.error.code // .data.mid' <<<"$SHORT_MID")"
|
|
61
61
|
else
|
|
62
|
-
|
|
62
|
+
LONG_PRICE=$(jq -r '.data.mid' <<<"$LONG_MID")
|
|
63
|
+
SHORT_PRICE=$(jq -r '.data.mid' <<<"$SHORT_MID")
|
|
63
64
|
DEVIATION=$(echo "$LONG_PRICE $SHORT_PRICE" | awk '{d=($1-$2)/$2*100; print (d<0?-d:d)}')
|
|
64
65
|
if (( $(echo "$DEVIATION > 5" | bc -l) )); then
|
|
65
66
|
add_result "price" "false" "Price deviation ${DEVIATION}% > 5% — possible wrong token"
|
|
@@ -68,21 +69,37 @@ else
|
|
|
68
69
|
fi
|
|
69
70
|
fi
|
|
70
71
|
|
|
71
|
-
# 2.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
# 2. Balances. Perp legs: portfolio .perp.available only (missing / unreadable = fail, never 0 or equity).
|
|
73
|
+
# The comparison against the full SIZE is conservative (arb exec needs notional / leverage × buffer).
|
|
74
|
+
perp_available() {
|
|
75
|
+
local out; out=$(pj --json portfolio -e "$1")
|
|
76
|
+
if ! isok "$out"; then echo "ERROR:$(jq -r '.error.code' <<<"$out")"; return; fi
|
|
77
|
+
jq -r '.data.exchanges[0].perp.available // "MISSING"' <<<"$out"
|
|
78
|
+
}
|
|
79
|
+
check_perp_balance() {
|
|
80
|
+
local name="$1" venue="$2" bal; bal=$(perp_available "$venue")
|
|
81
|
+
if [[ "$bal" == ERROR:* || "$bal" == "MISSING" ]]; then
|
|
82
|
+
add_result "$name" "false" "$venue perp available unreadable ($bal)"
|
|
83
|
+
elif (( $(echo "$bal < $SIZE" | bc -l) )); then
|
|
84
|
+
add_result "$name" "false" "$venue perp available $bal < $SIZE"
|
|
85
|
+
else
|
|
86
|
+
add_result "$name" "true" "$venue perp available=$bal"
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
89
|
+
if $SPOT_MODE; then
|
|
90
|
+
# Spot leg: needs spot quote balance (or perp funds the CLI transfers perp→spot on that venue).
|
|
91
|
+
# The exact rule (shared vs separate spot margin, transfer shortfall) is enforced by the arb exec
|
|
92
|
+
# dry-run below; here the spot balances are read so a read failure is surfaced.
|
|
93
|
+
SPOT_BAL=$(pj --json -e "$LONG_VENUE" trade spot-balance)
|
|
94
|
+
if isok "$SPOT_BAL"; then
|
|
95
|
+
add_result "balance_spot" "true" "$LONG_VENUE spot balances: $(jq -c '[.data.balances[] | {token, available}]' <<<"$SPOT_BAL" | tr -d '"') — sufficiency checked by the dry-run"
|
|
96
|
+
else
|
|
97
|
+
add_result "balance_spot" "false" "$LONG_VENUE spot balance unreadable ($(jq -r '.error.code' <<<"$SPOT_BAL"))"
|
|
98
|
+
fi
|
|
83
99
|
else
|
|
84
|
-
|
|
100
|
+
check_perp_balance "balance_long" "$LONG_VENUE"
|
|
85
101
|
fi
|
|
102
|
+
check_perp_balance "balance_short" "$SHORT_EX"
|
|
86
103
|
|
|
87
104
|
# 3. Market info (symbol availability)
|
|
88
105
|
LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
|
|
@@ -101,7 +118,7 @@ RISK_CHECK=$(pj --json risk check --notional "$SIZE" --leverage "${LEVERAGE:-1}"
|
|
|
101
118
|
if isok "$RISK_CHECK" && [[ "$(jq -r '.data.allowed' <<<"$RISK_CHECK" 2>/dev/null || true)" == "true" ]]; then
|
|
102
119
|
add_result "risk" "true" "Within risk limits"
|
|
103
120
|
else
|
|
104
|
-
RISK_WHY=$(jq -r '.data.reason
|
|
121
|
+
if isok "$RISK_CHECK"; then RISK_WHY=$(jq -r '.data.reason' <<<"$RISK_CHECK"); else RISK_WHY=$(jq -r '.error.code' <<<"$RISK_CHECK"); fi
|
|
105
122
|
add_result "risk" "false" "Risk check failed: $RISK_WHY"
|
|
106
123
|
fi
|
|
107
124
|
|
|
@@ -110,7 +127,7 @@ DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LE
|
|
|
110
127
|
if isok "$DRY_RUN"; then
|
|
111
128
|
add_result "dry_run" "true" "Dry-run succeeded"
|
|
112
129
|
else
|
|
113
|
-
DRY_ERR=$(jq -r '.error.code
|
|
130
|
+
DRY_ERR=$(jq -r '"\(.error.code): \(.error.message)"' <<<"$DRY_RUN" | tr -d '"\\')
|
|
114
131
|
add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
|
|
115
132
|
fi
|
|
116
133
|
|
package/dist/cli-spec.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
interface ArgSpec {
|
|
3
|
-
name: string;
|
|
4
|
-
required: boolean;
|
|
5
|
-
}
|
|
6
|
-
interface OptionSpec {
|
|
7
|
-
flags: string;
|
|
8
|
-
description: string;
|
|
9
|
-
default?: unknown;
|
|
10
|
-
}
|
|
11
|
-
interface CommandSpec {
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
14
|
-
args: ArgSpec[];
|
|
15
|
-
options: OptionSpec[];
|
|
16
|
-
subcommands?: CommandSpec[];
|
|
17
|
-
}
|
|
18
|
-
interface CliSpec {
|
|
19
|
-
name: string;
|
|
20
|
-
version: string;
|
|
21
|
-
description: string;
|
|
22
|
-
globalOptions: OptionSpec[];
|
|
23
|
-
commands: CommandSpec[];
|
|
24
|
-
exchanges: string[];
|
|
25
|
-
envelope: {
|
|
26
|
-
success: string;
|
|
27
|
-
error: string;
|
|
28
|
-
example: {
|
|
29
|
-
ok: boolean;
|
|
30
|
-
data: {
|
|
31
|
-
equity: string;
|
|
32
|
-
};
|
|
33
|
-
meta: {
|
|
34
|
-
timestamp: string;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
errorCodes: Record<string, {
|
|
39
|
-
status: number;
|
|
40
|
-
retryable: boolean;
|
|
41
|
-
description: string;
|
|
42
|
-
}>;
|
|
43
|
-
tips: string[];
|
|
44
|
-
}
|
|
45
|
-
export declare function getCliSpec(program: Command): CliSpec;
|
|
46
|
-
export {};
|
package/dist/cli-spec.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generates a structured CLI spec from Commander's program tree.
|
|
3
|
-
* Internal helper retained for tests; no command surfaces it externally.
|
|
4
|
-
*/
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
const require = createRequire(import.meta.url);
|
|
7
|
-
const { version: pkgVersion } = require("../package.json");
|
|
8
|
-
function extractCommand(cmd) {
|
|
9
|
-
const args = cmd._args ?? [];
|
|
10
|
-
const options = cmd.options.map((opt) => ({
|
|
11
|
-
flags: opt.flags,
|
|
12
|
-
description: opt.description ?? "",
|
|
13
|
-
...(opt.defaultValue !== undefined ? { default: opt.defaultValue } : {}),
|
|
14
|
-
}));
|
|
15
|
-
const subcommands = cmd.commands
|
|
16
|
-
.filter((sub) => sub.name() !== "help")
|
|
17
|
-
.map((sub) => extractCommand(sub));
|
|
18
|
-
return {
|
|
19
|
-
name: cmd.name(),
|
|
20
|
-
description: cmd.description(),
|
|
21
|
-
args: args.map((a) => ({ name: a._name, required: a.required })),
|
|
22
|
-
options,
|
|
23
|
-
...(subcommands.length > 0 ? { subcommands } : {}),
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export function getCliSpec(program) {
|
|
27
|
-
const commands = program.commands
|
|
28
|
-
.filter((cmd) => cmd.name() !== "help")
|
|
29
|
-
.map((cmd) => extractCommand(cmd));
|
|
30
|
-
const globalOptions = program.options.map((opt) => ({
|
|
31
|
-
flags: opt.flags,
|
|
32
|
-
description: opt.description ?? "",
|
|
33
|
-
...(opt.defaultValue !== undefined ? { default: opt.defaultValue } : {}),
|
|
34
|
-
}));
|
|
35
|
-
return {
|
|
36
|
-
name: "perp",
|
|
37
|
-
version: pkgVersion,
|
|
38
|
-
description: "Multi-DEX Perpetual Futures CLI (Pacifica, Hyperliquid, Lighter)",
|
|
39
|
-
globalOptions,
|
|
40
|
-
commands,
|
|
41
|
-
exchanges: ["pacifica", "hyperliquid", "lighter", "aster"],
|
|
42
|
-
envelope: {
|
|
43
|
-
success: "{ ok: true, data: T, meta: { timestamp } }",
|
|
44
|
-
error: "{ ok: false, error: { code, message, retryable?, retryAfterMs? }, meta: { timestamp } }",
|
|
45
|
-
example: {
|
|
46
|
-
ok: true,
|
|
47
|
-
data: { equity: "10000.00" },
|
|
48
|
-
meta: { timestamp: "2026-03-08T00:00:00.000Z" },
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
errorCodes: {
|
|
52
|
-
INVALID_PARAMS: { status: 400, retryable: false, description: "Bad arguments or missing fields" },
|
|
53
|
-
SYMBOL_NOT_FOUND: { status: 404, retryable: false, description: "Market/symbol not found" },
|
|
54
|
-
ORDER_NOT_FOUND: { status: 404, retryable: false, description: "Order ID not found" },
|
|
55
|
-
POSITION_NOT_FOUND: { status: 404, retryable: false, description: "No open position for symbol" },
|
|
56
|
-
INSUFFICIENT_BALANCE: { status: 400, retryable: false, description: "Not enough balance" },
|
|
57
|
-
MARGIN_INSUFFICIENT: { status: 400, retryable: false, description: "Not enough margin" },
|
|
58
|
-
SIZE_TOO_SMALL: { status: 400, retryable: false, description: "Below minimum order size" },
|
|
59
|
-
SIZE_TOO_LARGE: { status: 400, retryable: false, description: "Exceeds maximum order size" },
|
|
60
|
-
RISK_VIOLATION: { status: 403, retryable: false, description: "Risk limit exceeded" },
|
|
61
|
-
DUPLICATE_ORDER: { status: 409, retryable: false, description: "Order already submitted" },
|
|
62
|
-
EXCHANGE_UNREACHABLE: { status: 503, retryable: true, description: "Network/connection failure" },
|
|
63
|
-
RATE_LIMITED: { status: 429, retryable: true, description: "Too many requests" },
|
|
64
|
-
PRICE_STALE: { status: 503, retryable: true, description: "Price data outdated" },
|
|
65
|
-
SIGNATURE_FAILED: { status: 500, retryable: false, description: "Signing/auth error" },
|
|
66
|
-
EXCHANGE_ERROR: { status: 502, retryable: true, description: "Exchange returned error" },
|
|
67
|
-
TIMEOUT: { status: 504, retryable: true, description: "Request timed out" },
|
|
68
|
-
CLI_ERROR: { status: 400, retryable: false, description: "Command parse error" },
|
|
69
|
-
FATAL: { status: 500, retryable: false, description: "Unrecoverable internal error" },
|
|
70
|
-
UNKNOWN: { status: 500, retryable: false, description: "Unclassified error" },
|
|
71
|
-
},
|
|
72
|
-
tips: [
|
|
73
|
-
"Always use --json flag for structured output",
|
|
74
|
-
"All numbers in market/account data are strings to avoid float precision loss",
|
|
75
|
-
"Check error.retryable before retrying failed commands",
|
|
76
|
-
"Use 'perp --json trade check <symbol> <side> <size>' to validate before trading",
|
|
77
|
-
"Use 'perp --json market mid <symbol>' for fast price lookups",
|
|
78
|
-
"If this CLI is useful, run 'perp -e hyperliquid wallet manage referral set' (or -e lighter; signs one referral action, supports --dry-run) to support development",
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
}
|