binance-historical
Version:
Download historical klines from binance api
286 lines (285 loc) • 9.17 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runCommand = runCommand;
const prompts_1 = __importDefault(require("prompts"));
const commander_1 = require("commander");
const klines_1 = require("./klines");
const utils_1 = require("./utils");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const VALID_INTERVALS = [
'1m',
'3m',
'5m',
'15m',
'30m',
'1h',
'2h',
'4h',
'6h',
'8h',
'12h',
'1d',
'3d',
'1w',
];
const questions = [
{
type: 'text',
name: 'pair',
message: 'Pair that you want to track:',
initial: 'ETHUSDT',
},
{
type: 'select',
name: 'interval',
message: 'The interval:',
choices: [
{ title: '1 minute', value: '1m' },
{ title: '3 minutes', value: '3m' },
{ title: '5 minutes', value: '5m' },
{ title: '15 minutes', value: '15m' },
{ title: '30 minutes', value: '30m' },
{ title: '1 hour', value: '1h' },
{ title: '2 hours', value: '2h' },
{ title: '4 hours', value: '4h' },
{ title: '6 hours', value: '6h' },
{ title: '8 hours', value: '8h' },
{ title: '12 hours', value: '12h' },
{ title: '1 day', value: '1d' },
{ title: '3 days', value: '3d' },
{ title: '1 week', value: '1w' },
],
initial: 7,
},
{
type: 'date',
name: 'startDate',
message: 'The starting date of the interval:',
initial: new Date(),
},
{
type: 'date',
name: 'endDate',
message: 'The ending date of the interval:',
initial: new Date(),
},
{
type: 'text',
name: 'fileName',
message: 'The path of the file that will be saved:',
initial: `${process.cwd()}/`,
},
{
type: 'select',
name: 'format',
message: 'Output format:',
choices: [
{ title: 'JSON', value: 'json' },
{ title: 'CSV', value: 'csv' },
],
initial: 0,
},
];
function isValidInterval(interval) {
return VALID_INTERVALS.includes(interval);
}
function isValidFormat(format) {
return format === 'json' || format === 'csv';
}
function parseDate(dateStr) {
const date = new Date(dateStr);
if (Number.isNaN(date.getTime())) {
return null;
}
return date;
}
function validateOptions(options) {
const errors = [];
if (options.interval && !isValidInterval(options.interval)) {
errors.push(`Invalid interval "${options.interval}". Valid intervals: ${VALID_INTERVALS.join(', ')}`);
}
if (options.start && !parseDate(options.start)) {
errors.push(`Invalid start date "${options.start}". Use format: YYYY-MM-DD or ISO 8601`);
}
if (options.end && !parseDate(options.end)) {
errors.push(`Invalid end date "${options.end}". Use format: YYYY-MM-DD or ISO 8601`);
}
if (options.start && options.end) {
const startDate = parseDate(options.start);
const endDate = parseDate(options.end);
if (startDate && endDate && startDate >= endDate) {
errors.push('Start date must be before end date');
}
}
if (options.format && !isValidFormat(options.format)) {
errors.push(`Invalid format "${options.format}". Valid formats: json, csv`);
}
return { valid: errors.length === 0, errors };
}
function hasAllRequiredOptions(options) {
return !!(options.pair &&
options.interval &&
options.start &&
options.end &&
options.output &&
options.format);
}
async function promptUser() {
const { pair, interval, startDate, endDate, fileName, format } = await (0, prompts_1.default)(questions);
return { pair, interval, startDate, endDate, fileName, format };
}
async function promptMissingOptions(options) {
const providedValues = {};
const missingQuestions = [];
if (options.pair) {
providedValues.pair = options.pair;
}
else {
missingQuestions.push(questions[0]);
}
if (options.interval && isValidInterval(options.interval)) {
providedValues.interval = options.interval;
}
else {
missingQuestions.push(questions[1]);
}
if (options.start) {
const date = parseDate(options.start);
if (date) {
providedValues.startDate = date;
}
else {
missingQuestions.push(questions[2]);
}
}
else {
missingQuestions.push(questions[2]);
}
if (options.end) {
const date = parseDate(options.end);
if (date) {
providedValues.endDate = date;
}
else {
missingQuestions.push(questions[3]);
}
}
else {
missingQuestions.push(questions[3]);
}
if (options.output) {
providedValues.fileName = options.output;
}
else {
missingQuestions.push(questions[4]);
}
if (options.format && isValidFormat(options.format)) {
providedValues.format = options.format;
}
else {
missingQuestions.push(questions[5]);
}
if (missingQuestions.length > 0) {
const answers = await (0, prompts_1.default)(missingQuestions);
return { ...providedValues, ...answers };
}
return providedValues;
}
async function downloadKlines(config) {
const { pair, interval, startDate, endDate, fileName, format } = config;
const kLines = await (0, klines_1.getKline)(pair, interval, startDate, endDate).catch((error) => {
console.error('Error fetching klines:', error.message || error);
return null;
});
if (kLines) {
const extension = format === 'csv' ? 'csv' : 'json';
const outputPath = fileName +
`${pair}_${interval}_${(0, utils_1.formatDate)(startDate)}_${(0, utils_1.formatDate)(endDate)}.${extension}`;
(0, utils_1.saveKline)(outputPath, kLines, format);
console.log(`Downloaded ${kLines.length} klines to ${outputPath}`);
}
}
async function processWithOptions(options) {
const validation = validateOptions(options);
if (!validation.valid) {
for (const err of validation.errors) {
console.error(`Error: ${err}`);
}
process.exit(1);
}
if (hasAllRequiredOptions(options)) {
const pair = options.pair;
const interval = options.interval;
const startDate = parseDate(options.start);
const endDate = parseDate(options.end);
const fileName = options.output;
const format = options.format;
await downloadKlines({ pair, interval, startDate, endDate, fileName, format });
}
else {
const result = await promptMissingOptions(options);
if (!result.pair ||
!result.interval ||
!result.startDate ||
!result.endDate ||
!result.fileName ||
!result.format) {
console.error('Missing required information');
process.exit(1);
}
await downloadKlines(result);
}
}
async function processInteractive() {
const result = await promptUser();
if (!result.pair ||
!result.interval ||
!result.startDate ||
!result.endDate ||
!result.fileName ||
!result.format) {
console.error('Missing required information');
process.exit(1);
}
await downloadKlines(result);
}
async function runCommand() {
const program = new commander_1.Command();
let version = '1.0.0';
try {
const packageJsonPath = (0, node_path_1.join)(__dirname, '../package.json');
const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf-8'));
version = packageJson.version;
}
catch {
// Fallback to default version if package.json cannot be read
}
program
.name('binance-historical')
.description('Download historical klines from Binance')
.version(version)
.option('-p, --pair <symbol>', 'Trading pair (e.g., BTCUSDT, ETHUSDT)')
.option('-i, --interval <interval>', `Kline interval (${VALID_INTERVALS.join(', ')})`)
.option('-s, --start <date>', 'Start date (YYYY-MM-DD or ISO 8601)')
.option('-e, --end <date>', 'End date (YYYY-MM-DD or ISO 8601)')
.option('-o, --output <path>', 'Output directory path (filename is auto-generated)')
.option('-f, --format <format>', 'Output format (json, csv)', 'json')
.action(async (options) => {
const hasAnyOption = options.pair ||
options.interval ||
options.start ||
options.end ||
options.output;
if (hasAnyOption) {
await processWithOptions(options);
}
else {
await processInteractive();
}
});
program.parse();
}