/
historical.ts
201 lines (173 loc) · 5.9 KB
/
historical.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import type {
ModuleOptions,
ModuleOptionsWithValidateTrue,
ModuleOptionsWithValidateFalse,
ModuleThis,
} from "../lib/moduleCommon.js";
export type HistoricalHistoryResult = Array<HistoricalRowHistory>;
export type HistoricalDividendsResult = Array<HistoricalRowDividend>;
export type HistoricalStockSplitsResult = Array<HistoricalRowStockSplit>;
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;
}
export interface HistoricalRowDividend {
date: Date;
dividends: number;
}
export interface HistoricalRowStockSplit {
date: Date;
stockSplits: string;
}
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,
}
export interface HistoricalOptionsEventsHistory extends HistoricalOptions {
events?: "history";
}
export interface HistoricalOptionsEventsDividends extends HistoricalOptions {
events: "dividends";
}
export interface HistoricalOptionsEventsSplit extends HistoricalOptions {
events: "split";
}
const queryOptionsDefaults: Omit<HistoricalOptions, "period1"> = {
interval: "1d",
events: "history",
includeAdjustedClose: true,
};
export default function historical(
this: ModuleThis,
symbol: string,
queryOptionsOverrides: HistoricalOptionsEventsHistory,
moduleOptions?: ModuleOptionsWithValidateTrue
): Promise<HistoricalHistoryResult>;
export default function historical(
this: ModuleThis,
symbol: string,
queryOptionsOverrides: HistoricalOptionsEventsDividends,
moduleOptions?: ModuleOptionsWithValidateTrue
): Promise<HistoricalDividendsResult>;
export default function historical(
this: ModuleThis,
symbol: string,
queryOptionsOverrides: HistoricalOptionsEventsSplit,
moduleOptions?: ModuleOptionsWithValidateTrue
): Promise<HistoricalStockSplitsResult>;
export default function historical(
this: ModuleThis,
symbol: string,
queryOptionsOverrides: HistoricalOptions,
moduleOptions?: ModuleOptionsWithValidateFalse
): Promise<any>;
export default function historical(
this: ModuleThis,
symbol: string,
queryOptionsOverrides: HistoricalOptions,
moduleOptions?: ModuleOptions
): Promise<any> {
let schemaKey;
if (
!queryOptionsOverrides.events ||
queryOptionsOverrides.events === "history"
)
schemaKey = "#/definitions/HistoricalHistoryResult";
else if (queryOptionsOverrides.events === "dividends")
schemaKey = "#/definitions/HistoricalDividendsResult";
else if (queryOptionsOverrides.events === "split")
schemaKey = "#/definitions/HistoricalStockSplitsResult";
else throw new Error("No such event type:" + queryOptionsOverrides.events);
return this._moduleExec({
moduleName: "historical",
query: {
assertSymbol: symbol,
url: "https://${YF_QUERY_HOST}/v7/finance/download/" + symbol,
schemaKey: "#/definitions/HistoricalOptions",
defaults: queryOptionsDefaults,
overrides: queryOptionsOverrides,
fetchType: "csv",
transformWith(queryOptions: HistoricalOptions) {
if (!queryOptions.period2) queryOptions.period2 = new Date();
const dates = ["period1", "period2"] as const;
for (const fieldName of dates) {
const value = queryOptions[fieldName];
if (value instanceof Date)
queryOptions[fieldName] = Math.floor(value.getTime() / 1000);
else if (typeof value === "string") {
const timestamp = new Date(value as string).getTime();
if (isNaN(timestamp))
throw new Error(
"yahooFinance.historical() option '" +
fieldName +
"' invalid date provided: '" +
value +
"'"
);
queryOptions[fieldName] = Math.floor(timestamp / 1000);
}
}
if (queryOptions.period1 === queryOptions.period2) {
throw new Error(
"yahooFinance.historical() options `period1` and `period2` " +
"cannot share the same value."
);
}
return queryOptions;
},
},
result: {
schemaKey,
transformWith(result: any) {
if (result.length === 0) return result;
const filteredResults = [];
const fieldCount = Object.keys(result[0]).length;
// Count number of null values in object (1-level deep)
function nullFieldCount(object: Object) {
let nullCount = 0;
for (const val of Object.values(object))
if (val === null) nullCount++;
return nullCount;
}
for (const row of result) {
const nullCount = nullFieldCount(row);
if (nullCount === 0) {
// No nulls is a legit (regular) result
filteredResults.push(row);
} else if (nullCount !== fieldCount - 1 /* skip "date" */) {
// Unhandled case: some but not all values are null.
// Note: no need to check for null "date", validation does it for us
console.error(nullCount, row);
throw new Error(
"Historical returned a result with SOME (but not " +
"all) null values. Please report this, and provide the " +
"query that caused it."
);
} else {
// All fields (except "date") are null: silently skip (no-op)
}
}
/*
* We may consider, for future optimization, to count rows and create
* new array in advance, and skip consecutive blocks of null results.
* Of doubtful utility.
*/
return filteredResults;
},
},
moduleOptions,
});
}