yahoo-finance2
Version:
JS API for Yahoo Finance
295 lines (294 loc) • 10.7 kB
JavaScript
;
/**
* Chart module for retrieving historical price data with extensive customization options.
*
* This module provides detailed historical price, volume, and event data for financial
* instruments, with flexible date ranges, intervals, and data formats. It's the primary
* source for building charts and conducting technical analysis.
*
* @example Basic Usage
* ```typescript
* import YahooFinance from "yahoo-finance2";
* const yahooFinance = new YahooFinance();
*
* // Get last year of daily data
* const result = await yahooFinance.chart('AAPL', {
* period1: '2023-01-01',
* period2: '2024-01-01'
* });
*
* console.log(result.quotes[0]); // First day's OHLCV data
* console.log(result.meta.currency); // "USD"
* ```
*
* @example Different Time Intervals
* ```typescript
* // Intraday 5-minute data for last 5 days
* const intraday = await yahooFinance.chart('TSLA', {
* period1: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000),
* interval: '5m'
* });
*
* // Weekly data for 2 years
* const weekly = await yahooFinance.chart('NVDA', {
* period1: '2022-01-01',
* period2: '2024-01-01',
* interval: '1wk'
* });
*
* // Monthly data for maximum available period
* const monthly = await yahooFinance.chart('MSFT', {
* period1: new Date('1985-01-01'),
* interval: '1mo'
* });
* ```
*
* @example Object Format for Charting Libraries
* ```typescript
* // Get data in object format (better for some charting libraries)
* const chartData = await yahooFinance.chart('GOOGL', {
* period1: '2023-06-01',
* period2: '2023-12-01',
* return: 'object'
* });
*
* // Access data by timestamp keys
* const timestamps = chartData.timestamp;
* const prices = chartData.indicators.quote[0];
*
* timestamps.forEach((timestamp, index) => {
* console.log(`${new Date(timestamp * 1000)}: $${prices.close[index]}`);
* });
* ```
*
* @example Working with Events and Dividends
* ```typescript
* const data = await yahooFinance.chart('AAPL', {
* period1: '2020-01-01',
* period2: '2024-01-01',
* events: 'div|split|earn'
* });
*
* // Process dividends
* if (data.events?.dividends) {
* data.events.dividends.forEach(dividend => {
* console.log(`Dividend: $${dividend.amount} on ${dividend.date}`);
* });
* }
*
* // Process stock splits
* if (data.events?.splits) {
* data.events.splits.forEach(split => {
* console.log(`Split: ${split.splitRatio} on ${split.date}`);
* });
* }
* ```
*
* @example Cryptocurrency and International Markets
* ```typescript
* // Bitcoin data
* const btc = await yahooFinance.chart('BTC-USD', {
* period1: '2024-01-01',
* interval: '1h'
* });
*
* // European stocks
* const asml = await yahooFinance.chart('ASML.AS', {
* period1: '2023-01-01'
* });
*
* // Japanese stocks
* const toyota = await yahooFinance.chart('7203.T', {
* period1: '2023-01-01'
* });
* ```
*
* @example Error Handling and Edge Cases
* ```typescript
* try {
* const result = await yahooFinance.chart('INVALID', {
* period1: '2023-01-01'
* });
* } catch (error) {
* if (error.message.includes('No data found')) {
* console.log('Symbol may be delisted or invalid');
* }
* }
*
* // Handle potential empty results
* const result = await yahooFinance.chart('AAPL', {
* period1: '2025-01-01' // Future date
* });
*
* if (result.quotes.length === 0) {
* console.log('No data available for the specified period');
* }
* ```
*
* @remarks
* **Data Formats**: The module provides two return formats:
* - `array` (default): Easier to iterate over with Date objects
* - `object`: Optimized for charting libraries, with timestamp keys
*
* **Intervals**: Intraday intervals (1m, 5m, etc.) have limited historical availability
* (~60 days for 1-minute data). Daily and longer intervals have extensive history.
*
* **Market Hours**: Pre/post market data is included by default but may not be
* available for all symbols or time periods.
*
* **Delisted Stocks**: When a stock is delisted, ALL historical data becomes
* unavailable through Yahoo Finance, including periods when it was actively traded.
*
* @see {@link historical} for a simpler interface focused on OHLCV data
*
* @module chart
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports._chart = void 0;
exports.default = chart;
const index_js_1 = require("../lib/validate/index.js");
// @yf-schema: see the docs on how this file is automatically updated.
const chart_schema_js_1 = __importDefault(require("./chart.schema.js"));
const definitions = (0, index_js_1.getTypedDefinitions)(chart_schema_js_1.default);
const queryOptionsDefaults = {
useYfid: true,
interval: "1d",
includePrePost: true,
events: "div|split|earn",
lang: "en-US",
return: "array",
};
/* --- array input, typed output, honor "return" param --- */
// TODO: make this a deprecration passthrough
exports._chart = chart;
async function chart(symbol, queryOptionsOverrides, moduleOptions) {
const returnAs = queryOptionsOverrides?.return || "array";
const result = (await this._moduleExec({
moduleName: "chart",
query: {
assertSymbol: symbol,
url: "https://${YF_QUERY_HOST}/v8/finance/chart/" + symbol,
definitions,
schemaKey: "#/definitions/ChartOptions",
defaults: queryOptionsDefaults,
overrides: queryOptionsOverrides,
transformWith(queryOptions) {
if (!queryOptions.period2)
queryOptions.period2 = new Date();
const dates = ["period1", "period2"];
for (const fieldName of dates) {
const value = queryOptions[fieldName];
if (value instanceof Date) {
queryOptions[fieldName] = Math.floor(value.getTime() / 1000);
}
else if (typeof value === "string") {
const timestamp = new Date(value).getTime();
if (isNaN(timestamp)) {
throw new Error("yahooFinance.chart() option '" +
fieldName +
"' invalid date provided: '" +
value +
"'");
}
queryOptions[fieldName] = Math.floor(timestamp / 1000);
}
}
if (queryOptions.period1 === queryOptions.period2) {
throw new Error("yahooFinance.chart() options `period1` and `period2` " +
"cannot share the same value.");
}
// Don't pass this on to Yahoo
delete queryOptions.return;
return queryOptions;
},
},
result: {
definitions,
schemaKey: "#/definitions/ChartResultObject",
// deno-lint-ignore no-explicit-any
transformWith(result) {
if (!result.chart) {
throw new Error("Unexpected result: " + JSON.stringify(result));
}
const chart = result.chart.result[0];
// If there are no quotes, chart.timestamp will be empty, but Yahoo also
// gives us chart.indicators.quotes = [{}]. Let's clean that up and
// deliver an empty array rather than an invalid ChartIndicatorQuote/
if (!chart.timestamp) {
if (chart.indicators.quote.length !== 1) {
throw new Error("No timestamp with quotes.length !== 1, please report with your query");
}
if (Object.keys(chart.indicators.quote[0]).length !== 0) {
// i.e. {}
throw new Error("No timestamp with unexpected quote, please report with your query" +
JSON.stringify(chart.indicators.quote[0]));
}
chart.indicators.quote.pop();
}
return chart;
},
},
moduleOptions,
}));
if (returnAs === "object") {
return result;
}
else if (returnAs === "array") {
const timestamp = result.timestamp;
/*
seems as though yahoo inserts extra quotes at the event times, so no need.
if (result.events) {
for (let event of ["dividends", "splits"]) {
// @ts-ignore
if (result.events[event])
// @ts-ignore
timestamp = timestamp.filter((ts) => !result.events[event][ts]);
}
}
*/
// istanbul ignore next
if (timestamp &&
result?.indicators?.quote &&
result.indicators.quote[0].high.length !== timestamp.length) {
console.log({
origTimestampSize: result.timestamp && result.timestamp.length,
filteredSize: timestamp.length,
quoteSize: result.indicators.quote[0].high.length,
});
throw new Error("Timestamp count mismatch, please report this with the query you used");
}
const result2 = {
meta: result.meta,
quotes: timestamp ? new Array(timestamp.length) : [],
};
const adjclose = result?.indicators?.adjclose?.[0].adjclose;
if (timestamp) {
for (let i = 0; i < timestamp.length; i++) {
result2.quotes[i] = {
date: new Date(timestamp[i] * 1000),
high: result.indicators.quote[0].high[i],
volume: result.indicators.quote[0].volume[i],
open: result.indicators.quote[0].open[i],
low: result.indicators.quote[0].low[i],
close: result.indicators.quote[0].close[i],
};
if (adjclose)
result2.quotes[i].adjclose = adjclose[i];
}
}
if (result.events) {
result2.events = {};
for (const event of ["dividends", "splits"]) {
if (result.events[event]) {
result2.events[event] = Object.values(result.events[event]);
}
}
}
return result2;
}
// TypeScript runtime validation ensures no other values for
// "returnAs" are possible.
}