11import prompts from 'prompts' ;
2- import type { PromptResult , BinanceInterval } from './types' ;
2+ import { Command } from 'commander' ;
3+ import type { BinanceInterval , OutputFormat , PromptResult } from './types' ;
34import { getKline } from './klines' ;
45import { formatDate , saveKline } from './utils' ;
5- import { Command } from 'commander' ;
66
77const VALID_INTERVALS : BinanceInterval [ ] = [
88 '1m' ,
@@ -68,6 +68,16 @@ const questions: Array<prompts.PromptObject> = [
6868 message : 'The path of the file that will be saved:' ,
6969 initial : `${ process . cwd ( ) } /` ,
7070 } ,
71+ {
72+ type : 'select' ,
73+ name : 'format' ,
74+ message : 'Output format:' ,
75+ choices : [
76+ { title : 'JSON' , value : 'json' } ,
77+ { title : 'CSV' , value : 'csv' } ,
78+ ] ,
79+ initial : 0 ,
80+ } ,
7181] ;
7282
7383interface CliOptions {
@@ -76,12 +86,17 @@ interface CliOptions {
7686 start ?: string ;
7787 end ?: string ;
7888 output ?: string ;
89+ format ?: string ;
7990}
8091
8192function isValidInterval ( interval : string ) : interval is BinanceInterval {
8293 return VALID_INTERVALS . includes ( interval as BinanceInterval ) ;
8394}
8495
96+ function isValidFormat ( format : string ) : format is OutputFormat {
97+ return format === 'json' || format === 'csv' ;
98+ }
99+
85100function parseDate ( dateStr : string ) : Date | null {
86101 const date = new Date ( dateStr ) ;
87102 if ( Number . isNaN ( date . getTime ( ) ) ) {
@@ -122,6 +137,10 @@ function validateOptions(options: CliOptions): {
122137 }
123138 }
124139
140+ if ( options . format && ! isValidFormat ( options . format ) ) {
141+ errors . push ( `Invalid format "${ options . format } ". Valid formats: json, csv` ) ;
142+ }
143+
125144 return { valid : errors . length === 0 , errors } ;
126145}
127146
@@ -131,14 +150,15 @@ function hasAllRequiredOptions(options: CliOptions): boolean {
131150 options . interval &&
132151 options . start &&
133152 options . end &&
134- options . output
153+ options . output &&
154+ options . format
135155 ) ;
136156}
137157
138158async function promptUser ( ) : Promise < Partial < PromptResult > > {
139- const { pair, interval, startDate, endDate, fileName } =
159+ const { pair, interval, startDate, endDate, fileName, format } =
140160 await prompts ( questions ) ;
141- return { pair, interval, startDate, endDate, fileName } ;
161+ return { pair, interval, startDate, endDate, fileName, format } ;
142162}
143163
144164async function promptMissingOptions (
@@ -187,6 +207,12 @@ async function promptMissingOptions(
187207 missingQuestions . push ( questions [ 4 ] ) ;
188208 }
189209
210+ if ( options . format && isValidFormat ( options . format ) ) {
211+ providedValues . format = options . format ;
212+ } else {
213+ missingQuestions . push ( questions [ 5 ] ) ;
214+ }
215+
190216 if ( missingQuestions . length > 0 ) {
191217 const answers = await prompts ( missingQuestions ) ;
192218 return { ...providedValues , ...answers } ;
@@ -196,7 +222,7 @@ async function promptMissingOptions(
196222}
197223
198224async function downloadKlines ( config : PromptResult ) : Promise < void > {
199- const { pair, interval, startDate, endDate, fileName } = config ;
225+ const { pair, interval, startDate, endDate, fileName, format } = config ;
200226
201227 const kLines = await getKline ( pair , interval , startDate , endDate ) . catch (
202228 ( error ) => {
@@ -206,10 +232,11 @@ async function downloadKlines(config: PromptResult): Promise<void> {
206232 ) ;
207233
208234 if ( kLines ) {
235+ const extension = format === 'csv' ? 'csv' : 'json' ;
209236 const outputPath =
210237 fileName +
211- `${ pair } _${ interval } _${ formatDate ( startDate ) } _${ formatDate ( endDate ) } .json ` ;
212- saveKline ( outputPath , kLines ) ;
238+ `${ pair } _${ interval } _${ formatDate ( startDate ) } _${ formatDate ( endDate ) } .${ extension } ` ;
239+ saveKline ( outputPath , kLines , format ) ;
213240 console . log ( `Downloaded ${ kLines . length } klines to ${ outputPath } ` ) ;
214241 }
215242}
@@ -229,16 +256,18 @@ async function processWithOptions(options: CliOptions): Promise<void> {
229256 const startDate = parseDate ( options . start as string ) as Date ;
230257 const endDate = parseDate ( options . end as string ) as Date ;
231258 const fileName = options . output as string ;
259+ const format = options . format as OutputFormat ;
232260
233- await downloadKlines ( { pair, interval, startDate, endDate, fileName } ) ;
261+ await downloadKlines ( { pair, interval, startDate, endDate, fileName, format } ) ;
234262 } else {
235263 const result = await promptMissingOptions ( options ) ;
236264 if (
237265 ! result . pair ||
238266 ! result . interval ||
239267 ! result . startDate ||
240268 ! result . endDate ||
241- ! result . fileName
269+ ! result . fileName ||
270+ ! result . format
242271 ) {
243272 console . error ( 'Missing required information' ) ;
244273 process . exit ( 1 ) ;
@@ -254,7 +283,8 @@ async function processInteractive(): Promise<void> {
254283 ! result . interval ||
255284 ! result . startDate ||
256285 ! result . endDate ||
257- ! result . fileName
286+ ! result . fileName ||
287+ ! result . format
258288 ) {
259289 console . error ( 'Missing required information' ) ;
260290 process . exit ( 1 ) ;
@@ -273,7 +303,7 @@ export async function runCommand(): Promise<void> {
273303 program
274304 . command ( 'download' )
275305 . description (
276- 'Download a JSON file containing historical klines from Binance API' ,
306+ 'Download historical klines from Binance API' ,
277307 )
278308 . option ( '-p, --pair <symbol>' , 'Trading pair (e.g., BTCUSDT, ETHUSDT)' )
279309 . option (
@@ -286,6 +316,7 @@ export async function runCommand(): Promise<void> {
286316 '-o, --output <path>' ,
287317 'Output directory path (filename is auto-generated)' ,
288318 )
319+ . option ( '-f, --format <format>' , 'Output format (json, csv)' , 'json' )
289320 . action ( async ( options : CliOptions ) => {
290321 const hasAnyOption =
291322 options . pair ||
0 commit comments