@raintree-technology/perps 0.1.2 → 0.1.4
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 +19 -1
- package/README.md +42 -4
- package/dist/adapters/aevo.d.ts +1 -6
- package/dist/adapters/aevo.js +0 -21
- package/dist/adapters/certification.js +55 -9
- package/dist/adapters/decibel/order-manager.d.ts +41 -0
- package/dist/adapters/decibel/order-manager.js +216 -0
- package/dist/adapters/decibel/rest-client.d.ts +21 -0
- package/dist/adapters/decibel/rest-client.js +15 -8
- package/dist/adapters/decibel/ws-feed.js +19 -4
- package/dist/adapters/decibel.d.ts +15 -10
- package/dist/adapters/decibel.js +371 -50
- package/dist/adapters/hyperliquid.d.ts +1 -6
- package/dist/adapters/hyperliquid.js +0 -18
- package/dist/adapters/interface.d.ts +15 -14
- package/dist/adapters/orderly.d.ts +1 -9
- package/dist/adapters/orderly.js +0 -33
- package/dist/adapters/paradex.d.ts +1 -9
- package/dist/adapters/paradex.js +1 -34
- package/dist/cli/experience.js +0 -1
- package/dist/cli/program.js +28 -5
- package/dist/commands/arb/alert.d.ts +0 -4
- package/dist/commands/arb/alert.js +4 -14
- package/dist/commands/markets/index.js +2 -0
- package/dist/commands/markets/read-simple.d.ts +2 -0
- package/dist/commands/markets/read-simple.js +130 -0
- package/dist/commands/order/advanced-simple.d.ts +2 -0
- package/dist/commands/order/advanced-simple.js +820 -0
- package/dist/commands/order/index.js +2 -0
- package/dist/index.js +35 -1
- package/dist/lib/exit-codes.js +5 -1
- package/dist/lib/prompts.d.ts +0 -18
- package/dist/lib/prompts.js +1 -22
- package/dist/lib/schema.d.ts +8 -8
- package/dist/lib/schema.js +47 -8
- package/package.json +5 -2
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
import { getContext, getOutputOptions, getSelectedExchange } from "../../cli/program.js";
|
|
2
|
+
import { output, outputError, outputSuccess } from "../../cli/output.js";
|
|
3
|
+
import { getExchangeAdapter } from "../../lib/exchange.js";
|
|
4
|
+
import { getExchangeCredentials } from "../../lib/config.js";
|
|
5
|
+
import { inferExitCode } from "../../lib/exit-codes.js";
|
|
6
|
+
import { normalizeMarket, normalizeSide } from "./shared.js";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
function parseNumber(value, label) {
|
|
9
|
+
const parsed = Number.parseFloat(value);
|
|
10
|
+
if (!Number.isFinite(parsed)) {
|
|
11
|
+
throw new Error(`${label} must be a finite number`);
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
function parsePositiveNumber(value, label) {
|
|
16
|
+
const parsed = parseNumber(value, label);
|
|
17
|
+
if (parsed <= 0) {
|
|
18
|
+
throw new Error(`${label} must be a positive number`);
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
function parsePositiveInteger(value, label) {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
25
|
+
throw new Error(`${label} must be a positive integer`);
|
|
26
|
+
}
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
function parseNonNegativeInteger(value, label) {
|
|
30
|
+
const parsed = Number(value);
|
|
31
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
32
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
function parseNonZeroNumber(value, label) {
|
|
37
|
+
const parsed = parseNumber(value, label);
|
|
38
|
+
if (parsed === 0) {
|
|
39
|
+
throw new Error(`${label} must be non-zero`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
function parseMarginType(value) {
|
|
44
|
+
const normalized = value.trim().toLowerCase();
|
|
45
|
+
if (normalized === "cross" || normalized === "isolated") {
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
throw new Error('Margin type must be "cross" or "isolated"');
|
|
49
|
+
}
|
|
50
|
+
function parseOrderType(value) {
|
|
51
|
+
if (typeof value !== "string") {
|
|
52
|
+
throw new Error("Order type must be a string");
|
|
53
|
+
}
|
|
54
|
+
const normalized = value.trim().toLowerCase();
|
|
55
|
+
const allowed = ["market", "limit", "stop", "stop_limit", "take_profit"];
|
|
56
|
+
if (!allowed.includes(normalized)) {
|
|
57
|
+
throw new Error(`Unsupported order type '${value}'`);
|
|
58
|
+
}
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
function parseBoolean(value, label) {
|
|
62
|
+
if (value === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (typeof value === "boolean")
|
|
65
|
+
return value;
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
const normalized = value.trim().toLowerCase();
|
|
68
|
+
if (normalized === "true")
|
|
69
|
+
return true;
|
|
70
|
+
if (normalized === "false")
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`${label} must be a boolean`);
|
|
74
|
+
}
|
|
75
|
+
function parseJsonArrayInput(options, label) {
|
|
76
|
+
const payload = options.ordersFile !== undefined
|
|
77
|
+
? readFileSync(options.ordersFile, "utf8")
|
|
78
|
+
: options.orders;
|
|
79
|
+
if (payload === undefined) {
|
|
80
|
+
throw new Error(`${label} payload is required via --orders or --orders-file`);
|
|
81
|
+
}
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(payload);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
throw new Error(`${label} payload must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(parsed)) {
|
|
90
|
+
throw new Error(`${label} payload must be a JSON array`);
|
|
91
|
+
}
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
function isRecord(value) {
|
|
95
|
+
return typeof value === "object" && value !== null;
|
|
96
|
+
}
|
|
97
|
+
function parseBatchOrderParams(input) {
|
|
98
|
+
if (!isRecord(input)) {
|
|
99
|
+
throw new Error("Each batch order item must be an object");
|
|
100
|
+
}
|
|
101
|
+
const sideRaw = input.side;
|
|
102
|
+
const marketRaw = input.market;
|
|
103
|
+
const typeRaw = input.type;
|
|
104
|
+
const sizeRaw = input.size;
|
|
105
|
+
if (typeof sideRaw !== "string")
|
|
106
|
+
throw new Error("Batch order item requires string field 'side'");
|
|
107
|
+
if (typeof marketRaw !== "string")
|
|
108
|
+
throw new Error("Batch order item requires string field 'market'");
|
|
109
|
+
if (typeof sizeRaw !== "string" && typeof sizeRaw !== "number") {
|
|
110
|
+
throw new Error("Batch order item requires string/number field 'size'");
|
|
111
|
+
}
|
|
112
|
+
const params = {
|
|
113
|
+
side: normalizeSide(sideRaw),
|
|
114
|
+
market: normalizeMarket(marketRaw),
|
|
115
|
+
type: typeRaw === undefined ? "limit" : parseOrderType(typeRaw),
|
|
116
|
+
size: parsePositiveNumber(String(sizeRaw), "Batch order size").toString(),
|
|
117
|
+
};
|
|
118
|
+
if (input.price !== undefined) {
|
|
119
|
+
params.price = parsePositiveNumber(String(input.price), "Batch order price").toString();
|
|
120
|
+
}
|
|
121
|
+
if (input.triggerPrice !== undefined) {
|
|
122
|
+
params.triggerPrice = parsePositiveNumber(String(input.triggerPrice), "Batch order triggerPrice").toString();
|
|
123
|
+
}
|
|
124
|
+
if (input.reduceOnly !== undefined) {
|
|
125
|
+
params.reduceOnly = parseBoolean(input.reduceOnly, "Batch order reduceOnly");
|
|
126
|
+
}
|
|
127
|
+
if (input.postOnly !== undefined) {
|
|
128
|
+
params.postOnly = parseBoolean(input.postOnly, "Batch order postOnly");
|
|
129
|
+
}
|
|
130
|
+
if (input.clientOrderId !== undefined) {
|
|
131
|
+
if (typeof input.clientOrderId !== "string" || input.clientOrderId.trim().length === 0) {
|
|
132
|
+
throw new Error("Batch order clientOrderId must be a non-empty string when provided");
|
|
133
|
+
}
|
|
134
|
+
params.clientOrderId = input.clientOrderId;
|
|
135
|
+
}
|
|
136
|
+
return params;
|
|
137
|
+
}
|
|
138
|
+
function parseBatchCancelParams(input) {
|
|
139
|
+
if (!isRecord(input)) {
|
|
140
|
+
throw new Error("Each batch cancel item must be an object");
|
|
141
|
+
}
|
|
142
|
+
if (typeof input.orderId !== "string" || input.orderId.trim().length === 0) {
|
|
143
|
+
throw new Error("Batch cancel item requires non-empty string field 'orderId'");
|
|
144
|
+
}
|
|
145
|
+
const params = {
|
|
146
|
+
orderId: input.orderId.trim(),
|
|
147
|
+
};
|
|
148
|
+
if (input.market !== undefined) {
|
|
149
|
+
if (typeof input.market !== "string" || input.market.trim().length === 0) {
|
|
150
|
+
throw new Error("Batch cancel item market must be a non-empty string when provided");
|
|
151
|
+
}
|
|
152
|
+
params.market = normalizeMarket(input.market);
|
|
153
|
+
}
|
|
154
|
+
return params;
|
|
155
|
+
}
|
|
156
|
+
function assertFeatureMethod(adapter, feature, method, capabilityLabel) {
|
|
157
|
+
if (!adapter.info.features[feature]) {
|
|
158
|
+
throw new Error(`${adapter.info.name} does not support ${capabilityLabel}`);
|
|
159
|
+
}
|
|
160
|
+
if (typeof adapter[method] !== "function") {
|
|
161
|
+
throw new Error(`${adapter.info.name} reports ${capabilityLabel} support but is missing adapter method '${method}'`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function handleCommandError(outputOpts, err) {
|
|
165
|
+
const code = inferExitCode(err);
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
if (outputOpts.json) {
|
|
168
|
+
output({
|
|
169
|
+
status: "error",
|
|
170
|
+
error: {
|
|
171
|
+
message,
|
|
172
|
+
exitCode: code,
|
|
173
|
+
},
|
|
174
|
+
}, outputOpts);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
outputError(message);
|
|
178
|
+
}
|
|
179
|
+
process.exit(code);
|
|
180
|
+
}
|
|
181
|
+
async function runWithConnectedAdapter(command, adapter, requirement, action) {
|
|
182
|
+
const ctx = getContext(command);
|
|
183
|
+
const outputOpts = getOutputOptions(command);
|
|
184
|
+
const exchangeId = getSelectedExchange(command);
|
|
185
|
+
let connected = false;
|
|
186
|
+
try {
|
|
187
|
+
const credentials = getExchangeCredentials(ctx.config, exchangeId, requirement);
|
|
188
|
+
await adapter.connect({
|
|
189
|
+
testnet: ctx.config.testnet,
|
|
190
|
+
rpcUrl: credentials.fullnodeUrl,
|
|
191
|
+
wsUrl: credentials.wsUrl,
|
|
192
|
+
credentials,
|
|
193
|
+
});
|
|
194
|
+
connected = true;
|
|
195
|
+
await action(adapter, outputOpts);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
handleCommandError(outputOpts, err);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
if (connected) {
|
|
202
|
+
await adapter.disconnect().catch(() => undefined);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function formatFloat(value) {
|
|
207
|
+
const parsed = Number.parseFloat(value);
|
|
208
|
+
return Number.isFinite(parsed) ? parsed.toFixed(6) : value;
|
|
209
|
+
}
|
|
210
|
+
export function registerAdvancedOrderSimpleCommands(order) {
|
|
211
|
+
order
|
|
212
|
+
.command("get <orderId>")
|
|
213
|
+
.description("Show an order by ID")
|
|
214
|
+
.option("--symbol <symbol>", "Optional market symbol hint")
|
|
215
|
+
.action(async function (orderId) {
|
|
216
|
+
const adapter = getExchangeAdapter();
|
|
217
|
+
const opts = this.opts();
|
|
218
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
219
|
+
const orderRow = await connectedAdapter.getOrder(orderId);
|
|
220
|
+
if (out.json) {
|
|
221
|
+
output({ orderId, order: orderRow }, out);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (!orderRow) {
|
|
225
|
+
outputError(`Order ${orderId} not found${opts.symbol ? ` for ${normalizeMarket(opts.symbol)}` : ""}`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
outputSuccess(`Order ${orderId}`);
|
|
229
|
+
console.log(` Market: ${orderRow.market}`);
|
|
230
|
+
console.log(` Side: ${orderRow.side}`);
|
|
231
|
+
console.log(` Type: ${orderRow.type}`);
|
|
232
|
+
console.log(` Size: ${formatFloat(orderRow.size)}`);
|
|
233
|
+
if (orderRow.price) {
|
|
234
|
+
console.log(` Price: ${formatFloat(orderRow.price)}`);
|
|
235
|
+
}
|
|
236
|
+
if (orderRow.triggerPrice) {
|
|
237
|
+
console.log(` Trigger: ${formatFloat(orderRow.triggerPrice)}`);
|
|
238
|
+
}
|
|
239
|
+
console.log(` Status: ${orderRow.status}`);
|
|
240
|
+
console.log("");
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
order
|
|
244
|
+
.command("open [symbol]")
|
|
245
|
+
.description("Show open orders (optionally for one market)")
|
|
246
|
+
.action(async function (symbol) {
|
|
247
|
+
const adapter = getExchangeAdapter();
|
|
248
|
+
const market = symbol ? normalizeMarket(symbol) : undefined;
|
|
249
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
250
|
+
const orders = await connectedAdapter.getOrders(market);
|
|
251
|
+
if (out.json) {
|
|
252
|
+
output({ orders }, out);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
outputSuccess(`Fetched ${orders.length} open orders`);
|
|
256
|
+
for (const row of orders) {
|
|
257
|
+
console.log(` ${row.id} ${row.market.padEnd(10)} ${row.side.padEnd(5)} ${row.type.padEnd(11)} ${row.status.padEnd(9)} size=${formatFloat(row.size)}${row.price ? ` price=${formatFloat(row.price)}` : ""}`);
|
|
258
|
+
}
|
|
259
|
+
console.log("");
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
order
|
|
263
|
+
.command("trades [symbol]")
|
|
264
|
+
.description("Show account trade fills")
|
|
265
|
+
.option("--limit <n>", "Maximum rows (default: 100)")
|
|
266
|
+
.action(async function (symbol) {
|
|
267
|
+
const adapter = getExchangeAdapter();
|
|
268
|
+
const outputOpts = getOutputOptions(this);
|
|
269
|
+
try {
|
|
270
|
+
const opts = this.opts();
|
|
271
|
+
const limit = opts.limit !== undefined ? parsePositiveInteger(opts.limit, "Limit") : 100;
|
|
272
|
+
const market = symbol ? normalizeMarket(symbol) : undefined;
|
|
273
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
274
|
+
const trades = await connectedAdapter.getTrades(market, limit);
|
|
275
|
+
if (out.json) {
|
|
276
|
+
output({ trades }, out);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
outputSuccess(`Fetched ${trades.length} fills`);
|
|
280
|
+
for (const row of trades) {
|
|
281
|
+
console.log(` ${row.market.padEnd(10)} ${row.side.padEnd(5)} price=${row.price} size=${row.size} fee=${row.fee} timestamp=${new Date(row.timestamp).toISOString()}`);
|
|
282
|
+
}
|
|
283
|
+
console.log("");
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
handleCommandError(outputOpts, err);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
order
|
|
291
|
+
.command("position <symbol>")
|
|
292
|
+
.description("Show the current position for one market")
|
|
293
|
+
.action(async function (symbol) {
|
|
294
|
+
const adapter = getExchangeAdapter();
|
|
295
|
+
const market = normalizeMarket(symbol);
|
|
296
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
297
|
+
const position = await connectedAdapter.getPosition(market);
|
|
298
|
+
if (out.json) {
|
|
299
|
+
output({ market, position }, out);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (!position) {
|
|
303
|
+
outputError(`No open position for ${market}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
outputSuccess(`Position ${market}`);
|
|
307
|
+
console.log(` Side: ${position.side}`);
|
|
308
|
+
console.log(` Size: ${formatFloat(position.size)}`);
|
|
309
|
+
console.log(` Entry: ${formatFloat(position.entryPrice)}`);
|
|
310
|
+
console.log(` Mark: ${formatFloat(position.markPrice)}`);
|
|
311
|
+
console.log(` Leverage: ${position.leverage}x`);
|
|
312
|
+
console.log(` uPnL: ${position.unrealizedPnl}`);
|
|
313
|
+
console.log(` MarginType: ${position.marginType}`);
|
|
314
|
+
console.log("");
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
order
|
|
318
|
+
.command("batch-place")
|
|
319
|
+
.description("Place multiple orders from a JSON payload")
|
|
320
|
+
.option("--orders <json>", "JSON array of order params")
|
|
321
|
+
.option("--orders-file <path>", "Path to a JSON file containing an array of order params")
|
|
322
|
+
.action(async function () {
|
|
323
|
+
const adapter = getExchangeAdapter();
|
|
324
|
+
const outputOpts = getOutputOptions(this);
|
|
325
|
+
try {
|
|
326
|
+
assertFeatureMethod(adapter, "batchOrders", "batchPlaceOrders", "batch order placement");
|
|
327
|
+
const opts = this.opts();
|
|
328
|
+
const parsed = parseJsonArrayInput(opts, "batch-place");
|
|
329
|
+
const paramsList = parsed.map((item) => parseBatchOrderParams(item));
|
|
330
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
331
|
+
const orders = await connectedAdapter.batchPlaceOrders(paramsList);
|
|
332
|
+
if (out.json) {
|
|
333
|
+
output({ orders }, out);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
outputSuccess(`Placed ${orders.length} orders`);
|
|
337
|
+
for (const row of orders) {
|
|
338
|
+
console.log(` ${row.id} ${row.market.padEnd(10)} ${row.side.padEnd(5)} ${row.type.padEnd(11)} ${row.status.padEnd(9)} size=${formatFloat(row.size)}${row.price ? ` price=${formatFloat(row.price)}` : ""}`);
|
|
339
|
+
}
|
|
340
|
+
console.log("");
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
handleCommandError(outputOpts, err);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
order
|
|
348
|
+
.command("batch-cancel")
|
|
349
|
+
.description("Cancel multiple orders from a JSON payload")
|
|
350
|
+
.option("--orders <json>", "JSON array of cancel params")
|
|
351
|
+
.option("--orders-file <path>", "Path to a JSON file containing an array of cancel params")
|
|
352
|
+
.action(async function () {
|
|
353
|
+
const adapter = getExchangeAdapter();
|
|
354
|
+
const outputOpts = getOutputOptions(this);
|
|
355
|
+
try {
|
|
356
|
+
assertFeatureMethod(adapter, "batchOrders", "batchCancelOrders", "batch order cancellation");
|
|
357
|
+
const opts = this.opts();
|
|
358
|
+
const parsed = parseJsonArrayInput(opts, "batch-cancel");
|
|
359
|
+
const paramsList = parsed.map((item) => parseBatchCancelParams(item));
|
|
360
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
361
|
+
const results = await connectedAdapter.batchCancelOrders(paramsList);
|
|
362
|
+
const cancelled = results.filter(Boolean).length;
|
|
363
|
+
if (out.json) {
|
|
364
|
+
output({ results }, out);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
outputSuccess(`Cancelled ${cancelled}/${results.length} orders`);
|
|
368
|
+
console.log("");
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
handleCommandError(outputOpts, err);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
order
|
|
376
|
+
.command("cancel-all-after <timeoutMs>")
|
|
377
|
+
.description("Schedule an automatic cancel-all timeout in milliseconds (0 disables)")
|
|
378
|
+
.action(async function (timeoutMs) {
|
|
379
|
+
const adapter = getExchangeAdapter();
|
|
380
|
+
const outputOpts = getOutputOptions(this);
|
|
381
|
+
try {
|
|
382
|
+
assertFeatureMethod(adapter, "cancelAllAfter", "cancelAllAfter", "cancel-all-after scheduling");
|
|
383
|
+
const timeout = parseNonNegativeInteger(timeoutMs, "timeoutMs");
|
|
384
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
385
|
+
await connectedAdapter.cancelAllAfter(timeout);
|
|
386
|
+
if (out.json) {
|
|
387
|
+
output({ timeoutMs: timeout }, out);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
outputSuccess("Cancel-all-after updated");
|
|
391
|
+
console.log(` timeoutMs: ${timeout}`);
|
|
392
|
+
console.log("");
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
handleCommandError(outputOpts, err);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
const mmp = order.command("mmp").description("Manage Market Maker Protection");
|
|
400
|
+
mmp
|
|
401
|
+
.command("set <symbol>")
|
|
402
|
+
.description("Set MMP config for a market")
|
|
403
|
+
.requiredOption("--window-ms <ms>", "MMP rolling window in milliseconds")
|
|
404
|
+
.requiredOption("--frozen-ms <ms>", "Freeze duration in milliseconds")
|
|
405
|
+
.requiredOption("--quantity-limit <size>", "Max quantity in window")
|
|
406
|
+
.requiredOption("--delta-limit <delta>", "Max delta in window")
|
|
407
|
+
.action(async function (symbol) {
|
|
408
|
+
const adapter = getExchangeAdapter();
|
|
409
|
+
const outputOpts = getOutputOptions(this);
|
|
410
|
+
try {
|
|
411
|
+
assertFeatureMethod(adapter, "mmp", "setMMP", "MMP configuration");
|
|
412
|
+
const opts = this.opts();
|
|
413
|
+
const config = {
|
|
414
|
+
market: normalizeMarket(symbol),
|
|
415
|
+
windowMs: parsePositiveInteger(opts.windowMs, "windowMs"),
|
|
416
|
+
frozenMs: parsePositiveInteger(opts.frozenMs, "frozenMs"),
|
|
417
|
+
quantityLimit: parsePositiveNumber(opts.quantityLimit, "quantityLimit").toString(),
|
|
418
|
+
deltaLimit: parsePositiveNumber(opts.deltaLimit, "deltaLimit").toString(),
|
|
419
|
+
};
|
|
420
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
421
|
+
await connectedAdapter.setMMP(config);
|
|
422
|
+
if (out.json) {
|
|
423
|
+
output({ config }, out);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
outputSuccess(`MMP updated for ${config.market}`);
|
|
427
|
+
console.log(` windowMs: ${config.windowMs}`);
|
|
428
|
+
console.log(` frozenMs: ${config.frozenMs}`);
|
|
429
|
+
console.log(` quantityLimit: ${config.quantityLimit}`);
|
|
430
|
+
console.log(` deltaLimit: ${config.deltaLimit}`);
|
|
431
|
+
console.log("");
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
handleCommandError(outputOpts, err);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
mmp
|
|
439
|
+
.command("get <symbol>")
|
|
440
|
+
.description("Get MMP status for a market")
|
|
441
|
+
.action(async function (symbol) {
|
|
442
|
+
const adapter = getExchangeAdapter();
|
|
443
|
+
const outputOpts = getOutputOptions(this);
|
|
444
|
+
try {
|
|
445
|
+
assertFeatureMethod(adapter, "mmp", "getMMP", "MMP status queries");
|
|
446
|
+
const market = normalizeMarket(symbol);
|
|
447
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
448
|
+
const status = await connectedAdapter.getMMP(market);
|
|
449
|
+
if (out.json) {
|
|
450
|
+
output({ status }, out);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
outputSuccess(`MMP ${market}`);
|
|
454
|
+
console.log(` Frozen: ${status.frozen}`);
|
|
455
|
+
if (status.frozenUntil) {
|
|
456
|
+
console.log(` FrozenUntil: ${new Date(status.frozenUntil).toISOString()}`);
|
|
457
|
+
}
|
|
458
|
+
if (status.config) {
|
|
459
|
+
console.log(` Window: ${status.config.windowMs}`);
|
|
460
|
+
console.log(` FrozenMs: ${status.config.frozenMs}`);
|
|
461
|
+
console.log(` Quantity: ${status.config.quantityLimit}`);
|
|
462
|
+
console.log(` Delta: ${status.config.deltaLimit}`);
|
|
463
|
+
}
|
|
464
|
+
console.log("");
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
handleCommandError(outputOpts, err);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
mmp
|
|
472
|
+
.command("reset <symbol>")
|
|
473
|
+
.description("Reset MMP freeze state for a market")
|
|
474
|
+
.action(async function (symbol) {
|
|
475
|
+
const adapter = getExchangeAdapter();
|
|
476
|
+
const outputOpts = getOutputOptions(this);
|
|
477
|
+
try {
|
|
478
|
+
assertFeatureMethod(adapter, "mmp", "resetMMP", "MMP reset");
|
|
479
|
+
const market = normalizeMarket(symbol);
|
|
480
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
481
|
+
await connectedAdapter.resetMMP(market);
|
|
482
|
+
if (out.json) {
|
|
483
|
+
output({ market, reset: true }, out);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
outputSuccess(`MMP reset for ${market}`);
|
|
487
|
+
console.log("");
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
handleCommandError(outputOpts, err);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
order
|
|
495
|
+
.command("modify <orderId> <symbol>")
|
|
496
|
+
.description("Modify an existing order")
|
|
497
|
+
.option("--price <price>", "New limit price")
|
|
498
|
+
.option("--size <size>", "New order size")
|
|
499
|
+
.option("--trigger-price <price>", "New trigger price for stop/take-profit orders")
|
|
500
|
+
.action(async function (orderId, symbol) {
|
|
501
|
+
const adapter = getExchangeAdapter();
|
|
502
|
+
const outputOpts = getOutputOptions(this);
|
|
503
|
+
try {
|
|
504
|
+
assertFeatureMethod(adapter, "modifyOrders", "modifyOrder", "order modification");
|
|
505
|
+
const opts = this.opts();
|
|
506
|
+
const update = {
|
|
507
|
+
orderId,
|
|
508
|
+
market: normalizeMarket(symbol),
|
|
509
|
+
};
|
|
510
|
+
let updatedFields = 0;
|
|
511
|
+
if (opts.price !== undefined) {
|
|
512
|
+
update.price = parsePositiveNumber(opts.price, "Price").toString();
|
|
513
|
+
updatedFields++;
|
|
514
|
+
}
|
|
515
|
+
if (opts.size !== undefined) {
|
|
516
|
+
update.size = parsePositiveNumber(opts.size, "Size").toString();
|
|
517
|
+
updatedFields++;
|
|
518
|
+
}
|
|
519
|
+
if (opts.triggerPrice !== undefined) {
|
|
520
|
+
update.triggerPrice = parsePositiveNumber(opts.triggerPrice, "Trigger price").toString();
|
|
521
|
+
updatedFields++;
|
|
522
|
+
}
|
|
523
|
+
if (updatedFields === 0) {
|
|
524
|
+
throw new Error("At least one of --price, --size, or --trigger-price is required");
|
|
525
|
+
}
|
|
526
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
527
|
+
const updated = await connectedAdapter.modifyOrder(update);
|
|
528
|
+
if (out.json) {
|
|
529
|
+
output({
|
|
530
|
+
order: updated,
|
|
531
|
+
}, out);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
outputSuccess(`Order ${orderId} modified`);
|
|
535
|
+
console.log(` ID: ${updated.id}`);
|
|
536
|
+
console.log(` Market: ${updated.market}`);
|
|
537
|
+
console.log(` Side: ${updated.side}`);
|
|
538
|
+
console.log(` Type: ${updated.type}`);
|
|
539
|
+
console.log(` Size: ${formatFloat(updated.size)}`);
|
|
540
|
+
if (updated.price) {
|
|
541
|
+
console.log(` Price: ${formatFloat(updated.price)}`);
|
|
542
|
+
}
|
|
543
|
+
if (updated.triggerPrice) {
|
|
544
|
+
console.log(` Trigger: ${formatFloat(updated.triggerPrice)}`);
|
|
545
|
+
}
|
|
546
|
+
console.log(` Status: ${updated.status}`);
|
|
547
|
+
console.log("");
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
handleCommandError(outputOpts, err);
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
order
|
|
555
|
+
.command("leverage <symbol> <value>")
|
|
556
|
+
.description("Set leverage for a market")
|
|
557
|
+
.action(async function (symbol, value) {
|
|
558
|
+
const adapter = getExchangeAdapter();
|
|
559
|
+
const outputOpts = getOutputOptions(this);
|
|
560
|
+
try {
|
|
561
|
+
const leverage = parsePositiveNumber(value, "Leverage");
|
|
562
|
+
const market = normalizeMarket(symbol);
|
|
563
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (_connectedAdapter, out) => {
|
|
564
|
+
await _connectedAdapter.setLeverage(market, leverage);
|
|
565
|
+
if (out.json) {
|
|
566
|
+
output({ market, leverage }, out);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
outputSuccess(`Leverage set for ${market}`);
|
|
570
|
+
console.log(` Leverage: ${leverage}x`);
|
|
571
|
+
console.log("");
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
handleCommandError(outputOpts, err);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
order
|
|
579
|
+
.command("margin-type <symbol> <type>")
|
|
580
|
+
.description("Set margin type (cross or isolated) for a market")
|
|
581
|
+
.action(async function (symbol, type) {
|
|
582
|
+
const adapter = getExchangeAdapter();
|
|
583
|
+
const outputOpts = getOutputOptions(this);
|
|
584
|
+
try {
|
|
585
|
+
const marginType = parseMarginType(type);
|
|
586
|
+
const market = normalizeMarket(symbol);
|
|
587
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (_connectedAdapter, out) => {
|
|
588
|
+
await _connectedAdapter.setMarginType(market, marginType);
|
|
589
|
+
if (out.json) {
|
|
590
|
+
output({ market, marginType }, out);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
outputSuccess(`Margin type set for ${market}`);
|
|
594
|
+
console.log(` Type: ${marginType}`);
|
|
595
|
+
console.log("");
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
catch (err) {
|
|
599
|
+
handleCommandError(outputOpts, err);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
const twap = order
|
|
603
|
+
.command("twap")
|
|
604
|
+
.description("Manage TWAP orders");
|
|
605
|
+
twap
|
|
606
|
+
.command("place <side> <symbol> <size>")
|
|
607
|
+
.description("Place a TWAP order")
|
|
608
|
+
.requiredOption("--duration-ms <ms>", "TWAP duration in milliseconds")
|
|
609
|
+
.option("--interval-ms <ms>", "TWAP execution interval in milliseconds")
|
|
610
|
+
.action(async function (side, symbol, size) {
|
|
611
|
+
const adapter = getExchangeAdapter();
|
|
612
|
+
const outputOpts = getOutputOptions(this);
|
|
613
|
+
try {
|
|
614
|
+
assertFeatureMethod(adapter, "twapOrders", "placeTWAP", "TWAP orders");
|
|
615
|
+
const opts = this.opts();
|
|
616
|
+
const params = {
|
|
617
|
+
side: normalizeSide(side),
|
|
618
|
+
market: normalizeMarket(symbol),
|
|
619
|
+
size: parsePositiveNumber(size, "Size").toString(),
|
|
620
|
+
durationMs: parsePositiveInteger(opts.durationMs, "Duration"),
|
|
621
|
+
};
|
|
622
|
+
if (opts.intervalMs !== undefined) {
|
|
623
|
+
params.intervalMs = parsePositiveInteger(opts.intervalMs, "Interval");
|
|
624
|
+
}
|
|
625
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
626
|
+
const status = await connectedAdapter.placeTWAP(params);
|
|
627
|
+
if (out.json) {
|
|
628
|
+
output({ twap: status }, out);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
outputSuccess(`TWAP order placed (${status.id})`);
|
|
632
|
+
console.log(` Market: ${status.market}`);
|
|
633
|
+
console.log(` Side: ${status.side}`);
|
|
634
|
+
console.log(` Status: ${status.status}`);
|
|
635
|
+
console.log(` Total Size: ${formatFloat(status.totalSize)}`);
|
|
636
|
+
console.log(` Remaining: ${formatFloat(status.remainingSize)}`);
|
|
637
|
+
console.log("");
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
catch (err) {
|
|
641
|
+
handleCommandError(outputOpts, err);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
twap
|
|
645
|
+
.command("cancel <twapId>")
|
|
646
|
+
.description("Cancel a TWAP order")
|
|
647
|
+
.action(async function (twapId) {
|
|
648
|
+
const adapter = getExchangeAdapter();
|
|
649
|
+
const outputOpts = getOutputOptions(this);
|
|
650
|
+
try {
|
|
651
|
+
assertFeatureMethod(adapter, "twapOrders", "cancelTWAP", "TWAP orders");
|
|
652
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
653
|
+
const cancelled = await connectedAdapter.cancelTWAP(twapId);
|
|
654
|
+
if (out.json) {
|
|
655
|
+
output({ twapId, cancelled }, out);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (cancelled) {
|
|
659
|
+
outputSuccess(`TWAP ${twapId} cancelled`);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
outputError(`Unable to cancel TWAP ${twapId}`);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
handleCommandError(outputOpts, err);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
twap
|
|
671
|
+
.command("status <twapId>")
|
|
672
|
+
.description("Show TWAP status")
|
|
673
|
+
.action(async function (twapId) {
|
|
674
|
+
const adapter = getExchangeAdapter();
|
|
675
|
+
const outputOpts = getOutputOptions(this);
|
|
676
|
+
try {
|
|
677
|
+
assertFeatureMethod(adapter, "twapOrders", "getTWAPStatus", "TWAP status queries");
|
|
678
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
679
|
+
const status = await connectedAdapter.getTWAPStatus(twapId);
|
|
680
|
+
if (out.json) {
|
|
681
|
+
output({ twapId, status }, out);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (!status) {
|
|
685
|
+
outputError(`No TWAP status found for ${twapId}`);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
outputSuccess(`TWAP ${twapId}`);
|
|
689
|
+
console.log(` Market: ${status.market}`);
|
|
690
|
+
console.log(` Side: ${status.side}`);
|
|
691
|
+
console.log(` Status: ${status.status}`);
|
|
692
|
+
console.log(` Total Size: ${formatFloat(status.totalSize)}`);
|
|
693
|
+
console.log(` Executed: ${formatFloat(status.executedSize)}`);
|
|
694
|
+
console.log(` Remaining: ${formatFloat(status.remainingSize)}`);
|
|
695
|
+
console.log("");
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
catch (err) {
|
|
699
|
+
handleCommandError(outputOpts, err);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
order
|
|
703
|
+
.command("isolated-margin <symbol> [amount]")
|
|
704
|
+
.description("Adjust isolated margin for a market (positive adds, negative removes)")
|
|
705
|
+
.option("--amount <amount>", "Margin amount (non-zero). Supports negative values.")
|
|
706
|
+
.action(async function (symbol, amount) {
|
|
707
|
+
const adapter = getExchangeAdapter();
|
|
708
|
+
const outputOpts = getOutputOptions(this);
|
|
709
|
+
try {
|
|
710
|
+
assertFeatureMethod(adapter, "isolatedMargin", "updateIsolatedMargin", "isolated margin updates");
|
|
711
|
+
const opts = this.opts();
|
|
712
|
+
const rawAmount = amount ?? opts.amount;
|
|
713
|
+
if (rawAmount === undefined) {
|
|
714
|
+
throw new Error("Amount is required as <amount> or --amount");
|
|
715
|
+
}
|
|
716
|
+
const parsedAmount = parseNonZeroNumber(rawAmount, "Amount");
|
|
717
|
+
const market = normalizeMarket(symbol);
|
|
718
|
+
await runWithConnectedAdapter(this, adapter, { requireTrading: true }, async (connectedAdapter, out) => {
|
|
719
|
+
await connectedAdapter.updateIsolatedMargin(market, parsedAmount.toString());
|
|
720
|
+
if (out.json) {
|
|
721
|
+
output({ market, amount: parsedAmount.toString() }, out);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
outputSuccess(`Updated isolated margin for ${market}`);
|
|
725
|
+
console.log(` Amount: ${parsedAmount}`);
|
|
726
|
+
console.log("");
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
catch (err) {
|
|
730
|
+
handleCommandError(outputOpts, err);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
order
|
|
734
|
+
.command("history [symbol]")
|
|
735
|
+
.description("Show order history")
|
|
736
|
+
.option("--limit <n>", "Maximum rows (default: 100)")
|
|
737
|
+
.action(async function (symbol) {
|
|
738
|
+
const adapter = getExchangeAdapter();
|
|
739
|
+
const outputOpts = getOutputOptions(this);
|
|
740
|
+
try {
|
|
741
|
+
assertFeatureMethod(adapter, "orderHistory", "getOrderHistory", "order history");
|
|
742
|
+
const opts = this.opts();
|
|
743
|
+
const limit = opts.limit !== undefined ? parsePositiveInteger(opts.limit, "Limit") : 100;
|
|
744
|
+
const market = symbol ? normalizeMarket(symbol) : undefined;
|
|
745
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
746
|
+
const orders = await connectedAdapter.getOrderHistory(market, limit);
|
|
747
|
+
if (out.json) {
|
|
748
|
+
output({ orders }, out);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
outputSuccess(`Fetched ${orders.length} historical orders`);
|
|
752
|
+
for (const row of orders) {
|
|
753
|
+
console.log(` ${row.id} ${row.market.padEnd(10)} ${row.side.padEnd(5)} ${row.type.padEnd(11)} ${row.status.padEnd(9)} size=${formatFloat(row.size)}${row.price ? ` price=${formatFloat(row.price)}` : ""}`);
|
|
754
|
+
}
|
|
755
|
+
console.log("");
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
handleCommandError(outputOpts, err);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
order
|
|
763
|
+
.command("funding-history [symbol]")
|
|
764
|
+
.description("Show funding payment history")
|
|
765
|
+
.option("--limit <n>", "Maximum rows (default: 100)")
|
|
766
|
+
.action(async function (symbol) {
|
|
767
|
+
const adapter = getExchangeAdapter();
|
|
768
|
+
const outputOpts = getOutputOptions(this);
|
|
769
|
+
try {
|
|
770
|
+
assertFeatureMethod(adapter, "fundingHistory", "getFundingHistory", "funding history");
|
|
771
|
+
const opts = this.opts();
|
|
772
|
+
const limit = opts.limit !== undefined ? parsePositiveInteger(opts.limit, "Limit") : 100;
|
|
773
|
+
const market = symbol ? normalizeMarket(symbol) : undefined;
|
|
774
|
+
await runWithConnectedAdapter(this, adapter, { requireReadAccess: true }, async (connectedAdapter, out) => {
|
|
775
|
+
const payments = await connectedAdapter.getFundingHistory(market, limit);
|
|
776
|
+
if (out.json) {
|
|
777
|
+
output({ payments }, out);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
outputSuccess(`Fetched ${payments.length} funding records`);
|
|
781
|
+
for (const row of payments) {
|
|
782
|
+
console.log(` ${row.market.padEnd(10)} amount=${row.amount} rate=${row.rate} timestamp=${new Date(row.timestamp).toISOString()}`);
|
|
783
|
+
}
|
|
784
|
+
console.log("");
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
catch (err) {
|
|
788
|
+
handleCommandError(outputOpts, err);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
order
|
|
792
|
+
.command("public-trades <symbol>")
|
|
793
|
+
.description("Show recent public trades")
|
|
794
|
+
.option("--limit <n>", "Maximum rows (default: 100)")
|
|
795
|
+
.action(async function (symbol) {
|
|
796
|
+
const adapter = getExchangeAdapter();
|
|
797
|
+
const outputOpts = getOutputOptions(this);
|
|
798
|
+
try {
|
|
799
|
+
assertFeatureMethod(adapter, "publicTrades", "getPublicTrades", "public trade history");
|
|
800
|
+
const opts = this.opts();
|
|
801
|
+
const limit = opts.limit !== undefined ? parsePositiveInteger(opts.limit, "Limit") : 100;
|
|
802
|
+
const market = normalizeMarket(symbol);
|
|
803
|
+
await runWithConnectedAdapter(this, adapter, {}, async (connectedAdapter, out) => {
|
|
804
|
+
const trades = await connectedAdapter.getPublicTrades(market, limit);
|
|
805
|
+
if (out.json) {
|
|
806
|
+
output({ trades }, out);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
outputSuccess(`Fetched ${trades.length} public trades`);
|
|
810
|
+
for (const row of trades) {
|
|
811
|
+
console.log(` ${row.market.padEnd(10)} ${row.side.padEnd(5)} price=${row.price} size=${row.size} timestamp=${new Date(row.timestamp).toISOString()}`);
|
|
812
|
+
}
|
|
813
|
+
console.log("");
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
handleCommandError(outputOpts, err);
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
}
|