openbroker 1.3.2 → 1.5.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/CHANGELOG.md +23 -0
- package/dist/auto/audit.d.ts +57 -0
- package/dist/auto/audit.d.ts.map +1 -0
- package/dist/auto/audit.js +407 -0
- package/dist/auto/cli.d.ts +2 -0
- package/dist/auto/cli.d.ts.map +1 -0
- package/dist/auto/cli.js +423 -0
- package/dist/auto/events.d.ts +11 -0
- package/dist/auto/events.d.ts.map +1 -0
- package/dist/auto/events.js +36 -0
- package/dist/auto/examples/dca.d.ts +4 -0
- package/dist/auto/examples/dca.d.ts.map +1 -0
- package/dist/auto/examples/dca.js +60 -0
- package/dist/auto/examples/funding-arb.d.ts +4 -0
- package/dist/auto/examples/funding-arb.d.ts.map +1 -0
- package/dist/auto/examples/funding-arb.js +81 -0
- package/dist/auto/examples/grid.d.ts +4 -0
- package/dist/auto/examples/grid.d.ts.map +1 -0
- package/dist/auto/examples/grid.js +114 -0
- package/dist/auto/examples/mm-maker.d.ts +4 -0
- package/dist/auto/examples/mm-maker.d.ts.map +1 -0
- package/dist/auto/examples/mm-maker.js +131 -0
- package/dist/auto/examples/mm-spread.d.ts +4 -0
- package/dist/auto/examples/mm-spread.d.ts.map +1 -0
- package/dist/auto/examples/mm-spread.js +119 -0
- package/dist/auto/examples/price-alert.d.ts +4 -0
- package/dist/auto/examples/price-alert.d.ts.map +1 -0
- package/dist/auto/examples/price-alert.js +85 -0
- package/dist/auto/keep-awake.d.ts +11 -0
- package/dist/auto/keep-awake.d.ts.map +1 -0
- package/dist/auto/keep-awake.js +70 -0
- package/dist/auto/loader.d.ts +22 -0
- package/dist/auto/loader.d.ts.map +1 -0
- package/dist/auto/loader.js +127 -0
- package/dist/auto/prune.d.ts +40 -0
- package/dist/auto/prune.d.ts.map +1 -0
- package/dist/auto/prune.js +204 -0
- package/dist/auto/registry.d.ts +24 -0
- package/dist/auto/registry.d.ts.map +1 -0
- package/dist/auto/registry.js +93 -0
- package/dist/auto/report.d.ts +3 -0
- package/dist/auto/report.d.ts.map +1 -0
- package/dist/auto/report.js +385 -0
- package/dist/auto/runtime.d.ts +33 -0
- package/dist/auto/runtime.d.ts.map +1 -0
- package/dist/auto/runtime.js +844 -0
- package/dist/auto/types.d.ts +236 -0
- package/dist/auto/types.d.ts.map +1 -0
- package/dist/auto/types.js +3 -0
- package/dist/core/client.d.ts +691 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2061 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +143 -0
- package/dist/core/types.d.ts +228 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.d.ts +61 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +142 -0
- package/dist/core/ws.d.ts +121 -0
- package/dist/core/ws.d.ts.map +1 -0
- package/dist/core/ws.js +222 -0
- package/dist/info/account.d.ts +3 -0
- package/dist/info/account.d.ts.map +1 -0
- package/dist/info/account.js +198 -0
- package/dist/info/all-markets.d.ts +3 -0
- package/dist/info/all-markets.d.ts.map +1 -0
- package/dist/info/all-markets.js +272 -0
- package/dist/info/candles.d.ts +3 -0
- package/dist/info/candles.d.ts.map +1 -0
- package/dist/info/candles.js +120 -0
- package/dist/info/fees.d.ts +3 -0
- package/dist/info/fees.d.ts.map +1 -0
- package/dist/info/fees.js +87 -0
- package/dist/info/fills.d.ts +3 -0
- package/dist/info/fills.d.ts.map +1 -0
- package/dist/info/fills.js +105 -0
- package/dist/info/funding-history.d.ts +3 -0
- package/dist/info/funding-history.d.ts.map +1 -0
- package/dist/info/funding-history.js +98 -0
- package/dist/info/funding-scan.d.ts +3 -0
- package/dist/info/funding-scan.d.ts.map +1 -0
- package/dist/info/funding-scan.js +178 -0
- package/dist/info/funding.d.ts +3 -0
- package/dist/info/funding.d.ts.map +1 -0
- package/dist/info/funding.js +158 -0
- package/dist/info/markets.d.ts +3 -0
- package/dist/info/markets.d.ts.map +1 -0
- package/dist/info/markets.js +178 -0
- package/dist/info/order-status.d.ts +3 -0
- package/dist/info/order-status.d.ts.map +1 -0
- package/dist/info/order-status.js +85 -0
- package/dist/info/orders.d.ts +3 -0
- package/dist/info/orders.d.ts.map +1 -0
- package/dist/info/orders.js +162 -0
- package/dist/info/outcomes.d.ts +3 -0
- package/dist/info/outcomes.d.ts.map +1 -0
- package/dist/info/outcomes.js +175 -0
- package/dist/info/positions.d.ts +3 -0
- package/dist/info/positions.d.ts.map +1 -0
- package/dist/info/positions.js +127 -0
- package/dist/info/rate-limit.d.ts +3 -0
- package/dist/info/rate-limit.d.ts.map +1 -0
- package/dist/info/rate-limit.js +58 -0
- package/dist/info/search-markets.d.ts +3 -0
- package/dist/info/search-markets.d.ts.map +1 -0
- package/dist/info/search-markets.js +296 -0
- package/dist/info/spot.d.ts +3 -0
- package/dist/info/spot.d.ts.map +1 -0
- package/dist/info/spot.js +192 -0
- package/dist/info/trades.d.ts +3 -0
- package/dist/info/trades.d.ts.map +1 -0
- package/dist/info/trades.js +97 -0
- package/dist/lib.d.ts +14 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +17 -0
- package/dist/operations/bracket.d.ts +28 -0
- package/dist/operations/bracket.d.ts.map +1 -0
- package/dist/operations/bracket.js +266 -0
- package/dist/operations/cancel.d.ts +3 -0
- package/dist/operations/cancel.d.ts.map +1 -0
- package/dist/operations/cancel.js +107 -0
- package/dist/operations/chase.d.ts +25 -0
- package/dist/operations/chase.d.ts.map +1 -0
- package/dist/operations/chase.js +215 -0
- package/dist/operations/limit-order.d.ts +3 -0
- package/dist/operations/limit-order.d.ts.map +1 -0
- package/dist/operations/limit-order.js +144 -0
- package/dist/operations/market-order.d.ts +3 -0
- package/dist/operations/market-order.d.ts.map +1 -0
- package/dist/operations/market-order.js +153 -0
- package/dist/operations/outcome-order.d.ts +3 -0
- package/dist/operations/outcome-order.d.ts.map +1 -0
- package/dist/operations/outcome-order.js +171 -0
- package/dist/operations/scale.d.ts +3 -0
- package/dist/operations/scale.d.ts.map +1 -0
- package/dist/operations/scale.js +212 -0
- package/dist/operations/set-tpsl.d.ts +3 -0
- package/dist/operations/set-tpsl.d.ts.map +1 -0
- package/dist/operations/set-tpsl.js +277 -0
- package/dist/operations/spot-order.d.ts +3 -0
- package/dist/operations/spot-order.d.ts.map +1 -0
- package/dist/operations/spot-order.js +173 -0
- package/dist/operations/trigger-order.d.ts +3 -0
- package/dist/operations/trigger-order.d.ts.map +1 -0
- package/dist/operations/trigger-order.js +177 -0
- package/dist/operations/twap-cancel.d.ts +3 -0
- package/dist/operations/twap-cancel.d.ts.map +1 -0
- package/dist/operations/twap-cancel.js +57 -0
- package/dist/operations/twap-status.d.ts +3 -0
- package/dist/operations/twap-status.d.ts.map +1 -0
- package/dist/operations/twap-status.js +81 -0
- package/dist/operations/twap.d.ts +3 -0
- package/dist/operations/twap.d.ts.map +1 -0
- package/dist/operations/twap.js +124 -0
- package/dist/setup/approve-builder.d.ts +3 -0
- package/dist/setup/approve-builder.d.ts.map +1 -0
- package/dist/setup/approve-builder.js +155 -0
- package/dist/setup/env.d.ts +4 -0
- package/dist/setup/env.d.ts.map +1 -0
- package/dist/setup/env.js +8 -0
- package/dist/setup/onboard.d.ts +10 -0
- package/dist/setup/onboard.d.ts.map +1 -0
- package/dist/setup/onboard.js +462 -0
- package/package.json +10 -4
- package/scripts/core/client.ts +19 -1
- package/scripts/core/types.ts +7 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Bracket Order - Entry with Take Profit and Stop Loss
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
import { formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log(`
|
|
8
|
+
Open Broker - Bracket Order
|
|
9
|
+
===========================
|
|
10
|
+
|
|
11
|
+
Execute an entry order with automatic take-profit and stop-loss orders.
|
|
12
|
+
Creates a complete trade setup in one command.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx tsx scripts/operations/bracket.ts --coin <COIN> --side <buy|sell> --size <SIZE> --tp <PCT> --sl <PCT>
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--coin Asset to trade (e.g., ETH, BTC)
|
|
19
|
+
--side Entry side: buy (long) or sell (short)
|
|
20
|
+
--size Position size in base asset
|
|
21
|
+
--entry Entry type: market or limit (default: market)
|
|
22
|
+
--price Entry price (required if --entry limit)
|
|
23
|
+
--tp Take profit distance in % from entry
|
|
24
|
+
--sl Stop loss distance in % from entry
|
|
25
|
+
--slippage Slippage for market entry in bps (default: 50)
|
|
26
|
+
--leverage Set leverage (e.g., 10 for 10x). Cross for main perps, isolated for HIP-3
|
|
27
|
+
--dry Dry run - show bracket plan without executing
|
|
28
|
+
|
|
29
|
+
Take Profit / Stop Loss:
|
|
30
|
+
For LONG (buy): TP is above entry, SL is below entry
|
|
31
|
+
For SHORT (sell): TP is below entry, SL is above entry
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
# Long ETH with 3% take profit and 1.5% stop loss
|
|
35
|
+
npx tsx scripts/operations/bracket.ts --coin ETH --side buy --size 0.5 --tp 3 --sl 1.5
|
|
36
|
+
|
|
37
|
+
# Short BTC with limit entry at $100k, 5% TP, 2% SL
|
|
38
|
+
npx tsx scripts/operations/bracket.ts --coin BTC --side sell --size 0.1 --entry limit --price 100000 --tp 5 --sl 2
|
|
39
|
+
|
|
40
|
+
# Preview bracket setup
|
|
41
|
+
npx tsx scripts/operations/bracket.ts --coin SOL --side buy --size 10 --tp 5 --sl 2 --dry
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
export async function runBracket(opts) {
|
|
45
|
+
const out = opts.output ?? ((line) => console.log(line));
|
|
46
|
+
const entryType = opts.entryType ?? 'market';
|
|
47
|
+
const isLong = opts.side === 'buy';
|
|
48
|
+
if (opts.size <= 0 || isNaN(opts.size))
|
|
49
|
+
throw new Error('size must be positive');
|
|
50
|
+
if (opts.tpPct <= 0 || opts.slPct <= 0)
|
|
51
|
+
throw new Error('tp and sl must be positive percentages');
|
|
52
|
+
if (entryType === 'limit' && opts.entryPrice === undefined) {
|
|
53
|
+
throw new Error('entryPrice is required for limit entry');
|
|
54
|
+
}
|
|
55
|
+
const client = getClient();
|
|
56
|
+
if (opts.verbose)
|
|
57
|
+
client.verbose = true;
|
|
58
|
+
out('Open Broker - Bracket Order');
|
|
59
|
+
out('===========================\n');
|
|
60
|
+
const mids = await client.getAllMids();
|
|
61
|
+
const midPrice = parseFloat(mids[opts.coin]);
|
|
62
|
+
if (!midPrice)
|
|
63
|
+
throw new Error(`No market data for ${opts.coin}`);
|
|
64
|
+
const entry = entryType === 'limit' ? opts.entryPrice : midPrice;
|
|
65
|
+
let tpPrice = isLong
|
|
66
|
+
? entry * (1 + opts.tpPct / 100)
|
|
67
|
+
: entry * (1 - opts.tpPct / 100);
|
|
68
|
+
let slPrice = isLong
|
|
69
|
+
? entry * (1 - opts.slPct / 100)
|
|
70
|
+
: entry * (1 + opts.slPct / 100);
|
|
71
|
+
const riskReward = opts.tpPct / opts.slPct;
|
|
72
|
+
const notional = entry * opts.size;
|
|
73
|
+
out('Bracket Plan');
|
|
74
|
+
out('------------');
|
|
75
|
+
out(`Coin: ${opts.coin}`);
|
|
76
|
+
out(`Position: ${isLong ? 'LONG' : 'SHORT'}`);
|
|
77
|
+
out(`Size: ${opts.size}`);
|
|
78
|
+
out(`Entry Type: ${entryType.toUpperCase()}`);
|
|
79
|
+
out(`Current Mid: ${formatUsd(midPrice)}`);
|
|
80
|
+
out(`Entry Price: ${formatUsd(entry)}${entryType === 'market' ? ' (approx)' : ''}`);
|
|
81
|
+
out(`Take Profit: ${formatUsd(tpPrice)} (+${opts.tpPct}%)`);
|
|
82
|
+
out(`Stop Loss: ${formatUsd(slPrice)} (-${opts.slPct}%)`);
|
|
83
|
+
out(`Risk/Reward: 1:${riskReward.toFixed(2)}`);
|
|
84
|
+
out(`Est. Notional: ${formatUsd(notional)}`);
|
|
85
|
+
const potentialProfit = notional * (opts.tpPct / 100);
|
|
86
|
+
const potentialLoss = notional * (opts.slPct / 100);
|
|
87
|
+
out('\nRisk Analysis');
|
|
88
|
+
out('-------------');
|
|
89
|
+
out(`Potential Profit: ${formatUsd(potentialProfit)}`);
|
|
90
|
+
out(`Potential Loss: ${formatUsd(potentialLoss)}`);
|
|
91
|
+
if (opts.dryRun) {
|
|
92
|
+
out('\n🔍 Dry run - bracket not executed');
|
|
93
|
+
return { status: 'dry', entryPrice: entry, tpPrice, slPrice };
|
|
94
|
+
}
|
|
95
|
+
out('\nExecuting bracket...\n');
|
|
96
|
+
// Step 1: Entry
|
|
97
|
+
out('Step 1: Entry order');
|
|
98
|
+
let actualEntry = entry;
|
|
99
|
+
let entryOid = null;
|
|
100
|
+
if (entryType === 'market') {
|
|
101
|
+
const entryResponse = await client.marketOrder(opts.coin, isLong, opts.size, opts.slippage, opts.leverage);
|
|
102
|
+
if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
|
|
103
|
+
const status = entryResponse.response.data.statuses[0];
|
|
104
|
+
if (status?.filled) {
|
|
105
|
+
actualEntry = parseFloat(status.filled.avgPx);
|
|
106
|
+
out(` ✅ Filled @ ${formatUsd(actualEntry)}`);
|
|
107
|
+
}
|
|
108
|
+
else if (status?.error) {
|
|
109
|
+
out(` ❌ Entry failed: ${status.error}`);
|
|
110
|
+
out('\n⚠️ Bracket aborted - no position opened');
|
|
111
|
+
return { status: 'entry_failed', reason: status.error };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const reason = typeof entryResponse.response === 'string' ? entryResponse.response : 'Unknown error';
|
|
116
|
+
out(` ❌ Entry failed: ${reason}`);
|
|
117
|
+
out('\n⚠️ Bracket aborted - no position opened');
|
|
118
|
+
return { status: 'entry_failed', reason };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const entryResponse = await client.limitOrder(opts.coin, isLong, opts.size, entry, 'Gtc', false, opts.leverage);
|
|
123
|
+
if (entryResponse.status === 'ok' && entryResponse.response && typeof entryResponse.response === 'object') {
|
|
124
|
+
const status = entryResponse.response.data.statuses[0];
|
|
125
|
+
if (status?.resting) {
|
|
126
|
+
entryOid = status.resting.oid;
|
|
127
|
+
out(` ✅ Limit order placed @ ${formatUsd(entry)} (OID: ${entryOid})`);
|
|
128
|
+
out(` ⏳ Waiting for fill before placing TP/SL...`);
|
|
129
|
+
out('\n⚠️ Note: TP/SL will be placed after entry fills. Monitor manually or use a strategy script.');
|
|
130
|
+
return { status: 'limit_resting', entryOid, entryPrice: entry };
|
|
131
|
+
}
|
|
132
|
+
else if (status?.filled) {
|
|
133
|
+
actualEntry = parseFloat(status.filled.avgPx);
|
|
134
|
+
out(` ✅ Filled immediately @ ${formatUsd(actualEntry)}`);
|
|
135
|
+
}
|
|
136
|
+
else if (status?.error) {
|
|
137
|
+
out(` ❌ Entry failed: ${status.error}`);
|
|
138
|
+
return { status: 'entry_failed', reason: status.error };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
out(` ❌ Entry failed`);
|
|
143
|
+
return { status: 'entry_failed', reason: 'Unknown error' };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Recalculate TP/SL based on actual entry
|
|
147
|
+
if (isLong) {
|
|
148
|
+
tpPrice = actualEntry * (1 + opts.tpPct / 100);
|
|
149
|
+
slPrice = actualEntry * (1 - opts.slPct / 100);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
tpPrice = actualEntry * (1 - opts.tpPct / 100);
|
|
153
|
+
slPrice = actualEntry * (1 + opts.slPct / 100);
|
|
154
|
+
}
|
|
155
|
+
await sleep(500);
|
|
156
|
+
// Step 2: Take Profit (trigger order)
|
|
157
|
+
out('\nStep 2: Take Profit order (trigger)');
|
|
158
|
+
const tpSide = !isLong;
|
|
159
|
+
const tpResponse = await client.takeProfit(opts.coin, tpSide, opts.size, tpPrice);
|
|
160
|
+
let tpOid = null;
|
|
161
|
+
if (tpResponse.status === 'ok' && tpResponse.response && typeof tpResponse.response === 'object') {
|
|
162
|
+
const status = tpResponse.response.data.statuses[0];
|
|
163
|
+
if (status?.resting) {
|
|
164
|
+
tpOid = status.resting.oid;
|
|
165
|
+
out(` ✅ TP trigger placed @ ${formatUsd(tpPrice)} (OID: ${tpOid})`);
|
|
166
|
+
}
|
|
167
|
+
else if (status?.error) {
|
|
168
|
+
out(` ❌ TP failed: ${status.error}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
out(` ⚠️ TP status: ${JSON.stringify(status)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const reason = typeof tpResponse.response === 'string' ? tpResponse.response : 'Unknown error';
|
|
176
|
+
out(` ❌ TP failed: ${reason}`);
|
|
177
|
+
}
|
|
178
|
+
await sleep(500);
|
|
179
|
+
// Step 3: Stop Loss (trigger order)
|
|
180
|
+
out('\nStep 3: Stop Loss order (trigger)');
|
|
181
|
+
const slSide = !isLong;
|
|
182
|
+
const slResponse = await client.stopLoss(opts.coin, slSide, opts.size, slPrice);
|
|
183
|
+
let slOid = null;
|
|
184
|
+
if (slResponse.status === 'ok' && slResponse.response && typeof slResponse.response === 'object') {
|
|
185
|
+
const status = slResponse.response.data.statuses[0];
|
|
186
|
+
if (status?.resting) {
|
|
187
|
+
slOid = status.resting.oid;
|
|
188
|
+
out(` ✅ SL trigger placed @ ${formatUsd(slPrice)} (OID: ${slOid})`);
|
|
189
|
+
}
|
|
190
|
+
else if (status?.error) {
|
|
191
|
+
out(` ❌ SL failed: ${status.error}`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
out(` ⚠️ SL status: ${JSON.stringify(status)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const reason = typeof slResponse.response === 'string' ? slResponse.response : 'Unknown error';
|
|
199
|
+
out(` ❌ SL failed: ${reason}`);
|
|
200
|
+
}
|
|
201
|
+
out('\n========== Bracket Summary ==========');
|
|
202
|
+
out(`Position: ${isLong ? 'LONG' : 'SHORT'} ${opts.size} ${opts.coin}`);
|
|
203
|
+
out(`Entry: ${formatUsd(actualEntry)}`);
|
|
204
|
+
out(`Take Profit: ${formatUsd(tpPrice)} (+${opts.tpPct}%) - Trigger order`);
|
|
205
|
+
out(`Stop Loss: ${formatUsd(slPrice)} (-${opts.slPct}%) - Trigger order`);
|
|
206
|
+
if (tpOid && slOid) {
|
|
207
|
+
out(`\n✅ Bracket complete! TP and SL are trigger orders.`);
|
|
208
|
+
out(` They will only execute when price reaches trigger level.`);
|
|
209
|
+
out(` When one fills, cancel the other manually.`);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
status: tpOid && slOid ? 'complete' : 'partial',
|
|
213
|
+
entryPrice: actualEntry,
|
|
214
|
+
tpPrice,
|
|
215
|
+
slPrice,
|
|
216
|
+
tpOid,
|
|
217
|
+
slOid,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function main() {
|
|
221
|
+
const args = parseArgs(process.argv.slice(2));
|
|
222
|
+
const coin = args.coin;
|
|
223
|
+
const side = args.side;
|
|
224
|
+
const size = parseFloat(args.size);
|
|
225
|
+
const entryType = (args.entry || 'market');
|
|
226
|
+
const entryPrice = args.price ? parseFloat(args.price) : undefined;
|
|
227
|
+
const tpPct = parseFloat(args.tp);
|
|
228
|
+
const slPct = parseFloat(args.sl);
|
|
229
|
+
const slippage = args.slippage ? parseInt(args.slippage) : undefined;
|
|
230
|
+
const leverage = args.leverage ? parseInt(args.leverage) : undefined;
|
|
231
|
+
const dryRun = args.dry;
|
|
232
|
+
if (!coin || !side || isNaN(size) || isNaN(tpPct) || isNaN(slPct)) {
|
|
233
|
+
printUsage();
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
if (side !== 'buy' && side !== 'sell') {
|
|
237
|
+
console.error('Error: --side must be "buy" or "sell"');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const result = await runBracket({
|
|
242
|
+
coin,
|
|
243
|
+
side: side,
|
|
244
|
+
size,
|
|
245
|
+
tpPct,
|
|
246
|
+
slPct,
|
|
247
|
+
entryType,
|
|
248
|
+
entryPrice,
|
|
249
|
+
slippage,
|
|
250
|
+
leverage,
|
|
251
|
+
dryRun,
|
|
252
|
+
verbose: args.verbose,
|
|
253
|
+
});
|
|
254
|
+
if (result.status === 'entry_failed')
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Only run when invoked as a script — not when imported as a module
|
|
263
|
+
// (e.g. by `openbroker-plugin` via the lib re-export of `runBracket`).
|
|
264
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
265
|
+
main();
|
|
266
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancel.d.ts","sourceRoot":"","sources":["../../scripts/operations/cancel.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Cancel orders on Hyperliquid
|
|
3
|
+
import { getClient } from '../core/client.js';
|
|
4
|
+
import { formatUsd, parseArgs } from '../core/utils.js';
|
|
5
|
+
function printUsage() {
|
|
6
|
+
console.log(`
|
|
7
|
+
Open Broker - Cancel Orders
|
|
8
|
+
===========================
|
|
9
|
+
|
|
10
|
+
Cancel open orders.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
npx tsx scripts/operations/cancel.ts [--coin <COIN>] [--oid <ORDER_ID>] [--all]
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--coin Cancel orders for specific coin only
|
|
17
|
+
--oid Cancel specific order by ID
|
|
18
|
+
--all Cancel all open orders
|
|
19
|
+
--dry Dry run - show what would be cancelled
|
|
20
|
+
|
|
21
|
+
Examples:
|
|
22
|
+
npx tsx scripts/operations/cancel.ts --all # Cancel all orders
|
|
23
|
+
npx tsx scripts/operations/cancel.ts --coin ETH # Cancel all ETH orders
|
|
24
|
+
npx tsx scripts/operations/cancel.ts --coin ETH --oid 123456 # Cancel specific order
|
|
25
|
+
npx tsx scripts/operations/cancel.ts --all --dry # Show all orders (dry run)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
async function main() {
|
|
29
|
+
const args = parseArgs(process.argv.slice(2));
|
|
30
|
+
const coin = args.coin;
|
|
31
|
+
const oid = args.oid ? parseInt(args.oid) : undefined;
|
|
32
|
+
const all = args.all;
|
|
33
|
+
const dryRun = args.dry;
|
|
34
|
+
// Must specify something to cancel
|
|
35
|
+
if (!coin && !oid && !all) {
|
|
36
|
+
printUsage();
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const client = getClient();
|
|
40
|
+
console.log('Open Broker - Cancel Orders');
|
|
41
|
+
console.log('===========================\n');
|
|
42
|
+
try {
|
|
43
|
+
// Get open orders
|
|
44
|
+
const orders = await client.getOpenOrders();
|
|
45
|
+
// Filter orders based on arguments
|
|
46
|
+
let targetOrders = orders;
|
|
47
|
+
if (coin) {
|
|
48
|
+
targetOrders = orders.filter(o => o.coin === coin);
|
|
49
|
+
}
|
|
50
|
+
if (oid) {
|
|
51
|
+
targetOrders = orders.filter(o => o.oid === oid);
|
|
52
|
+
}
|
|
53
|
+
if (targetOrders.length === 0) {
|
|
54
|
+
if (oid) {
|
|
55
|
+
console.log(`No order found with ID ${oid}`);
|
|
56
|
+
}
|
|
57
|
+
else if (coin) {
|
|
58
|
+
console.log(`No open orders for ${coin}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log('No open orders to cancel');
|
|
62
|
+
}
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Display orders to be cancelled
|
|
66
|
+
console.log('Orders to Cancel');
|
|
67
|
+
console.log('----------------');
|
|
68
|
+
console.log('Coin | Side | Size | Price | Order ID');
|
|
69
|
+
console.log('---------|------|------------|------------|----------');
|
|
70
|
+
for (const order of targetOrders) {
|
|
71
|
+
const side = order.side === 'B' ? 'BUY ' : 'SELL';
|
|
72
|
+
console.log(`${order.coin.padEnd(8)} | ${side} | ${parseFloat(order.sz).toFixed(4).padStart(10)} | ` +
|
|
73
|
+
`${formatUsd(parseFloat(order.limitPx)).padStart(10)} | ${order.oid}`);
|
|
74
|
+
}
|
|
75
|
+
console.log(`\nTotal: ${targetOrders.length} order(s)`);
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
console.log('\n🔍 Dry run - orders not cancelled');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log('\nCancelling...');
|
|
81
|
+
let successCount = 0;
|
|
82
|
+
let failCount = 0;
|
|
83
|
+
for (const order of targetOrders) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await client.cancel(order.coin, order.oid);
|
|
86
|
+
if (response.status === 'ok') {
|
|
87
|
+
console.log(`✅ Cancelled ${order.coin} order ${order.oid}`);
|
|
88
|
+
successCount++;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(`❌ Failed to cancel ${order.coin} order ${order.oid}`);
|
|
92
|
+
failCount++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.log(`❌ Error cancelling ${order.coin} order ${order.oid}: ${err}`);
|
|
97
|
+
failCount++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
console.log(`\nResult: ${successCount} cancelled, ${failCount} failed`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.error('Error:', error);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
main();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
export interface ChaseOptions {
|
|
3
|
+
coin: string;
|
|
4
|
+
side: 'buy' | 'sell';
|
|
5
|
+
size: number;
|
|
6
|
+
offsetBps?: number;
|
|
7
|
+
timeoutSec?: number;
|
|
8
|
+
intervalMs?: number;
|
|
9
|
+
maxChaseBps?: number;
|
|
10
|
+
leverage?: number;
|
|
11
|
+
reduceOnly?: boolean;
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
/** Receives each output line. Defaults to console.log. */
|
|
15
|
+
output?: (line: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface ChaseResult {
|
|
18
|
+
status: 'dry' | 'filled' | 'timeout' | 'max_chase_exceeded';
|
|
19
|
+
iterations: number;
|
|
20
|
+
durationSec: number;
|
|
21
|
+
startMid: number;
|
|
22
|
+
endMid: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function runChase(opts: ChaseOptions): Promise<ChaseResult>;
|
|
25
|
+
//# sourceMappingURL=chase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chase.d.ts","sourceRoot":"","sources":["../../scripts/operations/chase.ts"],"names":[],"mappings":";AAuCA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,oBAAoB,CAAC;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA2JvE"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
// Chase Order - Follow price with limit orders until filled
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { getClient } from '../core/client.js';
|
|
5
|
+
import { formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log(`
|
|
8
|
+
Open Broker - Chase Order
|
|
9
|
+
=========================
|
|
10
|
+
|
|
11
|
+
Place a limit order that chases the price until filled.
|
|
12
|
+
Keeps adjusting the order to stay near the best price while avoiding taker fees.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx tsx scripts/operations/chase.ts --coin <COIN> --side <buy|sell> --size <SIZE>
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--coin Asset to trade (e.g., ETH, BTC)
|
|
19
|
+
--side Order side: buy or sell
|
|
20
|
+
--size Order size in base asset
|
|
21
|
+
--offset Offset from mid price in bps (default: 5 = 0.05%)
|
|
22
|
+
--timeout Max time to chase in seconds (default: 300 = 5 min)
|
|
23
|
+
--interval Update interval in ms (default: 2000)
|
|
24
|
+
--max-chase Max price to chase to in bps from start (default: 100 = 1%)
|
|
25
|
+
--leverage Set leverage (e.g., 10 for 10x). Cross for main perps, isolated for HIP-3
|
|
26
|
+
--reduce Reduce-only order
|
|
27
|
+
--dry Dry run - show chase parameters without executing
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
# Chase buy 0.5 ETH with 5 bps offset, 5 min timeout
|
|
31
|
+
npx tsx scripts/operations/chase.ts --coin ETH --side buy --size 0.5
|
|
32
|
+
|
|
33
|
+
# Chase sell with tighter offset and longer timeout
|
|
34
|
+
npx tsx scripts/operations/chase.ts --coin BTC --side sell --size 0.1 --offset 2 --timeout 600
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
37
|
+
export async function runChase(opts) {
|
|
38
|
+
const out = opts.output ?? ((line) => console.log(line));
|
|
39
|
+
const offsetBps = opts.offsetBps ?? 5;
|
|
40
|
+
const timeoutSec = opts.timeoutSec ?? 300;
|
|
41
|
+
const intervalMs = opts.intervalMs ?? 2000;
|
|
42
|
+
const maxChaseBps = opts.maxChaseBps ?? 100;
|
|
43
|
+
const isBuy = opts.side === 'buy';
|
|
44
|
+
if (opts.size <= 0 || isNaN(opts.size))
|
|
45
|
+
throw new Error('size must be positive');
|
|
46
|
+
const client = getClient();
|
|
47
|
+
if (opts.verbose)
|
|
48
|
+
client.verbose = true;
|
|
49
|
+
out('Open Broker - Chase Order');
|
|
50
|
+
out('=========================\n');
|
|
51
|
+
const mids = await client.getAllMids();
|
|
52
|
+
const startMid = parseFloat(mids[opts.coin]);
|
|
53
|
+
if (!startMid)
|
|
54
|
+
throw new Error(`No market data for ${opts.coin}`);
|
|
55
|
+
const maxChasePrice = isBuy
|
|
56
|
+
? startMid * (1 + maxChaseBps / 10000)
|
|
57
|
+
: startMid * (1 - maxChaseBps / 10000);
|
|
58
|
+
out('Chase Parameters');
|
|
59
|
+
out('----------------');
|
|
60
|
+
out(`Coin: ${opts.coin}`);
|
|
61
|
+
out(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
|
|
62
|
+
out(`Size: ${opts.size}`);
|
|
63
|
+
out(`Start Mid: ${formatUsd(startMid)}`);
|
|
64
|
+
out(`Offset: ${offsetBps} bps (${(offsetBps / 100).toFixed(2)}%)`);
|
|
65
|
+
out(`Max Chase: ${maxChaseBps} bps to ${formatUsd(maxChasePrice)}`);
|
|
66
|
+
out(`Timeout: ${timeoutSec}s`);
|
|
67
|
+
out(`Update Rate: ${intervalMs}ms`);
|
|
68
|
+
out(`Order Type: ALO (post-only)`);
|
|
69
|
+
if (opts.dryRun) {
|
|
70
|
+
out('\n🔍 Dry run - chase not started');
|
|
71
|
+
return { status: 'dry', iterations: 0, durationSec: 0, startMid, endMid: startMid };
|
|
72
|
+
}
|
|
73
|
+
out('\nChasing...\n');
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
let currentOid = null;
|
|
76
|
+
let lastPrice = null;
|
|
77
|
+
let iteration = 0;
|
|
78
|
+
let filled = false;
|
|
79
|
+
let exitReason = 'timeout';
|
|
80
|
+
while (Date.now() - startTime < timeoutSec * 1000) {
|
|
81
|
+
iteration++;
|
|
82
|
+
const currentMids = await client.getAllMids();
|
|
83
|
+
const currentMid = parseFloat(currentMids[opts.coin]);
|
|
84
|
+
if (isBuy && currentMid > maxChasePrice) {
|
|
85
|
+
out(`\n⚠️ Price ${formatUsd(currentMid)} exceeded max chase ${formatUsd(maxChasePrice)}`);
|
|
86
|
+
exitReason = 'max_chase_exceeded';
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (!isBuy && currentMid < maxChasePrice) {
|
|
90
|
+
out(`\n⚠️ Price ${formatUsd(currentMid)} exceeded max chase ${formatUsd(maxChasePrice)}`);
|
|
91
|
+
exitReason = 'max_chase_exceeded';
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
const orderPrice = isBuy
|
|
95
|
+
? currentMid * (1 - offsetBps / 10000)
|
|
96
|
+
: currentMid * (1 + offsetBps / 10000);
|
|
97
|
+
const priceChanged = !lastPrice || Math.abs(orderPrice - lastPrice) / lastPrice > 0.0001;
|
|
98
|
+
if (priceChanged) {
|
|
99
|
+
if (currentOid !== null) {
|
|
100
|
+
try {
|
|
101
|
+
await client.cancel(opts.coin, currentOid);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Order might have filled
|
|
105
|
+
}
|
|
106
|
+
currentOid = null;
|
|
107
|
+
}
|
|
108
|
+
const orders = await client.getOpenOrders();
|
|
109
|
+
const ourOrder = orders.find(o => o.coin === opts.coin && o.oid === currentOid);
|
|
110
|
+
if (!ourOrder && currentOid !== null) {
|
|
111
|
+
const state = await client.getUserState();
|
|
112
|
+
const pos = state.assetPositions.find(p => p.position.coin === opts.coin);
|
|
113
|
+
if (pos && Math.abs(parseFloat(pos.position.szi)) >= opts.size * 0.99) {
|
|
114
|
+
filled = true;
|
|
115
|
+
exitReason = 'filled';
|
|
116
|
+
out(`\n✅ Order filled!`);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
out(`[${iteration}] Mid: ${formatUsd(currentMid)} → Order: ${formatUsd(orderPrice)}...`);
|
|
121
|
+
const response = await client.limitOrder(opts.coin, isBuy, opts.size, orderPrice, 'Alo', opts.reduceOnly, opts.leverage);
|
|
122
|
+
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
123
|
+
const status = response.response.data.statuses[0];
|
|
124
|
+
if (status?.resting) {
|
|
125
|
+
currentOid = status.resting.oid;
|
|
126
|
+
lastPrice = orderPrice;
|
|
127
|
+
out(`OID: ${currentOid}`);
|
|
128
|
+
}
|
|
129
|
+
else if (status?.filled) {
|
|
130
|
+
filled = true;
|
|
131
|
+
exitReason = 'filled';
|
|
132
|
+
out(`✅ Filled @ ${formatUsd(parseFloat(status.filled.avgPx))}`);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
else if (status?.error) {
|
|
136
|
+
out(`❌ ${status.error}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
out(`❌ Failed`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
if (currentOid !== null) {
|
|
145
|
+
const orders = await client.getOpenOrders();
|
|
146
|
+
const ourOrder = orders.find(o => o.oid === currentOid);
|
|
147
|
+
if (!ourOrder) {
|
|
148
|
+
filled = true;
|
|
149
|
+
exitReason = 'filled';
|
|
150
|
+
out(`\n✅ Order filled!`);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await sleep(intervalMs);
|
|
156
|
+
}
|
|
157
|
+
if (currentOid !== null && !filled) {
|
|
158
|
+
out(`\nCancelling unfilled order...`);
|
|
159
|
+
try {
|
|
160
|
+
await client.cancel(opts.coin, currentOid);
|
|
161
|
+
out(`✅ Cancelled`);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
out(`⚠️ Could not cancel (may have filled)`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
168
|
+
const endMid = parseFloat((await client.getAllMids())[opts.coin]);
|
|
169
|
+
const priceMove = ((endMid - startMid) / startMid) * 10000;
|
|
170
|
+
out('\n========== Chase Summary ==========');
|
|
171
|
+
out(`Duration: ${elapsed.toFixed(1)}s`);
|
|
172
|
+
out(`Iterations: ${iteration}`);
|
|
173
|
+
out(`Start Mid: ${formatUsd(startMid)}`);
|
|
174
|
+
out(`End Mid: ${formatUsd(endMid)} (${priceMove >= 0 ? '+' : ''}${priceMove.toFixed(1)} bps)`);
|
|
175
|
+
out(`Result: ${filled ? '✅ Filled' : '❌ Not filled'}`);
|
|
176
|
+
return { status: exitReason, iterations: iteration, durationSec: elapsed, startMid, endMid };
|
|
177
|
+
}
|
|
178
|
+
async function main() {
|
|
179
|
+
const args = parseArgs(process.argv.slice(2));
|
|
180
|
+
const coin = args.coin;
|
|
181
|
+
const side = args.side;
|
|
182
|
+
const size = parseFloat(args.size);
|
|
183
|
+
if (!coin || !side || isNaN(size)) {
|
|
184
|
+
printUsage();
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
if (side !== 'buy' && side !== 'sell') {
|
|
188
|
+
console.error('Error: --side must be "buy" or "sell"');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await runChase({
|
|
193
|
+
coin,
|
|
194
|
+
side: side,
|
|
195
|
+
size,
|
|
196
|
+
offsetBps: args.offset ? parseInt(args.offset) : undefined,
|
|
197
|
+
timeoutSec: args.timeout ? parseInt(args.timeout) : undefined,
|
|
198
|
+
intervalMs: args.interval ? parseInt(args.interval) : undefined,
|
|
199
|
+
maxChaseBps: args['max-chase'] ? parseInt(args['max-chase']) : undefined,
|
|
200
|
+
leverage: args.leverage ? parseInt(args.leverage) : undefined,
|
|
201
|
+
reduceOnly: args.reduce,
|
|
202
|
+
dryRun: args.dry,
|
|
203
|
+
verbose: args.verbose,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Only run when invoked as a script — not when imported as a module
|
|
212
|
+
// (e.g. by `openbroker-plugin` via the lib re-export of `runChase`).
|
|
213
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
214
|
+
main();
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limit-order.d.ts","sourceRoot":"","sources":["../../scripts/operations/limit-order.ts"],"names":[],"mappings":""}
|