Support single-token Alpaca auth
This commit is contained in:
parent
dd32be7717
commit
5025de78b9
8 changed files with 63 additions and 20 deletions
|
|
@ -6,6 +6,7 @@ import WebSocket from "ws";
|
|||
export type AlpacaEquitiesFeed = "iex" | "sip";
|
||||
|
||||
export type AlpacaEquitiesAdapterConfig = {
|
||||
apiKey: string;
|
||||
keyId: string;
|
||||
secretKey: string;
|
||||
restUrl: string;
|
||||
|
|
@ -63,10 +64,18 @@ const normalizeSymbols = (symbols: string[]): string[] => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const buildHeaders = (config: AlpacaEquitiesAdapterConfig): Record<string, string> => ({
|
||||
"APCA-API-KEY-ID": config.keyId,
|
||||
"APCA-API-SECRET-KEY": config.secretKey
|
||||
});
|
||||
const buildHeaders = (config: AlpacaEquitiesAdapterConfig): Record<string, string> => {
|
||||
if (config.apiKey) {
|
||||
return {
|
||||
Authorization: `Bearer ${config.apiKey}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"APCA-API-KEY-ID": config.keyId,
|
||||
"APCA-API-SECRET-KEY": config.secretKey
|
||||
};
|
||||
};
|
||||
|
||||
const parseTimestamp = (value: string): number => {
|
||||
const parsed = Date.parse(value);
|
||||
|
|
@ -184,8 +193,10 @@ export const createAlpacaEquitiesAdapter = (
|
|||
return {
|
||||
name: "alpaca",
|
||||
start: async (handlers: EquityIngestHandlers) => {
|
||||
if (!config.keyId || !config.secretKey) {
|
||||
throw new Error("Alpaca equities adapter requires ALPACA_KEY_ID and ALPACA_SECRET_KEY.");
|
||||
if (!config.apiKey && (!config.keyId || !config.secretKey)) {
|
||||
throw new Error(
|
||||
"Alpaca equities adapter requires ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY."
|
||||
);
|
||||
}
|
||||
|
||||
const symbols = normalizeSymbols(config.symbols);
|
||||
|
|
@ -195,7 +206,9 @@ export const createAlpacaEquitiesAdapter = (
|
|||
|
||||
const exchangeNameMap = await fetchExchangeMeta(config);
|
||||
const wsUrl = buildWsUrl(config.wsBaseUrl, config.feed);
|
||||
const ws = new WebSocket(wsUrl);
|
||||
const ws = new WebSocket(wsUrl, {
|
||||
headers: buildHeaders(config)
|
||||
});
|
||||
|
||||
let seq = 0;
|
||||
let stopped = false;
|
||||
|
|
@ -205,8 +218,8 @@ export const createAlpacaEquitiesAdapter = (
|
|||
ws.send(
|
||||
JSON.stringify({
|
||||
action: "auth",
|
||||
key: config.keyId,
|
||||
secret: config.secretKey
|
||||
key: config.apiKey || config.keyId,
|
||||
secret: config.apiKey ? "" : config.secretKey
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const envSchema = z.object({
|
|||
SYNTHETIC_EQUITIES_MODE: z.string().default(""),
|
||||
|
||||
// Alpaca (equities)
|
||||
ALPACA_API_KEY: z.string().default(""),
|
||||
ALPACA_KEY_ID: z.string().default(""),
|
||||
ALPACA_SECRET_KEY: z.string().default(""),
|
||||
ALPACA_REST_URL: z.string().default("https://data.alpaca.markets"),
|
||||
|
|
@ -167,7 +168,17 @@ const selectAdapter = (name: string): EquityIngestAdapter => {
|
|||
}
|
||||
|
||||
if (name === "alpaca") {
|
||||
const hasApiKey = Boolean(env.ALPACA_API_KEY);
|
||||
const hasKeyPair = Boolean(env.ALPACA_KEY_ID && env.ALPACA_SECRET_KEY);
|
||||
if (!hasApiKey && !hasKeyPair) {
|
||||
logger.warn("alpaca credentials missing; set ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY");
|
||||
throw new Error(
|
||||
"ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY are required for the alpaca adapter."
|
||||
);
|
||||
}
|
||||
|
||||
return createAlpacaEquitiesAdapter({
|
||||
apiKey: env.ALPACA_API_KEY,
|
||||
keyId: env.ALPACA_KEY_ID,
|
||||
secretKey: env.ALPACA_SECRET_KEY,
|
||||
restUrl: env.ALPACA_REST_URL,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import WebSocket from "ws";
|
|||
type AlpacaFeed = "indicative" | "opra";
|
||||
|
||||
type AlpacaOptionsAdapterConfig = {
|
||||
apiKey: string;
|
||||
keyId: string;
|
||||
secretKey: string;
|
||||
restUrl: string;
|
||||
|
|
@ -148,10 +149,18 @@ const normalizeUnderlyings = (value: string[]): string[] => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const buildHeaders = (config: AlpacaOptionsAdapterConfig): Record<string, string> => ({
|
||||
"APCA-API-KEY-ID": config.keyId,
|
||||
"APCA-API-SECRET-KEY": config.secretKey
|
||||
});
|
||||
const buildHeaders = (config: AlpacaOptionsAdapterConfig): Record<string, string> => {
|
||||
if (config.apiKey) {
|
||||
return {
|
||||
Authorization: `Bearer ${config.apiKey}`
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"APCA-API-KEY-ID": config.keyId,
|
||||
"APCA-API-SECRET-KEY": config.secretKey
|
||||
};
|
||||
};
|
||||
|
||||
const fetchJson = async <T>(
|
||||
url: URL,
|
||||
|
|
@ -398,8 +407,8 @@ export const createAlpacaOptionsAdapter = (
|
|||
return {
|
||||
name: "alpaca",
|
||||
start: async (handlers: OptionIngestHandlers) => {
|
||||
if (!config.keyId || !config.secretKey) {
|
||||
throw new Error("Alpaca adapter requires ALPACA_KEY_ID and ALPACA_SECRET_KEY.");
|
||||
if (!config.apiKey && (!config.keyId || !config.secretKey)) {
|
||||
throw new Error("Alpaca adapter requires ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY.");
|
||||
}
|
||||
|
||||
const underlyings = normalizeUnderlyings(config.underlyings);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const envSchema = z.object({
|
|||
CLICKHOUSE_URL: z.string().default("http://127.0.0.1:8123"),
|
||||
CLICKHOUSE_DATABASE: z.string().default("default"),
|
||||
OPTIONS_INGEST_ADAPTER: z.string().min(1).default("synthetic"),
|
||||
ALPACA_API_KEY: z.string().default(""),
|
||||
ALPACA_KEY_ID: z.string().default(""),
|
||||
ALPACA_SECRET_KEY: z.string().default(""),
|
||||
ALPACA_REST_URL: z.string().default("https://data.alpaca.markets"),
|
||||
|
|
@ -229,14 +230,17 @@ const selectAdapter = (name: string): OptionIngestAdapter => {
|
|||
}
|
||||
|
||||
if (name === "alpaca") {
|
||||
if (!env.ALPACA_KEY_ID || !env.ALPACA_SECRET_KEY) {
|
||||
logger.warn("alpaca credentials missing; set ALPACA_KEY_ID and ALPACA_SECRET_KEY");
|
||||
throw new Error("ALPACA_KEY_ID and ALPACA_SECRET_KEY are required for the alpaca adapter.");
|
||||
const hasApiKey = Boolean(env.ALPACA_API_KEY);
|
||||
const hasKeyPair = Boolean(env.ALPACA_KEY_ID && env.ALPACA_SECRET_KEY);
|
||||
if (!hasApiKey && !hasKeyPair) {
|
||||
logger.warn("alpaca credentials missing; set ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY");
|
||||
throw new Error("ALPACA_API_KEY or ALPACA_KEY_ID and ALPACA_SECRET_KEY are required for the alpaca adapter.");
|
||||
}
|
||||
|
||||
const underlyings = env.ALPACA_UNDERLYINGS.split(",").map((symbol) => symbol.trim());
|
||||
|
||||
return createAlpacaOptionsAdapter({
|
||||
apiKey: env.ALPACA_API_KEY,
|
||||
keyId: env.ALPACA_KEY_ID,
|
||||
secretKey: env.ALPACA_SECRET_KEY,
|
||||
restUrl: env.ALPACA_REST_URL,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue