@adaptic/utils 0.0.914 → 0.0.916
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 +128 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +128 -26
- package/dist/index.mjs.map +1 -1
- package/dist/types/alpaca/legacy/positions.d.ts.map +1 -1
- package/dist/types/index.d.ts +8 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/massive.d.ts +12 -1
- package/dist/types/massive.d.ts.map +1 -1
- package/dist/types/schemas/alpaca-schemas.d.ts +6 -6
- package/dist/types/schemas/massive-schemas.d.ts +71 -61
- package/dist/types/schemas/massive-schemas.d.ts.map +1 -1
- package/dist/types/types/massive-types.d.ts +59 -0
- package/dist/types/types/massive-types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5789,6 +5789,24 @@ async function closeAllPositions$1(auth, params = { cancel_orders: true, useLimi
|
|
|
5789
5789
|
account: auth.adapticAccountId || "direct",
|
|
5790
5790
|
});
|
|
5791
5791
|
if (useLimitOrders) {
|
|
5792
|
+
// Cancel all existing orders first when requested, to free up qty_available
|
|
5793
|
+
// for the limit close orders. Without this, existing trailing stops and
|
|
5794
|
+
// pending orders reduce qty_available, causing "insufficient qty" errors.
|
|
5795
|
+
if (cancel_orders) {
|
|
5796
|
+
try {
|
|
5797
|
+
await cancelAllOrders$1(auth);
|
|
5798
|
+
getLogger().info("Canceled all open orders before placing limit close orders", {
|
|
5799
|
+
account: auth.adapticAccountId || "direct",
|
|
5800
|
+
});
|
|
5801
|
+
}
|
|
5802
|
+
catch (cancelError) {
|
|
5803
|
+
getLogger().warn(`Failed to cancel orders before limit closure: ${cancelError instanceof Error ? cancelError.message : String(cancelError)}`, {
|
|
5804
|
+
account: auth.adapticAccountId || "direct",
|
|
5805
|
+
type: "warn",
|
|
5806
|
+
});
|
|
5807
|
+
// Continue with closure attempt even if cancel failed
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5792
5810
|
const positions = await fetchAllPositions(auth);
|
|
5793
5811
|
if (positions.length === 0) {
|
|
5794
5812
|
getLogger().info("No positions to close", {
|
|
@@ -5799,13 +5817,10 @@ async function closeAllPositions$1(auth, params = { cancel_orders: true, useLimi
|
|
|
5799
5817
|
getLogger().info(`Found ${positions.length} positions to close`, {
|
|
5800
5818
|
account: auth.adapticAccountId || "direct",
|
|
5801
5819
|
});
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
alpacaApiKey: process.env.ALPACA_API_KEY,
|
|
5805
|
-
alpacaApiSecret: process.env.ALPACA_API_SECRET || process.env.ALPACA_SECRET_KEY,
|
|
5806
|
-
};
|
|
5820
|
+
// Use the passed auth for quote fetching (not hardcoded env vars)
|
|
5821
|
+
// so multi-account setups use the correct credentials per account
|
|
5807
5822
|
const symbols = positions.map((position) => position.symbol);
|
|
5808
|
-
const quotesResponse = await getLatestQuotes$1(
|
|
5823
|
+
const quotesResponse = await getLatestQuotes$1(auth, { symbols });
|
|
5809
5824
|
const lengthOfQuotes = Object.keys(quotesResponse.quotes).length;
|
|
5810
5825
|
if (lengthOfQuotes === 0) {
|
|
5811
5826
|
getLogger().error("No quotes available for positions, received 0 quotes", {
|
|
@@ -5815,11 +5830,12 @@ async function closeAllPositions$1(auth, params = { cancel_orders: true, useLimi
|
|
|
5815
5830
|
return [];
|
|
5816
5831
|
}
|
|
5817
5832
|
if (lengthOfQuotes !== positions.length) {
|
|
5818
|
-
getLogger().warn(`Received ${lengthOfQuotes} quotes for ${positions.length} positions
|
|
5833
|
+
getLogger().warn(`Received ${lengthOfQuotes} quotes for ${positions.length} positions (expected ${positions.length}), proceeding with available quotes`, {
|
|
5819
5834
|
account: auth.adapticAccountId || "direct",
|
|
5820
5835
|
type: "warn",
|
|
5821
5836
|
});
|
|
5822
|
-
|
|
5837
|
+
// Continue with available quotes instead of aborting — some symbols may
|
|
5838
|
+
// lack quotes (halted, delisted) but other positions should still close
|
|
5823
5839
|
}
|
|
5824
5840
|
for (const position of positions) {
|
|
5825
5841
|
const quote = quotesResponse.quotes[position.symbol];
|
|
@@ -7161,18 +7177,29 @@ const fetchLastTrade = async (symbol, options) => {
|
|
|
7161
7177
|
}
|
|
7162
7178
|
const apiKey = options?.apiKey || MASSIVE_API_KEY;
|
|
7163
7179
|
validateMassiveApiKey$1(apiKey);
|
|
7164
|
-
const baseUrl = `https://api.massive.com/
|
|
7180
|
+
const baseUrl = `https://api.massive.com/v3/trades/${encodeURIComponent(symbol)}`;
|
|
7165
7181
|
const params = new URLSearchParams({
|
|
7166
7182
|
apiKey,
|
|
7183
|
+
limit: "1",
|
|
7184
|
+
sort: "timestamp",
|
|
7185
|
+
order: "desc",
|
|
7167
7186
|
});
|
|
7168
7187
|
return massiveLimit(async () => {
|
|
7169
7188
|
try {
|
|
7170
7189
|
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
7171
|
-
const data = await response.json();
|
|
7172
|
-
if (
|
|
7173
|
-
throw new Error(`Massive.com API error: ${data.
|
|
7190
|
+
const data = (await response.json());
|
|
7191
|
+
if ("message" in data) {
|
|
7192
|
+
throw new Error(`Massive.com API error: ${data.message}`);
|
|
7174
7193
|
}
|
|
7175
|
-
|
|
7194
|
+
if (!data.results ||
|
|
7195
|
+
!Array.isArray(data.results) ||
|
|
7196
|
+
data.results.length === 0) {
|
|
7197
|
+
throw new Error(`Massive.com API error: No trade results for ${symbol}`);
|
|
7198
|
+
}
|
|
7199
|
+
const latestTrade = data.results[0];
|
|
7200
|
+
const price = latestTrade.price;
|
|
7201
|
+
const vol = latestTrade.size;
|
|
7202
|
+
const timestamp = latestTrade.sip_timestamp;
|
|
7176
7203
|
if (typeof price !== "number" ||
|
|
7177
7204
|
typeof vol !== "number" ||
|
|
7178
7205
|
typeof timestamp !== "number") {
|
|
@@ -7205,6 +7232,77 @@ const fetchLastTrade = async (symbol, options) => {
|
|
|
7205
7232
|
}
|
|
7206
7233
|
});
|
|
7207
7234
|
};
|
|
7235
|
+
/**
|
|
7236
|
+
* Fetches the latest NBBO quote for a given stock ticker using the Massive v3 API.
|
|
7237
|
+
* Returns processed spread information including bid, ask, spread, and mid price.
|
|
7238
|
+
*
|
|
7239
|
+
* @param symbol - The stock ticker symbol.
|
|
7240
|
+
* @param options - Optional parameters including API key override.
|
|
7241
|
+
* @returns Processed quote data with spread metrics.
|
|
7242
|
+
*/
|
|
7243
|
+
const fetchLastQuote = async (symbol, options) => {
|
|
7244
|
+
if (!options?.apiKey && !MASSIVE_API_KEY) {
|
|
7245
|
+
throw new Error("Massive API key is missing");
|
|
7246
|
+
}
|
|
7247
|
+
const apiKey = options?.apiKey || MASSIVE_API_KEY;
|
|
7248
|
+
validateMassiveApiKey$1(apiKey);
|
|
7249
|
+
const baseUrl = `https://api.massive.com/v3/quotes/${encodeURIComponent(symbol)}`;
|
|
7250
|
+
const params = new URLSearchParams({
|
|
7251
|
+
apiKey,
|
|
7252
|
+
limit: "1",
|
|
7253
|
+
sort: "timestamp",
|
|
7254
|
+
order: "desc",
|
|
7255
|
+
});
|
|
7256
|
+
return massiveLimit(async () => {
|
|
7257
|
+
try {
|
|
7258
|
+
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
7259
|
+
const data = (await response.json());
|
|
7260
|
+
if ("message" in data) {
|
|
7261
|
+
throw new Error(`Massive.com API error: ${data.message}`);
|
|
7262
|
+
}
|
|
7263
|
+
const results = data.results;
|
|
7264
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
7265
|
+
throw new Error(`Massive.com API error: No quote results for ${symbol}`);
|
|
7266
|
+
}
|
|
7267
|
+
const quote = results[0];
|
|
7268
|
+
if (typeof quote.bid_price !== "number" ||
|
|
7269
|
+
typeof quote.ask_price !== "number") {
|
|
7270
|
+
throw new Error("Invalid quote data received from Massive.com API");
|
|
7271
|
+
}
|
|
7272
|
+
const midPrice = (quote.bid_price + quote.ask_price) / 2;
|
|
7273
|
+
const spread = quote.ask_price - quote.bid_price;
|
|
7274
|
+
return {
|
|
7275
|
+
bid: quote.bid_price,
|
|
7276
|
+
ask: quote.ask_price,
|
|
7277
|
+
spread,
|
|
7278
|
+
spreadPercent: midPrice > 0 ? (spread / midPrice) * 100 : 0,
|
|
7279
|
+
midPrice,
|
|
7280
|
+
bidSize: quote.bid_size,
|
|
7281
|
+
askSize: quote.ask_size,
|
|
7282
|
+
time: new Date(Math.floor(quote.sip_timestamp / 1000000)),
|
|
7283
|
+
};
|
|
7284
|
+
}
|
|
7285
|
+
catch (error) {
|
|
7286
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
7287
|
+
const contextualMessage = `Error fetching last quote for ${symbol}`;
|
|
7288
|
+
getLogger().error(`${contextualMessage}: ${errorMessage}`, {
|
|
7289
|
+
symbol,
|
|
7290
|
+
errorType: error instanceof Error && error.message.includes("AUTH_ERROR")
|
|
7291
|
+
? "AUTH_ERROR"
|
|
7292
|
+
: error instanceof Error && error.message.includes("RATE_LIMIT")
|
|
7293
|
+
? "RATE_LIMIT"
|
|
7294
|
+
: error instanceof Error &&
|
|
7295
|
+
error.message.includes("NETWORK_ERROR")
|
|
7296
|
+
? "NETWORK_ERROR"
|
|
7297
|
+
: "UNKNOWN",
|
|
7298
|
+
url: hideApiKeyFromurl(`${baseUrl}?${params.toString()}`),
|
|
7299
|
+
source: "MassiveAPI.fetchLastQuote",
|
|
7300
|
+
timestamp: new Date().toISOString(),
|
|
7301
|
+
});
|
|
7302
|
+
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
7303
|
+
}
|
|
7304
|
+
});
|
|
7305
|
+
};
|
|
7208
7306
|
// use Massive for all price data fetching
|
|
7209
7307
|
/**
|
|
7210
7308
|
* Fetches price data for a given stock ticker.
|
|
@@ -66228,22 +66326,25 @@ const MassiveTradesResponseSchema = objectType({
|
|
|
66228
66326
|
results: arrayType(MassiveTradeSchema),
|
|
66229
66327
|
});
|
|
66230
66328
|
// ===== Last Trade Schemas =====
|
|
66231
|
-
/** Schema for Massive last trade response */
|
|
66329
|
+
/** Schema for Massive last trade response (v3 format - returns array of trades) */
|
|
66232
66330
|
const MassiveLastTradeResponseSchema = objectType({
|
|
66233
66331
|
status: stringType(),
|
|
66234
66332
|
request_id: stringType(),
|
|
66235
|
-
results: objectType({
|
|
66236
|
-
|
|
66237
|
-
|
|
66238
|
-
|
|
66239
|
-
|
|
66240
|
-
|
|
66241
|
-
|
|
66242
|
-
|
|
66243
|
-
|
|
66244
|
-
|
|
66245
|
-
|
|
66246
|
-
|
|
66333
|
+
results: arrayType(objectType({
|
|
66334
|
+
conditions: arrayType(numberType()).optional(),
|
|
66335
|
+
correction: numberType().optional(),
|
|
66336
|
+
exchange: numberType(),
|
|
66337
|
+
id: stringType(),
|
|
66338
|
+
participant_timestamp: numberType(),
|
|
66339
|
+
price: numberType(),
|
|
66340
|
+
sequence_number: numberType(),
|
|
66341
|
+
sip_timestamp: numberType(),
|
|
66342
|
+
size: numberType(),
|
|
66343
|
+
tape: numberType().optional(),
|
|
66344
|
+
trf_id: numberType().optional(),
|
|
66345
|
+
trf_timestamp: numberType().optional(),
|
|
66346
|
+
}))
|
|
66347
|
+
.min(1),
|
|
66247
66348
|
});
|
|
66248
66349
|
// ===== Aggregates (Bars) Schemas =====
|
|
66249
66350
|
/** Schema for Massive aggregates (bars) response */
|
|
@@ -67419,6 +67520,7 @@ const adaptic = {
|
|
|
67419
67520
|
fetchTickerInfo: fetchTickerInfo,
|
|
67420
67521
|
fetchGroupedDaily: fetchGroupedDaily,
|
|
67421
67522
|
fetchLastTrade: fetchLastTrade,
|
|
67523
|
+
fetchLastQuote: fetchLastQuote,
|
|
67422
67524
|
fetchTrades: fetchTrades,
|
|
67423
67525
|
fetchPrices: fetchPrices,
|
|
67424
67526
|
analyseMassivePriceData: analyseMassivePriceData,
|