openbroker 1.3.2 → 1.4.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/auto/audit.d.ts +57 -0
  3. package/dist/auto/audit.d.ts.map +1 -0
  4. package/dist/auto/audit.js +407 -0
  5. package/dist/auto/cli.d.ts +2 -0
  6. package/dist/auto/cli.d.ts.map +1 -0
  7. package/dist/auto/cli.js +423 -0
  8. package/dist/auto/events.d.ts +11 -0
  9. package/dist/auto/events.d.ts.map +1 -0
  10. package/dist/auto/events.js +36 -0
  11. package/dist/auto/examples/dca.d.ts +4 -0
  12. package/dist/auto/examples/dca.d.ts.map +1 -0
  13. package/dist/auto/examples/dca.js +60 -0
  14. package/dist/auto/examples/funding-arb.d.ts +4 -0
  15. package/dist/auto/examples/funding-arb.d.ts.map +1 -0
  16. package/dist/auto/examples/funding-arb.js +81 -0
  17. package/dist/auto/examples/grid.d.ts +4 -0
  18. package/dist/auto/examples/grid.d.ts.map +1 -0
  19. package/dist/auto/examples/grid.js +114 -0
  20. package/dist/auto/examples/mm-maker.d.ts +4 -0
  21. package/dist/auto/examples/mm-maker.d.ts.map +1 -0
  22. package/dist/auto/examples/mm-maker.js +131 -0
  23. package/dist/auto/examples/mm-spread.d.ts +4 -0
  24. package/dist/auto/examples/mm-spread.d.ts.map +1 -0
  25. package/dist/auto/examples/mm-spread.js +119 -0
  26. package/dist/auto/examples/price-alert.d.ts +4 -0
  27. package/dist/auto/examples/price-alert.d.ts.map +1 -0
  28. package/dist/auto/examples/price-alert.js +85 -0
  29. package/dist/auto/keep-awake.d.ts +11 -0
  30. package/dist/auto/keep-awake.d.ts.map +1 -0
  31. package/dist/auto/keep-awake.js +70 -0
  32. package/dist/auto/loader.d.ts +22 -0
  33. package/dist/auto/loader.d.ts.map +1 -0
  34. package/dist/auto/loader.js +127 -0
  35. package/dist/auto/prune.d.ts +40 -0
  36. package/dist/auto/prune.d.ts.map +1 -0
  37. package/dist/auto/prune.js +204 -0
  38. package/dist/auto/registry.d.ts +24 -0
  39. package/dist/auto/registry.d.ts.map +1 -0
  40. package/dist/auto/registry.js +93 -0
  41. package/dist/auto/report.d.ts +3 -0
  42. package/dist/auto/report.d.ts.map +1 -0
  43. package/dist/auto/report.js +385 -0
  44. package/dist/auto/runtime.d.ts +33 -0
  45. package/dist/auto/runtime.d.ts.map +1 -0
  46. package/dist/auto/runtime.js +844 -0
  47. package/dist/auto/types.d.ts +236 -0
  48. package/dist/auto/types.d.ts.map +1 -0
  49. package/dist/auto/types.js +3 -0
  50. package/dist/core/client.d.ts +684 -0
  51. package/dist/core/client.d.ts.map +1 -0
  52. package/dist/core/client.js +2040 -0
  53. package/dist/core/config.d.ts +22 -0
  54. package/dist/core/config.d.ts.map +1 -0
  55. package/dist/core/config.js +143 -0
  56. package/dist/core/types.d.ts +221 -0
  57. package/dist/core/types.d.ts.map +1 -0
  58. package/dist/core/types.js +2 -0
  59. package/dist/core/utils.d.ts +61 -0
  60. package/dist/core/utils.d.ts.map +1 -0
  61. package/dist/core/utils.js +142 -0
  62. package/dist/core/ws.d.ts +121 -0
  63. package/dist/core/ws.d.ts.map +1 -0
  64. package/dist/core/ws.js +222 -0
  65. package/dist/info/account.d.ts +3 -0
  66. package/dist/info/account.d.ts.map +1 -0
  67. package/dist/info/account.js +198 -0
  68. package/dist/info/all-markets.d.ts +3 -0
  69. package/dist/info/all-markets.d.ts.map +1 -0
  70. package/dist/info/all-markets.js +272 -0
  71. package/dist/info/candles.d.ts +3 -0
  72. package/dist/info/candles.d.ts.map +1 -0
  73. package/dist/info/candles.js +120 -0
  74. package/dist/info/fees.d.ts +3 -0
  75. package/dist/info/fees.d.ts.map +1 -0
  76. package/dist/info/fees.js +87 -0
  77. package/dist/info/fills.d.ts +3 -0
  78. package/dist/info/fills.d.ts.map +1 -0
  79. package/dist/info/fills.js +105 -0
  80. package/dist/info/funding-history.d.ts +3 -0
  81. package/dist/info/funding-history.d.ts.map +1 -0
  82. package/dist/info/funding-history.js +98 -0
  83. package/dist/info/funding-scan.d.ts +3 -0
  84. package/dist/info/funding-scan.d.ts.map +1 -0
  85. package/dist/info/funding-scan.js +178 -0
  86. package/dist/info/funding.d.ts +3 -0
  87. package/dist/info/funding.d.ts.map +1 -0
  88. package/dist/info/funding.js +158 -0
  89. package/dist/info/markets.d.ts +3 -0
  90. package/dist/info/markets.d.ts.map +1 -0
  91. package/dist/info/markets.js +178 -0
  92. package/dist/info/order-status.d.ts +3 -0
  93. package/dist/info/order-status.d.ts.map +1 -0
  94. package/dist/info/order-status.js +85 -0
  95. package/dist/info/orders.d.ts +3 -0
  96. package/dist/info/orders.d.ts.map +1 -0
  97. package/dist/info/orders.js +162 -0
  98. package/dist/info/outcomes.d.ts +3 -0
  99. package/dist/info/outcomes.d.ts.map +1 -0
  100. package/dist/info/outcomes.js +175 -0
  101. package/dist/info/positions.d.ts +3 -0
  102. package/dist/info/positions.d.ts.map +1 -0
  103. package/dist/info/positions.js +127 -0
  104. package/dist/info/rate-limit.d.ts +3 -0
  105. package/dist/info/rate-limit.d.ts.map +1 -0
  106. package/dist/info/rate-limit.js +58 -0
  107. package/dist/info/search-markets.d.ts +3 -0
  108. package/dist/info/search-markets.d.ts.map +1 -0
  109. package/dist/info/search-markets.js +296 -0
  110. package/dist/info/spot.d.ts +3 -0
  111. package/dist/info/spot.d.ts.map +1 -0
  112. package/dist/info/spot.js +192 -0
  113. package/dist/info/trades.d.ts +3 -0
  114. package/dist/info/trades.d.ts.map +1 -0
  115. package/dist/info/trades.js +97 -0
  116. package/dist/lib.d.ts +14 -0
  117. package/dist/lib.d.ts.map +1 -0
  118. package/dist/lib.js +17 -0
  119. package/dist/operations/bracket.d.ts +28 -0
  120. package/dist/operations/bracket.d.ts.map +1 -0
  121. package/dist/operations/bracket.js +266 -0
  122. package/dist/operations/cancel.d.ts +3 -0
  123. package/dist/operations/cancel.d.ts.map +1 -0
  124. package/dist/operations/cancel.js +107 -0
  125. package/dist/operations/chase.d.ts +25 -0
  126. package/dist/operations/chase.d.ts.map +1 -0
  127. package/dist/operations/chase.js +215 -0
  128. package/dist/operations/limit-order.d.ts +3 -0
  129. package/dist/operations/limit-order.d.ts.map +1 -0
  130. package/dist/operations/limit-order.js +144 -0
  131. package/dist/operations/market-order.d.ts +3 -0
  132. package/dist/operations/market-order.d.ts.map +1 -0
  133. package/dist/operations/market-order.js +153 -0
  134. package/dist/operations/outcome-order.d.ts +3 -0
  135. package/dist/operations/outcome-order.d.ts.map +1 -0
  136. package/dist/operations/outcome-order.js +171 -0
  137. package/dist/operations/scale.d.ts +3 -0
  138. package/dist/operations/scale.d.ts.map +1 -0
  139. package/dist/operations/scale.js +212 -0
  140. package/dist/operations/set-tpsl.d.ts +3 -0
  141. package/dist/operations/set-tpsl.d.ts.map +1 -0
  142. package/dist/operations/set-tpsl.js +277 -0
  143. package/dist/operations/spot-order.d.ts +3 -0
  144. package/dist/operations/spot-order.d.ts.map +1 -0
  145. package/dist/operations/spot-order.js +173 -0
  146. package/dist/operations/trigger-order.d.ts +3 -0
  147. package/dist/operations/trigger-order.d.ts.map +1 -0
  148. package/dist/operations/trigger-order.js +177 -0
  149. package/dist/operations/twap-cancel.d.ts +3 -0
  150. package/dist/operations/twap-cancel.d.ts.map +1 -0
  151. package/dist/operations/twap-cancel.js +57 -0
  152. package/dist/operations/twap-status.d.ts +3 -0
  153. package/dist/operations/twap-status.d.ts.map +1 -0
  154. package/dist/operations/twap-status.js +81 -0
  155. package/dist/operations/twap.d.ts +3 -0
  156. package/dist/operations/twap.d.ts.map +1 -0
  157. package/dist/operations/twap.js +124 -0
  158. package/dist/setup/approve-builder.d.ts +3 -0
  159. package/dist/setup/approve-builder.d.ts.map +1 -0
  160. package/dist/setup/approve-builder.js +155 -0
  161. package/dist/setup/env.d.ts +4 -0
  162. package/dist/setup/env.d.ts.map +1 -0
  163. package/dist/setup/env.js +8 -0
  164. package/dist/setup/onboard.d.ts +10 -0
  165. package/dist/setup/onboard.d.ts.map +1 -0
  166. package/dist/setup/onboard.js +462 -0
  167. package/package.json +10 -4
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Scale In/Out - Place a grid of limit orders
3
+ import { getClient } from '../core/client.js';
4
+ import { formatUsd, parseArgs, sleep } from '../core/utils.js';
5
+ function printUsage() {
6
+ console.log(`
7
+ Open Broker - Scale In/Out
8
+ ==========================
9
+
10
+ Place a grid of limit orders to scale into or out of a position.
11
+ Orders are distributed across price levels based on the specified range and distribution.
12
+
13
+ Usage:
14
+ npx tsx scripts/operations/scale.ts --coin <COIN> --side <buy|sell> --size <SIZE> --levels <N> --range <PCT>
15
+
16
+ Options:
17
+ --coin Asset to trade (e.g., ETH, BTC)
18
+ --side Order side: buy or sell
19
+ --size Total order size in base asset
20
+ --levels Number of price levels (orders)
21
+ --range Price range from current mid (e.g., 2 for ±2%)
22
+ --distribution Size distribution: linear, exponential, or flat (default: linear)
23
+ - linear: more size at better prices
24
+ - exponential: much more size at better prices
25
+ - flat: equal size at all levels
26
+ --leverage Set leverage (e.g., 10 for 10x). Cross for main perps, isolated for HIP-3
27
+ --reduce Reduce-only orders (for scaling out of position)
28
+ --tif Time in force: GTC, ALO (default: GTC)
29
+ --dry Dry run - show order plan without executing
30
+
31
+ Examples:
32
+ # Scale into 1 ETH with 5 buy orders, 2% below current price
33
+ npx tsx scripts/operations/scale.ts --coin ETH --side buy --size 1 --levels 5 --range 2
34
+
35
+ # Scale out of 0.5 BTC with 4 sell orders, 3% above current price (reduce-only)
36
+ npx tsx scripts/operations/scale.ts --coin BTC --side sell --size 0.5 --levels 4 --range 3 --reduce
37
+
38
+ # Use exponential distribution for more aggressive scaling
39
+ npx tsx scripts/operations/scale.ts --coin ETH --side buy --size 2 --levels 8 --range 5 --distribution exponential
40
+ `);
41
+ }
42
+ function calculateLevels(midPrice, isBuy, totalSize, numLevels, rangePct, distribution) {
43
+ const levels = [];
44
+ // Calculate weights based on distribution
45
+ let weights = [];
46
+ for (let i = 0; i < numLevels; i++) {
47
+ switch (distribution) {
48
+ case 'flat':
49
+ weights.push(1);
50
+ break;
51
+ case 'linear':
52
+ weights.push(i + 1); // 1, 2, 3, 4, 5... (more at worse prices = better for buyer)
53
+ break;
54
+ case 'exponential':
55
+ weights.push(Math.pow(2, i)); // 1, 2, 4, 8, 16...
56
+ break;
57
+ }
58
+ }
59
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
60
+ // Calculate price levels
61
+ for (let i = 0; i < numLevels; i++) {
62
+ // Distance increases with level (i=0 is closest to mid)
63
+ const distancePct = ((i + 1) / numLevels) * rangePct;
64
+ // Buy orders go below mid, sell orders go above
65
+ const price = isBuy
66
+ ? midPrice * (1 - distancePct / 100)
67
+ : midPrice * (1 + distancePct / 100);
68
+ const size = (weights[i] / totalWeight) * totalSize;
69
+ levels.push({
70
+ level: i + 1,
71
+ price,
72
+ size,
73
+ distanceFromMid: distancePct,
74
+ });
75
+ }
76
+ return levels;
77
+ }
78
+ async function main() {
79
+ const args = parseArgs(process.argv.slice(2));
80
+ const coin = args.coin;
81
+ const side = args.side;
82
+ const totalSize = parseFloat(args.size);
83
+ const numLevels = parseInt(args.levels);
84
+ const rangePct = parseFloat(args.range);
85
+ const distribution = (args.distribution || 'linear');
86
+ const leverage = args.leverage ? parseInt(args.leverage) : undefined;
87
+ const reduceOnly = args.reduce;
88
+ const tifArg = (args.tif?.toUpperCase() || 'GTC');
89
+ const dryRun = args.dry;
90
+ if (!coin || !side || isNaN(totalSize) || isNaN(numLevels) || isNaN(rangePct)) {
91
+ printUsage();
92
+ process.exit(1);
93
+ }
94
+ if (side !== 'buy' && side !== 'sell') {
95
+ console.error('Error: --side must be "buy" or "sell"');
96
+ process.exit(1);
97
+ }
98
+ if (!['linear', 'exponential', 'flat'].includes(distribution)) {
99
+ console.error('Error: --distribution must be linear, exponential, or flat');
100
+ process.exit(1);
101
+ }
102
+ // Map uppercase CLI input to Pascal case for SDK
103
+ const tifMap = {
104
+ 'GTC': 'Gtc',
105
+ 'ALO': 'Alo'
106
+ };
107
+ const tif = tifMap[tifArg];
108
+ if (!tif) {
109
+ console.error('Error: --tif must be GTC or ALO');
110
+ process.exit(1);
111
+ }
112
+ const isBuy = side === 'buy';
113
+ const client = getClient();
114
+ if (args.verbose) {
115
+ client.verbose = true;
116
+ }
117
+ console.log('Open Broker - Scale In/Out');
118
+ console.log('==========================\n');
119
+ try {
120
+ const mids = await client.getAllMids();
121
+ const midPrice = parseFloat(mids[coin]);
122
+ if (!midPrice) {
123
+ console.error(`Error: No market data for ${coin}`);
124
+ process.exit(1);
125
+ }
126
+ const levels = calculateLevels(midPrice, isBuy, totalSize, numLevels, rangePct, distribution);
127
+ const notional = levels.reduce((sum, l) => sum + l.price * l.size, 0);
128
+ const avgPrice = notional / totalSize;
129
+ console.log('Order Plan');
130
+ console.log('----------');
131
+ console.log(`Coin: ${coin}`);
132
+ console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
133
+ console.log(`Total Size: ${totalSize}`);
134
+ console.log(`Current Mid: ${formatUsd(midPrice)}`);
135
+ console.log(`Levels: ${numLevels}`);
136
+ console.log(`Range: ${rangePct}% ${isBuy ? 'below' : 'above'} mid`);
137
+ console.log(`Distribution: ${distribution}`);
138
+ console.log(`Time in Force: ${tif}`);
139
+ console.log(`Reduce Only: ${reduceOnly ? 'Yes' : 'No'}`);
140
+ console.log(`Est. Notional: ${formatUsd(notional)}`);
141
+ console.log(`Avg Price: ${formatUsd(avgPrice)}`);
142
+ console.log('\nOrder Grid');
143
+ console.log('----------');
144
+ console.log('Level | Price | Size | Distance');
145
+ console.log('------|--------------|------------|----------');
146
+ for (const level of levels) {
147
+ console.log(` ${level.level.toString().padStart(2)} | ` +
148
+ `${formatUsd(level.price).padStart(12)} | ` +
149
+ `${level.size.toFixed(6).padStart(10)} | ` +
150
+ `${level.distanceFromMid.toFixed(2)}%`);
151
+ }
152
+ if (dryRun) {
153
+ console.log('\nšŸ” Dry run - orders not placed');
154
+ return;
155
+ }
156
+ console.log('\nPlacing orders...\n');
157
+ const results = [];
158
+ for (const level of levels) {
159
+ process.stdout.write(`Level ${level.level}: ${formatUsd(level.price)} x ${level.size.toFixed(6)}... `);
160
+ try {
161
+ const response = await client.limitOrder(coin, isBuy, level.size, level.price, tif, reduceOnly, leverage);
162
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
163
+ const status = response.response.data.statuses[0];
164
+ if (status?.resting) {
165
+ console.log(`āœ… OID: ${status.resting.oid}`);
166
+ results.push({ level: level.level, oid: status.resting.oid });
167
+ }
168
+ else if (status?.filled) {
169
+ console.log(`āœ… Filled immediately @ ${formatUsd(parseFloat(status.filled.avgPx))}`);
170
+ results.push({ level: level.level, oid: status.filled.oid });
171
+ }
172
+ else if (status?.error) {
173
+ console.log(`āŒ ${status.error}`);
174
+ results.push({ level: level.level, error: status.error });
175
+ }
176
+ else {
177
+ console.log(`āš ļø Unknown status`);
178
+ results.push({ level: level.level, error: 'Unknown status' });
179
+ }
180
+ }
181
+ else {
182
+ const error = typeof response.response === 'string' ? response.response : 'Failed';
183
+ console.log(`āŒ ${error}`);
184
+ results.push({ level: level.level, error });
185
+ }
186
+ }
187
+ catch (err) {
188
+ const error = err instanceof Error ? err.message : String(err);
189
+ console.log(`āŒ ${error}`);
190
+ results.push({ level: level.level, error });
191
+ }
192
+ // Small delay between orders
193
+ await sleep(100);
194
+ }
195
+ // Summary
196
+ const successful = results.filter(r => r.oid).length;
197
+ const failed = results.filter(r => r.error).length;
198
+ console.log('\n========== Summary ==========');
199
+ console.log(`Orders Placed: ${successful}/${numLevels}`);
200
+ if (failed > 0) {
201
+ console.log(`Failed: ${failed}`);
202
+ }
203
+ if (successful > 0) {
204
+ console.log(`Order IDs: ${results.filter(r => r.oid).map(r => r.oid).join(', ')}`);
205
+ }
206
+ }
207
+ catch (error) {
208
+ console.error('Error:', error);
209
+ process.exit(1);
210
+ }
211
+ }
212
+ main();
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env npx tsx
2
+ export {};
3
+ //# sourceMappingURL=set-tpsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"set-tpsl.d.ts","sourceRoot":"","sources":["../../scripts/operations/set-tpsl.ts"],"names":[],"mappings":""}
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Set Take Profit and/or Stop Loss on an existing position
3
+ import { getClient } from '../core/client.js';
4
+ import { formatUsd, parseArgs, sleep } from '../core/utils.js';
5
+ function printUsage() {
6
+ console.log(`
7
+ Open Broker - Set TP/SL
8
+ =======================
9
+
10
+ Add take profit and/or stop loss orders to an existing position.
11
+ Uses trigger orders that execute when price reaches the target.
12
+
13
+ Usage:
14
+ npx tsx scripts/operations/set-tpsl.ts --coin <COIN> [--tp <PRICE>] [--sl <PRICE>]
15
+
16
+ Options:
17
+ --coin Asset with open position (e.g., ETH, BTC, HYPE)
18
+ --tp Take profit trigger price
19
+ --sl Stop loss trigger price
20
+ --size Size to protect (default: full position size)
21
+ --sl-slippage Stop loss slippage in bps (default: 100 = 1%)
22
+ --dry Dry run - show orders without placing
23
+ --verbose Show debug output
24
+
25
+ Price Formats:
26
+ --tp 40 Absolute price ($40)
27
+ --tp +10% Percentage above entry price
28
+ --sl -5% Percentage below entry price (for longs)
29
+ --sl entry Stop loss at entry price (breakeven)
30
+
31
+ Examples:
32
+ # Set TP at $40 and SL at $30 on HYPE long
33
+ npx tsx scripts/operations/set-tpsl.ts --coin HYPE --tp 40 --sl 30
34
+
35
+ # Set TP at +10% from entry, SL at entry (breakeven)
36
+ npx tsx scripts/operations/set-tpsl.ts --coin HYPE --tp +10% --sl entry
37
+
38
+ # Set only stop loss at -5% from entry
39
+ npx tsx scripts/operations/set-tpsl.ts --coin ETH --sl -5%
40
+
41
+ # Set TP/SL on partial position
42
+ npx tsx scripts/operations/set-tpsl.ts --coin ETH --tp 4000 --sl 3500 --size 0.5
43
+
44
+ How Trigger Orders Work:
45
+ - TP/SL are trigger orders, NOT regular limit orders
46
+ - They sit dormant until price reaches the trigger level
47
+ - Once triggered, they execute as limit orders
48
+ - These are reduce-only orders (close position, don't reverse)
49
+ - SL has slippage buffer to ensure fill in fast markets
50
+ `);
51
+ }
52
+ function parsePrice(input, entryPrice, isLong) {
53
+ if (!input)
54
+ return null;
55
+ // Handle "entry" keyword for breakeven
56
+ if (input.toLowerCase() === 'entry') {
57
+ return entryPrice;
58
+ }
59
+ // Handle percentage format: +10%, -5%
60
+ const pctMatch = input.match(/^([+-]?)(\d+(?:\.\d+)?)%$/);
61
+ if (pctMatch) {
62
+ const sign = pctMatch[1] || '+';
63
+ const pct = parseFloat(pctMatch[2]) / 100;
64
+ if (sign === '+') {
65
+ return entryPrice * (1 + pct);
66
+ }
67
+ else {
68
+ return entryPrice * (1 - pct);
69
+ }
70
+ }
71
+ // Handle absolute price
72
+ const price = parseFloat(input);
73
+ if (!isNaN(price) && price > 0) {
74
+ return price;
75
+ }
76
+ return null;
77
+ }
78
+ async function main() {
79
+ const args = parseArgs(process.argv.slice(2));
80
+ if (args.help) {
81
+ printUsage();
82
+ process.exit(0);
83
+ }
84
+ const coin = args.coin;
85
+ const tpInput = args.tp;
86
+ const slInput = args.sl;
87
+ const sizeOverride = args.size ? parseFloat(args.size) : undefined;
88
+ const slSlippage = args['sl-slippage'] ? parseInt(args['sl-slippage']) : 100;
89
+ const dryRun = args.dry;
90
+ if (!coin) {
91
+ printUsage();
92
+ process.exit(1);
93
+ }
94
+ if (!tpInput && !slInput) {
95
+ console.error('Error: Must specify at least --tp or --sl');
96
+ process.exit(1);
97
+ }
98
+ const client = getClient();
99
+ if (args.verbose) {
100
+ client.verbose = true;
101
+ }
102
+ console.log('Open Broker - Set TP/SL');
103
+ console.log('=======================\n');
104
+ try {
105
+ // Get current position
106
+ const userState = await client.getUserState();
107
+ const position = userState.assetPositions.find(p => p.position.coin === coin);
108
+ if (!position) {
109
+ console.error(`Error: No open position for ${coin}`);
110
+ console.log('\nYour positions:');
111
+ for (const pos of userState.assetPositions) {
112
+ const size = parseFloat(pos.position.szi);
113
+ if (Math.abs(size) > 0) {
114
+ console.log(` ${pos.position.coin}: ${size > 0 ? 'LONG' : 'SHORT'} ${Math.abs(size)}`);
115
+ }
116
+ }
117
+ process.exit(1);
118
+ }
119
+ const posSize = parseFloat(position.position.szi);
120
+ const entryPrice = parseFloat(position.position.entryPx);
121
+ const isLong = posSize > 0;
122
+ const absSize = Math.abs(posSize);
123
+ const size = sizeOverride ?? absSize;
124
+ // Get current price
125
+ const mids = await client.getAllMids();
126
+ const currentPrice = parseFloat(mids[coin]);
127
+ // Parse TP and SL prices
128
+ const tpPrice = tpInput ? parsePrice(tpInput, entryPrice, isLong) : null;
129
+ const slPrice = slInput ? parsePrice(slInput, entryPrice, isLong) : null;
130
+ if (tpInput && tpPrice === null) {
131
+ console.error(`Error: Invalid TP price format: ${tpInput}`);
132
+ console.log('Use absolute price (e.g., 40), percentage (e.g., +10%), or "entry"');
133
+ process.exit(1);
134
+ }
135
+ if (slInput && slPrice === null) {
136
+ console.error(`Error: Invalid SL price format: ${slInput}`);
137
+ console.log('Use absolute price (e.g., 35), percentage (e.g., -5%), or "entry"');
138
+ process.exit(1);
139
+ }
140
+ // Validate TP/SL make sense for position direction
141
+ if (isLong) {
142
+ if (tpPrice && tpPrice <= currentPrice) {
143
+ console.warn(`āš ļø Warning: TP (${formatUsd(tpPrice)}) is at or below current price (${formatUsd(currentPrice)})`);
144
+ console.warn(' For LONG positions, TP should be above current price');
145
+ }
146
+ if (slPrice && slPrice >= currentPrice) {
147
+ console.warn(`āš ļø Warning: SL (${formatUsd(slPrice)}) is at or above current price (${formatUsd(currentPrice)})`);
148
+ console.warn(' For LONG positions, SL should be below current price');
149
+ }
150
+ }
151
+ else {
152
+ if (tpPrice && tpPrice >= currentPrice) {
153
+ console.warn(`āš ļø Warning: TP (${formatUsd(tpPrice)}) is at or above current price (${formatUsd(currentPrice)})`);
154
+ console.warn(' For SHORT positions, TP should be below current price');
155
+ }
156
+ if (slPrice && slPrice <= currentPrice) {
157
+ console.warn(`āš ļø Warning: SL (${formatUsd(slPrice)}) is at or below current price (${formatUsd(currentPrice)})`);
158
+ console.warn(' For SHORT positions, SL should be above current price');
159
+ }
160
+ }
161
+ // Calculate risk/reward
162
+ let tpDistance = 0, slDistance = 0, riskReward = 0;
163
+ if (tpPrice) {
164
+ tpDistance = isLong
165
+ ? (tpPrice - entryPrice) / entryPrice * 100
166
+ : (entryPrice - tpPrice) / entryPrice * 100;
167
+ }
168
+ if (slPrice) {
169
+ slDistance = isLong
170
+ ? (entryPrice - slPrice) / entryPrice * 100
171
+ : (slPrice - entryPrice) / entryPrice * 100;
172
+ }
173
+ if (tpDistance > 0 && slDistance > 0) {
174
+ riskReward = tpDistance / slDistance;
175
+ }
176
+ console.log('Current Position');
177
+ console.log('----------------');
178
+ console.log(`Coin: ${coin}`);
179
+ console.log(`Direction: ${isLong ? 'LONG' : 'SHORT'}`);
180
+ console.log(`Size: ${absSize}`);
181
+ console.log(`Entry Price: ${formatUsd(entryPrice)}`);
182
+ console.log(`Current Price: ${formatUsd(currentPrice)}`);
183
+ console.log(`Unrealized: ${formatUsd(parseFloat(position.position.unrealizedPnl))}`);
184
+ console.log('\nOrders to Place');
185
+ console.log('---------------');
186
+ if (tpPrice) {
187
+ const tpSide = isLong ? 'SELL' : 'BUY';
188
+ console.log(`Take Profit: ${tpSide} ${size} @ ${formatUsd(tpPrice)} (+${tpDistance.toFixed(2)}% from entry)`);
189
+ }
190
+ if (slPrice) {
191
+ const slSide = isLong ? 'SELL' : 'BUY';
192
+ const slLimitPrice = isLong
193
+ ? slPrice * (1 - slSlippage / 10000)
194
+ : slPrice * (1 + slSlippage / 10000);
195
+ console.log(`Stop Loss: ${slSide} ${size} @ ${formatUsd(slPrice)} trigger, ${formatUsd(slLimitPrice)} limit (-${slDistance.toFixed(2)}%)`);
196
+ }
197
+ if (riskReward > 0) {
198
+ console.log(`Risk/Reward: 1:${riskReward.toFixed(2)}`);
199
+ }
200
+ // Potential outcomes
201
+ const potentialProfit = tpPrice ? Math.abs(tpPrice - entryPrice) * size : 0;
202
+ const potentialLoss = slPrice ? Math.abs(entryPrice - slPrice) * size : 0;
203
+ console.log('\nPotential Outcomes');
204
+ console.log('------------------');
205
+ if (tpPrice)
206
+ console.log(`If TP hits: +${formatUsd(potentialProfit)}`);
207
+ if (slPrice)
208
+ console.log(`If SL hits: -${formatUsd(potentialLoss)}`);
209
+ if (dryRun) {
210
+ console.log('\nšŸ” Dry run - orders not placed');
211
+ return;
212
+ }
213
+ console.log('\nPlacing trigger orders...\n');
214
+ // Place Take Profit
215
+ let tpOid = null;
216
+ if (tpPrice) {
217
+ const tpSide = !isLong; // Opposite of position direction
218
+ const response = await client.takeProfit(coin, tpSide, size, tpPrice);
219
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
220
+ const status = response.response.data.statuses[0];
221
+ if (status?.resting) {
222
+ tpOid = status.resting.oid;
223
+ console.log(`āœ… Take Profit placed @ ${formatUsd(tpPrice)} (OID: ${tpOid})`);
224
+ }
225
+ else if (status?.error) {
226
+ console.log(`āŒ TP failed: ${status.error}`);
227
+ }
228
+ else {
229
+ console.log(`āš ļø TP status:`, JSON.stringify(status));
230
+ }
231
+ }
232
+ else {
233
+ console.log(`āŒ TP failed: ${typeof response.response === 'string' ? response.response : 'Unknown error'}`);
234
+ }
235
+ await sleep(200);
236
+ }
237
+ // Place Stop Loss
238
+ let slOid = null;
239
+ if (slPrice) {
240
+ const slSide = !isLong; // Opposite of position direction
241
+ const response = await client.stopLoss(coin, slSide, size, slPrice, slSlippage);
242
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
243
+ const status = response.response.data.statuses[0];
244
+ if (status?.resting) {
245
+ slOid = status.resting.oid;
246
+ console.log(`āœ… Stop Loss placed @ ${formatUsd(slPrice)} (OID: ${slOid})`);
247
+ }
248
+ else if (status?.error) {
249
+ console.log(`āŒ SL failed: ${status.error}`);
250
+ }
251
+ else {
252
+ console.log(`āš ļø SL status:`, JSON.stringify(status));
253
+ }
254
+ }
255
+ else {
256
+ console.log(`āŒ SL failed: ${typeof response.response === 'string' ? response.response : 'Unknown error'}`);
257
+ }
258
+ }
259
+ // Summary
260
+ console.log('\n========== Summary ==========');
261
+ console.log(`Position: ${isLong ? 'LONG' : 'SHORT'} ${absSize} ${coin}`);
262
+ console.log(`Entry: ${formatUsd(entryPrice)}`);
263
+ if (tpOid)
264
+ console.log(`Take Profit: ${formatUsd(tpPrice)} (OID: ${tpOid})`);
265
+ if (slOid)
266
+ console.log(`Stop Loss: ${formatUsd(slPrice)} (OID: ${slOid})`);
267
+ if (tpOid && slOid) {
268
+ console.log(`\nšŸ’” Tip: When one order fills, cancel the other manually:`);
269
+ console.log(` npx tsx scripts/operations/cancel.ts --coin ${coin} --oid <OID>`);
270
+ }
271
+ }
272
+ catch (error) {
273
+ console.error('Error:', error);
274
+ process.exit(1);
275
+ }
276
+ }
277
+ main();
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env npx tsx
2
+ export {};
3
+ //# sourceMappingURL=spot-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spot-order.d.ts","sourceRoot":"","sources":["../../scripts/operations/spot-order.ts"],"names":[],"mappings":""}
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env npx tsx
2
+ // Execute a spot order on Hyperliquid
3
+ import { getClient } from '../core/client.js';
4
+ import { formatUsd, parseArgs, checkBuilderFeeApproval } from '../core/utils.js';
5
+ function printUsage() {
6
+ console.log(`
7
+ Open Broker - Spot Order
8
+ ========================
9
+
10
+ Buy or sell spot tokens on Hyperliquid.
11
+
12
+ Usage:
13
+ npx tsx scripts/operations/spot-order.ts --coin <COIN> --side <buy|sell> --size <SIZE>
14
+
15
+ Options:
16
+ --coin Base token to trade (e.g., PURR, HYPE)
17
+ --side Order side: buy or sell
18
+ --size Order size in base token units
19
+ --price Limit price (omit for market order)
20
+ --tif Time-in-force for limit orders: Gtc, Ioc, Alo (default: Gtc)
21
+ --slippage Slippage tolerance in bps for market orders (default: from config, usually 50 = 0.5%)
22
+ --dry Dry run - show order details without executing
23
+ --verbose Show full API request/response for debugging
24
+
25
+ Environment:
26
+ HYPERLIQUID_PRIVATE_KEY Your wallet private key (0x...)
27
+ HYPERLIQUID_NETWORK "mainnet" or "testnet" (default: mainnet)
28
+
29
+ Examples:
30
+ npx tsx scripts/operations/spot-order.ts --coin PURR --side buy --size 1000
31
+ npx tsx scripts/operations/spot-order.ts --coin HYPE --side sell --size 50 --price 25.50
32
+ npx tsx scripts/operations/spot-order.ts --coin PURR --side buy --size 500 --dry
33
+ `);
34
+ }
35
+ async function main() {
36
+ const args = parseArgs(process.argv.slice(2));
37
+ const coin = args.coin;
38
+ const side = args.side;
39
+ const size = parseFloat(args.size);
40
+ const price = args.price ? parseFloat(args.price) : undefined;
41
+ const tif = args.tif ?? 'Gtc';
42
+ const slippage = args.slippage ? parseInt(args.slippage) : undefined;
43
+ const dryRun = args.dry;
44
+ if (!coin || !side || isNaN(size)) {
45
+ printUsage();
46
+ process.exit(1);
47
+ }
48
+ if (side !== 'buy' && side !== 'sell') {
49
+ console.error('Error: --side must be "buy" or "sell"');
50
+ process.exit(1);
51
+ }
52
+ if (size <= 0) {
53
+ console.error('Error: --size must be positive');
54
+ process.exit(1);
55
+ }
56
+ const isBuy = side === 'buy';
57
+ const client = getClient();
58
+ if (args.verbose) {
59
+ client.verbose = true;
60
+ }
61
+ console.log('Open Broker - Spot Order');
62
+ console.log('========================\n');
63
+ await checkBuilderFeeApproval(client);
64
+ try {
65
+ // Load spot metadata to get the pair index, then use allMids for accurate price
66
+ const spotMeta = await client.getSpotMeta();
67
+ const tokenMap = new Map();
68
+ for (const t of spotMeta.tokens)
69
+ tokenMap.set(t.index, t.name);
70
+ // Find the USDC-quoted pair for this coin (prefer quote token 0 = USDC)
71
+ let pairName = '';
72
+ let spotCoinKey = '';
73
+ for (const pair of spotMeta.universe) {
74
+ const baseName = tokenMap.get(pair.tokens[0]) ?? '';
75
+ if (baseName.toUpperCase() !== coin.toUpperCase())
76
+ continue;
77
+ const quoteName = tokenMap.get(pair.tokens[1]) ?? 'USDC';
78
+ // Prefer USDC pair; if already found a USDC pair, skip non-USDC pairs
79
+ if (pairName && pair.tokens[1] !== 0)
80
+ continue;
81
+ pairName = `${baseName}/${quoteName}`;
82
+ spotCoinKey = pair.name; // "@107" or "PURR/USDC"
83
+ if (pair.tokens[1] === 0)
84
+ break; // USDC pair found, stop
85
+ }
86
+ if (!spotCoinKey) {
87
+ console.error(`Error: No spot market found for ${coin}`);
88
+ console.error('Use "openbroker spot" to see available spot markets.');
89
+ process.exit(1);
90
+ }
91
+ // Use allMids for live price (spotMetaAndAssetCtxs contexts can be misaligned)
92
+ const mids = await client.getAllMids();
93
+ const midPrice = parseFloat(mids[spotCoinKey] || '0');
94
+ if (!midPrice || midPrice === 0) {
95
+ console.error(`Error: No spot price for ${coin} (${spotCoinKey})`);
96
+ process.exit(1);
97
+ }
98
+ const isMarket = price === undefined;
99
+ const slippageBps = slippage ?? 50;
100
+ const displayPrice = isMarket
101
+ ? (isBuy ? midPrice * (1 + slippageBps / 10000) : midPrice * (1 - slippageBps / 10000))
102
+ : price;
103
+ const notional = midPrice * size;
104
+ console.log('Order Details');
105
+ console.log('-------------');
106
+ console.log(`Pair: ${pairName}`);
107
+ console.log(`Side: ${isBuy ? 'BUY' : 'SELL'}`);
108
+ console.log(`Size: ${size} ${coin}`);
109
+ console.log(`Mid Price: ${formatUsd(midPrice)}`);
110
+ if (isMarket) {
111
+ console.log(`Type: Market (IOC)`);
112
+ console.log(`Limit Price: ${formatUsd(displayPrice)} (${slippageBps} bps slippage)`);
113
+ }
114
+ else {
115
+ console.log(`Type: Limit (${tif})`);
116
+ console.log(`Limit Price: ${formatUsd(price)}`);
117
+ }
118
+ console.log(`Notional: ~${formatUsd(notional)}`);
119
+ console.log(`Builder Fee: ${client.builderInfo.f / 10} bps`);
120
+ if (dryRun) {
121
+ console.log('\nšŸ” Dry run - order not submitted');
122
+ return;
123
+ }
124
+ console.log('\nExecuting...');
125
+ const response = isMarket
126
+ ? await client.spotMarketOrder(coin, isBuy, size, slippage)
127
+ : await client.spotLimitOrder(coin, isBuy, size, price, tif);
128
+ console.log('\nResult');
129
+ console.log('------');
130
+ if (args.verbose || process.env.VERBOSE) {
131
+ console.log('\nFull Response:');
132
+ console.log(JSON.stringify(response, null, 2));
133
+ }
134
+ if (response.status === 'ok' && response.response && typeof response.response === 'object') {
135
+ const statuses = response.response.data.statuses;
136
+ for (const status of statuses) {
137
+ if (status.filled) {
138
+ const fillSz = parseFloat(status.filled.totalSz);
139
+ const avgPx = parseFloat(status.filled.avgPx);
140
+ const fillNotional = fillSz * avgPx;
141
+ console.log(`āœ… Filled`);
142
+ console.log(` Order ID: ${status.filled.oid}`);
143
+ console.log(` Size: ${fillSz} ${coin}`);
144
+ console.log(` Avg Price: ${formatUsd(avgPx)}`);
145
+ console.log(` Notional: ${formatUsd(fillNotional)}`);
146
+ }
147
+ else if (status.resting) {
148
+ console.log(`ā³ Resting`);
149
+ console.log(` Order ID: ${status.resting.oid}`);
150
+ }
151
+ else if (status.error) {
152
+ console.log(`āŒ Error: ${status.error}`);
153
+ }
154
+ else {
155
+ console.log(`āš ļø Unknown status:`);
156
+ console.log(JSON.stringify(status, null, 2));
157
+ }
158
+ }
159
+ }
160
+ else if (response.status === 'err') {
161
+ console.log(`āŒ API Error: ${response.response || JSON.stringify(response)}`);
162
+ }
163
+ else {
164
+ console.log(`āŒ Unexpected response:`);
165
+ console.log(JSON.stringify(response, null, 2));
166
+ }
167
+ }
168
+ catch (error) {
169
+ console.error('Error executing spot order:', error);
170
+ process.exit(1);
171
+ }
172
+ }
173
+ main();
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env npx tsx
2
+ export {};
3
+ //# sourceMappingURL=trigger-order.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-order.d.ts","sourceRoot":"","sources":["../../scripts/operations/trigger-order.ts"],"names":[],"mappings":""}