UNPKG

yahoo-finance2

Version:
406 lines (392 loc) 14.2 kB
"use strict"; /** * Historical data module for retrieving price history, dividends, and stock splits. * * This module provides historical price data, dividend payments, and stock split * information for financial instruments. While functional, many users prefer * the {@link chart} module which offers more flexibility and features. * * @example Basic Price History * ```typescript * import YahooFinance from "yahoo-finance2"; * const yahooFinance = new YahooFinance(); * * // Get 1 year of daily data * const history = await yahooFinance.historical('AAPL', { * period1: '2023-01-01', * period2: '2024-01-01' * }); * * console.log(history[0]); // Most recent day * // { date: Date, open: 150.5, high: 155.2, low: 149.8, close: 154.1, ... } * ``` * * @example Different Intervals * ```typescript * // Weekly data * const weekly = await yahooFinance.historical('AAPL', { * period1: '2023-01-01', * interval: '1wk' * }); * * // Monthly data * const monthly = await yahooFinance.historical('AAPL', { * period1: '2022-01-01', * interval: '1mo' * }); * ``` * * @example Dividends and Splits * ```typescript * // Get dividend history * const dividends = await yahooFinance.historical('AAPL', { * period1: '2023-01-01', * events: 'dividends' * }); * // Returns: [{ date: Date, dividends: 0.24 }, ...] * * // Get stock splits * const splits = await yahooFinance.historical('AAPL', { * period1: '2020-01-01', * events: 'split' * }); * // Returns: [{ date: Date, stockSplits: "4:1" }, ...] * ``` * * @remarks * **Limitations**: * - Intervals limited to daily ("1d"), weekly ("1wk"), monthly ("1mo") * - Events (prices, dividends, splits) require separate requests * - Consider using {@link chart} module for more flexibility * * **Performance**: The chart module often provides better performance and * more features for historical data needs. * * @module historical */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = historical; const validateAndCoerceTypes_js_1 = __importDefault(require("../lib/validateAndCoerceTypes.js")); const index_js_1 = require("../lib/validate/index.js"); // @yf-schema: see the docs on how this file is automatically updated. const historical_schema_js_1 = __importDefault(require("./historical.schema.js")); const chart_schema_js_1 = __importDefault(require("./chart.schema.js")); const historicalDefinitions = (0, index_js_1.getTypedDefinitions)(historical_schema_js_1.default); const chartDefinitions = (0, index_js_1.getTypedDefinitions)(chart_schema_js_1.default); const queryOptionsDefaults = { interval: "1d", events: "history", includeAdjustedClose: true, }; // Count number of null values in object (1-level deep) function nullFieldCount(object) { if (object == null) { return; } let nullCount = 0; for (const val of Object.values(object)) if (val === null) nullCount++; return nullCount; } /** * Get historical price data, dividends, or stock splits for a financial instrument. * * This function retrieves historical data from Yahoo Finance. The type of data returned * depends on the `events` parameter - price history (default), dividends, or stock splits. * * @example Price History * ```typescript * import YahooFinance from "yahoo-finance2"; * const yahooFinance = new YahooFinance(); * * // Get 1 year of daily price data * const prices = await yahooFinance.historical('AAPL', { * period1: '2023-01-01', * period2: '2024-01-01' * }); * * prices.forEach(day => { * console.log(`${day.date.toISOString().split('T')[0]}: $${day.close}`); * }); * ``` * * @example Different Time Periods * ```typescript * // Last 30 days * const recent = await yahooFinance.historical('TSLA', { * period1: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) * }); * * // Specific date range * const range = await yahooFinance.historical('GOOGL', { * period1: '2023-06-01', * period2: '2023-12-31', * interval: '1wk' // Weekly data * }); * ``` * * @example Dividends and Splits * ```typescript * // Get all dividends in 2023 * const dividends = await yahooFinance.historical('MSFT', { * period1: '2023-01-01', * period2: '2024-01-01', * events: 'dividends' * }); * * // Find stock splits since 2020 * const splits = await yahooFinance.historical('AAPL', { * period1: '2020-01-01', * events: 'split' * }); * * console.log(splits); // [{ date: Date('2020-08-31'), stockSplits: "4:1" }] * ``` * * @param symbol - Stock, ETF, or other financial instrument symbol. * Use search() to find valid symbols. * @param queryOptionsOverrides - Required configuration: * - `period1`: Start date (required) * - `period2`: End date (optional, defaults to now) * - `interval`: "1d", "1wk", or "1mo" * - `events`: "history", "dividends", or "split" * - `includeAdjustedClose`: Include adjusted prices * @param moduleOptions - Optional module configuration (validateResult, etc.) * * @returns Promise that resolves to: * - Array of HistoricalRowHistory (for price data) * - Array of HistoricalRowDividend (for dividend data) * - Array of HistoricalRowStockSplit (for split data) * * @throws Will throw an error if: * - Network request fails * - Invalid symbol or date range * - Validation fails (if enabled) * * @remarks * **Limitations:** * - Limited to daily/weekly/monthly intervals only * - Events (prices, dividends, splits) require separate API calls * - Less flexible than the chart module * * **Alternative**: Consider using {@link chart} module for: * - More interval options (1m, 5m, 15m, etc.) * - Combined events in single request * - Better performance for complex queries * * **Date Formats**: Accepts Date objects, ISO strings ("2023-01-01"), * or Unix timestamps (milliseconds since epoch). * * @see {@link HistoricalOptions} for all available options * @see {@link chart} for a more flexible alternative */ async function historical(symbol, queryOptionsOverrides, moduleOptions) { let _schemaKey; this._notices.show("ripHistorical"); if (!queryOptionsOverrides.events || queryOptionsOverrides.events === "history") { _schemaKey = "#/definitions/HistoricalHistoryResult"; } else if (queryOptionsOverrides.events === "dividends") { _schemaKey = "#/definitions/HistoricalDividendsResult"; } else if (queryOptionsOverrides.events === "split") { _schemaKey = "#/definitions/HistoricalStockSplitsResult"; } else throw new Error("No such event type:" + queryOptionsOverrides.events); const queryOpts = { ...queryOptionsDefaults, ...queryOptionsOverrides }; (0, validateAndCoerceTypes_js_1.default)({ source: "historical", type: "options", object: queryOpts, definitions: historicalDefinitions, schemaOrSchemaKey: "#/definitions/HistoricalOptions", options: this._opts.validation, logger: this._opts.logger, logObj: this._logObj, versionCheck: this._opts.versionCheck, }); // Don't forget that queryOpts are already validated and safe-safe. const eventsMap = { history: "", dividends: "div", split: "split" }; const chartQueryOpts = { period1: queryOpts.period1, period2: queryOpts.period2, interval: queryOpts.interval, events: eventsMap[queryOpts.events || "history"], }; (0, validateAndCoerceTypes_js_1.default)({ source: "historical", type: "options", object: chartQueryOpts, definitions: chartDefinitions, schemaOrSchemaKey: "#/definitions/ChartOptions", options: this._opts.validation, logger: this._opts.logger, logObj: this._logObj, versionCheck: this._opts.versionCheck, }); /* throw new Error( "Internal error, please report. historical() provided invalid chart() query options.", ); */ // TODO: do we even care? if (queryOpts.includeAdjustedClose === false) { /* */ } const result = await this.chart(symbol, chartQueryOpts, { ...moduleOptions, validateResult: true, }); let out; if (queryOpts.events === "dividends") { out = (result.events?.dividends ?? []).map((d) => ({ date: d.date, dividends: d.amount, })); } else if (queryOpts.events === "split") { out = (result.events?.splits ?? []).map((s) => ({ date: s.date, stockSplits: s.splitRatio, })); } else { out = (result.quotes ?? []) .filter((quote) => { const fieldCount = Object.keys(quote).length; const nullCount = nullFieldCount(quote); if (nullCount === 0) { // No nulls is a legit (regular) result return true; } else if (nullCount !== fieldCount - 1 /* skip "date" */) { // Unhandled case: some but not all values are null. // Note: no need to check for null "date", validation does it for us console.error(nullCount, quote); throw new Error("Historical returned a result with SOME (but not " + "all) null values. Please report this, and provide the " + "query that caused it."); } else { // All fields (except "date") are null return false; } }) .map((quote) => { if (!quote.adjclose) return quote; const { adjclose, ...rest } = quote; return { ...rest, adjClose: adjclose }; }); } const validateResult = !moduleOptions || moduleOptions.validateResult === undefined || moduleOptions.validateResult === true; const validationOpts = { ...this._opts.validation, // Set logErrors=false if validateResult=false logErrors: validateResult ? this._opts.validation.logErrors : false, }; (0, validateAndCoerceTypes_js_1.default)({ source: "historical", type: "result", object: out, definitions: historicalDefinitions, schemaOrSchemaKey: "#/definitions/HistoricalResult", options: validationOpts, logger: this._opts.logger, logObj: this._logObj, versionCheck: this._opts.versionCheck, }); return out; /* // Original historical() retrieval code when Yahoo API still existed. return this._moduleExec({ moduleName: "historical", query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/v7/finance/download/" + symbol, schemaKey: "#/definitions/HistoricalOptions", defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, fetchType: "csv", transformWith(queryOptions: HistoricalOptions) { if (!queryOptions.period2) queryOptions.period2 = new Date(); const dates = ["period1", "period2"] as const; 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 as string).getTime(); if (isNaN(timestamp)) throw new Error( "yahooFinance.historical() option '" + fieldName + "' invalid date provided: '" + value + "'", ); queryOptions[fieldName] = Math.floor(timestamp / 1000); } } if (queryOptions.period1 === queryOptions.period2) { throw new Error( "yahooFinance.historical() options `period1` and `period2` " + "cannot share the same value.", ); } return queryOptions; }, }, result: { schemaKey, transformWith(result: any) { if (result.length === 0) return result; const filteredResults = []; const fieldCount = Object.keys(result[0]).length; // Count number of null values in object (1-level deep) function nullFieldCount(object: Object) { let nullCount = 0; for (const val of Object.values(object)) if (val === null) nullCount++; return nullCount; } for (const row of result) { const nullCount = nullFieldCount(row); if (nullCount === 0) { // No nulls is a legit (regular) result filteredResults.push(row); } else if (nullCount !== fieldCount - 1 /* skip "date" */ /*) { // Unhandled case: some but not all values are null. // Note: no need to check for null "date", validation does it for us console.error(nullCount, row); throw new Error( "Historical returned a result with SOME (but not " + "all) null values. Please report this, and provide the " + "query that caused it.", ); } else { // All fields (except "date") are null: silently skip (no-op) } } /* * We may consider, for future optimization, to count rows and create * new array in advance, and skip consecutive blocks of null results. * Of doubtful utility. */ /* return filteredResults; }, }, moduleOptions, }); */ }