@adaptic/utils 0.0.947 → 0.0.948
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 +80 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +80 -65
- package/dist/index.mjs.map +1 -1
- package/dist/types/alpaca/legacy/positions.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -5835,75 +5835,90 @@ async function closePosition$1(auth, symbolOrAssetId, params) {
|
|
|
5835
5835
|
// Crypto positions cannot use limit orders with SIP quotes or time_in_force="day".
|
|
5836
5836
|
// Use direct DELETE (market order) for crypto regardless of useLimitOrder flag.
|
|
5837
5837
|
if (useLimitOrder && !isCryptoSymbol(symbolOrAssetId)) {
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5838
|
+
// Attempt limit order closure; if quotes are unavailable (after-hours, IEX gaps),
|
|
5839
|
+
// fall back to market order (DELETE) so the position still gets closed.
|
|
5840
|
+
try {
|
|
5841
|
+
const { position } = await fetchPosition(auth, symbolOrAssetId);
|
|
5842
|
+
if (!position) {
|
|
5843
|
+
throw new Error(`Position not found for ${symbolOrAssetId}`);
|
|
5844
|
+
}
|
|
5845
|
+
// Use the passed auth for quote fetching so multi-account setups
|
|
5846
|
+
// use the correct credentials per account
|
|
5847
|
+
const quotesResponse = await getLatestQuotes$1(auth, {
|
|
5848
|
+
symbols: [symbolOrAssetId],
|
|
5849
|
+
});
|
|
5850
|
+
const quote = quotesResponse.quotes[symbolOrAssetId];
|
|
5851
|
+
if (!quote) {
|
|
5852
|
+
throw new Error(`No quote available for ${symbolOrAssetId}`);
|
|
5853
|
+
}
|
|
5854
|
+
let qty = Math.abs(parseFloat(position.qty));
|
|
5855
|
+
if (params?.qty !== undefined) {
|
|
5856
|
+
qty = params.qty;
|
|
5857
|
+
}
|
|
5858
|
+
else if (params?.percentage !== undefined) {
|
|
5859
|
+
qty = Math.abs(parseFloat(position.qty)) * (params.percentage / 100);
|
|
5860
|
+
}
|
|
5861
|
+
const side = position.side === "long" ? "sell" : "buy";
|
|
5862
|
+
const positionIntent = side === "sell" ? "sell_to_close" : "buy_to_close";
|
|
5863
|
+
const currentPrice = side === "sell" ? quote.bp : quote.ap;
|
|
5864
|
+
if (!currentPrice) {
|
|
5865
|
+
throw new Error(`No valid price available for ${symbolOrAssetId}`);
|
|
5866
|
+
}
|
|
5867
|
+
const limitSlippage = slippagePercent1 / 100;
|
|
5868
|
+
const limitPrice = side === "sell"
|
|
5869
|
+
? roundPriceForAlpaca$5(currentPrice * (1 - limitSlippage))
|
|
5870
|
+
: roundPriceForAlpaca$5(currentPrice * (1 + limitSlippage));
|
|
5871
|
+
getLogger().info(`Creating limit order to close ${symbolOrAssetId} position: ${side} ${qty} shares at ${limitPrice.toFixed(2)}`, {
|
|
5872
|
+
account: auth.adapticAccountId || "direct",
|
|
5873
|
+
symbol: symbolOrAssetId,
|
|
5874
|
+
});
|
|
5875
|
+
return await createLimitOrder(auth, {
|
|
5876
|
+
symbol: symbolOrAssetId,
|
|
5877
|
+
qty,
|
|
5878
|
+
side,
|
|
5879
|
+
limitPrice,
|
|
5880
|
+
position_intent: positionIntent,
|
|
5881
|
+
extended_hours: extendedHours,
|
|
5882
|
+
});
|
|
5857
5883
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5884
|
+
catch (limitOrderError) {
|
|
5885
|
+
// Quote unavailable or invalid price — fall back to market order (DELETE)
|
|
5886
|
+
// so the position still gets closed rather than leaving it open
|
|
5887
|
+
const errMsg = limitOrderError instanceof Error ? limitOrderError.message : String(limitOrderError);
|
|
5888
|
+
getLogger().warn(`Limit order closure failed for ${symbolOrAssetId} (${errMsg}), falling back to market order`, {
|
|
5889
|
+
account: auth.adapticAccountId || "direct",
|
|
5890
|
+
symbol: symbolOrAssetId,
|
|
5891
|
+
type: "warn",
|
|
5892
|
+
});
|
|
5893
|
+
// Fall through to the DELETE (market order) path below
|
|
5863
5894
|
}
|
|
5864
|
-
const limitSlippage = slippagePercent1 / 100;
|
|
5865
|
-
const limitPrice = side === "sell"
|
|
5866
|
-
? roundPriceForAlpaca$5(currentPrice * (1 - limitSlippage))
|
|
5867
|
-
: roundPriceForAlpaca$5(currentPrice * (1 + limitSlippage));
|
|
5868
|
-
getLogger().info(`Creating limit order to close ${symbolOrAssetId} position: ${side} ${qty} shares at ${limitPrice.toFixed(2)}`, {
|
|
5869
|
-
account: auth.adapticAccountId || "direct",
|
|
5870
|
-
symbol: symbolOrAssetId,
|
|
5871
|
-
});
|
|
5872
|
-
return await createLimitOrder(auth, {
|
|
5873
|
-
symbol: symbolOrAssetId,
|
|
5874
|
-
qty,
|
|
5875
|
-
side,
|
|
5876
|
-
limitPrice,
|
|
5877
|
-
position_intent: positionIntent,
|
|
5878
|
-
extended_hours: extendedHours,
|
|
5879
|
-
});
|
|
5880
5895
|
}
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
}
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
if (params?.percentage !== undefined) {
|
|
5890
|
-
queryParams.append("percentage", params.percentage.toString());
|
|
5891
|
-
}
|
|
5892
|
-
const queryString = queryParams.toString();
|
|
5893
|
-
const url = `${apiBaseUrl}/positions/${encodeURIComponent(symbolOrAssetId)}${queryString ? `?${queryString}` : ""}`;
|
|
5894
|
-
const response = await fetch(url, {
|
|
5895
|
-
method: "DELETE",
|
|
5896
|
-
headers: {
|
|
5897
|
-
"APCA-API-KEY-ID": APIKey,
|
|
5898
|
-
"APCA-API-SECRET-KEY": APISecret,
|
|
5899
|
-
},
|
|
5900
|
-
});
|
|
5901
|
-
if (!response.ok) {
|
|
5902
|
-
const errorText = await response.text();
|
|
5903
|
-
throw new Error(`Failed to close position: ${response.status} ${response.statusText} ${errorText}`);
|
|
5904
|
-
}
|
|
5905
|
-
return (await response.json());
|
|
5896
|
+
// Market order (DELETE) path — used when limit orders are not requested,
|
|
5897
|
+
// for crypto symbols, or as a fallback when limit order quotes are unavailable
|
|
5898
|
+
if (isCryptoSymbol(symbolOrAssetId)) {
|
|
5899
|
+
getLogger().info(`Closing crypto position ${symbolOrAssetId} via market order (DELETE endpoint)`, { account: auth.adapticAccountId || "direct", symbol: symbolOrAssetId });
|
|
5900
|
+
}
|
|
5901
|
+
const queryParams = new URLSearchParams();
|
|
5902
|
+
if (params?.qty !== undefined) {
|
|
5903
|
+
queryParams.append("qty", params.qty.toString());
|
|
5906
5904
|
}
|
|
5905
|
+
if (params?.percentage !== undefined) {
|
|
5906
|
+
queryParams.append("percentage", params.percentage.toString());
|
|
5907
|
+
}
|
|
5908
|
+
const queryString = queryParams.toString();
|
|
5909
|
+
const url = `${apiBaseUrl}/positions/${encodeURIComponent(symbolOrAssetId)}${queryString ? `?${queryString}` : ""}`;
|
|
5910
|
+
const response = await fetch(url, {
|
|
5911
|
+
method: "DELETE",
|
|
5912
|
+
headers: {
|
|
5913
|
+
"APCA-API-KEY-ID": APIKey,
|
|
5914
|
+
"APCA-API-SECRET-KEY": APISecret,
|
|
5915
|
+
},
|
|
5916
|
+
});
|
|
5917
|
+
if (!response.ok) {
|
|
5918
|
+
const errorText = await response.text();
|
|
5919
|
+
throw new Error(`Failed to close position: ${response.status} ${response.statusText} ${errorText}`);
|
|
5920
|
+
}
|
|
5921
|
+
return (await response.json());
|
|
5907
5922
|
}
|
|
5908
5923
|
catch (error) {
|
|
5909
5924
|
getLogger().error("Error in closePosition:", error);
|