From 8257099df72ed877a0d702a86633ab2b849809eb Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sat, 4 May 2024 22:04:05 +0800 Subject: [PATCH 01/13] Add YFinance Typebox types + validation & coercion --- package.json | 1 + schema.json | 130 ++++----- src/lib/datetime.spec.ts | 15 + src/lib/datetime.ts | 100 +++++++ src/lib/moduleCommon.ts | 2 + src/lib/moduleExecTypebox.spec.ts | 208 ++++++++++++++ src/lib/moduleExecTypebox.ts | 209 ++++++++++++++ src/lib/validateAndCoerceTypes.spec.ts | 361 +++++++++++++++++++++++++ src/lib/validateAndCoerceTypes.ts | 85 +++++- src/lib/yahooFinanceTypes.spec.ts | 239 ++++++++++++++++ src/lib/yahooFinanceTypes.ts | 161 +++++++++++ tests/http/getCrumb-getcrumb | 67 +++++ tests/testYf.ts | 2 + yarn.lock | 5 + 14 files changed, 1521 insertions(+), 64 deletions(-) create mode 100644 src/lib/datetime.spec.ts create mode 100644 src/lib/datetime.ts create mode 100644 src/lib/moduleExecTypebox.spec.ts create mode 100644 src/lib/moduleExecTypebox.ts create mode 100644 src/lib/yahooFinanceTypes.spec.ts create mode 100644 src/lib/yahooFinanceTypes.ts create mode 100644 tests/http/getCrumb-getcrumb diff --git a/package.json b/package.json index a184bc96..e3ec9675 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "schema.json" ], "dependencies": { + "@sinclair/typebox": "^0.32.27", "@types/tough-cookie": "^4.0.2", "ajv": "8.10.0", "ajv-formats": "2.1.1", diff --git a/schema.json b/schema.json index 7729a246..6079e962 100644 --- a/schema.json +++ b/schema.json @@ -697,10 +697,14 @@ "ModuleThis": { "type": "object", "properties": { - "_moduleExec": {} + "_moduleExec": {}, + "_moduleExecTypebox": { + "$comment": "(\n this: { [key: string]: any },\n opts: ModuleExecOptions) -> undefined" + } }, "required": [ - "_moduleExec" + "_moduleExec", + "_moduleExecTypebox" ] }, "ModuleOptions": { @@ -9314,6 +9318,67 @@ ], "additionalProperties": false }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "query": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/SearchOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "query" + ], + "additionalProperties": false + }, + "SearchOptions": { + "type": "object", + "properties": { + "lang": { + "type": "string" + }, + "region": { + "type": "string" + }, + "quotesCount": { + "yahooFinanceType": "number" + }, + "newsCount": { + "yahooFinanceType": "number" + }, + "enableFuzzyQuery": { + "type": "boolean" + }, + "quotesQueryId": { + "type": "string" + }, + "multiQuoteQueryId": { + "type": "string" + }, + "newsQueryId": { + "type": "string" + }, + "enableCb": { + "type": "boolean" + }, + "enableNavLinks": { + "type": "boolean" + }, + "enableEnhancedTrivialQuery": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "SearchQuoteYahoo": { "type": "object", "properties": { @@ -10120,67 +10185,6 @@ "timeTakenForResearchReports" ] }, - "SearchOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quotesCount": { - "yahooFinanceType": "number" - }, - "newsCount": { - "yahooFinanceType": "number" - }, - "enableFuzzyQuery": { - "type": "boolean" - }, - "quotesQueryId": { - "type": "string" - }, - "multiQuoteQueryId": { - "type": "string" - }, - "newsQueryId": { - "type": "string" - }, - "enableCb": { - "type": "boolean" - }, - "enableNavLinks": { - "type": "boolean" - }, - "enableEnhancedTrivialQuery": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/SearchOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, "TrendingSymbol": { "type": "object", "properties": { diff --git a/src/lib/datetime.spec.ts b/src/lib/datetime.spec.ts new file mode 100644 index 00000000..1ea1f147 --- /dev/null +++ b/src/lib/datetime.spec.ts @@ -0,0 +1,15 @@ +import { isTime } from "./datetime"; + +// There is reasonable coverage for most of this module already, so these tests aren't extensive. +// Just plugging the gaps in test coverage +describe("isTime", () => { + it("Should correctly identify a time given in the non-zulu TZ as a valid time", () => { + expect(isTime("20:20:39+00:00")).toBe(true); + }); + it("Should correctly identify a time given in a TZ behind Zulu as a valid time", () => { + expect(isTime("20:20:39-10:00")).toBe(true); + }); + it("Should correctly reject a time given in the non-zulu TZ with impossible values", () => { + expect(isTime("35:61:39+00:00")).toBe(false); + }); +}); diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts new file mode 100644 index 00000000..4c4d5243 --- /dev/null +++ b/src/lib/datetime.ts @@ -0,0 +1,100 @@ +/* +The contents of this file are copied from: +* https://github.com/sinclairzx81/typebox/blob/7a42aeef5bb989c07bbfc9acdbd9d74b3febed05/example/formats/date.ts +* https://github.com/sinclairzx81/typebox/blob/7a42aeef5bb989c07bbfc9acdbd9d74b3febed05/example/formats/date-time.ts +* https://github.com/sinclairzx81/typebox/blob/7a42aeef5bb989c07bbfc9acdbd9d74b3febed05/example/formats/time.ts +* +* License info: +* +* The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; +const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; +const YEAR = /^(\d\d\d\d)$/; +const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i; +const DATE_TIME_SEPARATOR = /t|\s/i; + +function IsLeapYear(year: number): boolean { + return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); +} +export const isYear = (value: string): boolean => { + const matches: string[] | null = YEAR.exec(value); + return !!matches; +}; +/** + * `[ajv-formats]` ISO8601 Date component + * @example `2020-12-12` + */ +export const isDate = (value: string): boolean => { + const matches: string[] | null = DATE.exec(value); + if (!matches) return false; + const year: number = +matches[1]; + const month: number = +matches[2]; + const day: number = +matches[3]; + return ( + month >= 1 && + month <= 12 && + day >= 1 && + day <= (month === 2 && IsLeapYear(year) ? 29 : DAYS[month]) + ); +}; + +/** + * `[ajv-formats]` ISO8601 Time component + * @example `20:20:39+00:00` + */ +export const isTime = (value: string, strictTimeZone?: boolean): boolean => { + const matches: string[] | null = TIME.exec(value); + if (!matches) return false; + const hr: number = +matches[1]; + const min: number = +matches[2]; + const sec: number = +matches[3]; + const tz: string | undefined = matches[4]; + const tzSign: number = matches[5] === "-" ? -1 : 1; + const tzH: number = +(matches[6] || 0); + const tzM: number = +(matches[7] || 0); + if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false; + if (hr <= 23 && min <= 59 && sec < 60) return true; + const utcMin = min - tzM * tzSign; + const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0); + return ( + (utcHr === 23 || utcHr === -1) && + (utcMin === 59 || utcMin === -1) && + sec < 61 + ); +}; + +/** + * `[ajv-formats]` ISO8601 DateTime + * @example `2020-12-12T20:20:40+00:00` + */ +export const isDateTime = ( + value: string, + strictTimeZone?: boolean +): boolean => { + const dateTime: string[] = value.split(DATE_TIME_SEPARATOR); + return ( + dateTime.length === 2 && + isDate(dateTime[0]) && + isTime(dateTime[1], strictTimeZone) + ); +}; diff --git a/src/lib/moduleCommon.ts b/src/lib/moduleCommon.ts index dff4b6b8..e5b18f9a 100644 --- a/src/lib/moduleCommon.ts +++ b/src/lib/moduleCommon.ts @@ -1,4 +1,5 @@ //import ModuleExec from "./moduleExec.js"; +import moduleExecTypebox from "./moduleExecTypebox"; export interface ModuleOptions { validateResult?: boolean; @@ -19,4 +20,5 @@ export interface ModuleThis { // TODO: should be ModuleExec function but requiring functions breaks // schema generation because json-schema does not support functions. _moduleExec: any; + _moduleExecTypebox: typeof moduleExecTypebox; } diff --git a/src/lib/moduleExecTypebox.spec.ts b/src/lib/moduleExecTypebox.spec.ts new file mode 100644 index 00000000..93a96910 --- /dev/null +++ b/src/lib/moduleExecTypebox.spec.ts @@ -0,0 +1,208 @@ +import { jest } from "@jest/globals"; + +import search from "../modules/search.tb.js"; +import { InvalidOptionsError } from "./errors.js"; +import testYf from "../../tests/testYf.js"; +import { TransformDecodeCheckError } from "@sinclair/typebox/value"; +import { Type } from "@sinclair/typebox"; + +const yf = testYf({ search }); +yf._opts.validation.logOptionsErrors = false; +yf._opts.validation.logErrors = false; + +describe("moduleExecTypebox", () => { + describe("options validation", () => { + it("throws InvalidOptions on invalid options", async () => { + const rwo = (options: any) => yf.search("symbol", options); + await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); + }); + + it("accepts empty queryOptions", async () => { + await expect( + yf.search("AAPL", undefined, { devel: "search-AAPL.json" }) + ).resolves.toBeDefined(); + }); + + it("logs errors on invalid options when logOptionsErrors = true", async () => { + yf._opts.validation.logOptionsErrors = true; + const realConsole = console; + const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; + + /* @ts-ignore */ + console = fakeConsole; + const rwo = (options: any) => yf.search("symbol", options); + await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); + console = realConsole; + + expect( + fakeConsole.log.mock.calls.length + + fakeConsole.error.mock.calls.length + + fakeConsole.dir.mock.calls.length + ).toBe(1); + yf._opts.validation.logOptionsErrors = false; + }); + + it("does not log errors on invalid options when logOptionsErrors = false", async () => { + yf._opts.validation.logOptionsErrors = false; + console.log(yf._opts.validation); + const realConsole = console; + const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; + + /* @ts-ignore */ + console = fakeConsole; + const rwo = (options: any) => yf.search("symbol", options); + await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); + console = realConsole; + expect( + fakeConsole.log.mock.calls.length + + fakeConsole.error.mock.calls.length + + fakeConsole.dir.mock.calls.length + ).toBe(0); + }); + }); + + describe("result validation", () => { + if (process.env.FETCH_DEVEL !== "nocache") + it("throws on unexpected input", async () => { + yf._opts.validation.logErrors = false; + await expect( + yf.search("AAPL", {}, { devel: "search-badResult.fake.json" }) + ).rejects.toThrow(TransformDecodeCheckError); + yf._opts.validation.logErrors = true; + }); + + it("dont throw or log on unexpected input with {validateResult: false}", async () => { + yf._opts.validation.logErrors = true; + const realConsole = console; + const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; + + /* @ts-ignore */ + console = fakeConsole; + if (process.env.FETCH_DEVEL !== "nocache") + await expect( + yf.search( + "AAPL", + {}, + { + devel: "search-badResult.fake.json", + validateResult: false, + } + ) + ).resolves.toBeDefined(); + console = realConsole; + + expect(fakeConsole.log).not.toHaveBeenCalled(); + expect(fakeConsole.error).not.toHaveBeenCalled(); + expect(fakeConsole.dir).not.toHaveBeenCalled(); + }); + }); + describe("correctly invokes callbacks when provided", () => { + it("Should invoke the query options transformWith function when one is provided", async () => { + const yf = testYf({ _fetch: jest.fn() }); + const overrides = { overrideKey: "thingy" }; + const optionsTransformWith = jest.fn((v: Record) => ({ + ...v, + overrideKey: "bobby", + })); + + await yf._moduleExecTypebox({ + query: { + transformWith: optionsTransformWith, + assertSymbol: false, + schema: Type.Any(), + overrides, + }, + result: { + schema: Type.Any(), + }, + }); + expect(optionsTransformWith).toHaveBeenCalledTimes(1); + expect(optionsTransformWith).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + { + "overrideKey": "thingy", + }, + ], + ], + "results": [ + { + "type": "return", + "value": { + "overrideKey": "bobby", + }, + }, + ], + } + `); + }); + it("Should invoke the result transformWith function when one is provided", async () => { + const yf = testYf({ _fetch: jest.fn(() => ({ statusCode: 200 })) }); + const resultTransformedWith = jest.fn((v: Record) => { + return { overrideKey: "bobby" }; + }); + + await yf._moduleExecTypebox({ + query: { + assertSymbol: false, + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + transformWith: resultTransformedWith, + }, + }); + expect(resultTransformedWith).toHaveBeenCalledTimes(1); + expect(resultTransformedWith).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + { + "statusCode": 200, + }, + ], + ], + "results": [ + { + "type": "return", + "value": { + "overrideKey": "bobby", + }, + }, + ], + } + `); + }); + it("should throw when symbol assertion is enabled but a non-string symbol is provided", () => { + const yf = testYf({ _fetch: jest.fn() }); + expect( + async () => + await yf._moduleExecTypebox({ + query: { + assertSymbol: true, + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + }, + }) + ).rejects.toThrow(); + }); + it("should pass a string symbol when symbol assertion is enabled", () => { + const yf = testYf({ _fetch: jest.fn() }); + expect( + async () => + await yf._moduleExecTypebox({ + query: { + assertSymbol: "AAPL", + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + }, + }) + ).not.toThrow(); + }); + }); +}); diff --git a/src/lib/moduleExecTypebox.ts b/src/lib/moduleExecTypebox.ts new file mode 100644 index 00000000..60f51463 --- /dev/null +++ b/src/lib/moduleExecTypebox.ts @@ -0,0 +1,209 @@ +/* + * moduleExec(options: ModuleExecOptions) + * + * 1. Query Stage + * 1. Validate user-supplied module params, e.g. { period: '1d' } + * 2. Merge query params: (module defaults, user-supplied overrides, etc) + * 3. Optionally transform query params + * + * 2. Call lib/yahooFinanceFetch + * + * 3. Result Stage + * 1. Optional transform the result + * 2. Validate the result and coerce types + * + * Further info below, inline. + */ + +import { validateAndCoerceTypebox } from "./validateAndCoerceTypes.js"; +import csv2json from "./csv2json.js"; +import { TSchema } from "@sinclair/typebox"; + +/* +interface TransformFunc { + (result: { [key: string]: any }): { [key: string]: any }; +} +*/ + +interface ModuleExecOptions { + /** + * Name of the module, e.g. "search", "quoteSummary", etc. Used in error + * reporting. + */ + moduleName: string; + + query: { + /** + * If given, a runtime assertion is performed to check that the given + * argument is a string. If not, a helpful error is thrown. + */ + assertSymbol?: string; + /** + * URL of the API to query, WITHOUT query params. + */ + url: string; + /** + * The schema to use to validate the options overrides + */ + schema: TSchema; + /** + * Defaults for this query, e.g. { period: '1d' } in history, + * and other required options that aren't often changed { locale: 'en' }. + */ + defaults: any; + /** + * Query parameters generated inside the module, most commonly something + * like { q: query } to take e.g. yf.search(query) and pass it how Yahoo + * expects it. + */ + runtime?: any; + /** + * Query options passed by the user that will override the default and + * runtime params. Will be validated with schemaKey. + */ + overrides: any; + /** + * Called with the merged (defaults,runtime,overrides) before running + * the query. Useful to transform options we allow but not Yahoo, e.g. + * allow a "2020-01-01" date but transform this to a UNIX epoch. + */ + transformWith?: (opts: TOpts) => unknown; + /** + * Default: 'json'. Can be 'text' or 'csv' (augments fetch's "text"). + */ + fetchType?: string; + /** + * Default: false. This request requires Yahoo cookies & crumb. + */ + needsCrumb?: boolean; + }; + + result: { + /** + * The schema to validate (and coerce) the retruned result from Yahoo. + */ + schema: TSchema; + /** + * Mutate the Yahoo result *before* validating and coercion. Mostly used + * to e.g. throw if no (resault.returnField) and return result.returnField. + */ + transformWith?: (result: unknown) => TResult; + }; + + moduleOptions?: { + /** + * Allow validation failures to pass if false; + */ + validateResult?: boolean; + /** + * Any options to pass to fetch() just for this request. + */ + fetchOptions?: any; + }; +} + +async function moduleExec( + this: { [key: string]: any }, + opts: ModuleExecOptions +) { + const queryOpts = opts.query; + const moduleOpts = opts.moduleOptions; + const moduleName = opts.moduleName; + const resultOpts = opts.result; + + if (queryOpts.assertSymbol) { + const symbol = queryOpts.assertSymbol; + if (typeof symbol !== "string") + throw new Error( + `yahooFinance.${moduleName}() expects a single string symbol as its ` + + `query, not a(n) ${typeof symbol}: ${JSON.stringify(symbol)}` + ); + } + + // Check that query options passed by the user are valid for this module + validateAndCoerceTypebox({ + type: "options", + data: queryOpts.overrides ?? {}, + schema: queryOpts.schema, + options: this._opts.validation, + }); + + let queryOptions = { + ...queryOpts.defaults, // Module defaults e.g. { period: '1wk', lang: 'en' } + ...queryOpts.runtime, // Runtime params e.g. { q: query } + ...queryOpts.overrides, // User supplied options that override above + }; + + /* + * Called with the merged (defaults,runtime,overrides) before running + * the query. Useful to transform options we allow but not Yahoo, e.g. + * allow a "2020-01-01" date but transform this to a UNIX epoch. + */ + if (queryOpts.transformWith) { + queryOptions = queryOpts.transformWith(queryOptions); + } + + // this._fetch is lib/yahooFinanceFetch + let result = await this._fetch( + queryOpts.url, + queryOptions, + moduleOpts, + queryOpts.fetchType, + queryOpts.needsCrumb ?? false + ); + + if (queryOpts.fetchType === "csv") { + result = csv2json(result); + } + + /* + * Mutate the Yahoo result *before* validating and coercion. Mostly used + * to e.g. throw if no (result.returnField) and return result.returnField. + */ + if (resultOpts.transformWith) { + result = resultOpts.transformWith(result); + } + + const validateResult = + !moduleOpts || + moduleOpts.validateResult === undefined || + moduleOpts.validateResult === true; + + const validationOpts = { + ...this._opts.validation, + // Set logErrors=false if validateResult=false + logErrors: validateResult ? this._opts.validation.logErrors : false, + }; + + /* + * Validate the returned result (after transforming, above) and coerce types. + * + * The coersion works as follows: if we're expecting a "Date" type, but Yahoo + * gives us { raw: 1231421524, fmt: "2020-01-01" }, we'll return that as + * `new Date(1231421524 * 1000)`. + * + * Beyond that, ensures that user won't process unexpected data, in two + * cases: + * + * a) Missing required properties or unexpected additional properties + * b) A total new change in format that we really have no idea what to do + * with, e.g. a new kind of Date that we've never seen before and + * + * The idea is that if you receive a result, it's safe to use / store in + * database, etc. Otherwise you'll receive an error. + */ + try { + validateAndCoerceTypebox({ + type: "result", + data: result, + schema: resultOpts.schema, + options: validationOpts, + }); + } catch (error) { + if (validateResult) throw error; + } + + return result as any; +} + +export default moduleExec; diff --git a/src/lib/validateAndCoerceTypes.spec.ts b/src/lib/validateAndCoerceTypes.spec.ts index 4a836b20..dc5efe01 100644 --- a/src/lib/validateAndCoerceTypes.spec.ts +++ b/src/lib/validateAndCoerceTypes.spec.ts @@ -1,8 +1,11 @@ import { jest } from "@jest/globals"; import validateAndCoerceTypes, { ajv } from "./validateAndCoerceTypes.js"; +import { validateAndCoerceTypebox } from "./validateAndCoerceTypes.js"; import type { ValidateParams } from "./validateAndCoerceTypes.js"; import { InvalidOptionsError, FailedYahooValidationError } from "./errors.js"; +import { Type } from "@sinclair/typebox"; +import { YahooFinanceDate, YahooNumber } from "./yahooFinanceTypes.js"; ajv.addSchema({ $id: "testSchema", @@ -404,3 +407,361 @@ describe("validateAndCoerceTypes", () => { }); }); }); + +describe("validateAndCoerceTypebox", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it("Should throw a sensible error on failure", () => { + const testCase = { date: { weird: 123 } }; + const testSchema = Type.Object({ + date: YahooFinanceDate, + }); + + let error; + try { + validateAndCoerceTypebox({ + type: "result", + data: testCase, + schema: testSchema, + options: {}, + }); + } catch (e) { + error = e; + } + + expect(error).toMatchInlineSnapshot( + `[Error: Unable to decode value as it does not match the expected schema]` + ); + // TODO: Fix Jest types here + // @ts-ignore + expect(JSON.stringify(error?.error, null, 2)).toMatchInlineSnapshot(` + "{ + "type": 62, + "schema": { + "title": "YahooFinanceDate", + "anyOf": [ + { + "type": "Date" + }, + { + "type": "number" + }, + { + "title": "RawDateObject", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "title": "ISOStringDate", + "anyOf": [ + { + "format": "date", + "type": "string" + }, + { + "format": "year", + "type": "string" + }, + { + "format": "date-time", + "type": "string" + } + ] + } + ] + }, + "path": "/date", + "value": { + "weird": 123 + }, + "message": "Expected union value" + }" + `); + + // TODO: Fix Jest types here + // @ts-ignore + expect(JSON.stringify(error?.value, null, 2)).toMatchInlineSnapshot(` + "{ + "date": { + "weird": 123 + } + }" + `); + }); + it("Should log errors when logErrors = true", () => { + const logSpy = jest.spyOn(console, "log"); + const logSpyFn = jest.fn(() => undefined); + logSpy.mockImplementation(logSpyFn); + + const testSchema = Type.Object({ + aNumber: YahooNumber, + }); + const testCase = { aNumber: "foo" }; + expect(() => { + validateAndCoerceTypebox({ + type: "result", + data: testCase, + schema: testSchema, + options: { + logErrors: true, + }, + }); + }).toThrow(); + expect(logSpy).toHaveBeenCalledTimes(2); + expect(logSpyFn).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + "{ + "schema": { + "type": "object", + "properties": { + "aNumber": { + "title": "YahooNumber", + "anyOf": [ + { + "title": "RawNumber", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "type": "number" + } + ] + } + }, + "required": [ + "aNumber" + ] + }, + "value": { + "aNumber": "foo" + }, + "error": { + "type": 62, + "schema": { + "title": "YahooNumber", + "anyOf": [ + { + "title": "RawNumber", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "type": "number" + } + ] + }, + "path": "/aNumber", + "value": "foo", + "message": "Expected union value" + } + }", + ], + [ + " + This may happen intermittently and you should catch errors appropriately. + However: 1) if this recently started happening on every request for a symbol + that used to work, Yahoo may have changed their API. 2) If this happens on + every request for a symbol you've never used before, but not for other + symbols, you've found an edge-case (OR, we may just be protecting you from + "bad" data sometimes stored for e.g. misspelt symbols on Yahoo's side). + Please see if anyone has reported this previously: + + https://github.com/gadicc/node-yahoo-finance2/issues?q=is%3Aissue+undefined + + or open a new issue (and mention the symbol): yahoo-finance2 v0.0.1 + + https://github.com/gadicc/node-yahoo-finance2/issues/new?labels=bug%2C+validation&template=validation.md&title=undefined + + For information on how to turn off the above logging or skip these errors, + see https://github.com/gadicc/node-yahoo-finance2/tree/devel/docs/validation.md. + + At the end of the doc, there's also a section on how to + [Help Fix Validation Errors](https://github.com/gadicc/node-yahoo-finance2/blob/devel/docs/validation.md#help-fix) + in case you'd like to contribute to the project. Most of the time, these + fixes are very quick and easy; it's just hard for our small core team to keep up, + so help is always appreciated! + ", + ], + ], + "results": [ + { + "type": "return", + "value": undefined, + }, + { + "type": "return", + "value": undefined, + }, + ], + } + `); + }); + it("Should not log errors when logErrors = false", () => { + const logSpy = jest.spyOn(console, "log"); + const logSpyFn = jest.fn(() => undefined); + logSpy.mockImplementation(logSpyFn); + + const testSchema = Type.Object({ + aNumber: YahooNumber, + }); + const testCase = { aNumber: "foo" }; + expect(() => { + validateAndCoerceTypebox({ + type: "result", + data: testCase, + schema: testSchema, + options: { + logErrors: false, + }, + }); + }).toThrow(); + expect(logSpy).toHaveBeenCalledTimes(0); + }); + + it("Should log options errors when logOptionsErrors = true", () => { + const logSpy = jest.spyOn(console, "error"); + const logSpyFn = jest.fn(() => undefined); + logSpy.mockImplementation(logSpyFn); + + const testSchema = Type.Object({ + aNumber: YahooNumber, + }); + const testCase = { aNumber: "foo" }; + expect(() => { + validateAndCoerceTypebox({ + type: "options", + data: testCase, + schema: testSchema, + options: { + // @ts-ignore + logErrors: "bananas", + logOptionsErrors: true, + }, + }); + }).toThrow(); + expect(logSpy).toHaveBeenCalledTimes(1); + expect(logSpyFn).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + "[yahooFinance] Invalid options ("{ + "type": 62, + "schema": { + "title": "YahooNumber", + "anyOf": [ + { + "title": "RawNumber", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "type": "number" + } + ] + }, + "path": "/aNumber", + "value": "foo", + "message": "Expected union value" + }")", + ], + ], + "results": [ + { + "type": "return", + "value": undefined, + }, + ], + } + `); + }); + + it("Should not log options errors when logOptionsErrors = false", () => { + const logSpy = jest.spyOn(console, "error"); + const logSpyFn = jest.fn(() => undefined); + logSpy.mockImplementation(logSpyFn); + + const testSchema = Type.Object({ + aNumber: YahooNumber, + }); + const testCase = { aNumber: "foo" }; + expect(() => { + validateAndCoerceTypebox({ + type: "options", + data: testCase, + schema: testSchema, + options: { + // @ts-ignore + logErrors: "bananas", + logOptionsErrors: false, + }, + }); + }).toThrow(); + expect(logSpy).toHaveBeenCalledTimes(0); + }); + + it("Should not log errors when logErrors = true and validation succeeds", () => { + const logSpy = jest.spyOn(console, "log"); + const logSpyFn = jest.fn(() => undefined); + logSpy.mockImplementation(logSpyFn); + + const testSchema = Type.Object({ + aNumber: YahooNumber, + }); + const testCase = { aNumber: 10 }; + expect(() => { + validateAndCoerceTypebox({ + type: "result", + data: testCase, + schema: testSchema, + options: { logErrors: true }, + }); + }).not.toThrow(); + expect(logSpy).toHaveBeenCalledTimes(0); + }); + + it("Should still throw if an unexpected error occurs", () => { + const testCase = { aNumber: 10 }; + expect(() => { + validateAndCoerceTypebox({ + type: "result", + data: testCase, + // Create a TypeError to ensure that we still crash hard even + // if it's not an error we expect + // @ts-ignore + schema: undefined, + }); + }).toThrow(TypeError); + }); +}); diff --git a/src/lib/validateAndCoerceTypes.ts b/src/lib/validateAndCoerceTypes.ts index 17e7956b..99aa5ba1 100644 --- a/src/lib/validateAndCoerceTypes.ts +++ b/src/lib/validateAndCoerceTypes.ts @@ -6,6 +6,12 @@ import addFormats from "ajv-formats"; import schema from "../../schema.json"; import pkg from "../../package.json"; import { InvalidOptionsError, FailedYahooValidationError } from "./errors.js"; +import { StaticDecode, type TSchema } from "@sinclair/typebox"; +import { + TransformDecodeCheckError, + TransformDecodeError, + Value, +} from "@sinclair/typebox/value"; // https://ajv.js.org/docs/api.html#options export const ajv = new Ajv({ @@ -194,7 +200,6 @@ export interface ValidationOptions { logErrors?: boolean; logOptionsErrors?: boolean; } - export interface ValidateParams { source: string; type: "options" | "result"; @@ -226,6 +231,84 @@ function disallowAdditionalProps(show = false) { if (process.env.NODE_ENV === "test") disallowAdditionalProps(); +const handleResultError = ( + e: TransformDecodeError | TransformDecodeCheckError, + options: ValidationOptions +) => { + const title = e.schema.title; + if (options.logErrors) { + console.log(JSON.stringify(e, null, 2)); + console.log(` + This may happen intermittently and you should catch errors appropriately. + However: 1) if this recently started happening on every request for a symbol + that used to work, Yahoo may have changed their API. 2) If this happens on + every request for a symbol you've never used before, but not for other + symbols, you've found an edge-case (OR, we may just be protecting you from + "bad" data sometimes stored for e.g. misspelt symbols on Yahoo's side). + Please see if anyone has reported this previously: + + ${pkg.repository}/issues?q=is%3Aissue+${title} + + or open a new issue (and mention the symbol): ${pkg.name} v${pkg.version} + + ${pkg.repository}/issues/new?labels=bug%2C+validation&template=validation.md&title=${title} + + For information on how to turn off the above logging or skip these errors, + see https://github.com/gadicc/node-yahoo-finance2/tree/devel/docs/validation.md. + + At the end of the doc, there's also a section on how to + [Help Fix Validation Errors](https://github.com/gadicc/node-yahoo-finance2/blob/devel/docs/validation.md#help-fix) + in case you'd like to contribute to the project. Most of the time, these + fixes are very quick and easy; it's just hard for our small core team to keep up, + so help is always appreciated! + `); + } + throw e; +}; + +const handleOptionsError = ( + e: TransformDecodeCheckError | TransformDecodeError, + { logOptionsErrors }: ValidationOptions +) => { + if (logOptionsErrors) { + console.error( + `[yahooFinance] Invalid options ("${JSON.stringify(e.error, null, 2)}")` + ); + } + throw new InvalidOptionsError("Validation called with invalid options"); +}; + +export const validateAndCoerceTypebox = ({ + type, + data, + schema, + options, +}: { + type?: "result" | "options"; + data: unknown; + schema: T; + options: ValidationOptions; +}): StaticDecode => { + try { + return Value.Decode(schema, data); + } catch (e) { + if ( + e instanceof TransformDecodeError || + e instanceof TransformDecodeCheckError + ) { + // TODO: The existing implementation of 'validate' assumes that the `type` parameter may not be provided + // and defaults to validating the options if it is not. + // We should probably explore validating this further up in the call chain. + // It'd be nice to do this in the body of a module (e.g. search) so that we can avoid + // polluting core code with type checks and edge cases + type === "result" + ? handleResultError(e, options) + : handleOptionsError(e, options); + } + throw e; + } +}; + function validate({ source, type, diff --git a/src/lib/yahooFinanceTypes.spec.ts b/src/lib/yahooFinanceTypes.spec.ts new file mode 100644 index 00000000..abfcf60b --- /dev/null +++ b/src/lib/yahooFinanceTypes.spec.ts @@ -0,0 +1,239 @@ +import { Value } from "@sinclair/typebox/value"; +import { + EmptyObjectCoerceToNull, + EpochTimestamp, + ISOStringDate, + NullableYahooFinanceDate, + NullableYahooNumber, + RawDateObject, + RawNumber, + TwoNumberRangeString, + YahooDateInMs, + YahooFinanceDate, + YahooNumber, + YahooTwoNumberRange, +} from "./yahooFinanceTypes"; + +describe("NullableYahooNumber", () => { + it("Should pass regular numbers", () => { + const testCase = 2; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(true); + expect(Value.Decode(NullableYahooNumber, testCase)).toBe(2); + }); + it("Should coerce raw number objects", () => { + const testCase = { raw: 0.006599537, fmt: "6.5%" }; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(true); + expect(Value.Decode(NullableYahooNumber, testCase)).toBe(0.006599537); + }); + it("Should pass nulls through", () => { + const testCase = null; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(true); + expect(Value.Decode(NullableYahooNumber, testCase)).toBe(null); + }); + it("Should reject invalid objects", () => { + const testCase = { number: 10 }; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(false); + }); + + it("Should coerce an empty object to null", () => { + const testCase = {}; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(true); + expect(Value.Decode(NullableYahooNumber, testCase)).toBe(null); + }); + + it("Should fail if data.raw is not a number", () => { + const testCase = { raw: true, fmt: "bananas" }; + expect(Value.Check(NullableYahooNumber, testCase)).toBe(false); + }); +}); +describe("YahooNumber", () => { + it("Should not accept empty objects as valid inputs", () => { + const testCase = {}; + expect(Value.Check(YahooNumber, testCase)).toBe(false); + }); +}); + +describe("YahooTwoNumberRange", () => { + it("Should correctly parse a string two number range", () => { + const testCase = "-549.867 - -541.19"; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(true); + expect(Value.Decode(YahooTwoNumberRange, testCase)).toMatchInlineSnapshot(` + { + "high": -541.19, + "low": -549.867, + } + `); + }); + + it("Should throw for invalid string ranges", () => { + const testCase = "X - 523.12"; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(false); + }); + it("Should reject null", () => { + const testCase = null; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(false); + }); + + it("Should pass through a valid object range", () => { + const testCase = { low: 10.24, high: 100.453 }; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(true); + expect(Value.Decode(YahooTwoNumberRange, testCase)).toMatchInlineSnapshot(` + { + "high": 100.453, + "low": 10.24, + } + `); + }); + + it("Should fail if an invalid type is provided for an expected object key", () => { + const testCase = { low: true, high: undefined }; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(false); + }); + + it("Should fail if an object is malformed", () => { + const testCase = { africa: "by Toto" }; + expect(Value.Check(YahooTwoNumberRange, testCase)).toBe(false); + }); +}); + +describe("YahooFinanceDate", () => { + it("Should coerce a raw date object correctly", () => { + const testCase = { raw: 1612313997 }; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(true); + const decoded = Value.Decode(YahooFinanceDate, testCase); + expect(decoded).toMatchInlineSnapshot(`2021-02-03T00:59:57.000Z`); + expect(+decoded).toBe(testCase.raw * 1000); + }); + + it("Should coerce timestamps from the UNIX epoch to the date using MS", () => { + const testCase = 1612313997; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(true); + const decoded = Value.Decode(YahooFinanceDate, testCase); + expect(+decoded).toBe(testCase * 1000); + }); + + it("Should parse an ISO8601 date correctly", () => { + const testCase = "2024-02-29"; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(true); + expect(Value.Decode(YahooFinanceDate, testCase)).toMatchInlineSnapshot( + `2024-02-29T00:00:00.000Z` + ); + }); + + it("Should parse an ISO8601 datetime correctly", () => { + const testCase = "2024-05-04T13:24:41.100Z"; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(true); + expect(Value.Decode(YahooFinanceDate, testCase)).toMatchInlineSnapshot( + `2024-05-04T13:24:41.100Z` + ); + }); + + it("Should parse an ISO8601 year correctly", () => { + const testCase = "2024-05-04"; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(true); + expect(Value.Decode(YahooFinanceDate, testCase)).toMatchInlineSnapshot( + `2024-05-04T00:00:00.000Z` + ); + }); + + it("Should reject obviously garbled strings", () => { + const testCase = "fdsfsdfsd"; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(false); + }); + it("Should reject null", () => { + const testCase = null; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(false); + }); + + it("Should reject an empty object", () => { + const testCase = {}; + expect(Value.Check(YahooFinanceDate, testCase)).toBe(false); + }); +}); + +describe("NullableYahooFinanceDate", () => { + it("Should accept null", () => { + const testCase = null; + expect(Value.Check(NullableYahooFinanceDate, testCase)).toBe(true); + expect(Value.Decode(NullableYahooFinanceDate, testCase)).toBe(null); + }); + it("Should coerce an empty object to null", () => { + const testCase = {}; + expect(Value.Check(NullableYahooFinanceDate, testCase)).toBe(true); + expect(Value.Decode(NullableYahooFinanceDate, testCase)).toBe(null); + }); +}); + +describe("YahooDateInMs", () => { + it("Should coerce a date in ms to a date object", () => { + const testCase = 1612313997000; + expect(Value.Check(YahooDateInMs, testCase)).toBe(true); + expect(Value.Decode(YahooDateInMs, testCase)).toMatchInlineSnapshot( + `2021-02-03T00:59:57.000Z` + ); + }); +}); + +/* +This test suite isn't super essential, it's just here for the sake of completeness. + +To generate decoded types for Typebox an `encode` function must also be provided. Given that +those functions may be called we should unit test them even though in most cases they are trivial. +*/ +describe("Test building block type encoding", () => { + it("Should encode EmptyObjectCoerceToNull correctly", () => { + const testCase = {}; + expect( + Value.Encode( + EmptyObjectCoerceToNull, + Value.Decode(EmptyObjectCoerceToNull, testCase) + ) + ).toMatchInlineSnapshot(`{}`); + }); + it("Should encode RawNumber correctly", () => { + const testCase = { raw: 10, fmt: "0.06f" }; + expect(Value.Encode(RawNumber, Value.Decode(RawNumber, testCase))) + .toMatchInlineSnapshot(` + { + "raw": 10, + } + `); + }); + it("Should encode TwoNumberRangeString correctly", () => { + const testCase = "10 - 20"; + expect( + Value.Encode( + TwoNumberRangeString, + Value.Decode(TwoNumberRangeString, testCase) + ) + ).toBe("10 - 20"); + }); + it("Should encode the epoch timestamp back to seconds", () => { + const testCase = 1000; + expect( + Value.Encode(EpochTimestamp, Value.Decode(EpochTimestamp, testCase)) + ).toBe(testCase); + }); + + it("Should encode the raw date back to an object", () => { + const testCase = { raw: 1000 }; + expect(Value.Encode(RawDateObject, Value.Decode(RawDateObject, testCase))) + .toMatchInlineSnapshot(` + { + "raw": 1000, + } + `); + }); + it("Should encode the ISOStringDate back to a valid ISOString", () => { + const testCase = new Date(1000).toISOString(); + expect( + Value.Encode(ISOStringDate, Value.Decode(ISOStringDate, testCase)) + ).toBe(testCase); + }); + it("Should encode the YahooDateInMs back to MS", () => { + const testCase = +new Date(1000); + expect( + Value.Encode(YahooDateInMs, Value.Decode(YahooDateInMs, testCase)) + ).toBe(testCase); + }); +}); diff --git a/src/lib/yahooFinanceTypes.ts b/src/lib/yahooFinanceTypes.ts new file mode 100644 index 00000000..3cef2f5b --- /dev/null +++ b/src/lib/yahooFinanceTypes.ts @@ -0,0 +1,161 @@ +import { Type } from "@sinclair/typebox"; +import { Value } from "@sinclair/typebox/value"; +import { FormatRegistry } from "@sinclair/typebox"; +import { isDate, isDateTime, isYear } from "./datetime"; + +FormatRegistry.Set("date", isDate); +FormatRegistry.Set("date-time", isDateTime); +FormatRegistry.Set("year", isYear); + +// Strictly must be empty +export const EmptyObjectCoerceToNull = Type.Transform( + Type.Object({}, { maxProperties: 0, title: "EmptyObjectCoerceToNull" }) +) + .Decode(() => null) + .Encode(() => ({})); + +// Technically this will also contain a string 'fmt' key but we don't care because we don't use it +export const RawNumber = Type.Transform( + Type.Object( + { + raw: Type.Number(), + }, + { + title: "RawNumber", + } + ) +) + .Decode((v) => v.raw) + .Encode((v) => ({ raw: v })); + +export const TwoNumberRangeString = Type.Transform( + Type.RegExp(/^(-?\d+(?:\.\d+)?) - (-?\d+(?:\.\d+)?)$/g, { + title: "TwoNumberRangeString", + }) +) + .Decode((value) => { + // Split the two numbers allowing for negatives on either side + const validatedNumbers = value.match(/-?\d+(?:\.\d+)?/g); + + if (!validatedNumbers) { + throw new Error(`Unable to decode number range from: ${value}`); + } + + const [low, high] = validatedNumbers.map((number) => parseFloat(number)); + + if (isNaN(low) || isNaN(high)) { + throw new Error( + `Unable to decode number range from: ${value}. Decoded value for low is: ${low}, decoded value for high is: ${high}` + ); + } + return { low, high }; + }) + .Encode(({ low, high }) => `${low} - ${high}`); + +const TwoNumberRange = Type.Object( + { + low: Type.Number(), + high: Type.Number(), + }, + { title: "TwoNumberRange" } +); + +export const EpochTimestamp = Type.Transform(Type.Number()) + .Decode((v) => new Date(v * 1000)) + .Encode((v) => +v / 1000); + +export const RawDateObject = Type.Transform( + Type.Object( + { + raw: EpochTimestamp, + }, + { title: "RawDateObject" } + ) +) + .Decode((v) => v.raw) + .Encode((v) => ({ + raw: Value.Encode(EpochTimestamp, v), + })); + +export const ISOStringDate = Type.Transform( + Type.Union( + [ + Type.String({ format: "date" }), + Type.String({ format: "year" }), + Type.String({ format: "date-time" }), + ], + { title: "ISOStringDate" } + ) +) + .Decode((v) => new Date(v)) + .Encode((v) => v.toISOString()); + +export const YahooFinanceDate = Type.Union( + [Type.Date(), EpochTimestamp, RawDateObject, ISOStringDate], + { title: "YahooFinanceDate" } +); + +/** + * Validates and decodes all nullable date representations produced by Yahoo + * e.g. accepted inputs include: + * - 1612313997 + * - { raw: 1612313997 } + * - "2024-02-29" + * - "2024-05-04T13:24:41.100Z" + * - {} (coerces to null) + */ +export const NullableYahooFinanceDate = Type.Union( + [YahooFinanceDate, Type.Null(), EmptyObjectCoerceToNull], + { + title: "NullableYahooFinanceDate", + } +); + +/** + * Validates and decodes all number types and coerces to a number + * e.g. accepted inputs include: + * - 10.54 + * - {raw: 10.54, fmt: "%6f"} + */ + +export const YahooNumber = Type.Union([RawNumber, Type.Number()], { + title: "YahooNumber", +}); + +/** + * Validates and decodes dates represented as milliseconds since the unix epoch to Date objects + * e.g. accepted inputs include: + * - 1612313997000 + */ +export const YahooDateInMs = Type.Transform( + Type.Number({ title: "YahooDateInMs" }) +) + .Decode((v) => new Date(v)) + .Encode((v) => +v); +/** + * Validates and decodes all nullable number types and coerces to a number or null + * e.g. accepted inputs include: + * - 10.54 + * - {raw: 10.54, fmt: "%6f"} + * - null + * - {} (coerces to null) + */ +export const NullableYahooNumber = Type.Union( + [RawNumber, EmptyObjectCoerceToNull, Type.Number(), Type.Null()], + { + title: "NullableYahooNumber", + } +); + +/** + * Validates and decodes 2 number ranges to a consistent object format of { low: , high: } + * e.g. accepted inputs include: + * - { low: 103, high: 10043 } + * - "-32432 - 453" + */ +export const YahooTwoNumberRange = Type.Union( + [TwoNumberRange, TwoNumberRangeString], + { + title: "YahooTwoNumberRange", + } +); diff --git a/tests/http/getCrumb-getcrumb b/tests/http/getCrumb-getcrumb new file mode 100644 index 00000000..1cd08dd9 --- /dev/null +++ b/tests/http/getCrumb-getcrumb @@ -0,0 +1,67 @@ +{ + "request": { + "url": "https://query2.finance.yahoo.com/v1/test/getcrumb" + }, + "response": { + "ok": true, + "status": 200, + "statusText": "OK", + "headers": { + "content-type": [ + "text/plain;charset=utf-8" + ], + "access-control-allow-origin": [ + "https://finance.yahoo.com" + ], + "access-control-allow-credentials": [ + "true" + ], + "y-rid": [ + "4o3fbchj9sck2" + ], + "cache-control": [ + "private, max-age=60, stale-while-revalidate=30" + ], + "vary": [ + "Origin,Accept-Encoding" + ], + "content-length": [ + "11" + ], + "x-envoy-upstream-service-time": [ + "1" + ], + "date": [ + "Mon, 22 Jul 2024 10:20:50 GMT" + ], + "server": [ + "ATS" + ], + "x-envoy-decorator-operation": [ + "finance-external-services-api--mtls-production-sg3.finance-k8s.svc.yahoo.local:4080/*" + ], + "age": [ + "0" + ], + "strict-transport-security": [ + "max-age=31536000" + ], + "referrer-policy": [ + "no-referrer-when-downgrade" + ], + "connection": [ + "close" + ], + "expect-ct": [ + "max-age=31536000, report-uri=\"http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only\"" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-content-type-options": [ + "nosniff" + ] + }, + "body": "mloUP8q7ZPH" + } +} \ No newline at end of file diff --git a/tests/testYf.ts b/tests/testYf.ts index 2936e3a0..79aa1f2a 100644 --- a/tests/testYf.ts +++ b/tests/testYf.ts @@ -2,6 +2,7 @@ import _env from "../src/env-test"; import _opts from "../src/lib/options"; import _fetch from "../src/lib/yahooFinanceFetch"; import _moduleExec from "../src/lib/moduleExec"; +import _moduleExecTypebox from "../src/lib/moduleExecTypebox"; export default function genYf(extend: object): any { return { @@ -9,6 +10,7 @@ export default function genYf(extend: object): any { _opts, _fetch, _moduleExec, + _moduleExecTypebox, ...extend, }; } diff --git a/yarn.lock b/yarn.lock index d3a57ce5..9ca568da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1510,6 +1510,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sinclair/typebox@^0.32.27": + version "0.32.27" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.32.27.tgz#4cf1c777318c482da530cc667a1eeaaefcd2c521" + integrity sha512-JHRrubCKiXi6VKlbBTpTQnExkUFasPMIaXCJYJhqVBGLliQVt1yBZZgiZo3/uSmvAdXlIIdGoTAT6RB09L0QqA== + "@sinonjs/commons@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" From 81cb253f71c449af73842431238730f62a47a54d Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sat, 11 May 2024 21:47:27 +0800 Subject: [PATCH 02/13] Add Typebox search module --- src/lib/moduleExec.spec.ts | 5 +- src/lib/moduleExecTypebox.spec.ts | 2 +- src/modules/search.spec.ts | 4 +- src/modules/search.ts | 378 ++++++++++++++++++---------- tests/http/quote-AAPL.bad.fake.json | 73 ++++++ 5 files changed, 321 insertions(+), 141 deletions(-) create mode 100644 tests/http/quote-AAPL.bad.fake.json diff --git a/src/lib/moduleExec.spec.ts b/src/lib/moduleExec.spec.ts index cc2496db..655d3884 100644 --- a/src/lib/moduleExec.spec.ts +++ b/src/lib/moduleExec.spec.ts @@ -4,6 +4,7 @@ import search from "../modules/search.js"; import chart from "../modules/chart.js"; import { InvalidOptionsError } from "./errors.js"; import testYf from "../../tests/testYf.js"; +import { TransformDecodeCheckError } from "@sinclair/typebox/value"; const yf = testYf({ search, chart }); yf._opts.validation.logOptionsErrors = false; @@ -54,7 +55,7 @@ describe("moduleExec", () => { fakeConsole.log.mock.calls.length + fakeConsole.error.mock.calls.length + fakeConsole.dir.mock.calls.length - ).toBeGreaterThan(1); + ).toBe(1); yf._opts.validation.logOptionsErrors = false; }); @@ -83,7 +84,7 @@ describe("moduleExec", () => { yf._opts.validation.logErrors = false; await expect( yf.search("AAPL", {}, { devel: "search-badResult.fake.json" }) - ).rejects.toThrow(/Failed Yahoo Schema/); + ).rejects.toThrow(TransformDecodeCheckError); yf._opts.validation.logErrors = true; }); diff --git a/src/lib/moduleExecTypebox.spec.ts b/src/lib/moduleExecTypebox.spec.ts index 93a96910..5e6630da 100644 --- a/src/lib/moduleExecTypebox.spec.ts +++ b/src/lib/moduleExecTypebox.spec.ts @@ -1,6 +1,6 @@ import { jest } from "@jest/globals"; -import search from "../modules/search.tb.js"; +import search from "../modules/search.js"; import { InvalidOptionsError } from "./errors.js"; import testYf from "../../tests/testYf.js"; import { TransformDecodeCheckError } from "@sinclair/typebox/value"; diff --git a/src/modules/search.spec.ts b/src/modules/search.spec.ts index 805f83a3..b8e1bef8 100644 --- a/src/modules/search.spec.ts +++ b/src/modules/search.spec.ts @@ -1,14 +1,13 @@ import search from "./search.js"; -import { InvalidOptionsError } from "../lib/errors.js"; import testSymbols from "../../tests/testSymbols.js"; import testYf from "../../tests/testYf.js"; const yf = testYf({ search }); +yf._opts.validation.logErrors = true; describe("search", () => { // See also common module tests in moduleExec.spec.js - const testSearches = testSymbols({ add: [ "Evolution Gaming Group", // STO @@ -18,7 +17,6 @@ describe("search", () => { "BJ0CDD2", // additionalProperty: { exchDisp: "London" } ], }); - // validate different searches it.each(testSearches)( "passed validation for search '%s'", diff --git a/src/modules/search.ts b/src/modules/search.ts index a56dd474..237d8300 100644 --- a/src/modules/search.ts +++ b/src/modules/search.ts @@ -1,148 +1,255 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { YahooFinanceDate, YahooNumber } from "../lib/yahooFinanceTypes.js"; -export interface SearchQuoteYahoo { - [key: string]: any; - symbol: string; // "BABA" - isYahooFinance: true; // true - exchange: string; // "NYQ" - exchDisp?: string; // "London" e.g. with BJ0CDD2 - shortname?: string; // "Alibaba Group Holding Limited" - longname?: string; // "Alibaba Group Holding Limited" - index: "quotes"; // "quotes" - score: number; // 1111958.0 - newListingDate?: Date; // "2021-02-16" - prevName?: string; - nameChangeDate?: Date; - sector?: string; // "Industrials" - industry?: string; // "Building Products & Equipment" - dispSecIndFlag?: boolean; // true -} -export interface SearchQuoteYahooEquity extends SearchQuoteYahoo { - quoteType: "EQUITY"; - typeDisp: "Equity"; -} -export interface SearchQuoteYahooOption extends SearchQuoteYahoo { - quoteType: "OPTION"; - typeDisp: "Option"; -} -export interface SearchQuoteYahooETF extends SearchQuoteYahoo { - quoteType: "ETF"; - typeDisp: "ETF"; // "Option" -} -export interface SearchQuoteYahooFund extends SearchQuoteYahoo { - quoteType: "MUTUALFUND"; - typeDisp: "Fund"; -} -export interface SearchQuoteYahooIndex extends SearchQuoteYahoo { - quoteType: "INDEX"; - typeDisp: "Index"; -} -export interface SearchQuoteYahooCurrency extends SearchQuoteYahoo { - quoteType: "CURRENCY"; - typeDisp: "Currency"; -} -export interface SearchQuoteYahooCryptocurrency extends SearchQuoteYahoo { - quoteType: "CRYPTOCURRENCY"; - typeDisp: "Cryptocurrency"; -} +const SearchQuoteYahoo = Type.Object( + { + symbol: Type.String(), // "BABA" + isYahooFinance: Type.Literal(true), // true + exchange: Type.String(), // "NYQ" + exchDisp: Type.Optional(Type.String()), // "London", e.g. with BJ0CDD2 + shortname: Type.Optional(Type.String()), // "Alibaba Group Holding Limited" + longname: Type.Optional(Type.String()), // "Alibaba Group Holding Limited" + index: Type.Literal("quotes"), // "quotes" + score: YahooNumber, // 1111958.0 + newListingDate: Type.Optional(YahooFinanceDate), // "2021-02-16" + prevName: Type.Optional(Type.String()), + nameChangeDate: Type.Optional(YahooFinanceDate), + sector: Type.Optional(Type.String()), // "Industrials" + industry: Type.Optional(Type.String()), // "Building Products & Equipment" + dispSecIndFlag: Type.Optional(Type.Boolean()), // true + }, + { + additionalProperties: Type.Any(), + } +); -export interface SearchQuoteYahooFuture extends SearchQuoteYahoo { - quoteType: "FUTURE"; - typeDisp: "Future" | "Futures"; -} +const SearchQuoteYahooEquity = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("EQUITY"), + typeDisp: Type.Literal("Equity"), + }), + ], + { + title: "SearchQuoteYahooEntity", + } +); -export interface SearchQuoteNonYahoo { - [key: string]: any; - index: string; // '78ddc07626ff4bbcae663e88514c23a0' - name: string; // 'AAPlasma' - permalink: string; // 'aaplasma', - isYahooFinance: false; // false -} +const SearchQuoteYahooOption = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("OPTION"), + typeDisp: Type.Literal("Option"), + }), + ], + { + title: "SearchQuoteYahooOption", + } +); -export interface SearchNews { - [key: string]: any; - uuid: string; // "9aff624a-e84c-35f3-9c23-db39852006dc" - title: string; // "Analyst Report: Alibaba Group Holding Limited" - publisher: string; // "Morningstar Research" - link: string; // "https://finance.yahoo.com/m/9aff624a-e84c-35f3-9c23-db39852006dc/analyst-report%3A-alibaba-group.html" - providerPublishTime: Date; // coerced to new Date(1611286342 * 1000) - type: string; // "STORY" TODO "STORY" | ??? - thumbnail?: { resolutions: SearchNewsThumbnailResolution[] }; - relatedTickers?: string[]; // [ "AAPL" ] -} +const SearchQuoteYahooETF = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("ETF"), + typeDisp: Type.Literal("ETF"), + }), + ], + { + title: "SearchQuoteYahooETF", + } +); -export interface SearchNewsThumbnailResolution { - url: string; - width: number; - height: number; - tag: string; -} +const SearchQuoteYahooFund = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("MUTUALFUND"), + typeDisp: Type.Literal("Fund"), + }), + ], + { + title: "SearchQuoteYahooFund", + } +); -export interface SearchResult { - [key: string]: any; - explains: Array; - count: number; - quotes: Array< - | SearchQuoteYahooEquity - | SearchQuoteYahooOption - | SearchQuoteYahooETF - | SearchQuoteYahooFund - | SearchQuoteYahooIndex - | SearchQuoteYahooCurrency - | SearchQuoteYahooCryptocurrency - | SearchQuoteNonYahoo - | SearchQuoteYahooFuture - >; - news: Array; - nav: Array; - lists: Array; - researchReports: Array; - totalTime: number; - // ALWAYS present, but TEMPORARILY marked optional ("?") since its - // sudden appearance, let's make sure it doesn't get suddenly removed. - // Array until we can find some examples of what it actually looks - // like (#255). - screenerFieldResults?: Array; - // ALWAYS present, but TEMPORARILY marked optional ("?") since its - // sudden appearance, let's make sure it doesn't get suddenly removed. - // Array until we can find some examples of what it actually looks - // like (#399). - culturalAssets?: Array; - timeTakenForQuotes: number; // 26 - timeTakenForNews: number; // 419 - timeTakenForAlgowatchlist: number; // 700 - timeTakenForPredefinedScreener: number; // 400 - timeTakenForCrunchbase: number; // 400 - timeTakenForNav: number; // 400 - timeTakenForResearchReports: number; // 0 - // ALWAYS present, but TEMPORARILY marked optional ("?") since its - // sudden appearance, let's make sure it doesn't get suddenly removed. - timeTakenForScreenerField?: number; - // ALWAYS present, but TEMPORARILY marked optional ("?") since its - // sudden appearance, let's make sure it doesn't get suddenly removed. - timeTakenForCulturalAssets?: number; -} +const SearchQuoteYahooIndex = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("INDEX"), + typeDisp: Type.Literal("Index"), + }), + ], + { + title: "SearchQuoteYahooIndex", + } +); -export interface SearchOptions { - lang?: string; - region?: string; - quotesCount?: number; - newsCount?: number; - enableFuzzyQuery?: boolean; - quotesQueryId?: string; - multiQuoteQueryId?: string; - newsQueryId?: string; - enableCb?: boolean; - enableNavLinks?: boolean; - enableEnhancedTrivialQuery?: boolean; -} +const SearchQuoteYahooCurrency = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("CURRENCY"), + typeDisp: Type.Literal("Currency"), + }), + ], + { + title: "SearchQuoteYahooCurrency", + } +); + +const SearchQuoteYahooCryptocurrency = Type.Composite([ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("CRYPTOCURRENCY"), + typeDisp: Type.Literal("Cryptocurrency"), + }), +]); + +const SearchQuoteYahooFuture = Type.Composite( + [ + SearchQuoteYahoo, + Type.Object({ + quoteType: Type.Literal("FUTURE"), + typeDisp: Type.Union([Type.Literal("Future"), Type.Literal("Futures")]), + }), + ], + { + title: "SearchQuoteYahooFuture", + } +); + +const SearchQuoteNonYahoo = Type.Object( + { + index: Type.String(), // '78ddc07626ff4bbcae663e88514c23a0' + name: Type.String(), // 'AAPlasma' + permalink: Type.String(), // 'aaplasma' + isYahooFinance: Type.Literal(false), // false + }, + { + additionalProperties: Type.Any(), + title: "SearchQuoteNonYahoo", + } +); + +const SearchNewsThumbnailResolution = Type.Object( + { + url: Type.String(), + width: YahooNumber, + height: YahooNumber, + tag: Type.String(), + }, + { + title: "SearchNewsThumbnailResolution", + } +); + +const SearchNews = Type.Object( + { + uuid: Type.String(), // "9aff624a-e84c-35f3-9c23-db39852006dc" + title: Type.String(), // "Analyst Report: Alibaba Group Holding Limited" + publisher: Type.String(), // "Morningstar Research" + link: Type.String(), // "https://finance.yahoo.com/m/9aff624a-e84c-35f3-9c23-db39852006dc/analyst-report%3A-alibaba-group.html" + providerPublishTime: YahooFinanceDate, // coerced to New Date(1611285342 * 1000) + type: Type.String(), // "STORY" TODO "STORY" | ??? + thumbnail: Type.Optional( + Type.Object({ + resolutions: Type.Array(SearchNewsThumbnailResolution), + }) + ), + relatedTickers: Type.Optional(Type.Array(Type.String())), // [ "AAPL" ] + }, + { + additionalProperties: Type.Any(), + title: "SearchNews", + } +); + +type SearchResult = Static; +const SearchResultSchema = Type.Object( + { + explains: Type.Array(Type.Any()), + count: YahooNumber, + quotes: Type.Array( + Type.Union([ + SearchQuoteYahooEquity, + SearchQuoteYahooOption, + SearchQuoteYahooETF, + SearchQuoteYahooFund, + SearchQuoteYahooIndex, + SearchQuoteYahooCurrency, + SearchQuoteYahooCryptocurrency, + SearchQuoteNonYahoo, + SearchQuoteYahooFuture, + ]) + ), + news: Type.Array(SearchNews), + nav: Type.Array(Type.Any()), + lists: Type.Array(Type.Any()), + researchReports: Type.Array(Type.Any()), + totalTime: YahooNumber, + // ALWAYS present, but TEMPORARILY marked optional ("?") since its + // sudden appearance, let's make sure it doesn't get suddenly removed. + // Array until we can find some examples of what it actually looks + // like (#255). + screenerFieldResults: Type.Optional(Type.Array(Type.Any())), + // ALWAYS present, but TEMPORARILY marked optional ("?") since its + // sudden appearance, let's make sure it doesn't get suddenly removed. + // Array until we can find some examples of what it actually looks + // like (#399). + culturalAssets: Type.Optional(Type.Array(Type.Any())), + timeTakenForQuotes: YahooNumber, // 26 + timeTakenForNews: YahooNumber, // 419 + timeTakenForAlgowatchlist: YahooNumber, // 700 + timeTakenForPredefinedScreener: YahooNumber, // 400 + timeTakenForCrunchbase: YahooNumber, // 400 + timeTakenForNav: YahooNumber, // 400 + timeTakenForResearchReports: YahooNumber, // 0 + // ALWAYS present, but TEMPORARILY marked optional ("?") since its + // sudden appearance, let's make sure it doesn't get suddenly removed. + timeTakenForScreenerField: Type.Optional(YahooNumber), + // ALWAYS present, but TEMPORARILY marked optional ("?") since its + // sudden appearance, let's make sure it doesn't get suddenly removed. + timeTakenForCulturalAssets: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "SearchResults", + } +); + +const SearchOptionsSchema = Type.Object( + { + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + quotesCount: Type.Optional(YahooNumber), + newsCount: Type.Optional(YahooNumber), + enableFuzzyQuery: Type.Optional(Type.Boolean()), + quotesQueryId: Type.Optional(Type.String()), + multiQuoteQueryId: Type.Optional(Type.String()), + newsQueryId: Type.Optional(Type.String()), + enableCb: Type.Optional(Type.Boolean()), + enableNavLinks: Type.Optional(Type.Boolean()), + enableEnhancedTrivialQuery: Type.Optional(Type.Boolean()), + }, + { + title: "SearchOptions", + additionalProperties: false, + } +); + +type SearchOptions = Static; -const queryOptionsDefaults = { +const queryOptionsDefaults: SearchOptions = { lang: "en-US", region: "US", quotesCount: 6, @@ -176,19 +283,20 @@ export default function search( queryOptionsOverrides?: SearchOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ - moduleName: "search", + return this._moduleExecTypebox({ + moduleName: "searchTypebox", query: { url: "https://${YF_QUERY_HOST}/v1/finance/search", - schemaKey: "#/definitions/SearchOptions", + schema: SearchOptionsSchema, defaults: queryOptionsDefaults, runtime: { q: query }, overrides: queryOptionsOverrides, + needsCrumb: false, }, result: { - schemaKey: "#/definitions/SearchResult", + schema: SearchResultSchema, }, moduleOptions, diff --git a/tests/http/quote-AAPL.bad.fake.json b/tests/http/quote-AAPL.bad.fake.json new file mode 100644 index 00000000..e26a7cc1 --- /dev/null +++ b/tests/http/quote-AAPL.bad.fake.json @@ -0,0 +1,73 @@ +{ + "request": { + "url": "https://query2.finance.yahoo.com/v7/finance/quote?symbols=AAPL" + }, + "response": { + "ok": true, + "status": 200, + "statusText": "OK", + "headers": { + "content-type": [ + "application/json;charset=utf-8" + ], + "cache-control": [ + "public, max-age=1, stale-while-revalidate=9" + ], + "vary": [ + "Origin,Accept-Encoding" + ], + "y-rid": [ + "4n543alif8f75" + ], + "x-yahoo-request-id": [ + "4n543alif8f75" + ], + "x-request-id": [ + "d3b398d2-e2b6-4984-80a2-3f569abcc46e" + ], + "content-encoding": [ + "gzip" + ], + "content-length": [ + "1088" + ], + "x-envoy-upstream-service-time": [ + "2" + ], + "date": [ + "Sun, 03 Sep 2023 07:59:33 GMT" + ], + "server": [ + "ATS" + ], + "x-envoy-decorator-operation": [ + "finance-quote-api--mtls-production-ir2.finance-k8s.svc.yahoo.local:4080/*" + ], + "age": [ + "0" + ], + "strict-transport-security": [ + "max-age=31536000" + ], + "referrer-policy": [ + "no-referrer-when-downgrade" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "connection": [ + "close" + ], + "expect-ct": [ + "max-age=31536000, report-uri=\"http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only\"" + ], + "x-xss-protection": [ + "1; mode=block" + ], + "x-content-type-options": [ + "nosniff" + ] + }, + "body": "{\"quoteResponse\":{\"result\":[{\"language\":false,\"region\":\"US\",\"quoteType\":\"EQUITY\",\"typeDisp\":\"Equity\",\"quoteSourceName\":\"Delayed Quote\",\"triggerable\":true,\"customPriceAlertConfidence\":\"HIGH\",\"marketState\":\"CLOSED\",\"currency\":\"USD\",\"exchange\":\"NMS\",\"shortName\":\"Apple Inc.\",\"longName\":\"Apple Inc.\",\"messageBoardId\":\"finmb_24937\",\"exchangeTimezoneName\":\"America/New_York\",\"exchangeTimezoneShortName\":\"EDT\",\"gmtOffSetMilliseconds\":-14400000,\"market\":\"us_market\",\"esgPopulated\":false,\"regularMarketChangePercent\":0.8463361,\"regularMarketPrice\":189.46,\"earningsTimestampStart\":1698231540,\"earningsTimestampEnd\":1698667200,\"trailingAnnualDividendRate\":0.93,\"trailingPE\":31.735346,\"dividendRate\":0.96,\"trailingAnnualDividendYield\":0.004950232,\"dividendYield\":0.51,\"epsTrailingTwelveMonths\":5.97,\"epsForward\":6.61,\"epsCurrentYear\":6.07,\"priceEpsCurrentYear\":31.21252,\"sharesOutstanding\":15634199552,\"bookValue\":3.852,\"fiftyDayAverage\":186.7088,\"fiftyDayAverageChange\":2.7512054,\"fiftyDayAverageChangePercent\":0.014735275,\"twoHundredDayAverage\":163.40675,\"twoHundredDayAverageChange\":26.053253,\"twoHundredDayAverageChangePercent\":0.15943804,\"marketCap\":2962055495680,\"forwardPE\":28.662632,\"priceToBook\":49.18484,\"sourceInterval\":15,\"exchangeDataDelayedBy\":0,\"averageAnalystRating\":\"2.0 - Buy\",\"tradeable\":false,\"cryptoTradeable\":false,\"firstTradeDateMilliseconds\":345479400000,\"priceHint\":2,\"postMarketChangePercent\":-0.09501105,\"postMarketTime\":1693612792,\"postMarketPrice\":189.28,\"postMarketChange\":-0.18000793,\"regularMarketChange\":1.5900116,\"regularMarketTime\":1693598401,\"regularMarketDayHigh\":189.9175,\"regularMarketDayRange\":\"188.28 - 189.9175\",\"regularMarketDayLow\":188.28,\"regularMarketVolume\":42235109,\"regularMarketPreviousClose\":187.87,\"bid\":189.24,\"ask\":189.31,\"bidSize\":10,\"askSize\":8,\"fullExchangeName\":\"NasdaqGS\",\"financialCurrency\":\"USD\",\"regularMarketOpen\":189.485,\"averageDailyVolume3Month\":56039552,\"averageDailyVolume10Day\":51167990,\"fiftyTwoWeekLowChange\":65.29001,\"fiftyTwoWeekLowChangePercent\":0.5258115,\"fiftyTwoWeekRange\":\"124.17 - 198.23\",\"fiftyTwoWeekHighChange\":-8.769989,\"fiftyTwoWeekHighChangePercent\":-0.044241484,\"fiftyTwoWeekLow\":124.17,\"fiftyTwoWeekHigh\":198.23,\"fiftyTwoWeekChangePercent\":22.604025,\"dividendDate\":1692230400,\"earningsTimestamp\":1691096400,\"displayName\":\"Apple\",\"symbol\":\"AAPL\"}],\"error\":null}}" + } +} \ No newline at end of file From c008ccac0766ccc235ead62bbdcd455ff5b2e105 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:01:49 +0800 Subject: [PATCH 03/13] Add QuoteSummary Typebox module --- schema.json | 90 +- src/modules/quoteSummary-iface.ts | 2368 +++++++++++++++++------------ src/modules/quoteSummary.spec.ts | 8 +- src/modules/quoteSummary.ts | 113 +- src/modules/trendingSymbols.ts | 61 +- 5 files changed, 1518 insertions(+), 1122 deletions(-) diff --git a/schema.json b/schema.json index 6079e962..a26fcc14 100644 --- a/schema.json +++ b/schema.json @@ -8611,6 +8611,51 @@ "up" ] }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "symbol": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/QuoteSummaryOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "symbol" + ], + "additionalProperties": false + }, + "QuoteSummaryOptions": { + "type": "object", + "properties": { + "formatted": { + "type": "boolean" + }, + "modules": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/QuoteSummaryModules" + } + }, + { + "type": "string", + "const": "all" + } + ] + } + }, + "additionalProperties": false + }, "QuoteSummaryModules": { "type": "string", "enum": [ @@ -8649,51 +8694,6 @@ "upgradeDowngradeHistory" ] }, - "QuoteSummaryOptions": { - "type": "object", - "properties": { - "formatted": { - "type": "boolean" - }, - "modules": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteSummaryModules" - } - }, - { - "type": "string", - "const": "all" - } - ] - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/QuoteSummaryOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol" - ], - "additionalProperties": false - }, "RecommendationsBySymbolResponse": { "type": "object", "properties": { diff --git a/src/modules/quoteSummary-iface.ts b/src/modules/quoteSummary-iface.ts index 867c4a8f..07464cb9 100644 --- a/src/modules/quoteSummary-iface.ts +++ b/src/modules/quoteSummary-iface.ts @@ -1,1013 +1,25 @@ +import { Type } from "@sinclair/typebox"; +import { + NullableYahooFinanceDate, + NullableYahooNumber, + YahooFinanceDate, + YahooNumber, +} from "../lib/yahooFinanceTypes"; + /* * To generate the initial file, we took the output of all submodules for * 'AAPL', 'OCDO.L', '0700.HK' and '^IXIC' and ran the results through - * the awesome https://app.quicktype.io/. + * the awesome https://app.quicktype.io/ + * and then the smashing https://sinclairzx81.github.io/typebox-workbench * * Manual cleanup afterwards: * * 1) Spaces: 4 to 2 * ~~2) Wrapped in a module~~ <--- undid this after tooling issues. * 3) Alphabeticalize QuoteSummaryResult - * 4) RawNumberObj type to Date|number for coersion */ -export interface QuoteSummaryResult { - [key: string]: any; - assetProfile?: AssetProfile; - balanceSheetHistory?: BalanceSheetHistory; - balanceSheetHistoryQuarterly?: BalanceSheetHistory; - calendarEvents?: CalendarEvents; - cashflowStatementHistory?: CashflowStatementHistory; - cashflowStatementHistoryQuarterly?: CashflowStatementHistory; - defaultKeyStatistics?: DefaultKeyStatistics; - earnings?: QuoteSummaryEarnings; - earningsHistory?: EarningsHistory; - earningsTrend?: EarningsTrend; - financialData?: FinancialData; - fundOwnership?: Ownership; - fundPerformance?: FundPerformance; - fundProfile?: FundProfile; - incomeStatementHistory?: IncomeStatementHistory; - incomeStatementHistoryQuarterly?: IncomeStatementHistory; - indexTrend?: IndexTrend; - industryTrend?: Trend; - insiderHolders?: Holders; - insiderTransactions?: InsiderTransactions; - institutionOwnership?: Ownership; - majorDirectHolders?: Holders; - majorHoldersBreakdown?: MajorHoldersBreakdown; - netSharePurchaseActivity?: NetSharePurchaseActivity; - price?: Price; - quoteType?: QuoteType; - recommendationTrend?: RecommendationTrend; - secFilings?: SECFilings; - sectorTrend?: Trend; - summaryDetail?: SummaryDetail; - summaryProfile?: SummaryProfile; - topHoldings?: TopHoldings; - upgradeDowngradeHistory?: UpgradeDowngradeHistory; -} - -export interface AssetProfile { - [key: string]: any; - maxAge: number; - address1?: string; - address2?: string; - address3?: string; - city?: string; - state?: string; - zip?: string; - country?: string; - phone?: string; - fax?: string; - website?: string; - industry?: string; - industryDisp?: string; - industryKey?: string; - industrySymbol?: string; - sector?: string; - sectorDisp?: string; - sectorKey?: string; - longBusinessSummary?: string; - fullTimeEmployees?: number; - companyOfficers: CompanyOfficer[]; - auditRisk?: number; - boardRisk?: number; - compensationRisk?: number; - shareHolderRightsRisk?: number; - overallRisk?: number; - governanceEpochDate?: Date; - compensationAsOfEpochDate?: Date; - name?: string; // 'Bitcoin'; - startDate?: Date; // new Date('2013-04-28') - description?: string; // 'Bitcoin (BTC) is a cryptocurrency...' - twitter?: string; // in e.g. "ADA-USD" (#418) -} - -export interface CompanyOfficer { - [key: string]: any; - maxAge: number; - name: string; - age?: number; - title: string; - yearBorn?: number; - fiscalYear?: number; - totalPay?: number; - exercisedValue?: number; - unexercisedValue?: number; -} - -export interface BalanceSheetHistory { - [key: string]: any; - balanceSheetStatements: BalanceSheetStatement[]; - maxAge: number; -} - -export interface BalanceSheetStatement { - [key: string]: any; - maxAge: number; - endDate: Date; - cash?: number; - shortTermInvestments?: number; - netReceivables?: number; - inventory?: number; - otherCurrentAssets?: number; - totalCurrentAssets?: number; - longTermInvestments?: number; - propertyPlantEquipment?: number; - otherAssets?: number; - totalAssets?: number; - accountsPayable?: number; - shortLongTermDebt?: number; - otherCurrentLiab?: number; - longTermDebt?: number; - otherLiab?: number; - totalCurrentLiabilities?: number; - totalLiab?: number; - commonStock?: number; - retainedEarnings?: number; - treasuryStock?: number; - otherStockholderEquity?: number; - totalStockholderEquity?: number; - netTangibleAssets?: number; - goodWill?: number; - intangibleAssets?: number; - deferredLongTermAssetCharges?: number; - deferredLongTermLiab?: number; - minorityInterest?: number | null; - capitalSurplus?: number; -} - -export interface CalendarEvents { - [key: string]: any; - maxAge: number; - earnings: CalendarEventsEarnings; - exDividendDate?: Date; - dividendDate?: Date; -} - -export interface CalendarEventsEarnings { - [key: string]: any; - earningsDate: Date[]; - earningsAverage?: number; - earningsLow?: number; - earningsHigh?: number; - revenueAverage?: number; - revenueLow?: number; - revenueHigh?: number; -} - -export interface CashflowStatementHistory { - [key: string]: any; - cashflowStatements: CashflowStatement[]; - maxAge: number; -} - -export interface CashflowStatement { - [key: string]: any; - maxAge: number; - endDate: Date; - netIncome: number; - depreciation?: number; - changeToNetincome?: number; - changeToAccountReceivables?: number; - changeToLiabilities?: number; - changeToInventory?: number; - changeToOperatingActivities?: number; - totalCashFromOperatingActivities?: number; - capitalExpenditures?: number; - investments?: number; - otherCashflowsFromInvestingActivities?: number; - totalCashflowsFromInvestingActivities?: number; - dividendsPaid?: number; - netBorrowings?: number; - otherCashflowsFromFinancingActivities?: number; - totalCashFromFinancingActivities?: number; - changeInCash?: number; - repurchaseOfStock?: number; - issuanceOfStock?: number; - effectOfExchangeRate?: number; -} - -export interface DefaultKeyStatistics { - [key: string]: any; - maxAge: number; - priceHint: number; - enterpriseValue?: number; - forwardPE?: number; - profitMargins?: number; - floatShares?: number; - sharesOutstanding?: number; - sharesShort?: number; - sharesShortPriorMonth?: Date; - sharesShortPreviousMonthDate?: Date; - dateShortInterest?: Date; - sharesPercentSharesOut?: number; - heldPercentInsiders?: number; - heldPercentInstitutions?: number; - shortRatio?: number; - shortPercentOfFloat?: number; - beta?: number; - impliedSharesOutstanding?: number; - category: null | string; - bookValue?: number; - priceToBook?: number; - fundFamily: null | string; - legalType: null | string; - lastFiscalYearEnd?: Date; - nextFiscalYearEnd?: Date; - mostRecentQuarter?: Date; - earningsQuarterlyGrowth?: number; - netIncomeToCommon?: number; - trailingEps?: number; - forwardEps?: number; - pegRatio?: number; - lastSplitFactor: null | string; - lastSplitDate?: number; - enterpriseToRevenue?: number; - enterpriseToEbitda?: number; - "52WeekChange"?: number; - SandP52WeekChange?: number; - lastDividendValue?: number; - lastDividendDate?: Date; - ytdReturn?: number; - beta3Year?: number; - totalAssets?: number; - yield?: number; - fundInceptionDate?: Date; - threeYearAverageReturn?: number; - fiveYearAverageReturn?: number; - morningStarOverallRating?: number; - morningStarRiskRating?: number; - annualReportExpenseRatio?: number; - lastCapGain?: number; - annualHoldingsTurnover?: number; -} - -export interface QuoteSummaryEarnings { - [key: string]: any; - maxAge: number; - earningsChart: EarningsChart; - financialsChart: FinancialsChart; - financialCurrency?: string; -} - -export interface EarningsChart { - [key: string]: any; - quarterly: EarningsChartQuarterly[]; - currentQuarterEstimate?: number; - currentQuarterEstimateDate?: string; - currentQuarterEstimateYear?: number; - earningsDate: Date[]; -} - -export interface EarningsChartQuarterly { - [key: string]: any; - date: string; - actual: number; - estimate: number; -} - -export interface FinancialsChart { - [key: string]: any; - yearly: Yearly[]; - quarterly: FinancialsChartQuarterly[]; -} - -export interface FinancialsChartQuarterly { - [key: string]: any; - date: string; - revenue: number; - earnings: number; -} - -export interface Yearly { - [key: string]: any; - date: number; - revenue: number; - earnings: number; -} - -export interface EarningsHistory { - [key: string]: any; - history: EarningsHistoryHistory[]; - maxAge: number; -} - -export interface EarningsHistoryHistory { - [key: string]: any; - maxAge: number; - epsActual: number | null; - epsEstimate: number | null; - epsDifference: number | null; - surprisePercent: number | null; - quarter: Date | null; - period: string; -} - -export interface EarningsTrend { - [key: string]: any; - trend: EarningsTrendTrend[]; - maxAge: number; -} - -export interface EarningsTrendTrend { - [key: string]: any; - maxAge: number; - period: string; - endDate: Date | null; - growth: number | null; - earningsEstimate: EarningsEstimate; - revenueEstimate: RevenueEstimate; - epsTrend: EpsTrend; - epsRevisions: EpsRevisions; -} - -export interface EarningsEstimate { - [key: string]: any; - avg: number | null; - low: number | null; - high: number | null; - yearAgoEps: number | null; - numberOfAnalysts: number | null; - growth: number | null; -} - -export interface EpsRevisions { - [key: string]: any; - upLast7days: number | null; - upLast30days: number | null; - downLast30days: number | null; - downLast90days: number | null; -} - -export interface EpsTrend { - [key: string]: any; - current: number | null; - "7daysAgo": number | null; - "30daysAgo": number | null; - "60daysAgo": number | null; - "90daysAgo": number | null; -} - -export interface RevenueEstimate { - [key: string]: any; - avg: number | null; - low: number | null; - high: number | null; - numberOfAnalysts: number | null; - yearAgoRevenue: number | null; - growth: number | null; -} - -export interface FinancialData { - [key: string]: any; - maxAge: number; - currentPrice?: number; - targetHighPrice?: number; - targetLowPrice?: number; - targetMeanPrice?: number; - targetMedianPrice?: number; - recommendationMean?: number; - recommendationKey: string; - numberOfAnalystOpinions?: number; - totalCash?: number; - totalCashPerShare?: number; - ebitda?: number; - totalDebt?: number; - quickRatio?: number; - currentRatio?: number; - totalRevenue?: number; - debtToEquity?: number; - revenuePerShare?: number; - returnOnAssets?: number; - returnOnEquity?: number; - grossProfits?: number; - freeCashflow?: number; - operatingCashflow?: number; - earningsGrowth?: number; - revenueGrowth?: number; - grossMargins?: number; - ebitdaMargins?: number; - operatingMargins?: number; - profitMargins?: number; - financialCurrency: string | null; -} - -export interface Ownership { - [key: string]: any; - maxAge: number; - ownershipList: OwnershipList[]; -} - -export interface OwnershipList { - [key: string]: any; - maxAge: number; - reportDate: Date; - organization: string; - pctHeld: number; - position: number; - value: number; - pctChange?: number; -} - -export interface FundPerformance { - [key: string]: any; - maxAge: number; - loadAdjustedReturns?: PeriodRange; - rankInCategory?: PeriodRange; - performanceOverview: FundPerformancePerformanceOverview; - performanceOverviewCat: FundPerformancePerformanceOverviewCat; - trailingReturns: FundPerformanceTrailingReturns; - trailingReturnsNav: FundPerformanceTrailingReturns; - trailingReturnsCat: FundPerformanceTrailingReturns; - annualTotalReturns: FundPerformanceReturns; - pastQuarterlyReturns: FundPerformanceReturns; - riskOverviewStatistics: FundPerformanceRiskOverviewStats; - riskOverviewStatisticsCat: FundPerformanceRiskOverviewStatsCat; -} - -export interface PeriodRange { - [key: string]: any; - asOfDate?: Date; - ytd?: number; - oneMonth?: number; - threeMonth?: number; - oneYear?: number; - threeYear?: number; - fiveYear?: number; - tenYear?: number; -} - -export interface FundPerformanceTrailingReturns extends PeriodRange { - [key: string]: any; - lastBullMkt?: number; - lastBearMkt?: number; -} - -export interface FundPerformancePerformanceOverview { - [key: string]: any; - asOfDate?: Date; - ytdReturnPct?: number; - oneYearTotalReturn?: number; - threeYearTotalReturn?: number; - fiveYrAvgReturnPct?: number; - morningStarReturnRating?: number; - numYearsUp?: number; - numYearsDown?: number; - bestOneYrTotalReturn?: number; - worstOneYrTotalReturn?: number; - bestThreeYrTotalReturn?: number; - worstThreeYrTotalReturn?: number; -} - -export interface FundPerformancePerformanceOverviewCat { - [key: string]: any; - ytdReturnPct?: number; - fiveYrAvgReturnPct?: number; -} - -export interface FundPerformanceReturns { - [key: string]: any; - returns: FundPerformanceReturnsRow[]; - returnsCat?: FundPerformanceReturnsRow[]; -} - -export interface FundPerformanceReturnsRow { - [key: string]: any; - year: number; // coerce to number from string "2020" - annualValue?: number; - q1?: number; - q2?: number; - q3?: number; - q4?: number; -} - -export interface FundPerformanceRiskOverviewStats { - [key: string]: any; - riskStatistics: FundPerformanceRiskOverviewStatsRow[]; - riskRating?: number; -} - -export interface FundPerformanceRiskOverviewStatsCat { - [key: string]: any; - riskStatisticsCat: FundPerformanceRiskOverviewStatsRow[]; -} - -export interface FundPerformanceRiskOverviewStatsRow { - [key: string]: any; - year: string; // "5y" | "3y" | "10y" | anything else? - alpha: number; // 7.76 - beta: number; // 1.04 - meanAnnualReturn: number; // 2.05 - rSquared: number; // 84.03 - stdDev?: number; // 17.12 - sharpeRatio: number; // 1.37 - treynorRatio: number; // 23.61 -} - -export interface FundProfile { - [key: string]: any; - maxAge: number; - styleBoxUrl?: null | string; - family: null | string; - categoryName: null | string; - legalType: null | string; - managementInfo?: FundProfileManagementInfo; - feesExpensesInvestment?: FundProfileFeesExpensesInvestment; - feesExpensesInvestmentCat?: FundProfileFeesExpensesInvestmentCat; - brokerages?: FundProfileBrokerage[]; - initInvestment?: number; - initIraInvestment?: number; - initAipInvestment?: number; - subseqInvestment?: number; - subseqIraInvestment?: number; - subseqAipInvestment?: number; -} - -export interface FundProfileManagementInfo { - [key: string]: any; - managerName: null | string; - managerBio: null | string; - startdate?: Date; -} - -export interface FundProfileFeesExpensesInvestment { - [key: string]: any; - annualHoldingsTurnover?: number; - annualReportExpenseRatio?: number; - grossExpRatio?: number; - netExpRatio?: number; - projectionValues: object; - totalNetAssets?: number; -} - -export interface FundProfileFeesExpensesInvestmentCat - extends Omit { - [key: string]: any; - projectionValuesCat: object; -} - -export interface FundProfileBrokerage {} - -export interface IncomeStatementHistory { - [key: string]: any; - incomeStatementHistory: IncomeStatementHistoryElement[]; - maxAge: number; -} - -export interface IncomeStatementHistoryElement { - [key: string]: any; - maxAge: number; - endDate: Date; - totalRevenue: number; - costOfRevenue: number; - grossProfit: number; - researchDevelopment: number | null; - sellingGeneralAdministrative: number | null; - nonRecurring: number | null; - otherOperatingExpenses: number | null; - totalOperatingExpenses: number; - operatingIncome: number | null; // Example of null in EREGL.IS (#517) - totalOtherIncomeExpenseNet: number | null; // null since Since Feb 22 (#734) - ebit: number; - interestExpense: number | null; - incomeBeforeTax: number | null; - incomeTaxExpense: number; - minorityInterest: number | null; - netIncomeFromContinuingOps: number | null; - discontinuedOperations: number | null; - extraordinaryItems: number | null; - effectOfAccountingCharges: number | null; - otherItems: number | null; - netIncome: number; - netIncomeApplicableToCommonShares: number | null; -} - -export interface IndexTrend { - [key: string]: any; - maxAge: number; - symbol: string; - peRatio: number; - pegRatio: number; - estimates: Estimate[]; -} - -export interface Estimate { - [key: string]: any; - period: string; - growth?: number; -} - -export interface Trend { - [key: string]: any; - maxAge: number; - symbol: null; - estimates: any[]; -} - -export interface Holders { - [key: string]: any; - holders: Holder[]; - maxAge: number; -} - -export interface Holder { - [key: string]: any; - maxAge: number; - name: string; - relation: Relation | string; - url: string; - transactionDescription: string; - latestTransDate: Date; - positionDirect?: number; - positionDirectDate?: Date; - positionIndirect?: number; - positionIndirectDate?: Date; - positionSummaryDate?: Date; -} - -export enum Relation { - ChairmanOfTheBoard = "Chairman of the Board", - ChiefExecutiveOfficer = "Chief Executive Officer", - ChiefFinancialOfficer = "Chief Financial Officer", - ChiefOperatingOfficer = "Chief Operating Officer", - ChiefTechnologyOfficer = "Chief Technology Officer", - Director = "Director", - DirectorIndependent = "Director (Independent)", - Empty = "", - GeneralCounsel = "General Counsel", - IndependentNonExecutiveDirector = "Independent Non-Executive Director", - Officer = "Officer", - President = "President", -} - -export interface InsiderTransactions { - [key: string]: any; - transactions: Transaction[]; - maxAge: number; -} - -export interface Transaction { - [key: string]: any; - maxAge: number; - shares: number; - filerUrl: string; - transactionText: string; - filerName: string; - filerRelation: Relation | string; - moneyText: string; - startDate: Date; - ownership: OwnershipEnum | string; - value?: number; -} - -export enum OwnershipEnum { - D = "D", - I = "I", -} - -export interface MajorHoldersBreakdown { - [key: string]: any; - maxAge: number; - insidersPercentHeld?: number; - institutionsPercentHeld?: number; - institutionsFloatPercentHeld?: number; - institutionsCount?: number; -} -export interface NetSharePurchaseActivity { - [key: string]: any; - maxAge: number; - period: string; - buyInfoCount: number; - buyInfoShares: number; - buyPercentInsiderShares?: number; - sellInfoCount: number; - sellInfoShares?: number; - sellPercentInsiderShares?: number; - netInfoCount: number; - netInfoShares: number; - netPercentInsiderShares?: number; - totalInsiderShares: number; -} - -/* - * Dates are usually epoch numbers, but including other modules can change - * result to include an ISODate. - */ -export interface Price { - [key: string]: any; - averageDailyVolume10Day?: number; - averageDailyVolume3Month?: number; - exchange?: string; - exchangeName?: string; - exchangeDataDelayedBy?: number; - maxAge: number; - postMarketChangePercent?: number; - postMarketChange?: number; - postMarketTime?: Date; - postMarketPrice?: number; - postMarketSource?: string; - preMarketChangePercent?: number; - preMarketChange?: number; - preMarketTime?: Date; - preMarketPrice?: number; - preMarketSource?: string; - priceHint: number; - regularMarketChangePercent?: number; - regularMarketChange?: number; - regularMarketTime?: Date; - regularMarketPrice?: number; - regularMarketDayHigh?: number; - regularMarketDayLow?: number; - regularMarketVolume?: number; - regularMarketPreviousClose?: number; - regularMarketSource?: string; - regularMarketOpen?: number; - - quoteSourceName?: string; - quoteType: string; - - symbol: string; - underlyingSymbol: null | string; // "GCM22.CMX" (from GC=F future) - shortName: null | string; - longName: null | string; - - lastMarket: null | string; - marketState?: string; - marketCap?: number; - - // Crypto only? Is Price actually Quote? TODO after - currency?: string; - currencySymbol?: string; - fromCurrency: string | null; - toCurrency?: string | null; - volume24Hr?: number; - volumeAllCurrencies?: number; - circulatingSupply?: number; - - // futures - expireDate?: Date; // 1656374400, - openInterest?: number; // 444411, -} - -export interface QuoteType { - [key: string]: any; - exchange: string; - quoteType: string; - symbol: string; - underlyingSymbol: string; - shortName: null | string; - longName: null | string; - firstTradeDateEpochUtc: null | Date; - timeZoneFullName: string; - timeZoneShortName: string; - uuid: string; - messageBoardId?: null | string; - gmtOffSetMilliseconds: number; - maxAge: number; -} - -export interface RecommendationTrend { - [key: string]: any; - trend: RecommendationTrendTrend[]; - maxAge: number; -} - -export interface RecommendationTrendTrend { - [key: string]: any; - period: string; - strongBuy: number; - buy: number; - hold: number; - sell: number; - strongSell: number; -} - -export interface SECFilings { - [key: string]: any; - filings: Filing[]; - maxAge: number; -} - -export interface Filing { - [key: string]: any; - date: string; // TODO: check the format - epochDate: Date; - type: FilingType; - title: string; - edgarUrl: string; - maxAge: number; - url?: string; - exhibits?: { type: string; url: string; downloadUrl?: string }[]; -} - -// May consider switching this to string, as we keep finding more and more. -type FilingType = - | "10-K" - | "10-Q" - | "8-K" - | "8-K/A" - | "10-K/A" - | "10-Q/A" - | "SD" - | "PX14A6G" - | "SC 13G/A" - | "DEFA14A" - | "25-NSE" - | "S-8 POS" - | "6-K" - | "F-3ASR" - | "SC 13D/A" - | "20-F" - | "425" - | "SC14D9C" - | "SC 13G" - | "S-8" - | "DEF 14A" - | "F-10"; - -export interface SummaryDetail { - [key: string]: any; - maxAge: number; - priceHint: number; - previousClose?: number; // missing in e.g. "APS.AX" - open?: number; - dayLow?: number; - dayHigh?: number; - regularMarketPreviousClose?: number; // missing in e.g. "APS.AX" - regularMarketOpen?: number; - regularMarketDayLow?: number; - regularMarketDayHigh?: number; - regularMarketVolume?: number; - dividendRate?: number; - dividendYield?: number; - exDividendDate?: Date; - payoutRatio?: number; - fiveYearAvgDividendYield?: number; - beta?: number; - trailingPE?: number; - forwardPE?: number; - volume?: number; - averageVolume?: number; - averageVolume10days?: number; - averageDailyVolume10Day?: number; - bid?: number; - ask?: number; - bidSize?: number; - askSize?: number; - marketCap?: number; - fiftyDayAverage?: number; - fiftyTwoWeekLow?: number; - fiftyTwoWeekHigh?: number; - twoHundredDayAverage?: number; - priceToSalesTrailing12Months?: number; - trailingAnnualDividendRate?: number; - trailingAnnualDividendYield?: number; - currency: string; - algorithm: null; - tradeable: boolean; - yield?: number; - totalAssets?: number; - navPrice?: number; - ytdReturn?: number; - - // crypto only (optional, or null in other types) - // TODO: how does Price / SummaryDetail compare? common base? - fromCurrency: string | null; // 'BTC' - toCurrency?: string | null; // 'USD-X' - lastMarket: string | null; // 'CoinMarketCap' - volume24Hr?: number; // 62650314752 - volumeAllCurrencies?: number; // 62650314752 - circulatingSupply?: number; // 18638932 - startDate?: Date; // new Date(1367107200 * 1000) - coinMarketCapLink?: string | null; // "https://coinmarketcap.com/currencies/cardano" - - // futures - expireDate?: Date; // 1656374400, - openInterest?: number; // 444411, -} - -export interface SummaryProfile { - [key: string]: any; - address1?: string; - address2?: string; - address3?: string; - city?: string; - state?: string; - zip?: string; - country?: string; - phone?: string; - fax?: string; - website?: string; - industry?: string; - industryDisp?: string; - sector?: string; - sectorDisp?: string; - longBusinessSummary?: string; - fullTimeEmployees?: number; - companyOfficers: any[]; - maxAge: number; - twitter?: string; // in e.g. "ADA-USD" (#418) - - // seems like for cryptocurency only - // TODO: how does this relate to Quote type. Common base? - name?: string; // 'Bitcoin' - startDate?: Date; // new Date('2013-04-28') - description?: string; // 'Bitcoin (BTC) is a cryptocurrency...' -} - -export interface TopHoldings { - [key: string]: any; - maxAge: number; - stockPosition?: number; - bondPosition?: number; - holdings: TopHoldingsHolding[]; - equityHoldings: TopHoldingsEquityHoldings; - bondHoldings: object; - bondRatings: TopHoldingsBondRating[]; - sectorWeightings: TopHoldingsSectorWeighting[]; - cashPosition?: number; - otherPosition?: number; - preferredPosition?: number; - convertiblePosition?: number; -} - -export interface TopHoldingsHolding { - [key: string]: any; - symbol: string; - holdingName: string; - holdingPercent: number; -} - -export interface TopHoldingsEquityHoldings { - [key: string]: any; - medianMarketCap?: number; - medianMarketCapCat?: number; - priceToBook: number; - priceToBookCat?: number; - priceToCashflow: number; - priceToCashflowCat?: number; - priceToEarnings: number; - priceToEarningsCat?: number; - priceToSales: number; - priceToSalesCat?: number; - threeYearEarningsGrowth?: number; - threeYearEarningsGrowthCat?: number; -} - -export interface TopHoldingsBondRating { - [key: string]: any; - a?: number; - aa?: number; - aaa?: number; - other?: number; - b?: number; - bb?: number; - bbb?: number; - below_b?: number; - us_government?: number; -} - -export interface TopHoldingsSectorWeighting { - [key: string]: any; - realestate?: number; - consumer_cyclical?: number; - basic_materials?: number; - consumer_defensive?: number; - technology?: number; - communication_services?: number; - financial_services?: number; - utilities?: number; - industrials?: number; - energy?: number; - healthcare?: number; -} - -export interface UpgradeDowngradeHistory { - [key: string]: any; - history: UpgradeDowngradeHistoryHistory[]; - maxAge: number; -} - -export interface UpgradeDowngradeHistoryHistory { - [key: string]: any; - epochGradeDate: Date; - firm: string; - toGrade: Grade; - fromGrade?: Grade; - action: Action; -} - -export enum Action { - Down = "down", - Init = "init", - Main = "main", - Reit = "reit", - Up = "up", -} - -export enum Grade { +enum EnumGrade { Accumulate = "Accumulate", Add = "Add", Average = "Average", @@ -1046,7 +58,7 @@ export enum Grade { AboveAverage = "Above Average", Inline = "In-line", Outperformer = "Outperformer", - OVerweight = "OVerweight", // Not a typo, how it was returned from API + OVerweight = "OVerweight", Cautious = "Cautious", MarketWeight = "Market Weight", SectorUnderperform = "Sector Underperform", @@ -1069,3 +81,1359 @@ export enum Grade { marketperform = "market perform", BUy = "BUy", // Not a typo, how it was returned from API } + +enum Action { + Down = "down", + Init = "init", + Main = "main", + Reit = "reit", + Up = "up", +} + +const Grade = Type.Enum(EnumGrade, { title: "QuoteSummaryEnumGrade" }); +const ActionSchema = Type.Enum(Action, { title: "QuoteSummaryAction" }); + +const UpgradeDowngradeHistoryHistory = Type.Object( + { + epochGradeDate: YahooFinanceDate, + firm: Type.String(), + toGrade: Grade, + fromGrade: Type.Optional(Grade), + action: ActionSchema, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryUpgradeDowngradeHistoryHistory", + } +); + +const UpgradeDowngradeHistory = Type.Object( + { + history: Type.Array(UpgradeDowngradeHistoryHistory), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryUpgradeDowngradeHistory", + } +); + +const TopHoldingsSectorWeighting = Type.Object( + { + realestate: Type.Optional(YahooNumber), + consumer_cyclical: Type.Optional(YahooNumber), + basic_materials: Type.Optional(YahooNumber), + consumer_defensive: Type.Optional(YahooNumber), + technology: Type.Optional(YahooNumber), + communication_services: Type.Optional(YahooNumber), + financial_services: Type.Optional(YahooNumber), + utilities: Type.Optional(YahooNumber), + industrials: Type.Optional(YahooNumber), + energy: Type.Optional(YahooNumber), + healthcare: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTopHoldingsSectorWeighting", + } +); + +const TopHoldingsBondRating = Type.Object( + { + a: Type.Optional(YahooNumber), + aa: Type.Optional(YahooNumber), + aaa: Type.Optional(YahooNumber), + other: Type.Optional(YahooNumber), + b: Type.Optional(YahooNumber), + bb: Type.Optional(YahooNumber), + bbb: Type.Optional(YahooNumber), + below_b: Type.Optional(YahooNumber), + us_government: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTopHoldingsBondRating", + } +); + +const TopHoldingsEquityHoldings = Type.Object( + { + medianMarketCap: Type.Optional(YahooNumber), + medianMarketCapCat: Type.Optional(YahooNumber), + priceToBook: YahooNumber, + priceToBookCat: Type.Optional(YahooNumber), + priceToCashflow: YahooNumber, + priceToCashflowCat: Type.Optional(YahooNumber), + priceToEarnings: YahooNumber, + priceToEarningsCat: Type.Optional(YahooNumber), + priceToSales: YahooNumber, + priceToSalesCat: Type.Optional(YahooNumber), + threeYearEarningsGrowth: Type.Optional(YahooNumber), + threeYearEarningsGrowthCat: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTopHoldingsEquityHoldings", + } +); + +const TopHoldingsHolding = Type.Object( + { + symbol: Type.String(), + holdingName: Type.String(), + holdingPercent: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTopHoldingsHolding", + } +); + +const TopHoldings = Type.Object( + { + maxAge: YahooNumber, + stockPosition: Type.Optional(YahooNumber), + bondPosition: Type.Optional(YahooNumber), + holdings: Type.Array(TopHoldingsHolding), + equityHoldings: TopHoldingsEquityHoldings, + bondHoldings: Type.Object({}), + bondRatings: Type.Array(TopHoldingsBondRating), + sectorWeightings: Type.Array(TopHoldingsSectorWeighting), + cashPosition: Type.Optional(YahooNumber), + otherPosition: Type.Optional(YahooNumber), + preferredPosition: Type.Optional(YahooNumber), + convertiblePosition: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTopHoldings", + } +); + +const SummaryProfile = Type.Object( + { + address1: Type.Optional(Type.String()), + address2: Type.Optional(Type.String()), + address3: Type.Optional(Type.String()), + city: Type.Optional(Type.String()), + state: Type.Optional(Type.String()), + zip: Type.Optional(Type.String()), + country: Type.Optional(Type.String()), + phone: Type.Optional(Type.String()), + fax: Type.Optional(Type.String()), + website: Type.Optional(Type.String()), + industry: Type.Optional(Type.String()), + industryDisp: Type.Optional(Type.String()), + sector: Type.Optional(Type.String()), + sectorDisp: Type.Optional(Type.String()), + longBusinessSummary: Type.Optional(Type.String()), + fullTimeEmployees: Type.Optional(YahooNumber), + companyOfficers: Type.Array(Type.Any()), + maxAge: YahooNumber, + twitter: Type.Optional(Type.String()), // in e.g. "ADA-USD" (#418) + + // seems like for cryptocurency only + // TODO: how does this relate to Quote type. Common base? + + name: Type.Optional(Type.String()), // 'Bitcoin' + startDate: Type.Optional(YahooFinanceDate), // new Date('2013-04-28') + description: Type.Optional(Type.String()), // 'Bitcoin (BTC) is a cryptocurrency...' + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummarySummaryProfile", + } +); + +const SummaryDetail = Type.Object( + { + maxAge: YahooNumber, + priceHint: YahooNumber, + previousClose: Type.Optional(YahooNumber), // missing in e.g. "APS.AX" + open: Type.Optional(YahooNumber), + dayLow: Type.Optional(YahooNumber), + dayHigh: Type.Optional(YahooNumber), + regularMarketPreviousClose: Type.Optional(YahooNumber), // missing in e.g. "APS.AX" + regularMarketOpen: Type.Optional(YahooNumber), + regularMarketDayLow: Type.Optional(YahooNumber), + regularMarketDayHigh: Type.Optional(YahooNumber), + regularMarketVolume: Type.Optional(YahooNumber), + dividendRate: Type.Optional(YahooNumber), + dividendYield: Type.Optional(YahooNumber), + exDividendDate: Type.Optional(YahooFinanceDate), + payoutRatio: Type.Optional(YahooNumber), + fiveYearAvgDividendYield: Type.Optional(YahooNumber), + beta: Type.Optional(YahooNumber), + trailingPE: Type.Optional(YahooNumber), + forwardPE: Type.Optional(YahooNumber), + volume: Type.Optional(YahooNumber), + averageVolume: Type.Optional(YahooNumber), + averageVolume10days: Type.Optional(YahooNumber), + averageDailyVolume10Day: Type.Optional(YahooNumber), + bid: Type.Optional(YahooNumber), + ask: Type.Optional(YahooNumber), + bidSize: Type.Optional(YahooNumber), + askSize: Type.Optional(YahooNumber), + marketCap: Type.Optional(YahooNumber), + fiftyDayAverage: Type.Optional(YahooNumber), + fiftyTwoWeekLow: Type.Optional(YahooNumber), + fiftyTwoWeekHigh: Type.Optional(YahooNumber), + twoHundredDayAverage: Type.Optional(YahooNumber), + priceToSalesTrailing12Months: Type.Optional(YahooNumber), + trailingAnnualDividendRate: Type.Optional(YahooNumber), + trailingAnnualDividendYield: Type.Optional(YahooNumber), + currency: Type.String(), + algorithm: Type.Null(), + tradeable: Type.Boolean(), + yield: Type.Optional(YahooNumber), + totalAssets: Type.Optional(YahooNumber), + navPrice: Type.Optional(YahooNumber), + ytdReturn: Type.Optional(YahooNumber), + + // crypto only (optional, or null in other types) + // TODO: how does Price / SummaryDetail compare? common base? + + fromCurrency: Type.Union([Type.String(), Type.Null()]), // 'BTC' + toCurrency: Type.Optional(Type.Union([Type.String(), Type.Null()])), // 'USD-X' + lastMarket: Type.Union([Type.String(), Type.Null()]), // 'CoinMarketCap' + volume24Hr: Type.Optional(YahooNumber), // 62650314752 + volumeAllCurrencies: Type.Optional(YahooNumber), // 62650314752 + circulatingSupply: Type.Optional(YahooNumber), // 18638932 + startDate: Type.Optional(YahooFinanceDate), // new Date(1367107200 * 1000) + coinMarketCapLink: Type.Optional(Type.Union([Type.String(), Type.Null()])), // "https://coinmarketcap.com/currencies/cardano" + + // futures + expireDate: Type.Optional(YahooFinanceDate), // 1656374400, + openInterest: Type.Optional(YahooNumber), // 444411, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummarySummaryDetail", + } +); + +// May consider switching this to string, as we keep finding more and more. +const FilingType = Type.Union( + [ + Type.Literal("10-K"), + Type.Literal("10-Q"), + Type.Literal("8-K"), + Type.Literal("8-K/A"), + Type.Literal("10-K/A"), + Type.Literal("10-Q/A"), + Type.Literal("SD"), + Type.Literal("PX14A6G"), + Type.Literal("SC 13G/A"), + Type.Literal("DEFA14A"), + Type.Literal("25-NSE"), + Type.Literal("S-8 POS"), + Type.Literal("6-K"), + Type.Literal("F-3ASR"), + Type.Literal("SC 13D/A"), + Type.Literal("20-F"), + Type.Literal("425"), + Type.Literal("SC14D9C"), + Type.Literal("SC 13G"), + Type.Literal("S-8"), + Type.Literal("DEF 14A"), + Type.Literal("F-10"), + ], + { + title: "QuoteSummaryFilingType", + } +); + +const Filing = Type.Object( + { + date: Type.String(), + epochDate: YahooFinanceDate, + type: FilingType, + title: Type.String(), + edgarUrl: Type.String(), + maxAge: YahooNumber, + url: Type.Optional(Type.String()), + exhibits: Type.Optional( + Type.Array( + Type.Object({ + type: Type.String(), + url: Type.String(), + downloadUrl: Type.Optional(Type.String()), + }) + ) + ), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFiling", + } +); + +const SECFilings = Type.Object( + { + filings: Type.Array(Filing), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummarySECFilings", + } +); + +const RecommendationTrendTrend = Type.Object( + { + period: Type.String(), + strongBuy: YahooNumber, + buy: YahooNumber, + hold: YahooNumber, + sell: YahooNumber, + strongSell: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryRecommendationTrendTrend", + } +); + +const RecommendationTrend = Type.Object( + { + trend: Type.Array(RecommendationTrendTrend), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryRecommendationTrend", + } +); + +const QuoteType = Type.Object( + { + exchange: Type.String(), + quoteType: Type.String(), + symbol: Type.String(), + underlyingSymbol: Type.String(), + shortName: Type.Union([Type.Null(), Type.String()]), + longName: Type.Union([Type.Null(), Type.String()]), + firstTradeDateEpochUtc: NullableYahooFinanceDate, + timeZoneFullName: Type.String(), + timeZoneShortName: Type.String(), + uuid: Type.String(), + messageBoardId: Type.Optional(Type.Union([Type.Null(), Type.String()])), + gmtOffSetMilliseconds: YahooNumber, + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryQuoteType", + } +); + +const Price = Type.Object( + { + averageDailyVolume10Day: Type.Optional(YahooNumber), + averageDailyVolume3Month: Type.Optional(YahooNumber), + exchange: Type.Optional(Type.String()), + exchangeName: Type.Optional(Type.String()), + exchangeDataDelayedBy: Type.Optional(YahooNumber), + maxAge: YahooNumber, + postMarketChangePercent: Type.Optional(YahooNumber), + postMarketChange: Type.Optional(YahooNumber), + postMarketTime: Type.Optional(YahooFinanceDate), + postMarketPrice: Type.Optional(YahooNumber), + postMarketSource: Type.Optional(Type.String()), + preMarketChangePercent: Type.Optional(YahooNumber), + preMarketChange: Type.Optional(YahooNumber), + preMarketTime: Type.Optional(YahooFinanceDate), + preMarketPrice: Type.Optional(YahooNumber), + preMarketSource: Type.Optional(Type.String()), + priceHint: YahooNumber, + regularMarketChangePercent: Type.Optional(YahooNumber), + regularMarketChange: Type.Optional(YahooNumber), + regularMarketTime: Type.Optional(YahooFinanceDate), + regularMarketPrice: Type.Optional(YahooNumber), + regularMarketDayHigh: Type.Optional(YahooNumber), + regularMarketDayLow: Type.Optional(YahooNumber), + regularMarketVolume: Type.Optional(YahooNumber), + regularMarketPreviousClose: Type.Optional(YahooNumber), + regularMarketSource: Type.Optional(Type.String()), + regularMarketOpen: Type.Optional(YahooNumber), + quoteSourceName: Type.Optional(Type.String()), + quoteType: Type.String(), + symbol: Type.String(), + underlyingSymbol: Type.Union([Type.Null(), Type.String()]), + shortName: Type.Union([Type.Null(), Type.String()]), + longName: Type.Union([Type.Null(), Type.String()]), + lastMarket: Type.Union([Type.Null(), Type.String()]), + marketState: Type.Optional(Type.String()), + marketCap: Type.Optional(YahooNumber), + currency: Type.Optional(Type.String()), + currencySymbol: Type.Optional(Type.String()), + fromCurrency: Type.Union([Type.String(), Type.Null()]), + toCurrency: Type.Optional(Type.Union([Type.String(), Type.Null()])), + volume24Hr: Type.Optional(YahooNumber), + volumeAllCurrencies: Type.Optional(YahooNumber), + circulatingSupply: Type.Optional(YahooNumber), + expireDate: Type.Optional(YahooFinanceDate), + openInterest: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryPrice", + } +); + +const NetSharePurchaseActivity = Type.Object( + { + maxAge: YahooNumber, + period: Type.String(), + buyInfoCount: YahooNumber, + buyInfoShares: YahooNumber, + buyPercentInsiderShares: Type.Optional(YahooNumber), + sellInfoCount: YahooNumber, + sellInfoShares: Type.Optional(YahooNumber), + sellPercentInsiderShares: Type.Optional(YahooNumber), + netInfoCount: YahooNumber, + netInfoShares: YahooNumber, + netPercentInsiderShares: Type.Optional(YahooNumber), + totalInsiderShares: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryNetSharePurchaseActivity", + } +); + +const MajorHoldersBreakdown = Type.Object( + { + maxAge: YahooNumber, + insidersPercentHeld: Type.Optional(YahooNumber), + institutionsPercentHeld: Type.Optional(YahooNumber), + institutionsFloatPercentHeld: Type.Optional(YahooNumber), + institutionsCount: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryMajorHoldersBreakdown", + } +); + +enum EnumOwnership { + D = "D", + I = "I", +} + +enum EnumRelation { + ChairmanOfTheBoard = "Chairman of the Board", + ChiefExecutiveOfficer = "Chief Executive Officer", + ChiefFinancialOfficer = "Chief Financial Officer", + ChiefOperatingOfficer = "Chief Operating Officer", + ChiefTechnologyOfficer = "Chief Technology Officer", + Director = "Director", + DirectorIndependent = "Director (Independent)", + Empty = "", + GeneralCounsel = "General Counsel", + IndependentNonExecutiveDirector = "Independent Non-Executive Director", + Officer = "Officer", + President = "President", +} + +const Relation = Type.Enum(EnumRelation, { title: "QuoteSummaryRelation" }); + +const OwnershipSchema = Type.Enum(EnumOwnership, { + title: "QuoteSummaryOwnership", +}); + +const Transaction = Type.Object( + { + maxAge: YahooNumber, + shares: YahooNumber, + filerUrl: Type.String(), + transactionText: Type.String(), + filerName: Type.String(), + filerRelation: Type.Union([Relation, Type.String()]), + moneyText: Type.String(), + startDate: YahooFinanceDate, + ownership: Type.Union([OwnershipSchema, Type.String()]), + value: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTransaction", + } +); + +const InsiderTransactions = Type.Object( + { + transactions: Type.Array(Transaction), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryInsiderTransactions", + } +); + +const Holder = Type.Object( + { + maxAge: YahooNumber, + name: Type.String(), + relation: Type.Union([Relation, Type.String()]), + url: Type.String(), + transactionDescription: Type.String(), + latestTransDate: YahooFinanceDate, + positionDirect: Type.Optional(YahooNumber), + positionDirectDate: Type.Optional(YahooFinanceDate), + positionIndirect: Type.Optional(YahooNumber), + positionIndirectDate: Type.Optional(YahooFinanceDate), + positionSummaryDate: Type.Optional(YahooFinanceDate), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryHolder", + } +); + +const Holders = Type.Object( + { + holders: Type.Array(Holder), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryHolders", + } +); + +const Trend = Type.Object( + { + maxAge: YahooNumber, + symbol: Type.Null(), + estimates: Type.Array(Type.Any()), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryTrend", + } +); + +const Estimate = Type.Object( + { + period: Type.String(), + growth: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEstimate", + } +); + +const IndexTrend = Type.Object( + { + maxAge: YahooNumber, + symbol: Type.String(), + peRatio: YahooNumber, + pegRatio: YahooNumber, + estimates: Type.Array(Estimate), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryIndexTrend", + } +); + +const IncomeStatementHistoryElement = Type.Object( + { + maxAge: NullableYahooNumber, + endDate: YahooFinanceDate, + totalRevenue: NullableYahooNumber, + costOfRevenue: NullableYahooNumber, + grossProfit: NullableYahooNumber, + researchDevelopment: NullableYahooNumber, + sellingGeneralAdministrative: NullableYahooNumber, + nonRecurring: NullableYahooNumber, + otherOperatingExpenses: NullableYahooNumber, + totalOperatingExpenses: NullableYahooNumber, + operatingIncome: NullableYahooNumber, + totalOtherIncomeExpenseNet: NullableYahooNumber, + ebit: NullableYahooNumber, + interestExpense: NullableYahooNumber, + incomeBeforeTax: NullableYahooNumber, + incomeTaxExpense: NullableYahooNumber, + minorityInterest: NullableYahooNumber, + netIncomeFromContinuingOps: NullableYahooNumber, + discontinuedOperations: NullableYahooNumber, + extraordinaryItems: NullableYahooNumber, + effectOfAccountingCharges: NullableYahooNumber, + otherItems: NullableYahooNumber, + netIncome: NullableYahooNumber, + netIncomeApplicableToCommonShares: NullableYahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryIncomeStatementHistoryElement", + } +); + +const IncomeStatementHistory = Type.Object( + { + incomeStatementHistory: Type.Array(IncomeStatementHistoryElement), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryIncomeStatementHistory", + } +); + +const FundProfileBrokerage = Type.Object( + {}, + { + title: "QuoteSummaryFundProfileBrokerage", + } +); + +const FundProfileFeesExpensesInvestment = Type.Object( + { + annualHoldingsTurnover: Type.Optional(YahooNumber), + annualReportExpenseRatio: Type.Optional(YahooNumber), + grossExpRatio: Type.Optional(YahooNumber), + netExpRatio: Type.Optional(YahooNumber), + projectionValues: Type.Object({}), + totalNetAssets: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundProfileFeesExpensesInvestment", + } +); + +const FundProfileFeesExpensesInvestmentCat = Type.Composite( + [ + Type.Omit(FundProfileFeesExpensesInvestment, ["projectionValues"]), + Type.Object({ + projectionValuesCat: Type.Object({}), + }), + ], + { + title: "QuoteSummaryFundProfileFeesExpensesInvestmentCat", + additionalProperties: Type.Any(), + } +); + +const FundProfileManagementInfo = Type.Object( + { + managerName: Type.Union([Type.Null(), Type.String()]), + managerBio: Type.Union([Type.Null(), Type.String()]), + startdate: Type.Optional(YahooFinanceDate), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundProfileManagementInfo", + } +); + +const FundProfile = Type.Object( + { + maxAge: YahooNumber, + styleBoxUrl: Type.Optional(Type.Union([Type.Null(), Type.String()])), + family: Type.Union([Type.Null(), Type.String()]), + categoryName: Type.Union([Type.Null(), Type.String()]), + legalType: Type.Union([Type.Null(), Type.String()]), + managementInfo: Type.Optional(FundProfileManagementInfo), + feesExpensesInvestment: Type.Optional(FundProfileFeesExpensesInvestment), + feesExpensesInvestmentCat: Type.Optional( + FundProfileFeesExpensesInvestmentCat + ), + brokerages: Type.Optional(Type.Array(FundProfileBrokerage)), + initInvestment: Type.Optional(YahooNumber), + initIraInvestment: Type.Optional(YahooNumber), + initAipInvestment: Type.Optional(YahooNumber), + subseqInvestment: Type.Optional(YahooNumber), + subseqIraInvestment: Type.Optional(YahooNumber), + subseqAipInvestment: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundProfile", + } +); + +const FundPerformanceRiskOverviewStatsRow = Type.Object( + { + year: Type.String(), // "5y" | "3y" | "10y" | anything else? + alpha: YahooNumber, // 7.76 + beta: YahooNumber, // 1.04 + meanAnnualReturn: YahooNumber, // 2.05 + rSquared: YahooNumber, // 84.03 + stdDev: Type.Optional(YahooNumber), // 17.12 + sharpeRatio: YahooNumber, // 1.37 + treynorRatio: YahooNumber, // 23.61 + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformanceRiskOverviewStatsRow", + } +); + +const FundPerformanceRiskOverviewStatsCat = Type.Object( + { + riskStatisticsCat: Type.Array(FundPerformanceRiskOverviewStatsRow), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformanceRiskOverviewStatsCat", + } +); + +const FundPerformanceRiskOverviewStats = Type.Object( + { + riskStatistics: Type.Array(FundPerformanceRiskOverviewStatsRow), + riskRating: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformanceRiskOverviewStats", + } +); + +const FundPerformanceReturnsRow = Type.Object( + { + year: YahooFinanceDate, + annualValue: Type.Optional(YahooNumber), + q1: Type.Optional(YahooNumber), + q2: Type.Optional(YahooNumber), + q3: Type.Optional(YahooNumber), + q4: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformanceReturnsRow", + } +); + +const FundPerformanceReturns = Type.Object( + { + returns: Type.Array(FundPerformanceReturnsRow), + returnsCat: Type.Optional(Type.Array(FundPerformanceReturnsRow)), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformanceReturns", + } +); + +const FundPerformancePerformanceOverviewCat = Type.Object( + { + ytdReturnPct: Type.Optional(YahooNumber), + fiveYrAvgReturnPct: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformancePerformanceOverviewCat", + } +); + +const FundPerformancePerformanceOverview = Type.Object( + { + asOfDate: Type.Optional(YahooFinanceDate), + ytdReturnPct: Type.Optional(YahooNumber), + oneYearTotalReturn: Type.Optional(YahooNumber), + threeYearTotalReturn: Type.Optional(YahooNumber), + fiveYrAvgReturnPct: Type.Optional(YahooNumber), + morningStarReturnRating: Type.Optional(YahooNumber), + numYearsUp: Type.Optional(YahooNumber), + numYearsDown: Type.Optional(YahooNumber), + bestOneYrTotalReturn: Type.Optional(YahooNumber), + worstOneYrTotalReturn: Type.Optional(YahooNumber), + bestThreeYrTotalReturn: Type.Optional(YahooNumber), + worstThreeYrTotalReturn: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformancePerformanceOverview", + } +); + +const PeriodRange = Type.Object( + { + asOfDate: Type.Optional(YahooFinanceDate), + ytd: Type.Optional(YahooNumber), + oneMonth: Type.Optional(YahooNumber), + threeMonth: Type.Optional(YahooNumber), + oneYear: Type.Optional(YahooNumber), + threeYear: Type.Optional(YahooNumber), + fiveYear: Type.Optional(YahooNumber), + tenYear: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryPeriodRange", + } +); + +const FundPerformanceTrailingReturns = Type.Composite( + [ + PeriodRange, + Type.Object( + { + lastBullMkt: Type.Optional(YahooNumber), + lastBearMkt: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + } + ), + ], + { + title: "QuoteSummaryFundPerformanceTrailingReturns", + } +); + +const FundPerformance = Type.Object( + { + maxAge: YahooNumber, + loadAdjustedReturns: Type.Optional(PeriodRange), + rankInCategory: Type.Optional(PeriodRange), + performanceOverview: FundPerformancePerformanceOverview, + performanceOverviewCat: FundPerformancePerformanceOverviewCat, + trailingReturns: FundPerformanceTrailingReturns, + trailingReturnsNav: FundPerformanceTrailingReturns, + trailingReturnsCat: FundPerformanceTrailingReturns, + annualTotalReturns: FundPerformanceReturns, + pastQuarterlyReturns: FundPerformanceReturns, + riskOverviewStatistics: FundPerformanceRiskOverviewStats, + riskOverviewStatisticsCat: FundPerformanceRiskOverviewStatsCat, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFundPerformance", + } +); + +const OwnershipList = Type.Object( + { + maxAge: YahooNumber, + reportDate: YahooFinanceDate, + organization: Type.String(), + pctHeld: YahooNumber, + position: YahooNumber, + value: YahooNumber, + pctChange: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryOwnershipList", + } +); + +const Ownership = Type.Object( + { + maxAge: YahooNumber, + ownershipList: Type.Array(OwnershipList), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryOwnership", + } +); + +const FinancialData = Type.Object( + { + maxAge: YahooNumber, + currentPrice: Type.Optional(YahooNumber), + targetHighPrice: Type.Optional(YahooNumber), + targetLowPrice: Type.Optional(YahooNumber), + targetMeanPrice: Type.Optional(YahooNumber), + targetMedianPrice: Type.Optional(YahooNumber), + recommendationMean: Type.Optional(YahooNumber), + recommendationKey: Type.String(), + numberOfAnalystOpinions: Type.Optional(YahooNumber), + totalCash: Type.Optional(YahooNumber), + totalCashPerShare: Type.Optional(YahooNumber), + ebitda: Type.Optional(YahooNumber), + totalDebt: Type.Optional(YahooNumber), + quickRatio: Type.Optional(YahooNumber), + currentRatio: Type.Optional(YahooNumber), + totalRevenue: Type.Optional(YahooNumber), + debtToEquity: Type.Optional(YahooNumber), + revenuePerShare: Type.Optional(YahooNumber), + returnOnAssets: Type.Optional(YahooNumber), + returnOnEquity: Type.Optional(YahooNumber), + grossProfits: Type.Optional(YahooNumber), + freeCashflow: Type.Optional(YahooNumber), + operatingCashflow: Type.Optional(YahooNumber), + earningsGrowth: Type.Optional(YahooNumber), + revenueGrowth: Type.Optional(YahooNumber), + grossMargins: Type.Optional(YahooNumber), + ebitdaMargins: Type.Optional(YahooNumber), + operatingMargins: Type.Optional(YahooNumber), + profitMargins: Type.Optional(YahooNumber), + financialCurrency: Type.Union([Type.String(), Type.Null()]), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFinancialData", + } +); + +const RevenueEstimate = Type.Object( + { + avg: NullableYahooNumber, + low: NullableYahooNumber, + high: NullableYahooNumber, + numberOfAnalysts: NullableYahooNumber, + yearAgoRevenue: NullableYahooNumber, + growth: NullableYahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryRevenueEstimate", + } +); + +const EpsTrend = Type.Object( + { + current: NullableYahooNumber, + "7daysAgo": NullableYahooNumber, + "30daysAgo": NullableYahooNumber, + "60daysAgo": NullableYahooNumber, + "90daysAgo": NullableYahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEpsTrend", + } +); + +const EpsRevisions = Type.Object( + { + upLast7days: NullableYahooNumber, + upLast30days: NullableYahooNumber, + downLast30days: NullableYahooNumber, + downLast90days: NullableYahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEpsRevisions", + } +); + +const EarningsEstimate = Type.Object( + { + avg: NullableYahooNumber, + low: NullableYahooNumber, + high: NullableYahooNumber, + yearAgoEps: NullableYahooNumber, + numberOfAnalysts: NullableYahooNumber, + growth: NullableYahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsEstimate", + } +); + +const EarningsTrendTrend = Type.Object( + { + maxAge: YahooNumber, + period: Type.String(), + endDate: NullableYahooFinanceDate, + growth: NullableYahooNumber, + earningsEstimate: EarningsEstimate, + revenueEstimate: RevenueEstimate, + epsTrend: EpsTrend, + epsRevisions: EpsRevisions, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsTrendTrend", + } +); + +const EarningsTrend = Type.Object( + { + trend: Type.Array(EarningsTrendTrend), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsTrend", + } +); + +const EarningsHistoryHistory = Type.Object( + { + maxAge: YahooNumber, + epsActual: NullableYahooNumber, + epsEstimate: NullableYahooNumber, + epsDifference: NullableYahooNumber, + surprisePercent: NullableYahooNumber, + quarter: NullableYahooFinanceDate, + period: Type.String(), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsHistoryHistory", + } +); + +const EarningsHistory = Type.Object( + { + history: Type.Array(EarningsHistoryHistory), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsHistory", + } +); + +const Yearly = Type.Object( + { + date: YahooNumber, + revenue: YahooNumber, + earnings: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryYearly", + } +); + +const FinancialsChartQuarterly = Type.Object( + { + date: Type.String(), + revenue: YahooNumber, + earnings: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFinancialsChartQuarterly", + } +); + +const FinancialsChart = Type.Object( + { + yearly: Type.Array(Yearly), + quarterly: Type.Array(FinancialsChartQuarterly), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryFinancialsChart", + } +); + +const EarningsChartQuarterly = Type.Object( + { + date: Type.String(), + actual: YahooNumber, + estimate: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsChartQuarterly", + } +); + +const EarningsChart = Type.Object( + { + quarterly: Type.Array(EarningsChartQuarterly), + currentQuarterEstimate: Type.Optional(YahooNumber), + currentQuarterEstimateDate: Type.Optional(Type.String()), + currentQuarterEstimateYear: Type.Optional(YahooNumber), + earningsDate: Type.Array(YahooFinanceDate), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarningsChart", + } +); + +const QuoteSummaryEarnings = Type.Object( + { + maxAge: YahooNumber, + earningsChart: EarningsChart, + financialsChart: FinancialsChart, + financialCurrency: Type.Optional(Type.String()), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryEarnings", + } +); + +const DefaultKeyStatistics = Type.Object( + { + maxAge: YahooNumber, + priceHint: YahooNumber, + enterpriseValue: Type.Optional(YahooNumber), + forwardPE: Type.Optional(YahooNumber), + profitMargins: Type.Optional(YahooNumber), + floatShares: Type.Optional(YahooNumber), + sharesOutstanding: Type.Optional(YahooNumber), + sharesShort: Type.Optional(YahooNumber), + sharesShortPriorMonth: Type.Optional(YahooFinanceDate), + sharesShortPreviousMonthDate: Type.Optional(YahooFinanceDate), + dateShortInterest: Type.Optional(YahooNumber), + sharesPercentSharesOut: Type.Optional(YahooNumber), + heldPercentInsiders: Type.Optional(YahooNumber), + heldPercentInstitutions: Type.Optional(YahooNumber), + shortRatio: Type.Optional(YahooNumber), + shortPercentOfFloat: Type.Optional(YahooNumber), + beta: Type.Optional(YahooNumber), + impliedSharesOutstanding: Type.Optional(YahooNumber), + category: Type.Union([Type.Null(), Type.String()]), + bookValue: Type.Optional(YahooNumber), + priceToBook: Type.Optional(YahooNumber), + fundFamily: Type.Union([Type.Null(), Type.String()]), + legalType: Type.Union([Type.Null(), Type.String()]), + lastFiscalYearEnd: Type.Optional(YahooFinanceDate), + nextFiscalYearEnd: Type.Optional(YahooFinanceDate), + mostRecentQuarter: Type.Optional(YahooFinanceDate), + earningsQuarterlyGrowth: Type.Optional(YahooNumber), + netIncomeToCommon: Type.Optional(YahooNumber), + trailingEps: Type.Optional(YahooNumber), + forwardEps: Type.Optional(YahooNumber), + pegRatio: Type.Optional(YahooNumber), + lastSplitFactor: Type.Union([Type.Null(), Type.String()]), + lastSplitDate: Type.Optional(YahooNumber), + enterpriseToRevenue: Type.Optional(YahooNumber), + enterpriseToEbitda: Type.Optional(YahooNumber), + "52WeekChange": Type.Optional(YahooNumber), + SandP52WeekChange: Type.Optional(YahooNumber), + lastDividendValue: Type.Optional(YahooNumber), + lastDividendDate: Type.Optional(YahooFinanceDate), + ytdReturn: Type.Optional(YahooNumber), + beta3Year: Type.Optional(YahooNumber), + totalAssets: Type.Optional(YahooNumber), + yield: Type.Optional(YahooNumber), + fundInceptionDate: Type.Optional(YahooFinanceDate), + threeYearAverageReturn: Type.Optional(YahooNumber), + fiveYearAverageReturn: Type.Optional(YahooNumber), + morningStarOverallRating: Type.Optional(YahooNumber), + morningStarRiskRating: Type.Optional(YahooNumber), + annualReportExpenseRatio: Type.Optional(YahooNumber), + lastCapGain: Type.Optional(YahooNumber), + annualHoldingsTurnover: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryDefaultKeyStatistics", + } +); + +const CashflowStatement = Type.Object( + { + maxAge: YahooNumber, + endDate: YahooFinanceDate, + netIncome: YahooNumber, + depreciation: Type.Optional(YahooNumber), + changeToNetincome: Type.Optional(YahooNumber), + changeToAccountReceivables: Type.Optional(YahooNumber), + changeToLiabilities: Type.Optional(YahooNumber), + changeToInventory: Type.Optional(YahooNumber), + changeToOperatingActivities: Type.Optional(YahooNumber), + totalCashFromOperatingActivities: Type.Optional(YahooNumber), + capitalExpenditures: Type.Optional(YahooNumber), + investments: Type.Optional(YahooNumber), + otherCashflowsFromInvestingActivities: Type.Optional(YahooNumber), + totalCashflowsFromInvestingActivities: Type.Optional(YahooNumber), + dividendsPaid: Type.Optional(YahooNumber), + netBorrowings: Type.Optional(YahooNumber), + otherCashflowsFromFinancingActivities: Type.Optional(YahooNumber), + totalCashFromFinancingActivities: Type.Optional(YahooNumber), + changeInCash: Type.Optional(YahooNumber), + repurchaseOfStock: Type.Optional(YahooNumber), + issuanceOfStock: Type.Optional(YahooNumber), + effectOfExchangeRate: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryCashflowStatement", + } +); +const CashflowStatementHistory = Type.Object( + { + cashflowStatements: Type.Array(CashflowStatement), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryCashflowStatementHistory", + } +); + +const CalendarEventsEarnings = Type.Object( + { + earningsDate: Type.Array(YahooFinanceDate), + earningsAverage: Type.Optional(YahooNumber), + earningsLow: Type.Optional(YahooNumber), + earningsHigh: Type.Optional(YahooNumber), + revenueAverage: Type.Optional(YahooNumber), + revenueLow: Type.Optional(YahooNumber), + revenueHigh: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSumamryCalendarEventsEarnings", + } +); + +const CalendarEvents = Type.Object( + { + maxAge: YahooNumber, + earnings: CalendarEventsEarnings, + exDividendDate: Type.Optional(YahooFinanceDate), + dividendDate: Type.Optional(YahooFinanceDate), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryCalendarEvents", + } +); + +const BalanceSheetStatement = Type.Object( + { + maxAge: YahooNumber, + endDate: YahooFinanceDate, + cash: Type.Optional(YahooNumber), + shortTermInvestments: Type.Optional(YahooNumber), + netReceivables: Type.Optional(YahooNumber), + inventory: Type.Optional(YahooNumber), + otherCurrentAssets: Type.Optional(YahooNumber), + totalCurrentAssets: Type.Optional(YahooNumber), + longTermInvestments: Type.Optional(YahooNumber), + propertyPlantEquipment: Type.Optional(YahooNumber), + otherAssets: Type.Optional(YahooNumber), + totalAssets: Type.Optional(YahooNumber), + accountsPayable: Type.Optional(YahooNumber), + shortLongTermDebt: Type.Optional(YahooNumber), + otherCurrentLiab: Type.Optional(YahooNumber), + longTermDebt: Type.Optional(YahooNumber), + otherLiab: Type.Optional(YahooNumber), + totalCurrentLiabilities: Type.Optional(YahooNumber), + totalLiab: Type.Optional(YahooNumber), + commonStock: Type.Optional(YahooNumber), + retainedEarnings: Type.Optional(YahooNumber), + treasuryStock: Type.Optional(YahooNumber), + otherStockholderEquity: Type.Optional(YahooNumber), + totalStockholderEquity: Type.Optional(YahooNumber), + netTangibleAssets: Type.Optional(YahooNumber), + goodWill: Type.Optional(YahooNumber), + intangibleAssets: Type.Optional(YahooNumber), + deferredLongTermAssetCharges: Type.Optional(YahooNumber), + deferredLongTermLiab: Type.Optional(YahooNumber), + minorityInterest: Type.Optional(NullableYahooNumber), + capitalSurplus: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryBalanceSheetStatement", + } +); + +const BalanceSheetHistory = Type.Object( + { + balanceSheetStatements: Type.Array(BalanceSheetStatement), + maxAge: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryBalanceSheetHistory", + } +); + +const CompanyOfficer = Type.Object( + { + maxAge: YahooNumber, + name: Type.String(), + age: Type.Optional(YahooNumber), + title: Type.String(), + yearBorn: Type.Optional(YahooNumber), + fiscalYear: Type.Optional(YahooNumber), + totalPay: Type.Optional(YahooNumber), + exercisedValue: Type.Optional(YahooNumber), + unexercisedValue: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryCompanyOfficer", + } +); + +const AssetProfile = Type.Object( + { + maxAge: YahooNumber, + address1: Type.Optional(Type.String()), + address2: Type.Optional(Type.String()), + address3: Type.Optional(Type.String()), + city: Type.Optional(Type.String()), + state: Type.Optional(Type.String()), + zip: Type.Optional(Type.String()), + country: Type.Optional(Type.String()), + phone: Type.Optional(Type.String()), + fax: Type.Optional(Type.String()), + website: Type.Optional(Type.String()), + industry: Type.Optional(Type.String()), + industryDisp: Type.Optional(Type.String()), + industryKey: Type.Optional(Type.String()), + industrySymbol: Type.Optional(Type.String()), + sector: Type.Optional(Type.String()), + sectorDisp: Type.Optional(Type.String()), + sectorKey: Type.Optional(Type.String()), + longBusinessSummary: Type.Optional(Type.String()), + fullTimeEmployees: Type.Optional(YahooNumber), + companyOfficers: Type.Array(CompanyOfficer), + auditRisk: Type.Optional(YahooNumber), + boardRisk: Type.Optional(YahooNumber), + compensationRisk: Type.Optional(YahooNumber), + shareHolderRightsRisk: Type.Optional(YahooNumber), + overallRisk: Type.Optional(YahooNumber), + governanceEpochDate: Type.Optional(YahooFinanceDate), + compensationAsOfEpochDate: Type.Optional(YahooFinanceDate), + + name: Type.Optional(Type.String()), // 'Bitcoin'; + startDate: Type.Optional(YahooFinanceDate), // new Date('2013-04-28') + description: Type.Optional(Type.String()), // 'Bitcoin (BTC) is a cryptocurrency...' + twitter: Type.Optional(Type.String()), // in e.g. "ADA-USD" (#418) + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryAssetProfile", + } +); + +export const QuoteSummaryResult = Type.Object( + { + assetProfile: Type.Optional(AssetProfile), + balanceSheetHistory: Type.Optional(BalanceSheetHistory), + balanceSheetHistoryQuarterly: Type.Optional(BalanceSheetHistory), + calendarEvents: Type.Optional(CalendarEvents), + cashflowStatementHistory: Type.Optional(CashflowStatementHistory), + cashflowStatementHistoryQuarterly: Type.Optional(CashflowStatementHistory), + defaultKeyStatistics: Type.Optional(DefaultKeyStatistics), + earnings: Type.Optional(QuoteSummaryEarnings), + earningsHistory: Type.Optional(EarningsHistory), + earningsTrend: Type.Optional(EarningsTrend), + financialData: Type.Optional(FinancialData), + fundOwnership: Type.Optional(Ownership), + fundPerformance: Type.Optional(FundPerformance), + fundProfile: Type.Optional(FundProfile), + institutionOwnership: Type.Optional(Ownership), + majorDirectHolders: Type.Optional(Holders), + majorHoldersBreakdown: Type.Optional(MajorHoldersBreakdown), + netSharePurchaseActivity: Type.Optional(NetSharePurchaseActivity), + price: Type.Optional(Price), + quoteType: Type.Optional(QuoteType), + recommendationTrend: Type.Optional(RecommendationTrend), + secFilings: Type.Optional(SECFilings), + sectorTrend: Type.Optional(Trend), + summaryDetail: Type.Optional(SummaryDetail), + summaryProfile: Type.Optional(SummaryProfile), + topHoldings: Type.Optional(TopHoldings), + upgradeDowngradeHistory: Type.Optional(UpgradeDowngradeHistory), + }, + { + additionalProperties: Type.Any(), + title: "QuoteSummaryResult", + } +); diff --git a/src/modules/quoteSummary.spec.ts b/src/modules/quoteSummary.spec.ts index e4b31e3b..b193b83d 100644 --- a/src/modules/quoteSummary.spec.ts +++ b/src/modules/quoteSummary.spec.ts @@ -1,5 +1,4 @@ -import quoteSummary, { QuoteSummaryModules } from "./quoteSummary.js"; -import { InvalidOptionsError } from "../lib/errors.js"; +import quoteSummary from "./quoteSummary.js"; import testSymbols from "../../tests/testSymbols.js"; import testYf from "../../tests/testYf.js"; @@ -10,10 +9,7 @@ interface itValidatesOpts { skip?: Array; } -function itValidates( - name: QuoteSummaryModules | "all", - opts: itValidatesOpts = {} -) { +function itValidates(name: string | "all", opts: itValidatesOpts = {}) { let symbols = testSymbols({ add: [ // incomeStatementHistory/sellingGeneralAdministrative is null (#258) diff --git a/src/modules/quoteSummary.ts b/src/modules/quoteSummary.ts index 4774cf71..ed2cb67a 100644 --- a/src/modules/quoteSummary.ts +++ b/src/modules/quoteSummary.ts @@ -1,15 +1,51 @@ -// /// -// import QuoteSummaryResult from "QuoteSummaryIfaces"; -import { QuoteSummaryResult } from "./quoteSummary-iface.js"; - import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { Static, Type } from "@sinclair/typebox"; +import { QuoteSummaryResult } from "./quoteSummary-iface.js"; + +const QuoteSummaryModules = Type.Union([ + Type.Literal("assetProfile"), + Type.Literal("balanceSheetHistory"), + Type.Literal("balanceSheetHistoryQuarterly"), + Type.Literal("calendarEvents"), + Type.Literal("cashflowStatementHistory"), + Type.Literal("cashflowStatementHistoryQuarterly"), + Type.Literal("defaultKeyStatistics"), + Type.Literal("earnings"), + Type.Literal("earningsHistory"), + Type.Literal("earningsTrend"), + Type.Literal("financialData"), + Type.Literal("fundOwnership"), + Type.Literal("fundPerformance"), + Type.Literal("fundProfile"), + Type.Literal("incomeStatementHistory"), + Type.Literal("incomeStatementHistoryQuarterly"), + Type.Literal("indexTrend"), + Type.Literal("industryTrend"), + Type.Literal("insiderHolders"), + Type.Literal("insiderTransactions"), + Type.Literal("institutionOwnership"), + Type.Literal("majorDirectHolders"), + Type.Literal("majorHoldersBreakdown"), + Type.Literal("netSharePurchaseActivity"), + Type.Literal("price"), + Type.Literal("quoteType"), + Type.Literal("recommendationTrend"), + Type.Literal("secFilings"), + Type.Literal("sectorTrend"), + Type.Literal("summaryDetail"), + Type.Literal("summaryProfile"), + Type.Literal("topHoldings"), + Type.Literal("upgradeDowngradeHistory"), +]); -export const quoteSummary_modules = [ +type QuoteSummaryModulesLiteral = Static; + +const quoteSummaryModules = [ "assetProfile", "balanceSheetHistory", "balanceSheetHistoryQuarterly", @@ -45,47 +81,17 @@ export const quoteSummary_modules = [ "upgradeDowngradeHistory", ]; -export type QuoteSummaryModules = - | "assetProfile" - | "balanceSheetHistory" - | "balanceSheetHistoryQuarterly" - | "calendarEvents" - | "cashflowStatementHistory" - | "cashflowStatementHistoryQuarterly" - | "defaultKeyStatistics" - | "earnings" - | "earningsHistory" - | "earningsTrend" - | "financialData" - | "fundOwnership" - | "fundPerformance" - | "fundProfile" - | "incomeStatementHistory" - | "incomeStatementHistoryQuarterly" - | "indexTrend" - | "industryTrend" - | "insiderHolders" - | "insiderTransactions" - | "institutionOwnership" - | "majorDirectHolders" - | "majorHoldersBreakdown" - | "netSharePurchaseActivity" - | "price" - | "quoteType" - | "recommendationTrend" - | "secFilings" - | "sectorTrend" - | "summaryDetail" - | "summaryProfile" - | "topHoldings" - | "upgradeDowngradeHistory"; +type QuoteSummaryOptions = Static; +type QuoteSummaryResult = Static; -export interface QuoteSummaryOptions { - formatted?: boolean; - modules?: Array | "all"; -} +const QuoteSummaryOptions = Type.Object({ + formatted: Type.Optional(Type.Boolean()), + modules: Type.Optional( + Type.Union([Type.Array(QuoteSummaryModules), Type.Literal("all")]) + ), +}); -const queryOptionsDefaults = { +const queryOptionsDefaults: QuoteSummaryOptions = { formatted: false, modules: ["price", "summaryDetail"], }; @@ -110,25 +116,30 @@ export default function quoteSummary( queryOptionsOverrides?: QuoteSummaryOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "quoteSummary", - query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/v10/finance/quoteSummary/" + symbol, needsCrumb: true, - schemaKey: "#/definitions/QuoteSummaryOptions", + schema: QuoteSummaryOptions, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, - transformWith(options: QuoteSummaryOptions) { - if (options.modules === "all") - options.modules = quoteSummary_modules as Array; + transformWith(options: unknown) { + if ( + typeof options === "object" && + options != null && + "modules" in options && + options.modules === "all" + ) + options.modules = + quoteSummaryModules as Array; return options; }, }, result: { - schemaKey: "#/definitions/QuoteSummaryResult", + schema: QuoteSummaryResult, transformWith(result: any) { if (!result.quoteSummary) throw new Error("Unexpected result: " + JSON.stringify(result)); diff --git a/src/modules/trendingSymbols.ts b/src/modules/trendingSymbols.ts index 19588124..177a26aa 100644 --- a/src/modules/trendingSymbols.ts +++ b/src/modules/trendingSymbols.ts @@ -1,30 +1,51 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { NullableYahooNumber, YahooNumber } from "../lib/yahooFinanceTypes.js"; -export interface TrendingSymbol { - [key: string]: any; - symbol: string; -} +const TrendingSymbol = Type.Object( + { + symbol: Type.String(), + }, + { + additionalProperties: Type.Any(), + } +); -export interface TrendingSymbolsResult { - [key: string]: any; - count: number; - quotes: TrendingSymbol[]; - jobTimestamp: number; - startInterval: number; -} +const TrendingSymbolsResult = Type.Object( + { + count: YahooNumber, + quotes: Type.Array(TrendingSymbol), + jobTimestamp: YahooNumber, + startInterval: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "TrendingSymbolsResult", + } +); -export interface TrendingSymbolsOptions { - lang?: string; - region?: string; - count?: number; -} +const TrendingSymbolsOptions = Type.Optional( + Type.Object( + { + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + count: Type.Optional(YahooNumber), + }, + { + title: "TrendingSymbolsOptions", + } + ) +); + +type TrendingSymbolsResult = Static; +type TrendingSymbolsOptions = Static; -const queryOptionsDefaults = { +const queryOptionsDefaults: TrendingSymbolsOptions = { lang: "en-US", count: 5, }; @@ -49,16 +70,16 @@ export default function trendingSymbols( queryOptionsOverrides?: TrendingSymbolsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "trendingSymbols", query: { url: "https://${YF_QUERY_HOST}/v1/finance/trending/" + query, - schemaKey: "#/definitions/TrendingSymbolsOptions", + schema: TrendingSymbolsOptions, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, }, result: { - schemaKey: "#/definitions/TrendingSymbolsResult", + schema: TrendingSymbolsResult, transformWith(result: any) { if (!result.finance) throw new Error("Unexpected result: " + JSON.stringify(result)); From 9062fa4b4b5bbae0e2c125fdead9b4151d958c99 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:03:06 +0800 Subject: [PATCH 04/13] Add Screener Typebox module --- src/modules/screener.ts | 366 +++++++++++++++++++++------------------- 1 file changed, 193 insertions(+), 173 deletions(-) diff --git a/src/modules/screener.ts b/src/modules/screener.ts index 7e958a57..dc560915 100644 --- a/src/modules/screener.ts +++ b/src/modules/screener.ts @@ -1,175 +1,211 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { + YahooFinanceDate, + YahooNumber, + YahooTwoNumberRange, +} from "../lib/yahooFinanceTypes.js"; -export interface ScreenerResult { - id: string; - title: string; - description: string; - canonicalName: string; - criteriaMeta: ScreenerCriteriaMeta; - rawCriteria: string; - start: number; - count: number; - total: number; - quotes: ScreenerQuote[]; - useRecords: boolean; - predefinedScr: boolean; - versionId: number; - creationDate: number; - lastUpdated: number; - isPremium: boolean; - iconUrl: string; -} +const ScreenerCriterum = Type.Object( + { + field: Type.String(), + operators: Type.Array(Type.String()), + values: Type.Array(YahooNumber), + labelsSelected: Type.Array(YahooNumber), + dependentValues: Type.Array(Type.Any()), + }, + { + title: "ScreenerCriterum", + } +); -export interface ScreenerCriteriaMeta { - size: number; - offset: number; - sortField: string; - sortType: string; - quoteType: string; - criteria: ScreenerCriterum[]; - topOperator: string; -} +const ScreenerCriteriaMeta = Type.Object( + { + size: YahooNumber, + offset: YahooNumber, + sortField: Type.String(), + sortType: Type.String(), + quoteType: Type.String(), + criteria: Type.Array(ScreenerCriterum), + topOperator: Type.String(), + }, + { + title: "ScreenerCriteriaMeta", + } +); -export interface ScreenerCriterum { - field: string; - operators: string[]; - values: number[]; - labelsSelected: number[]; - dependentValues: any[]; -} +const ScreenerQuote = Type.Object( + { + language: Type.String(), + region: Type.String(), + quoteType: Type.String(), + typeDisp: Type.String(), + quoteSourceName: Type.String(), + triggerable: Type.Boolean(), + customPriceAlertConfidence: Type.String(), + lastCloseTevEbitLtm: Type.Optional(YahooNumber), + lastClosePriceToNNWCPerShare: Type.Optional(YahooNumber), + firstTradeDateMilliseconds: YahooNumber, + priceHint: YahooNumber, + postMarketChangePercent: Type.Optional(YahooNumber), + postMarketTime: Type.Optional(YahooNumber), + postMarketPrice: Type.Optional(YahooNumber), + postMarketChange: Type.Optional(YahooNumber), + regularMarketChange: YahooNumber, + regularMarketTime: YahooNumber, + regularMarketPrice: YahooNumber, + regularMarketDayHigh: Type.Optional(YahooNumber), + regularMarketDayRange: YahooTwoNumberRange, + currency: Type.String(), + regularMarketDayLow: Type.Optional(YahooNumber), + regularMarketVolume: Type.Optional(YahooNumber), + regularMarketPreviousClose: YahooNumber, + bid: Type.Optional(YahooNumber), + ask: Type.Optional(YahooNumber), + bidSize: Type.Optional(YahooNumber), + askSize: Type.Optional(YahooNumber), + market: Type.String(), + messageBoardId: Type.String(), + fullExchangeName: Type.String(), + longName: Type.String(), + financialCurrency: Type.Optional(Type.String()), + regularMarketOpen: Type.Optional(YahooNumber), + averageDailyVolume3Month: YahooNumber, + averageDailyVolume10Day: YahooNumber, + fiftyTwoWeekLowChange: YahooNumber, + fiftyTwoWeekLowChangePercent: YahooNumber, + fiftyTwoWeekRange: YahooTwoNumberRange, + fiftyTwoWeekHighChange: YahooNumber, + fiftyTwoWeekHighChangePercent: YahooNumber, + fiftyTwoWeekChangePercent: YahooNumber, + earningsTimestamp: Type.Optional(YahooNumber), + earningsTimestampStart: Type.Optional(YahooNumber), + earningsTimestampEnd: Type.Optional(YahooNumber), + trailingAnnualDividendRate: Type.Optional(YahooNumber), + trailingAnnualDividendYield: Type.Optional(YahooNumber), + marketState: Type.String(), + epsTrailingTwelveMonths: Type.Optional(YahooNumber), + epsForward: Type.Optional(YahooNumber), + epsCurrentYear: Type.Optional(YahooNumber), + priceEpsCurrentYear: Type.Optional(YahooNumber), + sharesOutstanding: Type.Optional(YahooNumber), + bookValue: Type.Optional(YahooNumber), + fiftyDayAverage: YahooNumber, + fiftyDayAverageChange: YahooNumber, + fiftyDayAverageChangePercent: YahooNumber, + twoHundredDayAverage: YahooNumber, + twoHundredDayAverageChange: YahooNumber, + twoHundredDayAverageChangePercent: YahooNumber, + marketCap: Type.Optional(YahooNumber), + forwardPE: Type.Optional(YahooNumber), + priceToBook: Type.Optional(YahooNumber), + sourceInterval: YahooNumber, + exchangeDataDelayedBy: YahooNumber, + exchangeTimezoneName: Type.String(), + exchangeTimezoneShortName: Type.String(), + gmtOffSetMilliseconds: YahooNumber, + esgPopulated: Type.Boolean(), + tradeable: Type.Boolean(), + cryptoTradeable: Type.Boolean(), + exchange: Type.String(), + fiftyTwoWeekLow: YahooNumber, + fiftyTwoWeekHigh: YahooNumber, + shortName: Type.String(), + averageAnalystRating: Type.Optional(Type.String()), + regularMarketChangePercent: YahooNumber, + symbol: Type.String(), + dividendDate: Type.Optional(YahooFinanceDate), + displayName: Type.Optional(Type.String()), + trailingPE: Type.Optional(YahooNumber), + prevName: Type.Optional(Type.String()), + nameChangeDate: Type.Optional(YahooFinanceDate), + ipoExpectedDate: Type.Optional(YahooFinanceDate), + dividendYield: Type.Optional(YahooNumber), + dividendRate: Type.Optional(YahooNumber), + yieldTTM: Type.Optional(YahooNumber), + peTTM: Type.Optional(YahooNumber), + annualReturnNavY3: Type.Optional(YahooNumber), + annualReturnNavY5: Type.Optional(YahooNumber), + ytdReturn: Type.Optional(YahooNumber), + trailingThreeMonthReturns: Type.Optional(YahooNumber), + netAssets: Type.Optional(YahooNumber), + netExpenseRatio: Type.Optional(YahooNumber), + }, + { + title: "ScreenerQuote", + } +); -export interface ScreenerQuote { - language: string; - region: string; - quoteType: string; - typeDisp: string; - quoteSourceName: string; - triggerable: boolean; - customPriceAlertConfidence: string; - lastCloseTevEbitLtm?: number; - lastClosePriceToNNWCPerShare?: number; - firstTradeDateMilliseconds: number; - priceHint: number; - postMarketChangePercent?: number; - postMarketTime?: number; - postMarketPrice?: number; - postMarketChange?: number; - regularMarketChange: number; - regularMarketTime: number; - regularMarketPrice: number; - regularMarketDayHigh?: number; - regularMarketDayRange?: string; - currency: string; - regularMarketDayLow?: number; - regularMarketVolume?: number; - regularMarketPreviousClose: number; - bid?: number; - ask?: number; - bidSize?: number; - askSize?: number; - market: string; - messageBoardId: string; - fullExchangeName: string; - longName: string; - financialCurrency?: string; - regularMarketOpen?: number; - averageDailyVolume3Month: number; - averageDailyVolume10Day: number; - fiftyTwoWeekLowChange: number; - fiftyTwoWeekLowChangePercent: number; - fiftyTwoWeekRange: string; - fiftyTwoWeekHighChange: number; - fiftyTwoWeekHighChangePercent: number; - fiftyTwoWeekChangePercent: number; - earningsTimestamp?: number; - earningsTimestampStart?: number; - earningsTimestampEnd?: number; - trailingAnnualDividendRate?: number; - trailingAnnualDividendYield?: number; - marketState: string; - epsTrailingTwelveMonths?: number; - epsForward?: number; - epsCurrentYear?: number; - priceEpsCurrentYear?: number; - sharesOutstanding?: number; - bookValue?: number; - fiftyDayAverage: number; - fiftyDayAverageChange: number; - fiftyDayAverageChangePercent: number; - twoHundredDayAverage: number; - twoHundredDayAverageChange: number; - twoHundredDayAverageChangePercent: number; - marketCap?: number; - forwardPE?: number; - priceToBook?: number; - sourceInterval: number; - exchangeDataDelayedBy: number; - exchangeTimezoneName: string; - exchangeTimezoneShortName: string; - gmtOffSetMilliseconds: number; - esgPopulated: boolean; - tradeable: boolean; - cryptoTradeable: boolean; - exchange: string; - fiftyTwoWeekLow: number; - fiftyTwoWeekHigh: number; - shortName: string; - averageAnalystRating?: string; - regularMarketChangePercent: number; - symbol: string; - dividendDate?: number; - displayName?: string; - trailingPE?: number; - prevName?: string; - nameChangeDate?: number; - ipoExpectedDate?: number; - dividendYield?: number; - dividendRate?: number; - yieldTTM?: number; - peTTM?: number; - annualReturnNavY3?: number; - annualReturnNavY5?: number; - ytdReturn?: number; - trailingThreeMonthReturns?: number; - netAssets?: number; - netExpenseRatio?: number; -} +const ScreenerResult = Type.Object( + { + id: Type.String(), + title: Type.String(), + description: Type.String(), + canonicalName: Type.String(), + criteriaMeta: ScreenerCriteriaMeta, + rawCriteria: Type.String(), + start: YahooNumber, + count: YahooNumber, + total: YahooNumber, + quotes: Type.Array(ScreenerQuote), + useRecords: Type.Boolean(), + predefinedScr: Type.Boolean(), + versionId: YahooNumber, + creationDate: YahooFinanceDate, + lastUpdated: YahooFinanceDate, + isPremium: Type.Boolean(), + iconUrl: Type.String(), + }, + { + title: "ScreenerResult", + } +); -export type PredefinedScreenerModules = - | "aggressive_small_caps" - | "conservative_foreign_funds" - | "day_gainers" - | "day_losers" - | "growth_technology_stocks" - | "high_yield_bond" - | "most_actives" - | "most_shorted_stocks" - | "portfolio_anchors" - | "small_cap_gainers" - | "solid_large_growth_funds" - | "solid_midcap_growth_funds" - | "top_mutual_funds" - | "undervalued_growth_stocks" - | "undervalued_large_caps"; +const PredefinedScreenerModules = Type.Union( + [ + Type.Literal("aggressive_small_caps"), + Type.Literal("conservative_foreign_funds"), + Type.Literal("day_gainers"), + Type.Literal("day_losers"), + Type.Literal("growth_technology_stocks"), + Type.Literal("high_yield_bond"), + Type.Literal("most_actives"), + Type.Literal("most_shorted_stocks"), + Type.Literal("portfolio_anchors"), + Type.Literal("small_cap_gainers"), + Type.Literal("solid_large_growth_funds"), + Type.Literal("solid_midcap_growth_funds"), + Type.Literal("top_mutual_funds"), + Type.Literal("undervalued_growth_stocks"), + Type.Literal("undervalued_large_caps"), + ], + { + title: "ScreenerPredefinedScreenerModules", + } +); -const queryOptionsDefaults = { +type ScreenerResult = Static; + +type ScreenerOptions = Static; + +const queryOptionsDefaults: ScreenerOptions = { lang: "en-US", region: "US", scrIds: "day_gainers", count: 5, }; -export interface ScreenerOptions { - lang?: string; - region?: string; - scrIds: PredefinedScreenerModules; - count?: number; -} +const ScreenerOptions = Type.Object({ + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + scrIds: PredefinedScreenerModules, + count: Type.Optional(Type.Number()), +}); export default function screener( this: ModuleThis, @@ -188,17 +224,17 @@ export default function screener( queryOptionsOverrides?: ScreenerOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "screener", query: { url: "https://${YF_QUERY_HOST}/v1/finance/screener/predefined/saved", - schemaKey: "#/definitions/ScreenerOptions", + schema: ScreenerOptions, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, needsCrumb: true, }, result: { - schemaKey: "#/definitions/ScreenerResult", + schema: ScreenerResult, transformWith(result: any) { // console.log(result); if (!result.finance) @@ -209,19 +245,3 @@ export default function screener( moduleOptions, }); } - -// aggressive_small_caps -// conservative_foreign_funds -// day_gainers -// day_losers -// growth_technology_stocks -// high_yield_bond -// most_actives -// most_shorted_stocks -// portfolio_anchors -// small_cap_gainers -// solid_large_growth_funds -// solid_midcap_growth_funds -// top_mutual_funds -// undervalued_growth_stocks -// undervalued_large_caps From 4b4efee06b55cb4d9c77279e2753a35a6772560c Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Tue, 4 Jun 2024 20:03:50 +0800 Subject: [PATCH 05/13] Add RecommendationBySymbol module --- schema.json | 225 ++++++++++++++++--------- src/modules/recommendationsBySymbol.ts | 57 +++++-- tests/http/quote-AAPL.bad.fake.json | 73 -------- 3 files changed, 186 insertions(+), 169 deletions(-) delete mode 100644 tests/http/quote-AAPL.bad.fake.json diff --git a/schema.json b/schema.json index a26fcc14..ee921544 100644 --- a/schema.json +++ b/schema.json @@ -5589,6 +5589,71 @@ ], "additionalProperties": false }, + "EnumGrade": { + "type": "string", + "enum": [ + "Accumulate", + "Add", + "Average", + "Below Average", + "Buy", + "Conviction Buy", + "", + "Equal-Weight", + "Fair Value", + "Equal-weight", + "Long-term Buy", + "Hold", + "Long-Term Buy", + "Market Outperform", + "Market Perform", + "Mixed", + "Negative", + "Neutral", + "In-Line", + "Outperform", + "Overweight", + "Peer Perform", + "Perform", + "Positive", + "Reduce", + "Sector Outperform", + "Sector Perform", + "Sector Weight", + "Sell", + "Strong Buy", + "Top Pick", + "Underperform", + "Underperformer", + "Underweight", + "Trim", + "Above Average", + "In-line", + "Outperformer", + "OVerweight", + "Cautious", + "Market Weight", + "Sector Underperform", + "Market Underperform", + "Peer perform", + "Gradually Accumulate", + "Action List Buy", + "Performer", + "Sector Performer", + "Speculative Buy", + "Strong Sell", + "Speculative Hold", + "Not Rated", + "Hold Neutral", + "Developing", + "buy", + "HOld", + "Trading Sell", + "Tender", + "market perform", + "BUy" + ] + }, "QuoteSummaryResult": { "type": "object", "properties": { @@ -8694,6 +8759,42 @@ "upgradeDowngradeHistory" ] }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "query": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/RecommendationsBySymbolOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "query" + ], + "additionalProperties": false + }, + "RecommendationsBySymbolOptions": { + "type": "object", + "additionalProperties": false + }, "RecommendationsBySymbolResponse": { "type": "object", "properties": { @@ -8730,42 +8831,65 @@ "$ref": "#/definitions/RecommendationsBySymbolResponse" } }, - "RecommendationsBySymbolOptions": { - "type": "object", - "additionalProperties": false - }, - "NamedParameters": { + "NamedParameters": { "type": "object", "properties": { "this": { "$ref": "#/definitions/ModuleThis" }, - "query": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, "queryOptionsOverrides": { - "$ref": "#/definitions/RecommendationsBySymbolOptions" + "$ref": "#/definitions/ScreenerOptions" }, "moduleOptions": { "$ref": "#/definitions/ModuleOptions" } }, "required": [ - "this", - "query" + "this" + ], + "additionalProperties": false + }, + "ScreenerOptions": { + "type": "object", + "properties": { + "lang": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scrIds": { + "$ref": "#/definitions/PredefinedScreenerModules" + }, + "count": { + "yahooFinanceType": "number" + } + }, + "required": [ + "scrIds" ], "additionalProperties": false }, + "PredefinedScreenerModules": { + "type": "string", + "enum": [ + "aggressive_small_caps", + "conservative_foreign_funds", + "day_gainers", + "day_losers", + "growth_technology_stocks", + "high_yield_bond", + "most_actives", + "most_shorted_stocks", + "portfolio_anchors", + "small_cap_gainers", + "solid_large_growth_funds", + "solid_midcap_growth_funds", + "top_mutual_funds", + "undervalued_growth_stocks", + "undervalued_large_caps" + ] + }, "ScreenerResult": { "type": "object", "properties": { @@ -9259,65 +9383,6 @@ ], "additionalProperties": false }, - "PredefinedScreenerModules": { - "type": "string", - "enum": [ - "aggressive_small_caps", - "conservative_foreign_funds", - "day_gainers", - "day_losers", - "growth_technology_stocks", - "high_yield_bond", - "most_actives", - "most_shorted_stocks", - "portfolio_anchors", - "small_cap_gainers", - "solid_large_growth_funds", - "solid_midcap_growth_funds", - "top_mutual_funds", - "undervalued_growth_stocks", - "undervalued_large_caps" - ] - }, - "ScreenerOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "scrIds": { - "$ref": "#/definitions/PredefinedScreenerModules" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "required": [ - "scrIds" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/ScreenerOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this" - ], - "additionalProperties": false - }, "NamedParameters": { "type": "object", "properties": { diff --git a/src/modules/recommendationsBySymbol.ts b/src/modules/recommendationsBySymbol.ts index 0d50b77b..6769b229 100644 --- a/src/modules/recommendationsBySymbol.ts +++ b/src/modules/recommendationsBySymbol.ts @@ -1,26 +1,51 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateFalse, ModuleOptionsWithValidateTrue, ModuleThis, } from "../lib/moduleCommon.js"; +import { YahooNumber } from "../lib/yahooFinanceTypes.js"; -export interface RecommendationsBySymbolResponse { - [key: string]: any; - recommendedSymbols: Array<{ - [key: string]: any; - score: number; // 0.1927 - symbol: string; // "BMW.DE" - }>; - symbol: string; -} +const RecommendationsBySymbolResponse = Type.Object( + { + recommendedSymbols: Type.Array( + Type.Object( + { + score: YahooNumber, // 0.1927 + symbol: Type.String(), // "BMW.DE" + }, + { + additionalProperties: Type.Any(), + } + ) + ), + symbol: Type.String(), + }, + { + additionalProperties: Type.Any(), + } +); + +const RecommendationsBySymbolResponseArray = Type.Array( + RecommendationsBySymbolResponse +); + +const RecommendationsBySymbolOptions = Type.Object({}); + +type RecommendationsBySymbolResponse = Static< + typeof RecommendationsBySymbolResponse +>; -export type RecommendationsBySymbolResponseArray = - RecommendationsBySymbolResponse[]; +type RecommendationsBySymbolOptions = Static< + typeof RecommendationsBySymbolOptions +>; -export interface RecommendationsBySymbolOptions {} +type RecommendationsBySymbolResponseArray = Static< + typeof RecommendationsBySymbolResponseArray +>; -const queryOptionsDefaults = {}; +const queryOptionsDefaults: RecommendationsBySymbolOptions = {}; export default function recommendationsBySymbol( this: ModuleThis, @@ -51,20 +76,20 @@ export default function recommendationsBySymbol( ): Promise { const symbols = typeof query === "string" ? query : query.join(","); - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "recommendationsBySymbol", query: { url: "https://${YF_QUERY_HOST}/v6/finance/recommendationsbysymbol/" + symbols, - schemaKey: "#/definitions/RecommendationsBySymbolOptions", + schema: RecommendationsBySymbolOptions, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, }, result: { - schemaKey: "#/definitions/RecommendationsBySymbolResponseArray", + schema: RecommendationsBySymbolResponseArray, transformWith(result: any) { if (!result.finance) throw new Error("Unexpected result: " + JSON.stringify(result)); diff --git a/tests/http/quote-AAPL.bad.fake.json b/tests/http/quote-AAPL.bad.fake.json deleted file mode 100644 index e26a7cc1..00000000 --- a/tests/http/quote-AAPL.bad.fake.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "request": { - "url": "https://query2.finance.yahoo.com/v7/finance/quote?symbols=AAPL" - }, - "response": { - "ok": true, - "status": 200, - "statusText": "OK", - "headers": { - "content-type": [ - "application/json;charset=utf-8" - ], - "cache-control": [ - "public, max-age=1, stale-while-revalidate=9" - ], - "vary": [ - "Origin,Accept-Encoding" - ], - "y-rid": [ - "4n543alif8f75" - ], - "x-yahoo-request-id": [ - "4n543alif8f75" - ], - "x-request-id": [ - "d3b398d2-e2b6-4984-80a2-3f569abcc46e" - ], - "content-encoding": [ - "gzip" - ], - "content-length": [ - "1088" - ], - "x-envoy-upstream-service-time": [ - "2" - ], - "date": [ - "Sun, 03 Sep 2023 07:59:33 GMT" - ], - "server": [ - "ATS" - ], - "x-envoy-decorator-operation": [ - "finance-quote-api--mtls-production-ir2.finance-k8s.svc.yahoo.local:4080/*" - ], - "age": [ - "0" - ], - "strict-transport-security": [ - "max-age=31536000" - ], - "referrer-policy": [ - "no-referrer-when-downgrade" - ], - "x-frame-options": [ - "SAMEORIGIN" - ], - "connection": [ - "close" - ], - "expect-ct": [ - "max-age=31536000, report-uri=\"http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only\"" - ], - "x-xss-protection": [ - "1; mode=block" - ], - "x-content-type-options": [ - "nosniff" - ] - }, - "body": "{\"quoteResponse\":{\"result\":[{\"language\":false,\"region\":\"US\",\"quoteType\":\"EQUITY\",\"typeDisp\":\"Equity\",\"quoteSourceName\":\"Delayed Quote\",\"triggerable\":true,\"customPriceAlertConfidence\":\"HIGH\",\"marketState\":\"CLOSED\",\"currency\":\"USD\",\"exchange\":\"NMS\",\"shortName\":\"Apple Inc.\",\"longName\":\"Apple Inc.\",\"messageBoardId\":\"finmb_24937\",\"exchangeTimezoneName\":\"America/New_York\",\"exchangeTimezoneShortName\":\"EDT\",\"gmtOffSetMilliseconds\":-14400000,\"market\":\"us_market\",\"esgPopulated\":false,\"regularMarketChangePercent\":0.8463361,\"regularMarketPrice\":189.46,\"earningsTimestampStart\":1698231540,\"earningsTimestampEnd\":1698667200,\"trailingAnnualDividendRate\":0.93,\"trailingPE\":31.735346,\"dividendRate\":0.96,\"trailingAnnualDividendYield\":0.004950232,\"dividendYield\":0.51,\"epsTrailingTwelveMonths\":5.97,\"epsForward\":6.61,\"epsCurrentYear\":6.07,\"priceEpsCurrentYear\":31.21252,\"sharesOutstanding\":15634199552,\"bookValue\":3.852,\"fiftyDayAverage\":186.7088,\"fiftyDayAverageChange\":2.7512054,\"fiftyDayAverageChangePercent\":0.014735275,\"twoHundredDayAverage\":163.40675,\"twoHundredDayAverageChange\":26.053253,\"twoHundredDayAverageChangePercent\":0.15943804,\"marketCap\":2962055495680,\"forwardPE\":28.662632,\"priceToBook\":49.18484,\"sourceInterval\":15,\"exchangeDataDelayedBy\":0,\"averageAnalystRating\":\"2.0 - Buy\",\"tradeable\":false,\"cryptoTradeable\":false,\"firstTradeDateMilliseconds\":345479400000,\"priceHint\":2,\"postMarketChangePercent\":-0.09501105,\"postMarketTime\":1693612792,\"postMarketPrice\":189.28,\"postMarketChange\":-0.18000793,\"regularMarketChange\":1.5900116,\"regularMarketTime\":1693598401,\"regularMarketDayHigh\":189.9175,\"regularMarketDayRange\":\"188.28 - 189.9175\",\"regularMarketDayLow\":188.28,\"regularMarketVolume\":42235109,\"regularMarketPreviousClose\":187.87,\"bid\":189.24,\"ask\":189.31,\"bidSize\":10,\"askSize\":8,\"fullExchangeName\":\"NasdaqGS\",\"financialCurrency\":\"USD\",\"regularMarketOpen\":189.485,\"averageDailyVolume3Month\":56039552,\"averageDailyVolume10Day\":51167990,\"fiftyTwoWeekLowChange\":65.29001,\"fiftyTwoWeekLowChangePercent\":0.5258115,\"fiftyTwoWeekRange\":\"124.17 - 198.23\",\"fiftyTwoWeekHighChange\":-8.769989,\"fiftyTwoWeekHighChangePercent\":-0.044241484,\"fiftyTwoWeekLow\":124.17,\"fiftyTwoWeekHigh\":198.23,\"fiftyTwoWeekChangePercent\":22.604025,\"dividendDate\":1692230400,\"earningsTimestamp\":1691096400,\"displayName\":\"Apple\",\"symbol\":\"AAPL\"}],\"error\":null}}" - } -} \ No newline at end of file From 38e4bd3160aee5a81034be84d6076ce1304295c5 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:02:43 +0800 Subject: [PATCH 06/13] Add Options Typebox Module --- schema.json | 102 +++++----- src/modules/options.spec.ts | 2 +- src/modules/options.ts | 385 +++++++++++++++++++++++++++++++----- 3 files changed, 383 insertions(+), 106 deletions(-) diff --git a/schema.json b/schema.json index ee921544..64be2b3f 100644 --- a/schema.json +++ b/schema.json @@ -2213,6 +2213,57 @@ }, "additionalProperties": false }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "symbol": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/OptionsOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "symbol", + "queryOptionsOverrides" + ], + "additionalProperties": false + }, + "OptionsOptions": { + "type": "object", + "properties": { + "formatted": { + "type": "boolean" + }, + "lang": { + "type": "string" + }, + "region": { + "type": "string" + }, + "date": { + "anyOf": [ + { + "yahooFinanceType": "date" + }, + { + "yahooFinanceType": "number" + }, + { + "type": "string" + } + ] + } + }, + "additionalProperties": false + }, "QuoteBase": { "type": "object", "properties": { @@ -5538,57 +5589,6 @@ "inTheMoney" ] }, - "OptionsOptions": { - "type": "object", - "properties": { - "formatted": { - "type": "boolean" - }, - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "date": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "yahooFinanceType": "number" - }, - { - "type": "string" - } - ] - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/OptionsOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, "EnumGrade": { "type": "string", "enum": [ diff --git a/src/modules/options.spec.ts b/src/modules/options.spec.ts index 123f4bae..8a89c6fe 100644 --- a/src/modules/options.spec.ts +++ b/src/modules/options.spec.ts @@ -57,7 +57,7 @@ describe("options", () => { it("throws on invalid", () => { return expect( yf.options("AAPL", { date: "something yfDate can't parse" }) - ).rejects.toThrow(/^Unsupported date type/); + ).rejects.toThrow("Validation called with invalid options"); }); }); }); diff --git a/src/modules/options.ts b/src/modules/options.ts index e1add34c..01f2b1cc 100644 --- a/src/modules/options.ts +++ b/src/modules/options.ts @@ -4,52 +4,334 @@ import type { ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { Type, Static } from "@sinclair/typebox"; -import { Quote } from "./quote.js"; +import { + YahooDateInMs, + YahooFinanceDate, + YahooNumber, + YahooTwoNumberRange, +} from "../lib/yahooFinanceTypes.js"; -export interface OptionsResult { - [key: string]: any; - underlyingSymbol: string; - expirationDates: Date[]; - strikes: number[]; - hasMiniOptions: boolean; - quote: Quote; - options: Option[]; -} +import { Value } from "@sinclair/typebox/value"; -export interface Option { - [key: string]: any; - expirationDate: Date; - hasMiniOptions: boolean; - calls: CallOrPut[]; - puts: CallOrPut[]; -} +const QuoteBase = Type.Object( + { + language: Type.String(), // "en-US", + region: Type.String(), // "US", + quoteType: Type.String(), // "EQUITY" | "ETF" | "MUTUALFUND"; + typeDisp: Type.Optional(Type.String()), // "Equity", not always present. + quoteSourceName: Type.Optional(Type.String()), // "Delayed Quote", + triggerable: Type.Boolean(), // true, + currency: Type.Optional(Type.String()), // "USD", + // Seems to appear / disappear based not on symbol but network load (#445) + customPriceAlertConfidence: Type.Optional(Type.String()), // "HIGH" | "LOW"; TODO: anything else? + marketState: Type.Union([ + Type.Literal("REGULAR"), + Type.Literal("CLOSED"), + Type.Literal("PRE"), + Type.Literal("PREPRE"), + Type.Literal("POST"), + Type.Literal("POSTPOST"), + ]), + tradeable: Type.Boolean(), // false, + cryptoTradeable: Type.Optional(Type.Boolean()), // false + exchange: Type.String(), // "NMS", + shortName: Type.Optional(Type.String()), // "NVIDIA Corporation", + longName: Type.Optional(Type.String()), // "NVIDIA Corporation", + messageBoardId: Type.Optional(Type.String()), // "finmb_32307", + exchangeTimezoneName: Type.String(), // "America/New_York", + exchangeTimezoneShortName: Type.String(), // "EST", + gmtOffSetMilliseconds: YahooNumber, // -18000000, + market: Type.String(), // "us_market", + esgPopulated: Type.Boolean(), // false, + fiftyTwoWeekLowChange: Type.Optional(YahooNumber), // 362.96002, + fiftyTwoWeekLowChangePercent: Type.Optional(YahooNumber), // 2.0088556, + fiftyTwoWeekRange: Type.Optional(YahooTwoNumberRange), // "180.68 - 589.07" -> { low, high } + fiftyTwoWeekHighChange: Type.Optional(YahooNumber), // -45.429993, + fiftyTwoWeekHighChangePercent: Type.Optional(YahooNumber), // -0.07712155, + fiftyTwoWeekLow: Type.Optional(YahooNumber), // 180.68, + fiftyTwoWeekHigh: Type.Optional(YahooNumber), // 589.07, + fiftyTwoWeekChangePercent: Type.Optional(YahooNumber), // 22.604025 + dividendDate: Type.Optional(YahooFinanceDate), // 1609200000, + // maybe always present on EQUITY? + earningsTimestamp: Type.Optional(YahooFinanceDate), // 1614200400, + earningsTimestampStart: Type.Optional(YahooFinanceDate), // 1614200400, + earningsTimestampEnd: Type.Optional(YahooFinanceDate), // 1614200400, + trailingAnnualDividendRate: Type.Optional(YahooNumber), // 0.64, + trailingPE: Type.Optional(YahooNumber), // 88.873634, + trailingAnnualDividendYield: Type.Optional(YahooNumber), // 0.0011709387, + epsTrailingTwelveMonths: Type.Optional(YahooNumber), // 6.117, + epsForward: Type.Optional(YahooNumber), // 11.68, + epsCurrentYear: Type.Optional(YahooNumber), // 9.72, + priceEpsCurrentYear: Type.Optional(YahooNumber), // 55.930042, + sharesOutstanding: Type.Optional(YahooNumber), // 619000000, + bookValue: Type.Optional(YahooNumber), // 24.772, + fiftyDayAverage: Type.Optional(YahooNumber), // 530.8828, + fiftyDayAverageChange: Type.Optional(YahooNumber), // 12.757202, + fiftyDayAverageChangePercent: Type.Optional(YahooNumber), // 0.024030166, + twoHundredDayAverage: Type.Optional(YahooNumber), // 515.8518, + twoHundredDayAverageChange: Type.Optional(YahooNumber), // 27.788208, + twoHundredDayAverageChangePercent: Type.Optional(YahooNumber), // 0.053868588, + marketCap: Type.Optional(YahooNumber), // 336513171456, + forwardPE: Type.Optional(YahooNumber), // 46.54452, + priceToBook: Type.Optional(YahooNumber), // 21.945745, + sourceInterval: YahooNumber, // 15, + exchangeDataDelayedBy: YahooNumber, // 0, + firstTradeDateMilliseconds: Type.Optional(YahooDateInMs), // 917015400000 -> Date + priceHint: YahooNumber, // 2, + postMarketChangePercent: Type.Optional(YahooNumber), // 0.093813874, + postMarketTime: Type.Optional(YahooFinanceDate), // 1612573179 -> new Date() + postMarketPrice: Type.Optional(YahooNumber), // 544.15, + postMarketChange: Type.Optional(YahooNumber), // 0.51000977, + regularMarketChange: Type.Optional(YahooNumber), // -2.9299927, + regularMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, + regularMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() + regularMarketPrice: Type.Optional(YahooNumber), // 543.64, + regularMarketDayHigh: Type.Optional(YahooNumber), // 549.19, + regularMarketDayRange: Type.Optional(YahooTwoNumberRange), // "541.867 - 549.19" -> { low, high } + regularMarketDayLow: Type.Optional(YahooNumber), // 541.867, + regularMarketVolume: Type.Optional(YahooNumber), // 4228841, + regularMarketPreviousClose: Type.Optional(YahooNumber), // 546.57, + preMarketChange: Type.Optional(YahooNumber), // -2.9299927, + preMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, + preMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() + preMarketPrice: Type.Optional(YahooNumber), // 543.64, + bid: Type.Optional(YahooNumber), // 543.84, + ask: Type.Optional(YahooNumber), // 544.15, + bidSize: Type.Optional(YahooNumber), // 18, + askSize: Type.Optional(YahooNumber), // 8, + fullExchangeName: Type.String(), // "NasdaqGS", + financialCurrency: Type.Optional(Type.String()), // "USD", + regularMarketOpen: Type.Optional(YahooNumber), // 549.0, + averageDailyVolume3Month: Type.Optional(YahooNumber), // 7475022, + averageDailyVolume10Day: Type.Optional(YahooNumber), // 5546385, + displayName: Type.Optional(Type.String()), // "NVIDIA", + symbol: Type.String(), // "NVDA" + underlyingSymbol: Type.Optional(Type.String()), // "LD.MI" (for LDO.MI, #363) + // only on ETF? not on EQUITY? + ytdReturn: Type.Optional(YahooNumber), // 0.31 + trailingThreeMonthReturns: Type.Optional(YahooNumber), // 16.98 + trailingThreeMonthNavReturns: Type.Optional(YahooNumber), // 17.08 + ipoExpectedDate: Type.Optional(YahooFinanceDate), // "2020-08-13", + newListingDate: Type.Optional(YahooFinanceDate), // "2021-02-16", + nameChangeDate: Type.Optional(YahooFinanceDate), + prevName: Type.Optional(Type.String()), + averageAnalystRating: Type.Optional(Type.String()), + pageViewGrowthWeekly: Type.Optional(YahooNumber), // Since 2021-11-11 (#326) + openInterest: Type.Optional(YahooNumber), // SOHO (#248) + beta: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + title: "QuoteBase", + } +); -export interface CallOrPut { - [key: string]: any; - contractSymbol: string; - strike: number; - currency?: string; - lastPrice: number; - change: number; - percentChange?: number; - volume?: number; - openInterest?: number; - bid?: number; - ask?: number; - contractSize: "REGULAR"; - expiration: Date; - lastTradeDate: Date; - impliedVolatility: number; - inTheMoney: boolean; -} +/* + * [TODO] Fields seen in a query but not in this module yet: + * + * - extendedMarketChange + * - extendedMarketChangePercent + * - extendedMarketPrice + * - extendedMarketTime + * - dayHigh (separate to regularMarketDayHigh, etc) + * - dayLow (separate to regularMarketDayLow, etc) + * - volume (separate to regularMarketVolume, etc) + * + * i.e. on yahoo site, with ?fields=dayHigh,dayLow,etc. + */ -export interface OptionsOptions { - formatted?: boolean; - lang?: string; - region?: string; - date?: Date | number | string; -} +/* + * Guaranteed fields, even we don't ask for them + */ +const QuoteCryptoCurrency = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("CRYPTOCURRENCY"), + circulatingSupply: YahooNumber, + fromCurrency: Type.String(), // 'BTC' + toCurrency: Type.String(), // 'USD=X' + lastMarket: Type.String(), // 'CoinMarketCap' + coinImageUrl: Type.Optional(Type.String()), // 'https://s.yimg.com/uc/fin/img/reports-thumbnails/1.png' + volume24Hr: Type.Optional(YahooNumber), // 62631043072 + volumeAllCurrencies: Type.Optional(YahooNumber), // 62631043072 + startDate: Type.Optional(YahooFinanceDate), // new Date(1367103600 * 1000) + }), + ], + { title: "QuoteCryptoCurrency" } +); + +const QuoteCurrency = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("CURRENCY"), + }), + ], + { title: "QuoteCurrency" } +); + +const QuoteEtf = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("ETF"), + }), +]); + +const QuoteEquity = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("EQUITY"), + dividendRate: Type.Optional(Type.Number()), + dividendYield: Type.Optional(Type.Number()), + }), + ], + { title: "QuoteEquity" } +); + +const QuoteFuture = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("FUTURE"), + headSymbolAsString: Type.String(), + contractSymbol: Type.Boolean(), + underlyingExchangeSymbol: Type.String(), + expireDate: YahooFinanceDate, + expireIsoDate: YahooFinanceDate, + }), + ], + { + title: "QuoteFuture", + } +); + +const QuoteIndex = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("INDEX"), + }), + ], + { + title: "QuoteIndex", + } +); + +const QuoteOption = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("OPTION"), + strike: YahooNumber, + openInterest: YahooNumber, + expireDate: YahooNumber, + expireIsoDate: YahooNumber, + underlyingSymbol: Type.String(), + }), + ], + { + title: "QuoteOption", + } +); + +const QuoteMutualfund = Type.Composite( + [ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("MUTUALFUND"), + }), + ], + { + title: "QuoteMutualFund", + } +); + +const QuoteSchema = Type.Union( + [ + QuoteCryptoCurrency, + QuoteCurrency, + QuoteEtf, + QuoteEquity, + QuoteFuture, + QuoteIndex, + QuoteMutualfund, + QuoteOption, + ], + { + title: "Quote", + } +); + +const CallOrPut = Type.Object( + { + contractSymbol: Type.String(), + strike: YahooNumber, + currency: Type.Optional(Type.String()), + lastPrice: YahooNumber, + change: YahooNumber, + percentChange: Type.Optional(YahooNumber), + volume: Type.Optional(YahooNumber), + openInterest: Type.Optional(YahooNumber), + bid: Type.Optional(YahooNumber), + ask: Type.Optional(YahooNumber), + contractSize: Type.Literal("REGULAR"), + expiration: YahooFinanceDate, + lastTradeDate: YahooFinanceDate, + impliedVolatility: YahooNumber, + inTheMoney: Type.Boolean(), + }, + { + additionalProperties: Type.Any(), + title: "CallOrPut", + } +); + +const Option = Type.Object( + { + expirationDate: YahooFinanceDate, + hasMiniOptions: Type.Boolean(), + calls: Type.Array(CallOrPut), + puts: Type.Array(CallOrPut), + }, + { + additionalProperties: Type.Any(), + title: "Option", + } +); + +const OptionsResultSchema = Type.Object( + { + underlyingSymbol: Type.String(), + expirationDates: Type.Array(YahooFinanceDate), + strikes: Type.Array(YahooNumber), + hasMiniOptions: Type.Boolean(), + quote: QuoteSchema, + options: Type.Array(Option), + }, + { + additionalProperties: Type.Any(), + title: "OptionsResult", + } +); + +const OptionsOptionsSchema = Type.Object( + { + formatted: Type.Optional(Type.Boolean()), + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + date: Type.Optional(YahooFinanceDate), + }, + { + title: "OptionsOptions", + } +); + +type OptionsOptions = Static; +type OptionsResult = Static; const queryOptionsDefaults: OptionsOptions = { formatted: false, @@ -77,34 +359,29 @@ export default function options( queryOptionsOverrides: OptionsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "options", query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/v7/finance/options/" + symbol, needsCrumb: true, - schemaKey: "#/definitions/OptionsOptions", + schema: OptionsOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, transformWith(queryOptions: OptionsOptions) { - const date = queryOptions.date; - if (date) { - // yfDate will convert valid number/string to Date. - if (date instanceof Date) { - // now we convert back to unix epoch in seconds for query - queryOptions.date = Math.floor(date.getTime() / 1000); - } else { - // yfDate didn't recognize it as a date. - throw new Error("Unsupported date type: " + date); - } + // This is honestly the easiest way to coerce the date properly + const parsed = Value.Decode(OptionsOptionsSchema, queryOptions); + + if (parsed.date) { + queryOptions.date = Math.floor(parsed.date.getTime() / 1000); } return queryOptions; }, }, result: { - schemaKey: "#/definitions/OptionsResult", + schema: OptionsResultSchema, transformWith(result: any) { if (!result.optionChain) throw new Error("Unexpected result: " + JSON.stringify(result)); From f67181db456ef168f2d3dd24c76eadcb09a8f46e Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 12:48:06 +0800 Subject: [PATCH 07/13] Add Insights Typebox Module --- schema.json | 236 ++++++++++++++-------------- src/modules/insights.ts | 336 ++++++++++++++++++++++++++++------------ 2 files changed, 357 insertions(+), 215 deletions(-) diff --git a/schema.json b/schema.json index 64be2b3f..299f88ba 100644 --- a/schema.json +++ b/schema.json @@ -1677,87 +1677,6 @@ ], "additionalProperties": false }, - "InsightsResult": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "instrumentInfo": { - "$ref": "#/definitions/InsightsInstrumentInfo" - }, - "companySnapshot": { - "$ref": "#/definitions/InsightsCompanySnapshot" - }, - "recommendation": { - "type": "object", - "properties": { - "targetPrice": { - "yahooFinanceType": "number" - }, - "provider": { - "type": "string" - }, - "rating": { - "type": "string", - "enum": [ - "BUY", - "SELL", - "HOLD" - ] - } - }, - "required": [ - "provider", - "rating" - ], - "additionalProperties": false - }, - "events": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsEvent" - } - }, - "reports": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsReport" - } - }, - "sigDevs": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsSigDev" - } - }, - "upsell": { - "$ref": "#/definitions/InsightsUpsell" - }, - "upsellSearchDD": { - "type": "object", - "properties": { - "researchReports": { - "$ref": "#/definitions/InsightsResearchReport" - } - }, - "required": [ - "researchReports" - ], - "additionalProperties": false - }, - "secReports": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsSecReport" - } - } - }, - "required": [ - "symbol", - "sigDevs" - ] - }, "InsightsInstrumentInfo": { "type": "object", "properties": { @@ -1953,6 +1872,124 @@ "sector" ] }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "query": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/TrendingSymbolsOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "query" + ], + "additionalProperties": false + }, + "TrendingSymbolsOptions": { + "type": "object", + "properties": { + "lang": { + "type": "string" + }, + "region": { + "type": "string" + }, + "count": { + "yahooFinanceType": "number" + } + }, + "additionalProperties": false + }, + "InsightsResult": { + "type": "object", + "properties": { + "symbol": { + "type": "string" + }, + "instrumentInfo": { + "$ref": "#/definitions/InsightsInstrumentInfo" + }, + "companySnapshot": { + "$ref": "#/definitions/InsightsCompanySnapshot" + }, + "recommendation": { + "type": "object", + "properties": { + "targetPrice": { + "yahooFinanceType": "number" + }, + "provider": { + "type": "string" + }, + "rating": { + "type": "string", + "enum": [ + "BUY", + "SELL", + "HOLD" + ] + } + }, + "required": [ + "provider", + "rating" + ], + "additionalProperties": false + }, + "events": { + "type": "array", + "items": { + "$ref": "#/definitions/InsightsEvent" + } + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/definitions/InsightsReport" + } + }, + "sigDevs": { + "type": "array", + "items": { + "$ref": "#/definitions/InsightsSigDev" + } + }, + "upsell": { + "$ref": "#/definitions/InsightsUpsell" + }, + "upsellSearchDD": { + "type": "object", + "properties": { + "researchReports": { + "$ref": "#/definitions/InsightsResearchReport" + } + }, + "required": [ + "researchReports" + ], + "additionalProperties": false + }, + "secReports": { + "type": "array", + "items": { + "$ref": "#/definitions/InsightsSecReport" + } + } + }, + "required": [ + "symbol", + "sigDevs" + ] + }, "InsightsEvent": { "type": "object", "properties": { @@ -2176,43 +2213,6 @@ }, "additionalProperties": false }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/TrendingSymbolsOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, - "TrendingSymbolsOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, "NamedParameters": { "type": "object", "properties": { diff --git a/src/modules/insights.ts b/src/modules/insights.ts index 40c52289..250c3964 100644 --- a/src/modules/insights.ts +++ b/src/modules/insights.ts @@ -4,78 +4,240 @@ import type { ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { Type, Static } from "@sinclair/typebox"; -import type { DateInMs } from "../lib/commonTypes.js"; +import { + YahooDateInMs, + YahooFinanceDate, + YahooNumber, +} from "../lib/yahooFinanceTypes.js"; -export interface InsightsResult { - [key: string]: any; - symbol: string; - instrumentInfo?: InsightsInstrumentInfo; - companySnapshot?: InsightsCompanySnapshot; - recommendation?: { - targetPrice?: number; - provider: string; - rating: "BUY" | "SELL" | "HOLD"; - }; - events?: InsightsEvent[]; - reports?: InsightsReport[]; - sigDevs: InsightsSigDev[]; - upsell?: InsightsUpsell; - upsellSearchDD?: { - researchReports: InsightsResearchReport; - }; - secReports?: InsightsSecReport[]; -} +const InsightsDirection = Type.Union( + [Type.Literal("Bearish"), Type.Literal("Bullish"), Type.Literal("Neutral")], + { title: "InsightsDirection" } +); -export interface InsightsSigDev { - [key: string]: any; - headline: string; - date: Date; -} +const InsightsOutlookSchema = Type.Object( + { + stateDescription: Type.String(), + direction: InsightsDirection, + score: YahooNumber, + scoreDescription: Type.String(), + sectorDirection: Type.Optional(InsightsDirection), + sectorScore: Type.Optional(YahooNumber), + sectorScoreDescription: Type.Optional(Type.String()), + indexDirection: InsightsDirection, + indexScore: YahooNumber, + indexScoreDescription: Type.String(), + }, + { + additionalProperties: Type.Any(), + title: "InsightsOutlook", + } +); -export interface InsightsReport { - [key: string]: any; - id: string; - headHtml: string; - provider: string; - reportDate: Date; - reportTitle: string; - reportType: string; - targetPrice?: number; - targetPriceStatus?: "Increased" | "Maintained" | "Decreased" | "-"; - investmentRating?: "Bullish" | "Neutral" | "Bearish"; - tickers?: string[]; -} +const InsightsInstrumentInfo = Type.Object( + { + keyTechnicals: Type.Object( + { + provider: Type.String(), + support: Type.Optional(YahooNumber), + resistance: Type.Optional(YahooNumber), + stopLoss: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + } + ), + technicalEvents: Type.Object( + { + provider: Type.String(), + sector: Type.Optional(Type.String()), + shortTermOutlook: InsightsOutlookSchema, + intermediateTermOutlook: InsightsOutlookSchema, + longTermOutlook: InsightsOutlookSchema, + }, + { + additionalProperties: Type.Any(), + } + ), + valuation: Type.Object( + { + color: Type.Optional(YahooNumber), + description: Type.Optional(Type.String()), + discount: Type.Optional(Type.String()), + provider: Type.String(), + relativeValue: Type.Optional(Type.String()), + }, + { + additionalProperties: Type.Any(), + } + ), + }, + { + additionalProperties: Type.Any(), + title: "InsightsInstrumentInfo", + } +); -export interface InsightsResearchReport { - reportId: string; - provider: string; - title: string; - reportDate: Date; - summary: string; - investmentRating?: "Bullish" | "Neutral" | "Bearish"; -} +const InsightsCompanySnapshot = Type.Object( + { + sectorInfo: Type.Optional(Type.String()), + company: Type.Object( + { + innovativeness: Type.Optional(YahooNumber), + hiring: Type.Optional(YahooNumber), + sustainability: Type.Optional(YahooNumber), + insiderSentiments: Type.Optional(YahooNumber), + earningsReports: Type.Optional(YahooNumber), + dividends: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + } + ), + sector: Type.Object( + { + innovativeness: YahooNumber, + hiring: YahooNumber, + sustainability: Type.Optional(YahooNumber), + insiderSentiments: YahooNumber, + earningsReports: Type.Optional(YahooNumber), + dividends: YahooNumber, + }, + { + additionalProperties: Type.Any(), + } + ), + }, + { title: "InsightsCompanySnapshot", additionalProperties: Type.Any() } +); -export interface InsightsSecReport { - id: string; - type: string; - title: string; - description: string; - filingDate: DateInMs; - snapshotUrl: string; - formType: string; -} +const InsightsEventSchema = Type.Object( + { + eventType: Type.String(), + pricePeriod: Type.String(), + tradingHorizon: Type.String(), + tradeType: Type.String(), + imageUrl: Type.String(), + startDate: YahooFinanceDate, + endDate: YahooFinanceDate, + }, + { title: "InsightsEvent", additionalProperties: Type.Any() } +); +const InsightsReport = Type.Object( + { + id: Type.String(), + headHtml: Type.String(), + provider: Type.String(), + reportDate: YahooFinanceDate, + reportTitle: Type.String(), + reportType: Type.String(), + targetPrice: Type.Optional(YahooNumber), + targetPriceStatus: Type.Optional( + Type.Union([ + Type.Literal("Increased"), + Type.Literal("Maintained"), + Type.Literal("Decreased"), + Type.Literal("-"), + ]) + ), + investmentRating: Type.Optional( + Type.Union([ + Type.Literal("Bullish"), + Type.Literal("Neutral"), + Type.Literal("Bearish"), + ]) + ), + tickers: Type.Optional(Type.Array(Type.String())), + }, + { title: "InsightsReport", additionalProperties: Type.Any() } +); +const InsightsSigDev = Type.Object( + { + headline: Type.String(), + date: YahooFinanceDate, + }, + { title: "InsightsSigDev", additionalProperties: Type.Any() } +); +const InsightsUpsell = Type.Object( + { + msBullishSummary: Type.Optional(Type.Array(Type.String())), + msBearishSummary: Type.Optional(Type.Array(Type.String())), + msBullishBearishSummariesPublishDate: Type.Optional(YahooDateInMs), + companyName: Type.Optional(Type.String()), + upsellReportType: Type.Optional(Type.String()), + }, + { title: "InsightsUpsell", additionalProperties: Type.Any() } +); +const InsightsResearchReport = Type.Object( + { + reportId: Type.String(), + provider: Type.String(), + title: Type.String(), + reportDate: YahooFinanceDate, + summary: Type.String(), + investmentRating: Type.Optional( + Type.Union([ + Type.Literal("Bullish"), + Type.Literal("Neutral"), + Type.Literal("Bearish"), + ]) + ), + }, + { title: "InsightsResearchReport" } +); +const InsightsSecReport = Type.Object( + { + id: Type.String(), + type: Type.String(), + title: Type.String(), + description: Type.String(), + filingDate: YahooDateInMs, + snapshotUrl: Type.String(), + formType: Type.String(), + }, + { + title: "InsightsSecReport", + additionalProperties: Type.Any(), + } +); -export interface InsightsEvent { - [key: string]: any; - eventType: string; - pricePeriod: string; - tradingHorizon: string; - tradeType: string; - imageUrl: string; - startDate: Date; - endDate: Date; -} +const InsightsResultSchema = Type.Object( + { + symbol: Type.String(), + instrumentInfo: Type.Optional(InsightsInstrumentInfo), + companySnapshot: Type.Optional(InsightsCompanySnapshot), + recommendation: Type.Optional( + Type.Object({ + targetPrice: Type.Optional(YahooNumber), + provider: Type.String(), + rating: Type.Union([ + Type.Literal("BUY"), + Type.Literal("SELL"), + Type.Literal("HOLD"), + ]), + }) + ), + events: Type.Optional(Type.Array(InsightsEventSchema)), + reports: Type.Optional(Type.Array(InsightsReport)), + sigDevs: Type.Optional(Type.Array(InsightsSigDev)), + upsell: Type.Optional(InsightsUpsell), + upsellSearchDD: Type.Optional( + Type.Object({ + researchReports: InsightsResearchReport, + }) + ), + secReports: Type.Optional(Type.Array(InsightsSecReport)), + }, + { + additionalProperties: Type.Any(), + title: "InsightsResult", + } +); + +type InsightsResult = Static; +type InsightsOutlook = Static; export interface InsightsInstrumentInfo { [key: string]: any; @@ -127,36 +289,16 @@ export interface InsightsCompanySnapshot { }; } -export type InsightsDirection = "Bearish" | "Bullish" | "Neutral"; +const InsightsOptionsSchema = Type.Object( + { + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + reportsCount: Type.Optional(YahooNumber), + }, + { title: "InsightsOptions" } +); -export interface InsightsOutlook { - [key: string]: any; - stateDescription: string; - direction: InsightsDirection; - score: number; - scoreDescription: string; - sectorDirection?: InsightsDirection; - sectorScore?: number; - sectorScoreDescription?: string; - indexDirection: InsightsDirection; - indexScore: number; - indexScoreDescription: string; -} - -export interface InsightsUpsell { - [key: string]: any; - msBullishSummary?: Array; - msBearishSummary?: Array; - msBullishBearishSummariesPublishDate?: DateInMs; - companyName?: string; // Missing in e.g. APS.AX - upsellReportType?: string; -} - -export interface InsightsOptions { - lang?: string; - region?: string; - reportsCount?: number; -} +type InsightsOptions = Static; const queryOptionsDefaults = { lang: "en-US", @@ -185,18 +327,18 @@ export default function trendingSymbols( queryOptionsOverrides?: InsightsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "insights", query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/ws/insights/v2/finance/insights", - schemaKey: "#/definitions/InsightsOptions", + schema: InsightsOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, runtime: { symbol }, }, result: { - schemaKey: "#/definitions/InsightsResult", + schema: InsightsResultSchema, transformWith(result: any) { if (!result.finance) throw new Error("Unexpected result: " + JSON.stringify(result)); From 99b089a1bfcd47460b84e9965177e348e76be9f6 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 13:00:49 +0800 Subject: [PATCH 08/13] Add Historical module --- src/modules/historical.spec.ts | 4 +- src/modules/historical.ts | 160 ++++++++++++++++++++++----------- 2 files changed, 112 insertions(+), 52 deletions(-) diff --git a/src/modules/historical.spec.ts b/src/modules/historical.spec.ts index 8d6d8571..2a4a5cf4 100644 --- a/src/modules/historical.spec.ts +++ b/src/modules/historical.spec.ts @@ -67,11 +67,11 @@ describe("historical", () => { }); describe("transformWith", () => { - const yf = { _moduleExec: jest.fn(), historical }; + const yf = { _moduleExecTypebox: jest.fn(), historical }; // @ts-ignore: TODO yf.historical("symbol", { period1: "required-but-not-used" }); // @ts-ignore: TODO - const { transformWith } = yf._moduleExec.mock.calls[0][0].query; + const { transformWith } = yf._moduleExecTypebox.mock.calls[0][0].query; it("uses today's date as default for period2", () => { const now = new Date(); diff --git a/src/modules/historical.ts b/src/modules/historical.ts index 16236f61..93875c9e 100644 --- a/src/modules/historical.ts +++ b/src/modules/historical.ts @@ -1,58 +1,115 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { YahooFinanceDate, YahooNumber } from "../lib/yahooFinanceTypes.js"; -export type HistoricalHistoryResult = Array; -export type HistoricalDividendsResult = Array; -export type HistoricalStockSplitsResult = Array; -export type HistoricalResult = - | HistoricalHistoryResult - | HistoricalDividendsResult - | HistoricalStockSplitsResult; - -export interface HistoricalRowHistory { - [key: string]: any; - date: Date; - open: number; - high: number; - low: number; - close: number; - adjClose?: number; - volume: number; -} +const HistoricalRowHistory = Type.Object( + { + date: YahooFinanceDate, + open: YahooNumber, + high: YahooNumber, + low: YahooNumber, + close: YahooNumber, + adjClose: Type.Optional(YahooNumber), + volume: YahooNumber, + }, + { + additionalProperties: Type.Any(), + title: "HistoricalRowHistory", + } +); -export interface HistoricalRowDividend { - date: Date; - dividends: number; -} +const HistoricalRowDividend = Type.Object( + { + date: YahooFinanceDate, + dividends: YahooNumber, + }, + { title: "HistoricalRowDividend" } +); -export interface HistoricalRowStockSplit { - date: Date; - stockSplits: string; -} +const HistoricalRowStockSplit = Type.Object( + { + date: YahooFinanceDate, + stockSplits: Type.String(), + }, + { title: "HistoricalRowStockSplit" } +); -export interface HistoricalOptions { - period1: Date | string | number; - period2?: Date | string | number; - interval?: "1d" | "1wk" | "1mo"; // '1d', TODO: all | types - events?: string; // 'history', - includeAdjustedClose?: boolean; // true, -} +const HistoricalOptionsSchema = Type.Object( + { + period1: Type.Union([Type.Date(), Type.String(), Type.Number()]), + period2: Type.Optional( + Type.Union([Type.Date(), Type.String(), Type.Number()]) + ), + interval: Type.Optional( + Type.Union([Type.Literal("1d"), Type.Literal("1wk"), Type.Literal("1mo")]) + ), + events: Type.Optional(Type.String()), + includeAdjustedClose: Type.Optional(Type.Boolean()), + }, + { title: "HistoricalOptions" } +); -export interface HistoricalOptionsEventsHistory extends HistoricalOptions { - events?: "history"; -} +const HistoricalOptionsEventsHistorySchema = Type.Composite( + [ + HistoricalOptionsSchema, + Type.Object({ + events: Type.Optional(Type.Literal("history")), + }), + ], + { title: "HistoricalOptionsEventsHistory" } +); -export interface HistoricalOptionsEventsDividends extends HistoricalOptions { - events: "dividends"; -} +const HistoricalOptionsEventsDividendsSchema = Type.Composite( + [ + HistoricalOptionsSchema, + Type.Object({ + events: Type.Literal("dividends"), + }), + ], + { title: "HistoricalOptionsEventsDividends" } +); -export interface HistoricalOptionsEventsSplit extends HistoricalOptions { - events: "split"; -} +const HistoricalOptionsEventsSplitSchema = Type.Composite( + [ + HistoricalOptionsSchema, + Type.Object({ + events: Type.Literal("split"), + }), + ], + { title: "HistoricalOptionsEventsSplit" } +); + +const HistoricalHistoryResultSchema = Type.Array(HistoricalRowHistory, { + title: "HistoricalHistoryResult", +}); +const HistoricalDividendsResultSchema = Type.Array(HistoricalRowDividend, { + title: "HistoricalDividendsResult", +}); +const HistoricalStockSplitsResultSchema = Type.Array(HistoricalRowStockSplit, { + title: "HistoricalRowStockSplit", +}); + +type HistoricalOptions = Static; +type HistoricalOptionsEventsHistory = Static< + typeof HistoricalOptionsEventsHistorySchema +>; + +type HistoricalHistoryResult = Static; +type HistoricalDividendsResult = Static; +type HistoricalOptionsEventsDividends = Static< + typeof HistoricalOptionsEventsDividendsSchema +>; +type HistoricalOptionsEventsSplit = Static< + typeof HistoricalOptionsEventsSplitSchema +>; +type HistoricalStockSplitsResult = Static< + typeof HistoricalStockSplitsResultSchema +>; const queryOptionsDefaults: Omit = { interval: "1d", @@ -94,25 +151,25 @@ export default function historical( queryOptionsOverrides: HistoricalOptions, moduleOptions?: ModuleOptions ): Promise { - let schemaKey; + let schema; if ( !queryOptionsOverrides.events || queryOptionsOverrides.events === "history" ) - schemaKey = "#/definitions/HistoricalHistoryResult"; + schema = HistoricalHistoryResultSchema; else if (queryOptionsOverrides.events === "dividends") - schemaKey = "#/definitions/HistoricalDividendsResult"; + schema = HistoricalDividendsResultSchema; else if (queryOptionsOverrides.events === "split") - schemaKey = "#/definitions/HistoricalStockSplitsResult"; + schema = HistoricalStockSplitsResultSchema; else throw new Error("No such event type:" + queryOptionsOverrides.events); - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "historical", query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/v7/finance/download/" + symbol, - schemaKey: "#/definitions/HistoricalOptions", + schema: HistoricalOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, fetchType: "csv", @@ -152,7 +209,7 @@ export default function historical( }, result: { - schemaKey, + schema, transformWith(result: any) { if (result.length === 0) return result; @@ -160,7 +217,10 @@ export default function historical( const fieldCount = Object.keys(result[0]).length; // Count number of null values in object (1-level deep) - function nullFieldCount(object: Object) { + function nullFieldCount(object: unknown) { + if (object == null) { + return; + } let nullCount = 0; for (const val of Object.values(object)) if (val === null) nullCount++; From ff198598df09d5acfd883920c9c266f8e2004be9 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 13:11:58 +0800 Subject: [PATCH 09/13] Add FundamentalsTimeSeries module --- schema.json | 204 +++++++++++++------------- src/modules/fundamentalsTimeSeries.ts | 64 +++++--- 2 files changed, 146 insertions(+), 122 deletions(-) diff --git a/schema.json b/schema.json index 299f88ba..87779374 100644 --- a/schema.json +++ b/schema.json @@ -1236,23 +1236,28 @@ ], "additionalProperties": false }, - "FundamentalsTimeSeriesResults": { - "type": "array", - "items": { - "$ref": "#/definitions/FundamentalsTimeSeriesResult" - } - }, - "FundamentalsTimeSeriesResult": { + "NamedParameters": { "type": "object", "properties": { - "date": { - "yahooFinanceType": "date" + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "symbol": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/FundamentalsTimeSeriesOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" } }, "required": [ - "date" + "this", + "symbol", + "queryOptionsOverrides" ], - "additionalProperties": {} + "additionalProperties": false }, "FundamentalsTimeSeriesOptions": { "type": "object", @@ -1308,7 +1313,50 @@ ], "additionalProperties": false }, - "NamedParameters": { + "NamedParameters": { + "type": "object", + "properties": { + "queryOptions": { + "$ref": "#/definitions/FundamentalsTimeSeriesOptions", + "description": "Input query options." + } + }, + "required": [ + "queryOptions" + ], + "additionalProperties": false + }, + "NamedParameters": { + "type": "object", + "properties": { + "response": { + "description": "Query response." + } + }, + "required": [ + "response" + ], + "additionalProperties": false + }, + "FundamentalsTimeSeriesResults": { + "type": "array", + "items": { + "$ref": "#/definitions/FundamentalsTimeSeriesResult" + } + }, + "FundamentalsTimeSeriesResult": { + "type": "object", + "properties": { + "date": { + "yahooFinanceType": "date" + } + }, + "required": [ + "date" + ], + "additionalProperties": {} + }, + "NamedParameters": { "type": "object", "properties": { "this": { @@ -1318,7 +1366,7 @@ "type": "string" }, "queryOptionsOverrides": { - "$ref": "#/definitions/FundamentalsTimeSeriesOptions" + "$ref": "#/definitions/HistoricalOptions" }, "moduleOptions": { "$ref": "#/definitions/ModuleOptions" @@ -1331,28 +1379,52 @@ ], "additionalProperties": false }, - "NamedParameters": { - "type": "object", - "properties": { - "queryOptions": { - "$ref": "#/definitions/FundamentalsTimeSeriesOptions", - "description": "Input query options." - } - }, - "required": [ - "queryOptions" - ], - "additionalProperties": false - }, - "NamedParameters": { + "HistoricalOptions": { "type": "object", "properties": { - "response": { - "description": "Query response." + "period1": { + "anyOf": [ + { + "yahooFinanceType": "date" + }, + { + "type": "string" + }, + { + "yahooFinanceType": "number" + } + ] + }, + "period2": { + "anyOf": [ + { + "yahooFinanceType": "date" + }, + { + "type": "string" + }, + { + "yahooFinanceType": "number" + } + ] + }, + "interval": { + "type": "string", + "enum": [ + "1d", + "1wk", + "1mo" + ] + }, + "events": { + "type": "string" + }, + "includeAdjustedClose": { + "type": "boolean" } }, "required": [ - "response" + "period1" ], "additionalProperties": false }, @@ -1453,55 +1525,6 @@ } ] }, - "HistoricalOptions": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "interval": { - "type": "string", - "enum": [ - "1d", - "1wk", - "1mo" - ] - }, - "events": { - "type": "string" - }, - "includeAdjustedClose": { - "type": "boolean" - } - }, - "required": [ - "period1" - ], - "additionalProperties": false - }, "HistoricalOptionsEventsHistory": { "type": "object", "properties": { @@ -1654,29 +1677,6 @@ ], "additionalProperties": false }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/HistoricalOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, "InsightsInstrumentInfo": { "type": "object", "properties": { diff --git a/src/modules/fundamentalsTimeSeries.ts b/src/modules/fundamentalsTimeSeries.ts index 0afdce41..82e966b5 100644 --- a/src/modules/fundamentalsTimeSeries.ts +++ b/src/modules/fundamentalsTimeSeries.ts @@ -1,3 +1,4 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, @@ -5,33 +6,56 @@ import type { ModuleThis, } from "../lib/moduleCommon.js"; import Timeseries_Keys from "../lib/timeseries.json"; +import { YahooFinanceDate, YahooNumber } from "../lib/yahooFinanceTypes.js"; -export const FundamentalsTimeSeries_Types = ["quarterly", "annual", "trailing"]; +const FundamentalsTimeSeries_Types = ["quarterly", "annual", "trailing"]; -export const FundamentalsTimeSeries_Modules = [ +const FundamentalsTimeSeries_Modules = [ "financials", "balance-sheet", "cash-flow", "all", ]; -export type FundamentalsTimeSeriesResults = Array; +const FundamentalsTimeSeriesResultSchema = Type.Object( + { + date: YahooFinanceDate, + }, + { + additionalProperties: Type.Unknown(), + title: "FundamentalsTimeSeriesResult", + } +); + +const FundamentalsTimeSeriesOptionsSchema = Type.Object( + { + period1: Type.Union([YahooFinanceDate, YahooNumber, Type.String()]), + period2: Type.Optional( + Type.Union([YahooFinanceDate, YahooNumber, Type.String()]) + ), + type: Type.Optional(Type.String()), + merge: Type.Optional(Type.Boolean()), // This returns a completely different format that will break the transformer + padTimeSeries: Type.Optional(Type.Boolean()), // Not exactly sure what this does, assume it pads p1 and p2??? + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + module: Type.String(), + }, + { + title: "FundamentalsTimeSeriesOptions", + } +); -export interface FundamentalsTimeSeriesResult { - [key: string]: unknown; - date: Date; -} +type FundamentalsTimeSeriesOptions = Static< + typeof FundamentalsTimeSeriesOptionsSchema +>; -export interface FundamentalsTimeSeriesOptions { - period1: Date | number | string; - period2?: Date | number | string; - type?: string; - merge?: boolean; // This returns a completely different format that will break the transformer - padTimeSeries?: boolean; // Not exactly sure what this does, assume it pads p1 and p2??? - lang?: string; - region?: string; - module: string; -} +const FundamentalsTimeSeriesResultsSchema = Type.Array( + FundamentalsTimeSeriesResultSchema +); + +type FundamentalsTimeSeriesResult = Static< + typeof FundamentalsTimeSeriesResultSchema +>; const queryOptionsDefaults: Omit< FundamentalsTimeSeriesOptions, @@ -64,21 +88,21 @@ export default function fundamentalsTimeSeries( queryOptionsOverrides: FundamentalsTimeSeriesOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "options", query: { assertSymbol: symbol, url: `https://query1.finance.yahoo.com/ws/fundamentals-timeseries/v1/finance/timeseries/${symbol}`, needsCrumb: false, - schemaKey: "#/definitions/FundamentalsTimeSeriesOptions", + schema: FundamentalsTimeSeriesOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, transformWith: processQuery, }, result: { - schemaKey: "#/definitions/FundamentalsTimeSeriesResults", + schema: FundamentalsTimeSeriesResultsSchema, transformWith(response: any) { if (!response || !response.timeseries) throw new Error(`Unexpected result: ${JSON.stringify(response)}`); From fc450b97636aa89b3d0170cc58c2932993f9be5a Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 13:21:21 +0800 Subject: [PATCH 10/13] Adds DailyGainers Module --- src/modules/dailyGainers.spec.ts | 4 +- src/modules/dailyGainers.ts | 285 ++++++++++++++++--------------- 2 files changed, 153 insertions(+), 136 deletions(-) diff --git a/src/modules/dailyGainers.spec.ts b/src/modules/dailyGainers.spec.ts index d7aee67c..7220ccd0 100644 --- a/src/modules/dailyGainers.spec.ts +++ b/src/modules/dailyGainers.spec.ts @@ -7,9 +7,7 @@ describe("dailyGainers", () => { if (process.env.FETCH_DEVEL !== "nocache") it("returns expected result", () => { const devel = "dailyGainers.json"; - return expect( - yf.dailyGainers({}, { devel: `dailyGainers.json` }) - ).resolves.toBeDefined(); + return expect(yf.dailyGainers({}, { devel })).resolves.toBeDefined(); }); it("throws on weird result", () => { diff --git a/src/modules/dailyGainers.ts b/src/modules/dailyGainers.ts index e4556a78..a98f0d88 100644 --- a/src/modules/dailyGainers.ts +++ b/src/modules/dailyGainers.ts @@ -1,136 +1,162 @@ +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { YahooNumber } from "../lib/yahooFinanceTypes.js"; -export interface DailyGainersResult { - id: string; - title: string; - description: string; - canonicalName: string; - criteriaMeta: DailyGainersCriteriaMeta; - rawCriteria: string; - start: number; - count: number; - total: number; - quotes: DailyGainersQuote[]; - useRecords: boolean; - predefinedScr: boolean; - versionId: number; - creationDate: number; - lastUpdated: number; - isPremium: boolean; - iconUrl: string; -} +const DailyGainersCriterum = Type.Object( + { + field: Type.String(), + operators: Type.Array(Type.String()), + values: Type.Array(YahooNumber), + labelsSelected: Type.Array(YahooNumber), + dependentValues: Type.Array(Type.Any()), + }, + { title: "DailyGainersCriterium" } +); -export interface DailyGainersCriteriaMeta { - size: number; - offset: number; - sortField: string; - sortType: string; - quoteType: string; - criteria: DailyGainersCriterum[]; - topOperator: string; -} +const DailyGainersQuote = Type.Object( + { + language: Type.String(), + region: Type.String(), + quoteType: Type.String(), + typeDisp: Type.String(), + quoteSourceName: Type.String(), + triggerable: Type.Boolean(), + customPriceAlertConfidence: Type.String(), + lastCloseTevEbitLtm: Type.Optional(YahooNumber), + lastClosePriceToNNWCPerShare: Type.Optional(YahooNumber), + firstTradeDateMilliseconds: YahooNumber, + priceHint: YahooNumber, + postMarketChangePercent: Type.Optional(YahooNumber), + postMarketTime: Type.Optional(YahooNumber), + postMarketPrice: Type.Optional(YahooNumber), + postMarketChange: Type.Optional(YahooNumber), + regularMarketChange: YahooNumber, + regularMarketTime: YahooNumber, + regularMarketPrice: YahooNumber, + regularMarketDayHigh: YahooNumber, + regularMarketDayRange: Type.String(), + currency: Type.String(), + regularMarketDayLow: YahooNumber, + regularMarketVolume: YahooNumber, + regularMarketPreviousClose: YahooNumber, + bid: Type.Optional(YahooNumber), + ask: Type.Optional(YahooNumber), + bidSize: Type.Optional(YahooNumber), + askSize: Type.Optional(YahooNumber), + market: Type.String(), + messageBoardId: Type.String(), + fullExchangeName: Type.String(), + longName: Type.String(), + financialCurrency: Type.Optional(Type.String()), + regularMarketOpen: YahooNumber, + averageDailyVolume3Month: YahooNumber, + averageDailyVolume10Day: YahooNumber, + fiftyTwoWeekLowChange: YahooNumber, + fiftyTwoWeekLowChangePercent: YahooNumber, + fiftyTwoWeekRange: Type.String(), + fiftyTwoWeekHighChange: YahooNumber, + fiftyTwoWeekHighChangePercent: YahooNumber, + fiftyTwoWeekChangePercent: YahooNumber, + earningsTimestamp: Type.Optional(YahooNumber), + earningsTimestampStart: Type.Optional(YahooNumber), + earningsTimestampEnd: Type.Optional(YahooNumber), + trailingAnnualDividendRate: YahooNumber, + trailingAnnualDividendYield: YahooNumber, + marketState: Type.String(), + epsTrailingTwelveMonths: Type.Optional(YahooNumber), + epsForward: Type.Optional(YahooNumber), + epsCurrentYear: Type.Optional(YahooNumber), + priceEpsCurrentYear: Type.Optional(YahooNumber), + sharesOutstanding: YahooNumber, + bookValue: Type.Optional(YahooNumber), + fiftyDayAverage: YahooNumber, + fiftyDayAverageChange: YahooNumber, + fiftyDayAverageChangePercent: YahooNumber, + twoHundredDayAverage: YahooNumber, + twoHundredDayAverageChange: YahooNumber, + twoHundredDayAverageChangePercent: YahooNumber, + marketCap: YahooNumber, + forwardPE: Type.Optional(YahooNumber), + priceToBook: Type.Optional(YahooNumber), + sourceInterval: YahooNumber, + exchangeDataDelayedBy: YahooNumber, + exchangeTimezoneName: Type.String(), + exchangeTimezoneShortName: Type.String(), + gmtOffSetMilliseconds: YahooNumber, + esgPopulated: Type.Boolean(), + tradeable: Type.Boolean(), + cryptoTradeable: Type.Boolean(), + exchange: Type.String(), + fiftyTwoWeekLow: YahooNumber, + fiftyTwoWeekHigh: YahooNumber, + shortName: Type.String(), + averageAnalystRating: Type.Optional(Type.String()), + regularMarketChangePercent: YahooNumber, + symbol: Type.String(), + dividendDate: Type.Optional(YahooNumber), + displayName: Type.Optional(Type.String()), + trailingPE: Type.Optional(YahooNumber), + prevName: Type.Optional(Type.String()), + nameChangeDate: Type.Optional(YahooNumber), + ipoExpectedDate: Type.Optional(YahooNumber), + dividendYield: Type.Optional(YahooNumber), + dividendRate: Type.Optional(YahooNumber), + }, + { title: "DailyGainersQuote" } +); -export interface DailyGainersCriterum { - field: string; - operators: string[]; - values: number[]; - labelsSelected: number[]; - dependentValues: any[]; -} +const DailyGainersOptionsSchema = Type.Object( + { + lang: Type.Optional(Type.String()), + region: Type.Optional(Type.String()), + count: Type.Optional(YahooNumber), + }, + { title: "DailyGainersOptions" } +); -export interface DailyGainersQuote { - language: string; - region: string; - quoteType: string; - typeDisp: string; - quoteSourceName: string; - triggerable: boolean; - customPriceAlertConfidence: string; - lastCloseTevEbitLtm?: number; - lastClosePriceToNNWCPerShare?: number; - firstTradeDateMilliseconds: number; - priceHint: number; - postMarketChangePercent?: number; - postMarketTime?: number; - postMarketPrice?: number; - postMarketChange?: number; - regularMarketChange: number; - regularMarketTime: number; - regularMarketPrice: number; - regularMarketDayHigh: number; - regularMarketDayRange: string; - currency: string; - regularMarketDayLow: number; - regularMarketVolume: number; - regularMarketPreviousClose: number; - bid?: number; - ask?: number; - bidSize?: number; - askSize?: number; - market: string; - messageBoardId: string; - fullExchangeName: string; - longName: string; - financialCurrency?: string; - regularMarketOpen: number; - averageDailyVolume3Month: number; - averageDailyVolume10Day: number; - fiftyTwoWeekLowChange: number; - fiftyTwoWeekLowChangePercent: number; - fiftyTwoWeekRange: string; - fiftyTwoWeekHighChange: number; - fiftyTwoWeekHighChangePercent: number; - fiftyTwoWeekChangePercent: number; - earningsTimestamp?: number; - earningsTimestampStart?: number; - earningsTimestampEnd?: number; - trailingAnnualDividendRate: number; - trailingAnnualDividendYield: number; - marketState: string; - epsTrailingTwelveMonths?: number; - epsForward?: number; - epsCurrentYear?: number; - priceEpsCurrentYear?: number; - sharesOutstanding: number; - bookValue?: number; - fiftyDayAverage: number; - fiftyDayAverageChange: number; - fiftyDayAverageChangePercent: number; - twoHundredDayAverage: number; - twoHundredDayAverageChange: number; - twoHundredDayAverageChangePercent: number; - marketCap: number; - forwardPE?: number; - priceToBook?: number; - sourceInterval: number; - exchangeDataDelayedBy: number; - exchangeTimezoneName: string; - exchangeTimezoneShortName: string; - gmtOffSetMilliseconds: number; - esgPopulated: boolean; - tradeable: boolean; - cryptoTradeable: boolean; - exchange: string; - fiftyTwoWeekLow: number; - fiftyTwoWeekHigh: number; - shortName: string; - averageAnalystRating?: string; - regularMarketChangePercent: number; - symbol: string; - dividendDate?: number; - displayName?: string; - trailingPE?: number; - prevName?: string; - nameChangeDate?: number; - ipoExpectedDate?: number; - dividendYield?: number; - dividendRate?: number; -} +const DailyGainersCriteriaMeta = Type.Object( + { + size: YahooNumber, + offset: YahooNumber, + sortField: Type.String(), + sortType: Type.String(), + quoteType: Type.String(), + criteria: Type.Array(DailyGainersCriterum), + topOperator: Type.String(), + }, + { title: "DailyGainersCriteriaMeta" } +); + +const DailyGainersResultSchema = Type.Object( + { + id: Type.String(), + title: Type.String(), + description: Type.String(), + canonicalName: Type.String(), + criteriaMeta: DailyGainersCriteriaMeta, + rawCriteria: Type.String(), + start: YahooNumber, + count: YahooNumber, + total: YahooNumber, + quotes: Type.Array(DailyGainersQuote), + useRecords: Type.Boolean(), + predefinedScr: Type.Boolean(), + versionId: YahooNumber, + creationDate: YahooNumber, + lastUpdated: YahooNumber, + isPremium: Type.Boolean(), + iconUrl: Type.String(), + }, + { title: "DailyGainersResult" } +); + +type DailyGainersResult = Static; +type DailyGainersOptions = Static; const queryOptionsDefaults = { lang: "en-US", @@ -139,12 +165,6 @@ const queryOptionsDefaults = { count: 5, }; -export interface DailyGainersOptions { - lang?: string; - region?: string; - count?: number; -} - export default function dailyGainers( this: ModuleThis, queryOptionsOverrides?: DailyGainersOptions, @@ -162,19 +182,18 @@ export default function dailyGainers( queryOptionsOverrides?: DailyGainersOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExec({ + return this._moduleExecTypebox({ moduleName: "dailyGainers", query: { url: "https://${YF_QUERY_HOST}/v1/finance/screener/predefined/saved", - schemaKey: "#/definitions/DailyGainersOptions", + schema: DailyGainersOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, needsCrumb: true, }, result: { - schemaKey: "#/definitions/DailyGainersResult", + schema: DailyGainersResultSchema, transformWith(result: any) { - // console.log(result); if (!result.finance) throw new Error("Unexpected result: " + JSON.stringify(result)); return result.finance.result[0]; From 8805004a96ffbbe6fed60ddf84731fcdd0188985 Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Sun, 21 Jul 2024 21:17:56 +1000 Subject: [PATCH 11/13] Adds Chart module --- schema.json | 272 +++++++++++------------ src/lib/moduleExec.spec.ts | 2 +- src/modules/chart.spec.ts | 4 +- src/modules/chart.ts | 426 +++++++++++++++++++++++-------------- 4 files changed, 409 insertions(+), 295 deletions(-) diff --git a/schema.json b/schema.json index 87779374..f0248010 100644 --- a/schema.json +++ b/schema.json @@ -76,6 +76,132 @@ "type": "object", "additionalProperties": false }, + "NamedParameters": { + "type": "object", + "properties": { + "this": { + "$ref": "#/definitions/ModuleThis" + }, + "symbol": { + "type": "string" + }, + "queryOptionsOverrides": { + "$ref": "#/definitions/ChartOptions" + }, + "moduleOptions": { + "$ref": "#/definitions/ModuleOptions" + } + }, + "required": [ + "this", + "symbol", + "queryOptionsOverrides" + ], + "additionalProperties": false + }, + "ModuleThis": { + "type": "object", + "properties": { + "_moduleExec": {}, + "_moduleExecTypebox": { + "$comment": "(\n this: { [key: string]: any },\n opts: ModuleExecOptions) -> undefined" + } + }, + "required": [ + "_moduleExec", + "_moduleExecTypebox" + ] + }, + "ChartOptions": { + "type": "object", + "properties": { + "period1": { + "anyOf": [ + { + "yahooFinanceType": "date" + }, + { + "type": "string" + }, + { + "yahooFinanceType": "number" + } + ] + }, + "period2": { + "anyOf": [ + { + "yahooFinanceType": "date" + }, + { + "type": "string" + }, + { + "yahooFinanceType": "number" + } + ] + }, + "useYfid": { + "type": "boolean" + }, + "interval": { + "type": "string", + "enum": [ + "1m", + "2m", + "5m", + "15m", + "30m", + "60m", + "90m", + "1h", + "1d", + "5d", + "1wk", + "1mo", + "3mo" + ] + }, + "includePrePost": { + "type": "boolean" + }, + "events": { + "type": "string" + }, + "lang": { + "type": "string" + }, + "return": { + "type": "string", + "enum": [ + "array", + "object" + ] + } + }, + "required": [ + "period1" + ], + "additionalProperties": false + }, + "ModuleOptions": { + "type": "object", + "properties": { + "validateResult": { + "type": "boolean" + }, + "devel": { + "type": [ + "boolean", + "string" + ] + }, + "fetchOptions": { + "type": "object" + } + }, + "additionalProperties": false + }, "ChartResultObject": { "type": "object", "properties": { @@ -460,78 +586,6 @@ "volume" ] }, - "ChartOptions": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "useYfid": { - "type": "boolean" - }, - "interval": { - "type": "string", - "enum": [ - "1m", - "2m", - "5m", - "15m", - "30m", - "60m", - "90m", - "1h", - "1d", - "5d", - "1wk", - "1mo", - "3mo" - ] - }, - "includePrePost": { - "type": "boolean" - }, - "events": { - "type": "string" - }, - "lang": { - "type": "string" - }, - "return": { - "type": "string", - "enum": [ - "array", - "object" - ] - } - }, - "required": [ - "period1" - ], - "additionalProperties": false - }, "ChartOptionsWithReturnArray": { "type": "object", "properties": { @@ -671,56 +725,35 @@ ], "additionalProperties": false }, - "NamedParameters": { + "NamedParameters": { "type": "object", "properties": { "this": { "$ref": "#/definitions/ModuleThis" }, - "symbol": { - "type": "string" - }, "queryOptionsOverrides": { - "$ref": "#/definitions/ChartOptions" + "$ref": "#/definitions/DailyGainersOptions" }, "moduleOptions": { "$ref": "#/definitions/ModuleOptions" } }, "required": [ - "this", - "symbol", - "queryOptionsOverrides" + "this" ], "additionalProperties": false }, - "ModuleThis": { - "type": "object", - "properties": { - "_moduleExec": {}, - "_moduleExecTypebox": { - "$comment": "(\n this: { [key: string]: any },\n opts: ModuleExecOptions) -> undefined" - } - }, - "required": [ - "_moduleExec", - "_moduleExecTypebox" - ] - }, - "ModuleOptions": { + "DailyGainersOptions": { "type": "object", "properties": { - "validateResult": { - "type": "boolean" + "lang": { + "type": "string" }, - "devel": { - "type": [ - "boolean", - "string" - ] + "region": { + "type": "string" }, - "fetchOptions": { - "type": "object" + "count": { + "yahooFinanceType": "number" } }, "additionalProperties": false @@ -1203,39 +1236,6 @@ ], "additionalProperties": false }, - "DailyGainersOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/DailyGainersOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this" - ], - "additionalProperties": false - }, "NamedParameters": { "type": "object", "properties": { diff --git a/src/lib/moduleExec.spec.ts b/src/lib/moduleExec.spec.ts index 655d3884..3e3a29f8 100644 --- a/src/lib/moduleExec.spec.ts +++ b/src/lib/moduleExec.spec.ts @@ -9,7 +9,7 @@ import { TransformDecodeCheckError } from "@sinclair/typebox/value"; const yf = testYf({ search, chart }); yf._opts.validation.logOptionsErrors = false; -describe("moduleExec", () => { +describe("moduleExecTypebox", () => { describe("assertSymbol", () => { const periodOpts = { period1: new Date("2022-02-22"), diff --git a/src/modules/chart.spec.ts b/src/modules/chart.spec.ts index a18b95ab..924f1e25 100644 --- a/src/modules/chart.spec.ts +++ b/src/modules/chart.spec.ts @@ -137,7 +137,7 @@ describe("chart", () => { * As long as it doesn't throw an error, that could confuse jest :) */ const yf = { - _moduleExec: jest.fn(async () => ({ + _moduleExecTypebox: jest.fn(async () => ({ meta: {}, timestamp: [], })), @@ -147,7 +147,7 @@ describe("chart", () => { // @ts-ignore: TODO yf.chart("symbol", { period1: "required-but-not-used" }); // @ts-ignore: TODO - const { transformWith } = yf._moduleExec.mock.calls[0][0].query; + const { transformWith } = yf._moduleExecTypebox.mock.calls[0][0].query; it("uses today's date as default for period2", () => { const now = new Date(); diff --git a/src/modules/chart.ts b/src/modules/chart.ts index 712f63fa..e93b529b 100644 --- a/src/modules/chart.ts +++ b/src/modules/chart.ts @@ -1,157 +1,276 @@ // Co-authored by @gadicc, @PythonCreator27 and @huned. +import { Static, Type } from "@sinclair/typebox"; import type { ModuleOptions, ModuleOptionsWithValidateTrue, ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { YahooFinanceDate, YahooNumber } from "../lib/yahooFinanceTypes.js"; + +const ChartMetaTradingPeriod = Type.Object( + { + timezone: Type.String(), // "EST", + start: YahooFinanceDate, // new Date(1637355600 * 1000), + end: YahooFinanceDate, // new Date(1637370000 * 10000), + gmtoffset: YahooNumber, // -18000 + }, + { + additionalProperties: Type.Any(), + title: "ChartMetaTradingPeriod", + } +); + +const ChartMetaTradingPeriods = Type.Object( + { + pre: Type.Optional(Type.Array(Type.Array(ChartMetaTradingPeriod))), + post: Type.Optional(Type.Array(Type.Array(ChartMetaTradingPeriod))), + regular: Type.Optional(Type.Array(Type.Array(ChartMetaTradingPeriod))), + }, + { + additionalProperties: Type.Any(), + title: "ChartMetaTradingPeriods", + } +); + +const ChartResultArrayQuote = Type.Object( + { + date: YahooFinanceDate, + high: Type.Union([YahooNumber, Type.Null()]), + low: Type.Union([YahooNumber, Type.Null()]), + open: Type.Union([YahooNumber, Type.Null()]), + close: Type.Union([YahooNumber, Type.Null()]), + volume: Type.Union([YahooNumber, Type.Null()]), + adjclose: Type.Optional(Type.Union([YahooNumber, Type.Null()])), + }, + { + additionalProperties: Type.Any(), + title: "ChartResultArrayQuote", + } +); + +const ChartEventDividend = Type.Object( + { + amount: YahooNumber, + date: YahooFinanceDate, + }, + { + additionalProperties: Type.Any(), + title: "ChartEventDividend", + } +); -export interface ChartResultObject { - [key: string]: any; - meta: ChartMeta; - timestamp?: Array; - events?: ChartEventsObject; - indicators: ChartIndicatorsObject; -} - -export interface ChartResultArray { - meta: ChartMeta; - events?: ChartEventsArray; - quotes: Array; -} - -export interface ChartResultArrayQuote { - [key: string]: any; - date: Date; - high: number | null; - low: number | null; - open: number | null; - close: number | null; - volume: number | null; - adjclose?: number | null; -} - -export interface ChartMeta { - [key: string]: any; - currency: string; // "USD" - symbol: string; // "AAPL", - exchangeName: string; // "NMS", - instrumentType: string; // "EQUITY", - firstTradeDate: Date | null; // new Date(345479400 * 1000); null in e.g. "APS.AX" - regularMarketTime: Date; // new Date(1637355602 * 1000), - gmtoffset: number; // -18000, - timezone: string; /// "EST", - exchangeTimezoneName: string; // "America/New_York", - regularMarketPrice: number; // 160.55, - chartPreviousClose?: number; // 79.75; missing in e.g. "APS.AX" - previousClose?: number; // 1137.06 - scale?: number; // 3, - priceHint: number; // 2, - currentTradingPeriod: { - [key: string]: any; - pre: ChartMetaTradingPeriod; - regular: ChartMetaTradingPeriod; - post: ChartMetaTradingPeriod; - }; - tradingPeriods?: ChartMetaTradingPeriods; - dataGranularity: string; // "1d", - range: string; // "", - validRanges: Array; // ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"] -} - -export interface ChartMetaTradingPeriod { - [key: string]: any; - timezone: string; // "EST", - start: Date; // new Date(1637355600 * 1000), - end: Date; // new Date(1637370000 * 10000), - gmtoffset: number; // -18000 -} - -export interface ChartMetaTradingPeriods { - [key: string]: any; - pre?: Array>; - post?: Array>; - regular?: Array>; -} - -export interface ChartEventsObject { - [key: string]: any; - dividends?: ChartEventDividends; - splits?: ChartEventSplits; -} - -export interface ChartEventsArray { - [key: string]: any; - dividends?: Array; - splits?: Array; -} - -export interface ChartEventDividends { - [key: string]: ChartEventDividend; -} - -export interface ChartEventDividend { - [key: string]: any; - amount: number; - date: Date; -} - -export interface ChartEventSplits { - [key: string]: ChartEventSplit; -} - -export interface ChartEventSplit { - [key: string]: any; - date: Date; // new Date(1598880600 * 1000) - numerator: number; // 4 - denominator: number; // 1 - splitRatio: string; // "4:1" -} - -export interface ChartIndicatorsObject { - [key: string]: any; - quote: Array; - adjclose?: Array; -} - -export interface ChartIndicatorQuote { - [key: string]: any; - high: Array; - low: Array; - open: Array; - close: Array; - volume: Array; -} - -export interface ChartIndicatorAdjclose { - [key: string]: any; - adjclose?: Array; // Missing in e.g. "APS.AX" -} - -export interface ChartOptions { - period1: Date | string | number; - period2?: Date | string | number; - useYfid?: boolean; // true - interval?: - | "1m" - | "2m" - | "5m" - | "15m" - | "30m" - | "60m" - | "90m" - | "1h" - | "1d" - | "5d" - | "1wk" - | "1mo" - | "3mo"; - includePrePost?: boolean; // true - events?: string; // 'history', - lang?: string; // "en-US" - return?: "array" | "object"; -} +const ChartEventDividends = Type.Object( + {}, + { + additionalProperties: ChartEventDividend, + title: "ChartEventDividends", + } +); + +const ChartEventSplit = Type.Object( + { + date: YahooFinanceDate, // new Date(1598880600 * 1000) + numerator: YahooNumber, // 4 + denominator: YahooNumber, // 1 + splitRatio: Type.String(), // "4:1" + }, + { + additionalProperties: Type.Any(), + } +); +const ChartEventsArray = Type.Object( + { + dividends: Type.Optional(Type.Array(ChartEventDividend)), + splits: Type.Optional(Type.Array(ChartEventSplit)), + }, + { + additionalProperties: Type.Any(), + title: "ChartEventsArray", + } +); + +const ChartMeta = Type.Object( + { + currency: Type.String(), // "USD" + symbol: Type.String(), // "AAPL", + exchangeName: Type.String(), // "NMS", + instrumentType: Type.String(), // "EQUITY", + firstTradeDate: Type.Union([YahooFinanceDate, Type.Null()]), // new Date(345479400 * 1000); null in e.g. "APS.AX" + regularMarketTime: YahooFinanceDate, // new Date(1637355602 * 1000), + gmtoffset: YahooNumber, // -18000, + timezone: Type.String(), /// "EST", + exchangeTimezoneName: Type.String(), // "America/New_York", + regularMarketPrice: YahooNumber, // 160.55, + chartPreviousClose: Type.Optional(YahooNumber), // 79.75; missing in e.g. "APS.AX" + previousClose: Type.Optional(YahooNumber), // 1137.06 + scale: Type.Optional(YahooNumber), // 3, + priceHint: YahooNumber, // 2, + currentTradingPeriod: Type.Object( + { + pre: ChartMetaTradingPeriod, + regular: ChartMetaTradingPeriod, + post: ChartMetaTradingPeriod, + }, + { + additionalProperties: Type.Any(), + } + ), + tradingPeriods: Type.Optional(ChartMetaTradingPeriods), + dataGranularity: Type.String(), // "1d", + range: Type.String(), // "" + validRanges: Type.Array(Type.String()), // ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"] + }, + { + additionalProperties: Type.Any(), + title: "ChartMeta", + } +); + +const ChartResultArraySchema = Type.Object( + { + meta: ChartMeta, + events: Type.Optional(ChartEventsArray), + quotes: Type.Array(ChartResultArrayQuote), + }, + { title: "ChartResultArray" } +); + +const ChartEventSplits = Type.Object( + {}, + { + additionalProperties: ChartEventSplit, + title: "ChartEventSplits", + } +); + +const ChartIndicatorQuote = Type.Object( + { + high: Type.Array(Type.Union([YahooNumber, Type.Null()])), + low: Type.Array(Type.Union([YahooNumber, Type.Null()])), + open: Type.Array(Type.Union([YahooNumber, Type.Null()])), + close: Type.Array(Type.Union([YahooNumber, Type.Null()])), + volume: Type.Array(Type.Union([YahooNumber, Type.Null()])), + }, + { + additionalProperties: Type.Any(), + title: "ChartIndicatorQuote", + } +); + +const ChartIndicatorAdjclose = Type.Object( + { + adjclose: Type.Optional(Type.Array(Type.Union([YahooNumber, Type.Null()]))), // Missing in e.g. "APS.AX" + }, + { + additionalProperties: Type.Any(), + title: "ChartIndicatorAdjClose", + } +); + +const ChartEventsObject = Type.Object( + { + dividends: Type.Optional(ChartEventDividends), + splits: Type.Optional(ChartEventSplits), + }, + { + additionalProperties: Type.Any(), + } +); + +const ChartIndicatorsObject = Type.Object( + { + quote: Type.Array(ChartIndicatorQuote), + adjclose: Type.Optional(Type.Array(ChartIndicatorAdjclose)), + }, + { + additionalProperties: Type.Any(), + title: "ChartIndicatorObject", + } +); +const ChartResultObjectSchema = Type.Object( + { + meta: ChartMeta, + timestamp: Type.Optional(Type.Array(YahooNumber)), + events: Type.Optional(ChartEventsObject), + indicators: ChartIndicatorsObject, + }, + { + additionalProperties: Type.Any(), + title: "ChartResultObject", + } +); + +const ChartOptionsSchema = Type.Object( + { + period1: Type.Union([Type.Date(), Type.String(), YahooNumber]), + period2: Type.Optional( + Type.Union([Type.Date(), Type.String(), YahooNumber]) + ), + useYfid: Type.Optional(Type.Boolean()), // true + interval: Type.Optional( + Type.Union([ + Type.Literal("1m"), + Type.Literal("2m"), + Type.Literal("5m"), + Type.Literal("15m"), + Type.Literal("30m"), + Type.Literal("60m"), + Type.Literal("90m"), + Type.Literal("1h"), + Type.Literal("1d"), + Type.Literal("5d"), + Type.Literal("1wk"), + Type.Literal("1mo"), + Type.Literal("3mo"), + ]) + ), + includePrePost: Type.Optional(Type.Boolean()), // true + events: Type.Optional(Type.String()), // 'history', + lang: Type.Optional(Type.String()), // "en-US" + return: Type.Optional( + Type.Union([Type.Literal("array"), Type.Literal("object")]) + ), + }, + { + title: "ChartOptions", + } +); + +const ChartOptionsWithReturnArraySchema = Type.Composite( + [ + ChartOptionsSchema, + Type.Object({ + return: Type.Optional(Type.Literal("array")), + }), + ], + { + title: "ChartOptionsWithReturnArray", + } +); + +const ChartOptionsWithReturnObjectSchema = Type.Composite( + [ + ChartOptionsSchema, + Type.Object({ + return: Type.Literal("object"), + }), + ], + { + title: "ChartOptionsWithReturnObject", + } +); + +type ChartOptions = Static; +type ChartOptionsWithReturnObject = Static< + typeof ChartOptionsWithReturnObjectSchema +>; +type ChartResultObject = Static; +type ChartOptionsWithReturnArray = Static< + typeof ChartOptionsWithReturnArraySchema +>; +type ChartResultArray = Static; const queryOptionsDefaults: Omit = { useYfid: true, @@ -161,14 +280,6 @@ const queryOptionsDefaults: Omit = { lang: "en-US", return: "array", }; - -export interface ChartOptionsWithReturnArray extends ChartOptions { - return?: "array"; -} -export interface ChartOptionsWithReturnObject extends ChartOptions { - return: "object"; -} - /* --- array input, typed output, honor "return" param --- */ // TODO: make this a deprecration passthrough @@ -203,13 +314,13 @@ export default async function chart( ): Promise { const returnAs = queryOptionsOverrides?.return || "array"; - const result = (await this._moduleExec({ + const result = (await this._moduleExecTypebox({ moduleName: "chart", query: { assertSymbol: symbol, url: "https://${YF_QUERY_HOST}/v8/finance/chart/" + symbol, - schemaKey: "#/definitions/ChartOptions", + schema: ChartOptionsSchema, defaults: queryOptionsDefaults, overrides: queryOptionsOverrides, transformWith(queryOptions: ChartOptions) { @@ -241,7 +352,7 @@ export default async function chart( }, result: { - schemaKey: "#/definitions/ChartResultObject", + schema: ChartResultObjectSchema, transformWith(result: any) { if (!result.chart) throw new Error("Unexpected result: " + JSON.stringify(result)); @@ -315,6 +426,7 @@ export default async function chart( if (timestamp) for (let i = 0; i < timestamp.length; i++) { result2.quotes[i] = { + // @ts-expect-error (eatkinson): clean this up with type in followup date: new Date(timestamp[i] * 1000), high: result.indicators.quote[0].high[i], volume: result.indicators.quote[0].volume[i], @@ -329,7 +441,9 @@ export default async function chart( result2.events = {}; for (const event of ["dividends", "splits"]) { + // @ts-expect-error (eatkinson): Fix up type in follow up if (result.events[event]) + // @ts-expect-error (eatkinson): Fix up type in follow up result2.events[event] = Object.values(result.events[event]); } } From 873a0a550994c76b6cdc85ed1ceb1196a58cf0ad Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Mon, 22 Jul 2024 22:02:58 +1000 Subject: [PATCH 12/13] Add quote typebox module, migrate quoteCombine and move over options type --- src/modules/options.ts | 122 +--------- src/modules/quote.ts | 462 ++++++++++++++++++++++---------------- src/other/quoteCombine.ts | 26 +-- 3 files changed, 282 insertions(+), 328 deletions(-) diff --git a/src/modules/options.ts b/src/modules/options.ts index 01f2b1cc..6f7bd111 100644 --- a/src/modules/options.ts +++ b/src/modules/options.ts @@ -6,128 +6,10 @@ import type { } from "../lib/moduleCommon.js"; import { Type, Static } from "@sinclair/typebox"; -import { - YahooDateInMs, - YahooFinanceDate, - YahooNumber, - YahooTwoNumberRange, -} from "../lib/yahooFinanceTypes.js"; - +import { YahooFinanceDate, YahooNumber } from "../lib/yahooFinanceTypes.js"; +import { QuoteBase } from "./quote.js"; import { Value } from "@sinclair/typebox/value"; -const QuoteBase = Type.Object( - { - language: Type.String(), // "en-US", - region: Type.String(), // "US", - quoteType: Type.String(), // "EQUITY" | "ETF" | "MUTUALFUND"; - typeDisp: Type.Optional(Type.String()), // "Equity", not always present. - quoteSourceName: Type.Optional(Type.String()), // "Delayed Quote", - triggerable: Type.Boolean(), // true, - currency: Type.Optional(Type.String()), // "USD", - // Seems to appear / disappear based not on symbol but network load (#445) - customPriceAlertConfidence: Type.Optional(Type.String()), // "HIGH" | "LOW"; TODO: anything else? - marketState: Type.Union([ - Type.Literal("REGULAR"), - Type.Literal("CLOSED"), - Type.Literal("PRE"), - Type.Literal("PREPRE"), - Type.Literal("POST"), - Type.Literal("POSTPOST"), - ]), - tradeable: Type.Boolean(), // false, - cryptoTradeable: Type.Optional(Type.Boolean()), // false - exchange: Type.String(), // "NMS", - shortName: Type.Optional(Type.String()), // "NVIDIA Corporation", - longName: Type.Optional(Type.String()), // "NVIDIA Corporation", - messageBoardId: Type.Optional(Type.String()), // "finmb_32307", - exchangeTimezoneName: Type.String(), // "America/New_York", - exchangeTimezoneShortName: Type.String(), // "EST", - gmtOffSetMilliseconds: YahooNumber, // -18000000, - market: Type.String(), // "us_market", - esgPopulated: Type.Boolean(), // false, - fiftyTwoWeekLowChange: Type.Optional(YahooNumber), // 362.96002, - fiftyTwoWeekLowChangePercent: Type.Optional(YahooNumber), // 2.0088556, - fiftyTwoWeekRange: Type.Optional(YahooTwoNumberRange), // "180.68 - 589.07" -> { low, high } - fiftyTwoWeekHighChange: Type.Optional(YahooNumber), // -45.429993, - fiftyTwoWeekHighChangePercent: Type.Optional(YahooNumber), // -0.07712155, - fiftyTwoWeekLow: Type.Optional(YahooNumber), // 180.68, - fiftyTwoWeekHigh: Type.Optional(YahooNumber), // 589.07, - fiftyTwoWeekChangePercent: Type.Optional(YahooNumber), // 22.604025 - dividendDate: Type.Optional(YahooFinanceDate), // 1609200000, - // maybe always present on EQUITY? - earningsTimestamp: Type.Optional(YahooFinanceDate), // 1614200400, - earningsTimestampStart: Type.Optional(YahooFinanceDate), // 1614200400, - earningsTimestampEnd: Type.Optional(YahooFinanceDate), // 1614200400, - trailingAnnualDividendRate: Type.Optional(YahooNumber), // 0.64, - trailingPE: Type.Optional(YahooNumber), // 88.873634, - trailingAnnualDividendYield: Type.Optional(YahooNumber), // 0.0011709387, - epsTrailingTwelveMonths: Type.Optional(YahooNumber), // 6.117, - epsForward: Type.Optional(YahooNumber), // 11.68, - epsCurrentYear: Type.Optional(YahooNumber), // 9.72, - priceEpsCurrentYear: Type.Optional(YahooNumber), // 55.930042, - sharesOutstanding: Type.Optional(YahooNumber), // 619000000, - bookValue: Type.Optional(YahooNumber), // 24.772, - fiftyDayAverage: Type.Optional(YahooNumber), // 530.8828, - fiftyDayAverageChange: Type.Optional(YahooNumber), // 12.757202, - fiftyDayAverageChangePercent: Type.Optional(YahooNumber), // 0.024030166, - twoHundredDayAverage: Type.Optional(YahooNumber), // 515.8518, - twoHundredDayAverageChange: Type.Optional(YahooNumber), // 27.788208, - twoHundredDayAverageChangePercent: Type.Optional(YahooNumber), // 0.053868588, - marketCap: Type.Optional(YahooNumber), // 336513171456, - forwardPE: Type.Optional(YahooNumber), // 46.54452, - priceToBook: Type.Optional(YahooNumber), // 21.945745, - sourceInterval: YahooNumber, // 15, - exchangeDataDelayedBy: YahooNumber, // 0, - firstTradeDateMilliseconds: Type.Optional(YahooDateInMs), // 917015400000 -> Date - priceHint: YahooNumber, // 2, - postMarketChangePercent: Type.Optional(YahooNumber), // 0.093813874, - postMarketTime: Type.Optional(YahooFinanceDate), // 1612573179 -> new Date() - postMarketPrice: Type.Optional(YahooNumber), // 544.15, - postMarketChange: Type.Optional(YahooNumber), // 0.51000977, - regularMarketChange: Type.Optional(YahooNumber), // -2.9299927, - regularMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, - regularMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() - regularMarketPrice: Type.Optional(YahooNumber), // 543.64, - regularMarketDayHigh: Type.Optional(YahooNumber), // 549.19, - regularMarketDayRange: Type.Optional(YahooTwoNumberRange), // "541.867 - 549.19" -> { low, high } - regularMarketDayLow: Type.Optional(YahooNumber), // 541.867, - regularMarketVolume: Type.Optional(YahooNumber), // 4228841, - regularMarketPreviousClose: Type.Optional(YahooNumber), // 546.57, - preMarketChange: Type.Optional(YahooNumber), // -2.9299927, - preMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, - preMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() - preMarketPrice: Type.Optional(YahooNumber), // 543.64, - bid: Type.Optional(YahooNumber), // 543.84, - ask: Type.Optional(YahooNumber), // 544.15, - bidSize: Type.Optional(YahooNumber), // 18, - askSize: Type.Optional(YahooNumber), // 8, - fullExchangeName: Type.String(), // "NasdaqGS", - financialCurrency: Type.Optional(Type.String()), // "USD", - regularMarketOpen: Type.Optional(YahooNumber), // 549.0, - averageDailyVolume3Month: Type.Optional(YahooNumber), // 7475022, - averageDailyVolume10Day: Type.Optional(YahooNumber), // 5546385, - displayName: Type.Optional(Type.String()), // "NVIDIA", - symbol: Type.String(), // "NVDA" - underlyingSymbol: Type.Optional(Type.String()), // "LD.MI" (for LDO.MI, #363) - // only on ETF? not on EQUITY? - ytdReturn: Type.Optional(YahooNumber), // 0.31 - trailingThreeMonthReturns: Type.Optional(YahooNumber), // 16.98 - trailingThreeMonthNavReturns: Type.Optional(YahooNumber), // 17.08 - ipoExpectedDate: Type.Optional(YahooFinanceDate), // "2020-08-13", - newListingDate: Type.Optional(YahooFinanceDate), // "2021-02-16", - nameChangeDate: Type.Optional(YahooFinanceDate), - prevName: Type.Optional(Type.String()), - averageAnalystRating: Type.Optional(Type.String()), - pageViewGrowthWeekly: Type.Optional(YahooNumber), // Since 2021-11-11 (#326) - openInterest: Type.Optional(YahooNumber), // SOHO (#248) - beta: Type.Optional(YahooNumber), - }, - { - additionalProperties: Type.Any(), - title: "QuoteBase", - } -); - /* * [TODO] Fields seen in a query but not in this module yet: * diff --git a/src/modules/quote.ts b/src/modules/quote.ts index 2b96bd7d..86451d71 100644 --- a/src/modules/quote.ts +++ b/src/modules/quote.ts @@ -5,108 +5,125 @@ import type { ModuleThis, } from "../lib/moduleCommon.js"; -import type { DateInMs, TwoNumberRange } from "../lib/commonTypes.js"; - -export interface QuoteBase { - [key: string]: any; - language: string; // "en-US", - region: string; // "US", - quoteType: string; // "EQUITY" | "ETF" | "MUTUALFUND"; - typeDisp?: string; // "Equity", not always present. - quoteSourceName?: string; // "Delayed Quote", - triggerable: boolean; // true, - currency?: string; // "USD", - // Seems to appear / disappear based not on symbol but network load (#445) - customPriceAlertConfidence?: string; // "HIGH" | "LOW"; TODO: anything else? - marketState: "REGULAR" | "CLOSED" | "PRE" | "PREPRE" | "POST" | "POSTPOST"; - tradeable: boolean; // false, - cryptoTradeable?: boolean; // false - exchange: string; // "NMS", - shortName?: string; // "NVIDIA Corporation", - longName?: string; // "NVIDIA Corporation", - messageBoardId?: string; // "finmb_32307", - exchangeTimezoneName: string; // "America/New_York", - exchangeTimezoneShortName: string; // "EST", - gmtOffSetMilliseconds: number; // -18000000, - market: string; // "us_market", - esgPopulated: boolean; // false, - fiftyTwoWeekLowChange?: number; // 362.96002, - fiftyTwoWeekLowChangePercent?: number; // 2.0088556, - fiftyTwoWeekRange?: TwoNumberRange; // "180.68 - 589.07" -> { low, high } - fiftyTwoWeekHighChange?: number; // -45.429993, - fiftyTwoWeekHighChangePercent?: number; // -0.07712155, - fiftyTwoWeekLow?: number; // 180.68, - fiftyTwoWeekHigh?: number; // 589.07, - fiftyTwoWeekChangePercent?: number; // 22.604025 - dividendDate?: Date; // 1609200000, - // maybe always present on EQUITY? - earningsTimestamp?: Date; // 1614200400, - earningsTimestampStart?: Date; // 1614200400, - earningsTimestampEnd?: Date; // 1614200400, - trailingAnnualDividendRate?: number; // 0.64, - trailingPE?: number; // 88.873634, - trailingAnnualDividendYield?: number; // 0.0011709387, - epsTrailingTwelveMonths?: number; // 6.117, - epsForward?: number; // 11.68, - epsCurrentYear?: number; // 9.72, - priceEpsCurrentYear?: number; // 55.930042, - sharesOutstanding?: number; // 619000000, - bookValue?: number; // 24.772, - fiftyDayAverage?: number; // 530.8828, - fiftyDayAverageChange?: number; // 12.757202, - fiftyDayAverageChangePercent?: number; // 0.024030166, - twoHundredDayAverage?: number; // 515.8518, - twoHundredDayAverageChange?: number; // 27.788208, - twoHundredDayAverageChangePercent?: number; // 0.053868588, - marketCap?: number; // 336513171456, - forwardPE?: number; // 46.54452, - priceToBook?: number; // 21.945745, - sourceInterval: number; // 15, - exchangeDataDelayedBy: number; // 0, - firstTradeDateMilliseconds?: DateInMs; // 917015400000 -> Date - priceHint: number; // 2, - postMarketChangePercent?: number; // 0.093813874, - postMarketTime?: Date; // 1612573179 -> new Date() - postMarketPrice?: number; // 544.15, - postMarketChange?: number; // 0.51000977, - regularMarketChange?: number; // -2.9299927, - regularMarketChangePercent?: number; // -0.53606904, - regularMarketTime?: Date; // 1612558802 -> new Date() - regularMarketPrice?: number; // 543.64, - regularMarketDayHigh?: number; // 549.19, - regularMarketDayRange?: TwoNumberRange; // "541.867 - 549.19" -> { low, high } - regularMarketDayLow?: number; // 541.867, - regularMarketVolume?: number; // 4228841, - regularMarketPreviousClose?: number; // 546.57, - preMarketChange?: number; // -2.9299927, - preMarketChangePercent?: number; // -0.53606904, - preMarketTime?: Date; // 1612558802 -> new Date() - preMarketPrice?: number; // 543.64, - bid?: number; // 543.84, - ask?: number; // 544.15, - bidSize?: number; // 18, - askSize?: number; // 8, - fullExchangeName: string; // "NasdaqGS", - financialCurrency?: string; // "USD", - regularMarketOpen?: number; // 549.0, - averageDailyVolume3Month?: number; // 7475022, - averageDailyVolume10Day?: number; // 5546385, - displayName?: string; // "NVIDIA", - symbol: string; // "NVDA" - underlyingSymbol?: string; // "LD.MI" (for LDO.MI, #363) - // only on ETF? not on EQUITY? - ytdReturn?: number; // 0.31 - trailingThreeMonthReturns?: number; // 16.98 - trailingThreeMonthNavReturns?: number; // 17.08 - ipoExpectedDate?: Date; // "2020-08-13", - newListingDate?: Date; // "2021-02-16", - nameChangeDate?: Date; - prevName?: string; - averageAnalystRating?: string; - pageViewGrowthWeekly?: number; // Since 2021-11-11 (#326) - openInterest?: number; // SOHO (#248) - beta?: number; -} +import { Static, Type } from "@sinclair/typebox"; +import { + YahooDateInMs, + YahooFinanceDate, + YahooNumber, + YahooTwoNumberRange, +} from "../lib/yahooFinanceTypes.js"; + +export const QuoteBase = Type.Object( + { + language: Type.String(), // "en-US", + region: Type.String(), // "US", + quoteType: Type.String(), // "EQUITY" | "ETF" | "MUTUALFUND"; + typeDisp: Type.Optional(Type.String()), // "Equity", not always present. + quoteSourceName: Type.Optional(Type.String()), // "Delayed Quote", + triggerable: Type.Boolean(), // true, + currency: Type.Optional(Type.String()), // "USD", + // Seems to appear / disappear based not on symbol but network load (#445) + customPriceAlertConfidence: Type.Optional(Type.String()), // "HIGH" | "LOW"; TODO: anything else? + marketState: Type.Union([ + Type.Literal("REGULAR"), + Type.Literal("CLOSED"), + Type.Literal("PRE"), + Type.Literal("PREPRE"), + Type.Literal("POST"), + Type.Literal("POSTPOST"), + ]), + tradeable: Type.Boolean(), // false, + cryptoTradeable: Type.Optional(Type.Boolean()), // false + exchange: Type.String(), // "NMS", + shortName: Type.Optional(Type.String()), // "NVIDIA Corporation", + longName: Type.Optional(Type.String()), // "NVIDIA Corporation", + messageBoardId: Type.Optional(Type.String()), // "finmb_32307", + exchangeTimezoneName: Type.String(), // "America/New_York", + exchangeTimezoneShortName: Type.String(), // "EST", + gmtOffSetMilliseconds: YahooNumber, // -18000000, + market: Type.String(), // "us_market", + esgPopulated: Type.Boolean(), // false, + fiftyTwoWeekLowChange: Type.Optional(YahooNumber), // 362.96002, + fiftyTwoWeekLowChangePercent: Type.Optional(YahooNumber), // 2.0088556, + fiftyTwoWeekRange: Type.Optional(YahooTwoNumberRange), // "180.68 - 589.07" -> { low, high } + fiftyTwoWeekHighChange: Type.Optional(YahooNumber), // -45.429993, + fiftyTwoWeekHighChangePercent: Type.Optional(YahooNumber), // -0.07712155, + fiftyTwoWeekLow: Type.Optional(YahooNumber), // 180.68, + fiftyTwoWeekHigh: Type.Optional(YahooNumber), // 589.07, + fiftyTwoWeekChangePercent: Type.Optional(YahooNumber), // 22.604025 + dividendDate: Type.Optional(YahooFinanceDate), // 1609200000, + // maybe always present on EQUITY? + earningsTimestamp: Type.Optional(YahooFinanceDate), // 1614200400, + earningsTimestampStart: Type.Optional(YahooFinanceDate), // 1614200400, + earningsTimestampEnd: Type.Optional(YahooFinanceDate), // 1614200400, + trailingAnnualDividendRate: Type.Optional(YahooNumber), // 0.64, + trailingPE: Type.Optional(YahooNumber), // 88.873634, + trailingAnnualDividendYield: Type.Optional(YahooNumber), // 0.0011709387, + epsTrailingTwelveMonths: Type.Optional(YahooNumber), // 6.117, + epsForward: Type.Optional(YahooNumber), // 11.68, + epsCurrentYear: Type.Optional(YahooNumber), // 9.72, + priceEpsCurrentYear: Type.Optional(YahooNumber), // 55.930042, + sharesOutstanding: Type.Optional(YahooNumber), // 619000000, + bookValue: Type.Optional(YahooNumber), // 24.772, + fiftyDayAverage: Type.Optional(YahooNumber), // 530.8828, + fiftyDayAverageChange: Type.Optional(YahooNumber), // 12.757202, + fiftyDayAverageChangePercent: Type.Optional(YahooNumber), // 0.024030166, + twoHundredDayAverage: Type.Optional(YahooNumber), // 515.8518, + twoHundredDayAverageChange: Type.Optional(YahooNumber), // 27.788208, + twoHundredDayAverageChangePercent: Type.Optional(YahooNumber), // 0.053868588, + marketCap: Type.Optional(YahooNumber), // 336513171456, + forwardPE: Type.Optional(YahooNumber), // 46.54452, + priceToBook: Type.Optional(YahooNumber), // 21.945745, + sourceInterval: YahooNumber, // 15, + exchangeDataDelayedBy: YahooNumber, // 0, + firstTradeDateMilliseconds: Type.Optional(YahooDateInMs), // 917015400000 -> Date + priceHint: YahooNumber, // 2, + postMarketChangePercent: Type.Optional(YahooNumber), // 0.093813874, + postMarketTime: Type.Optional(YahooFinanceDate), // 1612573179 -> new Date() + postMarketPrice: Type.Optional(YahooNumber), // 544.15, + postMarketChange: Type.Optional(YahooNumber), // 0.51000977, + regularMarketChange: Type.Optional(YahooNumber), // -2.9299927, + regularMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, + regularMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() + regularMarketPrice: Type.Optional(YahooNumber), // 543.64, + regularMarketDayHigh: Type.Optional(YahooNumber), // 549.19, + regularMarketDayRange: Type.Optional(YahooTwoNumberRange), // "541.867 - 549.19" -> { low, high } + regularMarketDayLow: Type.Optional(YahooNumber), // 541.867, + regularMarketVolume: Type.Optional(YahooNumber), // 4228841, + regularMarketPreviousClose: Type.Optional(YahooNumber), // 546.57, + preMarketChange: Type.Optional(YahooNumber), // -2.9299927, + preMarketChangePercent: Type.Optional(YahooNumber), // -0.53606904, + preMarketTime: Type.Optional(YahooFinanceDate), // 1612558802 -> new Date() + preMarketPrice: Type.Optional(YahooNumber), // 543.64, + bid: Type.Optional(YahooNumber), // 543.84, + ask: Type.Optional(YahooNumber), // 544.15, + bidSize: Type.Optional(YahooNumber), // 18, + askSize: Type.Optional(YahooNumber), // 8, + fullExchangeName: Type.String(), // "NasdaqGS", + financialCurrency: Type.Optional(Type.String()), // "USD", + regularMarketOpen: Type.Optional(YahooNumber), // 549.0, + averageDailyVolume3Month: Type.Optional(YahooNumber), // 7475022, + averageDailyVolume10Day: Type.Optional(YahooNumber), // 5546385, + displayName: Type.Optional(Type.String()), // "NVIDIA", + symbol: Type.String(), // "NVDA" + underlyingSymbol: Type.Optional(Type.String()), // "LD.MI" (for LDO.MI, #363) + // only on ETF? not on EQUITY? + ytdReturn: Type.Optional(YahooNumber), // 0.31 + trailingThreeMonthReturns: Type.Optional(YahooNumber), // 16.98 + trailingThreeMonthNavReturns: Type.Optional(YahooNumber), // 17.08 + ipoExpectedDate: Type.Optional(YahooFinanceDate), // "2020-08-13", + newListingDate: Type.Optional(YahooFinanceDate), // "2021-02-16", + nameChangeDate: Type.Optional(YahooFinanceDate), + prevName: Type.Optional(Type.String()), + averageAnalystRating: Type.Optional(Type.String()), + pageViewGrowthWeekly: Type.Optional(YahooNumber), // Since 2021-11-11 (#326) + openInterest: Type.Optional(YahooNumber), // SOHO (#248) + beta: Type.Optional(YahooNumber), + }, + { + additionalProperties: Type.Any(), + } +); /* * [TODO] Fields seen in a query but not in this module yet: @@ -125,91 +142,145 @@ export interface QuoteBase { /* * Guaranteed fields, even we don't ask for them */ - -export interface QuoteCryptoCurrency extends QuoteBase { - quoteType: "CRYPTOCURRENCY"; - circulatingSupply: number; - fromCurrency: string; // 'BTC' - toCurrency: string; // 'USD=X' - lastMarket: string; // 'CoinMarketCap' - coinImageUrl?: string; // 'https://s.yimg.com/uc/fin/img/reports-thumbnails/1.png' - volume24Hr?: number; // 62631043072 - volumeAllCurrencies?: number; // 62631043072 - startDate?: Date; // new Date(1367103600 * 1000) -} - -export interface QuoteCurrency extends QuoteBase { - quoteType: "CURRENCY"; -} - -export interface QuoteEtf extends QuoteBase { - quoteType: "ETF"; -} - -export interface QuoteEquity extends QuoteBase { - quoteType: "EQUITY"; - dividendRate?: number; // 0.96 - dividendYield?: number; // 0.51, -} - -export interface QuoteFuture extends QuoteBase { - quoteType: "FUTURE"; - headSymbolAsString: string; // "GC=F" - contractSymbol: boolean; // false - underlyingExchangeSymbol: string; // "GCM22.CMX" - expireDate: Date; // 1656374400 - expireIsoDate: number; // 2022 -} - -export interface QuoteIndex extends QuoteBase { - quoteType: "INDEX"; -} - -export interface QuoteOption extends QuoteBase { - quoteType: "OPTION"; - strike: number; - openInterest: number; - expireDate: number; - expireIsoDate: number; - underlyingSymbol: string; -} - -export interface QuoteMutualfund extends QuoteBase { - quoteType: "MUTUALFUND"; -} - -export type Quote = - | QuoteCryptoCurrency - | QuoteCurrency - | QuoteEtf - | QuoteEquity - | QuoteFuture - | QuoteIndex - | QuoteMutualfund - | QuoteOption; - -export type QuoteField = keyof Quote; - -export type ResultType = "array" | "object" | "map"; - -export type QuoteResponseArray = Quote[]; -export type QuoteResponseMap = Map; -export type QuoteResponseObject = { [key: string]: Quote }; - -export interface QuoteOptions { - fields?: QuoteField[]; - return?: ResultType; -} - -export interface QuoteOptionsWithReturnArray extends QuoteOptions { - return?: "array"; -} -export interface QuoteOptionsWithReturnMap extends QuoteOptions { - return: "map"; -} -export interface QuoteOptionsWithReturnObject extends QuoteOptions { - return: "object"; -} +const QuoteCryptoCurrency = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("CRYPTOCURRENCY"), + circulatingSupply: YahooNumber, + fromCurrency: Type.String(), // 'BTC' + toCurrency: Type.String(), // 'USD=X' + lastMarket: Type.String(), // 'CoinMarketCap' + coinImageUrl: Type.Optional(Type.String()), // 'https://s.yimg.com/uc/fin/img/reports-thumbnails/1.png' + volume24Hr: Type.Optional(YahooNumber), // 62631043072 + volumeAllCurrencies: Type.Optional(YahooNumber), // 62631043072 + startDate: Type.Optional(YahooFinanceDate), // new Date(1367103600 * 1000) + }), +]); + +const QuoteCurrency = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("CURRENCY"), + }), +]); + +const QuoteEtf = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("ETF"), + }), +]); + +const QuoteEquity = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("EQUITY"), + dividendRate: Type.Optional(Type.Number()), + dividendYield: Type.Optional(Type.Number()), + }), +]); + +const QuoteFuture = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("FUTURE"), + headSymbolAsString: Type.String(), + contractSymbol: Type.Boolean(), + underlyingExchangeSymbol: Type.String(), + expireDate: YahooFinanceDate, + expireIsoDate: YahooFinanceDate, + }), +]); + +const QuoteIndex = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("INDEX"), + }), +]); + +const QuoteOption = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("OPTION"), + strike: YahooNumber, + openInterest: YahooNumber, + expireDate: YahooNumber, + expireIsoDate: YahooNumber, + underlyingSymbol: Type.String(), + }), +]); + +const QuoteMutualfund = Type.Composite([ + QuoteBase, + Type.Object({ + quoteType: Type.Literal("MUTUALFUND"), + }), +]); + +const QuoteSchema = Type.Union([ + QuoteCryptoCurrency, + QuoteCurrency, + QuoteEtf, + QuoteEquity, + QuoteFuture, + QuoteIndex, + QuoteMutualfund, + QuoteOption, +]); + +export type Quote = Static; + +const QuoteField = Type.KeyOf(QuoteSchema); + +const ResultType = Type.Union([ + Type.Literal("array"), + Type.Literal("object"), + Type.Literal("map"), +]); + +const QuoteResponseArraySchema = Type.Array(QuoteSchema); + +type QuoteResponseArray = Quote[]; +type QuoteResponseMap = Map; +type QuoteResponseObject = { [key: string]: Quote }; + +export const QuoteOptionsSchema = Type.Object({ + fields: Type.Optional(Type.Array(QuoteField)), + return: Type.Optional(ResultType), +}); + +const QuoteOptionsWithReturnArraySchema = Type.Composite([ + QuoteOptionsSchema, + Type.Object({ + return: Type.Optional(Type.Literal("array")), + }), +]); +const QuoteOptionsWithReturnMapSchema = Type.Composite([ + QuoteOptionsSchema, + Type.Object({ + return: Type.Literal("map"), + }), +]); + +const QuoteOptionsWithReturnObjectSchema = Type.Composite([ + QuoteOptionsSchema, + Type.Object({ + return: Type.Literal("object"), + }), +]); + +type QuoteOptionsWithReturnArray = Static< + typeof QuoteOptionsWithReturnArraySchema +>; + +type QuoteOptionsWithReturnMap = Static; + +type QuoteOptionsWithReturnObject = Static< + typeof QuoteOptionsWithReturnObjectSchema +>; + +export type QuoteOptions = Static; const queryOptionsDefaults = {}; @@ -261,13 +332,13 @@ export default async function quote( const symbols = typeof query === "string" ? query : query.join(","); const returnAs = queryOptionsOverrides && queryOptionsOverrides.return; - const results: Quote[] = await this._moduleExec({ + const results: Quote[] = await this._moduleExecTypebox({ moduleName: "quote", query: { url: "https://${YF_QUERY_HOST}/v7/finance/quote", needsCrumb: true, - schemaKey: "#/definitions/QuoteOptions", + schema: QuoteOptionsSchema, defaults: queryOptionsDefaults, runtime: { symbols }, overrides: queryOptionsOverrides, @@ -283,8 +354,9 @@ export default async function quote( }, result: { - schemaKey: "#/definitions/QuoteResponseArray", + schema: QuoteResponseArraySchema, transformWith(rawResult: any) { + console.log({ rawResult: JSON.stringify(rawResult, null, 2) }); let results = rawResult?.quoteResponse?.result; if (!results || !Array.isArray(results)) @@ -305,14 +377,16 @@ export default async function quote( switch (returnAs) { case "array": return results as Quote[]; - case "object": + case "object": { const object = {} as any; - for (let result of results) object[result.symbol] = result; + for (const result of results) object[result.symbol] = result; return object; // TODO: type - case "map": + } + case "map": { const map = new Map(); - for (let result of results) map.set(result.symbol, result); + for (const result of results) map.set(result.symbol, result); return map; // TODO: type + } } } else { // By default, match the query input shape (string or string[]). diff --git a/src/other/quoteCombine.ts b/src/other/quoteCombine.ts index 957d7002..2096dd06 100644 --- a/src/other/quoteCombine.ts +++ b/src/other/quoteCombine.ts @@ -4,11 +4,10 @@ import type { ModuleOptionsWithValidateFalse, ModuleThis, } from "../lib/moduleCommon.js"; +import { validateAndCoerceTypebox } from "../lib/validateAndCoerceTypes.js"; import type { QuoteOptions, Quote } from "../modules/quote.js"; -import quote from "../modules/quote.js"; - -import validateAndCoerceTypes from "../lib/validateAndCoerceTypes.js"; +import quote, { QuoteOptionsSchema } from "../modules/quote.js"; const DEBOUNCE_TIME = 50; @@ -42,11 +41,10 @@ export default function quoteCombine( JSON.stringify(symbol, null, 2) ); - validateAndCoerceTypes({ - source: "quoteCombine", + validateAndCoerceTypebox({ type: "options", - object: queryOptionsOverrides, - schemaKey: "#/definitions/QuoteOptions", + data: queryOptionsOverrides, + schema: QuoteOptionsSchema, options: this._opts.validation, }); @@ -84,17 +82,17 @@ export default function quoteCombine( // @ts-ignore thisQuote(symbols, queryOptionsOverrides, moduleOptions) .then((results) => { - for (let result of results) { - for (let promise of entry.symbols.get(result.symbol)) { + for (const result of results) { + for (const promise of entry.symbols.get(result.symbol)) { promise.resolve(result); promise.resolved = true; } } // Check for symbols we asked for and didn't get back, - // e.g. non-existant symbols (#150) - for (let [symbol, promises] of entry.symbols) { - for (let promise of promises) { + // e.g. non-existent symbols (#150) + for (const [_, promises] of entry.symbols) { + for (const promise of promises) { if (!promise.resolved) { promise.resolve(undefined); } @@ -102,8 +100,8 @@ export default function quoteCombine( } }) .catch((error) => { - for (let symbolPromiseCallbacks of entry.symbols.values()) - for (let promise of symbolPromiseCallbacks) promise.reject(error); + for (const symbolPromiseCallbacks of entry.symbols.values()) + for (const promise of symbolPromiseCallbacks) promise.reject(error); }); }, DEBOUNCE_TIME); }); From 6af931d2d9361076302073c102732ee10f43736e Mon Sep 17 00:00:00 2001 From: Eddie Atkinson <45678264+eddie-atkinson@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:57:25 +1000 Subject: [PATCH 13/13] Remove JSON schema validation pathway entirely --- .circleci/config.yml | 3 - CONTRIBUTING.md | 16 +- docs/validation.md | 5 +- package.json | 14 +- schema.json | 10291 ---------------- scripts/json-transform.sh | 51 - scripts/schema-check.sh | 14 - scripts/schema.ts | 75 - .../schema/TypeFormatter/yfFunctionIgnorer.ts | 20 - .../TypeFormatter/yfNumberTypeFormatter.ts | 23 - .../TypeFormatter/yfReferenceTypeFormatter.ts | 20 - scripts/schema/postWalker.js | 74 - src/index-common.ts | 4 +- src/lib/errors.ts | 11 +- src/lib/moduleCommon.ts | 8 +- src/lib/moduleExec.spec.ts | 117 +- src/lib/moduleExec.ts | 58 +- src/lib/moduleExecTypebox.spec.ts | 208 - src/lib/moduleExecTypebox.ts | 209 - src/lib/options.ts | 56 +- src/lib/queue.ts | 22 +- src/lib/setGlobalConfig.spec.ts | 46 +- src/lib/setGlobalConfig.ts | 30 +- src/lib/validateAndCoerceTypes.spec.ts | 578 +- src/lib/validateAndCoerceTypes.ts | 376 +- src/modules/chart.spec.ts | 4 +- src/modules/chart.ts | 2 +- src/modules/dailyGainers.ts | 2 +- src/modules/fundamentalsTimeSeries.ts | 2 +- src/modules/historical.spec.ts | 4 +- src/modules/historical.ts | 2 +- src/modules/insights.ts | 2 +- src/modules/options.ts | 2 +- src/modules/quote.ts | 2 +- src/modules/quoteSummary.ts | 2 +- src/modules/recommendationsBySymbol.ts | 2 +- src/modules/screener.ts | 2 +- src/modules/search.ts | 2 +- src/modules/trendingSymbols.ts | 2 +- tests/http/getCrumb-getcrumb | 67 - tests/testYf.ts | 2 - yarn.lock | 90 - 42 files changed, 440 insertions(+), 12080 deletions(-) delete mode 100644 schema.json delete mode 100755 scripts/json-transform.sh delete mode 100755 scripts/schema-check.sh delete mode 100644 scripts/schema.ts delete mode 100644 scripts/schema/TypeFormatter/yfFunctionIgnorer.ts delete mode 100644 scripts/schema/TypeFormatter/yfNumberTypeFormatter.ts delete mode 100644 scripts/schema/TypeFormatter/yfReferenceTypeFormatter.ts delete mode 100644 scripts/schema/postWalker.js delete mode 100644 src/lib/moduleExecTypebox.spec.ts delete mode 100644 src/lib/moduleExecTypebox.ts delete mode 100644 tests/http/getCrumb-getcrumb diff --git a/.circleci/config.yml b/.circleci/config.yml index 79d8873f..b2566445 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,9 +42,6 @@ jobs: # Check for TypeScript errors - run: yarn test:ts - # Check that schema is up-to-date - - run: scripts/schema-check.sh - - run: yarn prettier --check src tests tests-modules # run tests! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51fe1a1e..c3d4fbac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,11 +71,6 @@ work and submitting your PR. All PRs should be submitted against the `devel` branch (github default). -**Changing TypeScript Interfaces** - -Run `yarn generateSchema` after changing any interfaces, this will regenerate -the `schema.json` file which is used for run-time tests. - **Commit Messages** Commit messages should follow the @@ -134,12 +129,11 @@ TODO Checklist: 1. Create it in `src/modules/myAmazingModule.ts` -1. Run `yarn generateSchema` (and on any future interface changes) -1. Test it in `src/modules/myAmazingModule.spec.ts` -1. Add it to `src/index-common.ts` -1. Docs in `docs/modules/myAmazingModule.md` -1. Link these docs in `README.md` and `docs/README.md`. -1. Commit all the above and any `tests/http/*` created in your tests. +2. Test it in `src/modules/myAmazingModule.spec.ts` +3. Add it to `src/index-common.ts` +4. Docs in `docs/modules/myAmazingModule.md` +5. Link these docs in `README.md` and `docs/README.md`. +6. Commit all the above and any `tests/http/*` created in your tests. For a model example, see the [recommendationsBySymbol PR](https://github.com/gadicc/node-yahoo-finance2/pull/28) diff --git a/docs/validation.md b/docs/validation.md index d6f18de1..2f69f595 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -146,16 +146,17 @@ receive back, we'll no longer throw a new error on new unknown keys. This is important because Yahoo constantly add new fields, and this would break all existing deployments. -You can revert to the old behaviour with: +You can revert to the old behaviour by passing the `_internalThrowOnAdditionalProperties` to the global configuration like so: ```js -yahooFinance._disallowAdditionalProps(); +yahooFinance.setGlobalConfig({ validation: { _internalThrowOnAdditionalProperties: true} }); ``` which is the default when `NODE_ENV==="test"`. This means that during our development of the library itself, we make sure that we're testing against all types. + ## Help Fix Validation Errors diff --git a/package.json b/package.json index e3ec9675..b589e3e0 100644 --- a/package.json +++ b/package.json @@ -37,15 +37,10 @@ "scripts": { "coverage": "yarn test --coverage", "lint": "eslint . --ext .js,.ts", - "//schema": "ts-json-schema-generator -f tsconfig.json -p 'src/{modules/**/*.ts,lib/options.ts}' -t '*' | node bin/schema-tweak.js > schema.json", - "schema": "node --loader ts-node/esm scripts/schema.js > schema.json", "timeseries": "node --loader ts-node/esm scripts/timeseries.js", - "build": "yarn run build:esm && yarn run build:cjs && yarn run build:post", + "build": "yarn run build:esm && yarn run build:cjs", "build:esm": "tsc --module es2020 --target es2019 --outDir dist/esm", "build:cjs": "tsc --module commonjs --target es2015 --outDir dist/cjs && sed 's/\"type\": \"module\",/\"type:\": \"commonjs\",/' dist/cjs/package.json > dist/cjs/package-changed.json && mv dist/cjs/package-changed.json dist/cjs/package.json", - "build:post": "scripts/json-transform.sh", - "generateSchema": "yarn schema", - "prepublishOnly": "yarn build && yarn generateSchema && yarn build:post", "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", "test:ts": "tsc --noEmit", "test:esm": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js -c tests-modules/esm/jest.config.js tests-modules/esm/tests/*", @@ -54,14 +49,11 @@ "test:build": "yarn test:modules" }, "files": [ - "dist", - "schema.json" + "dist" ], "dependencies": { "@sinclair/typebox": "^0.32.27", "@types/tough-cookie": "^4.0.2", - "ajv": "8.10.0", - "ajv-formats": "2.1.1", "node-fetch": "^2.6.1", "tough-cookie": "^4.1.2", "tough-cookie-file-store": "^2.0.3" @@ -84,11 +76,9 @@ "globby": "13.2.2", "jest": "29.7.0", "jest-tobetype": "1.2.3", - "oas-schema-walker": "1.1.5", "prettier": "2.8.8", "semantic-release": "19.0.5", "ts-jest": "29.1.2", - "ts-json-schema-generator": "1.5.0", "ts-node": "10.9.2", "typescript": "5.4.3" } diff --git a/schema.json b/schema.json deleted file mode 100644 index f0248010..00000000 --- a/schema.json +++ /dev/null @@ -1,10291 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$comment": "DO NOT EDIT THIS FILE. It is generated automatically from typescript interfaces in the project. To update, run `yarn schema`.", - "definitions": { - "Logger": { - "type": "object", - "properties": { - "info": {}, - "warn": {}, - "error": {}, - "debug": {} - }, - "required": [ - "info", - "warn", - "error", - "debug" - ], - "additionalProperties": false - }, - "YahooFinanceOptions": { - "type": "object", - "properties": { - "YF_QUERY_HOST": { - "type": "string" - }, - "cookieJar": { - "$ref": "#/definitions/ExtendedCookieJar" - }, - "queue": { - "$ref": "#/definitions/QueueOptions" - }, - "validation": { - "$ref": "#/definitions/ValidationOptions" - }, - "logger": { - "$ref": "#/definitions/Logger" - } - }, - "additionalProperties": false - }, - "ExtendedCookieJar": { - "type": "object", - "additionalProperties": false, - "properties": {} - }, - "CookieJar": { - "type": "object", - "additionalProperties": false - }, - "QueueOptions": { - "type": "object", - "properties": { - "concurrency": { - "yahooFinanceType": "number" - }, - "timeout": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, - "ValidationOptions": { - "type": "object", - "properties": { - "logErrors": { - "type": "boolean" - }, - "logOptionsErrors": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/ChartOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, - "ModuleThis": { - "type": "object", - "properties": { - "_moduleExec": {}, - "_moduleExecTypebox": { - "$comment": "(\n this: { [key: string]: any },\n opts: ModuleExecOptions) -> undefined" - } - }, - "required": [ - "_moduleExec", - "_moduleExecTypebox" - ] - }, - "ChartOptions": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "useYfid": { - "type": "boolean" - }, - "interval": { - "type": "string", - "enum": [ - "1m", - "2m", - "5m", - "15m", - "30m", - "60m", - "90m", - "1h", - "1d", - "5d", - "1wk", - "1mo", - "3mo" - ] - }, - "includePrePost": { - "type": "boolean" - }, - "events": { - "type": "string" - }, - "lang": { - "type": "string" - }, - "return": { - "type": "string", - "enum": [ - "array", - "object" - ] - } - }, - "required": [ - "period1" - ], - "additionalProperties": false - }, - "ModuleOptions": { - "type": "object", - "properties": { - "validateResult": { - "type": "boolean" - }, - "devel": { - "type": [ - "boolean", - "string" - ] - }, - "fetchOptions": { - "type": "object" - } - }, - "additionalProperties": false - }, - "ChartResultObject": { - "type": "object", - "properties": { - "meta": { - "$ref": "#/definitions/ChartMeta" - }, - "timestamp": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "events": { - "$ref": "#/definitions/ChartEventsObject" - }, - "indicators": { - "$ref": "#/definitions/ChartIndicatorsObject" - } - }, - "required": [ - "meta", - "indicators" - ] - }, - "ChartMeta": { - "type": "object", - "properties": { - "currency": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "exchangeName": { - "type": "string" - }, - "instrumentType": { - "type": "string" - }, - "firstTradeDate": { - "yahooFinanceType": "date|null" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "gmtoffset": { - "yahooFinanceType": "number" - }, - "timezone": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "chartPreviousClose": { - "yahooFinanceType": "number" - }, - "previousClose": { - "yahooFinanceType": "number" - }, - "scale": { - "yahooFinanceType": "number" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "currentTradingPeriod": { - "type": "object", - "properties": { - "pre": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - }, - "regular": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - }, - "post": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - } - }, - "required": [ - "pre", - "regular", - "post" - ] - }, - "tradingPeriods": { - "$ref": "#/definitions/ChartMetaTradingPeriods" - }, - "dataGranularity": { - "type": "string" - }, - "range": { - "type": "string" - }, - "validRanges": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "currency", - "symbol", - "exchangeName", - "instrumentType", - "firstTradeDate", - "regularMarketTime", - "gmtoffset", - "timezone", - "exchangeTimezoneName", - "regularMarketPrice", - "priceHint", - "currentTradingPeriod", - "dataGranularity", - "range", - "validRanges" - ] - }, - "ChartMetaTradingPeriod": { - "type": "object", - "properties": { - "timezone": { - "type": "string" - }, - "start": { - "yahooFinanceType": "date" - }, - "end": { - "yahooFinanceType": "date" - }, - "gmtoffset": { - "yahooFinanceType": "number" - } - }, - "required": [ - "timezone", - "start", - "end", - "gmtoffset" - ] - }, - "ChartMetaTradingPeriods": { - "type": "object", - "properties": { - "pre": { - "type": "array", - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - } - } - }, - "post": { - "type": "array", - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - } - } - }, - "regular": { - "type": "array", - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartMetaTradingPeriod" - } - } - } - } - }, - "ChartEventsObject": { - "type": "object", - "properties": { - "dividends": { - "$ref": "#/definitions/ChartEventDividends" - }, - "splits": { - "$ref": "#/definitions/ChartEventSplits" - } - } - }, - "ChartEventDividends": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ChartEventDividend" - } - }, - "ChartEventDividend": { - "type": "object", - "properties": { - "amount": { - "yahooFinanceType": "number" - }, - "date": { - "yahooFinanceType": "date" - } - }, - "required": [ - "amount", - "date" - ] - }, - "ChartEventSplits": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/ChartEventSplit" - } - }, - "ChartEventSplit": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - }, - "numerator": { - "yahooFinanceType": "number" - }, - "denominator": { - "yahooFinanceType": "number" - }, - "splitRatio": { - "type": "string" - } - }, - "required": [ - "date", - "numerator", - "denominator", - "splitRatio" - ] - }, - "ChartIndicatorsObject": { - "type": "object", - "properties": { - "quote": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartIndicatorQuote" - } - }, - "adjclose": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartIndicatorAdjclose" - } - } - }, - "required": [ - "quote" - ] - }, - "ChartIndicatorQuote": { - "type": "object", - "properties": { - "high": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - }, - "low": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - }, - "open": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - }, - "close": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - }, - "volume": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - } - }, - "required": [ - "high", - "low", - "open", - "close", - "volume" - ] - }, - "ChartIndicatorAdjclose": { - "type": "object", - "properties": { - "adjclose": { - "type": "array", - "items": { - "yahooFinanceType": "number|null" - } - } - } - }, - "ChartResultArray": { - "type": "object", - "properties": { - "meta": { - "$ref": "#/definitions/ChartMeta" - }, - "events": { - "$ref": "#/definitions/ChartEventsArray" - }, - "quotes": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartResultArrayQuote" - } - } - }, - "required": [ - "meta", - "quotes" - ], - "additionalProperties": false - }, - "ChartEventsArray": { - "type": "object", - "properties": { - "dividends": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartEventDividend" - } - }, - "splits": { - "type": "array", - "items": { - "$ref": "#/definitions/ChartEventSplit" - } - } - } - }, - "ChartResultArrayQuote": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - }, - "high": { - "yahooFinanceType": "number|null" - }, - "low": { - "yahooFinanceType": "number|null" - }, - "open": { - "yahooFinanceType": "number|null" - }, - "close": { - "yahooFinanceType": "number|null" - }, - "volume": { - "yahooFinanceType": "number|null" - }, - "adjclose": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "date", - "high", - "low", - "open", - "close", - "volume" - ] - }, - "ChartOptionsWithReturnArray": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "useYfid": { - "type": "boolean" - }, - "interval": { - "type": "string", - "enum": [ - "1m", - "2m", - "5m", - "15m", - "30m", - "60m", - "90m", - "1h", - "1d", - "5d", - "1wk", - "1mo", - "3mo" - ] - }, - "includePrePost": { - "type": "boolean" - }, - "events": { - "type": "string" - }, - "lang": { - "type": "string" - }, - "return": { - "type": "string", - "const": "array" - } - }, - "additionalProperties": false, - "required": [ - "period1" - ] - }, - "ChartOptionsWithReturnObject": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "useYfid": { - "type": "boolean" - }, - "interval": { - "type": "string", - "enum": [ - "1m", - "2m", - "5m", - "15m", - "30m", - "60m", - "90m", - "1h", - "1d", - "5d", - "1wk", - "1mo", - "3mo" - ] - }, - "includePrePost": { - "type": "boolean" - }, - "events": { - "type": "string" - }, - "lang": { - "type": "string" - }, - "return": { - "type": "string", - "const": "object" - } - }, - "required": [ - "period1", - "return" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/DailyGainersOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this" - ], - "additionalProperties": false - }, - "DailyGainersOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, - "DailyGainersResult": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "canonicalName": { - "type": "string" - }, - "criteriaMeta": { - "$ref": "#/definitions/DailyGainersCriteriaMeta" - }, - "rawCriteria": { - "type": "string" - }, - "start": { - "yahooFinanceType": "number" - }, - "count": { - "yahooFinanceType": "number" - }, - "total": { - "yahooFinanceType": "number" - }, - "quotes": { - "type": "array", - "items": { - "$ref": "#/definitions/DailyGainersQuote" - } - }, - "useRecords": { - "type": "boolean" - }, - "predefinedScr": { - "type": "boolean" - }, - "versionId": { - "yahooFinanceType": "number" - }, - "creationDate": { - "yahooFinanceType": "number" - }, - "lastUpdated": { - "yahooFinanceType": "number" - }, - "isPremium": { - "type": "boolean" - }, - "iconUrl": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "description", - "canonicalName", - "criteriaMeta", - "rawCriteria", - "start", - "count", - "total", - "quotes", - "useRecords", - "predefinedScr", - "versionId", - "creationDate", - "lastUpdated", - "isPremium", - "iconUrl" - ], - "additionalProperties": false - }, - "DailyGainersCriteriaMeta": { - "type": "object", - "properties": { - "size": { - "yahooFinanceType": "number" - }, - "offset": { - "yahooFinanceType": "number" - }, - "sortField": { - "type": "string" - }, - "sortType": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "criteria": { - "type": "array", - "items": { - "$ref": "#/definitions/DailyGainersCriterum" - } - }, - "topOperator": { - "type": "string" - } - }, - "required": [ - "size", - "offset", - "sortField", - "sortType", - "quoteType", - "criteria", - "topOperator" - ], - "additionalProperties": false - }, - "DailyGainersCriterum": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "operators": { - "type": "array", - "items": { - "type": "string" - } - }, - "values": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "labelsSelected": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "dependentValues": { - "type": "array", - "items": {} - } - }, - "required": [ - "field", - "operators", - "values", - "labelsSelected", - "dependentValues" - ], - "additionalProperties": false - }, - "DailyGainersQuote": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "lastCloseTevEbitLtm": { - "yahooFinanceType": "number" - }, - "lastClosePriceToNNWCPerShare": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "number" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "number" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "number" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "fullExchangeName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "type": "string" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "earningsTimestamp": { - "yahooFinanceType": "number" - }, - "earningsTimestampStart": { - "yahooFinanceType": "number" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "marketState": { - "type": "string" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "esgPopulated": { - "type": "boolean" - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "shortName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "symbol": { - "type": "string" - }, - "dividendDate": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "number" - }, - "dividendYield": { - "yahooFinanceType": "number" - }, - "dividendRate": { - "yahooFinanceType": "number" - } - }, - "required": [ - "language", - "region", - "quoteType", - "typeDisp", - "quoteSourceName", - "triggerable", - "customPriceAlertConfidence", - "firstTradeDateMilliseconds", - "priceHint", - "regularMarketChange", - "regularMarketTime", - "regularMarketPrice", - "regularMarketDayHigh", - "regularMarketDayRange", - "currency", - "regularMarketDayLow", - "regularMarketVolume", - "regularMarketPreviousClose", - "market", - "messageBoardId", - "fullExchangeName", - "longName", - "regularMarketOpen", - "averageDailyVolume3Month", - "averageDailyVolume10Day", - "fiftyTwoWeekLowChange", - "fiftyTwoWeekLowChangePercent", - "fiftyTwoWeekRange", - "fiftyTwoWeekHighChange", - "fiftyTwoWeekHighChangePercent", - "fiftyTwoWeekChangePercent", - "trailingAnnualDividendRate", - "trailingAnnualDividendYield", - "marketState", - "sharesOutstanding", - "fiftyDayAverage", - "fiftyDayAverageChange", - "fiftyDayAverageChangePercent", - "twoHundredDayAverage", - "twoHundredDayAverageChange", - "twoHundredDayAverageChangePercent", - "marketCap", - "sourceInterval", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "gmtOffSetMilliseconds", - "esgPopulated", - "tradeable", - "cryptoTradeable", - "exchange", - "fiftyTwoWeekLow", - "fiftyTwoWeekHigh", - "shortName", - "regularMarketChangePercent", - "symbol" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/FundamentalsTimeSeriesOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, - "FundamentalsTimeSeriesOptions": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "yahooFinanceType": "number" - }, - { - "type": "string" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "yahooFinanceType": "number" - }, - { - "type": "string" - } - ] - }, - "type": { - "type": "string" - }, - "merge": { - "type": "boolean" - }, - "padTimeSeries": { - "type": "boolean" - }, - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "module": { - "type": "string" - } - }, - "required": [ - "period1", - "module" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "queryOptions": { - "$ref": "#/definitions/FundamentalsTimeSeriesOptions", - "description": "Input query options." - } - }, - "required": [ - "queryOptions" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "response": { - "description": "Query response." - } - }, - "required": [ - "response" - ], - "additionalProperties": false - }, - "FundamentalsTimeSeriesResults": { - "type": "array", - "items": { - "$ref": "#/definitions/FundamentalsTimeSeriesResult" - } - }, - "FundamentalsTimeSeriesResult": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - } - }, - "required": [ - "date" - ], - "additionalProperties": {} - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/HistoricalOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, - "HistoricalOptions": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "interval": { - "type": "string", - "enum": [ - "1d", - "1wk", - "1mo" - ] - }, - "events": { - "type": "string" - }, - "includeAdjustedClose": { - "type": "boolean" - } - }, - "required": [ - "period1" - ], - "additionalProperties": false - }, - "HistoricalHistoryResult": { - "type": "array", - "items": { - "$ref": "#/definitions/HistoricalRowHistory" - } - }, - "HistoricalRowHistory": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - }, - "open": { - "yahooFinanceType": "number" - }, - "high": { - "yahooFinanceType": "number" - }, - "low": { - "yahooFinanceType": "number" - }, - "close": { - "yahooFinanceType": "number" - }, - "adjClose": { - "yahooFinanceType": "number" - }, - "volume": { - "yahooFinanceType": "number" - } - }, - "required": [ - "date", - "open", - "high", - "low", - "close", - "volume" - ] - }, - "HistoricalDividendsResult": { - "type": "array", - "items": { - "$ref": "#/definitions/HistoricalRowDividend" - } - }, - "HistoricalRowDividend": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - }, - "dividends": { - "yahooFinanceType": "number" - } - }, - "required": [ - "date", - "dividends" - ], - "additionalProperties": false - }, - "HistoricalStockSplitsResult": { - "type": "array", - "items": { - "$ref": "#/definitions/HistoricalRowStockSplit" - } - }, - "HistoricalRowStockSplit": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "date" - }, - "stockSplits": { - "type": "string" - } - }, - "required": [ - "date", - "stockSplits" - ], - "additionalProperties": false - }, - "HistoricalResult": { - "anyOf": [ - { - "$ref": "#/definitions/HistoricalHistoryResult" - }, - { - "$ref": "#/definitions/HistoricalDividendsResult" - }, - { - "$ref": "#/definitions/HistoricalStockSplitsResult" - } - ] - }, - "HistoricalOptionsEventsHistory": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "interval": { - "type": "string", - "enum": [ - "1d", - "1wk", - "1mo" - ] - }, - "events": { - "type": "string", - "const": "history" - }, - "includeAdjustedClose": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "period1" - ] - }, - "HistoricalOptionsEventsDividends": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "interval": { - "type": "string", - "enum": [ - "1d", - "1wk", - "1mo" - ] - }, - "events": { - "type": "string", - "const": "dividends" - }, - "includeAdjustedClose": { - "type": "boolean" - } - }, - "required": [ - "events", - "period1" - ], - "additionalProperties": false - }, - "HistoricalOptionsEventsSplit": { - "type": "object", - "properties": { - "period1": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "period2": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "type": "string" - }, - { - "yahooFinanceType": "number" - } - ] - }, - "interval": { - "type": "string", - "enum": [ - "1d", - "1wk", - "1mo" - ] - }, - "events": { - "type": "string", - "const": "split" - }, - "includeAdjustedClose": { - "type": "boolean" - } - }, - "required": [ - "events", - "period1" - ], - "additionalProperties": false - }, - "InsightsInstrumentInfo": { - "type": "object", - "properties": { - "keyTechnicals": { - "type": "object", - "properties": { - "provider": { - "type": "string" - }, - "support": { - "yahooFinanceType": "number" - }, - "resistance": { - "yahooFinanceType": "number" - }, - "stopLoss": { - "yahooFinanceType": "number" - } - }, - "required": [ - "provider" - ] - }, - "technicalEvents": { - "type": "object", - "properties": { - "provider": { - "type": "string" - }, - "sector": { - "type": "string" - }, - "shortTermOutlook": { - "$ref": "#/definitions/InsightsOutlook" - }, - "intermediateTermOutlook": { - "$ref": "#/definitions/InsightsOutlook" - }, - "longTermOutlook": { - "$ref": "#/definitions/InsightsOutlook" - } - }, - "required": [ - "provider", - "shortTermOutlook", - "intermediateTermOutlook", - "longTermOutlook" - ] - }, - "valuation": { - "type": "object", - "properties": { - "color": { - "yahooFinanceType": "number" - }, - "description": { - "type": "string" - }, - "discount": { - "type": "string" - }, - "provider": { - "type": "string" - }, - "relativeValue": { - "type": "string" - } - }, - "required": [ - "provider" - ] - } - }, - "required": [ - "keyTechnicals", - "technicalEvents", - "valuation" - ] - }, - "InsightsOutlook": { - "type": "object", - "properties": { - "stateDescription": { - "type": "string" - }, - "direction": { - "$ref": "#/definitions/InsightsDirection" - }, - "score": { - "yahooFinanceType": "number" - }, - "scoreDescription": { - "type": "string" - }, - "sectorDirection": { - "$ref": "#/definitions/InsightsDirection" - }, - "sectorScore": { - "yahooFinanceType": "number" - }, - "sectorScoreDescription": { - "type": "string" - }, - "indexDirection": { - "$ref": "#/definitions/InsightsDirection" - }, - "indexScore": { - "yahooFinanceType": "number" - }, - "indexScoreDescription": { - "type": "string" - } - }, - "required": [ - "stateDescription", - "direction", - "score", - "scoreDescription", - "indexDirection", - "indexScore", - "indexScoreDescription" - ] - }, - "InsightsDirection": { - "type": "string", - "enum": [ - "Bearish", - "Bullish", - "Neutral" - ] - }, - "InsightsCompanySnapshot": { - "type": "object", - "properties": { - "sectorInfo": { - "type": "string" - }, - "company": { - "type": "object", - "properties": { - "innovativeness": { - "yahooFinanceType": "number" - }, - "hiring": { - "yahooFinanceType": "number" - }, - "sustainability": { - "yahooFinanceType": "number" - }, - "insiderSentiments": { - "yahooFinanceType": "number" - }, - "earningsReports": { - "yahooFinanceType": "number" - }, - "dividends": { - "yahooFinanceType": "number" - } - } - }, - "sector": { - "type": "object", - "properties": { - "innovativeness": { - "yahooFinanceType": "number" - }, - "hiring": { - "yahooFinanceType": "number" - }, - "sustainability": { - "yahooFinanceType": "number" - }, - "insiderSentiments": { - "yahooFinanceType": "number" - }, - "earningsReports": { - "yahooFinanceType": "number" - }, - "dividends": { - "yahooFinanceType": "number" - } - }, - "required": [ - "innovativeness", - "hiring", - "insiderSentiments", - "dividends" - ] - } - }, - "required": [ - "company", - "sector" - ] - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/TrendingSymbolsOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, - "TrendingSymbolsOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, - "InsightsResult": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "instrumentInfo": { - "$ref": "#/definitions/InsightsInstrumentInfo" - }, - "companySnapshot": { - "$ref": "#/definitions/InsightsCompanySnapshot" - }, - "recommendation": { - "type": "object", - "properties": { - "targetPrice": { - "yahooFinanceType": "number" - }, - "provider": { - "type": "string" - }, - "rating": { - "type": "string", - "enum": [ - "BUY", - "SELL", - "HOLD" - ] - } - }, - "required": [ - "provider", - "rating" - ], - "additionalProperties": false - }, - "events": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsEvent" - } - }, - "reports": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsReport" - } - }, - "sigDevs": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsSigDev" - } - }, - "upsell": { - "$ref": "#/definitions/InsightsUpsell" - }, - "upsellSearchDD": { - "type": "object", - "properties": { - "researchReports": { - "$ref": "#/definitions/InsightsResearchReport" - } - }, - "required": [ - "researchReports" - ], - "additionalProperties": false - }, - "secReports": { - "type": "array", - "items": { - "$ref": "#/definitions/InsightsSecReport" - } - } - }, - "required": [ - "symbol", - "sigDevs" - ] - }, - "InsightsEvent": { - "type": "object", - "properties": { - "eventType": { - "type": "string" - }, - "pricePeriod": { - "type": "string" - }, - "tradingHorizon": { - "type": "string" - }, - "tradeType": { - "type": "string" - }, - "imageUrl": { - "type": "string" - }, - "startDate": { - "yahooFinanceType": "date" - }, - "endDate": { - "yahooFinanceType": "date" - } - }, - "required": [ - "eventType", - "pricePeriod", - "tradingHorizon", - "tradeType", - "imageUrl", - "startDate", - "endDate" - ] - }, - "InsightsReport": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "headHtml": { - "type": "string" - }, - "provider": { - "type": "string" - }, - "reportDate": { - "yahooFinanceType": "date" - }, - "reportTitle": { - "type": "string" - }, - "reportType": { - "type": "string" - }, - "targetPrice": { - "yahooFinanceType": "number" - }, - "targetPriceStatus": { - "type": "string", - "enum": [ - "Increased", - "Maintained", - "Decreased", - "-" - ] - }, - "investmentRating": { - "type": "string", - "enum": [ - "Bullish", - "Neutral", - "Bearish" - ] - }, - "tickers": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "id", - "headHtml", - "provider", - "reportDate", - "reportTitle", - "reportType" - ] - }, - "InsightsSigDev": { - "type": "object", - "properties": { - "headline": { - "type": "string" - }, - "date": { - "yahooFinanceType": "date" - } - }, - "required": [ - "headline", - "date" - ] - }, - "InsightsUpsell": { - "type": "object", - "properties": { - "msBullishSummary": { - "type": "array", - "items": { - "type": "string" - } - }, - "msBearishSummary": { - "type": "array", - "items": { - "type": "string" - } - }, - "msBullishBearishSummariesPublishDate": { - "yahooFinanceType": "DateInMs" - }, - "companyName": { - "type": "string" - }, - "upsellReportType": { - "type": "string" - } - } - }, - "DateInMs": { - "yahooFinanceType": "date" - }, - "InsightsResearchReport": { - "type": "object", - "properties": { - "reportId": { - "type": "string" - }, - "provider": { - "type": "string" - }, - "title": { - "type": "string" - }, - "reportDate": { - "yahooFinanceType": "date" - }, - "summary": { - "type": "string" - }, - "investmentRating": { - "type": "string", - "enum": [ - "Bullish", - "Neutral", - "Bearish" - ] - } - }, - "required": [ - "reportId", - "provider", - "title", - "reportDate", - "summary" - ], - "additionalProperties": false - }, - "InsightsSecReport": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "filingDate": { - "yahooFinanceType": "DateInMs" - }, - "snapshotUrl": { - "type": "string" - }, - "formType": { - "type": "string" - } - }, - "required": [ - "id", - "type", - "title", - "description", - "filingDate", - "snapshotUrl", - "formType" - ], - "additionalProperties": false - }, - "InsightsOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "reportsCount": { - "yahooFinanceType": "number" - } - }, - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/OptionsOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol", - "queryOptionsOverrides" - ], - "additionalProperties": false - }, - "OptionsOptions": { - "type": "object", - "properties": { - "formatted": { - "type": "boolean" - }, - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "date": { - "anyOf": [ - { - "yahooFinanceType": "date" - }, - { - "yahooFinanceType": "number" - }, - { - "type": "string" - } - ] - } - }, - "additionalProperties": false - }, - "QuoteBase": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - } - }, - "required": [ - "language", - "region", - "quoteType", - "triggerable", - "marketState", - "tradeable", - "exchange", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "gmtOffSetMilliseconds", - "market", - "esgPopulated", - "sourceInterval", - "exchangeDataDelayedBy", - "priceHint", - "fullExchangeName", - "symbol" - ] - }, - "TwoNumberRange": { - "type": "object", - "properties": { - "low": { - "yahooFinanceType": "number" - }, - "high": { - "yahooFinanceType": "number" - } - }, - "required": [ - "low", - "high" - ], - "additionalProperties": false - }, - "QuoteCryptoCurrency": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "CRYPTOCURRENCY" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "circulatingSupply": { - "yahooFinanceType": "number" - }, - "fromCurrency": { - "type": "string" - }, - "toCurrency": { - "type": "string" - }, - "lastMarket": { - "type": "string" - }, - "coinImageUrl": { - "type": "string" - }, - "volume24Hr": { - "yahooFinanceType": "number" - }, - "volumeAllCurrencies": { - "yahooFinanceType": "number" - }, - "startDate": { - "yahooFinanceType": "date" - } - }, - "required": [ - "circulatingSupply", - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fromCurrency", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "lastMarket", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "toCurrency", - "tradeable", - "triggerable" - ] - }, - "QuoteCurrency": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "CURRENCY" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable" - ] - }, - "QuoteEtf": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "ETF" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable" - ] - }, - "QuoteEquity": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "EQUITY" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "dividendRate": { - "yahooFinanceType": "number" - }, - "dividendYield": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable" - ] - }, - "QuoteFuture": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "FUTURE" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "headSymbolAsString": { - "type": "string" - }, - "contractSymbol": { - "type": "boolean" - }, - "underlyingExchangeSymbol": { - "type": "string" - }, - "expireDate": { - "yahooFinanceType": "date" - }, - "expireIsoDate": { - "yahooFinanceType": "number" - } - }, - "required": [ - "contractSymbol", - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "expireDate", - "expireIsoDate", - "fullExchangeName", - "gmtOffSetMilliseconds", - "headSymbolAsString", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable", - "underlyingExchangeSymbol" - ] - }, - "QuoteIndex": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "INDEX" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable" - ] - }, - "QuoteOption": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "OPTION" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "strike": { - "yahooFinanceType": "number" - }, - "expireDate": { - "yahooFinanceType": "number" - }, - "expireIsoDate": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "expireDate", - "expireIsoDate", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "openInterest", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "strike", - "symbol", - "tradeable", - "triggerable", - "underlyingSymbol" - ] - }, - "QuoteMutualfund": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string", - "const": "MUTUALFUND" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "currency": { - "type": "string" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "marketState": { - "type": "string", - "enum": [ - "REGULAR", - "CLOSED", - "PRE", - "PREPRE", - "POST", - "POSTPOST" - ] - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "shortName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "esgPopulated": { - "type": "boolean" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "dividendDate": { - "yahooFinanceType": "date" - }, - "earningsTimestamp": { - "yahooFinanceType": "date" - }, - "earningsTimestampStart": { - "yahooFinanceType": "date" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "date" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "DateInMs" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "yahooFinanceType": "TwoNumberRange" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "fullExchangeName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthNavReturns": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "date" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "pageViewGrowthWeekly": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - } - }, - "required": [ - "esgPopulated", - "exchange", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "fullExchangeName", - "gmtOffSetMilliseconds", - "language", - "market", - "marketState", - "priceHint", - "quoteType", - "region", - "sourceInterval", - "symbol", - "tradeable", - "triggerable" - ] - }, - "Quote": { - "anyOf": [ - { - "$ref": "#/definitions/QuoteCryptoCurrency" - }, - { - "$ref": "#/definitions/QuoteCurrency" - }, - { - "$ref": "#/definitions/QuoteEtf" - }, - { - "$ref": "#/definitions/QuoteEquity" - }, - { - "$ref": "#/definitions/QuoteFuture" - }, - { - "$ref": "#/definitions/QuoteIndex" - }, - { - "$ref": "#/definitions/QuoteMutualfund" - }, - { - "$ref": "#/definitions/QuoteOption" - } - ] - }, - "QuoteField": { - "type": "string", - "enum": [ - "quoteType", - "circulatingSupply", - "fromCurrency", - "toCurrency", - "lastMarket", - "coinImageUrl", - "volume24Hr", - "volumeAllCurrencies", - "startDate", - "language", - "region", - "typeDisp", - "quoteSourceName", - "triggerable", - "currency", - "customPriceAlertConfidence", - "marketState", - "tradeable", - "cryptoTradeable", - "exchange", - "shortName", - "longName", - "messageBoardId", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "gmtOffSetMilliseconds", - "market", - "esgPopulated", - "fiftyTwoWeekLowChange", - "fiftyTwoWeekLowChangePercent", - "fiftyTwoWeekRange", - "fiftyTwoWeekHighChange", - "fiftyTwoWeekHighChangePercent", - "fiftyTwoWeekLow", - "fiftyTwoWeekHigh", - "fiftyTwoWeekChangePercent", - "dividendDate", - "earningsTimestamp", - "earningsTimestampStart", - "earningsTimestampEnd", - "trailingAnnualDividendRate", - "trailingPE", - "trailingAnnualDividendYield", - "epsTrailingTwelveMonths", - "epsForward", - "epsCurrentYear", - "priceEpsCurrentYear", - "sharesOutstanding", - "bookValue", - "fiftyDayAverage", - "fiftyDayAverageChange", - "fiftyDayAverageChangePercent", - "twoHundredDayAverage", - "twoHundredDayAverageChange", - "twoHundredDayAverageChangePercent", - "marketCap", - "forwardPE", - "priceToBook", - "sourceInterval", - "exchangeDataDelayedBy", - "firstTradeDateMilliseconds", - "priceHint", - "postMarketChangePercent", - "postMarketTime", - "postMarketPrice", - "postMarketChange", - "regularMarketChange", - "regularMarketChangePercent", - "regularMarketTime", - "regularMarketPrice", - "regularMarketDayHigh", - "regularMarketDayRange", - "regularMarketDayLow", - "regularMarketVolume", - "regularMarketPreviousClose", - "preMarketChange", - "preMarketChangePercent", - "preMarketTime", - "preMarketPrice", - "bid", - "ask", - "bidSize", - "askSize", - "fullExchangeName", - "financialCurrency", - "regularMarketOpen", - "averageDailyVolume3Month", - "averageDailyVolume10Day", - "displayName", - "symbol", - "underlyingSymbol", - "ytdReturn", - "trailingThreeMonthReturns", - "trailingThreeMonthNavReturns", - "ipoExpectedDate", - "newListingDate", - "nameChangeDate", - "prevName", - "averageAnalystRating", - "pageViewGrowthWeekly", - "openInterest", - "beta", - "dividendRate", - "dividendYield", - "headSymbolAsString", - "contractSymbol", - "underlyingExchangeSymbol", - "expireDate", - "expireIsoDate", - "strike" - ] - }, - "ResultType": { - "type": "string", - "enum": [ - "array", - "object", - "map" - ] - }, - "QuoteResponseArray": { - "type": "array", - "items": { - "$ref": "#/definitions/Quote" - } - }, - "QuoteResponseMap": { - "type": "object", - "properties": { - "size": { - "yahooFinanceType": "number" - } - }, - "required": [ - "size" - ], - "additionalProperties": false - }, - "QuoteResponseObject": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Quote" - } - }, - "QuoteOptions": { - "type": "object", - "properties": { - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteField" - } - }, - "return": { - "$ref": "#/definitions/ResultType" - } - }, - "additionalProperties": false - }, - "QuoteOptionsWithReturnArray": { - "type": "object", - "properties": { - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteField" - } - }, - "return": { - "type": "string", - "const": "array" - } - }, - "additionalProperties": false - }, - "QuoteOptionsWithReturnMap": { - "type": "object", - "properties": { - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteField" - } - }, - "return": { - "type": "string", - "const": "map" - } - }, - "required": [ - "return" - ], - "additionalProperties": false - }, - "QuoteOptionsWithReturnObject": { - "type": "object", - "properties": { - "fields": { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteField" - } - }, - "return": { - "type": "string", - "const": "object" - } - }, - "required": [ - "return" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/QuoteOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, - "OptionsResult": { - "type": "object", - "properties": { - "underlyingSymbol": { - "type": "string" - }, - "expirationDates": { - "type": "array", - "items": { - "yahooFinanceType": "date" - } - }, - "strikes": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "hasMiniOptions": { - "type": "boolean" - }, - "quote": { - "$ref": "#/definitions/Quote" - }, - "options": { - "type": "array", - "items": { - "$ref": "#/definitions/Option" - } - } - }, - "required": [ - "underlyingSymbol", - "expirationDates", - "strikes", - "hasMiniOptions", - "quote", - "options" - ] - }, - "Option": { - "type": "object", - "properties": { - "expirationDate": { - "yahooFinanceType": "date" - }, - "hasMiniOptions": { - "type": "boolean" - }, - "calls": { - "type": "array", - "items": { - "$ref": "#/definitions/CallOrPut" - } - }, - "puts": { - "type": "array", - "items": { - "$ref": "#/definitions/CallOrPut" - } - } - }, - "required": [ - "expirationDate", - "hasMiniOptions", - "calls", - "puts" - ] - }, - "CallOrPut": { - "type": "object", - "properties": { - "contractSymbol": { - "type": "string" - }, - "strike": { - "yahooFinanceType": "number" - }, - "currency": { - "type": "string" - }, - "lastPrice": { - "yahooFinanceType": "number" - }, - "change": { - "yahooFinanceType": "number" - }, - "percentChange": { - "yahooFinanceType": "number" - }, - "volume": { - "yahooFinanceType": "number" - }, - "openInterest": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "contractSize": { - "type": "string", - "const": "REGULAR" - }, - "expiration": { - "yahooFinanceType": "date" - }, - "lastTradeDate": { - "yahooFinanceType": "date" - }, - "impliedVolatility": { - "yahooFinanceType": "number" - }, - "inTheMoney": { - "type": "boolean" - } - }, - "required": [ - "contractSymbol", - "strike", - "lastPrice", - "change", - "contractSize", - "expiration", - "lastTradeDate", - "impliedVolatility", - "inTheMoney" - ] - }, - "EnumGrade": { - "type": "string", - "enum": [ - "Accumulate", - "Add", - "Average", - "Below Average", - "Buy", - "Conviction Buy", - "", - "Equal-Weight", - "Fair Value", - "Equal-weight", - "Long-term Buy", - "Hold", - "Long-Term Buy", - "Market Outperform", - "Market Perform", - "Mixed", - "Negative", - "Neutral", - "In-Line", - "Outperform", - "Overweight", - "Peer Perform", - "Perform", - "Positive", - "Reduce", - "Sector Outperform", - "Sector Perform", - "Sector Weight", - "Sell", - "Strong Buy", - "Top Pick", - "Underperform", - "Underperformer", - "Underweight", - "Trim", - "Above Average", - "In-line", - "Outperformer", - "OVerweight", - "Cautious", - "Market Weight", - "Sector Underperform", - "Market Underperform", - "Peer perform", - "Gradually Accumulate", - "Action List Buy", - "Performer", - "Sector Performer", - "Speculative Buy", - "Strong Sell", - "Speculative Hold", - "Not Rated", - "Hold Neutral", - "Developing", - "buy", - "HOld", - "Trading Sell", - "Tender", - "market perform", - "BUy" - ] - }, - "QuoteSummaryResult": { - "type": "object", - "properties": { - "assetProfile": { - "$ref": "#/definitions/AssetProfile" - }, - "balanceSheetHistory": { - "$ref": "#/definitions/BalanceSheetHistory" - }, - "balanceSheetHistoryQuarterly": { - "$ref": "#/definitions/BalanceSheetHistory" - }, - "calendarEvents": { - "$ref": "#/definitions/CalendarEvents" - }, - "cashflowStatementHistory": { - "$ref": "#/definitions/CashflowStatementHistory" - }, - "cashflowStatementHistoryQuarterly": { - "$ref": "#/definitions/CashflowStatementHistory" - }, - "defaultKeyStatistics": { - "$ref": "#/definitions/DefaultKeyStatistics" - }, - "earnings": { - "$ref": "#/definitions/QuoteSummaryEarnings" - }, - "earningsHistory": { - "$ref": "#/definitions/EarningsHistory" - }, - "earningsTrend": { - "$ref": "#/definitions/EarningsTrend" - }, - "financialData": { - "$ref": "#/definitions/FinancialData" - }, - "fundOwnership": { - "$ref": "#/definitions/Ownership" - }, - "fundPerformance": { - "$ref": "#/definitions/FundPerformance" - }, - "fundProfile": { - "$ref": "#/definitions/FundProfile" - }, - "incomeStatementHistory": { - "$ref": "#/definitions/IncomeStatementHistory" - }, - "incomeStatementHistoryQuarterly": { - "$ref": "#/definitions/IncomeStatementHistory" - }, - "indexTrend": { - "$ref": "#/definitions/IndexTrend" - }, - "industryTrend": { - "$ref": "#/definitions/Trend" - }, - "insiderHolders": { - "$ref": "#/definitions/Holders" - }, - "insiderTransactions": { - "$ref": "#/definitions/InsiderTransactions" - }, - "institutionOwnership": { - "$ref": "#/definitions/Ownership" - }, - "majorDirectHolders": { - "$ref": "#/definitions/Holders" - }, - "majorHoldersBreakdown": { - "$ref": "#/definitions/MajorHoldersBreakdown" - }, - "netSharePurchaseActivity": { - "$ref": "#/definitions/NetSharePurchaseActivity" - }, - "price": { - "$ref": "#/definitions/Price" - }, - "quoteType": { - "$ref": "#/definitions/QuoteType" - }, - "recommendationTrend": { - "$ref": "#/definitions/RecommendationTrend" - }, - "secFilings": { - "$ref": "#/definitions/SECFilings" - }, - "sectorTrend": { - "$ref": "#/definitions/Trend" - }, - "summaryDetail": { - "$ref": "#/definitions/SummaryDetail" - }, - "summaryProfile": { - "$ref": "#/definitions/SummaryProfile" - }, - "topHoldings": { - "$ref": "#/definitions/TopHoldings" - }, - "upgradeDowngradeHistory": { - "$ref": "#/definitions/UpgradeDowngradeHistory" - } - } - }, - "AssetProfile": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "address1": { - "type": "string" - }, - "address2": { - "type": "string" - }, - "address3": { - "type": "string" - }, - "city": { - "type": "string" - }, - "state": { - "type": "string" - }, - "zip": { - "type": "string" - }, - "country": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "fax": { - "type": "string" - }, - "website": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "industryDisp": { - "type": "string" - }, - "industryKey": { - "type": "string" - }, - "industrySymbol": { - "type": "string" - }, - "sector": { - "type": "string" - }, - "sectorDisp": { - "type": "string" - }, - "sectorKey": { - "type": "string" - }, - "longBusinessSummary": { - "type": "string" - }, - "fullTimeEmployees": { - "yahooFinanceType": "number" - }, - "companyOfficers": { - "type": "array", - "items": { - "$ref": "#/definitions/CompanyOfficer" - } - }, - "auditRisk": { - "yahooFinanceType": "number" - }, - "boardRisk": { - "yahooFinanceType": "number" - }, - "compensationRisk": { - "yahooFinanceType": "number" - }, - "shareHolderRightsRisk": { - "yahooFinanceType": "number" - }, - "overallRisk": { - "yahooFinanceType": "number" - }, - "governanceEpochDate": { - "yahooFinanceType": "date" - }, - "compensationAsOfEpochDate": { - "yahooFinanceType": "date" - }, - "name": { - "type": "string" - }, - "startDate": { - "yahooFinanceType": "date" - }, - "description": { - "type": "string" - }, - "twitter": { - "type": "string" - } - }, - "required": [ - "maxAge", - "companyOfficers" - ] - }, - "CompanyOfficer": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "name": { - "type": "string" - }, - "age": { - "yahooFinanceType": "number" - }, - "title": { - "type": "string" - }, - "yearBorn": { - "yahooFinanceType": "number" - }, - "fiscalYear": { - "yahooFinanceType": "number" - }, - "totalPay": { - "yahooFinanceType": "number" - }, - "exercisedValue": { - "yahooFinanceType": "number" - }, - "unexercisedValue": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "name", - "title" - ] - }, - "BalanceSheetHistory": { - "type": "object", - "properties": { - "balanceSheetStatements": { - "type": "array", - "items": { - "$ref": "#/definitions/BalanceSheetStatement" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "balanceSheetStatements", - "maxAge" - ] - }, - "BalanceSheetStatement": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "endDate": { - "yahooFinanceType": "date" - }, - "cash": { - "yahooFinanceType": "number" - }, - "shortTermInvestments": { - "yahooFinanceType": "number" - }, - "netReceivables": { - "yahooFinanceType": "number" - }, - "inventory": { - "yahooFinanceType": "number" - }, - "otherCurrentAssets": { - "yahooFinanceType": "number" - }, - "totalCurrentAssets": { - "yahooFinanceType": "number" - }, - "longTermInvestments": { - "yahooFinanceType": "number" - }, - "propertyPlantEquipment": { - "yahooFinanceType": "number" - }, - "otherAssets": { - "yahooFinanceType": "number" - }, - "totalAssets": { - "yahooFinanceType": "number" - }, - "accountsPayable": { - "yahooFinanceType": "number" - }, - "shortLongTermDebt": { - "yahooFinanceType": "number" - }, - "otherCurrentLiab": { - "yahooFinanceType": "number" - }, - "longTermDebt": { - "yahooFinanceType": "number" - }, - "otherLiab": { - "yahooFinanceType": "number" - }, - "totalCurrentLiabilities": { - "yahooFinanceType": "number" - }, - "totalLiab": { - "yahooFinanceType": "number" - }, - "commonStock": { - "yahooFinanceType": "number" - }, - "retainedEarnings": { - "yahooFinanceType": "number" - }, - "treasuryStock": { - "yahooFinanceType": "number" - }, - "otherStockholderEquity": { - "yahooFinanceType": "number" - }, - "totalStockholderEquity": { - "yahooFinanceType": "number" - }, - "netTangibleAssets": { - "yahooFinanceType": "number" - }, - "goodWill": { - "yahooFinanceType": "number" - }, - "intangibleAssets": { - "yahooFinanceType": "number" - }, - "deferredLongTermAssetCharges": { - "yahooFinanceType": "number" - }, - "deferredLongTermLiab": { - "yahooFinanceType": "number" - }, - "minorityInterest": { - "yahooFinanceType": "number|null" - }, - "capitalSurplus": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "endDate" - ] - }, - "CalendarEvents": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "earnings": { - "$ref": "#/definitions/CalendarEventsEarnings" - }, - "exDividendDate": { - "yahooFinanceType": "date" - }, - "dividendDate": { - "yahooFinanceType": "date" - } - }, - "required": [ - "maxAge", - "earnings" - ] - }, - "CalendarEventsEarnings": { - "type": "object", - "properties": { - "earningsDate": { - "type": "array", - "items": { - "yahooFinanceType": "date" - } - }, - "earningsAverage": { - "yahooFinanceType": "number" - }, - "earningsLow": { - "yahooFinanceType": "number" - }, - "earningsHigh": { - "yahooFinanceType": "number" - }, - "revenueAverage": { - "yahooFinanceType": "number" - }, - "revenueLow": { - "yahooFinanceType": "number" - }, - "revenueHigh": { - "yahooFinanceType": "number" - } - }, - "required": [ - "earningsDate" - ] - }, - "CashflowStatementHistory": { - "type": "object", - "properties": { - "cashflowStatements": { - "type": "array", - "items": { - "$ref": "#/definitions/CashflowStatement" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "cashflowStatements", - "maxAge" - ] - }, - "CashflowStatement": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "endDate": { - "yahooFinanceType": "date" - }, - "netIncome": { - "yahooFinanceType": "number" - }, - "depreciation": { - "yahooFinanceType": "number" - }, - "changeToNetincome": { - "yahooFinanceType": "number" - }, - "changeToAccountReceivables": { - "yahooFinanceType": "number" - }, - "changeToLiabilities": { - "yahooFinanceType": "number" - }, - "changeToInventory": { - "yahooFinanceType": "number" - }, - "changeToOperatingActivities": { - "yahooFinanceType": "number" - }, - "totalCashFromOperatingActivities": { - "yahooFinanceType": "number" - }, - "capitalExpenditures": { - "yahooFinanceType": "number" - }, - "investments": { - "yahooFinanceType": "number" - }, - "otherCashflowsFromInvestingActivities": { - "yahooFinanceType": "number" - }, - "totalCashflowsFromInvestingActivities": { - "yahooFinanceType": "number" - }, - "dividendsPaid": { - "yahooFinanceType": "number" - }, - "netBorrowings": { - "yahooFinanceType": "number" - }, - "otherCashflowsFromFinancingActivities": { - "yahooFinanceType": "number" - }, - "totalCashFromFinancingActivities": { - "yahooFinanceType": "number" - }, - "changeInCash": { - "yahooFinanceType": "number" - }, - "repurchaseOfStock": { - "yahooFinanceType": "number" - }, - "issuanceOfStock": { - "yahooFinanceType": "number" - }, - "effectOfExchangeRate": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "endDate", - "netIncome" - ] - }, - "DefaultKeyStatistics": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "enterpriseValue": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "profitMargins": { - "yahooFinanceType": "number" - }, - "floatShares": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "sharesShort": { - "yahooFinanceType": "number" - }, - "sharesShortPriorMonth": { - "yahooFinanceType": "date" - }, - "sharesShortPreviousMonthDate": { - "yahooFinanceType": "date" - }, - "dateShortInterest": { - "yahooFinanceType": "date" - }, - "sharesPercentSharesOut": { - "yahooFinanceType": "number" - }, - "heldPercentInsiders": { - "yahooFinanceType": "number" - }, - "heldPercentInstitutions": { - "yahooFinanceType": "number" - }, - "shortRatio": { - "yahooFinanceType": "number" - }, - "shortPercentOfFloat": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "impliedSharesOutstanding": { - "yahooFinanceType": "number" - }, - "category": { - "type": [ - "null", - "string" - ] - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "fundFamily": { - "type": [ - "null", - "string" - ] - }, - "legalType": { - "type": [ - "null", - "string" - ] - }, - "lastFiscalYearEnd": { - "yahooFinanceType": "date" - }, - "nextFiscalYearEnd": { - "yahooFinanceType": "date" - }, - "mostRecentQuarter": { - "yahooFinanceType": "date" - }, - "earningsQuarterlyGrowth": { - "yahooFinanceType": "number" - }, - "netIncomeToCommon": { - "yahooFinanceType": "number" - }, - "trailingEps": { - "yahooFinanceType": "number" - }, - "forwardEps": { - "yahooFinanceType": "number" - }, - "pegRatio": { - "yahooFinanceType": "number" - }, - "lastSplitFactor": { - "type": [ - "null", - "string" - ] - }, - "lastSplitDate": { - "yahooFinanceType": "number" - }, - "enterpriseToRevenue": { - "yahooFinanceType": "number" - }, - "enterpriseToEbitda": { - "yahooFinanceType": "number" - }, - "52WeekChange": { - "yahooFinanceType": "number" - }, - "SandP52WeekChange": { - "yahooFinanceType": "number" - }, - "lastDividendValue": { - "yahooFinanceType": "number" - }, - "lastDividendDate": { - "yahooFinanceType": "date" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "beta3Year": { - "yahooFinanceType": "number" - }, - "totalAssets": { - "yahooFinanceType": "number" - }, - "yield": { - "yahooFinanceType": "number" - }, - "fundInceptionDate": { - "yahooFinanceType": "date" - }, - "threeYearAverageReturn": { - "yahooFinanceType": "number" - }, - "fiveYearAverageReturn": { - "yahooFinanceType": "number" - }, - "morningStarOverallRating": { - "yahooFinanceType": "number" - }, - "morningStarRiskRating": { - "yahooFinanceType": "number" - }, - "annualReportExpenseRatio": { - "yahooFinanceType": "number" - }, - "lastCapGain": { - "yahooFinanceType": "number" - }, - "annualHoldingsTurnover": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "priceHint", - "category", - "fundFamily", - "legalType", - "lastSplitFactor" - ] - }, - "QuoteSummaryEarnings": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "earningsChart": { - "$ref": "#/definitions/EarningsChart" - }, - "financialsChart": { - "$ref": "#/definitions/FinancialsChart" - }, - "financialCurrency": { - "type": "string" - } - }, - "required": [ - "maxAge", - "earningsChart", - "financialsChart" - ] - }, - "EarningsChart": { - "type": "object", - "properties": { - "quarterly": { - "type": "array", - "items": { - "$ref": "#/definitions/EarningsChartQuarterly" - } - }, - "currentQuarterEstimate": { - "yahooFinanceType": "number" - }, - "currentQuarterEstimateDate": { - "type": "string" - }, - "currentQuarterEstimateYear": { - "yahooFinanceType": "number" - }, - "earningsDate": { - "type": "array", - "items": { - "yahooFinanceType": "date" - } - } - }, - "required": [ - "quarterly", - "earningsDate" - ] - }, - "EarningsChartQuarterly": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "actual": { - "yahooFinanceType": "number" - }, - "estimate": { - "yahooFinanceType": "number" - } - }, - "required": [ - "date", - "actual", - "estimate" - ] - }, - "FinancialsChart": { - "type": "object", - "properties": { - "yearly": { - "type": "array", - "items": { - "$ref": "#/definitions/Yearly" - } - }, - "quarterly": { - "type": "array", - "items": { - "$ref": "#/definitions/FinancialsChartQuarterly" - } - } - }, - "required": [ - "yearly", - "quarterly" - ] - }, - "Yearly": { - "type": "object", - "properties": { - "date": { - "yahooFinanceType": "number" - }, - "revenue": { - "yahooFinanceType": "number" - }, - "earnings": { - "yahooFinanceType": "number" - } - }, - "required": [ - "date", - "revenue", - "earnings" - ] - }, - "FinancialsChartQuarterly": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "revenue": { - "yahooFinanceType": "number" - }, - "earnings": { - "yahooFinanceType": "number" - } - }, - "required": [ - "date", - "revenue", - "earnings" - ] - }, - "EarningsHistory": { - "type": "object", - "properties": { - "history": { - "type": "array", - "items": { - "$ref": "#/definitions/EarningsHistoryHistory" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "history", - "maxAge" - ] - }, - "EarningsHistoryHistory": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "epsActual": { - "yahooFinanceType": "number|null" - }, - "epsEstimate": { - "yahooFinanceType": "number|null" - }, - "epsDifference": { - "yahooFinanceType": "number|null" - }, - "surprisePercent": { - "yahooFinanceType": "number|null" - }, - "quarter": { - "yahooFinanceType": "date|null" - }, - "period": { - "type": "string" - } - }, - "required": [ - "maxAge", - "epsActual", - "epsEstimate", - "epsDifference", - "surprisePercent", - "quarter", - "period" - ] - }, - "EarningsTrend": { - "type": "object", - "properties": { - "trend": { - "type": "array", - "items": { - "$ref": "#/definitions/EarningsTrendTrend" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "trend", - "maxAge" - ] - }, - "EarningsTrendTrend": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "period": { - "type": "string" - }, - "endDate": { - "yahooFinanceType": "date|null" - }, - "growth": { - "yahooFinanceType": "number|null" - }, - "earningsEstimate": { - "$ref": "#/definitions/EarningsEstimate" - }, - "revenueEstimate": { - "$ref": "#/definitions/RevenueEstimate" - }, - "epsTrend": { - "$ref": "#/definitions/EpsTrend" - }, - "epsRevisions": { - "$ref": "#/definitions/EpsRevisions" - } - }, - "required": [ - "maxAge", - "period", - "endDate", - "growth", - "earningsEstimate", - "revenueEstimate", - "epsTrend", - "epsRevisions" - ] - }, - "EarningsEstimate": { - "type": "object", - "properties": { - "avg": { - "yahooFinanceType": "number|null" - }, - "low": { - "yahooFinanceType": "number|null" - }, - "high": { - "yahooFinanceType": "number|null" - }, - "yearAgoEps": { - "yahooFinanceType": "number|null" - }, - "numberOfAnalysts": { - "yahooFinanceType": "number|null" - }, - "growth": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "avg", - "low", - "high", - "yearAgoEps", - "numberOfAnalysts", - "growth" - ] - }, - "RevenueEstimate": { - "type": "object", - "properties": { - "avg": { - "yahooFinanceType": "number|null" - }, - "low": { - "yahooFinanceType": "number|null" - }, - "high": { - "yahooFinanceType": "number|null" - }, - "numberOfAnalysts": { - "yahooFinanceType": "number|null" - }, - "yearAgoRevenue": { - "yahooFinanceType": "number|null" - }, - "growth": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "avg", - "low", - "high", - "numberOfAnalysts", - "yearAgoRevenue", - "growth" - ] - }, - "EpsTrend": { - "type": "object", - "properties": { - "current": { - "yahooFinanceType": "number|null" - }, - "7daysAgo": { - "yahooFinanceType": "number|null" - }, - "30daysAgo": { - "yahooFinanceType": "number|null" - }, - "60daysAgo": { - "yahooFinanceType": "number|null" - }, - "90daysAgo": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "current", - "7daysAgo", - "30daysAgo", - "60daysAgo", - "90daysAgo" - ] - }, - "EpsRevisions": { - "type": "object", - "properties": { - "upLast7days": { - "yahooFinanceType": "number|null" - }, - "upLast30days": { - "yahooFinanceType": "number|null" - }, - "downLast30days": { - "yahooFinanceType": "number|null" - }, - "downLast90days": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "upLast7days", - "upLast30days", - "downLast30days", - "downLast90days" - ] - }, - "FinancialData": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "currentPrice": { - "yahooFinanceType": "number" - }, - "targetHighPrice": { - "yahooFinanceType": "number" - }, - "targetLowPrice": { - "yahooFinanceType": "number" - }, - "targetMeanPrice": { - "yahooFinanceType": "number" - }, - "targetMedianPrice": { - "yahooFinanceType": "number" - }, - "recommendationMean": { - "yahooFinanceType": "number" - }, - "recommendationKey": { - "type": "string" - }, - "numberOfAnalystOpinions": { - "yahooFinanceType": "number" - }, - "totalCash": { - "yahooFinanceType": "number" - }, - "totalCashPerShare": { - "yahooFinanceType": "number" - }, - "ebitda": { - "yahooFinanceType": "number" - }, - "totalDebt": { - "yahooFinanceType": "number" - }, - "quickRatio": { - "yahooFinanceType": "number" - }, - "currentRatio": { - "yahooFinanceType": "number" - }, - "totalRevenue": { - "yahooFinanceType": "number" - }, - "debtToEquity": { - "yahooFinanceType": "number" - }, - "revenuePerShare": { - "yahooFinanceType": "number" - }, - "returnOnAssets": { - "yahooFinanceType": "number" - }, - "returnOnEquity": { - "yahooFinanceType": "number" - }, - "grossProfits": { - "yahooFinanceType": "number" - }, - "freeCashflow": { - "yahooFinanceType": "number" - }, - "operatingCashflow": { - "yahooFinanceType": "number" - }, - "earningsGrowth": { - "yahooFinanceType": "number" - }, - "revenueGrowth": { - "yahooFinanceType": "number" - }, - "grossMargins": { - "yahooFinanceType": "number" - }, - "ebitdaMargins": { - "yahooFinanceType": "number" - }, - "operatingMargins": { - "yahooFinanceType": "number" - }, - "profitMargins": { - "yahooFinanceType": "number" - }, - "financialCurrency": { - "type": [ - "string", - "null" - ] - } - }, - "required": [ - "maxAge", - "recommendationKey", - "financialCurrency" - ] - }, - "Ownership": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "ownershipList": { - "type": "array", - "items": { - "$ref": "#/definitions/OwnershipList" - } - } - }, - "required": [ - "maxAge", - "ownershipList" - ] - }, - "OwnershipList": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "reportDate": { - "yahooFinanceType": "date" - }, - "organization": { - "type": "string" - }, - "pctHeld": { - "yahooFinanceType": "number" - }, - "position": { - "yahooFinanceType": "number" - }, - "value": { - "yahooFinanceType": "number" - }, - "pctChange": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "reportDate", - "organization", - "pctHeld", - "position", - "value" - ] - }, - "FundPerformance": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "loadAdjustedReturns": { - "$ref": "#/definitions/PeriodRange" - }, - "rankInCategory": { - "$ref": "#/definitions/PeriodRange" - }, - "performanceOverview": { - "$ref": "#/definitions/FundPerformancePerformanceOverview" - }, - "performanceOverviewCat": { - "$ref": "#/definitions/FundPerformancePerformanceOverviewCat" - }, - "trailingReturns": { - "$ref": "#/definitions/FundPerformanceTrailingReturns" - }, - "trailingReturnsNav": { - "$ref": "#/definitions/FundPerformanceTrailingReturns" - }, - "trailingReturnsCat": { - "$ref": "#/definitions/FundPerformanceTrailingReturns" - }, - "annualTotalReturns": { - "$ref": "#/definitions/FundPerformanceReturns" - }, - "pastQuarterlyReturns": { - "$ref": "#/definitions/FundPerformanceReturns" - }, - "riskOverviewStatistics": { - "$ref": "#/definitions/FundPerformanceRiskOverviewStats" - }, - "riskOverviewStatisticsCat": { - "$ref": "#/definitions/FundPerformanceRiskOverviewStatsCat" - } - }, - "required": [ - "maxAge", - "performanceOverview", - "performanceOverviewCat", - "trailingReturns", - "trailingReturnsNav", - "trailingReturnsCat", - "annualTotalReturns", - "pastQuarterlyReturns", - "riskOverviewStatistics", - "riskOverviewStatisticsCat" - ] - }, - "PeriodRange": { - "type": "object", - "properties": { - "asOfDate": { - "yahooFinanceType": "date" - }, - "ytd": { - "yahooFinanceType": "number" - }, - "oneMonth": { - "yahooFinanceType": "number" - }, - "threeMonth": { - "yahooFinanceType": "number" - }, - "oneYear": { - "yahooFinanceType": "number" - }, - "threeYear": { - "yahooFinanceType": "number" - }, - "fiveYear": { - "yahooFinanceType": "number" - }, - "tenYear": { - "yahooFinanceType": "number" - } - } - }, - "FundPerformancePerformanceOverview": { - "type": "object", - "properties": { - "asOfDate": { - "yahooFinanceType": "date" - }, - "ytdReturnPct": { - "yahooFinanceType": "number" - }, - "oneYearTotalReturn": { - "yahooFinanceType": "number" - }, - "threeYearTotalReturn": { - "yahooFinanceType": "number" - }, - "fiveYrAvgReturnPct": { - "yahooFinanceType": "number" - }, - "morningStarReturnRating": { - "yahooFinanceType": "number" - }, - "numYearsUp": { - "yahooFinanceType": "number" - }, - "numYearsDown": { - "yahooFinanceType": "number" - }, - "bestOneYrTotalReturn": { - "yahooFinanceType": "number" - }, - "worstOneYrTotalReturn": { - "yahooFinanceType": "number" - }, - "bestThreeYrTotalReturn": { - "yahooFinanceType": "number" - }, - "worstThreeYrTotalReturn": { - "yahooFinanceType": "number" - } - } - }, - "FundPerformancePerformanceOverviewCat": { - "type": "object", - "properties": { - "ytdReturnPct": { - "yahooFinanceType": "number" - }, - "fiveYrAvgReturnPct": { - "yahooFinanceType": "number" - } - } - }, - "FundPerformanceTrailingReturns": { - "type": "object", - "properties": { - "asOfDate": { - "yahooFinanceType": "date" - }, - "ytd": { - "yahooFinanceType": "number" - }, - "oneMonth": { - "yahooFinanceType": "number" - }, - "threeMonth": { - "yahooFinanceType": "number" - }, - "oneYear": { - "yahooFinanceType": "number" - }, - "threeYear": { - "yahooFinanceType": "number" - }, - "fiveYear": { - "yahooFinanceType": "number" - }, - "tenYear": { - "yahooFinanceType": "number" - }, - "lastBullMkt": { - "yahooFinanceType": "number" - }, - "lastBearMkt": { - "yahooFinanceType": "number" - } - } - }, - "FundPerformanceReturns": { - "type": "object", - "properties": { - "returns": { - "type": "array", - "items": { - "$ref": "#/definitions/FundPerformanceReturnsRow" - } - }, - "returnsCat": { - "type": "array", - "items": { - "$ref": "#/definitions/FundPerformanceReturnsRow" - } - } - }, - "required": [ - "returns" - ] - }, - "FundPerformanceReturnsRow": { - "type": "object", - "properties": { - "year": { - "yahooFinanceType": "number" - }, - "annualValue": { - "yahooFinanceType": "number" - }, - "q1": { - "yahooFinanceType": "number" - }, - "q2": { - "yahooFinanceType": "number" - }, - "q3": { - "yahooFinanceType": "number" - }, - "q4": { - "yahooFinanceType": "number" - } - }, - "required": [ - "year" - ] - }, - "FundPerformanceRiskOverviewStats": { - "type": "object", - "properties": { - "riskStatistics": { - "type": "array", - "items": { - "$ref": "#/definitions/FundPerformanceRiskOverviewStatsRow" - } - }, - "riskRating": { - "yahooFinanceType": "number" - } - }, - "required": [ - "riskStatistics" - ] - }, - "FundPerformanceRiskOverviewStatsRow": { - "type": "object", - "properties": { - "year": { - "type": "string" - }, - "alpha": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "meanAnnualReturn": { - "yahooFinanceType": "number" - }, - "rSquared": { - "yahooFinanceType": "number" - }, - "stdDev": { - "yahooFinanceType": "number" - }, - "sharpeRatio": { - "yahooFinanceType": "number" - }, - "treynorRatio": { - "yahooFinanceType": "number" - } - }, - "required": [ - "year", - "alpha", - "beta", - "meanAnnualReturn", - "rSquared", - "sharpeRatio", - "treynorRatio" - ] - }, - "FundPerformanceRiskOverviewStatsCat": { - "type": "object", - "properties": { - "riskStatisticsCat": { - "type": "array", - "items": { - "$ref": "#/definitions/FundPerformanceRiskOverviewStatsRow" - } - } - }, - "required": [ - "riskStatisticsCat" - ] - }, - "FundProfile": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "styleBoxUrl": { - "type": [ - "null", - "string" - ] - }, - "family": { - "type": [ - "null", - "string" - ] - }, - "categoryName": { - "type": [ - "null", - "string" - ] - }, - "legalType": { - "type": [ - "null", - "string" - ] - }, - "managementInfo": { - "$ref": "#/definitions/FundProfileManagementInfo" - }, - "feesExpensesInvestment": { - "$ref": "#/definitions/FundProfileFeesExpensesInvestment" - }, - "feesExpensesInvestmentCat": { - "$ref": "#/definitions/FundProfileFeesExpensesInvestmentCat" - }, - "brokerages": { - "type": "array", - "items": { - "$ref": "#/definitions/FundProfileBrokerage" - } - }, - "initInvestment": { - "yahooFinanceType": "number" - }, - "initIraInvestment": { - "yahooFinanceType": "number" - }, - "initAipInvestment": { - "yahooFinanceType": "number" - }, - "subseqInvestment": { - "yahooFinanceType": "number" - }, - "subseqIraInvestment": { - "yahooFinanceType": "number" - }, - "subseqAipInvestment": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "family", - "categoryName", - "legalType" - ] - }, - "FundProfileManagementInfo": { - "type": "object", - "properties": { - "managerName": { - "type": [ - "null", - "string" - ] - }, - "managerBio": { - "type": [ - "null", - "string" - ] - }, - "startdate": { - "yahooFinanceType": "date" - } - }, - "required": [ - "managerName", - "managerBio" - ] - }, - "FundProfileFeesExpensesInvestment": { - "type": "object", - "properties": { - "annualHoldingsTurnover": { - "yahooFinanceType": "number" - }, - "annualReportExpenseRatio": { - "yahooFinanceType": "number" - }, - "grossExpRatio": { - "yahooFinanceType": "number" - }, - "netExpRatio": { - "yahooFinanceType": "number" - }, - "projectionValues": { - "type": "object" - }, - "totalNetAssets": { - "yahooFinanceType": "number" - } - }, - "required": [ - "projectionValues" - ] - }, - "FundProfileFeesExpensesInvestmentCat": { - "type": "object", - "properties": { - "annualHoldingsTurnover": { - "yahooFinanceType": "number" - }, - "annualReportExpenseRatio": { - "yahooFinanceType": "number" - }, - "grossExpRatio": { - "yahooFinanceType": "number" - }, - "netExpRatio": { - "yahooFinanceType": "number" - }, - "totalNetAssets": { - "yahooFinanceType": "number" - }, - "projectionValuesCat": { - "type": "object" - } - }, - "required": [ - "projectionValuesCat" - ] - }, - "FundProfileBrokerage": { - "type": "object", - "additionalProperties": false - }, - "IncomeStatementHistory": { - "type": "object", - "properties": { - "incomeStatementHistory": { - "type": "array", - "items": { - "$ref": "#/definitions/IncomeStatementHistoryElement" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "incomeStatementHistory", - "maxAge" - ] - }, - "IncomeStatementHistoryElement": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "endDate": { - "yahooFinanceType": "date" - }, - "totalRevenue": { - "yahooFinanceType": "number" - }, - "costOfRevenue": { - "yahooFinanceType": "number" - }, - "grossProfit": { - "yahooFinanceType": "number" - }, - "researchDevelopment": { - "yahooFinanceType": "number|null" - }, - "sellingGeneralAdministrative": { - "yahooFinanceType": "number|null" - }, - "nonRecurring": { - "yahooFinanceType": "number|null" - }, - "otherOperatingExpenses": { - "yahooFinanceType": "number|null" - }, - "totalOperatingExpenses": { - "yahooFinanceType": "number" - }, - "operatingIncome": { - "yahooFinanceType": "number|null" - }, - "totalOtherIncomeExpenseNet": { - "yahooFinanceType": "number|null" - }, - "ebit": { - "yahooFinanceType": "number" - }, - "interestExpense": { - "yahooFinanceType": "number|null" - }, - "incomeBeforeTax": { - "yahooFinanceType": "number|null" - }, - "incomeTaxExpense": { - "yahooFinanceType": "number" - }, - "minorityInterest": { - "yahooFinanceType": "number|null" - }, - "netIncomeFromContinuingOps": { - "yahooFinanceType": "number|null" - }, - "discontinuedOperations": { - "yahooFinanceType": "number|null" - }, - "extraordinaryItems": { - "yahooFinanceType": "number|null" - }, - "effectOfAccountingCharges": { - "yahooFinanceType": "number|null" - }, - "otherItems": { - "yahooFinanceType": "number|null" - }, - "netIncome": { - "yahooFinanceType": "number" - }, - "netIncomeApplicableToCommonShares": { - "yahooFinanceType": "number|null" - } - }, - "required": [ - "maxAge", - "endDate", - "totalRevenue", - "costOfRevenue", - "grossProfit", - "researchDevelopment", - "sellingGeneralAdministrative", - "nonRecurring", - "otherOperatingExpenses", - "totalOperatingExpenses", - "operatingIncome", - "totalOtherIncomeExpenseNet", - "ebit", - "interestExpense", - "incomeBeforeTax", - "incomeTaxExpense", - "minorityInterest", - "netIncomeFromContinuingOps", - "discontinuedOperations", - "extraordinaryItems", - "effectOfAccountingCharges", - "otherItems", - "netIncome", - "netIncomeApplicableToCommonShares" - ] - }, - "IndexTrend": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "symbol": { - "type": "string" - }, - "peRatio": { - "yahooFinanceType": "number" - }, - "pegRatio": { - "yahooFinanceType": "number" - }, - "estimates": { - "type": "array", - "items": { - "$ref": "#/definitions/Estimate" - } - } - }, - "required": [ - "maxAge", - "symbol", - "peRatio", - "pegRatio", - "estimates" - ] - }, - "Estimate": { - "type": "object", - "properties": { - "period": { - "type": "string" - }, - "growth": { - "yahooFinanceType": "number" - } - }, - "required": [ - "period" - ] - }, - "Trend": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "symbol": { - "type": "null" - }, - "estimates": { - "type": "array", - "items": {} - } - }, - "required": [ - "maxAge", - "symbol", - "estimates" - ] - }, - "Holders": { - "type": "object", - "properties": { - "holders": { - "type": "array", - "items": { - "$ref": "#/definitions/Holder" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "holders", - "maxAge" - ] - }, - "Holder": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "name": { - "type": "string" - }, - "relation": { - "anyOf": [ - { - "$ref": "#/definitions/Relation" - }, - { - "type": "string" - } - ] - }, - "url": { - "type": "string" - }, - "transactionDescription": { - "type": "string" - }, - "latestTransDate": { - "yahooFinanceType": "date" - }, - "positionDirect": { - "yahooFinanceType": "number" - }, - "positionDirectDate": { - "yahooFinanceType": "date" - }, - "positionIndirect": { - "yahooFinanceType": "number" - }, - "positionIndirectDate": { - "yahooFinanceType": "date" - }, - "positionSummaryDate": { - "yahooFinanceType": "date" - } - }, - "required": [ - "maxAge", - "name", - "relation", - "url", - "transactionDescription", - "latestTransDate" - ] - }, - "Relation": { - "type": "string", - "enum": [ - "Chairman of the Board", - "Chief Executive Officer", - "Chief Financial Officer", - "Chief Operating Officer", - "Chief Technology Officer", - "Director", - "Director (Independent)", - "", - "General Counsel", - "Independent Non-Executive Director", - "Officer", - "President" - ] - }, - "InsiderTransactions": { - "type": "object", - "properties": { - "transactions": { - "type": "array", - "items": { - "$ref": "#/definitions/Transaction" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "transactions", - "maxAge" - ] - }, - "Transaction": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "shares": { - "yahooFinanceType": "number" - }, - "filerUrl": { - "type": "string" - }, - "transactionText": { - "type": "string" - }, - "filerName": { - "type": "string" - }, - "filerRelation": { - "anyOf": [ - { - "$ref": "#/definitions/Relation" - }, - { - "type": "string" - } - ] - }, - "moneyText": { - "type": "string" - }, - "startDate": { - "yahooFinanceType": "date" - }, - "ownership": { - "anyOf": [ - { - "$ref": "#/definitions/OwnershipEnum" - }, - { - "type": "string" - } - ] - }, - "value": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "shares", - "filerUrl", - "transactionText", - "filerName", - "filerRelation", - "moneyText", - "startDate", - "ownership" - ] - }, - "OwnershipEnum": { - "type": "string", - "enum": [ - "D", - "I" - ] - }, - "MajorHoldersBreakdown": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "insidersPercentHeld": { - "yahooFinanceType": "number" - }, - "institutionsPercentHeld": { - "yahooFinanceType": "number" - }, - "institutionsFloatPercentHeld": { - "yahooFinanceType": "number" - }, - "institutionsCount": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge" - ] - }, - "NetSharePurchaseActivity": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "period": { - "type": "string" - }, - "buyInfoCount": { - "yahooFinanceType": "number" - }, - "buyInfoShares": { - "yahooFinanceType": "number" - }, - "buyPercentInsiderShares": { - "yahooFinanceType": "number" - }, - "sellInfoCount": { - "yahooFinanceType": "number" - }, - "sellInfoShares": { - "yahooFinanceType": "number" - }, - "sellPercentInsiderShares": { - "yahooFinanceType": "number" - }, - "netInfoCount": { - "yahooFinanceType": "number" - }, - "netInfoShares": { - "yahooFinanceType": "number" - }, - "netPercentInsiderShares": { - "yahooFinanceType": "number" - }, - "totalInsiderShares": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "period", - "buyInfoCount", - "buyInfoShares", - "sellInfoCount", - "netInfoCount", - "netInfoShares", - "totalInsiderShares" - ] - }, - "Price": { - "type": "object", - "properties": { - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "exchange": { - "type": "string" - }, - "exchangeName": { - "type": "string" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "maxAge": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "date" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketSource": { - "type": "string" - }, - "preMarketChangePercent": { - "yahooFinanceType": "number" - }, - "preMarketChange": { - "yahooFinanceType": "number" - }, - "preMarketTime": { - "yahooFinanceType": "date" - }, - "preMarketPrice": { - "yahooFinanceType": "number" - }, - "preMarketSource": { - "type": "string" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "date" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "regularMarketSource": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "quoteSourceName": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": [ - "null", - "string" - ] - }, - "shortName": { - "type": [ - "null", - "string" - ] - }, - "longName": { - "type": [ - "null", - "string" - ] - }, - "lastMarket": { - "type": [ - "null", - "string" - ] - }, - "marketState": { - "type": "string" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "currency": { - "type": "string" - }, - "currencySymbol": { - "type": "string" - }, - "fromCurrency": { - "type": [ - "string", - "null" - ] - }, - "toCurrency": { - "type": [ - "string", - "null" - ] - }, - "volume24Hr": { - "yahooFinanceType": "number" - }, - "volumeAllCurrencies": { - "yahooFinanceType": "number" - }, - "circulatingSupply": { - "yahooFinanceType": "number" - }, - "expireDate": { - "yahooFinanceType": "date" - }, - "openInterest": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "priceHint", - "quoteType", - "symbol", - "underlyingSymbol", - "shortName", - "longName", - "lastMarket", - "fromCurrency" - ] - }, - "QuoteType": { - "type": "object", - "properties": { - "exchange": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "symbol": { - "type": "string" - }, - "underlyingSymbol": { - "type": "string" - }, - "shortName": { - "type": [ - "null", - "string" - ] - }, - "longName": { - "type": [ - "null", - "string" - ] - }, - "firstTradeDateEpochUtc": { - "yahooFinanceType": "date|null" - }, - "timeZoneFullName": { - "type": "string" - }, - "timeZoneShortName": { - "type": "string" - }, - "uuid": { - "type": "string" - }, - "messageBoardId": { - "type": [ - "null", - "string" - ] - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "exchange", - "quoteType", - "symbol", - "underlyingSymbol", - "shortName", - "longName", - "firstTradeDateEpochUtc", - "timeZoneFullName", - "timeZoneShortName", - "uuid", - "gmtOffSetMilliseconds", - "maxAge" - ] - }, - "RecommendationTrend": { - "type": "object", - "properties": { - "trend": { - "type": "array", - "items": { - "$ref": "#/definitions/RecommendationTrendTrend" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "trend", - "maxAge" - ] - }, - "RecommendationTrendTrend": { - "type": "object", - "properties": { - "period": { - "type": "string" - }, - "strongBuy": { - "yahooFinanceType": "number" - }, - "buy": { - "yahooFinanceType": "number" - }, - "hold": { - "yahooFinanceType": "number" - }, - "sell": { - "yahooFinanceType": "number" - }, - "strongSell": { - "yahooFinanceType": "number" - } - }, - "required": [ - "period", - "strongBuy", - "buy", - "hold", - "sell", - "strongSell" - ] - }, - "SECFilings": { - "type": "object", - "properties": { - "filings": { - "type": "array", - "items": { - "$ref": "#/definitions/Filing" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "filings", - "maxAge" - ] - }, - "Filing": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "epochDate": { - "yahooFinanceType": "date" - }, - "type": { - "type": "string", - "enum": [ - "10-K", - "10-Q", - "8-K", - "8-K/A", - "10-K/A", - "10-Q/A", - "SD", - "PX14A6G", - "SC 13G/A", - "DEFA14A", - "25-NSE", - "S-8 POS", - "6-K", - "F-3ASR", - "SC 13D/A", - "20-F", - "425", - "SC14D9C", - "SC 13G", - "S-8", - "DEF 14A", - "F-10" - ] - }, - "title": { - "type": "string" - }, - "edgarUrl": { - "type": "string" - }, - "maxAge": { - "yahooFinanceType": "number" - }, - "url": { - "type": "string" - }, - "exhibits": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "url": { - "type": "string" - }, - "downloadUrl": { - "type": "string" - } - }, - "required": [ - "type", - "url" - ], - "additionalProperties": false - } - } - }, - "required": [ - "date", - "epochDate", - "type", - "title", - "edgarUrl", - "maxAge" - ] - }, - "SummaryDetail": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "previousClose": { - "yahooFinanceType": "number" - }, - "open": { - "yahooFinanceType": "number" - }, - "dayLow": { - "yahooFinanceType": "number" - }, - "dayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "dividendRate": { - "yahooFinanceType": "number" - }, - "dividendYield": { - "yahooFinanceType": "number" - }, - "exDividendDate": { - "yahooFinanceType": "date" - }, - "payoutRatio": { - "yahooFinanceType": "number" - }, - "fiveYearAvgDividendYield": { - "yahooFinanceType": "number" - }, - "beta": { - "yahooFinanceType": "number" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "volume": { - "yahooFinanceType": "number" - }, - "averageVolume": { - "yahooFinanceType": "number" - }, - "averageVolume10days": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "priceToSalesTrailing12Months": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "currency": { - "type": "string" - }, - "algorithm": { - "type": "null" - }, - "tradeable": { - "type": "boolean" - }, - "yield": { - "yahooFinanceType": "number" - }, - "totalAssets": { - "yahooFinanceType": "number" - }, - "navPrice": { - "yahooFinanceType": "number" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "fromCurrency": { - "type": [ - "string", - "null" - ] - }, - "toCurrency": { - "type": [ - "string", - "null" - ] - }, - "lastMarket": { - "type": [ - "string", - "null" - ] - }, - "volume24Hr": { - "yahooFinanceType": "number" - }, - "volumeAllCurrencies": { - "yahooFinanceType": "number" - }, - "circulatingSupply": { - "yahooFinanceType": "number" - }, - "startDate": { - "yahooFinanceType": "date" - }, - "coinMarketCapLink": { - "type": [ - "string", - "null" - ] - }, - "expireDate": { - "yahooFinanceType": "date" - }, - "openInterest": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "priceHint", - "currency", - "algorithm", - "tradeable", - "fromCurrency", - "lastMarket" - ] - }, - "SummaryProfile": { - "type": "object", - "properties": { - "address1": { - "type": "string" - }, - "address2": { - "type": "string" - }, - "address3": { - "type": "string" - }, - "city": { - "type": "string" - }, - "state": { - "type": "string" - }, - "zip": { - "type": "string" - }, - "country": { - "type": "string" - }, - "phone": { - "type": "string" - }, - "fax": { - "type": "string" - }, - "website": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "industryDisp": { - "type": "string" - }, - "sector": { - "type": "string" - }, - "sectorDisp": { - "type": "string" - }, - "longBusinessSummary": { - "type": "string" - }, - "fullTimeEmployees": { - "yahooFinanceType": "number" - }, - "companyOfficers": { - "type": "array", - "items": {} - }, - "maxAge": { - "yahooFinanceType": "number" - }, - "twitter": { - "type": "string" - }, - "name": { - "type": "string" - }, - "startDate": { - "yahooFinanceType": "date" - }, - "description": { - "type": "string" - } - }, - "required": [ - "companyOfficers", - "maxAge" - ] - }, - "TopHoldings": { - "type": "object", - "properties": { - "maxAge": { - "yahooFinanceType": "number" - }, - "stockPosition": { - "yahooFinanceType": "number" - }, - "bondPosition": { - "yahooFinanceType": "number" - }, - "holdings": { - "type": "array", - "items": { - "$ref": "#/definitions/TopHoldingsHolding" - } - }, - "equityHoldings": { - "$ref": "#/definitions/TopHoldingsEquityHoldings" - }, - "bondHoldings": { - "type": "object" - }, - "bondRatings": { - "type": "array", - "items": { - "$ref": "#/definitions/TopHoldingsBondRating" - } - }, - "sectorWeightings": { - "type": "array", - "items": { - "$ref": "#/definitions/TopHoldingsSectorWeighting" - } - }, - "cashPosition": { - "yahooFinanceType": "number" - }, - "otherPosition": { - "yahooFinanceType": "number" - }, - "preferredPosition": { - "yahooFinanceType": "number" - }, - "convertiblePosition": { - "yahooFinanceType": "number" - } - }, - "required": [ - "maxAge", - "holdings", - "equityHoldings", - "bondHoldings", - "bondRatings", - "sectorWeightings" - ] - }, - "TopHoldingsHolding": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "holdingName": { - "type": "string" - }, - "holdingPercent": { - "yahooFinanceType": "number" - } - }, - "required": [ - "symbol", - "holdingName", - "holdingPercent" - ] - }, - "TopHoldingsEquityHoldings": { - "type": "object", - "properties": { - "medianMarketCap": { - "yahooFinanceType": "number" - }, - "medianMarketCapCat": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "priceToBookCat": { - "yahooFinanceType": "number" - }, - "priceToCashflow": { - "yahooFinanceType": "number" - }, - "priceToCashflowCat": { - "yahooFinanceType": "number" - }, - "priceToEarnings": { - "yahooFinanceType": "number" - }, - "priceToEarningsCat": { - "yahooFinanceType": "number" - }, - "priceToSales": { - "yahooFinanceType": "number" - }, - "priceToSalesCat": { - "yahooFinanceType": "number" - }, - "threeYearEarningsGrowth": { - "yahooFinanceType": "number" - }, - "threeYearEarningsGrowthCat": { - "yahooFinanceType": "number" - } - }, - "required": [ - "priceToBook", - "priceToCashflow", - "priceToEarnings", - "priceToSales" - ] - }, - "TopHoldingsBondRating": { - "type": "object", - "properties": { - "a": { - "yahooFinanceType": "number" - }, - "aa": { - "yahooFinanceType": "number" - }, - "aaa": { - "yahooFinanceType": "number" - }, - "other": { - "yahooFinanceType": "number" - }, - "b": { - "yahooFinanceType": "number" - }, - "bb": { - "yahooFinanceType": "number" - }, - "bbb": { - "yahooFinanceType": "number" - }, - "below_b": { - "yahooFinanceType": "number" - }, - "us_government": { - "yahooFinanceType": "number" - } - } - }, - "TopHoldingsSectorWeighting": { - "type": "object", - "properties": { - "realestate": { - "yahooFinanceType": "number" - }, - "consumer_cyclical": { - "yahooFinanceType": "number" - }, - "basic_materials": { - "yahooFinanceType": "number" - }, - "consumer_defensive": { - "yahooFinanceType": "number" - }, - "technology": { - "yahooFinanceType": "number" - }, - "communication_services": { - "yahooFinanceType": "number" - }, - "financial_services": { - "yahooFinanceType": "number" - }, - "utilities": { - "yahooFinanceType": "number" - }, - "industrials": { - "yahooFinanceType": "number" - }, - "energy": { - "yahooFinanceType": "number" - }, - "healthcare": { - "yahooFinanceType": "number" - } - } - }, - "UpgradeDowngradeHistory": { - "type": "object", - "properties": { - "history": { - "type": "array", - "items": { - "$ref": "#/definitions/UpgradeDowngradeHistoryHistory" - } - }, - "maxAge": { - "yahooFinanceType": "number" - } - }, - "required": [ - "history", - "maxAge" - ] - }, - "UpgradeDowngradeHistoryHistory": { - "type": "object", - "properties": { - "epochGradeDate": { - "yahooFinanceType": "date" - }, - "firm": { - "type": "string" - }, - "toGrade": { - "$ref": "#/definitions/Grade" - }, - "fromGrade": { - "$ref": "#/definitions/Grade" - }, - "action": { - "$ref": "#/definitions/Action" - } - }, - "required": [ - "epochGradeDate", - "firm", - "toGrade", - "action" - ] - }, - "Grade": { - "type": "string", - "enum": [ - "Accumulate", - "Add", - "Average", - "Below Average", - "Buy", - "Conviction Buy", - "", - "Equal-Weight", - "Fair Value", - "Equal-weight", - "Long-term Buy", - "Hold", - "Long-Term Buy", - "Market Outperform", - "Market Perform", - "Mixed", - "Negative", - "Neutral", - "In-Line", - "Outperform", - "Overweight", - "Peer Perform", - "Perform", - "Positive", - "Reduce", - "Sector Outperform", - "Sector Perform", - "Sector Weight", - "Sell", - "Strong Buy", - "Top Pick", - "Underperform", - "Underperformer", - "Underweight", - "Trim", - "Above Average", - "In-line", - "Outperformer", - "OVerweight", - "Cautious", - "Market Weight", - "Sector Underperform", - "Market Underperform", - "Peer perform", - "Gradually Accumulate", - "Action List Buy", - "Performer", - "Sector Performer", - "Speculative Buy", - "Strong Sell", - "Speculative Hold", - "Not Rated", - "Hold Neutral", - "Developing", - "buy", - "HOld", - "Trading Sell", - "Tender", - "market perform", - "BUy" - ] - }, - "Action": { - "type": "string", - "enum": [ - "down", - "init", - "main", - "reit", - "up" - ] - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "symbol": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/QuoteSummaryOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "symbol" - ], - "additionalProperties": false - }, - "QuoteSummaryOptions": { - "type": "object", - "properties": { - "formatted": { - "type": "boolean" - }, - "modules": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/definitions/QuoteSummaryModules" - } - }, - { - "type": "string", - "const": "all" - } - ] - } - }, - "additionalProperties": false - }, - "QuoteSummaryModules": { - "type": "string", - "enum": [ - "assetProfile", - "balanceSheetHistory", - "balanceSheetHistoryQuarterly", - "calendarEvents", - "cashflowStatementHistory", - "cashflowStatementHistoryQuarterly", - "defaultKeyStatistics", - "earnings", - "earningsHistory", - "earningsTrend", - "financialData", - "fundOwnership", - "fundPerformance", - "fundProfile", - "incomeStatementHistory", - "incomeStatementHistoryQuarterly", - "indexTrend", - "industryTrend", - "insiderHolders", - "insiderTransactions", - "institutionOwnership", - "majorDirectHolders", - "majorHoldersBreakdown", - "netSharePurchaseActivity", - "price", - "quoteType", - "recommendationTrend", - "secFilings", - "sectorTrend", - "summaryDetail", - "summaryProfile", - "topHoldings", - "upgradeDowngradeHistory" - ] - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/RecommendationsBySymbolOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, - "RecommendationsBySymbolOptions": { - "type": "object", - "additionalProperties": false - }, - "RecommendationsBySymbolResponse": { - "type": "object", - "properties": { - "recommendedSymbols": { - "type": "array", - "items": { - "type": "object", - "properties": { - "score": { - "yahooFinanceType": "number" - }, - "symbol": { - "type": "string" - } - }, - "required": [ - "score", - "symbol" - ] - } - }, - "symbol": { - "type": "string" - } - }, - "required": [ - "recommendedSymbols", - "symbol" - ] - }, - "RecommendationsBySymbolResponseArray": { - "type": "array", - "items": { - "$ref": "#/definitions/RecommendationsBySymbolResponse" - } - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/ScreenerOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this" - ], - "additionalProperties": false - }, - "ScreenerOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "scrIds": { - "$ref": "#/definitions/PredefinedScreenerModules" - }, - "count": { - "yahooFinanceType": "number" - } - }, - "required": [ - "scrIds" - ], - "additionalProperties": false - }, - "PredefinedScreenerModules": { - "type": "string", - "enum": [ - "aggressive_small_caps", - "conservative_foreign_funds", - "day_gainers", - "day_losers", - "growth_technology_stocks", - "high_yield_bond", - "most_actives", - "most_shorted_stocks", - "portfolio_anchors", - "small_cap_gainers", - "solid_large_growth_funds", - "solid_midcap_growth_funds", - "top_mutual_funds", - "undervalued_growth_stocks", - "undervalued_large_caps" - ] - }, - "ScreenerResult": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "canonicalName": { - "type": "string" - }, - "criteriaMeta": { - "$ref": "#/definitions/ScreenerCriteriaMeta" - }, - "rawCriteria": { - "type": "string" - }, - "start": { - "yahooFinanceType": "number" - }, - "count": { - "yahooFinanceType": "number" - }, - "total": { - "yahooFinanceType": "number" - }, - "quotes": { - "type": "array", - "items": { - "$ref": "#/definitions/ScreenerQuote" - } - }, - "useRecords": { - "type": "boolean" - }, - "predefinedScr": { - "type": "boolean" - }, - "versionId": { - "yahooFinanceType": "number" - }, - "creationDate": { - "yahooFinanceType": "number" - }, - "lastUpdated": { - "yahooFinanceType": "number" - }, - "isPremium": { - "type": "boolean" - }, - "iconUrl": { - "type": "string" - } - }, - "required": [ - "id", - "title", - "description", - "canonicalName", - "criteriaMeta", - "rawCriteria", - "start", - "count", - "total", - "quotes", - "useRecords", - "predefinedScr", - "versionId", - "creationDate", - "lastUpdated", - "isPremium", - "iconUrl" - ], - "additionalProperties": false - }, - "ScreenerCriteriaMeta": { - "type": "object", - "properties": { - "size": { - "yahooFinanceType": "number" - }, - "offset": { - "yahooFinanceType": "number" - }, - "sortField": { - "type": "string" - }, - "sortType": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "criteria": { - "type": "array", - "items": { - "$ref": "#/definitions/ScreenerCriterum" - } - }, - "topOperator": { - "type": "string" - } - }, - "required": [ - "size", - "offset", - "sortField", - "sortType", - "quoteType", - "criteria", - "topOperator" - ], - "additionalProperties": false - }, - "ScreenerCriterum": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "operators": { - "type": "array", - "items": { - "type": "string" - } - }, - "values": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "labelsSelected": { - "type": "array", - "items": { - "yahooFinanceType": "number" - } - }, - "dependentValues": { - "type": "array", - "items": {} - } - }, - "required": [ - "field", - "operators", - "values", - "labelsSelected", - "dependentValues" - ], - "additionalProperties": false - }, - "ScreenerQuote": { - "type": "object", - "properties": { - "language": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quoteType": { - "type": "string" - }, - "typeDisp": { - "type": "string" - }, - "quoteSourceName": { - "type": "string" - }, - "triggerable": { - "type": "boolean" - }, - "customPriceAlertConfidence": { - "type": "string" - }, - "lastCloseTevEbitLtm": { - "yahooFinanceType": "number" - }, - "lastClosePriceToNNWCPerShare": { - "yahooFinanceType": "number" - }, - "firstTradeDateMilliseconds": { - "yahooFinanceType": "number" - }, - "priceHint": { - "yahooFinanceType": "number" - }, - "postMarketChangePercent": { - "yahooFinanceType": "number" - }, - "postMarketTime": { - "yahooFinanceType": "number" - }, - "postMarketPrice": { - "yahooFinanceType": "number" - }, - "postMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketChange": { - "yahooFinanceType": "number" - }, - "regularMarketTime": { - "yahooFinanceType": "number" - }, - "regularMarketPrice": { - "yahooFinanceType": "number" - }, - "regularMarketDayHigh": { - "yahooFinanceType": "number" - }, - "regularMarketDayRange": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "regularMarketDayLow": { - "yahooFinanceType": "number" - }, - "regularMarketVolume": { - "yahooFinanceType": "number" - }, - "regularMarketPreviousClose": { - "yahooFinanceType": "number" - }, - "bid": { - "yahooFinanceType": "number" - }, - "ask": { - "yahooFinanceType": "number" - }, - "bidSize": { - "yahooFinanceType": "number" - }, - "askSize": { - "yahooFinanceType": "number" - }, - "market": { - "type": "string" - }, - "messageBoardId": { - "type": "string" - }, - "fullExchangeName": { - "type": "string" - }, - "longName": { - "type": "string" - }, - "financialCurrency": { - "type": "string" - }, - "regularMarketOpen": { - "yahooFinanceType": "number" - }, - "averageDailyVolume3Month": { - "yahooFinanceType": "number" - }, - "averageDailyVolume10Day": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekLowChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekRange": { - "type": "string" - }, - "fiftyTwoWeekHighChange": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHighChangePercent": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekChangePercent": { - "yahooFinanceType": "number" - }, - "earningsTimestamp": { - "yahooFinanceType": "number" - }, - "earningsTimestampStart": { - "yahooFinanceType": "number" - }, - "earningsTimestampEnd": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendRate": { - "yahooFinanceType": "number" - }, - "trailingAnnualDividendYield": { - "yahooFinanceType": "number" - }, - "marketState": { - "type": "string" - }, - "epsTrailingTwelveMonths": { - "yahooFinanceType": "number" - }, - "epsForward": { - "yahooFinanceType": "number" - }, - "epsCurrentYear": { - "yahooFinanceType": "number" - }, - "priceEpsCurrentYear": { - "yahooFinanceType": "number" - }, - "sharesOutstanding": { - "yahooFinanceType": "number" - }, - "bookValue": { - "yahooFinanceType": "number" - }, - "fiftyDayAverage": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChange": { - "yahooFinanceType": "number" - }, - "fiftyDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverage": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChange": { - "yahooFinanceType": "number" - }, - "twoHundredDayAverageChangePercent": { - "yahooFinanceType": "number" - }, - "marketCap": { - "yahooFinanceType": "number" - }, - "forwardPE": { - "yahooFinanceType": "number" - }, - "priceToBook": { - "yahooFinanceType": "number" - }, - "sourceInterval": { - "yahooFinanceType": "number" - }, - "exchangeDataDelayedBy": { - "yahooFinanceType": "number" - }, - "exchangeTimezoneName": { - "type": "string" - }, - "exchangeTimezoneShortName": { - "type": "string" - }, - "gmtOffSetMilliseconds": { - "yahooFinanceType": "number" - }, - "esgPopulated": { - "type": "boolean" - }, - "tradeable": { - "type": "boolean" - }, - "cryptoTradeable": { - "type": "boolean" - }, - "exchange": { - "type": "string" - }, - "fiftyTwoWeekLow": { - "yahooFinanceType": "number" - }, - "fiftyTwoWeekHigh": { - "yahooFinanceType": "number" - }, - "shortName": { - "type": "string" - }, - "averageAnalystRating": { - "type": "string" - }, - "regularMarketChangePercent": { - "yahooFinanceType": "number" - }, - "symbol": { - "type": "string" - }, - "dividendDate": { - "yahooFinanceType": "number" - }, - "displayName": { - "type": "string" - }, - "trailingPE": { - "yahooFinanceType": "number" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "number" - }, - "ipoExpectedDate": { - "yahooFinanceType": "number" - }, - "dividendYield": { - "yahooFinanceType": "number" - }, - "dividendRate": { - "yahooFinanceType": "number" - }, - "yieldTTM": { - "yahooFinanceType": "number" - }, - "peTTM": { - "yahooFinanceType": "number" - }, - "annualReturnNavY3": { - "yahooFinanceType": "number" - }, - "annualReturnNavY5": { - "yahooFinanceType": "number" - }, - "ytdReturn": { - "yahooFinanceType": "number" - }, - "trailingThreeMonthReturns": { - "yahooFinanceType": "number" - }, - "netAssets": { - "yahooFinanceType": "number" - }, - "netExpenseRatio": { - "yahooFinanceType": "number" - } - }, - "required": [ - "language", - "region", - "quoteType", - "typeDisp", - "quoteSourceName", - "triggerable", - "customPriceAlertConfidence", - "firstTradeDateMilliseconds", - "priceHint", - "regularMarketChange", - "regularMarketTime", - "regularMarketPrice", - "currency", - "regularMarketPreviousClose", - "market", - "messageBoardId", - "fullExchangeName", - "longName", - "averageDailyVolume3Month", - "averageDailyVolume10Day", - "fiftyTwoWeekLowChange", - "fiftyTwoWeekLowChangePercent", - "fiftyTwoWeekRange", - "fiftyTwoWeekHighChange", - "fiftyTwoWeekHighChangePercent", - "fiftyTwoWeekChangePercent", - "marketState", - "fiftyDayAverage", - "fiftyDayAverageChange", - "fiftyDayAverageChangePercent", - "twoHundredDayAverage", - "twoHundredDayAverageChange", - "twoHundredDayAverageChangePercent", - "sourceInterval", - "exchangeDataDelayedBy", - "exchangeTimezoneName", - "exchangeTimezoneShortName", - "gmtOffSetMilliseconds", - "esgPopulated", - "tradeable", - "cryptoTradeable", - "exchange", - "fiftyTwoWeekLow", - "fiftyTwoWeekHigh", - "shortName", - "regularMarketChangePercent", - "symbol" - ], - "additionalProperties": false - }, - "NamedParameters": { - "type": "object", - "properties": { - "this": { - "$ref": "#/definitions/ModuleThis" - }, - "query": { - "type": "string" - }, - "queryOptionsOverrides": { - "$ref": "#/definitions/SearchOptions" - }, - "moduleOptions": { - "$ref": "#/definitions/ModuleOptions" - } - }, - "required": [ - "this", - "query" - ], - "additionalProperties": false - }, - "SearchOptions": { - "type": "object", - "properties": { - "lang": { - "type": "string" - }, - "region": { - "type": "string" - }, - "quotesCount": { - "yahooFinanceType": "number" - }, - "newsCount": { - "yahooFinanceType": "number" - }, - "enableFuzzyQuery": { - "type": "boolean" - }, - "quotesQueryId": { - "type": "string" - }, - "multiQuoteQueryId": { - "type": "string" - }, - "newsQueryId": { - "type": "string" - }, - "enableCb": { - "type": "boolean" - }, - "enableNavLinks": { - "type": "boolean" - }, - "enableEnhancedTrivialQuery": { - "type": "boolean" - } - }, - "additionalProperties": false - }, - "SearchQuoteYahoo": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - } - }, - "required": [ - "symbol", - "isYahooFinance", - "exchange", - "index", - "score" - ] - }, - "SearchQuoteYahooEquity": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "EQUITY" - }, - "typeDisp": { - "type": "string", - "const": "Equity" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooOption": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "OPTION" - }, - "typeDisp": { - "type": "string", - "const": "Option" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooETF": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "ETF" - }, - "typeDisp": { - "type": "string", - "const": "ETF" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooFund": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "MUTUALFUND" - }, - "typeDisp": { - "type": "string", - "const": "Fund" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooIndex": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "INDEX" - }, - "typeDisp": { - "type": "string", - "const": "Index" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooCurrency": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "CURRENCY" - }, - "typeDisp": { - "type": "string", - "const": "Currency" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooCryptocurrency": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "CRYPTOCURRENCY" - }, - "typeDisp": { - "type": "string", - "const": "Cryptocurrency" - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteYahooFuture": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": true - }, - "exchange": { - "type": "string" - }, - "exchDisp": { - "type": "string" - }, - "shortname": { - "type": "string" - }, - "longname": { - "type": "string" - }, - "index": { - "type": "string", - "const": "quotes" - }, - "score": { - "yahooFinanceType": "number" - }, - "newListingDate": { - "yahooFinanceType": "date" - }, - "prevName": { - "type": "string" - }, - "nameChangeDate": { - "yahooFinanceType": "date" - }, - "sector": { - "type": "string" - }, - "industry": { - "type": "string" - }, - "dispSecIndFlag": { - "type": "boolean" - }, - "quoteType": { - "type": "string", - "const": "FUTURE" - }, - "typeDisp": { - "type": "string", - "enum": [ - "Future", - "Futures" - ] - } - }, - "required": [ - "exchange", - "index", - "isYahooFinance", - "quoteType", - "score", - "symbol", - "typeDisp" - ] - }, - "SearchQuoteNonYahoo": { - "type": "object", - "properties": { - "index": { - "type": "string" - }, - "name": { - "type": "string" - }, - "permalink": { - "type": "string" - }, - "isYahooFinance": { - "type": "boolean", - "const": false - } - }, - "required": [ - "index", - "name", - "permalink", - "isYahooFinance" - ] - }, - "SearchNews": { - "type": "object", - "properties": { - "uuid": { - "type": "string" - }, - "title": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "link": { - "type": "string" - }, - "providerPublishTime": { - "yahooFinanceType": "date" - }, - "type": { - "type": "string" - }, - "thumbnail": { - "type": "object", - "properties": { - "resolutions": { - "type": "array", - "items": { - "$ref": "#/definitions/SearchNewsThumbnailResolution" - } - } - }, - "required": [ - "resolutions" - ], - "additionalProperties": false - }, - "relatedTickers": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "uuid", - "title", - "publisher", - "link", - "providerPublishTime", - "type" - ] - }, - "SearchNewsThumbnailResolution": { - "type": "object", - "properties": { - "url": { - "type": "string" - }, - "width": { - "yahooFinanceType": "number" - }, - "height": { - "yahooFinanceType": "number" - }, - "tag": { - "type": "string" - } - }, - "required": [ - "url", - "width", - "height", - "tag" - ], - "additionalProperties": false - }, - "SearchResult": { - "type": "object", - "properties": { - "explains": { - "type": "array", - "items": {} - }, - "count": { - "yahooFinanceType": "number" - }, - "quotes": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/SearchQuoteYahooEquity" - }, - { - "$ref": "#/definitions/SearchQuoteYahooOption" - }, - { - "$ref": "#/definitions/SearchQuoteYahooETF" - }, - { - "$ref": "#/definitions/SearchQuoteYahooFund" - }, - { - "$ref": "#/definitions/SearchQuoteYahooIndex" - }, - { - "$ref": "#/definitions/SearchQuoteYahooCurrency" - }, - { - "$ref": "#/definitions/SearchQuoteYahooCryptocurrency" - }, - { - "$ref": "#/definitions/SearchQuoteNonYahoo" - }, - { - "$ref": "#/definitions/SearchQuoteYahooFuture" - } - ] - } - }, - "news": { - "type": "array", - "items": { - "$ref": "#/definitions/SearchNews" - } - }, - "nav": { - "type": "array", - "items": {} - }, - "lists": { - "type": "array", - "items": {} - }, - "researchReports": { - "type": "array", - "items": {} - }, - "totalTime": { - "yahooFinanceType": "number" - }, - "screenerFieldResults": { - "type": "array", - "items": {} - }, - "culturalAssets": { - "type": "array", - "items": {} - }, - "timeTakenForQuotes": { - "yahooFinanceType": "number" - }, - "timeTakenForNews": { - "yahooFinanceType": "number" - }, - "timeTakenForAlgowatchlist": { - "yahooFinanceType": "number" - }, - "timeTakenForPredefinedScreener": { - "yahooFinanceType": "number" - }, - "timeTakenForCrunchbase": { - "yahooFinanceType": "number" - }, - "timeTakenForNav": { - "yahooFinanceType": "number" - }, - "timeTakenForResearchReports": { - "yahooFinanceType": "number" - }, - "timeTakenForScreenerField": { - "yahooFinanceType": "number" - }, - "timeTakenForCulturalAssets": { - "yahooFinanceType": "number" - } - }, - "required": [ - "explains", - "count", - "quotes", - "news", - "nav", - "lists", - "researchReports", - "totalTime", - "timeTakenForQuotes", - "timeTakenForNews", - "timeTakenForAlgowatchlist", - "timeTakenForPredefinedScreener", - "timeTakenForCrunchbase", - "timeTakenForNav", - "timeTakenForResearchReports" - ] - }, - "TrendingSymbol": { - "type": "object", - "properties": { - "symbol": { - "type": "string" - } - }, - "required": [ - "symbol" - ] - }, - "TrendingSymbolsResult": { - "type": "object", - "properties": { - "count": { - "yahooFinanceType": "number" - }, - "quotes": { - "type": "array", - "items": { - "$ref": "#/definitions/TrendingSymbol" - } - }, - "jobTimestamp": { - "yahooFinanceType": "number" - }, - "startInterval": { - "yahooFinanceType": "number" - } - }, - "required": [ - "count", - "quotes", - "jobTimestamp", - "startInterval" - ] - } - } -} \ No newline at end of file diff --git a/scripts/json-transform.sh b/scripts/json-transform.sh deleted file mode 100755 index 2d3f3e50..00000000 --- a/scripts/json-transform.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -echo "json-transform.sh" -echo "=================" -echo - -echo "ESM" -echo "---" - -cd dist/esm - -for FILE in $(find -name \*.json) ; do - echo "Converting \"$FILE\" to ESM module \"$FILE.js\""; - echo -n "export default " > $FILE.js - cat $FILE >> $FILE.js -done - -for FILE in $(find -name \*js) ; do - # Lines that start with 'import' and end with '.json";'. - if grep -qE '^(import .*)\.json";$' $FILE; then - echo - echo "Modifying imports in \"$FILE\" (backup saved to .orig)" - grep -E '^(import .*)\.json";$' $FILE - sed -Ei.orig 's/^(import .*)\.json";$/\1.json.js";/' $FILE - fi -done - -echo -echo "CJS" -echo "---" - -cd ../cjs - -for FILE in $(find -name \*.json) ; do - echo "Converting \"$FILE\" to ESM module \"$FILE.js\""; - echo -n "module.exports = " > $FILE.js - cat $FILE >> $FILE.js -done - -for FILE in $(find -name \*js) ; do - # Lines that start with 'import' and end with '.json";'. - if grep -qE '^(const .*)\.json"\)\);$' $FILE; then - echo - echo "Modifying imports in \"$FILE\" (backup saved to .orig)" - grep -E '^(const .*)\.json"\)\);$' $FILE - sed -Ei.orig 's/^(const .*)\.json"\)\);$/\1.json.js"));/' $FILE - fi -done - -echo -echo "json-transform.sh finished." diff --git a/scripts/schema-check.sh b/scripts/schema-check.sh deleted file mode 100755 index ac7750e2..00000000 --- a/scripts/schema-check.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -cp schema.json schema.json.bak -yarn schema - -MD5SUM_OLD=$(md5sum schema.json.bak | awk '{print $1}') -MD5SUM_NEW=$(md5sum schema.json | awk '{print $1}') - -if [ "$MD5SUM_OLD" = "$MD5SUM_NEW" ] ; then - echo "[OK] schema.json is up to date - good to go!"; -else - echo "[FAIL] Please commit updated schema.json."; - exit 1; -fi diff --git a/scripts/schema.ts b/scripts/schema.ts deleted file mode 100644 index 8b2d5bd9..00000000 --- a/scripts/schema.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { writeFile } from "fs"; - -import { - createProgram, - createParser, - SchemaGenerator, - createFormatter, - Config, -} from "ts-json-schema-generator"; - -// @ts-expect-error: no types -import schemaWalker from "oas-schema-walker"; -import walkerCallback from "./schema/postWalker.js"; - -import yfNumberTypeFormatter from "./schema/TypeFormatter/yfNumberTypeFormatter.js"; -import yfReferenceTypeFormatter from "./schema/TypeFormatter/yfReferenceTypeFormatter.js"; -import yfFunctionIgnorer from "./schema/TypeFormatter/yfFunctionIgnorer.js"; - -//const OUTPUT_PATH = "schema.json"; -const OUTPUT_PATH = process.stdout; - -const config: Config = { - path: "src/{modules/**/!(*spec.ts),lib/options.ts}", - tsconfig: "tsconfig.json", - type: "*", -}; - -const formatter = createFormatter( - config, - (chainTypeFormatter, circularReferenceTypeFormatter) => { - chainTypeFormatter - .addTypeFormatter( - new yfReferenceTypeFormatter( - circularReferenceTypeFormatter, - config.encodeRefs ?? true - ) - ) - .addTypeFormatter(new yfNumberTypeFormatter()) - .addTypeFormatter(new yfFunctionIgnorer()); - } -); - -const program = createProgram(config); -const parser = createParser(program, config); -const generator = new SchemaGenerator(program, parser, formatter, config); -const _schema = generator.createSchema(config.type); - -const schema = { - $schema: _schema.$schema, - $comment: - "DO NOT EDIT THIS FILE. It is generated automatically " + - "from typescript interfaces in the project. To update, run " + - "`yarn schema`.", - ..._schema, -}; - -// @ts-expect-error: no types -for (const key of Object.keys(schema.definitions)) { - // @ts-expect-error: no types - schemaWalker.walkSchema(schema.definitions[key], {}, {}, walkerCallback); -} - -const schemaString = JSON.stringify(schema, null, 2); - -function throwErr(err?: Error | null): void { - if (err) throw err; -} - -if (OUTPUT_PATH === process.stdout) { - process.stdout.write(schemaString, throwErr); -} else if (typeof OUTPUT_PATH === "string") { - writeFile(OUTPUT_PATH, schemaString, throwErr); -} else { - throw new Error("Unsupported output path"); -} diff --git a/scripts/schema/TypeFormatter/yfFunctionIgnorer.ts b/scripts/schema/TypeFormatter/yfFunctionIgnorer.ts deleted file mode 100644 index 8dc04f65..00000000 --- a/scripts/schema/TypeFormatter/yfFunctionIgnorer.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - BaseType, - FunctionType, - Definition, - SubTypeFormatter, -} from "ts-json-schema-generator"; - -export default class yfNumberTypeFormatter implements SubTypeFormatter { - public supportsType(type: FunctionType): boolean { - return type instanceof FunctionType; - } - - public getDefinition(): Definition { - return {}; - } - - public getChildren(): BaseType[] { - return []; - } -} diff --git a/scripts/schema/TypeFormatter/yfNumberTypeFormatter.ts b/scripts/schema/TypeFormatter/yfNumberTypeFormatter.ts deleted file mode 100644 index d87a851f..00000000 --- a/scripts/schema/TypeFormatter/yfNumberTypeFormatter.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - BaseType, - Definition, - NumberType, - SubTypeFormatter, -} from "ts-json-schema-generator"; - -export default class yfNumberTypeFormatter implements SubTypeFormatter { - public supportsType(type: NumberType): boolean { - return type instanceof NumberType; - } - - public getDefinition(_type: NumberType): Definition { - return { - // @ts-ignore - yahooFinanceType: "number", - }; - } - - public getChildren(_type: NumberType): BaseType[] { - return []; - } -} diff --git a/scripts/schema/TypeFormatter/yfReferenceTypeFormatter.ts b/scripts/schema/TypeFormatter/yfReferenceTypeFormatter.ts deleted file mode 100644 index 7c44ac64..00000000 --- a/scripts/schema/TypeFormatter/yfReferenceTypeFormatter.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - Definition, - DefinitionTypeFormatter, - DefinitionType, -} from "ts-json-schema-generator"; - -export default class yfReferenceTypeFormatter extends DefinitionTypeFormatter { - public getDefinition(type: DefinitionType): Definition { - const ref = type.getName(); - - const types = ["TwoNumberRange", "DateInMs"]; - - if (types.includes(ref)) - return { - // @ts-ignore - yahooFinanceType: ref, - }; - else return super.getDefinition(type); - } -} diff --git a/scripts/schema/postWalker.js b/scripts/schema/postWalker.js deleted file mode 100644 index 4a9b207b..00000000 --- a/scripts/schema/postWalker.js +++ /dev/null @@ -1,74 +0,0 @@ -const isDate = (schema) => - schema.type === "string" && schema.format === "date-time"; - -const funcs = [ - /* - - // Now handled in TypeFormatter/yfReferenceTypeFormatter.ts - function TwoNumberRange(schema, parent, state) { - if (schema.$ref === "#/definitions/TwoNumberRange") { - const key = state.property.split("/").pop(); - parent.properties[key] = { yahooFinanceType: "TwoNumberRange" }; - return true; - } - }, - - // Now handled in TypeFormatter/yfReferenceTypeFormatter.ts - function DateInMs(schema, parent, state) { - if (schema.$ref === "#/definitions/DateInMs") { - const key = state.property.split("/").pop(); - parent.properties[key] = { yahooFinanceType: "DateInMs" }; - return true; - } - }, - - // Now handled in TypeFormatter/yfNumberTypeFormatter.ts - function Number(schema, parent, state) { - if (schema.type === "number") { - delete schema.type; - schema.yahooFinanceType = "number"; - return true; - } - }, - - */ - - function NumberNull(schema, parent, state) { - if (Array.isArray(schema.type)) { - if ( - schema.type.length === 2 && - schema.type.includes("number") && - schema.type.includes("null") - ) { - delete schema.type; - schema.yahooFinanceType = "number|null"; - return true; - } - } - }, - - function Date(schema, parent, state) { - const anyOf = schema.anyOf; - if ( - anyOf && - anyOf.length === 2 && - ((isDate(anyOf[0]) && anyOf[1].type === "null") || - (isDate(anyOf[1]) && anyOf[0].type === "null")) - ) { - delete schema.anyOf; - schema.yahooFinanceType = "date|null"; - return true; - } - - if (isDate(schema)) { - delete schema.format; - delete schema.type; - schema.yahooFinanceType = "date"; - return true; - } - }, -]; - -export default function callback(schema, parent, state) { - for (let func of funcs) if (func(schema, parent, state)) break; -} diff --git a/src/index-common.ts b/src/index-common.ts index 0cbc8bde..e0696154 100644 --- a/src/index-common.ts +++ b/src/index-common.ts @@ -1,10 +1,9 @@ // libs import yahooFinanceFetch from "./lib/yahooFinanceFetch.js"; -import moduleExec from "./lib/moduleExec.js"; import options from "./lib/options.js"; import errors from "./lib/errors.js"; import setGlobalConfig from "./lib/setGlobalConfig.js"; -import { disallowAdditionalProps } from "./lib/validateAndCoerceTypes.js"; +import moduleExec from "./lib/moduleExec.js"; // modules import autoc from "./modules/autoc.js"; @@ -30,7 +29,6 @@ export default { _fetch: yahooFinanceFetch, _moduleExec: moduleExec, _opts: options, - _disallowAdditionalProps: disallowAdditionalProps, // common errors, diff --git a/src/lib/errors.ts b/src/lib/errors.ts index 91a8b663..89f1f26c 100644 --- a/src/lib/errors.ts +++ b/src/lib/errors.ts @@ -1,10 +1,15 @@ -import type { ErrorObject } from "ajv/dist/types"; +import { + TransformDecodeError, + TransformDecodeCheckError, +} from "@sinclair/typebox/build/cjs/value"; // Yahoo's servers returned an HTTP 400 for this request. export class BadRequestError extends Error { name = "BadRequestError"; } +type ValidationError = TransformDecodeError | TransformDecodeCheckError; + // Yahoo's servers returned a 'not-ok' status for this request. // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok export class HTTPError extends Error { @@ -24,11 +29,11 @@ export class NoEnvironmentError extends Error { export class FailedYahooValidationError extends Error { name = "FailedYahooValidationError"; result: any; - errors?: null | ErrorObject[]; + errors?: null | ValidationError[]; constructor( message: string, - { result, errors }: { result: any; errors?: null | ErrorObject[] } + { result, errors }: { result: any; errors?: null | ValidationError[] } ) { super(message); this.result = result; diff --git a/src/lib/moduleCommon.ts b/src/lib/moduleCommon.ts index e5b18f9a..9eb99dae 100644 --- a/src/lib/moduleCommon.ts +++ b/src/lib/moduleCommon.ts @@ -1,5 +1,4 @@ -//import ModuleExec from "./moduleExec.js"; -import moduleExecTypebox from "./moduleExecTypebox"; +import moduleExec from "./moduleExec"; export interface ModuleOptions { validateResult?: boolean; @@ -17,8 +16,5 @@ export interface ModuleOptionsWithValidateTrue extends ModuleOptions { export interface ModuleThis { [key: string]: any; - // TODO: should be ModuleExec function but requiring functions breaks - // schema generation because json-schema does not support functions. - _moduleExec: any; - _moduleExecTypebox: typeof moduleExecTypebox; + _moduleExec: typeof moduleExec; } diff --git a/src/lib/moduleExec.spec.ts b/src/lib/moduleExec.spec.ts index 3e3a29f8..5343f4cd 100644 --- a/src/lib/moduleExec.spec.ts +++ b/src/lib/moduleExec.spec.ts @@ -2,12 +2,13 @@ import { jest } from "@jest/globals"; import search from "../modules/search.js"; import chart from "../modules/chart.js"; -import { InvalidOptionsError } from "./errors.js"; +import { FailedYahooValidationError, InvalidOptionsError } from "./errors.js"; import testYf from "../../tests/testYf.js"; -import { TransformDecodeCheckError } from "@sinclair/typebox/value"; +import { Type } from "@sinclair/typebox"; const yf = testYf({ search, chart }); yf._opts.validation.logOptionsErrors = false; +yf._opts.validation.logErrors = false; describe("moduleExecTypebox", () => { describe("assertSymbol", () => { @@ -69,7 +70,6 @@ describe("moduleExecTypebox", () => { const rwo = (options: any) => yf.search("symbol", options); await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); console = realConsole; - expect( fakeConsole.log.mock.calls.length + fakeConsole.error.mock.calls.length + @@ -84,7 +84,7 @@ describe("moduleExecTypebox", () => { yf._opts.validation.logErrors = false; await expect( yf.search("AAPL", {}, { devel: "search-badResult.fake.json" }) - ).rejects.toThrow(TransformDecodeCheckError); + ).rejects.toThrow(FailedYahooValidationError); yf._opts.validation.logErrors = true; }); @@ -113,4 +113,113 @@ describe("moduleExecTypebox", () => { expect(fakeConsole.dir).not.toHaveBeenCalled(); }); }); + describe("correctly invokes callbacks when provided", () => { + it("Should invoke the query options transformWith function when one is provided", async () => { + const yf = testYf({ _fetch: jest.fn() }); + const overrides = { overrideKey: "thingy" }; + const optionsTransformWith = jest.fn((v: Record) => ({ + ...v, + overrideKey: "bobby", + })); + + await yf._moduleExec({ + query: { + transformWith: optionsTransformWith, + assertSymbol: false, + schema: Type.Any(), + overrides, + }, + result: { + schema: Type.Any(), + }, + }); + expect(optionsTransformWith).toHaveBeenCalledTimes(1); + expect(optionsTransformWith).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + { + "overrideKey": "thingy", + }, + ], + ], + "results": [ + { + "type": "return", + "value": { + "overrideKey": "bobby", + }, + }, + ], + } + `); + }); + it("Should invoke the result transformWith function when one is provided", async () => { + const yf = testYf({ _fetch: jest.fn(() => ({ statusCode: 200 })) }); + const resultTransformedWith = jest.fn((v: Record) => { + return { overrideKey: "bobby" }; + }); + + await yf._moduleExec({ + query: { + assertSymbol: false, + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + transformWith: resultTransformedWith, + }, + }); + expect(resultTransformedWith).toHaveBeenCalledTimes(1); + expect(resultTransformedWith).toMatchInlineSnapshot(` + [MockFunction] { + "calls": [ + [ + { + "statusCode": 200, + }, + ], + ], + "results": [ + { + "type": "return", + "value": { + "overrideKey": "bobby", + }, + }, + ], + } + `); + }); + it("should throw when symbol assertion is enabled but a non-string symbol is provided", () => { + const yf = testYf({ _fetch: jest.fn() }); + expect( + async () => + await yf._moduleExec({ + query: { + assertSymbol: true, + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + }, + }) + ).rejects.toThrow(); + }); + it("should pass a string symbol when symbol assertion is enabled", () => { + const yf = testYf({ _fetch: jest.fn() }); + expect( + async () => + await yf._moduleExec({ + query: { + assertSymbol: "AAPL", + schema: Type.Any(), + }, + result: { + schema: Type.Any(), + }, + }) + ).not.toThrow(); + }); + }); }); diff --git a/src/lib/moduleExec.ts b/src/lib/moduleExec.ts index f7cb6747..60f51463 100644 --- a/src/lib/moduleExec.ts +++ b/src/lib/moduleExec.ts @@ -15,18 +15,17 @@ * Further info below, inline. */ -import validateAndCoerceTypes from "./validateAndCoerceTypes.js"; +import { validateAndCoerceTypebox } from "./validateAndCoerceTypes.js"; import csv2json from "./csv2json.js"; +import { TSchema } from "@sinclair/typebox"; -// The consuming module itself will have a stricter return type. -type TransformFunc = (arg: any) => any; /* interface TransformFunc { (result: { [key: string]: any }): { [key: string]: any }; } */ -interface ModuleExecOptions { +interface ModuleExecOptions { /** * Name of the module, e.g. "search", "quoteSummary", etc. Used in error * reporting. @@ -44,10 +43,9 @@ interface ModuleExecOptions { */ url: string; /** - * Key of schema used to validate user-provider query options. - * e.g. yf.search('AAPL', { isThisAValidOption: "maybe" }) + * The schema to use to validate the options overrides */ - schemaKey: string; + schema: TSchema; /** * Defaults for this query, e.g. { period: '1d' } in history, * and other required options that aren't often changed { locale: 'en' }. @@ -69,7 +67,7 @@ interface ModuleExecOptions { * the query. Useful to transform options we allow but not Yahoo, e.g. * allow a "2020-01-01" date but transform this to a UNIX epoch. */ - transformWith?: TransformFunc; + transformWith?: (opts: TOpts) => unknown; /** * Default: 'json'. Can be 'text' or 'csv' (augments fetch's "text"). */ @@ -77,19 +75,19 @@ interface ModuleExecOptions { /** * Default: false. This request requires Yahoo cookies & crumb. */ - needsCrumb: boolean; + needsCrumb?: boolean; }; result: { /** - * Key of schema to validate (and coerce) the retruned result from Yahoo. + * The schema to validate (and coerce) the retruned result from Yahoo. */ - schemaKey: string; + schema: TSchema; /** * Mutate the Yahoo result *before* validating and coercion. Mostly used * to e.g. throw if no (resault.returnField) and return result.returnField. */ - transformWith?: TransformFunc; + transformWith?: (result: unknown) => TResult; }; moduleOptions?: { @@ -104,9 +102,10 @@ interface ModuleExecOptions { }; } -type ThisWithModExec = { [key: string]: any; _moduleExec: typeof moduleExec }; - -async function moduleExec(this: ThisWithModExec, opts: ModuleExecOptions) { +async function moduleExec( + this: { [key: string]: any }, + opts: ModuleExecOptions +) { const queryOpts = opts.query; const moduleOpts = opts.moduleOptions; const moduleName = opts.moduleName; @@ -122,11 +121,10 @@ async function moduleExec(this: ThisWithModExec, opts: ModuleExecOptions) { } // Check that query options passed by the user are valid for this module - validateAndCoerceTypes({ - source: moduleName, + validateAndCoerceTypebox({ type: "options", - object: queryOpts.overrides ?? {}, - schemaKey: queryOpts.schemaKey, + data: queryOpts.overrides ?? {}, + schema: queryOpts.schema, options: this._opts.validation, }); @@ -141,8 +139,9 @@ async function moduleExec(this: ThisWithModExec, opts: ModuleExecOptions) { * the query. Useful to transform options we allow but not Yahoo, e.g. * allow a "2020-01-01" date but transform this to a UNIX epoch. */ - if (queryOpts.transformWith) + if (queryOpts.transformWith) { queryOptions = queryOpts.transformWith(queryOptions); + } // this._fetch is lib/yahooFinanceFetch let result = await this._fetch( @@ -150,16 +149,20 @@ async function moduleExec(this: ThisWithModExec, opts: ModuleExecOptions) { queryOptions, moduleOpts, queryOpts.fetchType, - queryOpts.needsCrumb + queryOpts.needsCrumb ?? false ); - if (queryOpts.fetchType === "csv") result = csv2json(result); + if (queryOpts.fetchType === "csv") { + result = csv2json(result); + } /* * Mutate the Yahoo result *before* validating and coercion. Mostly used - * to e.g. throw if no (resault.returnField) and return result.returnField. + * to e.g. throw if no (result.returnField) and return result.returnField. */ - if (opts.result.transformWith) result = opts.result.transformWith(result); + if (resultOpts.transformWith) { + result = resultOpts.transformWith(result); + } const validateResult = !moduleOpts || @@ -190,11 +193,10 @@ async function moduleExec(this: ThisWithModExec, opts: ModuleExecOptions) { * database, etc. Otherwise you'll receive an error. */ try { - validateAndCoerceTypes({ - source: moduleName, + validateAndCoerceTypebox({ type: "result", - object: result, - schemaKey: resultOpts.schemaKey, + data: result, + schema: resultOpts.schema, options: validationOpts, }); } catch (error) { diff --git a/src/lib/moduleExecTypebox.spec.ts b/src/lib/moduleExecTypebox.spec.ts deleted file mode 100644 index 5e6630da..00000000 --- a/src/lib/moduleExecTypebox.spec.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { jest } from "@jest/globals"; - -import search from "../modules/search.js"; -import { InvalidOptionsError } from "./errors.js"; -import testYf from "../../tests/testYf.js"; -import { TransformDecodeCheckError } from "@sinclair/typebox/value"; -import { Type } from "@sinclair/typebox"; - -const yf = testYf({ search }); -yf._opts.validation.logOptionsErrors = false; -yf._opts.validation.logErrors = false; - -describe("moduleExecTypebox", () => { - describe("options validation", () => { - it("throws InvalidOptions on invalid options", async () => { - const rwo = (options: any) => yf.search("symbol", options); - await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); - }); - - it("accepts empty queryOptions", async () => { - await expect( - yf.search("AAPL", undefined, { devel: "search-AAPL.json" }) - ).resolves.toBeDefined(); - }); - - it("logs errors on invalid options when logOptionsErrors = true", async () => { - yf._opts.validation.logOptionsErrors = true; - const realConsole = console; - const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; - - /* @ts-ignore */ - console = fakeConsole; - const rwo = (options: any) => yf.search("symbol", options); - await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); - console = realConsole; - - expect( - fakeConsole.log.mock.calls.length + - fakeConsole.error.mock.calls.length + - fakeConsole.dir.mock.calls.length - ).toBe(1); - yf._opts.validation.logOptionsErrors = false; - }); - - it("does not log errors on invalid options when logOptionsErrors = false", async () => { - yf._opts.validation.logOptionsErrors = false; - console.log(yf._opts.validation); - const realConsole = console; - const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; - - /* @ts-ignore */ - console = fakeConsole; - const rwo = (options: any) => yf.search("symbol", options); - await expect(rwo({ invalid: true })).rejects.toThrow(InvalidOptionsError); - console = realConsole; - expect( - fakeConsole.log.mock.calls.length + - fakeConsole.error.mock.calls.length + - fakeConsole.dir.mock.calls.length - ).toBe(0); - }); - }); - - describe("result validation", () => { - if (process.env.FETCH_DEVEL !== "nocache") - it("throws on unexpected input", async () => { - yf._opts.validation.logErrors = false; - await expect( - yf.search("AAPL", {}, { devel: "search-badResult.fake.json" }) - ).rejects.toThrow(TransformDecodeCheckError); - yf._opts.validation.logErrors = true; - }); - - it("dont throw or log on unexpected input with {validateResult: false}", async () => { - yf._opts.validation.logErrors = true; - const realConsole = console; - const fakeConsole = { error: jest.fn(), log: jest.fn(), dir: jest.fn() }; - - /* @ts-ignore */ - console = fakeConsole; - if (process.env.FETCH_DEVEL !== "nocache") - await expect( - yf.search( - "AAPL", - {}, - { - devel: "search-badResult.fake.json", - validateResult: false, - } - ) - ).resolves.toBeDefined(); - console = realConsole; - - expect(fakeConsole.log).not.toHaveBeenCalled(); - expect(fakeConsole.error).not.toHaveBeenCalled(); - expect(fakeConsole.dir).not.toHaveBeenCalled(); - }); - }); - describe("correctly invokes callbacks when provided", () => { - it("Should invoke the query options transformWith function when one is provided", async () => { - const yf = testYf({ _fetch: jest.fn() }); - const overrides = { overrideKey: "thingy" }; - const optionsTransformWith = jest.fn((v: Record) => ({ - ...v, - overrideKey: "bobby", - })); - - await yf._moduleExecTypebox({ - query: { - transformWith: optionsTransformWith, - assertSymbol: false, - schema: Type.Any(), - overrides, - }, - result: { - schema: Type.Any(), - }, - }); - expect(optionsTransformWith).toHaveBeenCalledTimes(1); - expect(optionsTransformWith).toMatchInlineSnapshot(` - [MockFunction] { - "calls": [ - [ - { - "overrideKey": "thingy", - }, - ], - ], - "results": [ - { - "type": "return", - "value": { - "overrideKey": "bobby", - }, - }, - ], - } - `); - }); - it("Should invoke the result transformWith function when one is provided", async () => { - const yf = testYf({ _fetch: jest.fn(() => ({ statusCode: 200 })) }); - const resultTransformedWith = jest.fn((v: Record) => { - return { overrideKey: "bobby" }; - }); - - await yf._moduleExecTypebox({ - query: { - assertSymbol: false, - schema: Type.Any(), - }, - result: { - schema: Type.Any(), - transformWith: resultTransformedWith, - }, - }); - expect(resultTransformedWith).toHaveBeenCalledTimes(1); - expect(resultTransformedWith).toMatchInlineSnapshot(` - [MockFunction] { - "calls": [ - [ - { - "statusCode": 200, - }, - ], - ], - "results": [ - { - "type": "return", - "value": { - "overrideKey": "bobby", - }, - }, - ], - } - `); - }); - it("should throw when symbol assertion is enabled but a non-string symbol is provided", () => { - const yf = testYf({ _fetch: jest.fn() }); - expect( - async () => - await yf._moduleExecTypebox({ - query: { - assertSymbol: true, - schema: Type.Any(), - }, - result: { - schema: Type.Any(), - }, - }) - ).rejects.toThrow(); - }); - it("should pass a string symbol when symbol assertion is enabled", () => { - const yf = testYf({ _fetch: jest.fn() }); - expect( - async () => - await yf._moduleExecTypebox({ - query: { - assertSymbol: "AAPL", - schema: Type.Any(), - }, - result: { - schema: Type.Any(), - }, - }) - ).not.toThrow(); - }); - }); -}); diff --git a/src/lib/moduleExecTypebox.ts b/src/lib/moduleExecTypebox.ts deleted file mode 100644 index 60f51463..00000000 --- a/src/lib/moduleExecTypebox.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* - * moduleExec(options: ModuleExecOptions) - * - * 1. Query Stage - * 1. Validate user-supplied module params, e.g. { period: '1d' } - * 2. Merge query params: (module defaults, user-supplied overrides, etc) - * 3. Optionally transform query params - * - * 2. Call lib/yahooFinanceFetch - * - * 3. Result Stage - * 1. Optional transform the result - * 2. Validate the result and coerce types - * - * Further info below, inline. - */ - -import { validateAndCoerceTypebox } from "./validateAndCoerceTypes.js"; -import csv2json from "./csv2json.js"; -import { TSchema } from "@sinclair/typebox"; - -/* -interface TransformFunc { - (result: { [key: string]: any }): { [key: string]: any }; -} -*/ - -interface ModuleExecOptions { - /** - * Name of the module, e.g. "search", "quoteSummary", etc. Used in error - * reporting. - */ - moduleName: string; - - query: { - /** - * If given, a runtime assertion is performed to check that the given - * argument is a string. If not, a helpful error is thrown. - */ - assertSymbol?: string; - /** - * URL of the API to query, WITHOUT query params. - */ - url: string; - /** - * The schema to use to validate the options overrides - */ - schema: TSchema; - /** - * Defaults for this query, e.g. { period: '1d' } in history, - * and other required options that aren't often changed { locale: 'en' }. - */ - defaults: any; - /** - * Query parameters generated inside the module, most commonly something - * like { q: query } to take e.g. yf.search(query) and pass it how Yahoo - * expects it. - */ - runtime?: any; - /** - * Query options passed by the user that will override the default and - * runtime params. Will be validated with schemaKey. - */ - overrides: any; - /** - * Called with the merged (defaults,runtime,overrides) before running - * the query. Useful to transform options we allow but not Yahoo, e.g. - * allow a "2020-01-01" date but transform this to a UNIX epoch. - */ - transformWith?: (opts: TOpts) => unknown; - /** - * Default: 'json'. Can be 'text' or 'csv' (augments fetch's "text"). - */ - fetchType?: string; - /** - * Default: false. This request requires Yahoo cookies & crumb. - */ - needsCrumb?: boolean; - }; - - result: { - /** - * The schema to validate (and coerce) the retruned result from Yahoo. - */ - schema: TSchema; - /** - * Mutate the Yahoo result *before* validating and coercion. Mostly used - * to e.g. throw if no (resault.returnField) and return result.returnField. - */ - transformWith?: (result: unknown) => TResult; - }; - - moduleOptions?: { - /** - * Allow validation failures to pass if false; - */ - validateResult?: boolean; - /** - * Any options to pass to fetch() just for this request. - */ - fetchOptions?: any; - }; -} - -async function moduleExec( - this: { [key: string]: any }, - opts: ModuleExecOptions -) { - const queryOpts = opts.query; - const moduleOpts = opts.moduleOptions; - const moduleName = opts.moduleName; - const resultOpts = opts.result; - - if (queryOpts.assertSymbol) { - const symbol = queryOpts.assertSymbol; - if (typeof symbol !== "string") - throw new Error( - `yahooFinance.${moduleName}() expects a single string symbol as its ` + - `query, not a(n) ${typeof symbol}: ${JSON.stringify(symbol)}` - ); - } - - // Check that query options passed by the user are valid for this module - validateAndCoerceTypebox({ - type: "options", - data: queryOpts.overrides ?? {}, - schema: queryOpts.schema, - options: this._opts.validation, - }); - - let queryOptions = { - ...queryOpts.defaults, // Module defaults e.g. { period: '1wk', lang: 'en' } - ...queryOpts.runtime, // Runtime params e.g. { q: query } - ...queryOpts.overrides, // User supplied options that override above - }; - - /* - * Called with the merged (defaults,runtime,overrides) before running - * the query. Useful to transform options we allow but not Yahoo, e.g. - * allow a "2020-01-01" date but transform this to a UNIX epoch. - */ - if (queryOpts.transformWith) { - queryOptions = queryOpts.transformWith(queryOptions); - } - - // this._fetch is lib/yahooFinanceFetch - let result = await this._fetch( - queryOpts.url, - queryOptions, - moduleOpts, - queryOpts.fetchType, - queryOpts.needsCrumb ?? false - ); - - if (queryOpts.fetchType === "csv") { - result = csv2json(result); - } - - /* - * Mutate the Yahoo result *before* validating and coercion. Mostly used - * to e.g. throw if no (result.returnField) and return result.returnField. - */ - if (resultOpts.transformWith) { - result = resultOpts.transformWith(result); - } - - const validateResult = - !moduleOpts || - moduleOpts.validateResult === undefined || - moduleOpts.validateResult === true; - - const validationOpts = { - ...this._opts.validation, - // Set logErrors=false if validateResult=false - logErrors: validateResult ? this._opts.validation.logErrors : false, - }; - - /* - * Validate the returned result (after transforming, above) and coerce types. - * - * The coersion works as follows: if we're expecting a "Date" type, but Yahoo - * gives us { raw: 1231421524, fmt: "2020-01-01" }, we'll return that as - * `new Date(1231421524 * 1000)`. - * - * Beyond that, ensures that user won't process unexpected data, in two - * cases: - * - * a) Missing required properties or unexpected additional properties - * b) A total new change in format that we really have no idea what to do - * with, e.g. a new kind of Date that we've never seen before and - * - * The idea is that if you receive a result, it's safe to use / store in - * database, etc. Otherwise you'll receive an error. - */ - try { - validateAndCoerceTypebox({ - type: "result", - data: result, - schema: resultOpts.schema, - options: validationOpts, - }); - } catch (error) { - if (validateResult) throw error; - } - - return result as any; -} - -export default moduleExec; diff --git a/src/lib/options.ts b/src/lib/options.ts index 45454ee1..330f310b 100644 --- a/src/lib/options.ts +++ b/src/lib/options.ts @@ -1,22 +1,56 @@ -// TODO, keep defaults there too? -import type { ValidationOptions } from "./validateAndCoerceTypes.js"; -import type { QueueOptions } from "./queue.js"; import { ExtendedCookieJar } from "./cookieJar.js"; +import { Static, Type } from "@sinclair/typebox"; +import { QueueOptionsSchema } from "./queue.js"; -export interface Logger { +const LoggerSchema = Type.Object({ + info: Type.Function([], Type.Void()), + warn: Type.Function([], Type.Void()), + error: Type.Function([], Type.Void()), + debug: Type.Function([], Type.Void()), +}); + +export type Logger = { info: (...args: any[]) => void; warn: (...args: any[]) => void; error: (...args: any[]) => void; debug: (...args: any[]) => void; -} +}; + +const ValidationOptionsSchema = Type.Object({ + logErrors: Type.Optional(Type.Boolean()), + logOptionsErrors: Type.Optional(Type.Boolean()), + _internalThrowOnAdditionalProperties: Type.Optional( + Type.Boolean({ + default: process.env.NODE_ENV === "test", + description: + "Use this property to throw when properties beyond what is explicitly specified in the schema are provided. It is an internal option and subject to change, use at your own risk", + }) + ), +}); + +export type ValidationOptions = Static; -export interface YahooFinanceOptions { - YF_QUERY_HOST?: string; - cookieJar?: ExtendedCookieJar; - queue?: QueueOptions; - validation?: ValidationOptions; +export const YahooFinanceOptionsSchema = Type.Object( + { + YF_QUERY_HOST: Type.Optional(Type.String()), + cookieJar: Type.Optional(Type.Any()), + queue: Type.Optional(QueueOptionsSchema), + validation: Type.Optional(ValidationOptionsSchema), + logger: Type.Optional(LoggerSchema), + }, + { title: "YahooFinanceOptions" } +); + +/* +TODO: Ideally we'd have the typebox type be our source of truth for types here. +However, Typebox does not support type checking for instances of a particular class +in the case of the `cookieJar`, and for functions with variadic params in the case +of the `logger` (ref: https://github.com/sinclairzx81/typebox/issues/931) +*/ +export type YahooFinanceOptions = Static & { + cookieJar: ExtendedCookieJar; logger?: Logger; -} +}; const options: YahooFinanceOptions = { YF_QUERY_HOST: process.env.YF_QUERY_HOST || "query2.finance.yahoo.com", diff --git a/src/lib/queue.ts b/src/lib/queue.ts index 973e265b..f58f4547 100644 --- a/src/lib/queue.ts +++ b/src/lib/queue.ts @@ -1,15 +1,25 @@ +import { Static, Type } from "@sinclair/typebox"; + interface Job { func: () => Promise; resolve: (arg: any) => void; reject: (arg: any) => void; } -export interface QueueOptions { - // TODO: adds func type to json schema which is not supported - //_queue?: Queue; - concurrency?: number; - timeout?: number; // TODO -} +export const QueueOptionsSchema = Type.Object( + { + // TODO: adds func type to json schema which is not supported + //_queue?: Queue; + concurrency: Type.Optional(Type.Number()), + timeout: Type.Optional(Type.Number()), // TODO + }, + { + additionalProperties: false, + title: "QueueOptions", + } +); + +export type QueueOptions = Static; export default class Queue { concurrency = 1; diff --git a/src/lib/setGlobalConfig.spec.ts b/src/lib/setGlobalConfig.spec.ts index d90fb50a..42a412fe 100644 --- a/src/lib/setGlobalConfig.spec.ts +++ b/src/lib/setGlobalConfig.spec.ts @@ -28,10 +28,52 @@ describe("setGlobalConfig", () => { it("should throw on invalid config", () => { consoleSilent(); - expect(() => yf.setGlobalConfig({ queue: { abc: "" } })).toThrow( - /yahooFinance.setGlobalConfig called with invalid options\./ + expect(() => yf.setGlobalConfig({ queue: { concurrency: "" } })).toThrow( + /Validation called with invalid options/ ); consoleRestore(); }); + it("should throw on an invalid logger", () => { + consoleSilent(); + + expect(() => + yf.setGlobalConfig({ + logger: { + info() {}, + debug() {}, + error() {}, + warn: "yeh this won't work", + }, + }) + ).toThrow(/Validation called with invalid options/); + + expect(() => + yf.setGlobalConfig({ + logger: { + info() {}, + debug() {}, + error() {}, + }, + }) + ).toThrow(/Validation called with invalid options/); + + expect(() => + yf.setGlobalConfig({ + logger: {}, + }) + ).toThrow(/Validation called with invalid options/); + + consoleRestore(); + }); + it("should throw on an invalid cookie jar", () => { + consoleSilent(); + + expect(() => + yf.setGlobalConfig({ + cookieJar: "not a cookie jar", + }) + ).toThrow(/cookieJar must be an instance of ExtendedCookieJar/); + consoleRestore(); + }); }); diff --git a/src/lib/setGlobalConfig.ts b/src/lib/setGlobalConfig.ts index 8d023007..6882e310 100644 --- a/src/lib/setGlobalConfig.ts +++ b/src/lib/setGlobalConfig.ts @@ -1,4 +1,7 @@ -import type { YahooFinanceOptions } from "./options.js"; +import { + YahooFinanceOptionsSchema, + type YahooFinanceOptions, +} from "./options.js"; import type { ModuleThis } from "./moduleCommon.js"; import validateAndCoerceTypes from "./validateAndCoerceTypes.js"; import { ExtendedCookieJar } from "./cookieJar.js"; @@ -7,16 +10,16 @@ export default function setGlobalConfig( this: ModuleThis, _config: YahooFinanceOptions ): void { - // Instances (e.g. cookieJar) don't validate well :) - const { cookieJar, logger, ...config } = _config; - - validateAndCoerceTypes({ - object: config, - source: "setGlobalConfig", + const parsed = validateAndCoerceTypes({ + data: _config, type: "options", options: this._opts.validation, - schemaKey: "#/definitions/YahooFinanceOptions", + schema: YahooFinanceOptionsSchema, }); + + // Instances (e.g. cookieJar) don't validate well :) + const { cookieJar, ...config } = parsed; + mergeObjects(this._opts, config); if (cookieJar) { @@ -24,17 +27,6 @@ export default function setGlobalConfig( throw new Error("cookieJar must be an instance of ExtendedCookieJar"); this._opts.cookieJar = cookieJar; } - if (logger) { - if (typeof logger.info !== "function") - throw new Error("logger.info must be a function"); - if (typeof logger.warn !== "function") - throw new Error("logger.warn must be a function"); - if (typeof logger.error !== "function") - throw new Error("logger.error must be a function"); - if (typeof logger.debug !== "function") - throw new Error("logger.debug must be a function"); - this._opts.logger = logger; - } } type Obj = Record; diff --git a/src/lib/validateAndCoerceTypes.spec.ts b/src/lib/validateAndCoerceTypes.spec.ts index dc5efe01..b9e94b9a 100644 --- a/src/lib/validateAndCoerceTypes.spec.ts +++ b/src/lib/validateAndCoerceTypes.spec.ts @@ -1,412 +1,9 @@ import { jest } from "@jest/globals"; -import validateAndCoerceTypes, { ajv } from "./validateAndCoerceTypes.js"; import { validateAndCoerceTypebox } from "./validateAndCoerceTypes.js"; -import type { ValidateParams } from "./validateAndCoerceTypes.js"; -import { InvalidOptionsError, FailedYahooValidationError } from "./errors.js"; import { Type } from "@sinclair/typebox"; import { YahooFinanceDate, YahooNumber } from "./yahooFinanceTypes.js"; - -ajv.addSchema({ - $id: "testSchema", - $schema: "http://json-schema.org/draft-07/schema#", - properties: { - date: { yahooFinanceType: "date" }, - dateNull: { yahooFinanceType: "date|null" }, - dateInMs: { yahooFinanceType: "DateInMs" }, - twoNumberRange: { yahooFinanceType: "TwoNumberRange" }, - number: { yahooFinanceType: "number" }, - numberNull: { yahooFinanceType: "number|null" }, - requiredRequired: { - type: "object", - properties: { required: { type: "boolean" } }, - required: ["required"], - }, - noAdditional: { - type: "object", - additionalProperties: false, - properties: {}, - }, - }, - type: "object", -}); - -// Default. Use to show (unexpected) errors during tests. -const defLogParams: ValidateParams = { - source: "validateAndCoerceTypes.spec.js", - schemaKey: "testSchema", - //schemaKey: "#/definitions/QuoteSummaryResult", - type: "result", - object: {}, - options: { - logErrors: true, - logOptionsErrors: true, - }, -}; - -// If we're purposefully testing failed validation, don't log it. -// i.e. Use to hide (expected) errors during tests. -const defNoLogParams = { - ...defLogParams, - options: { - ...defLogParams.options, - logErrors: false, - }, -}; - -describe("validateAndCoerceTypes", () => { - describe("coersion", () => { - describe("numbers", () => { - it("passes regular numbers", () => { - const object = { number: 2 }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.number).toBe(2); - }); - - it("corerces rawNumberObjs", () => { - const object = { number: { raw: 0.006599537, fmt: "6.5%" } }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.number).toBe(0.006599537); - }); - - it("passes if data is null and type IS number|null", () => { - const object = { numberNull: null }; - expect(() => - validateAndCoerceTypes({ ...defLogParams, object }) - ).not.toThrow(); - }); - - it("fails if data is null and type IS NOT number|null", () => { - const object = { number: null }; - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ ...defNoLogParams, object }); - } catch (e) { - error = e; - } - // @ts-ignore - expect(error).toBeDefined(); - expect(error.errors[0].message).toMatch(/Expecting number/); - }); - - it("passes and coerces {} to null if type IS number|null", () => { - const object = { numberNull: {} }; - expect(() => - validateAndCoerceTypes({ ...defLogParams, object }) - ).not.toThrow(); - expect(object.numberNull).toBe(null); - }); - - it("fails when receiving {} if type IS NOT number|null", () => { - const object = { number: {} }; - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ ...defNoLogParams, object }); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.errors[0].message).toMatch(/number \| null/); - }); - - it("fails if data is not a number nor object", () => { - const object = { number: true }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/Failed Yahoo Schema/); - }); - - it("fails if data.raw is not a number", () => { - const object = { number: { raw: "a string" } }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/Failed Yahoo Schema/); - }); - - it("fails if string returns a NaN", () => { - const object = { number: "not-a-number" }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/Failed Yahoo Schema/); - }); - }); - - describe("dates", () => { - it("coerces rawNumberObjs", () => { - const dateInMs = 1612313997; - const object = { date: { raw: dateInMs } }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.date).toBeInstanceOf(Date); - // @ts-ignore - expect(object.date.getTime()).toBe(dateInMs * 1000); - }); - - it("coerces epochs", () => { - const dateInMs = 1612313997; - const object = { date: dateInMs }; - validateAndCoerceTypes({ ...defLogParams, object }); - // @ts-ignore - expect(object.date.getTime()).toBe(new Date(dateInMs * 1000).getTime()); - }); - - it("coerces recognizable date string", () => { - const dateStr = "2021-02-02T21:00:01.000Z"; - const object = { date: dateStr }; - validateAndCoerceTypes({ ...defLogParams, object }); - // @ts-ignore - expect(object.date.getTime()).toBe(new Date(dateStr).getTime()); - }); - - it("throws on non-matching strings", () => { - const object = { date: "clearly not a date" }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/Failed Yahoo Schema/); - }); - - it("passes through Date objects", () => { - const date = new Date(); - const object = { date }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.date).toBe(date); - }); - - it("passes if data is null and type IS date|null", () => { - const object = { dateNull: null }; - expect(() => - validateAndCoerceTypes({ ...defLogParams, object }) - ).not.toThrow(); - }); - - it("fails if data is null and type IS NOT date|null", () => { - const object = { date: null }; - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ ...defNoLogParams, object }); - } catch (e) { - error = e; - } - // @ts-ignore - expect(error).toBeDefined(); - expect(error.errors[0].message).toMatch(/Expecting date/); - }); - - it("passes and coerces {} to null if type IS Date|null", () => { - const object = { dateNull: {} }; - expect(() => - validateAndCoerceTypes({ ...defLogParams, object }) - ).not.toThrow(); - expect(object.dateNull).toBe(null); - }); - - it("fails when receiving {} if type IS NOT date|null", () => { - const object = { date: {} }; - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ ...defNoLogParams, object }); - } catch (e) { - error = e; - } - expect(error).toBeDefined(); - expect(error.errors[0].message).toMatch(/date \| null/); - }); - }); - - describe("DateInMs", () => { - it("works with date in milliseconds", () => { - const object = { dateInMs: 917015400000 }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.dateInMs).toBeInstanceOf(Date); - }); - }); - - describe("TwoNumberRange", () => { - it("works with valid input", () => { - const object = { twoNumberRange: "541.867 - 549.19" }; - validateAndCoerceTypes({ ...defLogParams, object }); - expect(object.twoNumberRange).toMatchObject({ - low: 541.867, - high: 549.19, - }); - }); - - it("throws on invalid input", () => { - const object = { twoNumberRange: "X - 549.19" }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/^Failed Yahoo/); - }); - - it("throws no matching type on weird input", () => { - const object = { twoNumberRange: 12 }; - expect(() => - validateAndCoerceTypes({ ...defNoLogParams, object }) - ).toThrow(/^Failed Yahoo/); - }); - }); - - describe("failures", () => { - it("fails on invalid options usage", () => { - const options = { period1: true }; - expect(() => - validateAndCoerceTypes({ - ...defNoLogParams, - object: options, - type: "options", - schemaKey: "#/definitions/HistoricalOptions", - source: "historical-in-validate.spec", - options: { ...defNoLogParams.options, logOptionsErrors: false }, - }) - ).toThrow(InvalidOptionsError); - }); - - it("fails on error", () => { - const object = { date: { weird: 123 } }; - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ ...defNoLogParams, object }); - } catch (e) { - error = e; - } - - /* @ts-ignore */ - expect(error).toBeDefined(); - - /* @ts-ignore */ - if (!error) return; - expect(error.message).toMatch(/Failed Yahoo Schema/); - - /* @ts-ignore */ - const error0 = error.errors[0]; - expect(error0).toBeDefined(); - expect(error0.keyword).toBe("yahooFinanceType"); - expect(error0.message).toBe("No matching type"); - expect(error0.params).toBeDefined(); - - if (!error0.params) return; - expect(error0.params.schema).toBe("date"); - expect(error0.params.data).toBe(object.date); - expect(error0.instancePath).toBe("/date"); - expect(error0.schemaPath).toBe("#/properties/date/yahooFinanceType"); - }); - - it("fails on invalid schema key", () => { - expect(() => - validateAndCoerceTypes({ - ...defNoLogParams, - schemaKey: "SOME_MISSING_KEY", - }) - ).toThrow(/No such schema/); - }); - - // i.e. on output not from bin/modify-schema - it('fails when yahooFinanceType is not "date"|"number"', () => { - const schema = { yahooFinanceType: "impossible" }; - const validate = ajv.compile(schema); - expect(() => validate({})).toThrow(/No such yahooFinanceType/); - }); - - it("logs errors when logErrors=true", () => { - const origConsole = console; - const fakeConsole = { - error: jest.fn(), - log: jest.fn(), - dir: jest.fn(), - }; - - /* @ts-ignore */ - console = fakeConsole; - const object = { requiredRequired: {} }; - expect(() => - validateAndCoerceTypes({ - ...defLogParams, - object, - }) - ).toThrow("Failed Yahoo Schema validation"); - console = origConsole; - - expect(fakeConsole.log).toHaveBeenCalled(); - }); - - it("does not log errors when logErrors=false", () => { - const origConsole = console; - const fakeConsole = { - error: jest.fn(), - log: jest.fn(), - dir: jest.fn(), - }; - - /* @ts-ignore */ - console = fakeConsole; - const object = { requiredRequired: {} }; - expect(() => - validateAndCoerceTypes({ - ...defNoLogParams, - object, - }) - ).toThrow("Failed Yahoo Schema validation"); - console = origConsole; - - expect(fakeConsole.log).not.toHaveBeenCalled(); - expect(fakeConsole.error).not.toHaveBeenCalled(); - expect(fakeConsole.dir).not.toHaveBeenCalled(); - }); - - it("returns results/errors in error object", () => { - const object = { noAdditional: { additional: true } }; - - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ - ...defNoLogParams, - object, - }); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error.message).toMatch(/Failed Yahoo/); - expect(error.result).toBe(object); - expect(error.errors).toBeType("array"); - }); - - it("returns ref to problem data in error object", () => { - const object = { noAdditional: { additional: true }, number: "str" }; - - let error: FailedYahooValidationError | any; - try { - validateAndCoerceTypes({ - ...defNoLogParams, - object, - }); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error.message).toMatch(/Failed Yahoo/); - - let e; - - e = error.errors[0]; - expect(e.params).toMatchObject({ - data: "str", - schema: "number", - }); - expect(e.instancePath).toBe("/number"); - expect(e.data).toBe("str"); - - e = error.errors[1]; - expect(e.instancePath).toBe("/noAdditional"); - expect(e.params).toMatchObject({ - additionalProperty: "additional", - }); - expect(e.data).toBe(object.noAdditional); - }); - }); - }); -}); +import { FailedYahooValidationError } from "./errors.js"; describe("validateAndCoerceTypebox", () => { afterEach(() => { @@ -419,7 +16,7 @@ describe("validateAndCoerceTypebox", () => { date: YahooFinanceDate, }); - let error; + let error: FailedYahooValidationError | undefined; try { validateAndCoerceTypebox({ type: "result", @@ -428,68 +25,121 @@ describe("validateAndCoerceTypebox", () => { options: {}, }); } catch (e) { - error = e; + error = e as unknown as FailedYahooValidationError; } expect(error).toMatchInlineSnapshot( - `[Error: Unable to decode value as it does not match the expected schema]` + `[FailedYahooValidationError: Failed Yahoo Schema validation]` ); - // TODO: Fix Jest types here - // @ts-ignore - expect(JSON.stringify(error?.error, null, 2)).toMatchInlineSnapshot(` + + expect(JSON.stringify(error?.errors?.[0], null, 2)).toMatchInlineSnapshot(` "{ - "type": 62, "schema": { - "title": "YahooFinanceDate", - "anyOf": [ - { - "type": "Date" - }, - { - "type": "number" - }, - { - "title": "RawDateObject", - "type": "object", - "properties": { - "raw": { - "type": "number" - } - }, - "required": [ - "raw" - ] - }, - { - "title": "ISOStringDate", + "type": "object", + "properties": { + "date": { + "title": "YahooFinanceDate", "anyOf": [ { - "format": "date", - "type": "string" + "type": "Date" }, { - "format": "year", - "type": "string" + "type": "number" }, { - "format": "date-time", - "type": "string" + "title": "RawDateObject", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "title": "ISOStringDate", + "anyOf": [ + { + "format": "date", + "type": "string" + }, + { + "format": "year", + "type": "string" + }, + { + "format": "date-time", + "type": "string" + } + ] } ] } + }, + "required": [ + "date" ] }, - "path": "/date", "value": { - "weird": 123 + "date": { + "weird": 123 + } }, - "message": "Expected union value" + "error": { + "type": 62, + "schema": { + "title": "YahooFinanceDate", + "anyOf": [ + { + "type": "Date" + }, + { + "type": "number" + }, + { + "title": "RawDateObject", + "type": "object", + "properties": { + "raw": { + "type": "number" + } + }, + "required": [ + "raw" + ] + }, + { + "title": "ISOStringDate", + "anyOf": [ + { + "format": "date", + "type": "string" + }, + { + "format": "year", + "type": "string" + }, + { + "format": "date-time", + "type": "string" + } + ] + } + ] + }, + "path": "/date", + "value": { + "weird": 123 + }, + "message": "Expected union value" + } }" `); - // TODO: Fix Jest types here - // @ts-ignore - expect(JSON.stringify(error?.value, null, 2)).toMatchInlineSnapshot(` + expect(JSON.stringify(error?.errors?.[0]?.value, null, 2)) + .toMatchInlineSnapshot(` "{ "date": { "weird": 123 @@ -765,3 +415,41 @@ describe("validateAndCoerceTypebox", () => { }).toThrow(TypeError); }); }); + +describe("_internalThrowOnAddtionalProperties", () => { + const schema = Type.Object( + { name: Type.String() }, + { additionalProperties: Type.Any() } + ); + + const testCase = { name: "Gadi", aTotallyUnrelatedKey: "foo" }; + it("Should allow additional properties if a schema does when _throwOnAdditionalProperties is false", () => { + const result = validateAndCoerceTypebox({ + type: "result", + data: testCase, + options: { + _internalThrowOnAdditionalProperties: false, + }, + schema, + }); + + expect(result).toMatchInlineSnapshot(` + { + "aTotallyUnrelatedKey": "foo", + "name": "Gadi", + } + `); + }); + it("Should not allow additional properties even if a schema does when _throwOnAdditionalProperties is true", () => { + expect(() => { + validateAndCoerceTypebox({ + type: "options", + data: testCase, + schema: schema, + options: { + _internalThrowOnAdditionalProperties: true, + }, + }); + }).toThrow(); + }); +}); diff --git a/src/lib/validateAndCoerceTypes.ts b/src/lib/validateAndCoerceTypes.ts index 99aa5ba1..b48105a5 100644 --- a/src/lib/validateAndCoerceTypes.ts +++ b/src/lib/validateAndCoerceTypes.ts @@ -1,235 +1,12 @@ -import Ajv from "ajv"; -import type { SchemaValidateFunction } from "ajv/dist/types"; -import addFormats from "ajv-formats"; - -//import schema from '../../schema.json'; -import schema from "../../schema.json"; import pkg from "../../package.json"; -import { InvalidOptionsError, FailedYahooValidationError } from "./errors.js"; +import { FailedYahooValidationError, InvalidOptionsError } from "./errors.js"; import { StaticDecode, type TSchema } from "@sinclair/typebox"; import { TransformDecodeCheckError, TransformDecodeError, Value, } from "@sinclair/typebox/value"; - -// https://ajv.js.org/docs/api.html#options -export const ajv = new Ajv({ - // All rules, all errors. Don't end early after first error. - allErrors: true, - // Allow multiple non-null types, like in TypeSript. - allowUnionTypes: true, -}); -addFormats(ajv); - -ajv.addKeyword({ - keyword: "yahooFinanceType", - modifying: true, - errors: true, - schema: true, - compile(schema /*, parentSchema, it */) { - const validate: SchemaValidateFunction = (data, dataCtx) => { - const { parentData, parentDataProperty } = dataCtx; - - function set(value: any) { - parentData[parentDataProperty] = value; - return true; - } - - if (schema === "number" || schema === "number|null") { - if (typeof data === "number") return true; - - if (typeof data === "string") { - let float = Number.parseFloat(data); - if (Number.isNaN(float)) { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: "Number.parseFloat returned NaN", - params: { schema, data }, - }); - return false; - } - return set(float); - } - - if (data === null) { - if (schema === "number|null") { - return true; - } else { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: "Expecting number'ish but got null", - params: { schema, data }, - }); - return false; - } - } - - if (typeof data === "object") { - if (Object.keys(data).length === 0) { - // Value of {} becomes null - // Note, TypeScript types should be "number | null" - if (schema === "number|null") { - return set(null); - } else { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: - "Got {}->null for 'number', did you want 'number | null' ?", - params: { schema, data }, - }); - return false; - } - } - if (typeof data.raw === "number") return set(data.raw); - } - } else if (schema === "date" || schema === "date|null") { - if (data instanceof Date) { - // Validate existing date objects. - // Generally we receive JSON but in the case of "historical", the - // csv parser does the date conversion, and we want to validate - // afterwards. - return true; - } - - if (typeof data === "number") return set(new Date(data * 1000)); - - if (data === null) { - if (schema === "date|null") { - return true; - } else { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: "Expecting date'ish but got null", - params: { schema, data }, - }); - return false; - } - } - - if (typeof data === "object") { - if (Object.keys(data).length === 0) { - // Value of {} becomes null - // Note, TypeScript types should be "data | null" - if (schema === "date|null") { - return set(null); - } else { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: - "Got {}->null for 'date', did you want 'date | null' ?", - params: { schema, data }, - }); - return false; - } - } - if (typeof data.raw === "number") - return set(new Date(data.raw * 1000)); - } - - if (typeof data === "string") { - if ( - data.match(/^\d{4,4}-\d{2,2}-\d{2,2}$/) || - data.match( - /^\d{4,4}-\d{2,2}-\d{2,2}T\d{2,2}:\d{2,2}:\d{2,2}(\.\d{3,3})?Z$/ - ) - ) - return set(new Date(data)); - } - } else if (schema === "DateInMs") { - return set(new Date(data)); - } else if (schema === "TwoNumberRange") { - if ( - typeof data === "object" && - typeof data.low === "number" && - typeof data.high === "number" - ) - return true; - if (typeof data === "string") { - const parts = data.split("-").map(parseFloat); - if (Number.isNaN(parts[0]) || Number.isNaN(parts[1])) { - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: - "Number.parseFloat returned NaN: [" + parts.join(",") + "]", - params: { schema, data }, - }); - return false; - } - return set({ low: parts[0], high: parts[1] }); - } - } else { - throw new Error("No such yahooFinanceType: " + schema); - } - - validate.errors = validate.errors || []; - validate.errors.push({ - keyword: "yahooFinanceType", - message: "No matching type", - params: { schema, data }, - }); - return false; - }; - - return validate; - }, -}); - -ajv.addSchema(schema); - -/* istanbul ignore next */ -const logObj = - typeof process !== "undefined" && process?.stdout?.isTTY - ? (obj: any) => console.dir(obj, { depth: 4, colors: true }) - : (obj: any) => console.log(JSON.stringify(obj, null, 2)); - -export function resolvePath(obj: any, instancePath: string) { - const path = instancePath.split("/"); - let ref = obj; - for (let i = 1; i < path.length; i++) ref = ref[path[i]]; - return ref; -} - -export interface ValidationOptions { - logErrors?: boolean; - logOptionsErrors?: boolean; -} -export interface ValidateParams { - source: string; - type: "options" | "result"; - object: object; - schemaKey: string; - options: ValidationOptions; -} - -function disallowAdditionalProps(show = false) { - const disallowed = new Set(); - // @ts-ignore: this can cause a breaking catch-22 on schema generation - for (let key of Object.keys(schema.definitions)) { - if (key.match(/Options$/)) { - continue; - } - // @ts-ignore - const def = schema.definitions[key]; - if (def.type === "object" && def.additionalProperties === undefined) { - def.additionalProperties = false; - disallowed.add(key); - } - } - /* istanbul ignore next */ - if (show) - console.log( - "Disallowed additional props in " + Array.from(disallowed).join(", ") - ); -} - -if (process.env.NODE_ENV === "test") disallowAdditionalProps(); +import { ValidationOptions } from "./options"; const handleResultError = ( e: TransformDecodeError | TransformDecodeCheckError, @@ -263,7 +40,10 @@ const handleResultError = ( so help is always appreciated! `); } - throw e; + throw new FailedYahooValidationError("Failed Yahoo Schema validation", { + result: e.value, + errors: [e], + }); }; const handleOptionsError = ( @@ -290,7 +70,10 @@ export const validateAndCoerceTypebox = ({ options: ValidationOptions; }): StaticDecode => { try { - return Value.Decode(schema, data); + const validationSchema = options._internalThrowOnAdditionalProperties + ? { ...schema, additionalProperties: false } + : schema; + return Value.Decode(validationSchema, data); } catch (e) { if ( e instanceof TransformDecodeError || @@ -309,141 +92,4 @@ export const validateAndCoerceTypebox = ({ } }; -function validate({ - source, - type, - object, - schemaKey, - options, -}: ValidateParams): void { - const validator = ajv.getSchema(schemaKey); - if (!validator) throw new Error("No such schema with key: " + schemaKey); - - const valid = validator(object); - if (valid) return; - - if (type === "result") { - /* istanbul ignore else */ - if (validator.errors) { - let origData: any = false; - - validator.errors.forEach((error) => { - // For now let's ignore the base object which could be huge. - /* istanbul ignore else */ - if (error.instancePath !== "") - // Note, not the regular ajv data value from verbose:true - error.data = resolvePath(object, error.instancePath); - - if (error.schemaPath === "#/anyOf") { - if (origData === false) { - origData = error.data; - } else if (origData === error.data) { - error.data = "[shortened by validateAndCoerceTypes]"; - } - } - }); - - // Becaue of the "anyOf" in quote, errors are huuuuge and mostly - // irrelevant... so let's filter out (some of) the latter - validator.errors = validator.errors.filter((error) => { - if (error.schemaPath.startsWith("#/definitions/Quote")) { - const schemaQuoteType = error.schemaPath - .substring(19) - .split("/")[0] - .toUpperCase(); - - /* - * Filter out entries for non-matching schema type, i.e. - * { - * schemaPath: '#/definitions/QuoteCryptoCurrency/properties...' - * data: { - * quoteType: "EQUITY" - * } - * } - */ - if ( - typeof error.data === "object" && - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: Properrty "quoteType" does not exist on type "object" - error.data?.quoteType !== schemaQuoteType - ) - return false; - - /* - * Filter out the non-matching "const" for the above. - * { - * schemaPath: '#/definitions/QuoteCryptoCurrency/properties/quoteType/const' - * keyword: "const", - * params: { allowedValue: "CRYPTOCURRENCY"}}, - * data: "EQUITY" - * } - */ - if ( - typeof error.data === "string" && - error.params?.allowedValue === schemaQuoteType - ) - return false; - } - return true; - }); - - // In the case of there being NO match in #anyOf, bring back the data - if ( - validator.errors.length === 1 && - validator.errors[0].schemaPath === "#/anyOf" - ) - validator.errors[0].data = origData; - } - - if (options.logErrors === true) { - const title = encodeURIComponent("Failed validation: " + schemaKey); - console.log( - "The following result did not validate with schema: " + schemaKey - ); - logObj(validator.errors); - // logObj(object); - console.log(` -This may happen intermittently and you should catch errors appropriately. -However: 1) if this recently started happening on every request for a symbol -that used to work, Yahoo may have changed their API. 2) If this happens on -every request for a symbol you've never used before, but not for other -symbols, you've found an edge-case (OR, we may just be protecting you from -"bad" data sometimes stored for e.g. misspelt symbols on Yahoo's side). -Please see if anyone has reported this previously: - - ${pkg.repository}/issues?q=is%3Aissue+${title} - -or open a new issue (and mention the symbol): ${pkg.name} v${pkg.version} - - ${pkg.repository}/issues/new?labels=bug%2C+validation&template=validation.md&title=${title} - -For information on how to turn off the above logging or skip these errors, -see https://github.com/gadicc/node-yahoo-finance2/tree/devel/docs/validation.md. - -At the end of the doc, there's also a section on how to -[Help Fix Validation Errors](https://github.com/gadicc/node-yahoo-finance2/blob/devel/docs/validation.md#help-fix) -in case you'd like to contribute to the project. Most of the time, these -fixes are very quick and easy; it's just hard for our small core team to keep up, -so help is always appreciated! -`); - } /* if (logErrors) */ - - throw new FailedYahooValidationError("Failed Yahoo Schema validation", { - result: object, - errors: validator.errors, - }); - } /* if (type === 'options') */ else { - if (options.logOptionsErrors === true) { - console.error( - `[yahooFinance.${source}] Invalid options ("${schemaKey}")` - ); - logObj({ errors: validator.errors, input: object }); - } - throw new InvalidOptionsError( - `yahooFinance.${source} called with invalid options.` - ); - } -} - -export { disallowAdditionalProps }; -export default validate; +export default validateAndCoerceTypebox; diff --git a/src/modules/chart.spec.ts b/src/modules/chart.spec.ts index 924f1e25..a18b95ab 100644 --- a/src/modules/chart.spec.ts +++ b/src/modules/chart.spec.ts @@ -137,7 +137,7 @@ describe("chart", () => { * As long as it doesn't throw an error, that could confuse jest :) */ const yf = { - _moduleExecTypebox: jest.fn(async () => ({ + _moduleExec: jest.fn(async () => ({ meta: {}, timestamp: [], })), @@ -147,7 +147,7 @@ describe("chart", () => { // @ts-ignore: TODO yf.chart("symbol", { period1: "required-but-not-used" }); // @ts-ignore: TODO - const { transformWith } = yf._moduleExecTypebox.mock.calls[0][0].query; + const { transformWith } = yf._moduleExec.mock.calls[0][0].query; it("uses today's date as default for period2", () => { const now = new Date(); diff --git a/src/modules/chart.ts b/src/modules/chart.ts index e93b529b..d1f87279 100644 --- a/src/modules/chart.ts +++ b/src/modules/chart.ts @@ -314,7 +314,7 @@ export default async function chart( ): Promise { const returnAs = queryOptionsOverrides?.return || "array"; - const result = (await this._moduleExecTypebox({ + const result = (await this._moduleExec({ moduleName: "chart", query: { diff --git a/src/modules/dailyGainers.ts b/src/modules/dailyGainers.ts index a98f0d88..308588e3 100644 --- a/src/modules/dailyGainers.ts +++ b/src/modules/dailyGainers.ts @@ -182,7 +182,7 @@ export default function dailyGainers( queryOptionsOverrides?: DailyGainersOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "dailyGainers", query: { url: "https://${YF_QUERY_HOST}/v1/finance/screener/predefined/saved", diff --git a/src/modules/fundamentalsTimeSeries.ts b/src/modules/fundamentalsTimeSeries.ts index 82e966b5..29e946ef 100644 --- a/src/modules/fundamentalsTimeSeries.ts +++ b/src/modules/fundamentalsTimeSeries.ts @@ -88,7 +88,7 @@ export default function fundamentalsTimeSeries( queryOptionsOverrides: FundamentalsTimeSeriesOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "options", query: { diff --git a/src/modules/historical.spec.ts b/src/modules/historical.spec.ts index 2a4a5cf4..8d6d8571 100644 --- a/src/modules/historical.spec.ts +++ b/src/modules/historical.spec.ts @@ -67,11 +67,11 @@ describe("historical", () => { }); describe("transformWith", () => { - const yf = { _moduleExecTypebox: jest.fn(), historical }; + const yf = { _moduleExec: jest.fn(), historical }; // @ts-ignore: TODO yf.historical("symbol", { period1: "required-but-not-used" }); // @ts-ignore: TODO - const { transformWith } = yf._moduleExecTypebox.mock.calls[0][0].query; + const { transformWith } = yf._moduleExec.mock.calls[0][0].query; it("uses today's date as default for period2", () => { const now = new Date(); diff --git a/src/modules/historical.ts b/src/modules/historical.ts index 93875c9e..f43628db 100644 --- a/src/modules/historical.ts +++ b/src/modules/historical.ts @@ -163,7 +163,7 @@ export default function historical( schema = HistoricalStockSplitsResultSchema; else throw new Error("No such event type:" + queryOptionsOverrides.events); - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "historical", query: { diff --git a/src/modules/insights.ts b/src/modules/insights.ts index 250c3964..0ad36a46 100644 --- a/src/modules/insights.ts +++ b/src/modules/insights.ts @@ -327,7 +327,7 @@ export default function trendingSymbols( queryOptionsOverrides?: InsightsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "insights", query: { assertSymbol: symbol, diff --git a/src/modules/options.ts b/src/modules/options.ts index 6f7bd111..5b12bbb5 100644 --- a/src/modules/options.ts +++ b/src/modules/options.ts @@ -241,7 +241,7 @@ export default function options( queryOptionsOverrides: OptionsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "options", query: { diff --git a/src/modules/quote.ts b/src/modules/quote.ts index 86451d71..64607c67 100644 --- a/src/modules/quote.ts +++ b/src/modules/quote.ts @@ -332,7 +332,7 @@ export default async function quote( const symbols = typeof query === "string" ? query : query.join(","); const returnAs = queryOptionsOverrides && queryOptionsOverrides.return; - const results: Quote[] = await this._moduleExecTypebox({ + const results: Quote[] = await this._moduleExec({ moduleName: "quote", query: { diff --git a/src/modules/quoteSummary.ts b/src/modules/quoteSummary.ts index ed2cb67a..dc5292e4 100644 --- a/src/modules/quoteSummary.ts +++ b/src/modules/quoteSummary.ts @@ -116,7 +116,7 @@ export default function quoteSummary( queryOptionsOverrides?: QuoteSummaryOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "quoteSummary", query: { assertSymbol: symbol, diff --git a/src/modules/recommendationsBySymbol.ts b/src/modules/recommendationsBySymbol.ts index 6769b229..a1ba1b98 100644 --- a/src/modules/recommendationsBySymbol.ts +++ b/src/modules/recommendationsBySymbol.ts @@ -76,7 +76,7 @@ export default function recommendationsBySymbol( ): Promise { const symbols = typeof query === "string" ? query : query.join(","); - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "recommendationsBySymbol", query: { diff --git a/src/modules/screener.ts b/src/modules/screener.ts index dc560915..5dad6017 100644 --- a/src/modules/screener.ts +++ b/src/modules/screener.ts @@ -224,7 +224,7 @@ export default function screener( queryOptionsOverrides?: ScreenerOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "screener", query: { url: "https://${YF_QUERY_HOST}/v1/finance/screener/predefined/saved", diff --git a/src/modules/search.ts b/src/modules/search.ts index 237d8300..608959e4 100644 --- a/src/modules/search.ts +++ b/src/modules/search.ts @@ -283,7 +283,7 @@ export default function search( queryOptionsOverrides?: SearchOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "searchTypebox", query: { diff --git a/src/modules/trendingSymbols.ts b/src/modules/trendingSymbols.ts index 177a26aa..ca24a814 100644 --- a/src/modules/trendingSymbols.ts +++ b/src/modules/trendingSymbols.ts @@ -70,7 +70,7 @@ export default function trendingSymbols( queryOptionsOverrides?: TrendingSymbolsOptions, moduleOptions?: ModuleOptions ): Promise { - return this._moduleExecTypebox({ + return this._moduleExec({ moduleName: "trendingSymbols", query: { url: "https://${YF_QUERY_HOST}/v1/finance/trending/" + query, diff --git a/tests/http/getCrumb-getcrumb b/tests/http/getCrumb-getcrumb deleted file mode 100644 index 1cd08dd9..00000000 --- a/tests/http/getCrumb-getcrumb +++ /dev/null @@ -1,67 +0,0 @@ -{ - "request": { - "url": "https://query2.finance.yahoo.com/v1/test/getcrumb" - }, - "response": { - "ok": true, - "status": 200, - "statusText": "OK", - "headers": { - "content-type": [ - "text/plain;charset=utf-8" - ], - "access-control-allow-origin": [ - "https://finance.yahoo.com" - ], - "access-control-allow-credentials": [ - "true" - ], - "y-rid": [ - "4o3fbchj9sck2" - ], - "cache-control": [ - "private, max-age=60, stale-while-revalidate=30" - ], - "vary": [ - "Origin,Accept-Encoding" - ], - "content-length": [ - "11" - ], - "x-envoy-upstream-service-time": [ - "1" - ], - "date": [ - "Mon, 22 Jul 2024 10:20:50 GMT" - ], - "server": [ - "ATS" - ], - "x-envoy-decorator-operation": [ - "finance-external-services-api--mtls-production-sg3.finance-k8s.svc.yahoo.local:4080/*" - ], - "age": [ - "0" - ], - "strict-transport-security": [ - "max-age=31536000" - ], - "referrer-policy": [ - "no-referrer-when-downgrade" - ], - "connection": [ - "close" - ], - "expect-ct": [ - "max-age=31536000, report-uri=\"http://csp.yahoo.com/beacon/csp?src=yahoocom-expect-ct-report-only\"" - ], - "x-xss-protection": [ - "1; mode=block" - ], - "x-content-type-options": [ - "nosniff" - ] - }, - "body": "mloUP8q7ZPH" - } -} \ No newline at end of file diff --git a/tests/testYf.ts b/tests/testYf.ts index 79aa1f2a..2936e3a0 100644 --- a/tests/testYf.ts +++ b/tests/testYf.ts @@ -2,7 +2,6 @@ import _env from "../src/env-test"; import _opts from "../src/lib/options"; import _fetch from "../src/lib/yahooFinanceFetch"; import _moduleExec from "../src/lib/moduleExec"; -import _moduleExecTypebox from "../src/lib/moduleExecTypebox"; export default function genYf(extend: object): any { return { @@ -10,7 +9,6 @@ export default function genYf(extend: object): any { _opts, _fetch, _moduleExec, - _moduleExecTypebox, ...extend, }; } diff --git a/yarn.lock b/yarn.lock index 9ca568da..0f039811 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1644,11 +1644,6 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.12": - version "7.0.13" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" - integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== - "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -1883,23 +1878,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-formats@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv@8.10.0, ajv@^8.0.0: - version "8.10.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.10.0.tgz#e573f719bd3af069017e3b66538ab968d040e54d" - integrity sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -2151,13 +2129,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2467,11 +2438,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" - integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== - common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -3322,17 +3288,6 @@ glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.3: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4299,11 +4254,6 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -4768,13 +4718,6 @@ minimatch@^3.0.5, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -5232,11 +5175,6 @@ npmlog@^6.0.0, npmlog@^6.0.1: gauge "^4.0.0" set-blocking "^2.0.0" -oas-schema-walker@1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz#74c3cd47b70ff8e0b19adada14455b5d3ac38a22" - integrity sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ== - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -5769,11 +5707,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -5849,11 +5782,6 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-stable-stringify@^2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" - integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6397,19 +6325,6 @@ ts-jest@29.1.2: semver "^7.5.3" yargs-parser "^21.0.1" -ts-json-schema-generator@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz#9f5cea606c27ebcf13060157542ac1d3b225430f" - integrity sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA== - dependencies: - "@types/json-schema" "^7.0.12" - commander "^11.0.0" - glob "^8.0.3" - json5 "^2.2.3" - normalize-path "^3.0.0" - safe-stable-stringify "^2.4.3" - typescript "~5.3.2" - ts-node@10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" @@ -6493,11 +6408,6 @@ typescript@5.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== -typescript@~5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== - uglify-js@^3.1.4: version "3.12.5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.5.tgz#83241496087c640efe9dfc934832e71725aba008"