ticker-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +111 -0
- package/README.md +192 -0
- package/bun.lock +91 -0
- package/index.ts +40 -0
- package/package.json +24 -0
- package/src/commands/chart.ts +100 -0
- package/src/commands/index.ts +10 -0
- package/src/commands/indicator.ts +152 -0
- package/src/commands/insights.ts +42 -0
- package/src/commands/news.ts +46 -0
- package/src/commands/options.ts +69 -0
- package/src/commands/quote.ts +34 -0
- package/src/commands/recommendations.ts +42 -0
- package/src/commands/screener.ts +58 -0
- package/src/commands/search.ts +49 -0
- package/src/commands/summary.ts +50 -0
- package/src/services/index.ts +3 -0
- package/src/services/indicators.ts +188 -0
- package/src/services/yahoo.ts +205 -0
- package/src/types/index.ts +47 -0
- package/src/utils/date.ts +107 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/output.ts +40 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { sma, ema, rsi, macd, bb } from "indicatorts";
|
|
2
|
+
import type { ChartResultArrayQuote } from "yahoo-finance2/modules/chart";
|
|
3
|
+
import type { IndicatorDataPoint, IndicatorResult } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* Indicator configuration options
|
|
6
|
+
*/
|
|
7
|
+
export interface IndicatorConfig {
|
|
8
|
+
period?: number;
|
|
9
|
+
length?: number;
|
|
10
|
+
fast?: number;
|
|
11
|
+
slow?: number;
|
|
12
|
+
signal?: number;
|
|
13
|
+
short?: number;
|
|
14
|
+
long?: number;
|
|
15
|
+
stddev?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Indicator service for calculating technical indicators
|
|
20
|
+
*/
|
|
21
|
+
export class IndicatorService {
|
|
22
|
+
/**
|
|
23
|
+
* Calculate Simple Moving Average (SMA)
|
|
24
|
+
*/
|
|
25
|
+
calculateSMA(
|
|
26
|
+
data: ChartResultArrayQuote[],
|
|
27
|
+
config: IndicatorConfig = {},
|
|
28
|
+
): IndicatorResult {
|
|
29
|
+
const period = config.period ?? config.length ?? 20;
|
|
30
|
+
const closes = data.map((d) => d.close!);
|
|
31
|
+
|
|
32
|
+
const smaValues = sma(closes, { period });
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
symbol: "",
|
|
36
|
+
indicator: "SMA",
|
|
37
|
+
config: { length: period },
|
|
38
|
+
data: this.alignIndicatorData(data, smaValues),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Calculate Exponential Moving Average (EMA)
|
|
44
|
+
*/
|
|
45
|
+
calculateEMA(
|
|
46
|
+
data: ChartResultArrayQuote[],
|
|
47
|
+
config: IndicatorConfig = {},
|
|
48
|
+
): IndicatorResult {
|
|
49
|
+
const period = config.period ?? config.length ?? 20;
|
|
50
|
+
const closes = data.map((d) => d.close!);
|
|
51
|
+
|
|
52
|
+
const emaValues = ema(closes, { period });
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
symbol: "",
|
|
56
|
+
indicator: "EMA",
|
|
57
|
+
config: { length: period },
|
|
58
|
+
data: this.alignIndicatorData(data, emaValues),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Calculate Relative Strength Index (RSI)
|
|
64
|
+
*/
|
|
65
|
+
calculateRSI(
|
|
66
|
+
data: ChartResultArrayQuote[],
|
|
67
|
+
config: IndicatorConfig = {},
|
|
68
|
+
): IndicatorResult {
|
|
69
|
+
const period = config.period ?? config.length ?? 14;
|
|
70
|
+
const closes = data.map((d) => d.close!);
|
|
71
|
+
|
|
72
|
+
const rsiValues = rsi(closes, { period });
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
symbol: "",
|
|
76
|
+
indicator: "RSI",
|
|
77
|
+
config: { length: period },
|
|
78
|
+
data: this.alignIndicatorData(data, rsiValues),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculate MACD (Moving Average Convergence Divergence)
|
|
84
|
+
*/
|
|
85
|
+
calculateMACD(
|
|
86
|
+
data: ChartResultArrayQuote[],
|
|
87
|
+
config: IndicatorConfig = {},
|
|
88
|
+
): IndicatorResult {
|
|
89
|
+
const fast = config.fast ?? config.short ?? 12;
|
|
90
|
+
const slow = config.slow ?? config.long ?? 26;
|
|
91
|
+
const signal = config.signal ?? 9;
|
|
92
|
+
const closes = data.map((d) => d.close!);
|
|
93
|
+
|
|
94
|
+
const macdResult = macd(closes, { fast, slow, signal });
|
|
95
|
+
|
|
96
|
+
const indicatorData: IndicatorDataPoint[] = data.map((d, i) => ({
|
|
97
|
+
date: d.date,
|
|
98
|
+
macd: macdResult.macdLine[i],
|
|
99
|
+
signal: macdResult.signalLine[i],
|
|
100
|
+
histogram:
|
|
101
|
+
macdResult.macdLine[i] !== undefined &&
|
|
102
|
+
macdResult.signalLine[i] !== undefined
|
|
103
|
+
? macdResult.macdLine[i]! - macdResult.signalLine[i]!
|
|
104
|
+
: undefined,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
symbol: "",
|
|
109
|
+
indicator: "MACD",
|
|
110
|
+
config: { short: fast, long: slow, signal },
|
|
111
|
+
data: indicatorData,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Calculate Bollinger Bands
|
|
117
|
+
*/
|
|
118
|
+
calculateBollingerBands(
|
|
119
|
+
data: ChartResultArrayQuote[],
|
|
120
|
+
config: IndicatorConfig = {},
|
|
121
|
+
): IndicatorResult {
|
|
122
|
+
const period = config.period ?? config.length ?? 20;
|
|
123
|
+
const closes = data.map((d) => d.close!);
|
|
124
|
+
|
|
125
|
+
const bbResult = bb(closes, { period });
|
|
126
|
+
|
|
127
|
+
const indicatorData: IndicatorDataPoint[] = data.map((d, i) => ({
|
|
128
|
+
date: d.date,
|
|
129
|
+
upper: bbResult.upper[i],
|
|
130
|
+
middle: bbResult.middle[i],
|
|
131
|
+
lower: bbResult.lower[i],
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
symbol: "",
|
|
136
|
+
indicator: "BollingerBands",
|
|
137
|
+
config: { length: period },
|
|
138
|
+
data: indicatorData,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Calculate indicator by name
|
|
144
|
+
*/
|
|
145
|
+
calculate(
|
|
146
|
+
indicator: string,
|
|
147
|
+
data: ChartResultArrayQuote[],
|
|
148
|
+
config: IndicatorConfig = {},
|
|
149
|
+
): IndicatorResult {
|
|
150
|
+
switch (indicator.toLowerCase()) {
|
|
151
|
+
case "sma":
|
|
152
|
+
return this.calculateSMA(data, config);
|
|
153
|
+
case "ema":
|
|
154
|
+
return this.calculateEMA(data, config);
|
|
155
|
+
case "rsi":
|
|
156
|
+
return this.calculateRSI(data, config);
|
|
157
|
+
case "macd":
|
|
158
|
+
return this.calculateMACD(data, config);
|
|
159
|
+
case "bb":
|
|
160
|
+
case "bollinger":
|
|
161
|
+
case "bollingerbands":
|
|
162
|
+
return this.calculateBollingerBands(data, config);
|
|
163
|
+
default:
|
|
164
|
+
throw new Error(`Unknown indicator: ${indicator}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Align indicator values with dates
|
|
170
|
+
* Indicators often have undefined values at the start
|
|
171
|
+
*/
|
|
172
|
+
private alignIndicatorData(
|
|
173
|
+
data: ChartResultArrayQuote[],
|
|
174
|
+
values: number[],
|
|
175
|
+
): IndicatorDataPoint[] {
|
|
176
|
+
return data.map((d, i) => ({
|
|
177
|
+
date: d.date,
|
|
178
|
+
value: values[i],
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create an IndicatorService instance
|
|
185
|
+
*/
|
|
186
|
+
export function createIndicatorService(): IndicatorService {
|
|
187
|
+
return new IndicatorService();
|
|
188
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import YahooFinance from "yahoo-finance2";
|
|
2
|
+
import type { PeriodPreset, IntervalType } from "../types";
|
|
3
|
+
import { parseDate, getDefaultInterval } from "../utils";
|
|
4
|
+
import type { QuoteResponseArray } from "yahoo-finance2/modules/quote";
|
|
5
|
+
import type {
|
|
6
|
+
ChartOptions,
|
|
7
|
+
ChartResultArray,
|
|
8
|
+
} from "yahoo-finance2/modules/chart";
|
|
9
|
+
import type {
|
|
10
|
+
SearchOptions,
|
|
11
|
+
SearchResult,
|
|
12
|
+
} from "yahoo-finance2/modules/search";
|
|
13
|
+
import type {
|
|
14
|
+
OptionsOptions,
|
|
15
|
+
OptionsResult,
|
|
16
|
+
} from "yahoo-finance2/modules/options";
|
|
17
|
+
import type {
|
|
18
|
+
QuoteSummaryModules,
|
|
19
|
+
QuoteSummaryResult,
|
|
20
|
+
} from "yahoo-finance2/modules/quoteSummary";
|
|
21
|
+
import type {
|
|
22
|
+
ScreenerResult,
|
|
23
|
+
PredefinedScreenerModules,
|
|
24
|
+
} from "yahoo-finance2/modules/screener";
|
|
25
|
+
import type { InsightsResult } from "yahoo-finance2/modules/insights";
|
|
26
|
+
import type { RecommendationsBySymbolResponseArray } from "yahoo-finance2/modules/recommendationsBySymbol";
|
|
27
|
+
|
|
28
|
+
// Create a single instance of YahooFinance
|
|
29
|
+
const yahooFinance = new YahooFinance({
|
|
30
|
+
suppressNotices: ["yahooSurvey"],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Yahoo Finance service wrapper
|
|
35
|
+
*/
|
|
36
|
+
export class YahooService {
|
|
37
|
+
/**
|
|
38
|
+
* Get stock quotes for one or more symbols
|
|
39
|
+
*/
|
|
40
|
+
async getQuotes(symbols: string | string[]): Promise<QuoteResponseArray> {
|
|
41
|
+
const symbolArray = Array.isArray(symbols) ? symbols : [symbols];
|
|
42
|
+
|
|
43
|
+
return await yahooFinance.quote(symbolArray);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get chart/historical data for a symbol
|
|
48
|
+
*/
|
|
49
|
+
async getChart(
|
|
50
|
+
symbol: string,
|
|
51
|
+
options: {
|
|
52
|
+
period?: PeriodPreset;
|
|
53
|
+
start?: string;
|
|
54
|
+
end?: string;
|
|
55
|
+
interval?: IntervalType;
|
|
56
|
+
} = {},
|
|
57
|
+
): Promise<ChartResultArray> {
|
|
58
|
+
const { period, start, end, interval } = options;
|
|
59
|
+
|
|
60
|
+
const chartOptions: ChartOptions = {
|
|
61
|
+
period1: new Date(),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Calculate date range from period or use provided dates
|
|
65
|
+
if (start && end) {
|
|
66
|
+
chartOptions.period1 = parseDate(start);
|
|
67
|
+
chartOptions.period2 = parseDate(end);
|
|
68
|
+
} else {
|
|
69
|
+
// Use period to calculate period1 (period2 defaults to now)
|
|
70
|
+
const now = new Date();
|
|
71
|
+
const period1 = new Date();
|
|
72
|
+
|
|
73
|
+
const periodValue = period ?? "1mo";
|
|
74
|
+
switch (periodValue) {
|
|
75
|
+
case "1d":
|
|
76
|
+
period1.setDate(now.getDate() - 1);
|
|
77
|
+
break;
|
|
78
|
+
case "5d":
|
|
79
|
+
period1.setDate(now.getDate() - 5);
|
|
80
|
+
break;
|
|
81
|
+
case "1wk":
|
|
82
|
+
period1.setDate(now.getDate() - 7);
|
|
83
|
+
break;
|
|
84
|
+
case "1mo":
|
|
85
|
+
period1.setMonth(now.getMonth() - 1);
|
|
86
|
+
break;
|
|
87
|
+
case "3mo":
|
|
88
|
+
period1.setMonth(now.getMonth() - 3);
|
|
89
|
+
break;
|
|
90
|
+
case "6mo":
|
|
91
|
+
period1.setMonth(now.getMonth() - 6);
|
|
92
|
+
break;
|
|
93
|
+
case "1y":
|
|
94
|
+
period1.setFullYear(now.getFullYear() - 1);
|
|
95
|
+
break;
|
|
96
|
+
case "2y":
|
|
97
|
+
period1.setFullYear(now.getFullYear() - 2);
|
|
98
|
+
break;
|
|
99
|
+
case "5y":
|
|
100
|
+
period1.setFullYear(now.getFullYear() - 5);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
chartOptions.period1 = period1;
|
|
105
|
+
chartOptions.period2 = now;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
chartOptions.interval = interval ?? getDefaultInterval(period ?? "1mo");
|
|
109
|
+
|
|
110
|
+
return (await yahooFinance.chart(symbol, chartOptions)) as ChartResultArray;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get news for a symbol
|
|
115
|
+
*/
|
|
116
|
+
async getNews(symbol: string, limit: number = 10): Promise<SearchResult> {
|
|
117
|
+
return yahooFinance.search(symbol, {
|
|
118
|
+
newsCount: limit,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get insights for a symbol
|
|
124
|
+
*/
|
|
125
|
+
async getInsights(symbol: string): Promise<InsightsResult> {
|
|
126
|
+
const result = await yahooFinance.insights(symbol);
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get options chain for a symbol
|
|
132
|
+
*/
|
|
133
|
+
async getOptions(
|
|
134
|
+
symbol: string,
|
|
135
|
+
expirationDate?: string,
|
|
136
|
+
): Promise<OptionsResult> {
|
|
137
|
+
const options: OptionsOptions = {};
|
|
138
|
+
if (expirationDate) {
|
|
139
|
+
options.date = parseDate(expirationDate);
|
|
140
|
+
}
|
|
141
|
+
return yahooFinance.options(symbol, options);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get quote summary with specified modules
|
|
146
|
+
*/
|
|
147
|
+
async getSummary(
|
|
148
|
+
symbol: string,
|
|
149
|
+
modules?: QuoteSummaryModules[],
|
|
150
|
+
): Promise<QuoteSummaryResult> {
|
|
151
|
+
const defaultModules: QuoteSummaryModules[] = [
|
|
152
|
+
"summaryProfile",
|
|
153
|
+
"summaryDetail",
|
|
154
|
+
"defaultKeyStatistics",
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
return yahooFinance.quoteSummary(symbol, {
|
|
158
|
+
modules: modules ?? defaultModules,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Get analyst recommendations for a symbol
|
|
164
|
+
*/
|
|
165
|
+
async getRecommendations(
|
|
166
|
+
symbol: string,
|
|
167
|
+
): Promise<RecommendationsBySymbolResponseArray> {
|
|
168
|
+
return yahooFinance.recommendationsBySymbol([symbol]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get screener results
|
|
173
|
+
*/
|
|
174
|
+
async getScreener(query: PredefinedScreenerModules): Promise<ScreenerResult> {
|
|
175
|
+
return (await yahooFinance.screener(query, undefined, {
|
|
176
|
+
validateResult: false,
|
|
177
|
+
})) as ScreenerResult;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Search for symbols
|
|
182
|
+
*/
|
|
183
|
+
async search(
|
|
184
|
+
query: string,
|
|
185
|
+
options: { includeQuotes?: boolean; includeNews?: boolean } = {},
|
|
186
|
+
): Promise<SearchResult> {
|
|
187
|
+
const searchOptions: SearchOptions = {};
|
|
188
|
+
|
|
189
|
+
if (options.includeQuotes) {
|
|
190
|
+
searchOptions.quotesCount = 10;
|
|
191
|
+
}
|
|
192
|
+
if (options.includeNews) {
|
|
193
|
+
searchOptions.newsCount = 5;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return yahooFinance.search(query, searchOptions);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create a YahooService instance
|
|
202
|
+
*/
|
|
203
|
+
export function createYahooService(): YahooService {
|
|
204
|
+
return new YahooService();
|
|
205
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface IndicatorDataPoint {
|
|
2
|
+
date: Date;
|
|
3
|
+
value?: number;
|
|
4
|
+
macd?: number;
|
|
5
|
+
signal?: number;
|
|
6
|
+
histogram?: number;
|
|
7
|
+
upper?: number;
|
|
8
|
+
middle?: number;
|
|
9
|
+
lower?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IndicatorDataPoint {
|
|
13
|
+
date: Date;
|
|
14
|
+
value?: number;
|
|
15
|
+
macd?: number;
|
|
16
|
+
signal?: number;
|
|
17
|
+
histogram?: number;
|
|
18
|
+
upper?: number;
|
|
19
|
+
middle?: number;
|
|
20
|
+
lower?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface IndicatorResult {
|
|
23
|
+
symbol: string;
|
|
24
|
+
indicator: string;
|
|
25
|
+
config?: Record<string, number>;
|
|
26
|
+
data: IndicatorDataPoint[];
|
|
27
|
+
}
|
|
28
|
+
// Period presets
|
|
29
|
+
export type PeriodPreset =
|
|
30
|
+
| "1d"
|
|
31
|
+
| "5d"
|
|
32
|
+
| "1wk"
|
|
33
|
+
| "1mo"
|
|
34
|
+
| "3mo"
|
|
35
|
+
| "6mo"
|
|
36
|
+
| "1y"
|
|
37
|
+
| "2y"
|
|
38
|
+
| "5y";
|
|
39
|
+
export type IntervalType =
|
|
40
|
+
| "1m"
|
|
41
|
+
| "5m"
|
|
42
|
+
| "15m"
|
|
43
|
+
| "30m"
|
|
44
|
+
| "1h"
|
|
45
|
+
| "1d"
|
|
46
|
+
| "1wk"
|
|
47
|
+
| "1mo";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { PeriodPreset, IntervalType } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a date string or return the Date object
|
|
5
|
+
*/
|
|
6
|
+
export function parseDate(date: string | Date): Date {
|
|
7
|
+
if (date instanceof Date) return date;
|
|
8
|
+
const parsed = new Date(date);
|
|
9
|
+
if (isNaN(parsed.getTime())) {
|
|
10
|
+
throw new Error(`Invalid date: ${date}`);
|
|
11
|
+
}
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format a date as YYYY-MM-DD
|
|
17
|
+
*/
|
|
18
|
+
export function formatDate(date: Date): string {
|
|
19
|
+
return date.toISOString().split("T")[0] ?? "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get date range for a period preset
|
|
24
|
+
*/
|
|
25
|
+
export function getPeriodRange(period: PeriodPreset): {
|
|
26
|
+
start: Date;
|
|
27
|
+
end: Date;
|
|
28
|
+
} {
|
|
29
|
+
const end = new Date();
|
|
30
|
+
const start = new Date();
|
|
31
|
+
|
|
32
|
+
switch (period) {
|
|
33
|
+
case "1d":
|
|
34
|
+
start.setDate(end.getDate() - 1);
|
|
35
|
+
break;
|
|
36
|
+
case "5d":
|
|
37
|
+
start.setDate(end.getDate() - 5);
|
|
38
|
+
break;
|
|
39
|
+
case "1wk":
|
|
40
|
+
start.setDate(end.getDate() - 7);
|
|
41
|
+
break;
|
|
42
|
+
case "1mo":
|
|
43
|
+
start.setMonth(end.getMonth() - 1);
|
|
44
|
+
break;
|
|
45
|
+
case "3mo":
|
|
46
|
+
start.setMonth(end.getMonth() - 3);
|
|
47
|
+
break;
|
|
48
|
+
case "6mo":
|
|
49
|
+
start.setMonth(end.getMonth() - 6);
|
|
50
|
+
break;
|
|
51
|
+
case "1y":
|
|
52
|
+
start.setFullYear(end.getFullYear() - 1);
|
|
53
|
+
break;
|
|
54
|
+
case "2y":
|
|
55
|
+
start.setFullYear(end.getFullYear() - 2);
|
|
56
|
+
break;
|
|
57
|
+
case "5y":
|
|
58
|
+
start.setFullYear(end.getFullYear() - 5);
|
|
59
|
+
break;
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`Unknown period: ${period}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { start, end };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the default interval for a period
|
|
69
|
+
*/
|
|
70
|
+
export function getDefaultInterval(period: PeriodPreset): IntervalType {
|
|
71
|
+
switch (period) {
|
|
72
|
+
case "1d":
|
|
73
|
+
case "5d":
|
|
74
|
+
return "15m";
|
|
75
|
+
case "1wk":
|
|
76
|
+
return "1h";
|
|
77
|
+
case "1mo":
|
|
78
|
+
case "3mo":
|
|
79
|
+
return "1d";
|
|
80
|
+
case "6mo":
|
|
81
|
+
case "1y":
|
|
82
|
+
case "2y":
|
|
83
|
+
return "1d";
|
|
84
|
+
case "5y":
|
|
85
|
+
return "1wk";
|
|
86
|
+
default:
|
|
87
|
+
return "1d";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate a period preset
|
|
93
|
+
*/
|
|
94
|
+
export function isValidPeriod(period: string): period is PeriodPreset {
|
|
95
|
+
return ["1d", "5d", "1wk", "1mo", "3mo", "6mo", "1y", "2y", "5y"].includes(
|
|
96
|
+
period,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate an interval type
|
|
102
|
+
*/
|
|
103
|
+
export function isValidInterval(interval: string): interval is IntervalType {
|
|
104
|
+
return ["1m", "5m", "15m", "30m", "1h", "1d", "1wk", "1mo"].includes(
|
|
105
|
+
interval,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ChartResultArrayQuote } from "yahoo-finance2/modules/chart";
|
|
2
|
+
import type { IndicatorDataPoint } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Output formatter for JSON and table formats
|
|
6
|
+
*/
|
|
7
|
+
export class OutputFormatter {
|
|
8
|
+
private useTable: boolean;
|
|
9
|
+
private pretty: boolean;
|
|
10
|
+
|
|
11
|
+
constructor(options: { table?: boolean; pretty?: boolean } = {}) {
|
|
12
|
+
this.useTable = options.table ?? false;
|
|
13
|
+
this.pretty = options.pretty ?? true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format any data based on output settings
|
|
18
|
+
*/
|
|
19
|
+
format(data: unknown): string {
|
|
20
|
+
return this.formatJson(data);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format as JSON string
|
|
25
|
+
*/
|
|
26
|
+
private formatJson(data: unknown): string {
|
|
27
|
+
return JSON.stringify(data, null, this.pretty ? 2 : 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// TODO format as table
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create an output formatter with options
|
|
35
|
+
*/
|
|
36
|
+
export function createFormatter(
|
|
37
|
+
options: { table?: boolean; pretty?: boolean } = {},
|
|
38
|
+
): OutputFormatter {
|
|
39
|
+
return new OutputFormatter(options);
|
|
40
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|