@adaptic/utils 0.0.940 → 0.0.942
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 +117 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +117 -29
- package/dist/index.mjs.map +1 -1
- package/dist/types/alpaca/legacy/orders.d.ts +1 -0
- package/dist/types/alpaca/legacy/orders.d.ts.map +1 -1
- package/dist/types/alpaca/legacy/positions.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4956,9 +4956,11 @@ async function createLimitOrder(auth, params = {
|
|
|
4956
4956
|
position_intent: "buy_to_open",
|
|
4957
4957
|
extended_hours: false,
|
|
4958
4958
|
client_order_id: undefined,
|
|
4959
|
+
time_in_force: undefined,
|
|
4959
4960
|
}) {
|
|
4960
|
-
const { symbol, qty, side, limitPrice, position_intent, extended_hours, client_order_id, } = params;
|
|
4961
|
-
|
|
4961
|
+
const { symbol, qty, side, limitPrice, position_intent, extended_hours, client_order_id, time_in_force, } = params;
|
|
4962
|
+
const effectiveTimeInForce = time_in_force ?? "day";
|
|
4963
|
+
getLogger().info(`Creating limit order for ${symbol}: ${side} ${qty} shares at ${limitPrice.toFixed(2)} (${position_intent}, tif=${effectiveTimeInForce})`, {
|
|
4962
4964
|
account: auth.adapticAccountId || "direct",
|
|
4963
4965
|
symbol,
|
|
4964
4966
|
});
|
|
@@ -4969,7 +4971,7 @@ async function createLimitOrder(auth, params = {
|
|
|
4969
4971
|
position_intent,
|
|
4970
4972
|
type: "limit",
|
|
4971
4973
|
limit_price: limitPrice.toString(),
|
|
4972
|
-
time_in_force:
|
|
4974
|
+
time_in_force: effectiveTimeInForce,
|
|
4973
4975
|
order_class: "simple",
|
|
4974
4976
|
extended_hours,
|
|
4975
4977
|
};
|
|
@@ -5453,17 +5455,17 @@ const DEFAULT_FEED = "sip";
|
|
|
5453
5455
|
* Known quote currencies that signal a crypto pair when found as a suffix.
|
|
5454
5456
|
* E.g. "BTCUSD" → crypto, "AAPL" → equity.
|
|
5455
5457
|
*/
|
|
5456
|
-
const CRYPTO_QUOTE_CURRENCIES = ["USD", "USDT", "USDC", "BTC"];
|
|
5458
|
+
const CRYPTO_QUOTE_CURRENCIES$1 = ["USD", "USDT", "USDC", "BTC"];
|
|
5457
5459
|
/**
|
|
5458
5460
|
* Detect whether a symbol looks like a crypto pair (e.g. "BTCUSD", "DOGEUSD",
|
|
5459
5461
|
* "BTC/USD"). Equity tickers never end with these suffixes because Alpaca
|
|
5460
5462
|
* equity symbols are plain tickers without a quote currency.
|
|
5461
5463
|
*/
|
|
5462
|
-
function isCryptoSymbol(symbol) {
|
|
5464
|
+
function isCryptoSymbol$1(symbol) {
|
|
5463
5465
|
if (symbol.includes("/"))
|
|
5464
5466
|
return true;
|
|
5465
5467
|
const upper = symbol.toUpperCase();
|
|
5466
|
-
return CRYPTO_QUOTE_CURRENCIES.some((qc) => {
|
|
5468
|
+
return CRYPTO_QUOTE_CURRENCIES$1.some((qc) => {
|
|
5467
5469
|
if (!upper.endsWith(qc))
|
|
5468
5470
|
return false;
|
|
5469
5471
|
const base = upper.slice(0, -qc.length);
|
|
@@ -5479,7 +5481,7 @@ function normalizeCryptoSymbolForApi(symbol) {
|
|
|
5479
5481
|
if (symbol.includes("/"))
|
|
5480
5482
|
return symbol.toUpperCase();
|
|
5481
5483
|
const upper = symbol.toUpperCase();
|
|
5482
|
-
for (const qc of CRYPTO_QUOTE_CURRENCIES) {
|
|
5484
|
+
for (const qc of CRYPTO_QUOTE_CURRENCIES$1) {
|
|
5483
5485
|
if (upper.endsWith(qc)) {
|
|
5484
5486
|
const base = upper.slice(0, -qc.length);
|
|
5485
5487
|
if (base.length >= 2)
|
|
@@ -5513,7 +5515,7 @@ async function getLatestQuotes$1(auth, params) {
|
|
|
5513
5515
|
const equitySymbols = [];
|
|
5514
5516
|
const cryptoSymbols = [];
|
|
5515
5517
|
for (const sym of symbols) {
|
|
5516
|
-
if (isCryptoSymbol(sym)) {
|
|
5518
|
+
if (isCryptoSymbol$1(sym)) {
|
|
5517
5519
|
cryptoSymbols.push(sym);
|
|
5518
5520
|
}
|
|
5519
5521
|
else {
|
|
@@ -5689,6 +5691,24 @@ async function fetchNews$1(symbols, params) {
|
|
|
5689
5691
|
}
|
|
5690
5692
|
}
|
|
5691
5693
|
|
|
5694
|
+
/** Known quote currencies that signal a crypto pair when found as a suffix */
|
|
5695
|
+
const CRYPTO_QUOTE_CURRENCIES = ["USD", "USDT", "USDC", "BTC"];
|
|
5696
|
+
/**
|
|
5697
|
+
* Detect whether a symbol looks like a crypto pair (e.g. "BTCUSD", "DOGEUSD",
|
|
5698
|
+
* "BTC/USD"). Equity tickers never end with these suffixes because Alpaca
|
|
5699
|
+
* equity symbols are plain tickers without a quote currency.
|
|
5700
|
+
*/
|
|
5701
|
+
function isCryptoSymbol(symbol) {
|
|
5702
|
+
if (symbol.includes("/"))
|
|
5703
|
+
return true;
|
|
5704
|
+
const upper = symbol.toUpperCase();
|
|
5705
|
+
return CRYPTO_QUOTE_CURRENCIES.some((qc) => {
|
|
5706
|
+
if (!upper.endsWith(qc))
|
|
5707
|
+
return false;
|
|
5708
|
+
const base = upper.slice(0, -qc.length);
|
|
5709
|
+
return base.length >= 2;
|
|
5710
|
+
});
|
|
5711
|
+
}
|
|
5692
5712
|
/**
|
|
5693
5713
|
* Fetches all positions for an Alpaca trading account.
|
|
5694
5714
|
* @param auth - The authentication details for Alpaca
|
|
@@ -5789,7 +5809,9 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
|
|
|
5789
5809
|
}
|
|
5790
5810
|
}
|
|
5791
5811
|
}
|
|
5792
|
-
|
|
5812
|
+
// Crypto positions cannot use limit orders with SIP quotes or time_in_force="day".
|
|
5813
|
+
// Use direct DELETE (market order) for crypto regardless of useLimitOrder flag.
|
|
5814
|
+
if (useLimitOrder && !isCryptoSymbol(symbolOrAssetId)) {
|
|
5793
5815
|
const { position } = await fetchPosition(auth, symbolOrAssetId);
|
|
5794
5816
|
if (!position) {
|
|
5795
5817
|
throw new Error(`Position not found for ${symbolOrAssetId}`);
|
|
@@ -5839,6 +5861,9 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
|
|
|
5839
5861
|
});
|
|
5840
5862
|
}
|
|
5841
5863
|
else {
|
|
5864
|
+
if (isCryptoSymbol(symbolOrAssetId)) {
|
|
5865
|
+
getLogger().info(`Closing crypto position ${symbolOrAssetId} via market order (DELETE endpoint)`, { account: auth.adapticAccountId || "direct", symbol: symbolOrAssetId });
|
|
5866
|
+
}
|
|
5842
5867
|
const queryParams = new URLSearchParams();
|
|
5843
5868
|
if (params?.qty !== undefined) {
|
|
5844
5869
|
queryParams.append("qty", params.qty.toString());
|
|
@@ -5897,37 +5922,68 @@ async function closeAllPositions$1(auth, params = { cancel_orders: true, useLimi
|
|
|
5897
5922
|
// Continue with closure attempt even if cancel failed
|
|
5898
5923
|
}
|
|
5899
5924
|
}
|
|
5900
|
-
const
|
|
5901
|
-
if (
|
|
5925
|
+
const allPositions = await fetchAllPositions(auth);
|
|
5926
|
+
if (allPositions.length === 0) {
|
|
5902
5927
|
getLogger().info("No positions to close", {
|
|
5903
5928
|
account: auth.adapticAccountId || "direct",
|
|
5904
5929
|
});
|
|
5905
5930
|
return [];
|
|
5906
5931
|
}
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5932
|
+
// Separate crypto and equity positions — crypto cannot use SIP quotes or time_in_force="day"
|
|
5933
|
+
const equityPositions = allPositions.filter((p) => !isCryptoSymbol(p.symbol));
|
|
5934
|
+
const cryptoPositions = allPositions.filter((p) => isCryptoSymbol(p.symbol));
|
|
5935
|
+
getLogger().info(`Found ${allPositions.length} positions to close (${equityPositions.length} equity, ${cryptoPositions.length} crypto)`, { account: auth.adapticAccountId || "direct" });
|
|
5936
|
+
// Close crypto positions via direct DELETE (market order) — no SIP quotes needed
|
|
5937
|
+
for (const position of cryptoPositions) {
|
|
5938
|
+
try {
|
|
5939
|
+
const { APIKey, APISecret, type } = await validateAuth(auth);
|
|
5940
|
+
const apiBaseUrl = getTradingApiUrl(type);
|
|
5941
|
+
const url = `${apiBaseUrl}/positions/${encodeURIComponent(position.symbol)}`;
|
|
5942
|
+
const response = await fetch(url, {
|
|
5943
|
+
method: "DELETE",
|
|
5944
|
+
headers: {
|
|
5945
|
+
"APCA-API-KEY-ID": APIKey,
|
|
5946
|
+
"APCA-API-SECRET-KEY": APISecret,
|
|
5947
|
+
},
|
|
5948
|
+
});
|
|
5949
|
+
if (response.ok) {
|
|
5950
|
+
getLogger().info(`Closed crypto position ${position.symbol} via market order`, {
|
|
5951
|
+
account: auth.adapticAccountId || "direct",
|
|
5952
|
+
symbol: position.symbol,
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
5955
|
+
else {
|
|
5956
|
+
const errorText = await response.text();
|
|
5957
|
+
getLogger().warn(`Failed to close crypto position ${position.symbol}: ${response.status} ${errorText}`, { account: auth.adapticAccountId || "direct", symbol: position.symbol });
|
|
5958
|
+
}
|
|
5959
|
+
}
|
|
5960
|
+
catch (cryptoError) {
|
|
5961
|
+
getLogger().warn(`Error closing crypto position ${position.symbol}: ${cryptoError instanceof Error ? cryptoError.message : String(cryptoError)}`, { account: auth.adapticAccountId || "direct", symbol: position.symbol });
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
// Close equity positions via limit orders with SIP quotes
|
|
5965
|
+
if (equityPositions.length === 0) {
|
|
5966
|
+
return [];
|
|
5967
|
+
}
|
|
5910
5968
|
// Use the passed auth for quote fetching (not hardcoded env vars)
|
|
5911
5969
|
// so multi-account setups use the correct credentials per account
|
|
5912
|
-
const symbols =
|
|
5970
|
+
const symbols = equityPositions.map((position) => position.symbol);
|
|
5913
5971
|
const quotesResponse = await getLatestQuotes$1(auth, { symbols });
|
|
5914
5972
|
const lengthOfQuotes = Object.keys(quotesResponse.quotes).length;
|
|
5915
5973
|
if (lengthOfQuotes === 0) {
|
|
5916
|
-
getLogger().error("No quotes available for positions, received 0 quotes", {
|
|
5974
|
+
getLogger().error("No quotes available for equity positions, received 0 quotes", {
|
|
5917
5975
|
account: auth.adapticAccountId || "direct",
|
|
5918
5976
|
type: "error",
|
|
5919
5977
|
});
|
|
5920
5978
|
return [];
|
|
5921
5979
|
}
|
|
5922
|
-
if (lengthOfQuotes !==
|
|
5923
|
-
getLogger().warn(`Received ${lengthOfQuotes} quotes for ${
|
|
5980
|
+
if (lengthOfQuotes !== equityPositions.length) {
|
|
5981
|
+
getLogger().warn(`Received ${lengthOfQuotes} quotes for ${equityPositions.length} equity positions, proceeding with available quotes`, {
|
|
5924
5982
|
account: auth.adapticAccountId || "direct",
|
|
5925
5983
|
type: "warn",
|
|
5926
5984
|
});
|
|
5927
|
-
// Continue with available quotes instead of aborting — some symbols may
|
|
5928
|
-
// lack quotes (halted, delisted) but other positions should still close
|
|
5929
5985
|
}
|
|
5930
|
-
for (const position of
|
|
5986
|
+
for (const position of equityPositions) {
|
|
5931
5987
|
const quote = quotesResponse.quotes[position.symbol];
|
|
5932
5988
|
if (!quote) {
|
|
5933
5989
|
getLogger().warn(`No quote available for ${position.symbol}, skipping limit order`, {
|
|
@@ -5987,31 +6043,63 @@ async function closeAllPositionsAfterHours$1(auth, params = { cancel_orders: tru
|
|
|
5987
6043
|
account: auth.adapticAccountId || "direct",
|
|
5988
6044
|
});
|
|
5989
6045
|
const { cancel_orders, slippagePercent1 = 0.1 } = params;
|
|
5990
|
-
const
|
|
5991
|
-
if (
|
|
6046
|
+
const allPositions = await fetchAllPositions(auth);
|
|
6047
|
+
if (allPositions.length === 0) {
|
|
5992
6048
|
getLogger().info("No positions to close", {
|
|
5993
6049
|
account: auth.adapticAccountId || "direct",
|
|
5994
6050
|
});
|
|
5995
6051
|
return;
|
|
5996
6052
|
}
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6053
|
+
// Separate crypto and equity positions
|
|
6054
|
+
const equityPositions = allPositions.filter((p) => !isCryptoSymbol(p.symbol));
|
|
6055
|
+
const cryptoPositions = allPositions.filter((p) => isCryptoSymbol(p.symbol));
|
|
6056
|
+
getLogger().info(`Found ${allPositions.length} positions to close after hours (${equityPositions.length} equity, ${cryptoPositions.length} crypto)`, { account: auth.adapticAccountId || "direct" });
|
|
6000
6057
|
if (cancel_orders) {
|
|
6001
6058
|
await cancelAllOrders$1(auth);
|
|
6002
6059
|
getLogger().info("Cancelled all open orders", {
|
|
6003
6060
|
account: auth.adapticAccountId || "direct",
|
|
6004
6061
|
});
|
|
6005
6062
|
}
|
|
6063
|
+
// Close crypto positions via direct DELETE (market order)
|
|
6064
|
+
for (const position of cryptoPositions) {
|
|
6065
|
+
try {
|
|
6066
|
+
const { APIKey, APISecret, type } = await validateAuth(auth);
|
|
6067
|
+
const apiBaseUrl = getTradingApiUrl(type);
|
|
6068
|
+
const url = `${apiBaseUrl}/positions/${encodeURIComponent(position.symbol)}`;
|
|
6069
|
+
const response = await fetch(url, {
|
|
6070
|
+
method: "DELETE",
|
|
6071
|
+
headers: {
|
|
6072
|
+
"APCA-API-KEY-ID": APIKey,
|
|
6073
|
+
"APCA-API-SECRET-KEY": APISecret,
|
|
6074
|
+
},
|
|
6075
|
+
});
|
|
6076
|
+
if (response.ok) {
|
|
6077
|
+
getLogger().info(`Closed crypto position ${position.symbol} via market order`, {
|
|
6078
|
+
account: auth.adapticAccountId || "direct",
|
|
6079
|
+
symbol: position.symbol,
|
|
6080
|
+
});
|
|
6081
|
+
}
|
|
6082
|
+
else {
|
|
6083
|
+
const errorText = await response.text();
|
|
6084
|
+
getLogger().warn(`Failed to close crypto position ${position.symbol}: ${response.status} ${errorText}`, { account: auth.adapticAccountId || "direct", symbol: position.symbol });
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
catch (cryptoError) {
|
|
6088
|
+
getLogger().warn(`Error closing crypto position ${position.symbol}: ${cryptoError instanceof Error ? cryptoError.message : String(cryptoError)}`, { account: auth.adapticAccountId || "direct", symbol: position.symbol });
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
if (equityPositions.length === 0) {
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6006
6094
|
const alpacaAuthObj = {
|
|
6007
6095
|
type: "LIVE",
|
|
6008
6096
|
alpacaApiKey: process.env.ALPACA_API_KEY,
|
|
6009
6097
|
alpacaApiSecret: process.env.ALPACA_SECRET_KEY,
|
|
6010
6098
|
};
|
|
6011
6099
|
const alpacaAuth = alpacaAuthObj;
|
|
6012
|
-
const symbols =
|
|
6100
|
+
const symbols = equityPositions.map((position) => position.symbol);
|
|
6013
6101
|
const quotesResponse = await getLatestQuotes$1(alpacaAuth, { symbols });
|
|
6014
|
-
for (const position of
|
|
6102
|
+
for (const position of equityPositions) {
|
|
6015
6103
|
const quote = quotesResponse.quotes[position.symbol];
|
|
6016
6104
|
if (!quote) {
|
|
6017
6105
|
getLogger().warn(`No quote available for ${position.symbol}, skipping limit order`, {
|
|
@@ -6050,7 +6138,7 @@ async function closeAllPositionsAfterHours$1(auth, params = { cancel_orders: tru
|
|
|
6050
6138
|
extended_hours: true,
|
|
6051
6139
|
});
|
|
6052
6140
|
}
|
|
6053
|
-
getLogger().info(`All positions closed: ${
|
|
6141
|
+
getLogger().info(`All positions closed: ${allPositions.map((p) => p.symbol).join(", ")}`, {
|
|
6054
6142
|
account: auth.adapticAccountId || "direct",
|
|
6055
6143
|
});
|
|
6056
6144
|
}
|