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