yahoo-finance2
Version:
JS API for Yahoo Finance
358 lines (357 loc) • 13.6 kB
JavaScript
;
/**
* Fundamentals Time Series module for retrieving detailed financial statement data over time.
*
* This module provides comprehensive time-series financial data including balance sheet,
* income statement, and cash flow statement items across quarterly, annual, and trailing
* periods. Essential for financial analysis, trend analysis, and fundamental research.
*
* @example Basic Usage - Balance Sheet Data
* ```typescript
* import YahooFinance from "yahoo-finance2";
* const yahooFinance = new YahooFinance();
*
* // Get quarterly balance sheet data for the last 2 years
* const balanceSheetData = await yahooFinance.fundamentalsTimeSeries('AAPL', {
* period1: '2022-01-01',
* period2: '2024-01-01',
* type: 'quarterly',
* module: 'balance-sheet'
* });
*
* // Access specific metrics
* balanceSheetData.forEach(quarter => {
* console.log(`${quarter.date}: Cash = $${quarter.quarterlyCashAndCashEquivalents}`);
* console.log(`Total Assets = $${quarter.quarterlyTotalAssets}`);
* });
* ```
*
* @example Financials (Income Statement) Analysis
* ```typescript
* // Get annual income statement data
* const financialsData = await yahooFinance.fundamentalsTimeSeries('MSFT', {
* period1: '2020-01-01',
* type: 'annual',
* module: 'financials'
* });
*
* // Track revenue growth over years
* financialsData.forEach(year => {
* console.log(`${year.date.getFullYear()}: Revenue = $${year.annualTotalRevenue}`);
* console.log(`Net Income = $${year.annualNetIncome}`);
* });
*
* // Calculate revenue growth rate
* if (financialsData.length >= 2) {
* const latest = financialsData[financialsData.length - 1];
* const previous = financialsData[financialsData.length - 2];
* const growthRate = ((latest.annualTotalRevenue - previous.annualTotalRevenue) / previous.annualTotalRevenue) * 100;
* console.log(`Revenue growth: ${growthRate.toFixed(2)}%`);
* }
* ```
*
* @example Cash Flow Analysis
* ```typescript
* // Get cash flow data for operational analysis
* const cashFlowData = await yahooFinance.fundamentalsTimeSeries('GOOGL', {
* period1: '2022-01-01',
* type: 'quarterly',
* module: 'cash-flow'
* });
*
* // Analyze cash generation trends
* cashFlowData.forEach(quarter => {
* const operatingCF = quarter.quarterlyOperatingCashFlow;
* const freeCF = quarter.quarterlyFreeCashFlow;
* const capex = quarter.quarterlyCapitalExpenditure;
*
* console.log(`${quarter.date}: Operating CF = $${operatingCF}`);
* console.log(`Free CF = $${freeCF}, CapEx = $${capex}`);
* });
* ```
*
* @example Comprehensive Financial Analysis
* ```typescript
* // Get all financial data for complete analysis
* const allData = await yahooFinance.fundamentalsTimeSeries('NVDA', {
* period1: '2023-01-01',
* type: 'quarterly',
* module: 'all'
* });
*
* // Financial health analysis
* allData.forEach(quarter => {
* const totalAssets = quarter.quarterlyTotalAssets;
* const totalDebt = quarter.quarterlyTotalDebt;
* const cash = quarter.quarterlyCashAndCashEquivalents;
* const revenue = quarter.quarterlyTotalRevenue;
*
* // Key ratios
* const debtToAssets = (totalDebt / totalAssets) * 100;
* const cashRatio = (cash / totalDebt) * 100;
*
* console.log(`${quarter.date}:`);
* console.log(` Debt-to-Assets: ${debtToAssets.toFixed(2)}%`);
* console.log(` Cash Coverage: ${cashRatio.toFixed(2)}%`);
* });
* ```
*
* @example Trailing Twelve Months (TTM) Data
* ```typescript
* // Get trailing 12-month data for current analysis
* const ttmData = await yahooFinance.fundamentalsTimeSeries('TSLA', {
* period1: '2023-01-01',
* type: 'trailing',
* module: 'financials'
* });
*
* // Get the most recent TTM figures
* const latest = ttmData[ttmData.length - 1];
* console.log(`TTM Revenue: $${latest.trailingTotalRevenue}`);
* console.log(`TTM Net Income: $${latest.trailingNetIncome}`);
* ```
*
* @example Comparative Analysis
* ```typescript
* // Compare multiple companies
* const symbols = ['AAPL', 'MSFT', 'GOOGL'];
* const comparisons = await Promise.all(
* symbols.map(symbol =>
* yahooFinance.fundamentalsTimeSeries(symbol, {
* period1: '2023-01-01',
* type: 'annual',
* module: 'financials'
* })
* )
* );
*
* // Compare latest year revenues
* symbols.forEach((symbol, index) => {
* const data = comparisons[index];
* const latestYear = data[data.length - 1];
* console.log(`${symbol}: $${latestYear.annualTotalRevenue} revenue`);
* });
* ```
*
* @example Error Handling and Data Validation
* ```typescript
* try {
* const data = await yahooFinance.fundamentalsTimeSeries('AAPL', {
* period1: '2020-01-01',
* type: 'quarterly',
* module: 'balance-sheet'
* });
*
* // Check for data availability
* if (data.length === 0) {
* console.log('No financial data available for the specified period');
* return;
* }
*
* // Validate data completeness
* data.forEach(quarter => {
* if (!quarter.quarterlyTotalAssets) {
* console.warn(`Missing total assets data for ${quarter.date}`);
* }
* });
*
* } catch (error) {
* console.error('Failed to fetch fundamentals data:', error.message);
* }
* ```
*
* @remarks
* **Data Types**: The module supports three reporting periods:
* - `quarterly`: Individual quarter reporting periods
* - `annual`: Full fiscal year data
* - `trailing`: Rolling 12-month (TTM) data
*
* **Module Types**: Financial statement categories:
* - `financials`: Income statement items (revenue, expenses, net income)
* - `balance-sheet`: Balance sheet items (assets, liabilities, equity)
* - `cash-flow`: Cash flow statement items (operating, investing, financing)
* - `all`: Complete financial data from all statements
*
* **Data Availability**: Historical depth varies by company and may be limited
* for newer public companies. Large cap stocks typically have 5+ years of data.
*
* **Currency**: All values are in the company's reporting currency (usually USD
* for US companies). Values are typically in absolute amounts, not per-share.
*
* **Timing**: Data is typically available 1-2 days after earnings release.
* Trailing data is updated quarterly with the latest reported period.
*
* @see {@link quoteSummary} for current period fundamental data
*
* @module fundamentalsTimeSeries
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FundamentalsTimeSeries_Modules = exports.FundamentalsTimeSeries_Types = void 0;
exports.default = fundamentalsTimeSeries;
const timeseries_js_1 = __importDefault(require("../lib/timeseries.js"));
const index_js_1 = require("../lib/validate/index.js");
// @yf-schema: see the docs on how this file is automatically updated.
const fundamentalsTimeSeries_schema_js_1 = __importDefault(require("./fundamentalsTimeSeries.schema.js"));
const definitions = (0, index_js_1.getTypedDefinitions)(fundamentalsTimeSeries_schema_js_1.default);
exports.FundamentalsTimeSeries_Types = ["quarterly", "annual", "trailing"];
exports.FundamentalsTimeSeries_Modules = [
"financials",
"balance-sheet",
"cash-flow",
"all",
];
const queryOptionsDefaults = {
merge: false,
padTimeSeries: true,
lang: "en-US",
region: "US",
type: "quarterly",
};
function fundamentalsTimeSeries(symbol, queryOptionsOverrides, moduleOptions) {
return this._moduleExec({
moduleName: "options",
query: {
assertSymbol: symbol,
url: `https://query1.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/${symbol}`,
needsCrumb: false,
definitions,
schemaKey: "#/definitions/FundamentalsTimeSeriesOptions",
defaults: queryOptionsDefaults,
overrides: queryOptionsOverrides,
transformWith: processQuery,
},
result: {
definitions,
schemaKey: "#/definitions/FundamentalsTimeSeriesResults",
// deno-lint-ignore no-explicit-any
transformWith(response) {
if (!response || !response.timeseries) {
throw new Error(`Unexpected result: ${JSON.stringify(response)}`);
}
return processResponse(response, queryOptionsOverrides.module ?? queryOptionsOverrides.type);
},
},
moduleOptions,
});
}
/**
* Transform the input options into query parameters.
* The options module defines which keys that are used in the query.
* The keys are joined together into the query parameter type and
* pre-fixed with the options type (e.g. annualTotalRevenue).
* @param queryOptions Input query options.
* @returns Query parameters.
*/
const processQuery = function (queryOptions) {
// Convert dates
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.fundamentalsTimeSeries() option '" +
fieldName +
"' invalid date provided: '" +
value +
"'");
}
queryOptions[fieldName] = Math.floor(timestamp / 1000);
}
}
// Validate query parameters.
if (queryOptions.period1 === queryOptions.period2) {
throw new Error("yahooFinance.fundamentalsTimeSeries() options `period1` and `period2` " +
"cannot share the same value.");
}
else if (!exports.FundamentalsTimeSeries_Types.includes(queryOptions.type || "")) {
throw new Error("yahooFinance.fundamentalsTimeSeries() option type invalid.");
}
else if (!exports.FundamentalsTimeSeries_Modules.includes(queryOptions.module || "")) {
throw new Error("yahooFinance.fundamentalsTimeSeries() option module invalid.");
}
// Join the keys for the module into query types.
const keys = Object.entries(timeseries_js_1.default).reduce((previous, [module, keys]) => {
if (queryOptions.module == "all") {
return previous.concat(keys);
}
else if (module == queryOptions.module) {
return previous.concat(keys);
}
else
return previous;
}, []);
const queryType = queryOptions.type + keys.join(`,${queryOptions.type}`);
return {
period1: queryOptions.period1,
period2: queryOptions.period2,
type: queryType,
};
};
/**
* Transforms the time-series into an array with reported values per period.
* Each object represents a period and its properties are the data points.
* Financial statement content variates and keys are skipped when empty.
* The query keys include the option type (e.g. annualTotalRevenue).
* In the response the type is removed (e.g. totalRevenue) for
* easier mapping by the client.
* @param response Query response.
* @returns Formatted response.
*/
const processResponse = function (
// deno-lint-ignore no-explicit-any
response, requestModule) {
// deno-lint-ignore no-explicit-any
const keyedByTimestamp = {};
const replace = new RegExp(exports.FundamentalsTimeSeries_Types.join("|"));
for (let ct = 0; ct < response.timeseries.result.length; ct++) {
let periodType = "UNKNOWN";
const result = response.timeseries.result[ct];
if (!result.timestamp || !result.timestamp.length) {
continue;
}
for (let ct = 0; ct < result.timestamp.length; ct++) {
const timestamp = result.timestamp[ct];
const dataKey = Object.keys(result)[2];
if (!keyedByTimestamp[timestamp]) {
keyedByTimestamp[timestamp] = { date: timestamp };
}
if (!result[dataKey][ct] ||
!result[dataKey][ct].reportedValue ||
!result[dataKey][ct].reportedValue.raw) {
continue;
}
const short = dataKey.replace(replace, "");
const key = short == short.toUpperCase()
? short
: short[0].toLowerCase() + short.slice(1);
keyedByTimestamp[timestamp][key] = result[dataKey][ct].reportedValue.raw;
const thisPeriodType = result[dataKey][ct].periodType;
if (thisPeriodType) {
if (periodType !== "UNKNOWN" && periodType !== thisPeriodType) {
throw new Error("periodType mismatch - please report " + periodType + " " +
thisPeriodType);
}
periodType = thisPeriodType;
keyedByTimestamp[timestamp].periodType = periodType;
}
else {
console.log("missing periodType", keyedByTimestamp[timestamp]);
}
}
}
return Object.values(keyedByTimestamp).filter((entry) => Object.keys(entry).length > 1).map((entry) => ({
TYPE: requestModule === "all"
? "ALL"
: requestModule.toUpperCase().replace("-", "_"),
...entry,
}));
};