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.
@@ -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,9 @@
1
+ export { OutputFormatter, createFormatter } from "./output";
2
+ export {
3
+ parseDate,
4
+ formatDate,
5
+ getPeriodRange,
6
+ getDefaultInterval,
7
+ isValidPeriod,
8
+ isValidInterval,
9
+ } from "./date";
@@ -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
+ }