Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved time-series support for financial reports. #753

Merged
merged 13 commits into from
Mar 20, 2024
Merged
Prev Previous commit
Next Next commit
Support for option module all.
  • Loading branch information
nocodehummel committed Mar 18, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 55bee5c4095c5136d06cd1065056186ee9e51e94
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"files.eol": "\n",
"prettier.singleQuote": false,
"prettier.quoteProps": "preserve",
}
4 changes: 2 additions & 2 deletions docs/modules/fundamentalsTimeSeries.md
Original file line number Diff line number Diff line change
@@ -327,8 +327,8 @@ an array of symbols, and you'll receive an array of results back.
| ------------- | ----------| ---------- | --------------------------------- |
| `period1` | Date* | *required* | Starting period
| `period2` | Date* | (today) | Ending period
| `type` | "quarterly", "annual", "trailing" | "quarterly" | Financial time series type
| `module` | "income", "balance", "cashflow" | *required* | Financial statement module.
| `type` | "quarterly", "annual", "trailing" | "quarterly" | Reporting period type
| `module` | "income", "balance", "cashflow", "all" | *required* | Financial statement module
| `lang` | string | `"en-US"` | |
| `region` | string | `"US"` | |

17 changes: 16 additions & 1 deletion schema.json
Original file line number Diff line number Diff line change
@@ -1327,10 +1327,25 @@
],
"additionalProperties": false
},
"NamedParameters<typeof processQuery>": {
"type": "object",
"properties": {
"queryOptions": {
"$ref": "#/definitions/FundamentalsTimeSeriesOptions",
"description": "Input query options."
}
},
"required": [
"queryOptions"
],
"additionalProperties": false
},
"NamedParameters<typeof processResponse>": {
"type": "object",
"properties": {
"response": {}
"response": {
"description": "Query response."
}
},
"required": [
"response"
11 changes: 10 additions & 1 deletion src/modules/fundamentalsTimeSeries.spec.ts
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ describe("fundamentalsTimeSeries", () => {
{
period1: "2020-01-01",
period2: "2024-01-01",
module: "income",
module: "all",
},
{
devel: `fundamentalsTimeSeries-${symbol}-2020-01-01-to-2021-01-01.json`,
@@ -79,6 +79,15 @@ describe("fundamentalsTimeSeries", () => {
).rejects.toThrow(/option module invalid/);
});

it("throws if module not set", async () => {
await expect(
yf.fundamentalsTimeSeries("TSLA", {
period1: "2020-01-01",
period2: "2021-01-01",
})
).rejects.toThrow(/called with invalid options/);
});

it("throws error with unexpected results", () => {
return expect(
yf.fundamentalsTimeSeries(
151 changes: 89 additions & 62 deletions src/modules/fundamentalsTimeSeries.ts
Original file line number Diff line number Diff line change
@@ -74,67 +74,7 @@
schemaKey: "#/definitions/FundamentalsTimeSeriesOptions",
defaults: queryOptionsDefaults,
overrides: queryOptionsOverrides,
transformWith(
queryOptions: FundamentalsTimeSeriesOptions
): Partial<FundamentalsTimeSeriesOptions> {
// Convert dates
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.fundamentalsTimeSeries() option '" +
fieldName +
"' invalid date provided: '" +
value +
"'"
);

queryOptions[fieldName] = Math.floor(timestamp / 1000);
}
}

if (queryOptions.period1 === queryOptions.period2) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() options `period1` and `period2` " +
"cannot share the same value."
);
}

// Validate timeseries type and module
if (!FundamentalsTimeSeries_Types.includes(queryOptions.type || "")) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() option type invalid."
);
} else if (!queryOptions.module) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() option module not set."
);
} else if (
!FundamentalsTimeSeries_Modules.includes(queryOptions.module || "")
) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() option module invalid."
);
}

// Join the keys for the module into query types.
const keys = Timeseries_Keys[queryOptions.module];
const queryType =
queryOptions.type + keys.join(`,${queryOptions.type}`);

return {
period1: queryOptions.period1,
period2: queryOptions.period2,
type: queryType,
};
},
transformWith: processQuery,
},

result: {
@@ -151,9 +91,97 @@
});
}

/**
* 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.
*/
export const processQuery = function (
queryOptions: FundamentalsTimeSeriesOptions
): Partial<FundamentalsTimeSeriesOptions> {
// Convert dates
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.fundamentalsTimeSeries() option '" +
fieldName +
"' invalid date provided: '" +
value +
"'"
);

queryOptions[fieldName] = Math.floor(timestamp / 1000);

Check warning on line 125 in src/modules/fundamentalsTimeSeries.ts

Codecov / codecov/patch

src/modules/fundamentalsTimeSeries.ts#L125

Added line #L125 was not covered by tests
}
}

// Validate query parameters.
if (queryOptions.period1 === queryOptions.period2) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() options `period1` and `period2` " +
"cannot share the same value."
);
} else if (!FundamentalsTimeSeries_Types.includes(queryOptions.type || "")) {
throw new Error(
"yahooFinance.fundamentalsTimeSeries() option type invalid."
);
} else if (!queryOptions.module) {
throw new Error(

Check warning on line 140 in src/modules/fundamentalsTimeSeries.ts

Codecov / codecov/patch

src/modules/fundamentalsTimeSeries.ts#L140

Added line #L140 was not covered by tests
"yahooFinance.fundamentalsTimeSeries() option module not set."
);
} else if (
!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_Keys).reduce(
(previous: Array<string>, [module, keys]) => {
if (queryOptions.module == "all") {
return previous.concat(keys);
} else if (module == queryOptions.module) {
return previous.concat(Timeseries_Keys[queryOptions.module]);
} else return previous;
},
[] as Array<string>
);
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.
*/
export const processResponse = function (response: any): any {
const keyedByTimestamp: Record<string, any> = {};
const replace = new RegExp(FundamentalsTimeSeries_Types.join("|"));

for (let ct = 0; ct < response.timeseries.result.length; ct++) {
const result = response.timeseries.result[ct];
if (!result.timestamp || !result.timestamp.length) {
@@ -174,7 +202,6 @@
continue;
}

// Extract the type from the dataKey for later mapping.
const short = dataKey.replace(replace, "");
const key =
short == short.toUpperCase()

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.