fixed up exchanges US

This commit is contained in:
Boki 2025-07-07 00:10:51 -04:00
parent 5ca8fafe7e
commit f69181a8bc
6 changed files with 121 additions and 22 deletions

View file

@ -6,6 +6,7 @@ interface FetchCorporateActionsInput {
symbol: string; symbol: string;
exchange: string; exchange: string;
actionType: 'dividends' | 'splits'; actionType: 'dividends' | 'splits';
country?: string;
} }
export async function scheduleFetchCorporateActions( export async function scheduleFetchCorporateActions(
@ -58,7 +59,8 @@ export async function scheduleFetchCorporateActions(
await this.scheduleOperation('fetch-corporate-actions', { await this.scheduleOperation('fetch-corporate-actions', {
symbol: symbol.Code, symbol: symbol.Code,
exchange: symbol.Exchange, exchange: symbol.Exchange,
actionType: 'dividends' actionType: 'dividends',
country: symbol.Country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -73,7 +75,8 @@ export async function scheduleFetchCorporateActions(
await this.scheduleOperation('fetch-corporate-actions', { await this.scheduleOperation('fetch-corporate-actions', {
symbol: symbol.Code, symbol: symbol.Code,
exchange: symbol.Exchange, exchange: symbol.Exchange,
actionType: 'splits' actionType: 'splits',
country: symbol.Country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -102,7 +105,7 @@ export async function fetchCorporateActions(
input: FetchCorporateActionsInput input: FetchCorporateActionsInput
): Promise<{ success: boolean; recordsCount: number }> { ): Promise<{ success: boolean; recordsCount: number }> {
const logger = this.logger; const logger = this.logger;
const { symbol, exchange, actionType } = input; const { symbol, exchange, actionType, country } = input;
try { try {
logger.info(`Fetching ${actionType} for ${symbol}.${exchange}`); logger.info(`Fetching ${actionType} for ${symbol}.${exchange}`);
@ -113,9 +116,26 @@ export async function fetchCorporateActions(
throw new Error('EOD API key not configured'); throw new Error('EOD API key not configured');
} }
// Get country if not provided
let symbolCountry = country;
if (!symbolCountry) {
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
Code: symbol,
Exchange: exchange
});
if (!symbolDoc) {
throw new Error(`Symbol ${symbol}.${exchange} not found in database`);
}
symbolCountry = symbolDoc.Country;
}
// Build URL based on action type // Build URL based on action type
// For US symbols (Country: "USA"), use :US suffix instead of specific exchange code
const exchangeSuffix = symbolCountry === 'USA' ? 'US' : exchange;
const endpoint = actionType === 'dividends' ? 'div' : 'splits'; const endpoint = actionType === 'dividends' ? 'div' : 'splits';
const url = new URL(`https://eodhd.com/api/${endpoint}/${symbol}.${exchange}`); const url = new URL(`https://eodhd.com/api/${endpoint}/${symbol}.${exchangeSuffix}`);
url.searchParams.append('api_token', apiKey); url.searchParams.append('api_token', apiKey);
url.searchParams.append('fmt', 'json'); url.searchParams.append('fmt', 'json');

View file

@ -3,12 +3,13 @@ import type { DataIngestionServices } from '../../../types';
import { EOD_CONFIG } from '../shared'; import { EOD_CONFIG } from '../shared';
interface BulkFundamentalsInput { interface BulkFundamentalsInput {
symbols: Array<{ symbol: string; exchange: string }>; symbols: Array<{ symbol: string; exchange: string; country?: string }>;
} }
interface FetchSingleFundamentalsInput { interface FetchSingleFundamentalsInput {
symbol: string; symbol: string;
exchange: string; exchange: string;
country?: string;
} }
export async function scheduleFetchFundamentals( export async function scheduleFetchFundamentals(
@ -68,7 +69,8 @@ export async function scheduleFetchFundamentals(
const etf = etfs[i]; const etf = etfs[i];
await this.scheduleOperation('fetch-single-fundamentals', { await this.scheduleOperation('fetch-single-fundamentals', {
symbol: etf.Code, symbol: etf.Code,
exchange: etf.Exchange exchange: etf.Exchange,
country: etf.Country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -90,10 +92,11 @@ export async function scheduleFetchFundamentals(
for (let i = 0; i < nonEtfs.length; i += batchSize) { for (let i = 0; i < nonEtfs.length; i += batchSize) {
const batch = nonEtfs.slice(i, i + batchSize); const batch = nonEtfs.slice(i, i + batchSize);
// Convert to array of {symbol, exchange} objects // Convert to array of {symbol, exchange, country} objects
const symbolBatch = batch.map(s => ({ const symbolBatch = batch.map(s => ({
symbol: s.Code, symbol: s.Code,
exchange: s.Exchange exchange: s.Exchange,
country: s.Country
})); }));
await this.scheduleOperation('fetch-bulk-fundamentals', { await this.scheduleOperation('fetch-bulk-fundamentals', {
@ -139,12 +142,14 @@ export async function fetchBulkFundamentals(
throw new Error('EOD API key not configured'); throw new Error('EOD API key not configured');
} }
// Group symbols by exchange for the API call // Group symbols by actual exchange for API endpoint, but use country for symbol suffix
const exchangeGroups = symbols.reduce((acc, { symbol, exchange }) => { const exchangeGroups = symbols.reduce((acc, { symbol, exchange, country }) => {
if (!acc[exchange]) { if (!acc[exchange]) {
acc[exchange] = []; acc[exchange] = [];
} }
acc[exchange].push(`${symbol}.${exchange}`); // For US symbols (Country: "USA"), use :US suffix
const exchangeSuffix = country === 'USA' ? 'US' : exchange;
acc[exchange].push(`${symbol}.${exchangeSuffix}`);
return acc; return acc;
}, {} as Record<string, string[]>); }, {} as Record<string, string[]>);
@ -250,11 +255,25 @@ export async function fetchSingleFundamentals(
input: FetchSingleFundamentalsInput input: FetchSingleFundamentalsInput
): Promise<{ success: boolean; saved: boolean }> { ): Promise<{ success: boolean; saved: boolean }> {
const logger = this.logger; const logger = this.logger;
const { symbol, exchange } = input; const { symbol, exchange, country } = input;
try { try {
logger.info(`Fetching single fundamentals for ${symbol}.${exchange}`); logger.info(`Fetching single fundamentals for ${symbol}.${exchange}`);
// Get country if not provided
let symbolCountry = country;
if (!symbolCountry) {
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
Code: symbol,
Exchange: exchange
});
if (!symbolDoc) {
throw new Error(`Symbol ${symbol}.${exchange} not found in database`);
}
symbolCountry = symbolDoc.Country;
}
// Get API key // Get API key
const apiKey = EOD_CONFIG.API_TOKEN; const apiKey = EOD_CONFIG.API_TOKEN;
if (!apiKey) { if (!apiKey) {
@ -262,7 +281,10 @@ export async function fetchSingleFundamentals(
} }
// Build URL for single fundamentals endpoint // Build URL for single fundamentals endpoint
const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchange}`); // For US symbols (Country: "USA"), use :US suffix instead of specific exchange code
const exchangeSuffix = symbolCountry === 'USA' ? 'US' : exchange;
const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchangeSuffix}`);
url.searchParams.append('api_token', apiKey); url.searchParams.append('api_token', apiKey);
url.searchParams.append('fmt', 'json'); url.searchParams.append('fmt', 'json');

View file

@ -8,12 +8,14 @@ interface FetchIntradayInput {
interval: '1m' | '5m' | '1h'; interval: '1m' | '5m' | '1h';
fromDate?: Date; fromDate?: Date;
toDate?: Date; toDate?: Date;
country?: string;
} }
interface CrawlIntradayInput { interface CrawlIntradayInput {
symbol: string; symbol: string;
exchange: string; exchange: string;
interval: '1m' | '5m' | '1h'; interval: '1m' | '5m' | '1h';
country?: string;
} }
interface CrawlState { interface CrawlState {
@ -86,7 +88,8 @@ export async function scheduleIntradayCrawl(
await this.scheduleOperation('crawl-intraday', { await this.scheduleOperation('crawl-intraday', {
symbol: symbol.Code, symbol: symbol.Code,
exchange: symbol.Exchange, exchange: symbol.Exchange,
interval interval,
country: symbol.Country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -116,7 +119,7 @@ export async function crawlIntraday(
input: CrawlIntradayInput input: CrawlIntradayInput
): Promise<{ success: boolean; recordsProcessed: number; finished: boolean }> { ): Promise<{ success: boolean; recordsProcessed: number; finished: boolean }> {
const logger = this.logger; const logger = this.logger;
const { symbol, exchange, interval } = input; const { symbol, exchange, interval, country } = input;
try { try {
logger.info(`Starting intraday crawl for ${symbol}.${exchange} - ${interval}`); logger.info(`Starting intraday crawl for ${symbol}.${exchange} - ${interval}`);
@ -156,7 +159,8 @@ export async function crawlIntraday(
exchange, exchange,
interval, interval,
fromDate, fromDate,
toDate toDate,
country
}); });
// Update crawl state // Update crawl state
@ -204,7 +208,8 @@ export async function crawlIntraday(
await this.scheduleOperation('crawl-intraday', { await this.scheduleOperation('crawl-intraday', {
symbol, symbol,
exchange, exchange,
interval interval,
country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -238,7 +243,7 @@ export async function fetchIntraday(
input: FetchIntradayInput input: FetchIntradayInput
): Promise<{ success: boolean; recordsSaved: number }> { ): Promise<{ success: boolean; recordsSaved: number }> {
const logger = this.logger; const logger = this.logger;
const { symbol, exchange, interval, fromDate, toDate } = input; const { symbol, exchange, interval, fromDate, toDate, country } = input;
try { try {
logger.info(`Fetching intraday data for ${symbol}.${exchange} - ${interval}`, { logger.info(`Fetching intraday data for ${symbol}.${exchange} - ${interval}`, {
@ -246,6 +251,20 @@ export async function fetchIntraday(
to: toDate?.toISOString().split('T')[0] to: toDate?.toISOString().split('T')[0]
}); });
// Get country if not provided
let symbolCountry = country;
if (!symbolCountry) {
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
Code: symbol,
Exchange: exchange
});
if (!symbolDoc) {
throw new Error(`Symbol ${symbol}.${exchange} not found in database`);
}
symbolCountry = symbolDoc.Country;
}
// Get API key // Get API key
const apiKey = EOD_CONFIG.API_TOKEN; const apiKey = EOD_CONFIG.API_TOKEN;
if (!apiKey) { if (!apiKey) {
@ -253,7 +272,10 @@ export async function fetchIntraday(
} }
// Build URL // Build URL
const url = new URL(`https://eodhd.com/api/intraday/${symbol}.${exchange}`); // For US symbols (Country: "USA"), use :US suffix instead of specific exchange code
const exchangeSuffix = symbolCountry === 'USA' ? 'US' : exchange;
const url = new URL(`https://eodhd.com/api/intraday/${symbol}.${exchangeSuffix}`);
url.searchParams.append('api_token', apiKey); url.searchParams.append('api_token', apiKey);
url.searchParams.append('fmt', 'json'); url.searchParams.append('fmt', 'json');
url.searchParams.append('interval', interval); url.searchParams.append('interval', interval);

View file

@ -5,6 +5,7 @@ import { EOD_CONFIG } from '../shared';
interface FetchPricesInput { interface FetchPricesInput {
symbol: string; symbol: string;
exchange: string; exchange: string;
country?: string; // Optional to maintain backward compatibility
} }
export async function scheduleFetchPrices( export async function scheduleFetchPrices(
@ -55,7 +56,8 @@ export async function scheduleFetchPrices(
await this.scheduleOperation('fetch-prices', { await this.scheduleOperation('fetch-prices', {
symbol: symbol.Code, symbol: symbol.Code,
exchange: symbol.Exchange exchange: symbol.Exchange,
country: symbol.Country
}, { }, {
attempts: 3, attempts: 3,
backoff: { backoff: {
@ -84,11 +86,25 @@ export async function fetchPrices(
input: FetchPricesInput input: FetchPricesInput
): Promise<{ success: boolean; priceCount: number }> { ): Promise<{ success: boolean; priceCount: number }> {
const logger = this.logger; const logger = this.logger;
const { symbol, exchange } = input; const { symbol, exchange, country } = input;
try { try {
logger.info(`Fetching prices for ${symbol}.${exchange}`); logger.info(`Fetching prices for ${symbol}.${exchange}`);
// Use provided country or fetch from database
let symbolCountry = country;
if (!symbolCountry) {
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
Code: symbol,
Exchange: exchange
});
if (!symbolDoc) {
throw new Error(`Symbol ${symbol}.${exchange} not found in database`);
}
symbolCountry = symbolDoc.Country;
}
// Get API key from config // Get API key from config
const apiKey = EOD_CONFIG.API_TOKEN; const apiKey = EOD_CONFIG.API_TOKEN;
if (!apiKey) { if (!apiKey) {
@ -96,7 +112,10 @@ export async function fetchPrices(
} }
// Build URL for EOD price data // Build URL for EOD price data
const url = new URL(`https://eodhd.com/api/eod/${symbol}.${exchange}`); // For US symbols (Country: "USA"), use :US suffix instead of specific exchange code
const exchangeSuffix = symbolCountry === 'USA' ? 'US' : exchange;
const url = new URL(`https://eodhd.com/api/eod/${symbol}.${exchangeSuffix}`);
url.searchParams.append('api_token', apiKey); url.searchParams.append('api_token', apiKey);
url.searchParams.append('fmt', 'json'); url.searchParams.append('fmt', 'json');
// Fetch price data from EOD API // Fetch price data from EOD API

View file

@ -1 +1,2 @@
export * from './config'; export * from './config';
export * from './utils';

View file

@ -0,0 +1,15 @@
/**
* Get the exchange suffix for EOD API calls based on country
* US symbols use :US suffix, others use their actual exchange code
*/
export function getEodExchangeSuffix(exchange: string, country?: string): string {
return country === 'USA' ? 'US' : exchange;
}
/**
* Build symbol.exchange format for EOD API
*/
export function buildEodSymbol(symbol: string, exchange: string, country?: string): string {
const suffix = getEodExchangeSuffix(exchange, country);
return `${symbol}.${suffix}`;
}