openbroker 1.9.6 → 1.12.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 +35 -0
- package/README.md +52 -0
- package/SKILL.md +57 -5
- package/bin/cli.ts +11 -0
- package/dist/auto/examples/grid.js +1 -1
- package/dist/auto/examples/mm-maker.js +4 -4
- package/dist/auto/examples/mm-spread.js +4 -4
- package/dist/core/client.d.ts +83 -32
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +214 -29
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/utils.d.ts +29 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +27 -0
- package/dist/core/ws.d.ts +5 -0
- package/dist/core/ws.d.ts.map +1 -1
- package/dist/core/ws.js +9 -7
- package/dist/guardian/cli.d.ts +2 -0
- package/dist/guardian/cli.d.ts.map +1 -0
- package/dist/guardian/cli.js +288 -0
- package/dist/guardian/engine.d.ts +107 -0
- package/dist/guardian/engine.d.ts.map +1 -0
- package/dist/guardian/engine.js +526 -0
- package/dist/guardian/rules.d.ts +35 -0
- package/dist/guardian/rules.d.ts.map +1 -0
- package/dist/guardian/rules.js +237 -0
- package/dist/guardian/telegram.d.ts +24 -0
- package/dist/guardian/telegram.d.ts.map +1 -0
- package/dist/guardian/telegram.js +125 -0
- package/dist/guardian/types.d.ts +90 -0
- package/dist/guardian/types.d.ts.map +1 -0
- package/dist/guardian/types.js +45 -0
- package/dist/lib.d.ts +10 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +6 -1
- package/dist/operations/advanced-orders.test.js +302 -13
- package/dist/operations/bracket.d.ts +35 -5
- package/dist/operations/bracket.d.ts.map +1 -1
- package/dist/operations/bracket.js +238 -64
- package/dist/operations/cancel.js +4 -1
- package/dist/operations/chase.d.ts +4 -2
- package/dist/operations/chase.d.ts.map +1 -1
- package/dist/operations/chase.js +61 -24
- package/dist/operations/scale.d.ts.map +1 -1
- package/dist/operations/scale.js +10 -1
- package/dist/operations/set-tpsl.js +69 -57
- package/dist/operations/trigger-order.js +18 -6
- package/dist/operations/twap.js +13 -1
- package/package.json +2 -2
- package/scripts/auto/examples/grid.ts +1 -1
- package/scripts/auto/examples/mm-maker.ts +4 -4
- package/scripts/auto/examples/mm-spread.ts +4 -4
- package/scripts/core/client.ts +278 -33
- package/scripts/core/types.ts +23 -0
- package/scripts/core/utils.ts +37 -0
- package/scripts/core/ws.ts +9 -6
- package/scripts/guardian/cli.ts +322 -0
- package/scripts/guardian/engine.ts +614 -0
- package/scripts/guardian/rules.ts +265 -0
- package/scripts/guardian/telegram.ts +147 -0
- package/scripts/guardian/types.ts +160 -0
- package/scripts/lib.ts +36 -0
- package/scripts/operations/advanced-orders.test.ts +347 -14
- package/scripts/operations/bracket.ts +252 -73
- package/scripts/operations/cancel.ts +4 -1
- package/scripts/operations/chase.ts +61 -26
- package/scripts/operations/scale.ts +10 -1
- package/scripts/operations/set-tpsl.ts +62 -57
- package/scripts/operations/trigger-order.ts +20 -6
- package/scripts/operations/twap.ts +14 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Bracket Order - Entry with Take Profit and Stop Loss
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
5
|
+
import { formatUsd, parseArgs, parseOrderStatus, sleep } from '../core/utils.js';
|
|
6
6
|
import { UserFillWatcher } from './execution.js';
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -23,12 +23,24 @@ Options:
|
|
|
23
23
|
--price Entry price (required if --entry limit)
|
|
24
24
|
--tp Take profit distance in % from entry
|
|
25
25
|
--sl Stop loss distance in % from entry
|
|
26
|
+
--tp-price Take profit at an absolute price (instead of --tp)
|
|
27
|
+
--sl-price Stop loss at an absolute price (instead of --sl)
|
|
26
28
|
--slippage Slippage for market entry in bps (default: 50)
|
|
27
|
-
--entry-timeout Seconds to wait for limit entry fill before returning
|
|
28
|
-
|
|
29
|
+
--entry-timeout Seconds to wait for limit entry fill before returning
|
|
30
|
+
(default: 300; only used with --no-atomic)
|
|
31
|
+
--sl-slippage Stop-loss fill cap past the trigger in bps (default: 100)
|
|
32
|
+
--sl-limit Place the SL as a stop-limit instead of a market trigger.
|
|
33
|
+
Warning: a gap move past the limit band can skip the stop
|
|
34
|
+
entirely and leave the position unprotected.
|
|
35
|
+
--no-atomic For limit entries: place the entry alone and arm TP/SL only
|
|
36
|
+
after a confirmed fill, instead of the default atomic batch
|
|
37
|
+
where the exchange arms TP/SL on fill server-side.
|
|
29
38
|
--leverage Set leverage (e.g., 10 for 10x). Cross for main perps, isolated for HIP-3
|
|
30
39
|
--dry Dry run - show bracket plan without executing
|
|
31
40
|
|
|
41
|
+
At least one of --tp/--tp-price/--sl/--sl-price is required; one-sided
|
|
42
|
+
brackets (TP-only or SL-only) are supported.
|
|
43
|
+
|
|
32
44
|
Take Profit / Stop Loss:
|
|
33
45
|
For LONG (buy): TP is above entry, SL is below entry
|
|
34
46
|
For SHORT (sell): TP is below entry, SL is above entry
|
|
@@ -37,9 +49,12 @@ Examples:
|
|
|
37
49
|
# Long ETH with 3% take profit and 1.5% stop loss
|
|
38
50
|
npx tsx scripts/operations/bracket.ts --coin ETH --side buy --size 0.5 --tp 3 --sl 1.5
|
|
39
51
|
|
|
40
|
-
# Short BTC with limit entry at $100k, 5% TP, 2% SL
|
|
52
|
+
# Short BTC with limit entry at $100k, 5% TP, 2% SL (armed atomically on fill)
|
|
41
53
|
npx tsx scripts/operations/bracket.ts --coin BTC --side sell --size 0.1 --entry limit --price 100000 --tp 5 --sl 2
|
|
42
54
|
|
|
55
|
+
# Long with absolute targets, SL only
|
|
56
|
+
npx tsx scripts/operations/bracket.ts --coin SOL --side buy --size 10 --sl-price 120
|
|
57
|
+
|
|
43
58
|
# Preview bracket setup
|
|
44
59
|
npx tsx scripts/operations/bracket.ts --coin SOL --side buy --size 10 --tp 5 --sl 2 --dry
|
|
45
60
|
`);
|
|
@@ -47,14 +62,52 @@ Examples:
|
|
|
47
62
|
export async function runBracket(opts) {
|
|
48
63
|
const out = opts.output ?? ((line) => console.log(line));
|
|
49
64
|
const entryType = opts.entryType ?? 'market';
|
|
65
|
+
const slMarket = opts.slMarket !== false;
|
|
66
|
+
const atomic = opts.atomic !== false;
|
|
50
67
|
const isLong = opts.side === 'buy';
|
|
68
|
+
const hasTp = (opts.tpPct ?? 0) > 0 || (opts.tpPrice ?? 0) > 0;
|
|
69
|
+
const hasSl = (opts.slPct ?? 0) > 0 || (opts.slPrice ?? 0) > 0;
|
|
51
70
|
if (opts.size <= 0 || isNaN(opts.size))
|
|
52
71
|
throw new Error('size must be positive');
|
|
53
|
-
if (
|
|
54
|
-
throw new Error('
|
|
72
|
+
if (!hasTp && !hasSl)
|
|
73
|
+
throw new Error('provide a TP target, an SL target, or both (tpPct/tpPrice/slPct/slPrice)');
|
|
55
74
|
if (entryType === 'limit' && opts.entryPrice === undefined) {
|
|
56
75
|
throw new Error('entryPrice is required for limit entry');
|
|
57
76
|
}
|
|
77
|
+
// Resolve percent targets off the reference entry and validate every provided leg.
|
|
78
|
+
const resolveTargets = (entryPrice) => {
|
|
79
|
+
const tpPrice = !hasTp
|
|
80
|
+
? null
|
|
81
|
+
: (opts.tpPrice ?? 0) > 0
|
|
82
|
+
? opts.tpPrice
|
|
83
|
+
: isLong
|
|
84
|
+
? entryPrice * (1 + opts.tpPct / 100)
|
|
85
|
+
: entryPrice * (1 - opts.tpPct / 100);
|
|
86
|
+
const slPrice = !hasSl
|
|
87
|
+
? null
|
|
88
|
+
: (opts.slPrice ?? 0) > 0
|
|
89
|
+
? opts.slPrice
|
|
90
|
+
: isLong
|
|
91
|
+
? entryPrice * (1 - opts.slPct / 100)
|
|
92
|
+
: entryPrice * (1 + opts.slPct / 100);
|
|
93
|
+
if (tpPrice !== null && tpPrice <= 0)
|
|
94
|
+
throw new Error('TP must resolve to a positive price (keep TP% under 100 on shorts)');
|
|
95
|
+
if (slPrice !== null && slPrice <= 0)
|
|
96
|
+
throw new Error('SL must resolve to a positive price (keep SL% under 100)');
|
|
97
|
+
if (isLong) {
|
|
98
|
+
if (tpPrice !== null && tpPrice <= entryPrice)
|
|
99
|
+
throw new Error('for a long, TP price must be above entry');
|
|
100
|
+
if (slPrice !== null && slPrice >= entryPrice)
|
|
101
|
+
throw new Error('for a long, SL price must be below entry');
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if (tpPrice !== null && tpPrice >= entryPrice)
|
|
105
|
+
throw new Error('for a short, TP price must be below entry');
|
|
106
|
+
if (slPrice !== null && slPrice <= entryPrice)
|
|
107
|
+
throw new Error('for a short, SL price must be above entry');
|
|
108
|
+
}
|
|
109
|
+
return { tpPrice, slPrice };
|
|
110
|
+
};
|
|
58
111
|
const client = opts.client ?? getClient();
|
|
59
112
|
if (opts.verbose)
|
|
60
113
|
client.verbose = true;
|
|
@@ -65,38 +118,129 @@ export async function runBracket(opts) {
|
|
|
65
118
|
if (!midPrice)
|
|
66
119
|
throw new Error(`No market data for ${opts.coin}`);
|
|
67
120
|
const entry = entryType === 'limit' ? opts.entryPrice : midPrice;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const riskReward = opts.tpPct / opts.slPct;
|
|
121
|
+
// Validate targets against the pre-trade reference BEFORE the entry goes out,
|
|
122
|
+
// so a bad TP/SL never leaves an unprotected position behind.
|
|
123
|
+
let targets = resolveTargets(entry);
|
|
124
|
+
const legsLabel = hasTp && hasSl ? 'TP/SL' : hasTp ? 'TP' : 'SL';
|
|
125
|
+
const tpDistPct = targets.tpPrice !== null ? Math.abs(targets.tpPrice - entry) / entry * 100 : null;
|
|
126
|
+
const slDistPct = targets.slPrice !== null ? Math.abs(targets.slPrice - entry) / entry * 100 : null;
|
|
75
127
|
const notional = entry * opts.size;
|
|
76
128
|
out('Bracket Plan');
|
|
77
129
|
out('------------');
|
|
78
130
|
out(`Coin: ${opts.coin}`);
|
|
79
131
|
out(`Position: ${isLong ? 'LONG' : 'SHORT'}`);
|
|
80
132
|
out(`Size: ${opts.size}`);
|
|
81
|
-
out(`Entry Type: ${entryType.toUpperCase()}`);
|
|
133
|
+
out(`Entry Type: ${entryType.toUpperCase()}${entryType === 'limit' ? (atomic ? ' (atomic TP/SL)' : ' (fill-watch TP/SL)') : ''}`);
|
|
82
134
|
out(`Current Mid: ${formatUsd(midPrice)}`);
|
|
83
135
|
out(`Entry Price: ${formatUsd(entry)}${entryType === 'market' ? ' (approx)' : ''}`);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
136
|
+
if (targets.tpPrice !== null)
|
|
137
|
+
out(`Take Profit: ${formatUsd(targets.tpPrice)} (${tpDistPct.toFixed(2)}% from entry)`);
|
|
138
|
+
if (targets.slPrice !== null)
|
|
139
|
+
out(`Stop Loss: ${formatUsd(targets.slPrice)} (${slDistPct.toFixed(2)}% from entry, ${slMarket ? 'market trigger' : 'stop-limit'})`);
|
|
140
|
+
if (tpDistPct !== null && slDistPct !== null && slDistPct > 0) {
|
|
141
|
+
out(`Risk/Reward: 1:${(tpDistPct / slDistPct).toFixed(2)}`);
|
|
142
|
+
}
|
|
87
143
|
out(`Est. Notional: ${formatUsd(notional)}`);
|
|
88
|
-
const potentialProfit = notional * (opts.tpPct / 100);
|
|
89
|
-
const potentialLoss = notional * (opts.slPct / 100);
|
|
90
144
|
out('\nRisk Analysis');
|
|
91
145
|
out('-------------');
|
|
92
|
-
|
|
93
|
-
|
|
146
|
+
if (targets.tpPrice !== null)
|
|
147
|
+
out(`Potential Profit: ${formatUsd(Math.abs(targets.tpPrice - entry) * opts.size)}`);
|
|
148
|
+
if (targets.slPrice !== null)
|
|
149
|
+
out(`Potential Loss: ${formatUsd(Math.abs(entry - targets.slPrice) * opts.size)}`);
|
|
94
150
|
if (opts.dryRun) {
|
|
95
151
|
out('\n🔍 Dry run - bracket not executed');
|
|
96
|
-
return { status: 'dry', entryPrice: entry, tpPrice, slPrice };
|
|
152
|
+
return { status: 'dry', entryPrice: entry, tpPrice: targets.tpPrice ?? undefined, slPrice: targets.slPrice ?? undefined };
|
|
97
153
|
}
|
|
98
154
|
out('\nExecuting bracket...\n');
|
|
99
|
-
//
|
|
155
|
+
// ── Atomic path: limit entry + TP/SL in one normalTpsl batch ──────────
|
|
156
|
+
// The exchange arms the exits when the entry fills (children come back as
|
|
157
|
+
// "waitingForFill"), so the bracket survives this process exiting.
|
|
158
|
+
if (entryType === 'limit' && atomic) {
|
|
159
|
+
out(`Step 1: Limit entry + ${legsLabel} (atomic normalTpsl batch)`);
|
|
160
|
+
const response = await client.bracketOrder(opts.coin, isLong, opts.size, entry, {
|
|
161
|
+
takeProfitPrice: targets.tpPrice ?? undefined,
|
|
162
|
+
stopLossPrice: targets.slPrice ?? undefined,
|
|
163
|
+
stopLossSlippageBps: opts.slSlippageBps,
|
|
164
|
+
stopLossIsMarket: slMarket,
|
|
165
|
+
leverage: opts.leverage,
|
|
166
|
+
});
|
|
167
|
+
if (response.status !== 'ok' || !response.response || typeof response.response !== 'object') {
|
|
168
|
+
const reason = typeof response.response === 'string' ? response.response : 'Unknown error';
|
|
169
|
+
out(` ❌ Bracket failed: ${reason}`);
|
|
170
|
+
return { status: 'entry_failed', reason };
|
|
171
|
+
}
|
|
172
|
+
const statuses = response.response.data.statuses.map(parseOrderStatus);
|
|
173
|
+
const entryStatus = statuses[0];
|
|
174
|
+
const childStatuses = statuses.slice(1);
|
|
175
|
+
const failed = statuses.find((s) => s.kind === 'error' || s.kind === 'unknown');
|
|
176
|
+
if (failed) {
|
|
177
|
+
// Roll back anything that landed so no half-armed bracket is left behind.
|
|
178
|
+
const restingOids = statuses.flatMap((s) => (s.kind === 'resting' ? [s.oid] : []));
|
|
179
|
+
for (const oid of restingOids) {
|
|
180
|
+
try {
|
|
181
|
+
await client.cancel(opts.coin, oid);
|
|
182
|
+
}
|
|
183
|
+
catch { /* may have filled */ }
|
|
184
|
+
}
|
|
185
|
+
const reason = failed.kind === 'error' ? failed.error : `Unexpected order status: ${JSON.stringify(failed)}`;
|
|
186
|
+
out(` ❌ Bracket rejected: ${reason}`);
|
|
187
|
+
if (restingOids.length)
|
|
188
|
+
out(` Cancelled ${restingOids.length} resting order(s).`);
|
|
189
|
+
return { status: 'entry_failed', reason };
|
|
190
|
+
}
|
|
191
|
+
let entryOid = null;
|
|
192
|
+
let filledEntry = null;
|
|
193
|
+
if (entryStatus.kind === 'resting') {
|
|
194
|
+
entryOid = entryStatus.oid;
|
|
195
|
+
out(` ✅ Entry resting @ ${formatUsd(entry)} (OID: ${entryOid})`);
|
|
196
|
+
}
|
|
197
|
+
else if (entryStatus.kind === 'filled') {
|
|
198
|
+
entryOid = entryStatus.oid;
|
|
199
|
+
filledEntry = { size: entryStatus.totalSz, avgPx: entryStatus.avgPx };
|
|
200
|
+
out(` ✅ Entry filled immediately: ${entryStatus.totalSz} @ ${formatUsd(entryStatus.avgPx)}`);
|
|
201
|
+
}
|
|
202
|
+
let tpOid = null;
|
|
203
|
+
let slOid = null;
|
|
204
|
+
let childIdx = 0;
|
|
205
|
+
for (const label of [targets.tpPrice !== null ? 'TP' : null, targets.slPrice !== null ? 'SL' : null]) {
|
|
206
|
+
if (!label)
|
|
207
|
+
continue;
|
|
208
|
+
const child = childStatuses[childIdx++];
|
|
209
|
+
if (!child)
|
|
210
|
+
continue;
|
|
211
|
+
if (child.kind === 'waiting') {
|
|
212
|
+
out(` ✅ ${label} armed — activates when the entry fills (${child.state})`);
|
|
213
|
+
}
|
|
214
|
+
else if (child.kind === 'resting') {
|
|
215
|
+
if (label === 'TP')
|
|
216
|
+
tpOid = child.oid;
|
|
217
|
+
else
|
|
218
|
+
slOid = child.oid;
|
|
219
|
+
out(` ✅ ${label} trigger live (OID: ${child.oid})`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
out('\n========== Bracket Summary ==========');
|
|
223
|
+
out(`Position: ${isLong ? 'LONG' : 'SHORT'} ${opts.size} ${opts.coin}`);
|
|
224
|
+
out(`Entry: ${formatUsd(filledEntry?.avgPx ?? entry)}${filledEntry ? '' : ' (resting)'}`);
|
|
225
|
+
if (targets.tpPrice !== null)
|
|
226
|
+
out(`Take Profit: ${formatUsd(targets.tpPrice)}`);
|
|
227
|
+
if (targets.slPrice !== null)
|
|
228
|
+
out(`Stop Loss: ${formatUsd(targets.slPrice)} (${slMarket ? 'market trigger' : 'stop-limit'})`);
|
|
229
|
+
out(filledEntry
|
|
230
|
+
? `\n✅ Bracket complete! ${legsLabel} triggers are live.`
|
|
231
|
+
: `\n✅ Bracket armed! ${legsLabel} activates server-side when the entry fills.`);
|
|
232
|
+
return {
|
|
233
|
+
status: filledEntry ? 'complete' : 'armed',
|
|
234
|
+
entryPrice: filledEntry?.avgPx ?? entry,
|
|
235
|
+
tpPrice: targets.tpPrice ?? undefined,
|
|
236
|
+
slPrice: targets.slPrice ?? undefined,
|
|
237
|
+
tpOid,
|
|
238
|
+
slOid,
|
|
239
|
+
entryOid,
|
|
240
|
+
protectedSize: filledEntry?.size ?? opts.size,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// ── Fill-first path: market entry, or limit entry with --no-atomic ────
|
|
100
244
|
out('Step 1: Entry order');
|
|
101
245
|
let actualEntry = entry;
|
|
102
246
|
let entryOid = null;
|
|
@@ -185,64 +329,87 @@ export async function runBracket(opts) {
|
|
|
185
329
|
out('\n⚠️ Bracket aborted - no confirmed fill size');
|
|
186
330
|
return { status: 'entry_failed', reason: 'No confirmed fill size' };
|
|
187
331
|
}
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
332
|
+
// Re-resolve off the actual fill price; if the fill drifted past a fixed
|
|
333
|
+
// target, report it instead of leaving a half-armed bracket silently.
|
|
334
|
+
try {
|
|
335
|
+
targets = resolveTargets(actualEntry);
|
|
192
336
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
337
|
+
catch (error) {
|
|
338
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
339
|
+
out(`\n❌ Entry filled @ ${formatUsd(actualEntry)}, but ${legsLabel} could not be armed: ${reason}`);
|
|
340
|
+
out('⚠️ Position is OPEN and UNPROTECTED - set TP/SL manually (openbroker set-tpsl).');
|
|
341
|
+
return { status: 'partial', entryPrice: actualEntry, protectedSize: 0, reason };
|
|
196
342
|
}
|
|
197
343
|
await sleep(500);
|
|
198
|
-
// Step 2:
|
|
199
|
-
|
|
344
|
+
// Step 2: TP/SL triggers tied to the now-open position (positionTpsl) —
|
|
345
|
+
// OCO between themselves and cancelled by the venue if the position closes.
|
|
346
|
+
out(`\nStep 2: Position ${legsLabel} trigger orders`);
|
|
200
347
|
const exitSide = !isLong;
|
|
201
|
-
const pairResponse = await client.
|
|
348
|
+
const pairResponse = await client.tpslOrders(opts.coin, exitSide, filledSize, {
|
|
349
|
+
takeProfitPrice: targets.tpPrice ?? undefined,
|
|
350
|
+
stopLossPrice: targets.slPrice ?? undefined,
|
|
351
|
+
stopLossSlippageBps: opts.slSlippageBps,
|
|
352
|
+
stopLossIsMarket: slMarket,
|
|
353
|
+
grouping: 'positionTpsl',
|
|
354
|
+
leverage: opts.leverage,
|
|
355
|
+
});
|
|
202
356
|
let tpOid = null;
|
|
203
357
|
let slOid = null;
|
|
358
|
+
let exitErrors = 0;
|
|
204
359
|
if (pairResponse.status === 'ok' && pairResponse.response && typeof pairResponse.response === 'object') {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
360
|
+
const statuses = pairResponse.response.data.statuses.map(parseOrderStatus);
|
|
361
|
+
let idx = 0;
|
|
362
|
+
for (const leg of [targets.tpPrice !== null ? 'TP' : null, targets.slPrice !== null ? 'SL' : null]) {
|
|
363
|
+
if (!leg)
|
|
364
|
+
continue;
|
|
365
|
+
const status = statuses[idx++];
|
|
366
|
+
const price = leg === 'TP' ? targets.tpPrice : targets.slPrice;
|
|
367
|
+
if (status?.kind === 'resting') {
|
|
368
|
+
if (leg === 'TP')
|
|
369
|
+
tpOid = status.oid;
|
|
370
|
+
else
|
|
371
|
+
slOid = status.oid;
|
|
372
|
+
out(` ✅ ${leg} trigger placed @ ${formatUsd(price)} (OID: ${status.oid})`);
|
|
373
|
+
}
|
|
374
|
+
else if (status?.kind === 'waiting') {
|
|
375
|
+
out(` ✅ ${leg} trigger armed @ ${formatUsd(price)} (${status.state})`);
|
|
376
|
+
}
|
|
377
|
+
else if (status?.kind === 'error') {
|
|
378
|
+
exitErrors++;
|
|
379
|
+
out(` ❌ ${leg} failed: ${status.error}`);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
exitErrors++;
|
|
383
|
+
out(` ⚠️ ${leg} status: ${JSON.stringify(status)}`);
|
|
384
|
+
}
|
|
225
385
|
}
|
|
226
386
|
}
|
|
227
387
|
else {
|
|
388
|
+
exitErrors++;
|
|
228
389
|
const reason = typeof pairResponse.response === 'string' ? pairResponse.response : 'Unknown error';
|
|
229
|
-
out(` ❌
|
|
390
|
+
out(` ❌ ${legsLabel} orders failed: ${reason}`);
|
|
230
391
|
}
|
|
231
392
|
out('\n========== Bracket Summary ==========');
|
|
232
393
|
out(`Position: ${isLong ? 'LONG' : 'SHORT'} ${filledSize} ${opts.coin}`);
|
|
233
394
|
out(`Entry: ${formatUsd(actualEntry)}`);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
out(
|
|
395
|
+
if (targets.tpPrice !== null)
|
|
396
|
+
out(`Take Profit: ${formatUsd(targets.tpPrice)} - Trigger order`);
|
|
397
|
+
if (targets.slPrice !== null)
|
|
398
|
+
out(`Stop Loss: ${formatUsd(targets.slPrice)} (${slMarket ? 'market trigger' : 'stop-limit'}) - Trigger order`);
|
|
399
|
+
if (exitErrors === 0) {
|
|
400
|
+
out(`\n✅ Bracket complete! ${legsLabel} triggers track the position (positionTpsl).`);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
out('\n⚠️ Position is open but not fully protected - set the missing trigger manually.');
|
|
238
404
|
}
|
|
239
405
|
return {
|
|
240
|
-
status:
|
|
406
|
+
status: exitErrors === 0 ? 'complete' : 'partial',
|
|
241
407
|
entryPrice: actualEntry,
|
|
242
|
-
tpPrice,
|
|
243
|
-
slPrice,
|
|
408
|
+
tpPrice: targets.tpPrice ?? undefined,
|
|
409
|
+
slPrice: targets.slPrice ?? undefined,
|
|
244
410
|
tpOid,
|
|
245
411
|
slOid,
|
|
412
|
+
entryOid,
|
|
246
413
|
protectedSize: filledSize,
|
|
247
414
|
};
|
|
248
415
|
}
|
|
@@ -253,14 +420,17 @@ async function main() {
|
|
|
253
420
|
const size = parseFloat(args.size);
|
|
254
421
|
const entryType = (args.entry || 'market');
|
|
255
422
|
const entryPrice = args.price ? parseFloat(args.price) : undefined;
|
|
256
|
-
const tpPct = parseFloat(args.tp);
|
|
257
|
-
const slPct = parseFloat(args.sl);
|
|
423
|
+
const tpPct = args.tp ? parseFloat(args.tp) : undefined;
|
|
424
|
+
const slPct = args.sl ? parseFloat(args.sl) : undefined;
|
|
425
|
+
const tpPrice = args['tp-price'] ? parseFloat(args['tp-price']) : undefined;
|
|
426
|
+
const slPrice = args['sl-price'] ? parseFloat(args['sl-price']) : undefined;
|
|
258
427
|
const slippage = args.slippage ? parseInt(args.slippage) : undefined;
|
|
259
428
|
const entryTimeoutSec = args['entry-timeout'] ? parseInt(args['entry-timeout']) : undefined;
|
|
260
429
|
const slSlippageBps = args['sl-slippage'] ? parseInt(args['sl-slippage']) : undefined;
|
|
261
430
|
const leverage = args.leverage ? parseInt(args.leverage) : undefined;
|
|
262
431
|
const dryRun = args.dry;
|
|
263
|
-
|
|
432
|
+
const hasTarget = [tpPct, slPct, tpPrice, slPrice].some((v) => v !== undefined && !isNaN(v));
|
|
433
|
+
if (!coin || !side || isNaN(size) || !hasTarget) {
|
|
264
434
|
printUsage();
|
|
265
435
|
process.exit(1);
|
|
266
436
|
}
|
|
@@ -275,11 +445,15 @@ async function main() {
|
|
|
275
445
|
size,
|
|
276
446
|
tpPct,
|
|
277
447
|
slPct,
|
|
448
|
+
tpPrice,
|
|
449
|
+
slPrice,
|
|
278
450
|
entryType,
|
|
279
451
|
entryPrice,
|
|
280
452
|
slippage,
|
|
281
453
|
entryTimeoutSec,
|
|
282
454
|
slSlippageBps,
|
|
455
|
+
slMarket: !args['sl-limit'],
|
|
456
|
+
atomic: !args['no-atomic'],
|
|
283
457
|
leverage,
|
|
284
458
|
dryRun,
|
|
285
459
|
verbose: args.verbose,
|
|
@@ -16,6 +16,8 @@ Options:
|
|
|
16
16
|
--coin Cancel orders for specific coin only
|
|
17
17
|
--oid Cancel specific order by ID
|
|
18
18
|
--all Cancel all open orders
|
|
19
|
+
--fast Fast cancel (mempool-prioritized). Rejected by the API for
|
|
20
|
+
trigger orders (TP/SL) - use only on plain resting limits
|
|
19
21
|
--dry Dry run - show what would be cancelled
|
|
20
22
|
|
|
21
23
|
Examples:
|
|
@@ -30,6 +32,7 @@ async function main() {
|
|
|
30
32
|
const coin = args.coin;
|
|
31
33
|
const oid = args.oid ? parseInt(args.oid) : undefined;
|
|
32
34
|
const all = args.all;
|
|
35
|
+
const fast = args.fast;
|
|
33
36
|
const dryRun = args.dry;
|
|
34
37
|
// Must specify something to cancel
|
|
35
38
|
if (!coin && !oid && !all) {
|
|
@@ -82,7 +85,7 @@ async function main() {
|
|
|
82
85
|
let failCount = 0;
|
|
83
86
|
for (const order of targetOrders) {
|
|
84
87
|
try {
|
|
85
|
-
const response = await client.cancel(order.coin, order.oid);
|
|
88
|
+
const response = await client.cancel(order.coin, order.oid, { fast });
|
|
86
89
|
if (response.status === 'ok') {
|
|
87
90
|
console.log(`✅ Cancelled ${order.coin} order ${order.oid}`);
|
|
88
91
|
successCount++;
|
|
@@ -31,10 +31,12 @@ export interface ChaseClient {
|
|
|
31
31
|
oid: number;
|
|
32
32
|
}>>;
|
|
33
33
|
limitOrder(coin: string, isBuy: boolean, size: number, price: number, tif?: 'Gtc' | 'Ioc' | 'Alo', reduceOnly?: boolean, leverage?: number): Promise<OrderResponse>;
|
|
34
|
-
cancel(coin: string, oid: number
|
|
34
|
+
cancel(coin: string, oid: number, opts?: {
|
|
35
|
+
fast?: boolean;
|
|
36
|
+
}): Promise<CancelResponse>;
|
|
35
37
|
}
|
|
36
38
|
export interface ChaseResult {
|
|
37
|
-
status: 'dry' | 'filled' | 'timeout' | 'max_chase_exceeded';
|
|
39
|
+
status: 'dry' | 'filled' | 'timeout' | 'max_chase_exceeded' | 'min_notional';
|
|
38
40
|
iterations: number;
|
|
39
41
|
durationSec: number;
|
|
40
42
|
startMid: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chase.d.ts","sourceRoot":"","sources":["../../scripts/operations/chase.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjF,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAkCnE,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,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,aAAa,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACjH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACpK,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"chase.d.ts","sourceRoot":"","sources":["../../scripts/operations/chase.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEjF,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAkCnE,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,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,aAAa,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACtC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACjH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE,UAAU,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACpK,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACvF;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,oBAAoB,GAAG,cAAc,CAAC;IAC7E,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,CA6PvE"}
|
package/dist/operations/chase.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Chase Order - Follow price with limit orders until filled
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { getClient } from '../core/client.js';
|
|
5
|
-
import { formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
5
|
+
import { MIN_ORDER_NOTIONAL_USD, formatUsd, parseArgs, sleep } from '../core/utils.js';
|
|
6
6
|
import { UserFillWatcher } from './execution.js';
|
|
7
7
|
function printUsage() {
|
|
8
8
|
console.log(`
|
|
@@ -61,6 +61,10 @@ export async function runChase(opts) {
|
|
|
61
61
|
const startMid = parseFloat(mids[opts.coin]);
|
|
62
62
|
if (!startMid)
|
|
63
63
|
throw new Error(`No market data for ${opts.coin}`);
|
|
64
|
+
const startBelowMinimum = !opts.reduceOnly && opts.size * startMid < MIN_ORDER_NOTIONAL_USD;
|
|
65
|
+
const minNotionalMsg = `Chase size (~$${(opts.size * startMid).toFixed(2)}) is below the $${MIN_ORDER_NOTIONAL_USD} exchange minimum`;
|
|
66
|
+
if (startBelowMinimum && !opts.dryRun)
|
|
67
|
+
throw new Error(minNotionalMsg);
|
|
64
68
|
const maxChasePrice = isBuy
|
|
65
69
|
? startMid * (1 + maxChaseBps / 10000)
|
|
66
70
|
: startMid * (1 - maxChaseBps / 10000);
|
|
@@ -76,6 +80,8 @@ export async function runChase(opts) {
|
|
|
76
80
|
out(`Update Rate: ${intervalMs}ms`);
|
|
77
81
|
out(`Order Type: ALO (post-only)`);
|
|
78
82
|
if (opts.dryRun) {
|
|
83
|
+
if (startBelowMinimum)
|
|
84
|
+
out(`\n⚠️ ${minNotionalMsg}`);
|
|
79
85
|
out('\n🔍 Dry run - chase not started');
|
|
80
86
|
return { status: 'dry', iterations: 0, durationSec: 0, startMid, endMid: startMid };
|
|
81
87
|
}
|
|
@@ -113,10 +119,21 @@ export async function runChase(opts) {
|
|
|
113
119
|
break;
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
// A transient /info failure mid-chase must not kill the execution (and
|
|
123
|
+
// strand the resting order) — wait a tick and retry instead.
|
|
124
|
+
let currentMid = NaN;
|
|
125
|
+
try {
|
|
126
|
+
const currentMids = await client.getAllMids();
|
|
127
|
+
currentMid = parseFloat(currentMids[opts.coin]);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
/* transient failure — retry next tick */
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isFinite(currentMid) || currentMid <= 0) {
|
|
133
|
+
out(`[${iteration}] ⏳ No live price — retrying...`);
|
|
134
|
+
await sleep(intervalMs);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
120
137
|
if (isBuy && currentMid > maxChasePrice) {
|
|
121
138
|
out(`\n⚠️ Price ${formatUsd(currentMid)} exceeded max chase ${formatUsd(maxChasePrice)}`);
|
|
122
139
|
exitReason = 'max_chase_exceeded';
|
|
@@ -141,7 +158,9 @@ export async function runChase(opts) {
|
|
|
141
158
|
break;
|
|
142
159
|
}
|
|
143
160
|
try {
|
|
144
|
-
|
|
161
|
+
// Fast cancel (f: true): the chase quote is always a plain resting
|
|
162
|
+
// limit, and repricing latency is the whole point of chase.
|
|
163
|
+
await client.cancel(opts.coin, currentOid, { fast: true });
|
|
145
164
|
}
|
|
146
165
|
catch {
|
|
147
166
|
// Order might have filled between the fill check and cancel.
|
|
@@ -155,6 +174,13 @@ export async function runChase(opts) {
|
|
|
155
174
|
out(`\n✅ Order filled!`);
|
|
156
175
|
break;
|
|
157
176
|
}
|
|
177
|
+
// The dust remainder of a partial fill can drop below the exchange
|
|
178
|
+
// minimum — placing it would just error out on every tick.
|
|
179
|
+
if (!opts.reduceOnly && remainingSize * currentMid < MIN_ORDER_NOTIONAL_USD) {
|
|
180
|
+
out(`\n⚠️ Remaining size (~$${(remainingSize * currentMid).toFixed(2)}) fell below the $${MIN_ORDER_NOTIONAL_USD} exchange minimum`);
|
|
181
|
+
exitReason = 'min_notional';
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
158
184
|
out(`[${iteration}] Mid: ${formatUsd(currentMid)} → Order: ${formatUsd(orderPrice)} x ${remainingSize.toFixed(6)}...`);
|
|
159
185
|
const response = await client.limitOrder(opts.coin, isBuy, remainingSize, orderPrice, 'Alo', opts.reduceOnly, opts.leverage);
|
|
160
186
|
if (response.status === 'ok' && response.response && typeof response.response === 'object') {
|
|
@@ -175,11 +201,20 @@ export async function runChase(opts) {
|
|
|
175
201
|
}
|
|
176
202
|
}
|
|
177
203
|
else if (status?.error) {
|
|
178
|
-
|
|
204
|
+
if (/post.?only|immediately match/i.test(status.error)) {
|
|
205
|
+
// Stale mid crossed the book — the fast-market condition chase
|
|
206
|
+
// exists for. Reprice on the next tick instead of dying.
|
|
207
|
+
out(`↩️ Quote would cross the book — repricing`);
|
|
208
|
+
lastPrice = null;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
throw new Error(status.error);
|
|
212
|
+
}
|
|
179
213
|
}
|
|
180
214
|
}
|
|
181
215
|
else {
|
|
182
|
-
|
|
216
|
+
const reason = typeof response.response === 'string' ? response.response : 'Order rejected';
|
|
217
|
+
throw new Error(reason);
|
|
183
218
|
}
|
|
184
219
|
}
|
|
185
220
|
else {
|
|
@@ -211,25 +246,27 @@ export async function runChase(opts) {
|
|
|
211
246
|
}
|
|
212
247
|
}
|
|
213
248
|
finally {
|
|
249
|
+
// Always pull the working order before returning — even when the loop
|
|
250
|
+
// throws — so an error never strands a live resting quote at a stale price.
|
|
251
|
+
if (currentOid !== null && !filled) {
|
|
252
|
+
applyFills(currentOid);
|
|
253
|
+
out(`\nCancelling unfilled order...`);
|
|
254
|
+
try {
|
|
255
|
+
await client.cancel(opts.coin, currentOid, { fast: true });
|
|
256
|
+
out(`✅ Cancelled`);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
out(`⚠️ Could not cancel (may have filled)`);
|
|
260
|
+
}
|
|
261
|
+
applyFills(currentOid);
|
|
262
|
+
if (remainingSize <= opts.size * 0.001) {
|
|
263
|
+
filled = true;
|
|
264
|
+
exitReason = 'filled';
|
|
265
|
+
}
|
|
266
|
+
}
|
|
214
267
|
if (ownsFillWatcher)
|
|
215
268
|
await fillWatcher.stop();
|
|
216
269
|
}
|
|
217
|
-
if (currentOid !== null && !filled) {
|
|
218
|
-
applyFills(currentOid);
|
|
219
|
-
out(`\nCancelling unfilled order...`);
|
|
220
|
-
try {
|
|
221
|
-
await client.cancel(opts.coin, currentOid);
|
|
222
|
-
out(`✅ Cancelled`);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
out(`⚠️ Could not cancel (may have filled)`);
|
|
226
|
-
}
|
|
227
|
-
applyFills(currentOid);
|
|
228
|
-
if (remainingSize <= opts.size * 0.001) {
|
|
229
|
-
filled = true;
|
|
230
|
-
exitReason = 'filled';
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
270
|
const elapsed = (Date.now() - startTime) / 1000;
|
|
234
271
|
const endMid = parseFloat((await client.getAllMids())[opts.coin]);
|
|
235
272
|
const priceMove = ((endMid - startMid) / startMid) * 10000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale.d.ts","sourceRoot":"","sources":["../../scripts/operations/scale.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAyCtE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,SAAS,CACP,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GACzI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1B,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAClD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,MAAM,GAC9C,UAAU,EAAE,CA0Cd;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"scale.d.ts","sourceRoot":"","sources":["../../scripts/operations/scale.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAyCtE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,SAAS,CACP,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GACzI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1B,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAClD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,QAAQ,GAAG,aAAa,GAAG,MAAM,GAC9C,UAAU,EAAE,CA0Cd;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAsIvE"}
|