fixed up exchanges US
This commit is contained in:
parent
5ca8fafe7e
commit
f69181a8bc
6 changed files with 121 additions and 22 deletions
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * from './config';
|
export * from './config';
|
||||||
|
export * from './utils';
|
||||||
|
|
|
||||||
15
apps/stock/data-ingestion/src/handlers/eod/shared/utils.ts
Normal file
15
apps/stock/data-ingestion/src/handlers/eod/shared/utils.ts
Normal 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}`;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue