fixed up prices, symbols / exchanges
This commit is contained in:
parent
d8e7449605
commit
f196c5dcf4
9 changed files with 191 additions and 202 deletions
|
|
@ -1,14 +1,9 @@
|
||||||
import type { BaseHandler } from '@stock-bot/handlers';
|
|
||||||
import type { DataIngestionServices } from '../../../types';
|
|
||||||
import type { EodHandler } from '../eod.handler';
|
import type { EodHandler } from '../eod.handler';
|
||||||
import { EOD_CONFIG } from '../shared';
|
import { EOD_CONFIG } from '../shared';
|
||||||
import { getEodExchangeSuffix } from '../shared/utils';
|
|
||||||
|
|
||||||
interface FetchCorporateActionsInput {
|
interface FetchCorporateActionsInput {
|
||||||
symbol: string;
|
eodSearchCode: string;
|
||||||
exchange: string;
|
|
||||||
actionType: 'dividends' | 'splits';
|
actionType: 'dividends' | 'splits';
|
||||||
country?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scheduleFetchCorporateActions(
|
export async function scheduleFetchCorporateActions(
|
||||||
|
|
@ -57,10 +52,8 @@ export async function scheduleFetchCorporateActions(
|
||||||
const { symbol } = staleSymbolsDividends[i];
|
const { symbol } = staleSymbolsDividends[i];
|
||||||
|
|
||||||
await this.scheduleOperation('fetch-corporate-actions', {
|
await this.scheduleOperation('fetch-corporate-actions', {
|
||||||
symbol: symbol.Code,
|
eodSearchCode: symbol.eodSearchCode,
|
||||||
exchange: symbol.eodExchange || symbol.Exchange, // Use eodExchange if available
|
actionType: 'dividends'
|
||||||
actionType: 'dividends',
|
|
||||||
country: symbol.Country
|
|
||||||
}, {
|
}, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: {
|
backoff: {
|
||||||
|
|
@ -77,10 +70,8 @@ export async function scheduleFetchCorporateActions(
|
||||||
const { symbol } = staleSymbolsSplits[i];
|
const { symbol } = staleSymbolsSplits[i];
|
||||||
|
|
||||||
await this.scheduleOperation('fetch-corporate-actions', {
|
await this.scheduleOperation('fetch-corporate-actions', {
|
||||||
symbol: symbol.Code,
|
eodSearchCode: symbol.eodSearchCode,
|
||||||
exchange: symbol.eodExchange || symbol.Exchange, // Use eodExchange if available
|
actionType: 'splits'
|
||||||
actionType: 'splits',
|
|
||||||
country: symbol.Country
|
|
||||||
}, {
|
}, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: {
|
backoff: {
|
||||||
|
|
@ -109,9 +100,27 @@ 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, country } = input;
|
const { eodSearchCode, actionType } = input;
|
||||||
|
|
||||||
|
// Declare variables for catch block
|
||||||
|
let symbol: string = '';
|
||||||
|
let exchange: string = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Lookup symbol using eodSearchCode
|
||||||
|
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
||||||
|
eodSearchCode: eodSearchCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!symbolDoc) {
|
||||||
|
logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
|
||||||
|
throw new Error(`Symbol not found: ${eodSearchCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol = symbolDoc.Code;
|
||||||
|
exchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const country = symbolDoc.Country;
|
||||||
|
|
||||||
logger.info(`Fetching ${actionType} for ${symbol}.${exchange}`);
|
logger.info(`Fetching ${actionType} for ${symbol}.${exchange}`);
|
||||||
|
|
||||||
// Get API key
|
// Get API key
|
||||||
|
|
@ -120,23 +129,9 @@ 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
|
||||||
// Use utility function to handle US symbols and EUFUND special case
|
// Use utility function to handle US symbols and EUFUND special case
|
||||||
const exchangeSuffix = getEodExchangeSuffix(exchange, symbolCountry);
|
const exchangeSuffix = getEodExchangeSuffix(exchange, country);
|
||||||
|
|
||||||
const endpoint = actionType === 'dividends' ? 'div' : 'splits';
|
const endpoint = actionType === 'dividends' ? 'div' : 'splits';
|
||||||
const url = new URL(`https://eodhd.com/api/${endpoint}/${symbol}.${exchangeSuffix}`);
|
const url = new URL(`https://eodhd.com/api/${endpoint}/${symbol}.${exchangeSuffix}`);
|
||||||
|
|
@ -174,7 +169,7 @@ export async function fetchCorporateActions(
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
// Update symbol to indicate we checked but found no data
|
// Update symbol to indicate we checked but found no data
|
||||||
await this.mongodb.collection('eodSymbols').updateOne(
|
await this.mongodb.collection('eodSymbols').updateOne(
|
||||||
{ Code: symbol, Exchange: exchange },
|
{ eodSearchCode: eodSearchCode },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
[`last${actionType.charAt(0).toUpperCase() + actionType.slice(1)}Update`]: new Date(),
|
[`last${actionType.charAt(0).toUpperCase() + actionType.slice(1)}Update`]: new Date(),
|
||||||
|
|
@ -190,7 +185,7 @@ export async function fetchCorporateActions(
|
||||||
const recordsWithMetadata = data.map(record => ({
|
const recordsWithMetadata = data.map(record => ({
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
symbolExchange: `${symbol}.${exchange}`,
|
eodSearchCode,
|
||||||
...record,
|
...record,
|
||||||
actionType,
|
actionType,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
|
|
@ -200,16 +195,15 @@ export async function fetchCorporateActions(
|
||||||
// Determine collection name based on action type
|
// Determine collection name based on action type
|
||||||
const collectionName = actionType === 'dividends' ? 'eodDividends' : 'eodSplits';
|
const collectionName = actionType === 'dividends' ? 'eodDividends' : 'eodSplits';
|
||||||
|
|
||||||
// Save to MongoDB - use date and symbol as unique identifier
|
// Save to MongoDB - use date and eodSearchCode as unique identifier
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
collectionName,
|
collectionName,
|
||||||
recordsWithMetadata,
|
recordsWithMetadata,
|
||||||
['date', 'symbolExchange']
|
['date', 'eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update operation tracker based on action type
|
// Update operation tracker based on action type
|
||||||
const operationName = actionType === 'dividends' ? 'dividends_update' : 'splits_update';
|
const operationName = actionType === 'dividends' ? 'dividends_update' : 'splits_update';
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
recordCount: result.insertedCount,
|
recordCount: result.insertedCount,
|
||||||
|
|
@ -231,10 +225,9 @@ export async function fetchCorporateActions(
|
||||||
|
|
||||||
// Update operation tracker with failure
|
// Update operation tracker with failure
|
||||||
const operationName = actionType === 'dividends' ? 'dividends_update' : 'splits_update';
|
const operationName = actionType === 'dividends' ? 'dividends_update' : 'splits_update';
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, {
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
error: error.message
|
error: error instanceof Error ? error.message : String(error)
|
||||||
});
|
});
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,11 @@
|
||||||
import type { BaseHandler } from '@stock-bot/handlers';
|
|
||||||
import type { DataIngestionServices } from '../../../types';
|
|
||||||
import type { EodHandler } from '../eod.handler';
|
import type { EodHandler } from '../eod.handler';
|
||||||
import { EOD_CONFIG } from '../shared';
|
import { EOD_CONFIG } from '../shared';
|
||||||
import { getEodExchangeSuffix } from '../shared/utils';
|
|
||||||
|
|
||||||
interface BulkFundamentalsInput {
|
interface BulkFundamentalsInput {
|
||||||
symbols: Array<{ symbol: string; exchange: string; country?: string }>;
|
eodSearchCodes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FetchSingleFundamentalsInput {
|
interface FetchSingleFundamentalsInput {
|
||||||
symbol: string;
|
eodSearchCode: string;
|
||||||
exchange: string;
|
|
||||||
country?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scheduleFetchFundamentals(
|
export async function scheduleFetchFundamentals(
|
||||||
|
|
@ -67,9 +61,7 @@ export async function scheduleFetchFundamentals(
|
||||||
for (let i = 0; i < etfs.length; i++) {
|
for (let i = 0; i < etfs.length; i++) {
|
||||||
const { symbol: etf } = etfs[i];
|
const { symbol: etf } = etfs[i];
|
||||||
await this.scheduleOperation('fetch-single-fundamentals', {
|
await this.scheduleOperation('fetch-single-fundamentals', {
|
||||||
symbol: etf.Code,
|
eodSearchCode: etf.eodSearchCode
|
||||||
exchange: etf.eodExchange || etf.Exchange, // Use eodExchange if available
|
|
||||||
country: etf.Country
|
|
||||||
}, {
|
}, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: {
|
backoff: {
|
||||||
|
|
@ -91,15 +83,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, country} objects
|
// Convert to array of eodSearchCodes
|
||||||
const symbolBatch = batch.map(s => ({
|
const eodSearchCodes = batch.map(s => s.symbol.eodSearchCode);
|
||||||
symbol: s.symbol.Code,
|
|
||||||
exchange: s.symbol.eodExchange || s.symbol.Exchange, // Use eodExchange if available
|
|
||||||
country: s.symbol.Country
|
|
||||||
}));
|
|
||||||
|
|
||||||
await this.scheduleOperation('fetch-bulk-fundamentals', {
|
await this.scheduleOperation('fetch-bulk-fundamentals', {
|
||||||
symbols: symbolBatch
|
eodSearchCodes: eodSearchCodes
|
||||||
}, {
|
}, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: {
|
backoff: {
|
||||||
|
|
@ -110,7 +98,7 @@ export async function scheduleFetchFundamentals(
|
||||||
});
|
});
|
||||||
|
|
||||||
jobsScheduled++;
|
jobsScheduled++;
|
||||||
logger.info(`Scheduled fundamentals batch with ${symbolBatch.length} non-ETF symbols`);
|
logger.info(`Scheduled fundamentals batch with ${eodSearchCodes.length} non-ETF symbols`);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Successfully scheduled ${jobsScheduled} fundamentals fetch jobs`);
|
logger.info(`Successfully scheduled ${jobsScheduled} fundamentals fetch jobs`);
|
||||||
|
|
@ -130,10 +118,20 @@ export async function fetchBulkFundamentals(
|
||||||
input: BulkFundamentalsInput
|
input: BulkFundamentalsInput
|
||||||
): Promise<{ success: boolean; symbolsProcessed: number }> {
|
): Promise<{ success: boolean; symbolsProcessed: number }> {
|
||||||
const logger = this.logger;
|
const logger = this.logger;
|
||||||
const { symbols } = input;
|
const { eodSearchCodes } = input;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info('Fetching bulk fundamentals', { symbolCount: symbols.length });
|
logger.info('Fetching bulk fundamentals', { symbolCount: eodSearchCodes.length });
|
||||||
|
|
||||||
|
// Lookup all symbols
|
||||||
|
const symbolDocs = await this.mongodb.collection('eodSymbols').find({
|
||||||
|
eodSearchCode: { $in: eodSearchCodes }
|
||||||
|
}).toArray();
|
||||||
|
|
||||||
|
if (symbolDocs.length === 0) {
|
||||||
|
logger.error('No symbols found for provided eodSearchCodes');
|
||||||
|
return { success: true, symbolsProcessed: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
// Get API key
|
// Get API key
|
||||||
const apiKey = EOD_CONFIG.API_TOKEN;
|
const apiKey = EOD_CONFIG.API_TOKEN;
|
||||||
|
|
@ -142,7 +140,11 @@ export async function fetchBulkFundamentals(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group symbols by actual exchange for API endpoint, but use country for symbol suffix
|
// Group symbols by actual exchange for API endpoint, but use country for symbol suffix
|
||||||
const exchangeGroups = symbols.reduce((acc, { symbol, exchange, country }) => {
|
const exchangeGroups = symbolDocs.reduce((acc, symbolDoc) => {
|
||||||
|
const symbol = symbolDoc.Code;
|
||||||
|
const exchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const country = symbolDoc.Country;
|
||||||
|
|
||||||
if (!acc[exchange]) {
|
if (!acc[exchange]) {
|
||||||
acc[exchange] = [];
|
acc[exchange] = [];
|
||||||
}
|
}
|
||||||
|
|
@ -193,20 +195,34 @@ export async function fetchBulkFundamentals(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract symbol and exchange from the key
|
// Extract symbol and exchange from the key
|
||||||
const [symbol, exc] = symbolExchange.split('.');
|
const [symbol, exchangeSuffix] = symbolExchange.split('.');
|
||||||
|
|
||||||
|
// Find the original symbol doc to get the actual exchange code
|
||||||
|
const symbolDoc = symbolDocs.find(doc =>
|
||||||
|
doc.Code === symbol &&
|
||||||
|
(doc.eodExchange === exchangeSuffix || doc.Exchange === exchangeSuffix)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!symbolDoc) {
|
||||||
|
logger.warn(`Could not find symbol doc for ${symbolExchange}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualExchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const eodSearchCode = symbolDoc.eodSearchCode;
|
||||||
|
|
||||||
// Add metadata
|
// Add metadata
|
||||||
const fundamentalsWithMetadata = {
|
const fundamentalsWithMetadata = {
|
||||||
symbol,
|
symbol,
|
||||||
exchange: exc,
|
exchange: actualExchange,
|
||||||
symbolExchange,
|
eodSearchCode,
|
||||||
...fundamentals,
|
...fundamentals,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
source: 'eod'
|
source: 'eod'
|
||||||
};
|
};
|
||||||
|
|
||||||
fundamentalsToSave.push(fundamentalsWithMetadata);
|
fundamentalsToSave.push(fundamentalsWithMetadata);
|
||||||
symbolsToUpdate.push({ symbol, exchange: exc });
|
symbolsToUpdate.push({ eodSearchCode });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fundamentalsToSave.length > 0) {
|
if (fundamentalsToSave.length > 0) {
|
||||||
|
|
@ -214,14 +230,13 @@ export async function fetchBulkFundamentals(
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
'eodFundamentals',
|
'eodFundamentals',
|
||||||
fundamentalsToSave,
|
fundamentalsToSave,
|
||||||
['symbolExchange']
|
['eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Saved ${result.insertedCount} fundamentals records for ${exchange}`);
|
logger.info(`Saved ${result.insertedCount} fundamentals records for ${exchange}`);
|
||||||
|
|
||||||
// Update operation tracker for each symbol
|
// Update operation tracker for each symbol
|
||||||
const updatePromises = symbolsToUpdate.map(({ symbol, exchange }) => {
|
const updatePromises = symbolsToUpdate.map(({ eodSearchCode }) => {
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
recordCount: 1,
|
recordCount: 1,
|
||||||
|
|
@ -247,8 +262,7 @@ export async function fetchBulkFundamentals(
|
||||||
logger.error('Failed to fetch bulk fundamentals', { error });
|
logger.error('Failed to fetch bulk fundamentals', { error });
|
||||||
|
|
||||||
// Mark all symbols as failed
|
// Mark all symbols as failed
|
||||||
const failPromises = input.symbols.map(({ symbol, exchange }) => {
|
const failPromises = eodSearchCodes.map((eodSearchCode) => {
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
return this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
error: error.message
|
error: error.message
|
||||||
|
|
@ -265,25 +279,29 @@ 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, country } = input;
|
const { eodSearchCode } = input;
|
||||||
|
|
||||||
|
// Declare variables for catch block
|
||||||
|
let symbol: string = '';
|
||||||
|
let exchange: string = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(`Fetching single fundamentals for ${symbol}.${exchange}`);
|
// Lookup symbol using eodSearchCode
|
||||||
|
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
||||||
|
eodSearchCode: eodSearchCode
|
||||||
|
});
|
||||||
|
|
||||||
// Get country if not provided
|
if (!symbolDoc) {
|
||||||
let symbolCountry = country;
|
logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
|
||||||
if (!symbolCountry) {
|
throw new Error(`Symbol not found: ${eodSearchCode}`);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symbol = symbolDoc.Code;
|
||||||
|
exchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const country = symbolDoc.Country;
|
||||||
|
|
||||||
|
logger.info(`Fetching single fundamentals for ${symbol}.${exchange}`);
|
||||||
|
|
||||||
// Get API key
|
// Get API key
|
||||||
const apiKey = EOD_CONFIG.API_TOKEN;
|
const apiKey = EOD_CONFIG.API_TOKEN;
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
|
|
@ -292,7 +310,7 @@ export async function fetchSingleFundamentals(
|
||||||
|
|
||||||
// Build URL for single fundamentals endpoint
|
// Build URL for single fundamentals endpoint
|
||||||
// Use utility function to handle US symbols and EUFUND special case
|
// Use utility function to handle US symbols and EUFUND special case
|
||||||
const exchangeSuffix = getEodExchangeSuffix(exchange, symbolCountry);
|
const exchangeSuffix = getEodExchangeSuffix(exchange, country);
|
||||||
|
|
||||||
const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchangeSuffix}`);
|
const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchangeSuffix}`);
|
||||||
url.searchParams.append('api_token', apiKey);
|
url.searchParams.append('api_token', apiKey);
|
||||||
|
|
@ -317,7 +335,7 @@ export async function fetchSingleFundamentals(
|
||||||
const fundamentalsWithMetadata = {
|
const fundamentalsWithMetadata = {
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
symbolExchange: `${symbol}.${exchange}`,
|
eodSearchCode,
|
||||||
...fundamentals,
|
...fundamentals,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
source: 'eod'
|
source: 'eod'
|
||||||
|
|
@ -327,11 +345,10 @@ export async function fetchSingleFundamentals(
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
'eodFundamentals',
|
'eodFundamentals',
|
||||||
[fundamentalsWithMetadata],
|
[fundamentalsWithMetadata],
|
||||||
['symbolExchange']
|
['eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update operation tracker
|
// Update operation tracker
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
recordCount: result.insertedCount,
|
recordCount: result.insertedCount,
|
||||||
|
|
@ -352,7 +369,6 @@ export async function fetchSingleFundamentals(
|
||||||
logger.error('Failed to fetch single fundamentals', { error, symbol, exchange });
|
logger.error('Failed to fetch single fundamentals', { error, symbol, exchange });
|
||||||
|
|
||||||
// Update operation tracker with failure
|
// Update operation tracker with failure
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'fundamentals_update', {
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
error: error.message
|
error: error.message
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,15 @@ import type { EodHandler } from '../eod.handler';
|
||||||
import { EOD_CONFIG } from '../shared';
|
import { EOD_CONFIG } from '../shared';
|
||||||
|
|
||||||
interface FetchIntradayInput {
|
interface FetchIntradayInput {
|
||||||
symbol: string;
|
eodSearchCode: string;
|
||||||
exchange: string;
|
|
||||||
interval: '1m' | '5m' | '1h';
|
interval: '1m' | '5m' | '1h';
|
||||||
fromDate?: Date;
|
fromDate?: Date;
|
||||||
toDate?: Date;
|
toDate?: Date;
|
||||||
country?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CrawlIntradayInput {
|
interface CrawlIntradayInput {
|
||||||
symbol: string;
|
eodSearchCode: string;
|
||||||
exchange: string;
|
|
||||||
interval: '1m' | '5m' | '1h';
|
interval: '1m' | '5m' | '1h';
|
||||||
country?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,7 +49,7 @@ export async function scheduleIntradayCrawl(
|
||||||
}).toArray();
|
}).toArray();
|
||||||
|
|
||||||
// Add interval info to each symbol
|
// Add interval info to each symbol
|
||||||
symbolsForInterval.forEach(symbol => {
|
symbolsForInterval.forEach((symbol: any) => {
|
||||||
// Check if this interval needs processing (not finished or needs new data)
|
// Check if this interval needs processing (not finished or needs new data)
|
||||||
const operationStatus = symbol.operations?.[operationName];
|
const operationStatus = symbol.operations?.[operationName];
|
||||||
const shouldProcess = !operationStatus || !operationStatus.finished ||
|
const shouldProcess = !operationStatus || !operationStatus.finished ||
|
||||||
|
|
@ -101,10 +97,8 @@ export async function scheduleIntradayCrawl(
|
||||||
const { symbol, interval } = item;
|
const { symbol, interval } = item;
|
||||||
|
|
||||||
await this.scheduleOperation('crawl-intraday', {
|
await this.scheduleOperation('crawl-intraday', {
|
||||||
symbol: symbol.Code,
|
eodSearchCode: symbol.eodSearchCode,
|
||||||
exchange: symbol.eodExchange || symbol.Exchange, // Use eodExchange if available
|
interval
|
||||||
interval,
|
|
||||||
country: symbol.Country
|
|
||||||
}, {
|
}, {
|
||||||
priority: 5, // Initial crawl jobs get priority 5 (lower priority)
|
priority: 5, // Initial crawl jobs get priority 5 (lower priority)
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
|
|
@ -134,26 +128,31 @@ 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, country } = input;
|
const { eodSearchCode, interval } = input;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Lookup symbol using eodSearchCode
|
||||||
|
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
||||||
|
eodSearchCode: eodSearchCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!symbolDoc) {
|
||||||
|
logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
|
||||||
|
throw new Error(`Symbol not found: ${eodSearchCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbol = symbolDoc.Code;
|
||||||
|
const exchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const country = symbolDoc.Country;
|
||||||
|
|
||||||
logger.info(`Starting intraday crawl for ${symbol}.${exchange} - ${interval}`, {
|
logger.info(`Starting intraday crawl for ${symbol}.${exchange} - ${interval}`, {
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
interval,
|
interval,
|
||||||
country
|
country,
|
||||||
|
eodSearchCode
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get symbol to check if it exists
|
|
||||||
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
|
||||||
Code: symbol,
|
|
||||||
eodExchange: exchange
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!symbolDoc) {
|
|
||||||
throw new Error(`Symbol ${symbol}.${exchange} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('Found symbol document', {
|
logger.debug('Found symbol document', {
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
|
|
@ -202,12 +201,10 @@ export async function crawlIntraday(
|
||||||
|
|
||||||
// Fetch data for this batch
|
// Fetch data for this batch
|
||||||
const result = await fetchIntraday.call(this, {
|
const result = await fetchIntraday.call(this, {
|
||||||
symbol,
|
eodSearchCode,
|
||||||
exchange,
|
|
||||||
interval,
|
interval,
|
||||||
fromDate,
|
fromDate,
|
||||||
toDate,
|
toDate
|
||||||
country
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prepare update data
|
// Prepare update data
|
||||||
|
|
@ -270,7 +267,6 @@ export async function crawlIntraday(
|
||||||
finished: updateData.finished
|
finished: updateData.finished
|
||||||
});
|
});
|
||||||
|
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, updateData);
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, operationName, updateData);
|
||||||
|
|
||||||
logger.info(`Operation tracker updated for ${symbol}.${exchange} - ${interval}`);
|
logger.info(`Operation tracker updated for ${symbol}.${exchange} - ${interval}`);
|
||||||
|
|
@ -278,10 +274,8 @@ export async function crawlIntraday(
|
||||||
// If not finished, schedule next batch
|
// If not finished, schedule next batch
|
||||||
if (!updateData.finished) {
|
if (!updateData.finished) {
|
||||||
await this.scheduleOperation('crawl-intraday', {
|
await this.scheduleOperation('crawl-intraday', {
|
||||||
symbol,
|
eodSearchCode,
|
||||||
exchange,
|
interval
|
||||||
interval,
|
|
||||||
country
|
|
||||||
}, {
|
}, {
|
||||||
priority: 3, // Continuation jobs get higher priority (3) than initial jobs (5)
|
priority: 3, // Continuation jobs get higher priority (3) than initial jobs (5)
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
|
|
@ -319,9 +313,27 @@ export async function fetchIntraday(
|
||||||
input: FetchIntradayInput
|
input: FetchIntradayInput
|
||||||
): Promise<{ success: boolean; recordsSaved: number; recordsFetched: number }> {
|
): Promise<{ success: boolean; recordsSaved: number; recordsFetched: number }> {
|
||||||
const logger = this.logger;
|
const logger = this.logger;
|
||||||
const { symbol, exchange, interval, fromDate, toDate, country } = input;
|
const { eodSearchCode, interval, fromDate, toDate } = input;
|
||||||
|
|
||||||
|
// Declare variables for catch block
|
||||||
|
let symbol: string = '';
|
||||||
|
let exchange: string = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Lookup symbol using eodSearchCode
|
||||||
|
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
||||||
|
eodSearchCode: eodSearchCode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!symbolDoc) {
|
||||||
|
logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
|
||||||
|
throw new Error(`Symbol not found: ${eodSearchCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol = symbolDoc.Code;
|
||||||
|
exchange = symbolDoc.eodExchange || symbolDoc.Exchange;
|
||||||
|
const country = symbolDoc.Country;
|
||||||
|
|
||||||
logger.info(`Fetching intraday data for ${symbol}.${exchange} - ${interval}`, {
|
logger.info(`Fetching intraday data for ${symbol}.${exchange} - ${interval}`, {
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
|
|
@ -332,20 +344,6 @@ export async function fetchIntraday(
|
||||||
url: `https://eodhd.com/api/intraday/${symbol}.${exchange}`
|
url: `https://eodhd.com/api/intraday/${symbol}.${exchange}`
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get country if not provided
|
|
||||||
let symbolCountry = country;
|
|
||||||
if (!symbolCountry) {
|
|
||||||
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
|
||||||
Code: symbol,
|
|
||||||
eodExchange: 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) {
|
||||||
|
|
@ -392,7 +390,7 @@ export async function fetchIntraday(
|
||||||
const recordsWithMetadata = data.map(bar => ({
|
const recordsWithMetadata = data.map(bar => ({
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
symbolExchange: `${symbol}.${exchange}`,
|
eodSearchCode,
|
||||||
datetime: bar.datetime,
|
datetime: bar.datetime,
|
||||||
timestamp: bar.timestamp,
|
timestamp: bar.timestamp,
|
||||||
gmtoffset: bar.gmtoffset,
|
gmtoffset: bar.gmtoffset,
|
||||||
|
|
@ -404,12 +402,12 @@ export async function fetchIntraday(
|
||||||
source: 'eod'
|
source: 'eod'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Save to MongoDB - use timestamp and symbolExchange as unique identifier
|
// Save to MongoDB - use timestamp and eodSearchCode as unique identifier
|
||||||
const collectionName = `eodIntraday${interval.toUpperCase()}`;
|
const collectionName = `eodIntraday${interval.toUpperCase()}`;
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
collectionName,
|
collectionName,
|
||||||
recordsWithMetadata,
|
recordsWithMetadata,
|
||||||
['timestamp', 'symbolExchange']
|
['timestamp', 'eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Saved ${result.insertedCount} intraday records`, {
|
logger.info(`Saved ${result.insertedCount} intraday records`, {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import type { EodHandler } from '../eod.handler';
|
import type { EodHandler } from '../eod.handler';
|
||||||
import { EOD_CONFIG } from '../shared';
|
import { EOD_CONFIG } from '../shared';
|
||||||
import { getEodExchangeSuffix } from '../shared/utils';
|
|
||||||
|
|
||||||
interface FetchPricesInput {
|
interface FetchPricesInput {
|
||||||
symbol: string;
|
eodSearchCode: string;
|
||||||
exchange: string;
|
|
||||||
country?: string; // Optional to maintain backward compatibility
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scheduleFetchPrices(
|
export async function scheduleFetchPrices(
|
||||||
|
|
@ -18,7 +15,7 @@ export async function scheduleFetchPrices(
|
||||||
|
|
||||||
// Use OperationTracker to find stale symbols
|
// Use OperationTracker to find stale symbols
|
||||||
const staleSymbols = await this.operationRegistry.getStaleSymbols('eod', 'price_update', {
|
const staleSymbols = await this.operationRegistry.getStaleSymbols('eod', 'price_update', {
|
||||||
limit: 50000 // Higher limit to process all symbols
|
limit: 14000 // Higher limit to process all symbols
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!staleSymbols || staleSymbols.length === 0) {
|
if (!staleSymbols || staleSymbols.length === 0) {
|
||||||
|
|
@ -39,17 +36,20 @@ export async function scheduleFetchPrices(
|
||||||
|
|
||||||
// Schedule jobs with staggered delays
|
// Schedule jobs with staggered delays
|
||||||
for (let i = 0; i < staleSymbols.length; i++) {
|
for (let i = 0; i < staleSymbols.length; i++) {
|
||||||
const { symbol } = staleSymbols[i];
|
const staleSymbol = staleSymbols[i];
|
||||||
|
if (!staleSymbol || !staleSymbol.symbol) {
|
||||||
|
logger.warn(`Skipping invalid stale symbol at index ${i}`, { staleSymbol });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { symbol } = staleSymbol;
|
||||||
logger.debug(`Scheduling price fetch for ${symbol.Code}.${symbol.Exchange}`, {
|
logger.debug(`Scheduling price fetch for ${symbol.Code}.${symbol.Exchange}`, {
|
||||||
name: symbol.Name,
|
name: symbol.Name,
|
||||||
lastUpdate: staleSymbols[i].lastRun,
|
lastUpdate: staleSymbol.lastRun,
|
||||||
delay: i * 100
|
delay: i * 100
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.scheduleOperation('fetch-prices', {
|
await this.scheduleOperation('fetch-prices', {
|
||||||
symbol: symbol.Code,
|
eodSearchCode: symbol.eodSearchCode
|
||||||
exchange: symbol.eodExchange || symbol.Exchange, // Use eodExchange if available
|
|
||||||
country: symbol.Country
|
|
||||||
}, {
|
}, {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
backoff: {
|
backoff: {
|
||||||
|
|
@ -78,36 +78,35 @@ 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, country } = input;
|
const { eodSearchCode } = input;
|
||||||
|
|
||||||
|
// Declare variables that need to be accessible in catch block
|
||||||
|
let symbol: string = '';
|
||||||
|
let exchange: string = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(`Fetching prices for ${symbol}.${exchange}`);
|
// Lookup symbol using eodSearchCode
|
||||||
|
const symbolDoc = await this.mongodb.collection('eodSymbols').findOne({
|
||||||
|
eodSearchCode: eodSearchCode
|
||||||
|
});
|
||||||
|
|
||||||
// Use provided country or fetch from database
|
if (!symbolDoc) {
|
||||||
let symbolCountry = country;
|
logger.error(`Symbol not found for eodSearchCode: ${eodSearchCode}`);
|
||||||
if (!symbolCountry) {
|
throw new Error(`Symbol not found: ${eodSearchCode}`);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
symbol = symbolDoc.Code;
|
||||||
|
exchange = symbolDoc.Exchange;
|
||||||
|
|
||||||
|
logger.info(`Fetching prices for ${eodSearchCode}`);
|
||||||
|
|
||||||
// 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) {
|
||||||
throw new Error('EOD API key not configured');
|
throw new Error('EOD API key not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build URL for EOD price data
|
const url = new URL(`https://eodhd.com/api/eod/${eodSearchCode}`);
|
||||||
// Use utility function to handle US symbols and EUFUND special case
|
|
||||||
const exchangeSuffix = getEodExchangeSuffix(exchange, symbolCountry);
|
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -124,11 +123,11 @@ export async function fetchPrices(
|
||||||
throw new Error('Invalid response format from EOD API - expected array');
|
throw new Error('Invalid response format from EOD API - expected array');
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Fetched ${priceData.length} price records for ${symbol}.${exchange}`);
|
logger.info(`Fetched ${priceData.length} price records for ${eodSearchCode}`);
|
||||||
|
|
||||||
// Log date range of prices
|
// Log date range of prices
|
||||||
if (priceData.length > 0) {
|
if (priceData.length > 0) {
|
||||||
logger.debug(`Price data range for ${symbol}.${exchange}:`, {
|
logger.debug(`Price data range for ${eodSearchCode}:`, {
|
||||||
oldest: priceData[0].date,
|
oldest: priceData[0].date,
|
||||||
newest: priceData[priceData.length - 1].date,
|
newest: priceData[priceData.length - 1].date,
|
||||||
count: priceData.length
|
count: priceData.length
|
||||||
|
|
@ -139,7 +138,7 @@ export async function fetchPrices(
|
||||||
const pricesWithMetadata = priceData.map(price => ({
|
const pricesWithMetadata = priceData.map(price => ({
|
||||||
symbol,
|
symbol,
|
||||||
exchange,
|
exchange,
|
||||||
symbolExchange: `${symbol}.${exchange}`,
|
eodSearchCode,
|
||||||
date: price.date,
|
date: price.date,
|
||||||
open: price.open,
|
open: price.open,
|
||||||
high: price.high,
|
high: price.high,
|
||||||
|
|
@ -149,15 +148,14 @@ export async function fetchPrices(
|
||||||
volume: price.volume,
|
volume: price.volume,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Save to MongoDB - use date and symbol as unique identifier
|
// Save to MongoDB - use date and eodSearchCode as unique identifier
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
'eodPrices',
|
'eodPrices',
|
||||||
pricesWithMetadata,
|
pricesWithMetadata,
|
||||||
['date', 'symbolExchange']
|
['date', 'eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update operation tracker instead of directly updating the symbol
|
// Update operation tracker instead of directly updating the symbol
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
lastRecordDate: priceData.length > 0 ? priceData[priceData.length - 1].date : null,
|
lastRecordDate: priceData.length > 0 ? priceData[priceData.length - 1].date : null,
|
||||||
|
|
@ -168,7 +166,7 @@ export async function fetchPrices(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`Successfully saved ${result.insertedCount} price records for ${symbol}.${exchange}`);
|
logger.info(`Successfully saved ${result.insertedCount} price records for ${eodSearchCode}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|
@ -178,10 +176,9 @@ export async function fetchPrices(
|
||||||
logger.error('Failed to fetch or save prices', { error, symbol, exchange });
|
logger.error('Failed to fetch or save prices', { error, symbol, exchange });
|
||||||
|
|
||||||
// Update operation tracker with failure
|
// Update operation tracker with failure
|
||||||
const eodSearchCode = `${symbol}.${exchange}`;
|
|
||||||
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
|
await this.operationRegistry.updateOperation('eod', eodSearchCode, 'price_update', {
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
error: error.message
|
error: error instanceof Error ? error.message : String(error)
|
||||||
});
|
});
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export async function scheduleFetchSymbols(
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Found ${exchanges.length} exchanges to process`, {
|
logger.info(`Found ${exchanges.length} exchanges to process`, {
|
||||||
exchanges: exchanges.map(e => ({ code: e.Code, name: e.Name, country: e.Country }))
|
exchanges: exchanges.map((e: any) => ({ code: e.Code, name: e.Name, country: e.Country }))
|
||||||
});
|
});
|
||||||
|
|
||||||
let jobsCreated = 0;
|
let jobsCreated = 0;
|
||||||
|
|
@ -118,7 +118,7 @@ export async function fetchSymbols(
|
||||||
logger.debug(`Sample ${delisted ? 'delisted' : 'active'} symbols for ${exchangeCode}:`, {
|
logger.debug(`Sample ${delisted ? 'delisted' : 'active'} symbols for ${exchangeCode}:`, {
|
||||||
count: symbols.length,
|
count: symbols.length,
|
||||||
samples: symbols.slice(0, 5).map(s => ({
|
samples: symbols.slice(0, 5).map(s => ({
|
||||||
code: s.Code,
|
symbol: s.Code,
|
||||||
name: s.Name,
|
name: s.Name,
|
||||||
type: s.Type
|
type: s.Type
|
||||||
}))
|
}))
|
||||||
|
|
@ -128,7 +128,6 @@ export async function fetchSymbols(
|
||||||
// Add metadata to each symbol
|
// Add metadata to each symbol
|
||||||
const symbolsWithMetadata = symbols.map(symbol => ({
|
const symbolsWithMetadata = symbols.map(symbol => ({
|
||||||
...symbol,
|
...symbol,
|
||||||
Exchange: symbol.Exchange || exchangeCode, // Keep the original exchange (might be wrong)
|
|
||||||
eodExchange: exchangeCode, // Store the correct exchange code used to fetch this symbol
|
eodExchange: exchangeCode, // Store the correct exchange code used to fetch this symbol
|
||||||
eodSearchCode: `${symbol.Code}.${exchangeCode}`, // Create unique search code like AAPL.US
|
eodSearchCode: `${symbol.Code}.${exchangeCode}`, // Create unique search code like AAPL.US
|
||||||
delisted: delisted,
|
delisted: delisted,
|
||||||
|
|
@ -138,7 +137,7 @@ export async function fetchSymbols(
|
||||||
const result = await this.mongodb.batchUpsert(
|
const result = await this.mongodb.batchUpsert(
|
||||||
'eodSymbols',
|
'eodSymbols',
|
||||||
symbolsWithMetadata,
|
symbolsWithMetadata,
|
||||||
['Code', 'Exchange']
|
['eodSearchCode']
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(`Successfully saved ${result.insertedCount} ${delisted ? 'delisted' : 'active'} symbols for ${exchangeCode}`);
|
logger.info(`Successfully saved ${result.insertedCount} ${delisted ? 'delisted' : 'active'} symbols for ${exchangeCode}`);
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ export class EodHandler extends BaseHandler<DataIngestionServices> {
|
||||||
*/
|
*/
|
||||||
@Operation('schedule-intraday-crawl')
|
@Operation('schedule-intraday-crawl')
|
||||||
@ScheduledOperation('schedule-intraday-crawl', '0 3 * * *', {
|
@ScheduledOperation('schedule-intraday-crawl', '0 3 * * *', {
|
||||||
immediately: true,
|
// immediately: true,
|
||||||
})
|
})
|
||||||
@RateLimit(1) // 1 point for scheduling
|
@RateLimit(1) // 1 point for scheduling
|
||||||
scheduleIntradayCrawl = scheduleIntradayCrawl;
|
scheduleIntradayCrawl = scheduleIntradayCrawl;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './config';
|
export * from './config';
|
||||||
export * from './utils';
|
|
||||||
export * from './operation-provider';
|
export * from './operation-provider';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
/**
|
|
||||||
* Get the exchange suffix for EOD API calls based on country and exchange
|
|
||||||
* US symbols use :US suffix, except EUFUND and GBOND which always use their own codes
|
|
||||||
* Others use their actual exchange code
|
|
||||||
*/
|
|
||||||
export function getEodExchangeSuffix(exchange: string, country?: string): string {
|
|
||||||
// Special cases that always use their own exchange code
|
|
||||||
if (exchange === 'EUFUND' || exchange === 'GBOND') {
|
|
||||||
return exchange;
|
|
||||||
}
|
|
||||||
// US symbols use :US suffix
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
7
docs/serverinfo.md
Normal file
7
docs/serverinfo.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
12900k
|
||||||
|
P-cores: 48
|
||||||
|
E-cores: 39
|
||||||
|
Vcore: -0.045V adaptive
|
||||||
|
LLC: 4
|
||||||
|
SVID: Auto
|
||||||
|
XMPII 5600 with 5200 override
|
||||||
Loading…
Add table
Add a link
Reference in a new issue