@adaptic/utils 0.0.994 → 0.0.996

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
@@ -6945,8 +6945,38 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
6945
6945
  account: auth.adapticAccountId || "direct",
6946
6946
  symbol: normalizedSymbol,
6947
6947
  });
6948
- await cancelOrder$1(auth, order.id);
6949
- cancelledCount++;
6948
+ try {
6949
+ await cancelOrder$1(auth, order.id);
6950
+ cancelledCount++;
6951
+ }
6952
+ catch (cancelError) {
6953
+ // Alpaca rejects DELETE on an order that is already in
6954
+ // pending_cancel / pending_replace with HTTP 422 code
6955
+ // 42210000 ("order pending cancel"). The order is already
6956
+ // dying — which is exactly what this sweep wants — so
6957
+ // aborting the entire position close here is wrong: it
6958
+ // left REDUCE/EXIT risk actions permanently blocked when a
6959
+ // protective stop wedged in pending_cancel at the broker
6960
+ // (observed live 2026-06-10: an 8-day-old pending_cancel
6961
+ // trailing stop on a naked short made every exit attempt
6962
+ // throw). Treat already-cancelling as success-in-progress
6963
+ // and let the subsequent close attempt get the broker's
6964
+ // authoritative answer on held quantity. Any other cancel
6965
+ // failure still aborts the close as before.
6966
+ const message = cancelError instanceof Error
6967
+ ? cancelError.message
6968
+ : String(cancelError);
6969
+ const alreadyCancelling = message.includes("42210000") ||
6970
+ message.includes("order pending cancel");
6971
+ if (!alreadyCancelling) {
6972
+ throw cancelError;
6973
+ }
6974
+ getLogger().warn(`Order ${order.id} (${order.symbol}) is already pending cancel at the broker — proceeding with position close without it`, {
6975
+ account: auth.adapticAccountId || "direct",
6976
+ symbol: normalizedSymbol,
6977
+ type: "warn",
6978
+ });
6979
+ }
6950
6980
  }
6951
6981
  }
6952
6982
  if (cancelledCount > 0) {
@@ -6962,12 +6992,17 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
6962
6992
  const verifyDelayMs = 500;
6963
6993
  for (let attempt = 1; attempt <= maxVerifyAttempts; attempt++) {
6964
6994
  await new Promise((r) => setTimeout(r, verifyDelayMs));
6965
- const remainingOrders = isCryptoSymbol$1(symbolOrAssetId)
6995
+ const allRemainingOrders = isCryptoSymbol$1(symbolOrAssetId)
6966
6996
  ? (await getOrders$1(auth, { status: "open" })).filter((o) => o.symbol.replace(/[-/]/g, "") === normalizedSymbol)
6967
6997
  : await getOrders$1(auth, {
6968
6998
  status: "open",
6969
6999
  symbols: [normalizedSymbol],
6970
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");
6971
7006
  if (remainingOrders.length === 0) {
6972
7007
  getLogger().info(`Cancel verification passed for ${normalizedSymbol} (attempt ${attempt}/${maxVerifyAttempts})`, {
6973
7008
  account: auth.adapticAccountId || "direct",
@@ -9318,8 +9353,11 @@ const fetchTrades = async (symbol, options) => {
9318
9353
  *
9319
9354
  * This module provides functions to interact with the Massive.com Indices API.
9320
9355
  */
9321
- // Constants from environment variables
9322
- const { ALPACA_INDICES_API_KEY } = process.env;
9356
+ // Constants from environment variables.
9357
+ // Backward-compat: ALPACA_INDICES_API_KEY is the pre-2026-Q2 name (vendor was
9358
+ // renamed Alpaca → Massive); MASSIVE_INDICES_API_KEY is preferred. The fallback
9359
+ // stays so consumers that haven't migrated env vars don't break.
9360
+ const { MASSIVE_INDICES_API_KEY, ALPACA_INDICES_API_KEY } = process.env;
9323
9361
  // Define concurrency limits for API
9324
9362
  const MASSIVE_INDICES_CONCURRENCY_LIMIT = 5;
9325
9363
  const massiveIndicesLimit = pLimit(MASSIVE_INDICES_CONCURRENCY_LIMIT);
@@ -9331,7 +9369,7 @@ const MASSIVE_API_BASE_URL = "https://api.massive.com";
9331
9369
  * @throws {Error} If no API key is available
9332
9370
  */
9333
9371
  const validateApiKey = (apiKey) => {
9334
- const key = apiKey || ALPACA_INDICES_API_KEY;
9372
+ const key = apiKey || MASSIVE_INDICES_API_KEY || ALPACA_INDICES_API_KEY;
9335
9373
  if (!key) {
9336
9374
  throw new Error("Massive Indices API key is missing");
9337
9375
  }