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