@adaptic/utils 0.0.996 → 0.0.998

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/dist/index.mjs CHANGED
@@ -6998,11 +6998,33 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
6998
6998
  status: "open",
6999
6999
  symbols: [normalizedSymbol],
7000
7000
  });
7001
- // Orders stuck in pending_cancel / pending_replace are already
7002
- // terminalising (or wedged — see the 42210000 handling above);
7003
- // waiting longer will not change them, so they must not make
7004
- // the verification spin to exhaustion on every close attempt.
7005
- const remainingOrders = allRemainingOrders.filter((o) => o.status !== "pending_cancel" && o.status !== "pending_replace");
7001
+ // pending_cancel / pending_replace handling needs an age split
7002
+ // (2026-06-10 live evidence, both directions):
7003
+ // - FRESH pending (entered the state < 30s ago): the broker is
7004
+ // mid-terminalisation and STILL HOLDS the order's quantity.
7005
+ // Treating it as done made the close fire early and bounce
7006
+ // with 403 40310000 "insufficient qty available" (META/WFC
7007
+ // closes at 17:07Z), so it must stay in `remainingOrders`
7008
+ // and keep the verification loop waiting.
7009
+ // - STALE pending (>= 30s): a wedged zombie (observed 8 days
7010
+ // on one order) that will never terminalise — waiting spins
7011
+ // the loop to exhaustion on every close attempt, so it is
7012
+ // excluded and the close proceeds; the broker arbitrates the
7013
+ // held quantity.
7014
+ const WEDGED_PENDING_AGE_MS = 30_000;
7015
+ const remainingOrders = allRemainingOrders.filter((o) => {
7016
+ if (o.status !== "pending_cancel" &&
7017
+ o.status !== "pending_replace") {
7018
+ return true;
7019
+ }
7020
+ const updatedAtMs = Date.parse(o.updated_at ?? "");
7021
+ if (!Number.isFinite(updatedAtMs)) {
7022
+ // No usable timestamp — conservatively treat as fresh so the
7023
+ // loop waits rather than racing the broker's qty release.
7024
+ return true;
7025
+ }
7026
+ return Date.now() - updatedAtMs < WEDGED_PENDING_AGE_MS;
7027
+ });
7006
7028
  if (remainingOrders.length === 0) {
7007
7029
  getLogger().info(`Cancel verification passed for ${normalizedSymbol} (attempt ${attempt}/${maxVerifyAttempts})`, {
7008
7030
  account: auth.adapticAccountId || "direct",