openbroker 1.0.56 → 1.0.59
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/CHANGELOG.md +32 -0
- package/SKILL.md +29 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/scripts/core/client.ts +16 -6
- package/scripts/info/account.ts +97 -48
- package/scripts/info/fills.ts +17 -3
- package/scripts/info/funding.ts +9 -3
- package/scripts/info/markets.ts +9 -3
- package/scripts/info/orders.ts +18 -3
- package/scripts/info/positions.ts +42 -7
- package/scripts/plugin/tools.ts +184 -64
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Open Broker will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.59] - 2026-03-11
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Plugin Tools Parity with CLI**: Reviewed all plugin tools against CLI commands and aligned behavior
|
|
9
|
+
- **`ob_account`**: Now uses `crossMarginSummary` (matches CLI), includes `signingWallet`, `walletType`, `marginRatio`, translates order sides (`B`→`buy`, `A`→`sell`), adds `side` field to positions
|
|
10
|
+
- **`ob_positions`**: Now fetches mark prices (`getAllMids`) and cumulative funding (`getUserFunding`) in parallel — includes `markPrice`, `notional`, `cumulativeFunding`, `liquidationDistance`, `maxLeverage`, `leverageType` fields matching CLI output
|
|
11
|
+
- **`ob_markets`**: Now includes HIP-3 markets (was main dex only), adds `change24h` field, sorts by volume, includes `type` field (`perp`/`hip3`)
|
|
12
|
+
- **`ob_buy`/`ob_sell`**: Fixed default slippage — was using `builderInfo.f` (1 bps) instead of config default (50 bps), causing market orders to fail with price-too-far errors
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **SKILL.md CLI Fallback Guide**: Added troubleshooting section with plugin tool → CLI command mapping table, so agents can fall back to CLI when plugin tools return errors
|
|
16
|
+
|
|
17
|
+
## [1.0.58] - 2026-03-11
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **`ob_funding` HIP-3 Support**: Rewrote plugin tool to use `getMetaAndAssetCtxs()` + `getAllPerpMetas()` instead of `getPredictedFundings()` which only returned main dex data. Now correctly returns funding rates for HIP-3 assets like `xyz:GOLD`. Fixes "Cannot read properties of null" crash.
|
|
21
|
+
- **`ob_search` HIP-3 Filter**: Fixed inverted type filter condition — HIP-3 search was gated behind `typeFilter === 'perp'` instead of `typeFilter === 'hip3'`, causing empty results in some filter combinations. Added `funding` and `openInterest` fields to search results.
|
|
22
|
+
|
|
23
|
+
## [1.0.57] - 2026-03-10
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **Unified Account $0 Equity**: Made account mode detection more robust — handles wrapped API responses, case-insensitive matching. Added debug logging for spot balance lookup to diagnose edge cases. Plugin `ob_account` now returns `accountMode` field.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **`--json` Flag**: Info commands now support `--json` for machine-readable output
|
|
30
|
+
- `account --json` — full account state as JSON
|
|
31
|
+
- `positions --json` — positions array with mark prices and liquidation distances
|
|
32
|
+
- `funding --json` — funding rate data
|
|
33
|
+
- `fills --json` — trade fill history
|
|
34
|
+
- `orders --json` — order history
|
|
35
|
+
- `markets --json` — market data
|
|
36
|
+
|
|
5
37
|
## [1.0.56] - 2026-03-10
|
|
6
38
|
|
|
7
39
|
### Added
|
package/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Hyperliquid trading plugin with background position monitoring. Exe
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 22+, network access to api.hyperliquid.xyz
|
|
6
6
|
homepage: https://www.npmjs.com/package/openbroker
|
|
7
|
-
metadata: {"author": "monemetrics", "version": "1.0.
|
|
7
|
+
metadata: {"author": "monemetrics", "version": "1.0.59", "openclaw": {"requires": {"bins": ["openbroker"], "env": ["HYPERLIQUID_PRIVATE_KEY"]}, "primaryEnv": "HYPERLIQUID_PRIVATE_KEY", "install": [{"id": "node", "kind": "node", "package": "openbroker", "bins": ["openbroker"], "label": "Install openbroker (npm)"}]}}
|
|
8
8
|
allowed-tools: ob_account ob_positions ob_funding ob_markets ob_search ob_spot ob_fills ob_orders ob_order_status ob_fees ob_candles ob_funding_history ob_trades ob_rate_limit ob_funding_scan ob_buy ob_sell ob_limit ob_trigger ob_tpsl ob_cancel ob_twap ob_bracket ob_chase ob_watcher_status Bash(openbroker:*)
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -46,6 +46,34 @@ Or with the `ob_search` plugin tool: `{ "query": "gold" }` or `{ "query": "oil",
|
|
|
46
46
|
|
|
47
47
|
**HIP-3 assets use `dex:COIN` format** — e.g., `xyz:CL` not just `CL`. If you get an error like "No market data found", search for the asset to find the correct prefixed ticker. Common HIP-3 dexes: `xyz`, `flx`, `km`, `hyna`, `vntl`, `cash`.
|
|
48
48
|
|
|
49
|
+
## Troubleshooting: CLI Fallback
|
|
50
|
+
|
|
51
|
+
If an `ob_*` plugin tool returns unexpected errors, empty results, or crashes, **fall back to the equivalent CLI command** via Bash. The CLI and plugin tools share the same core code, but the CLI has more mature error handling and output.
|
|
52
|
+
|
|
53
|
+
| Plugin Tool | CLI Equivalent |
|
|
54
|
+
|-------------|---------------|
|
|
55
|
+
| `ob_account` | `openbroker account --json` |
|
|
56
|
+
| `ob_positions` | `openbroker positions --json` |
|
|
57
|
+
| `ob_funding` | `openbroker funding --json --include-hip3` |
|
|
58
|
+
| `ob_markets` | `openbroker markets --json --include-hip3` |
|
|
59
|
+
| `ob_search` | `openbroker search --query <QUERY>` |
|
|
60
|
+
| `ob_buy` | `openbroker buy --coin <COIN> --size <SIZE>` |
|
|
61
|
+
| `ob_sell` | `openbroker sell --coin <COIN> --size <SIZE>` |
|
|
62
|
+
| `ob_limit` | `openbroker limit --coin <COIN> --side <SIDE> --size <SIZE> --price <PRICE>` |
|
|
63
|
+
| `ob_tpsl` | `openbroker tpsl --coin <COIN> --tp <PRICE> --sl <PRICE>` |
|
|
64
|
+
| `ob_cancel` | `openbroker cancel --all` or `--coin <COIN>` |
|
|
65
|
+
| `ob_fills` | `openbroker fills --json` |
|
|
66
|
+
| `ob_orders` | `openbroker orders --json` |
|
|
67
|
+
| `ob_funding_scan` | `openbroker funding-scan --json` |
|
|
68
|
+
| `ob_candles` | `openbroker candles --coin <COIN> --json` |
|
|
69
|
+
|
|
70
|
+
**When to use CLI fallback:**
|
|
71
|
+
- Plugin tool returns `null`, empty data, or throws an error
|
|
72
|
+
- You need data the plugin tool doesn't expose (e.g., `--verbose` debug output)
|
|
73
|
+
- Long-running operations (strategies, TWAP) — the CLI handles timeouts and progress better
|
|
74
|
+
|
|
75
|
+
Add `--dry` to any trading CLI command to preview without executing. Add `--json` to info commands for structured output.
|
|
76
|
+
|
|
49
77
|
## Command Reference
|
|
50
78
|
|
|
51
79
|
### Setup
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/scripts/core/client.ts
CHANGED
|
@@ -656,14 +656,17 @@ export class HyperliquidClient {
|
|
|
656
656
|
}),
|
|
657
657
|
});
|
|
658
658
|
const data = await response.json();
|
|
659
|
-
this.log('userAbstraction response:', data);
|
|
659
|
+
this.log('userAbstraction response:', JSON.stringify(data));
|
|
660
660
|
|
|
661
|
-
// API
|
|
662
|
-
|
|
661
|
+
// API may return a bare string or an object. Normalize to string for matching.
|
|
662
|
+
const mode = typeof data === 'string' ? data : (data?.abstraction ?? data?.mode ?? String(data));
|
|
663
|
+
const modeLower = mode.toLowerCase();
|
|
664
|
+
|
|
665
|
+
if (modeLower.includes('unified')) {
|
|
663
666
|
this.accountMode = 'unified';
|
|
664
|
-
} else if (
|
|
667
|
+
} else if (modeLower.includes('portfolio')) {
|
|
665
668
|
this.accountMode = 'portfolio';
|
|
666
|
-
} else if (
|
|
669
|
+
} else if (modeLower.includes('dex')) {
|
|
667
670
|
this.accountMode = 'dexAbstraction';
|
|
668
671
|
} else {
|
|
669
672
|
// "default" or "disabled" both mean standard mode
|
|
@@ -1173,7 +1176,12 @@ export class HyperliquidClient {
|
|
|
1173
1176
|
if (unified) {
|
|
1174
1177
|
try {
|
|
1175
1178
|
const spotState = await this.getSpotBalances(user);
|
|
1176
|
-
|
|
1179
|
+
this.log('Unified spot balances:', JSON.stringify(spotState));
|
|
1180
|
+
|
|
1181
|
+
// Find USDC balance (case-insensitive, handles variations)
|
|
1182
|
+
const balances = spotState?.balances ?? [];
|
|
1183
|
+
const usdcBalance = balances.find(b => b.coin?.toUpperCase() === 'USDC');
|
|
1184
|
+
|
|
1177
1185
|
if (usdcBalance) {
|
|
1178
1186
|
const totalUsdc = usdcBalance.total;
|
|
1179
1187
|
const holdUsdc = usdcBalance.hold;
|
|
@@ -1200,6 +1208,8 @@ export class HyperliquidClient {
|
|
|
1200
1208
|
mainState.crossMarginSummary = { ...summary };
|
|
1201
1209
|
|
|
1202
1210
|
this.log(`Unified account: USDC balance $${parseFloat(totalUsdc).toFixed(2)}, margin used $${totalMarginUsed.toFixed(2)}`);
|
|
1211
|
+
} else {
|
|
1212
|
+
this.log('Unified account: no USDC balance found in spot state. Balances:', balances.map(b => b.coin));
|
|
1203
1213
|
}
|
|
1204
1214
|
} catch (err) {
|
|
1205
1215
|
this.log('Failed to fetch spot balances for unified account:', err instanceof Error ? err.message : String(err));
|
package/scripts/info/account.ts
CHANGED
|
@@ -6,37 +6,14 @@ import { formatUsd, formatPercent, parseArgs } from '../core/utils.js';
|
|
|
6
6
|
|
|
7
7
|
async function main() {
|
|
8
8
|
const args = parseArgs(process.argv.slice(2));
|
|
9
|
+
const jsonOutput = args.json as boolean;
|
|
9
10
|
const client = getClient();
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.log('Wallet Configuration');
|
|
15
|
-
console.log('--------------------');
|
|
16
|
-
console.log(`Trading Account: ${client.address}`);
|
|
17
|
-
console.log(`Signing Wallet: ${client.walletAddress}`);
|
|
18
|
-
console.log(`Wallet Type: ${client.isApiWallet ? 'API Wallet' : 'Main Wallet'}`);
|
|
12
|
+
if (args.verbose) {
|
|
13
|
+
client.verbose = true;
|
|
14
|
+
}
|
|
19
15
|
|
|
20
16
|
const accountMode = await client.getAccountMode();
|
|
21
|
-
const modeLabel: Record<string, string> = {
|
|
22
|
-
standard: 'Standard (separate balances per dex)',
|
|
23
|
-
unified: 'Unified Account (shared USDC across all dexes)',
|
|
24
|
-
portfolio: 'Portfolio Margin',
|
|
25
|
-
dexAbstraction: 'DEX Abstraction (deprecated)',
|
|
26
|
-
};
|
|
27
|
-
console.log(`Account Mode: ${modeLabel[accountMode] ?? accountMode}`);
|
|
28
|
-
|
|
29
|
-
// Check builder fee approval
|
|
30
|
-
const builderApproval = await client.getMaxBuilderFee();
|
|
31
|
-
console.log(`Builder Address: ${client.builderAddress}`);
|
|
32
|
-
console.log(`Builder Fee: ${client.builderFeeBps} bps`);
|
|
33
|
-
if (builderApproval) {
|
|
34
|
-
console.log(`Builder Approved: ✅ Yes (max: ${builderApproval})`);
|
|
35
|
-
} else {
|
|
36
|
-
console.log(`Builder Approved: ❌ No`);
|
|
37
|
-
console.log(`\n⚠️ Run: npx tsx scripts/setup/approve-builder.ts`);
|
|
38
|
-
}
|
|
39
|
-
console.log('');
|
|
40
17
|
|
|
41
18
|
try {
|
|
42
19
|
const state = await client.getUserStateAll();
|
|
@@ -47,6 +24,93 @@ async function main() {
|
|
|
47
24
|
const withdrawable = parseFloat(margin.withdrawable);
|
|
48
25
|
const totalNotional = parseFloat(margin.totalNtlPos);
|
|
49
26
|
|
|
27
|
+
const positions = state.assetPositions
|
|
28
|
+
.filter(ap => Math.abs(parseFloat(ap.position.szi)) >= 0.0001)
|
|
29
|
+
.map(ap => {
|
|
30
|
+
const pos = ap.position;
|
|
31
|
+
const size = parseFloat(pos.szi);
|
|
32
|
+
const entryPx = parseFloat(pos.entryPx);
|
|
33
|
+
const notional = parseFloat(pos.positionValue);
|
|
34
|
+
const markPx = Math.abs(notional / size);
|
|
35
|
+
const pnl = parseFloat(pos.unrealizedPnl);
|
|
36
|
+
return {
|
|
37
|
+
coin: pos.coin,
|
|
38
|
+
side: size > 0 ? 'long' : 'short',
|
|
39
|
+
size: pos.szi,
|
|
40
|
+
entryPrice: pos.entryPx,
|
|
41
|
+
markPrice: markPx,
|
|
42
|
+
notional: Math.abs(notional),
|
|
43
|
+
unrealizedPnl: pnl,
|
|
44
|
+
leverage: `${pos.leverage.value}x ${pos.leverage.type}`,
|
|
45
|
+
liquidationPx: pos.liquidationPx,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const totalPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
|
|
50
|
+
|
|
51
|
+
// JSON output
|
|
52
|
+
if (jsonOutput) {
|
|
53
|
+
const result: Record<string, unknown> = {
|
|
54
|
+
address: client.address,
|
|
55
|
+
signingWallet: client.walletAddress,
|
|
56
|
+
walletType: client.isApiWallet ? 'api' : 'main',
|
|
57
|
+
accountMode,
|
|
58
|
+
equity: accountValue,
|
|
59
|
+
totalNotional,
|
|
60
|
+
totalMarginUsed,
|
|
61
|
+
withdrawable,
|
|
62
|
+
marginRatio: totalMarginUsed > 0 && accountValue > 0 ? totalMarginUsed / accountValue : 0,
|
|
63
|
+
totalUnrealizedPnl: totalPnl,
|
|
64
|
+
positions,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (args.orders) {
|
|
68
|
+
const orders = await client.getOpenOrders();
|
|
69
|
+
result.openOrders = orders.map(o => ({
|
|
70
|
+
coin: o.coin,
|
|
71
|
+
oid: o.oid,
|
|
72
|
+
side: o.side === 'B' ? 'buy' : 'sell',
|
|
73
|
+
size: o.sz,
|
|
74
|
+
price: o.limitPx,
|
|
75
|
+
orderType: o.orderType,
|
|
76
|
+
timestamp: o.timestamp,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(JSON.stringify(result, null, 2));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Human-readable output
|
|
85
|
+
console.log('Open Broker - Account Info');
|
|
86
|
+
console.log('==========================\n');
|
|
87
|
+
|
|
88
|
+
console.log('Wallet Configuration');
|
|
89
|
+
console.log('--------------------');
|
|
90
|
+
console.log(`Trading Account: ${client.address}`);
|
|
91
|
+
console.log(`Signing Wallet: ${client.walletAddress}`);
|
|
92
|
+
console.log(`Wallet Type: ${client.isApiWallet ? 'API Wallet' : 'Main Wallet'}`);
|
|
93
|
+
|
|
94
|
+
const modeLabel: Record<string, string> = {
|
|
95
|
+
standard: 'Standard (separate balances per dex)',
|
|
96
|
+
unified: 'Unified Account (shared USDC across all dexes)',
|
|
97
|
+
portfolio: 'Portfolio Margin',
|
|
98
|
+
dexAbstraction: 'DEX Abstraction (deprecated)',
|
|
99
|
+
};
|
|
100
|
+
console.log(`Account Mode: ${modeLabel[accountMode] ?? accountMode}`);
|
|
101
|
+
|
|
102
|
+
// Check builder fee approval
|
|
103
|
+
const builderApproval = await client.getMaxBuilderFee();
|
|
104
|
+
console.log(`Builder Address: ${client.builderAddress}`);
|
|
105
|
+
console.log(`Builder Fee: ${client.builderFeeBps} bps`);
|
|
106
|
+
if (builderApproval) {
|
|
107
|
+
console.log(`Builder Approved: ✅ Yes (max: ${builderApproval})`);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(`Builder Approved: ❌ No`);
|
|
110
|
+
console.log(`\n⚠️ Run: npx tsx scripts/setup/approve-builder.ts`);
|
|
111
|
+
}
|
|
112
|
+
console.log('');
|
|
113
|
+
|
|
50
114
|
console.log('Margin Summary');
|
|
51
115
|
console.log('--------------');
|
|
52
116
|
console.log(`Account Value: ${formatUsd(accountValue)}`);
|
|
@@ -62,33 +126,18 @@ async function main() {
|
|
|
62
126
|
console.log('\nPositions Summary');
|
|
63
127
|
console.log('-----------------');
|
|
64
128
|
|
|
65
|
-
if (
|
|
129
|
+
if (positions.length === 0) {
|
|
66
130
|
console.log('No open positions');
|
|
67
131
|
} else {
|
|
68
|
-
let totalPnl = 0;
|
|
69
132
|
console.log('Coin | Size | Entry | Mark | PnL | Leverage');
|
|
70
133
|
console.log('---------|------------|------------|------------|------------|----------');
|
|
71
134
|
|
|
72
|
-
for (const
|
|
73
|
-
const
|
|
74
|
-
const size = parseFloat(pos.szi);
|
|
75
|
-
if (Math.abs(size) < 0.0001) continue;
|
|
76
|
-
|
|
77
|
-
const entryPx = parseFloat(pos.entryPx);
|
|
78
|
-
const pnl = parseFloat(pos.unrealizedPnl);
|
|
79
|
-
totalPnl += pnl;
|
|
80
|
-
|
|
81
|
-
// Get mark price from leverage calculation
|
|
82
|
-
const notional = parseFloat(pos.positionValue);
|
|
83
|
-
const markPx = Math.abs(notional / size);
|
|
84
|
-
|
|
85
|
-
const side = size > 0 ? 'L' : 'S';
|
|
86
|
-
const leverageStr = `${pos.leverage.value}x ${pos.leverage.type}`;
|
|
87
|
-
|
|
135
|
+
for (const p of positions) {
|
|
136
|
+
const side = p.side === 'long' ? 'L' : 'S';
|
|
88
137
|
console.log(
|
|
89
|
-
`${
|
|
90
|
-
`${formatUsd(
|
|
91
|
-
`${formatUsd(
|
|
138
|
+
`${p.coin.padEnd(8)} | ${side} ${Math.abs(parseFloat(p.size)).toFixed(4).padStart(8)} | ` +
|
|
139
|
+
`${formatUsd(parseFloat(p.entryPrice)).padStart(10)} | ${formatUsd(p.markPrice).padStart(10)} | ` +
|
|
140
|
+
`${formatUsd(p.unrealizedPnl).padStart(10)} | ${p.leverage}`
|
|
92
141
|
);
|
|
93
142
|
}
|
|
94
143
|
|
package/scripts/info/fills.ts
CHANGED
|
@@ -32,11 +32,9 @@ async function main() {
|
|
|
32
32
|
const filterCoin = args.coin as string | undefined;
|
|
33
33
|
const filterSide = args.side as string | undefined;
|
|
34
34
|
const top = parseInt(args.top as string) || 20;
|
|
35
|
+
const jsonOutput = args.json as boolean;
|
|
35
36
|
const client = getClient();
|
|
36
37
|
|
|
37
|
-
console.log('Open Broker - Trade Fills');
|
|
38
|
-
console.log('========================\n');
|
|
39
|
-
|
|
40
38
|
try {
|
|
41
39
|
let fills = await client.getUserFills();
|
|
42
40
|
|
|
@@ -52,6 +50,22 @@ async function main() {
|
|
|
52
50
|
fills.sort((a, b) => b.time - a.time);
|
|
53
51
|
fills = fills.slice(0, top);
|
|
54
52
|
|
|
53
|
+
if (jsonOutput) {
|
|
54
|
+
console.log(JSON.stringify(fills.map(f => ({
|
|
55
|
+
time: new Date(f.time).toISOString(),
|
|
56
|
+
coin: f.coin,
|
|
57
|
+
side: f.side === 'B' ? 'buy' : 'sell',
|
|
58
|
+
size: f.sz,
|
|
59
|
+
price: f.px,
|
|
60
|
+
fee: f.fee,
|
|
61
|
+
closedPnl: f.closedPnl,
|
|
62
|
+
})), null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('Open Broker - Trade Fills');
|
|
67
|
+
console.log('========================\n');
|
|
68
|
+
|
|
55
69
|
if (fills.length === 0) {
|
|
56
70
|
console.log('No fills found');
|
|
57
71
|
return;
|
package/scripts/info/funding.ts
CHANGED
|
@@ -19,9 +19,7 @@ async function main() {
|
|
|
19
19
|
const filterCoin = args.coin as string | undefined;
|
|
20
20
|
const sortBy = (args.sort as string) || 'annualized'; // annualized, hourly, oi
|
|
21
21
|
const showAll = args.all as boolean;
|
|
22
|
-
|
|
23
|
-
console.log('Open Broker - Funding Rates');
|
|
24
|
-
console.log('===========================\n');
|
|
22
|
+
const jsonOutput = args.json as boolean;
|
|
25
23
|
|
|
26
24
|
const client = getClient();
|
|
27
25
|
const includeHip3 = args['include-hip3'] as boolean || args['hip3'] as boolean || (filterCoin?.includes(':') ?? false);
|
|
@@ -101,11 +99,19 @@ async function main() {
|
|
|
101
99
|
// Limit
|
|
102
100
|
const displayData = filterCoin ? fundingData : fundingData.slice(0, topN);
|
|
103
101
|
|
|
102
|
+
if (jsonOutput) {
|
|
103
|
+
console.log(JSON.stringify(displayData, null, 2));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
104
107
|
if (displayData.length === 0) {
|
|
105
108
|
console.log(filterCoin ? `No data for ${filterCoin}` : 'No funding data available');
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
console.log('Open Broker - Funding Rates');
|
|
113
|
+
console.log('===========================\n');
|
|
114
|
+
|
|
109
115
|
// Table header
|
|
110
116
|
console.log('Coin | Hourly Rate | Annualized | Premium | Open Interest | Mark');
|
|
111
117
|
console.log('---------|-------------|------------|-------------|---------------|----------');
|
package/scripts/info/markets.ts
CHANGED
|
@@ -21,9 +21,7 @@ async function main() {
|
|
|
21
21
|
const filterCoin = args.coin as string | undefined;
|
|
22
22
|
const topN = parseInt(args.top as string) || 30;
|
|
23
23
|
const sortBy = (args.sort as string) || 'volume'; // volume, oi, change
|
|
24
|
-
|
|
25
|
-
console.log('Open Broker - Markets');
|
|
26
|
-
console.log('=====================\n');
|
|
24
|
+
const jsonOutput = args.json as boolean;
|
|
27
25
|
|
|
28
26
|
const client = getClient();
|
|
29
27
|
const includeHip3 = args['include-hip3'] as boolean || args['hip3'] as boolean || (filterCoin?.includes(':') ?? false);
|
|
@@ -109,6 +107,14 @@ async function main() {
|
|
|
109
107
|
// Limit
|
|
110
108
|
const displayData = filterCoin ? markets : markets.slice(0, topN);
|
|
111
109
|
|
|
110
|
+
if (jsonOutput) {
|
|
111
|
+
console.log(JSON.stringify(displayData, null, 2));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log('Open Broker - Markets');
|
|
116
|
+
console.log('=====================\n');
|
|
117
|
+
|
|
112
118
|
if (displayData.length === 0) {
|
|
113
119
|
console.log(filterCoin ? `No data for ${filterCoin}` : 'No market data available');
|
|
114
120
|
return;
|
package/scripts/info/orders.ts
CHANGED
|
@@ -32,11 +32,9 @@ async function main() {
|
|
|
32
32
|
const filterCoin = args.coin as string | undefined;
|
|
33
33
|
const filterStatus = args.status as string | undefined;
|
|
34
34
|
const top = parseInt(args.top as string) || 20;
|
|
35
|
+
const jsonOutput = args.json as boolean;
|
|
35
36
|
const client = getClient();
|
|
36
37
|
|
|
37
|
-
console.log('Open Broker - Order History');
|
|
38
|
-
console.log('==========================\n');
|
|
39
|
-
|
|
40
38
|
try {
|
|
41
39
|
let orders = await client.getHistoricalOrders();
|
|
42
40
|
|
|
@@ -52,6 +50,23 @@ async function main() {
|
|
|
52
50
|
orders.sort((a, b) => b.order.timestamp - a.order.timestamp);
|
|
53
51
|
orders = orders.slice(0, top);
|
|
54
52
|
|
|
53
|
+
if (jsonOutput) {
|
|
54
|
+
console.log(JSON.stringify(orders.map(entry => ({
|
|
55
|
+
time: new Date(entry.order.timestamp).toISOString(),
|
|
56
|
+
coin: entry.order.coin,
|
|
57
|
+
side: entry.order.side === 'B' ? 'buy' : 'sell',
|
|
58
|
+
orderType: entry.order.orderType,
|
|
59
|
+
size: entry.order.sz,
|
|
60
|
+
price: entry.order.limitPx,
|
|
61
|
+
status: entry.status,
|
|
62
|
+
oid: entry.order.oid,
|
|
63
|
+
})), null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log('Open Broker - Order History');
|
|
68
|
+
console.log('==========================\n');
|
|
69
|
+
|
|
55
70
|
if (orders.length === 0) {
|
|
56
71
|
console.log('No orders found');
|
|
57
72
|
return;
|
|
@@ -7,10 +7,12 @@ import { formatUsd, formatPercent, parseArgs } from '../core/utils.js';
|
|
|
7
7
|
async function main() {
|
|
8
8
|
const args = parseArgs(process.argv.slice(2));
|
|
9
9
|
const filterCoin = args.coin as string | undefined;
|
|
10
|
+
const jsonOutput = args.json as boolean;
|
|
10
11
|
const client = getClient();
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (args.verbose) {
|
|
14
|
+
client.verbose = true;
|
|
15
|
+
}
|
|
14
16
|
|
|
15
17
|
try {
|
|
16
18
|
const [state, mids, fundingHistory] = await Promise.all([
|
|
@@ -26,11 +28,6 @@ async function main() {
|
|
|
26
28
|
return true;
|
|
27
29
|
});
|
|
28
30
|
|
|
29
|
-
if (positions.length === 0) {
|
|
30
|
-
console.log(filterCoin ? `No position in ${filterCoin}` : 'No open positions');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
31
|
// Sum cumulative funding per coin
|
|
35
32
|
const fundingByCoin = new Map<string, number>();
|
|
36
33
|
for (const entry of fundingHistory) {
|
|
@@ -39,6 +36,44 @@ async function main() {
|
|
|
39
36
|
fundingByCoin.set(coin, (fundingByCoin.get(coin) ?? 0) + usdc);
|
|
40
37
|
}
|
|
41
38
|
|
|
39
|
+
// JSON output
|
|
40
|
+
if (jsonOutput) {
|
|
41
|
+
const result = positions.map(ap => {
|
|
42
|
+
const pos = ap.position;
|
|
43
|
+
const size = parseFloat(pos.szi);
|
|
44
|
+
const markPx = parseFloat(mids[pos.coin] || '0');
|
|
45
|
+
const liqPx = pos.liquidationPx ? parseFloat(pos.liquidationPx) : null;
|
|
46
|
+
return {
|
|
47
|
+
coin: pos.coin,
|
|
48
|
+
side: size > 0 ? 'long' : 'short',
|
|
49
|
+
size: pos.szi,
|
|
50
|
+
entryPrice: pos.entryPx,
|
|
51
|
+
markPrice: markPx,
|
|
52
|
+
notional: Math.abs(parseFloat(pos.positionValue)),
|
|
53
|
+
unrealizedPnl: parseFloat(pos.unrealizedPnl),
|
|
54
|
+
returnOnEquity: parseFloat(pos.returnOnEquity),
|
|
55
|
+
cumulativeFunding: fundingByCoin.get(pos.coin) ?? 0,
|
|
56
|
+
marginUsed: parseFloat(pos.marginUsed),
|
|
57
|
+
leverage: `${pos.leverage.value}x`,
|
|
58
|
+
leverageType: pos.leverage.type,
|
|
59
|
+
liquidationPrice: liqPx,
|
|
60
|
+
liquidationDistance: liqPx && markPx ? Math.abs((markPx - liqPx) / markPx) : null,
|
|
61
|
+
maxLeverage: pos.maxLeverage,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
console.log(JSON.stringify(result, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Human-readable output
|
|
69
|
+
console.log('Open Broker - Positions');
|
|
70
|
+
console.log('=======================\n');
|
|
71
|
+
|
|
72
|
+
if (positions.length === 0) {
|
|
73
|
+
console.log(filterCoin ? `No position in ${filterCoin}` : 'No open positions');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
42
77
|
for (const ap of positions) {
|
|
43
78
|
const pos = ap.position;
|
|
44
79
|
const size = parseFloat(pos.szi);
|
package/scripts/plugin/tools.ts
CHANGED
|
@@ -35,18 +35,27 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
35
35
|
const { getClient } = await import('../core/client.js');
|
|
36
36
|
const client = getClient();
|
|
37
37
|
const state = await client.getUserStateAll();
|
|
38
|
+
const accountMode = await client.getAccountMode();
|
|
39
|
+
|
|
40
|
+
const margin = state.crossMarginSummary;
|
|
41
|
+
const accountValue = parseFloat(margin.accountValue);
|
|
42
|
+
const totalMarginUsed = parseFloat(margin.totalMarginUsed);
|
|
38
43
|
|
|
39
44
|
const result: Record<string, unknown> = {
|
|
40
45
|
address: client.address,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
signingWallet: client.walletAddress,
|
|
47
|
+
walletType: client.isApiWallet ? 'api' : 'main',
|
|
48
|
+
accountMode,
|
|
49
|
+
equity: margin.accountValue,
|
|
50
|
+
totalNtlPos: margin.totalNtlPos,
|
|
51
|
+
totalMarginUsed: margin.totalMarginUsed,
|
|
52
|
+
withdrawable: margin.withdrawable,
|
|
53
|
+
marginRatio: totalMarginUsed > 0 && accountValue > 0 ? totalMarginUsed / accountValue : 0,
|
|
46
54
|
positions: state.assetPositions
|
|
47
55
|
.filter(ap => parseFloat(ap.position.szi) !== 0)
|
|
48
56
|
.map(ap => ({
|
|
49
57
|
coin: ap.position.coin,
|
|
58
|
+
side: parseFloat(ap.position.szi) > 0 ? 'long' : 'short',
|
|
50
59
|
size: ap.position.szi,
|
|
51
60
|
entryPrice: ap.position.entryPx,
|
|
52
61
|
positionValue: ap.position.positionValue,
|
|
@@ -61,7 +70,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
61
70
|
result.openOrders = orders.map(o => ({
|
|
62
71
|
coin: o.coin,
|
|
63
72
|
oid: o.oid,
|
|
64
|
-
side: o.side,
|
|
73
|
+
side: o.side === 'B' ? 'buy' : 'sell',
|
|
65
74
|
size: o.sz,
|
|
66
75
|
price: o.limitPx,
|
|
67
76
|
orderType: o.orderType,
|
|
@@ -85,22 +94,44 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
85
94
|
async execute(_id, params) {
|
|
86
95
|
const { getClient } = await import('../core/client.js');
|
|
87
96
|
const client = getClient();
|
|
88
|
-
const state = await
|
|
97
|
+
const [state, mids, fundingHistory] = await Promise.all([
|
|
98
|
+
client.getUserStateAll(),
|
|
99
|
+
client.getAllMids(),
|
|
100
|
+
client.getUserFunding(),
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
// Sum cumulative funding per coin
|
|
104
|
+
const fundingByCoin = new Map<string, number>();
|
|
105
|
+
for (const entry of fundingHistory) {
|
|
106
|
+
const coin = entry.delta.coin;
|
|
107
|
+
const usdc = parseFloat(entry.delta.usdc);
|
|
108
|
+
fundingByCoin.set(coin, (fundingByCoin.get(coin) ?? 0) + usdc);
|
|
109
|
+
}
|
|
89
110
|
|
|
90
111
|
let positions = state.assetPositions
|
|
91
112
|
.filter(ap => parseFloat(ap.position.szi) !== 0)
|
|
92
|
-
.map(ap =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
.map(ap => {
|
|
114
|
+
const pos = ap.position;
|
|
115
|
+
const markPx = parseFloat(mids[pos.coin] || '0');
|
|
116
|
+
const liqPx = pos.liquidationPx ? parseFloat(pos.liquidationPx) : null;
|
|
117
|
+
return {
|
|
118
|
+
coin: pos.coin,
|
|
119
|
+
side: parseFloat(pos.szi) > 0 ? 'long' : 'short',
|
|
120
|
+
size: pos.szi,
|
|
121
|
+
entryPrice: pos.entryPx,
|
|
122
|
+
markPrice: markPx,
|
|
123
|
+
notional: Math.abs(parseFloat(pos.positionValue)),
|
|
124
|
+
unrealizedPnl: parseFloat(pos.unrealizedPnl),
|
|
125
|
+
returnOnEquity: parseFloat(pos.returnOnEquity),
|
|
126
|
+
cumulativeFunding: fundingByCoin.get(pos.coin) ?? 0,
|
|
127
|
+
marginUsed: parseFloat(pos.marginUsed),
|
|
128
|
+
leverage: `${pos.leverage.value}x`,
|
|
129
|
+
leverageType: pos.leverage.type,
|
|
130
|
+
liquidationPrice: liqPx,
|
|
131
|
+
liquidationDistance: liqPx && markPx ? Math.abs((markPx - liqPx) / markPx) : null,
|
|
132
|
+
maxLeverage: pos.maxLeverage,
|
|
133
|
+
};
|
|
134
|
+
});
|
|
104
135
|
|
|
105
136
|
if (params.coin) {
|
|
106
137
|
const coin = normalizeCoin(params.coin as string);
|
|
@@ -125,23 +156,59 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
125
156
|
const { getClient } = await import('../core/client.js');
|
|
126
157
|
const { annualizeFundingRate } = await import('../core/utils.js');
|
|
127
158
|
const client = getClient();
|
|
128
|
-
const raw = await client.getPredictedFundings();
|
|
129
159
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Use the first venue's funding rate
|
|
133
|
-
const rate = venues.length > 0 ? parseFloat(venues[0][1].fundingRate) : 0;
|
|
134
|
-
return {
|
|
135
|
-
coin,
|
|
136
|
-
fundingRate: rate,
|
|
137
|
-
annualizedRate: annualizeFundingRate(rate),
|
|
138
|
-
venues: venues.map(([venue, data]) => ({ venue, fundingRate: data.fundingRate })),
|
|
139
|
-
};
|
|
140
|
-
});
|
|
160
|
+
const filterCoin = params.coin ? normalizeCoin(params.coin as string) : undefined;
|
|
161
|
+
const includeHip3 = !filterCoin || filterCoin.includes(':');
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
let results: Array<{ coin: string; fundingRate: number; annualizedRate: number; openInterest: string; markPx: string; type: string }> = [];
|
|
164
|
+
|
|
165
|
+
// Main dex funding from meta
|
|
166
|
+
try {
|
|
167
|
+
const { meta, assetCtxs } = await client.getMetaAndAssetCtxs();
|
|
168
|
+
for (let i = 0; i < meta.universe.length; i++) {
|
|
169
|
+
const asset = meta.universe[i];
|
|
170
|
+
const ctx = assetCtxs[i];
|
|
171
|
+
if (!ctx) continue;
|
|
172
|
+
if (filterCoin && asset.name !== filterCoin) continue;
|
|
173
|
+
|
|
174
|
+
const rate = parseFloat(ctx.funding);
|
|
175
|
+
results.push({
|
|
176
|
+
coin: asset.name,
|
|
177
|
+
fundingRate: rate,
|
|
178
|
+
annualizedRate: annualizeFundingRate(rate),
|
|
179
|
+
openInterest: ctx.openInterest,
|
|
180
|
+
markPx: ctx.markPx,
|
|
181
|
+
type: 'perp',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
} catch { /* skip */ }
|
|
185
|
+
|
|
186
|
+
// HIP-3 dex funding
|
|
187
|
+
if (includeHip3) {
|
|
188
|
+
try {
|
|
189
|
+
const allPerps = await client.getAllPerpMetas();
|
|
190
|
+
for (let dexIdx = 1; dexIdx < allPerps.length; dexIdx++) {
|
|
191
|
+
const dexData = allPerps[dexIdx];
|
|
192
|
+
if (!dexData?.meta?.universe) continue;
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
195
|
+
const asset = dexData.meta.universe[i];
|
|
196
|
+
const ctx = dexData.assetCtxs[i];
|
|
197
|
+
if (!asset || !ctx) continue;
|
|
198
|
+
if (filterCoin && asset.name !== filterCoin) continue;
|
|
199
|
+
|
|
200
|
+
const rate = parseFloat(ctx.funding);
|
|
201
|
+
results.push({
|
|
202
|
+
coin: asset.name,
|
|
203
|
+
fundingRate: rate,
|
|
204
|
+
annualizedRate: annualizeFundingRate(rate),
|
|
205
|
+
openInterest: ctx.openInterest,
|
|
206
|
+
markPx: ctx.markPx,
|
|
207
|
+
type: 'hip3',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch { /* skip */ }
|
|
145
212
|
}
|
|
146
213
|
|
|
147
214
|
results.sort((a, b) => Math.abs(b.annualizedRate) - Math.abs(a.annualizedRate));
|
|
@@ -155,7 +222,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
155
222
|
|
|
156
223
|
{
|
|
157
224
|
name: 'ob_markets',
|
|
158
|
-
description: 'View market data for Hyperliquid perpetuals (price, volume, open interest)',
|
|
225
|
+
description: 'View market data for Hyperliquid perpetuals (price, volume, open interest). Includes HIP-3 markets.',
|
|
159
226
|
parameters: {
|
|
160
227
|
type: 'object',
|
|
161
228
|
properties: {
|
|
@@ -168,28 +235,75 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
168
235
|
const client = getClient();
|
|
169
236
|
const { meta, assetCtxs } = await client.getMetaAndAssetCtxs();
|
|
170
237
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
238
|
+
const filterCoin = params.coin ? normalizeCoin(params.coin as string) : undefined;
|
|
239
|
+
const includeHip3 = !filterCoin || filterCoin.includes(':');
|
|
240
|
+
|
|
241
|
+
interface MarketEntry { coin: string; type: string; markPx: number; oraclePx: number; change24h: number; dayVolume: number; openInterest: number; maxLeverage: number; szDecimals: number; funding: string }
|
|
242
|
+
|
|
243
|
+
const markets: MarketEntry[] = [];
|
|
244
|
+
|
|
245
|
+
// Main dex
|
|
246
|
+
for (let i = 0; i < meta.universe.length; i++) {
|
|
247
|
+
const asset = meta.universe[i];
|
|
248
|
+
const ctx = assetCtxs[i];
|
|
249
|
+
if (!ctx) continue;
|
|
250
|
+
if (filterCoin && asset.name !== filterCoin) continue;
|
|
251
|
+
|
|
252
|
+
const markPx = parseFloat(ctx.markPx);
|
|
253
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
254
|
+
markets.push({
|
|
255
|
+
coin: asset.name,
|
|
256
|
+
type: 'perp',
|
|
257
|
+
markPx,
|
|
258
|
+
oraclePx: parseFloat(ctx.oraclePx),
|
|
259
|
+
change24h: prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0,
|
|
260
|
+
dayVolume: parseFloat(ctx.dayNtlVlm),
|
|
261
|
+
openInterest: parseFloat(ctx.openInterest),
|
|
262
|
+
maxLeverage: asset.maxLeverage,
|
|
263
|
+
szDecimals: asset.szDecimals,
|
|
264
|
+
funding: ctx.funding,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
183
267
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
268
|
+
// HIP-3 dexes
|
|
269
|
+
if (includeHip3) {
|
|
270
|
+
try {
|
|
271
|
+
const allPerps = await client.getAllPerpMetas();
|
|
272
|
+
for (let dexIdx = 1; dexIdx < allPerps.length; dexIdx++) {
|
|
273
|
+
const dexData = allPerps[dexIdx];
|
|
274
|
+
if (!dexData?.meta?.universe) continue;
|
|
275
|
+
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
276
|
+
const asset = dexData.meta.universe[i];
|
|
277
|
+
const ctx = dexData.assetCtxs[i];
|
|
278
|
+
if (!asset || !ctx) continue;
|
|
279
|
+
if (filterCoin && asset.name !== filterCoin) continue;
|
|
280
|
+
|
|
281
|
+
const markPx = parseFloat(ctx.markPx);
|
|
282
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
283
|
+
markets.push({
|
|
284
|
+
coin: asset.name,
|
|
285
|
+
type: 'hip3',
|
|
286
|
+
markPx,
|
|
287
|
+
oraclePx: parseFloat(ctx.oraclePx),
|
|
288
|
+
change24h: prevDayPx > 0 ? (markPx - prevDayPx) / prevDayPx : 0,
|
|
289
|
+
dayVolume: parseFloat(ctx.dayNtlVlm),
|
|
290
|
+
openInterest: parseFloat(ctx.openInterest),
|
|
291
|
+
maxLeverage: asset.maxLeverage,
|
|
292
|
+
szDecimals: asset.szDecimals,
|
|
293
|
+
funding: ctx.funding,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch { /* skip */ }
|
|
187
298
|
}
|
|
188
299
|
|
|
300
|
+
// Sort by volume (matches CLI behavior)
|
|
301
|
+
markets.sort((a, b) => b.dayVolume - a.dayVolume);
|
|
302
|
+
|
|
189
303
|
const top = (params.top as number) || 30;
|
|
190
|
-
|
|
304
|
+
const result = filterCoin ? markets : markets.slice(0, top);
|
|
191
305
|
|
|
192
|
-
return json({ markets });
|
|
306
|
+
return json({ markets: result });
|
|
193
307
|
},
|
|
194
308
|
},
|
|
195
309
|
|
|
@@ -226,7 +340,9 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
226
340
|
coin: asset.name,
|
|
227
341
|
type: 'perp',
|
|
228
342
|
markPx: assetCtxs[i]?.markPx,
|
|
343
|
+
funding: assetCtxs[i]?.funding,
|
|
229
344
|
dayVolume: assetCtxs[i]?.dayNtlVlm,
|
|
345
|
+
openInterest: assetCtxs[i]?.openInterest,
|
|
230
346
|
maxLeverage: asset.maxLeverage,
|
|
231
347
|
});
|
|
232
348
|
}
|
|
@@ -234,24 +350,26 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
234
350
|
} catch (e) { errors.push(`perp: ${e instanceof Error ? e.message : String(e)}`); }
|
|
235
351
|
}
|
|
236
352
|
|
|
237
|
-
// Search HIP-3 perps
|
|
238
|
-
if (!typeFilter || typeFilter === 'hip3'
|
|
353
|
+
// Search HIP-3 perps (always included unless filtering to perp-only or spot-only)
|
|
354
|
+
if (!typeFilter || typeFilter === 'hip3') {
|
|
239
355
|
try {
|
|
240
356
|
const allPerps = await client.getAllPerpMetas();
|
|
241
357
|
for (let dexIdx = 1; dexIdx < allPerps.length; dexIdx++) {
|
|
242
358
|
const dexData = allPerps[dexIdx];
|
|
243
|
-
if (!dexData
|
|
359
|
+
if (!dexData?.meta?.universe) continue;
|
|
244
360
|
for (let i = 0; i < dexData.meta.universe.length; i++) {
|
|
245
361
|
const asset = dexData.meta.universe[i];
|
|
362
|
+
const ctx = dexData.assetCtxs[i];
|
|
246
363
|
if (!asset) continue;
|
|
247
364
|
if (asset.name.toUpperCase().includes(query)) {
|
|
248
365
|
results.push({
|
|
249
|
-
// API returns names already prefixed (e.g., "xyz:CL")
|
|
250
366
|
coin: asset.name,
|
|
251
367
|
type: 'hip3',
|
|
252
368
|
dex: dexData.dexName,
|
|
253
|
-
markPx:
|
|
254
|
-
|
|
369
|
+
markPx: ctx?.markPx,
|
|
370
|
+
funding: ctx?.funding,
|
|
371
|
+
dayVolume: ctx?.dayNtlVlm,
|
|
372
|
+
openInterest: ctx?.openInterest,
|
|
255
373
|
maxLeverage: asset.maxLeverage,
|
|
256
374
|
});
|
|
257
375
|
}
|
|
@@ -757,7 +875,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
757
875
|
|
|
758
876
|
const coin = normalizeCoin(params.coin as string);
|
|
759
877
|
const size = params.size as number;
|
|
760
|
-
const slippageBps =
|
|
878
|
+
const slippageBps = params.slippage as number | undefined;
|
|
761
879
|
const leverage = params.leverage as number | undefined;
|
|
762
880
|
|
|
763
881
|
const mids = await client.getAllMids();
|
|
@@ -766,7 +884,8 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
766
884
|
|
|
767
885
|
const szDecimals = await client.getSzDecimals(coin);
|
|
768
886
|
const roundedSize = roundSize(size, szDecimals);
|
|
769
|
-
const
|
|
887
|
+
const effectiveSlippage = slippageBps ?? 50;
|
|
888
|
+
const slippagePrice = getSlippagePrice(midPrice, true, effectiveSlippage);
|
|
770
889
|
|
|
771
890
|
if (params.dry) {
|
|
772
891
|
return json({
|
|
@@ -776,7 +895,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
776
895
|
size: roundedSize,
|
|
777
896
|
midPrice,
|
|
778
897
|
slippagePrice,
|
|
779
|
-
slippageBps,
|
|
898
|
+
slippageBps: effectiveSlippage,
|
|
780
899
|
leverage,
|
|
781
900
|
});
|
|
782
901
|
}
|
|
@@ -809,7 +928,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
809
928
|
|
|
810
929
|
const coin = normalizeCoin(params.coin as string);
|
|
811
930
|
const size = params.size as number;
|
|
812
|
-
const slippageBps =
|
|
931
|
+
const slippageBps = params.slippage as number | undefined;
|
|
813
932
|
const leverage = params.leverage as number | undefined;
|
|
814
933
|
|
|
815
934
|
const mids = await client.getAllMids();
|
|
@@ -818,7 +937,8 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
818
937
|
|
|
819
938
|
const szDecimals = await client.getSzDecimals(coin);
|
|
820
939
|
const roundedSize = roundSize(size, szDecimals);
|
|
821
|
-
const
|
|
940
|
+
const effectiveSlippage = slippageBps ?? 50;
|
|
941
|
+
const slippagePrice = getSlippagePrice(midPrice, false, effectiveSlippage);
|
|
822
942
|
|
|
823
943
|
if (params.dry) {
|
|
824
944
|
return json({
|
|
@@ -828,7 +948,7 @@ export function createTools(watcher: PositionWatcher | null): PluginTool[] {
|
|
|
828
948
|
size: roundedSize,
|
|
829
949
|
midPrice,
|
|
830
950
|
slippagePrice,
|
|
831
|
-
slippageBps,
|
|
951
|
+
slippageBps: effectiveSlippage,
|
|
832
952
|
leverage,
|
|
833
953
|
});
|
|
834
954
|
}
|