perp-cli 0.7.30 → 0.7.32
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.
|
@@ -293,19 +293,32 @@ export class FundingArbStrategy {
|
|
|
293
293
|
logExecution({ type: "arb_close", exchange: pos.longExchange, symbol: pos.symbol, side: "sell", size: pos.size, status: "failed", error: msg, dryRun: false, meta: { mode: pos.mode, leg: "long" } });
|
|
294
294
|
this._failCooldown.set(`${pos.symbol}:close`, Date.now() + 5 * 60 * 1000);
|
|
295
295
|
}
|
|
296
|
+
if (!closeLongOk)
|
|
297
|
+
continue; // don't close short if long failed — keep hedge intact
|
|
296
298
|
try {
|
|
297
299
|
const closeShortResult = await shortAdapter.marketOrder(pos.symbol, "buy", pos.size);
|
|
298
300
|
logExecution({ type: "arb_close", exchange: pos.shortExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "success", dryRun: false, meta: { mode: pos.mode, leg: "short", signedSpread: signedSpread, response: closeShortResult } });
|
|
299
301
|
}
|
|
300
302
|
catch (err) {
|
|
301
303
|
const msg = err instanceof Error ? err.message : String(err);
|
|
302
|
-
ctx.log(` [ARB] Close short leg failed for ${pos.symbol}: ${msg}`);
|
|
304
|
+
ctx.log(` [ARB] Close short leg failed for ${pos.symbol}: ${msg} — re-opening long to restore hedge`);
|
|
303
305
|
logExecution({ type: "arb_close", exchange: pos.shortExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "failed", error: msg, dryRun: false, meta: { mode: pos.mode, leg: "short" } });
|
|
306
|
+
// Rollback: re-open long to restore hedge
|
|
307
|
+
try {
|
|
308
|
+
await longAdapter.marketOrder(pos.symbol, "buy", pos.size);
|
|
309
|
+
ctx.log(` [ARB] Rollback: re-opened long ${pos.size} ${pos.symbol} on ${pos.longExchange}`);
|
|
310
|
+
logExecution({ type: "multi_leg_rollback", exchange: pos.longExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "success", dryRun: false });
|
|
311
|
+
}
|
|
312
|
+
catch (rbErr) {
|
|
313
|
+
const rbMsg = rbErr instanceof Error ? rbErr.message : String(rbErr);
|
|
314
|
+
ctx.log(` [ARB] CRITICAL: Close rollback failed for ${pos.symbol}: ${rbMsg}`);
|
|
315
|
+
logExecution({ type: "multi_leg_rollback", exchange: pos.longExchange, symbol: pos.symbol, side: "buy", size: pos.size, status: "failed", error: rbMsg, dryRun: false });
|
|
316
|
+
}
|
|
317
|
+
this._failCooldown.set(`${pos.symbol}:close`, Date.now() + 5 * 60 * 1000);
|
|
318
|
+
continue;
|
|
304
319
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
ctx.log(` [ARB] Closed ${pos.symbol} position`);
|
|
308
|
-
}
|
|
320
|
+
toClose.push(pos);
|
|
321
|
+
ctx.log(` [ARB] Closed ${pos.symbol} position`);
|
|
309
322
|
}
|
|
310
323
|
}
|
|
311
324
|
}
|
package/dist/exchanges/aster.js
CHANGED
|
@@ -277,17 +277,51 @@ export class AsterAdapter {
|
|
|
277
277
|
}
|
|
278
278
|
// ── Trading ──
|
|
279
279
|
async marketOrder(symbol, side, size) {
|
|
280
|
+
const apiSymbol = this._toApi(symbol);
|
|
280
281
|
const result = await this._signedPost("/fapi/v1/order", {
|
|
281
|
-
symbol:
|
|
282
|
+
symbol: apiSymbol,
|
|
282
283
|
side: side.toUpperCase(),
|
|
283
284
|
type: "MARKET",
|
|
284
285
|
quantity: size,
|
|
285
286
|
});
|
|
286
|
-
// Validate fill — Binance-style API returns 200 even for 0-fill orders
|
|
287
287
|
const r = result;
|
|
288
288
|
const executedQty = Number(r.executedQty ?? 0);
|
|
289
|
+
const orderId = String(r.orderId ?? "");
|
|
290
|
+
// If immediately filled, return
|
|
291
|
+
if (executedQty > 0 && r.status === "FILLED")
|
|
292
|
+
return result;
|
|
293
|
+
// status=NEW: order accepted but not yet filled — poll for up to 3 seconds
|
|
294
|
+
if (r.status === "NEW" && orderId) {
|
|
295
|
+
for (let i = 0; i < 3; i++) {
|
|
296
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
297
|
+
try {
|
|
298
|
+
const check = await this._signedGet("/fapi/v1/order", {
|
|
299
|
+
symbol: apiSymbol,
|
|
300
|
+
orderId,
|
|
301
|
+
});
|
|
302
|
+
const filledQty = Number(check.executedQty ?? 0);
|
|
303
|
+
if (filledQty > 0)
|
|
304
|
+
return check; // filled
|
|
305
|
+
if (check.status === "CANCELED" || check.status === "EXPIRED" || check.status === "REJECTED") {
|
|
306
|
+
throw new Error(`Market ${side} ${symbol}: order ${check.status} (orderId: ${orderId})`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
if (e instanceof Error && e.message.startsWith("Market "))
|
|
311
|
+
throw e;
|
|
312
|
+
// query failed, retry
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Still not filled after 3s — cancel and throw
|
|
316
|
+
try {
|
|
317
|
+
await this._signedDelete("/fapi/v1/order", { symbol: apiSymbol, orderId });
|
|
318
|
+
}
|
|
319
|
+
catch { /* best effort cancel */ }
|
|
320
|
+
throw new Error(`Market ${side} ${symbol}: order not filled after 3s, cancelled (orderId: ${orderId})`);
|
|
321
|
+
}
|
|
322
|
+
// executedQty === 0 with non-NEW status
|
|
289
323
|
if (executedQty === 0) {
|
|
290
|
-
throw new Error(`Market ${side} ${symbol}: order accepted but 0 filled (status: ${r.status}, orderId: ${
|
|
324
|
+
throw new Error(`Market ${side} ${symbol}: order accepted but 0 filled (status: ${r.status}, orderId: ${orderId})`);
|
|
291
325
|
}
|
|
292
326
|
return result;
|
|
293
327
|
}
|