@discomedia/utils 1.0.32 → 1.0.33
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/dist/alpaca-crypto-pairs-DMOvRyzw.js +571 -0
- package/dist/alpaca-crypto-pairs-DMOvRyzw.js.map +1 -0
- package/dist/index-frontend.cjs +252 -1
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +252 -1
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/index.cjs +252 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +252 -1
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +1 -1
- package/dist/test.js +464 -688
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +60 -1
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/types/alpaca-crypto-pairs.d.ts +11 -0
- package/dist/types/types/alpaca-crypto-pairs.d.ts.map +1 -0
- package/dist/types/types/alpaca-types.d.ts +146 -0
- package/dist/types/types/alpaca-types.d.ts.map +1 -1
- package/dist/types-frontend/alpaca-market-data-api.d.ts +60 -1
- package/dist/types-frontend/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types-frontend/types/alpaca-crypto-pairs.d.ts +11 -0
- package/dist/types-frontend/types/alpaca-crypto-pairs.d.ts.map +1 -0
- package/dist/types-frontend/types/alpaca-types.d.ts +146 -0
- package/dist/types-frontend/types/alpaca-types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/test.js
CHANGED
|
@@ -146,18 +146,12 @@ const marketEarlyCloses = {
|
|
|
146
146
|
|
|
147
147
|
// Constants for NY market times (Eastern Time)
|
|
148
148
|
const MARKET_CONFIG = {
|
|
149
|
-
TIMEZONE: 'America/New_York',
|
|
150
149
|
UTC_OFFSET_STANDARD: -5, // EST
|
|
151
150
|
UTC_OFFSET_DST: -4, // EDT
|
|
152
151
|
TIMES: {
|
|
153
|
-
EXTENDED_START: { hour: 4, minute: 0 },
|
|
154
152
|
MARKET_OPEN: { hour: 9, minute: 30 },
|
|
155
|
-
EARLY_MARKET_END: { hour: 10, minute: 0 },
|
|
156
153
|
MARKET_CLOSE: { hour: 16, minute: 0 },
|
|
157
|
-
EARLY_CLOSE: { hour: 13, minute: 0 },
|
|
158
|
-
EXTENDED_END: { hour: 20, minute: 0 },
|
|
159
|
-
EARLY_EXTENDED_END: { hour: 17, minute: 0 },
|
|
160
|
-
},
|
|
154
|
+
EARLY_CLOSE: { hour: 13, minute: 0 }},
|
|
161
155
|
};
|
|
162
156
|
// Helper: Get NY offset for a given UTC date (DST rules for US)
|
|
163
157
|
/**
|
|
@@ -225,33 +219,6 @@ function fromNYTime(date) {
|
|
|
225
219
|
const utcMillis = nyMillis - offset * 60 * 60 * 1000;
|
|
226
220
|
return new Date(utcMillis);
|
|
227
221
|
}
|
|
228
|
-
// Helper: Format date in ISO, unix, etc.
|
|
229
|
-
/**
|
|
230
|
-
* Formats a date in ISO, unix-seconds, or unix-ms format.
|
|
231
|
-
* @param date - Date object
|
|
232
|
-
* @param outputFormat - Output format ('iso', 'unix-seconds', 'unix-ms')
|
|
233
|
-
* @returns Formatted date string or number
|
|
234
|
-
*/
|
|
235
|
-
function formatDate(date, outputFormat = 'iso') {
|
|
236
|
-
switch (outputFormat) {
|
|
237
|
-
case 'unix-seconds':
|
|
238
|
-
return Math.floor(date.getTime() / 1000);
|
|
239
|
-
case 'unix-ms':
|
|
240
|
-
return date.getTime();
|
|
241
|
-
case 'iso':
|
|
242
|
-
default:
|
|
243
|
-
return date.toISOString();
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
// Helper: Format date in NY locale string
|
|
247
|
-
/**
|
|
248
|
-
* Formats a date in NY locale string.
|
|
249
|
-
* @param date - Date object
|
|
250
|
-
* @returns NY locale string
|
|
251
|
-
*/
|
|
252
|
-
function formatNYLocale(date) {
|
|
253
|
-
return date.toLocaleString('en-US', { timeZone: 'America/New_York' });
|
|
254
|
-
}
|
|
255
222
|
// Market calendar logic
|
|
256
223
|
/**
|
|
257
224
|
* Market calendar logic for holidays, weekends, and market days.
|
|
@@ -347,82 +314,6 @@ class MarketCalendar {
|
|
|
347
314
|
return prevDay;
|
|
348
315
|
}
|
|
349
316
|
}
|
|
350
|
-
// Market open/close times
|
|
351
|
-
/**
|
|
352
|
-
* Returns market open/close times for a given date, including extended and early closes.
|
|
353
|
-
* @param date - Date object
|
|
354
|
-
* @returns MarketOpenCloseResult
|
|
355
|
-
*/
|
|
356
|
-
function getMarketTimes(date) {
|
|
357
|
-
const calendar = new MarketCalendar();
|
|
358
|
-
const nyDate = toNYTime(date);
|
|
359
|
-
if (!calendar.isMarketDay(date)) {
|
|
360
|
-
return {
|
|
361
|
-
marketOpen: false,
|
|
362
|
-
open: null,
|
|
363
|
-
close: null,
|
|
364
|
-
openExt: null,
|
|
365
|
-
closeExt: null,
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
const year = nyDate.getUTCFullYear();
|
|
369
|
-
const month = nyDate.getUTCMonth();
|
|
370
|
-
const day = nyDate.getUTCDate();
|
|
371
|
-
// Helper to build NY time for a given hour/minute
|
|
372
|
-
function buildNYTime(hour, minute) {
|
|
373
|
-
const d = new Date(Date.UTC(year, month, day, hour, minute, 0, 0));
|
|
374
|
-
return fromNYTime(d);
|
|
375
|
-
}
|
|
376
|
-
let open = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute);
|
|
377
|
-
let close = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute);
|
|
378
|
-
let openExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute);
|
|
379
|
-
let closeExt = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute);
|
|
380
|
-
if (calendar.isEarlyCloseDay(date)) {
|
|
381
|
-
close = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute);
|
|
382
|
-
closeExt = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute);
|
|
383
|
-
}
|
|
384
|
-
return {
|
|
385
|
-
marketOpen: true,
|
|
386
|
-
open,
|
|
387
|
-
close,
|
|
388
|
-
openExt,
|
|
389
|
-
closeExt,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
// Is within market hours
|
|
393
|
-
/**
|
|
394
|
-
* Checks if a date/time is within market hours, extended hours, or continuous.
|
|
395
|
-
* @param date - Date object
|
|
396
|
-
* @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
|
|
397
|
-
* @returns true if within hours, false otherwise
|
|
398
|
-
*/
|
|
399
|
-
function isWithinMarketHours(date, intradayReporting = 'market_hours') {
|
|
400
|
-
const calendar = new MarketCalendar();
|
|
401
|
-
if (!calendar.isMarketDay(date))
|
|
402
|
-
return false;
|
|
403
|
-
const nyDate = toNYTime(date);
|
|
404
|
-
const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
|
|
405
|
-
switch (intradayReporting) {
|
|
406
|
-
case 'extended_hours': {
|
|
407
|
-
let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
408
|
-
if (calendar.isEarlyCloseDay(date)) {
|
|
409
|
-
endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
410
|
-
}
|
|
411
|
-
const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
412
|
-
return minutes >= startMinutes && minutes <= endMinutes;
|
|
413
|
-
}
|
|
414
|
-
case 'continuous':
|
|
415
|
-
return true;
|
|
416
|
-
default: {
|
|
417
|
-
let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
418
|
-
if (calendar.isEarlyCloseDay(date)) {
|
|
419
|
-
endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
420
|
-
}
|
|
421
|
-
const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
422
|
-
return minutes >= startMinutes && minutes <= endMinutes;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
317
|
// Get last full trading date
|
|
427
318
|
/**
|
|
428
319
|
* Returns the last full trading date (market close) for a given date.
|
|
@@ -462,244 +353,6 @@ function getLastFullTradingDateImpl(currentDate = new Date()) {
|
|
|
462
353
|
const closeMinute = marketCloseMinutes % 60;
|
|
463
354
|
return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
|
|
464
355
|
}
|
|
465
|
-
// Get day boundaries
|
|
466
|
-
/**
|
|
467
|
-
* Returns the start and end boundaries for a market day, extended hours, or continuous.
|
|
468
|
-
* @param date - Date object
|
|
469
|
-
* @param intradayReporting - 'market_hours', 'extended_hours', or 'continuous'
|
|
470
|
-
* @returns Object with start and end Date
|
|
471
|
-
*/
|
|
472
|
-
function getDayBoundaries(date, intradayReporting = 'market_hours') {
|
|
473
|
-
const calendar = new MarketCalendar();
|
|
474
|
-
const nyDate = toNYTime(date);
|
|
475
|
-
const year = nyDate.getUTCFullYear();
|
|
476
|
-
const month = nyDate.getUTCMonth();
|
|
477
|
-
const day = nyDate.getUTCDate();
|
|
478
|
-
function buildNYTime(hour, minute, sec = 0, ms = 0) {
|
|
479
|
-
const d = new Date(Date.UTC(year, month, day, hour, minute, sec, ms));
|
|
480
|
-
return fromNYTime(d);
|
|
481
|
-
}
|
|
482
|
-
let start;
|
|
483
|
-
let end;
|
|
484
|
-
switch (intradayReporting) {
|
|
485
|
-
case 'extended_hours':
|
|
486
|
-
start = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_START.hour, MARKET_CONFIG.TIMES.EXTENDED_START.minute, 0, 0);
|
|
487
|
-
end = buildNYTime(MARKET_CONFIG.TIMES.EXTENDED_END.hour, MARKET_CONFIG.TIMES.EXTENDED_END.minute, 59, 999);
|
|
488
|
-
if (calendar.isEarlyCloseDay(date)) {
|
|
489
|
-
end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute, 59, 999);
|
|
490
|
-
}
|
|
491
|
-
break;
|
|
492
|
-
case 'continuous':
|
|
493
|
-
start = new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
|
|
494
|
-
end = new Date(Date.UTC(year, month, day, 23, 59, 59, 999));
|
|
495
|
-
break;
|
|
496
|
-
default:
|
|
497
|
-
start = buildNYTime(MARKET_CONFIG.TIMES.MARKET_OPEN.hour, MARKET_CONFIG.TIMES.MARKET_OPEN.minute, 0, 0);
|
|
498
|
-
end = buildNYTime(MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, MARKET_CONFIG.TIMES.MARKET_CLOSE.minute, 59, 999);
|
|
499
|
-
if (calendar.isEarlyCloseDay(date)) {
|
|
500
|
-
end = buildNYTime(MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, MARKET_CONFIG.TIMES.EARLY_CLOSE.minute, 59, 999);
|
|
501
|
-
}
|
|
502
|
-
break;
|
|
503
|
-
}
|
|
504
|
-
return { start, end };
|
|
505
|
-
}
|
|
506
|
-
// Period calculator
|
|
507
|
-
/**
|
|
508
|
-
* Calculates the start date for a given period ending at endDate.
|
|
509
|
-
* @param endDate - Date object
|
|
510
|
-
* @param period - Period string
|
|
511
|
-
* @returns Date object for period start
|
|
512
|
-
*/
|
|
513
|
-
function calculatePeriodStartDate(endDate, period) {
|
|
514
|
-
const calendar = new MarketCalendar();
|
|
515
|
-
let startDate;
|
|
516
|
-
switch (period) {
|
|
517
|
-
case 'YTD':
|
|
518
|
-
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), 0, 1));
|
|
519
|
-
break;
|
|
520
|
-
case '1D':
|
|
521
|
-
startDate = calendar.getPreviousMarketDay(endDate);
|
|
522
|
-
break;
|
|
523
|
-
case '3D':
|
|
524
|
-
startDate = new Date(endDate.getTime() - 3 * 24 * 60 * 60 * 1000);
|
|
525
|
-
break;
|
|
526
|
-
case '1W':
|
|
527
|
-
startDate = new Date(endDate.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
528
|
-
break;
|
|
529
|
-
case '2W':
|
|
530
|
-
startDate = new Date(endDate.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
531
|
-
break;
|
|
532
|
-
case '1M':
|
|
533
|
-
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 1, endDate.getUTCDate()));
|
|
534
|
-
break;
|
|
535
|
-
case '3M':
|
|
536
|
-
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 3, endDate.getUTCDate()));
|
|
537
|
-
break;
|
|
538
|
-
case '6M':
|
|
539
|
-
startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth() - 6, endDate.getUTCDate()));
|
|
540
|
-
break;
|
|
541
|
-
case '1Y':
|
|
542
|
-
startDate = new Date(Date.UTC(endDate.getUTCFullYear() - 1, endDate.getUTCMonth(), endDate.getUTCDate()));
|
|
543
|
-
break;
|
|
544
|
-
default:
|
|
545
|
-
throw new Error(`Invalid period: ${period}`);
|
|
546
|
-
}
|
|
547
|
-
// Ensure start date is a market day
|
|
548
|
-
while (!calendar.isMarketDay(startDate)) {
|
|
549
|
-
startDate = calendar.getNextMarketDay(startDate);
|
|
550
|
-
}
|
|
551
|
-
return startDate;
|
|
552
|
-
}
|
|
553
|
-
// Get market time period
|
|
554
|
-
/**
|
|
555
|
-
* Returns the start and end dates for a market time period.
|
|
556
|
-
* @param params - MarketTimeParams
|
|
557
|
-
* @returns PeriodDates object
|
|
558
|
-
*/
|
|
559
|
-
function getMarketTimePeriod(params) {
|
|
560
|
-
const { period, end = new Date(), intraday_reporting = 'market_hours', outputFormat = 'iso' } = params;
|
|
561
|
-
if (!period)
|
|
562
|
-
throw new Error('Period is required');
|
|
563
|
-
const calendar = new MarketCalendar();
|
|
564
|
-
const nyEndDate = toNYTime(end);
|
|
565
|
-
let endDate;
|
|
566
|
-
const isCurrentMarketDay = calendar.isMarketDay(end);
|
|
567
|
-
const isWithinHours = isWithinMarketHours(end, intraday_reporting);
|
|
568
|
-
const minutes = nyEndDate.getUTCHours() * 60 + nyEndDate.getUTCMinutes();
|
|
569
|
-
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
570
|
-
if (isCurrentMarketDay) {
|
|
571
|
-
if (minutes < marketStartMinutes) {
|
|
572
|
-
// Before market open - use previous day's close
|
|
573
|
-
const lastMarketDay = calendar.getPreviousMarketDay(end);
|
|
574
|
-
const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
575
|
-
endDate = dayEnd;
|
|
576
|
-
}
|
|
577
|
-
else if (isWithinHours) {
|
|
578
|
-
// During market hours - use current time
|
|
579
|
-
endDate = end;
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
// After market close - use today's close
|
|
583
|
-
const { end: dayEnd } = getDayBoundaries(end, intraday_reporting);
|
|
584
|
-
endDate = dayEnd;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
// Not a market day - use previous market day's close
|
|
589
|
-
const lastMarketDay = calendar.getPreviousMarketDay(end);
|
|
590
|
-
const { end: dayEnd } = getDayBoundaries(lastMarketDay, intraday_reporting);
|
|
591
|
-
endDate = dayEnd;
|
|
592
|
-
}
|
|
593
|
-
// Calculate start date
|
|
594
|
-
const periodStartDate = calculatePeriodStartDate(endDate, period);
|
|
595
|
-
const { start: dayStart } = getDayBoundaries(periodStartDate, intraday_reporting);
|
|
596
|
-
if (endDate.getTime() < dayStart.getTime()) {
|
|
597
|
-
throw new Error('Start date cannot be after end date');
|
|
598
|
-
}
|
|
599
|
-
return {
|
|
600
|
-
start: formatDate(dayStart, outputFormat),
|
|
601
|
-
end: formatDate(endDate, outputFormat),
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
// Market status
|
|
605
|
-
/**
|
|
606
|
-
* Returns the current market status for a given date.
|
|
607
|
-
* @param date - Date object (default: now)
|
|
608
|
-
* @returns MarketStatus object
|
|
609
|
-
*/
|
|
610
|
-
function getMarketStatusImpl(date = new Date()) {
|
|
611
|
-
const calendar = new MarketCalendar();
|
|
612
|
-
const nyDate = toNYTime(date);
|
|
613
|
-
const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
|
|
614
|
-
const isMarketDay = calendar.isMarketDay(date);
|
|
615
|
-
const isEarlyCloseDay = calendar.isEarlyCloseDay(date);
|
|
616
|
-
const extendedStartMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
|
|
617
|
-
const marketStartMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
618
|
-
const earlyMarketEndMinutes = MARKET_CONFIG.TIMES.EARLY_MARKET_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_MARKET_END.minute;
|
|
619
|
-
let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
620
|
-
let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
|
|
621
|
-
if (isEarlyCloseDay) {
|
|
622
|
-
marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
|
|
623
|
-
extendedEndMinutes =
|
|
624
|
-
MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
|
|
625
|
-
}
|
|
626
|
-
let status;
|
|
627
|
-
let nextStatus;
|
|
628
|
-
let nextStatusTime;
|
|
629
|
-
let marketPeriod;
|
|
630
|
-
if (!isMarketDay) {
|
|
631
|
-
status = 'closed';
|
|
632
|
-
nextStatus = 'extended hours';
|
|
633
|
-
marketPeriod = 'closed';
|
|
634
|
-
const nextMarketDay = calendar.getNextMarketDay(date);
|
|
635
|
-
nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
|
|
636
|
-
}
|
|
637
|
-
else if (minutes < extendedStartMinutes) {
|
|
638
|
-
status = 'closed';
|
|
639
|
-
nextStatus = 'extended hours';
|
|
640
|
-
marketPeriod = 'closed';
|
|
641
|
-
nextStatusTime = getDayBoundaries(date, 'extended_hours').start;
|
|
642
|
-
}
|
|
643
|
-
else if (minutes < marketStartMinutes) {
|
|
644
|
-
status = 'extended hours';
|
|
645
|
-
nextStatus = 'open';
|
|
646
|
-
marketPeriod = 'preMarket';
|
|
647
|
-
nextStatusTime = getDayBoundaries(date, 'market_hours').start;
|
|
648
|
-
}
|
|
649
|
-
else if (minutes < marketCloseMinutes) {
|
|
650
|
-
status = 'open';
|
|
651
|
-
nextStatus = 'extended hours';
|
|
652
|
-
marketPeriod = minutes < earlyMarketEndMinutes ? 'earlyMarket' : 'regularMarket';
|
|
653
|
-
nextStatusTime = getDayBoundaries(date, 'market_hours').end;
|
|
654
|
-
}
|
|
655
|
-
else if (minutes < extendedEndMinutes) {
|
|
656
|
-
status = 'extended hours';
|
|
657
|
-
nextStatus = 'closed';
|
|
658
|
-
marketPeriod = 'afterMarket';
|
|
659
|
-
nextStatusTime = getDayBoundaries(date, 'extended_hours').end;
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
status = 'closed';
|
|
663
|
-
nextStatus = 'extended hours';
|
|
664
|
-
marketPeriod = 'closed';
|
|
665
|
-
const nextMarketDay = calendar.getNextMarketDay(date);
|
|
666
|
-
nextStatusTime = getDayBoundaries(nextMarketDay, 'extended_hours').start;
|
|
667
|
-
}
|
|
668
|
-
// I think using nyDate here may be wrong - should use current time? i.e. date.getTime()
|
|
669
|
-
const nextStatusTimeDifference = nextStatusTime.getTime() - date.getTime();
|
|
670
|
-
return {
|
|
671
|
-
time: date,
|
|
672
|
-
timeString: formatNYLocale(nyDate),
|
|
673
|
-
status,
|
|
674
|
-
nextStatus,
|
|
675
|
-
marketPeriod,
|
|
676
|
-
nextStatusTime,
|
|
677
|
-
nextStatusTimeDifference,
|
|
678
|
-
nextStatusTimeString: formatNYLocale(nextStatusTime),
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
// API exports
|
|
682
|
-
/**
|
|
683
|
-
* Returns market open/close times for a given date.
|
|
684
|
-
* @param options - { date?: Date }
|
|
685
|
-
* @returns MarketOpenCloseResult
|
|
686
|
-
*/
|
|
687
|
-
function getMarketOpenClose(options = {}) {
|
|
688
|
-
const { date = new Date() } = options;
|
|
689
|
-
return getMarketTimes(date);
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Returns the start and end dates for a market time period as Date objects.
|
|
693
|
-
* @param params - MarketTimeParams
|
|
694
|
-
* @returns Object with start and end Date
|
|
695
|
-
*/
|
|
696
|
-
function getStartAndEndDates(params = {}) {
|
|
697
|
-
const { start, end } = getMarketTimePeriod(params);
|
|
698
|
-
return {
|
|
699
|
-
start: typeof start === 'string' || typeof start === 'number' ? new Date(start) : start,
|
|
700
|
-
end: typeof end === 'string' || typeof end === 'number' ? new Date(end) : end,
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
356
|
/**
|
|
704
357
|
* Returns the last full trading date as a Date object.
|
|
705
358
|
*/
|
|
@@ -711,230 +364,6 @@ function getStartAndEndDates(params = {}) {
|
|
|
711
364
|
function getLastFullTradingDate(currentDate = new Date()) {
|
|
712
365
|
return getLastFullTradingDateImpl(currentDate);
|
|
713
366
|
}
|
|
714
|
-
/**
|
|
715
|
-
* Returns the next market day after the reference date.
|
|
716
|
-
* @param referenceDate - Date object (default: now)
|
|
717
|
-
* @returns Object with date, yyyymmdd string, and ISO string
|
|
718
|
-
*/
|
|
719
|
-
function getNextMarketDay({ referenceDate } = {}) {
|
|
720
|
-
const calendar = new MarketCalendar();
|
|
721
|
-
const startDate = referenceDate ?? new Date();
|
|
722
|
-
// Find the next trading day (UTC Date object)
|
|
723
|
-
const nextDate = calendar.getNextMarketDay(startDate);
|
|
724
|
-
// Convert to NY time before extracting Y-M-D parts
|
|
725
|
-
const nyNext = toNYTime(nextDate);
|
|
726
|
-
const yyyymmdd = `${nyNext.getUTCFullYear()}-${String(nyNext.getUTCMonth() + 1).padStart(2, '0')}-${String(nyNext.getUTCDate()).padStart(2, '0')}`;
|
|
727
|
-
return {
|
|
728
|
-
date: nextDate, // raw Date, unchanged
|
|
729
|
-
yyyymmdd, // correct trading date string
|
|
730
|
-
dateISOString: nextDate.toISOString(),
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Returns the previous market day before the reference date.
|
|
735
|
-
* @param referenceDate - Date object (default: now)
|
|
736
|
-
* @returns Object with date, yyyymmdd string, and ISO string
|
|
737
|
-
*/
|
|
738
|
-
function getPreviousMarketDay({ referenceDate } = {}) {
|
|
739
|
-
const calendar = new MarketCalendar();
|
|
740
|
-
const startDate = referenceDate || new Date();
|
|
741
|
-
const prevDate = calendar.getPreviousMarketDay(startDate);
|
|
742
|
-
// convert to NY time first
|
|
743
|
-
const nyPrev = toNYTime(prevDate); // ← already in this file
|
|
744
|
-
const yyyymmdd = `${nyPrev.getUTCFullYear()}-${String(nyPrev.getUTCMonth() + 1).padStart(2, '0')}-${String(nyPrev.getUTCDate()).padStart(2, '0')}`;
|
|
745
|
-
return {
|
|
746
|
-
date: prevDate,
|
|
747
|
-
yyyymmdd,
|
|
748
|
-
dateISOString: prevDate.toISOString(),
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Returns the trading date for a given time. Note: Just trims the date string; does not validate if the date is a market day.
|
|
753
|
-
* @param time - a string, number (unix timestamp), or Date object representing the time
|
|
754
|
-
* @returns the trading date as a string in YYYY-MM-DD format
|
|
755
|
-
*/
|
|
756
|
-
/**
|
|
757
|
-
* Returns the trading date for a given time in YYYY-MM-DD format (NY time).
|
|
758
|
-
* @param time - string, number, or Date
|
|
759
|
-
* @returns trading date string
|
|
760
|
-
*/
|
|
761
|
-
function getTradingDate(time) {
|
|
762
|
-
const date = typeof time === 'number' ? new Date(time) : typeof time === 'string' ? new Date(time) : time;
|
|
763
|
-
const nyDate = toNYTime(date);
|
|
764
|
-
return `${nyDate.getUTCFullYear()}-${String(nyDate.getUTCMonth() + 1).padStart(2, '0')}-${String(nyDate.getUTCDate()).padStart(2, '0')}`;
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Returns the NY timezone offset string for a given date.
|
|
768
|
-
* @param date - Date object (default: now)
|
|
769
|
-
* @returns '-04:00' for EDT, '-05:00' for EST
|
|
770
|
-
*/
|
|
771
|
-
function getNYTimeZone(date) {
|
|
772
|
-
const offset = getNYOffset(date || new Date());
|
|
773
|
-
return offset === -4 ? '-04:00' : '-05:00';
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Returns the current market status for a given date.
|
|
777
|
-
* @param options - { date?: Date }
|
|
778
|
-
* @returns MarketStatus object
|
|
779
|
-
*/
|
|
780
|
-
function getMarketStatus(options = {}) {
|
|
781
|
-
const { date = new Date() } = options;
|
|
782
|
-
return getMarketStatusImpl(date);
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Checks if a date is a market day.
|
|
786
|
-
* @param date - Date object
|
|
787
|
-
* @returns true if market day, false otherwise
|
|
788
|
-
*/
|
|
789
|
-
function isMarketDay(date) {
|
|
790
|
-
const calendar = new MarketCalendar();
|
|
791
|
-
return calendar.isMarketDay(date);
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Returns full trading days from market open to market close.
|
|
795
|
-
* endDate is always the most recent market close (previous day's close if before open, today's close if after open).
|
|
796
|
-
* days: 1 or not specified = that day's open; 2 = previous market day's open, etc.
|
|
797
|
-
*/
|
|
798
|
-
/**
|
|
799
|
-
* Returns full trading days from market open to market close.
|
|
800
|
-
* @param options - { endDate?: Date, days?: number }
|
|
801
|
-
* @returns Object with startDate and endDate
|
|
802
|
-
*/
|
|
803
|
-
function getTradingStartAndEndDates(options = {}) {
|
|
804
|
-
const { endDate = new Date(), days = 1 } = options;
|
|
805
|
-
const calendar = new MarketCalendar();
|
|
806
|
-
// Find the most recent market close
|
|
807
|
-
let endMarketDay = endDate;
|
|
808
|
-
const nyEnd = toNYTime(endDate);
|
|
809
|
-
const marketOpenMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
|
|
810
|
-
const marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
|
|
811
|
-
const minutes = nyEnd.getUTCHours() * 60 + nyEnd.getUTCMinutes();
|
|
812
|
-
if (!calendar.isMarketDay(endDate) ||
|
|
813
|
-
minutes < marketOpenMinutes ||
|
|
814
|
-
(minutes >= marketOpenMinutes && minutes < marketCloseMinutes)) {
|
|
815
|
-
// Before market open, not a market day, or during market hours: use previous market day
|
|
816
|
-
endMarketDay = calendar.getPreviousMarketDay(endDate);
|
|
817
|
-
}
|
|
818
|
-
else {
|
|
819
|
-
// After market close: use today
|
|
820
|
-
endMarketDay = endDate;
|
|
821
|
-
}
|
|
822
|
-
// Get market close for endMarketDay
|
|
823
|
-
const endClose = getMarketOpenClose({ date: endMarketDay }).close;
|
|
824
|
-
// Find start market day by iterating back over market days
|
|
825
|
-
let startMarketDay = endMarketDay;
|
|
826
|
-
let count = Math.max(1, days);
|
|
827
|
-
for (let i = 1; i < count; i++) {
|
|
828
|
-
startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
|
|
829
|
-
}
|
|
830
|
-
// If days > 1, we need to go back (days-1) market days from endMarketDay
|
|
831
|
-
if (days > 1) {
|
|
832
|
-
startMarketDay = endMarketDay;
|
|
833
|
-
for (let i = 1; i < days; i++) {
|
|
834
|
-
startMarketDay = calendar.getPreviousMarketDay(startMarketDay);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
const startOpen = getMarketOpenClose({ date: startMarketDay }).open;
|
|
838
|
-
return { startDate: startOpen, endDate: endClose };
|
|
839
|
-
}
|
|
840
|
-
/**
|
|
841
|
-
* Counts trading time between two dates (passed as standard Date objects), excluding weekends and holidays, and closed market hours, using other functions in this library.
|
|
842
|
-
*
|
|
843
|
-
* This function calculates the actual trading time between two dates by:
|
|
844
|
-
* 1. Iterating through each calendar day between startDate and endDate (inclusive)
|
|
845
|
-
* 2. For each day that is a market day (not weekend/holiday), getting market open/close times
|
|
846
|
-
* 3. Calculating the overlap between the time range and market hours for that day
|
|
847
|
-
* 4. Summing up all the trading minutes across all days
|
|
848
|
-
*
|
|
849
|
-
* The function automatically handles:
|
|
850
|
-
* - Weekends (Saturday/Sunday) - skipped entirely
|
|
851
|
-
* - Market holidays - skipped entirely
|
|
852
|
-
* - Early close days (e.g. day before holidays) - uses early close time
|
|
853
|
-
* - Times outside market hours - only counts time within 9:30am-4pm ET (or early close)
|
|
854
|
-
*
|
|
855
|
-
* Examples:
|
|
856
|
-
* - 12pm to 3:30pm same day = 3.5 hours = 210 minutes = 0.54 days
|
|
857
|
-
* - 9:30am to 4pm same day = 6.5 hours = 390 minutes = 1 day
|
|
858
|
-
* - Friday 2pm to Monday 2pm = 6.5 hours (Friday 2pm-4pm + Monday 9:30am-2pm)
|
|
859
|
-
*
|
|
860
|
-
* @param startDate - Start date/time
|
|
861
|
-
* @param endDate - End date/time (default: now)
|
|
862
|
-
* @returns Object containing:
|
|
863
|
-
* - days: Trading time as fraction of full trading days (6.5 hours = 1 day)
|
|
864
|
-
* - hours: Trading time in hours
|
|
865
|
-
* - minutes: Trading time in minutes
|
|
866
|
-
*/
|
|
867
|
-
function countTradingDays(startDate, endDate = new Date()) {
|
|
868
|
-
const calendar = new MarketCalendar();
|
|
869
|
-
// Ensure start is before end
|
|
870
|
-
if (startDate.getTime() > endDate.getTime()) {
|
|
871
|
-
throw new Error('Start date must be before end date');
|
|
872
|
-
}
|
|
873
|
-
let totalMinutes = 0;
|
|
874
|
-
// Get the NY dates for iteration
|
|
875
|
-
const startNY = toNYTime(startDate);
|
|
876
|
-
const endNY = toNYTime(endDate);
|
|
877
|
-
// Create date at start of first day (in NY time)
|
|
878
|
-
const currentNY = new Date(Date.UTC(startNY.getUTCFullYear(), startNY.getUTCMonth(), startNY.getUTCDate(), 0, 0, 0, 0));
|
|
879
|
-
// Iterate through each calendar day
|
|
880
|
-
while (currentNY.getTime() <= endNY.getTime()) {
|
|
881
|
-
const currentUTC = fromNYTime(currentNY);
|
|
882
|
-
// Check if this is a market day
|
|
883
|
-
if (calendar.isMarketDay(currentUTC)) {
|
|
884
|
-
// Get market hours for this day
|
|
885
|
-
const marketTimes = getMarketTimes(currentUTC);
|
|
886
|
-
if (marketTimes.marketOpen && marketTimes.open && marketTimes.close) {
|
|
887
|
-
// Calculate the overlap between our time range and market hours
|
|
888
|
-
const dayStart = Math.max(startDate.getTime(), marketTimes.open.getTime());
|
|
889
|
-
const dayEnd = Math.min(endDate.getTime(), marketTimes.close.getTime());
|
|
890
|
-
// Only count if there's actual overlap
|
|
891
|
-
if (dayStart < dayEnd) {
|
|
892
|
-
totalMinutes += (dayEnd - dayStart) / (1000 * 60);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
// Move to next day
|
|
897
|
-
currentNY.setUTCDate(currentNY.getUTCDate() + 1);
|
|
898
|
-
}
|
|
899
|
-
// Convert to days, hours, minutes
|
|
900
|
-
const MINUTES_PER_TRADING_DAY = 390; // 6.5 hours
|
|
901
|
-
const days = totalMinutes / MINUTES_PER_TRADING_DAY;
|
|
902
|
-
const hours = totalMinutes / 60;
|
|
903
|
-
const minutes = totalMinutes;
|
|
904
|
-
return {
|
|
905
|
-
days: Math.round(days * 1000) / 1000, // Round to 3 decimal places
|
|
906
|
-
hours: Math.round(hours * 100) / 100, // Round to 2 decimal places
|
|
907
|
-
minutes: Math.round(minutes),
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
// Export MARKET_TIMES for compatibility
|
|
911
|
-
const MARKET_TIMES = {
|
|
912
|
-
TIMEZONE: MARKET_CONFIG.TIMEZONE,
|
|
913
|
-
PRE: {
|
|
914
|
-
START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
|
|
915
|
-
END: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
916
|
-
},
|
|
917
|
-
EARLY_MORNING: {
|
|
918
|
-
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
919
|
-
END: { HOUR: 10, MINUTE: 0, MINUTES: 600 },
|
|
920
|
-
},
|
|
921
|
-
EARLY_CLOSE_BEFORE_HOLIDAY: {
|
|
922
|
-
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
923
|
-
END: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
|
|
924
|
-
},
|
|
925
|
-
EARLY_EXTENDED_BEFORE_HOLIDAY: {
|
|
926
|
-
START: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
|
|
927
|
-
END: { HOUR: 17, MINUTE: 0, MINUTES: 1020 },
|
|
928
|
-
},
|
|
929
|
-
REGULAR: {
|
|
930
|
-
START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
|
|
931
|
-
END: { HOUR: 16, MINUTE: 0, MINUTES: 960 },
|
|
932
|
-
},
|
|
933
|
-
EXTENDED: {
|
|
934
|
-
START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
|
|
935
|
-
END: { HOUR: 20, MINUTE: 0, MINUTES: 1200 },
|
|
936
|
-
},
|
|
937
|
-
};
|
|
938
367
|
|
|
939
368
|
/*
|
|
940
369
|
How it works:
|
|
@@ -13457,6 +12886,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
13457
12886
|
dataURL;
|
|
13458
12887
|
apiURL;
|
|
13459
12888
|
v1beta1url;
|
|
12889
|
+
v1beta3url;
|
|
13460
12890
|
stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip'; // production values
|
|
13461
12891
|
optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // production values
|
|
13462
12892
|
stockWs = null;
|
|
@@ -13499,6 +12929,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
13499
12929
|
? 'https://paper-api.alpaca.markets/v2'
|
|
13500
12930
|
: 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
|
|
13501
12931
|
this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
|
|
12932
|
+
this.v1beta3url = 'https://data.alpaca.markets/v1beta3'; // used for crypto endpoints
|
|
13502
12933
|
this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
|
|
13503
12934
|
this.headers = {
|
|
13504
12935
|
'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
|
|
@@ -13636,7 +13067,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
13636
13067
|
}
|
|
13637
13068
|
}
|
|
13638
13069
|
async makeRequest(endpoint, method = 'GET', params, baseUrlName = 'data') {
|
|
13639
|
-
const baseUrl = baseUrlName === 'data' ? this.dataURL :
|
|
13070
|
+
const baseUrl = baseUrlName === 'data' ? this.dataURL :
|
|
13071
|
+
baseUrlName === 'api' ? this.apiURL :
|
|
13072
|
+
baseUrlName === 'v1beta1' ? this.v1beta1url : this.v1beta3url;
|
|
13640
13073
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
13641
13074
|
try {
|
|
13642
13075
|
if (params) {
|
|
@@ -14344,126 +13777,469 @@ class AlpacaMarketDataAPI extends EventEmitter {
|
|
|
14344
13777
|
}
|
|
14345
13778
|
return newsArticles;
|
|
14346
13779
|
}
|
|
13780
|
+
// ===== CRYPTO MARKET DATA METHODS =====
|
|
13781
|
+
/**
|
|
13782
|
+
* Get historical OHLCV bars for crypto symbols
|
|
13783
|
+
* Automatically handles pagination to fetch all available data
|
|
13784
|
+
* @param params Parameters for crypto historical bars request
|
|
13785
|
+
* @returns Historical bars data with all pages combined
|
|
13786
|
+
*/
|
|
13787
|
+
async getCryptoHistoricalBars(params) {
|
|
13788
|
+
const symbols = params.symbols;
|
|
13789
|
+
const symbolsStr = symbols.join(',');
|
|
13790
|
+
let allBars = {};
|
|
13791
|
+
let pageToken = null;
|
|
13792
|
+
let hasMorePages = true;
|
|
13793
|
+
let totalBarsCount = 0;
|
|
13794
|
+
let pageCount = 0;
|
|
13795
|
+
// Initialize bar arrays for each symbol
|
|
13796
|
+
symbols.forEach((symbol) => {
|
|
13797
|
+
allBars[symbol] = [];
|
|
13798
|
+
});
|
|
13799
|
+
log(`Starting crypto historical bars fetch for ${symbols.length} symbols (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, { type: 'info' });
|
|
13800
|
+
while (hasMorePages) {
|
|
13801
|
+
pageCount++;
|
|
13802
|
+
const requestParams = {
|
|
13803
|
+
...params,
|
|
13804
|
+
symbols: symbolsStr,
|
|
13805
|
+
...(pageToken && { page_token: pageToken }),
|
|
13806
|
+
};
|
|
13807
|
+
const response = await this.makeRequest('/crypto/us/bars', 'GET', requestParams, 'v1beta3');
|
|
13808
|
+
if (!response.bars) {
|
|
13809
|
+
log(`No crypto bars data found in response for ${symbols.length} symbols`, { type: 'warn' });
|
|
13810
|
+
break;
|
|
13811
|
+
}
|
|
13812
|
+
// Combine bars for each symbol
|
|
13813
|
+
let pageBarsCount = 0;
|
|
13814
|
+
Object.entries(response.bars).forEach(([symbol, bars]) => {
|
|
13815
|
+
if (bars && bars.length > 0) {
|
|
13816
|
+
allBars[symbol] = [...allBars[symbol], ...bars];
|
|
13817
|
+
pageBarsCount += bars.length;
|
|
13818
|
+
}
|
|
13819
|
+
});
|
|
13820
|
+
totalBarsCount += pageBarsCount;
|
|
13821
|
+
pageToken = response.next_page_token || null;
|
|
13822
|
+
hasMorePages = !!pageToken;
|
|
13823
|
+
log(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} crypto bars (total: ${totalBarsCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ', more pages available' : ', complete'}`);
|
|
13824
|
+
// Prevent infinite loops
|
|
13825
|
+
if (pageCount > 1000) {
|
|
13826
|
+
log(`Stopping crypto bars pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
13827
|
+
break;
|
|
13828
|
+
}
|
|
13829
|
+
}
|
|
13830
|
+
log(`Crypto historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages`, { type: 'info' });
|
|
13831
|
+
return {
|
|
13832
|
+
bars: allBars,
|
|
13833
|
+
next_page_token: null, // Always null since we fetch all pages
|
|
13834
|
+
};
|
|
13835
|
+
}
|
|
13836
|
+
/**
|
|
13837
|
+
* Get the most recent minute bar for requested crypto symbols
|
|
13838
|
+
* @param symbols Array of crypto symbols to query
|
|
13839
|
+
* @returns Latest bar data for each symbol
|
|
13840
|
+
*/
|
|
13841
|
+
async getCryptoLatestBars(symbols) {
|
|
13842
|
+
return this.makeRequest('/crypto/us/latest/bars', 'GET', { symbols: symbols.join(',') }, 'v1beta3');
|
|
13843
|
+
}
|
|
13844
|
+
/**
|
|
13845
|
+
* Get historical quotes for crypto symbols
|
|
13846
|
+
* Automatically handles pagination to fetch all available data
|
|
13847
|
+
* @param params Parameters for crypto historical quotes request
|
|
13848
|
+
* @returns Historical quotes data with all pages combined
|
|
13849
|
+
*/
|
|
13850
|
+
async getCryptoHistoricalQuotes(params) {
|
|
13851
|
+
const symbols = params.symbols;
|
|
13852
|
+
const symbolsStr = symbols.join(',');
|
|
13853
|
+
let allQuotes = {};
|
|
13854
|
+
let pageToken = null;
|
|
13855
|
+
let hasMorePages = true;
|
|
13856
|
+
let totalQuotesCount = 0;
|
|
13857
|
+
let pageCount = 0;
|
|
13858
|
+
// Initialize quotes arrays for each symbol
|
|
13859
|
+
symbols.forEach((symbol) => {
|
|
13860
|
+
allQuotes[symbol] = [];
|
|
13861
|
+
});
|
|
13862
|
+
log(`Starting crypto historical quotes fetch for ${symbols.length} symbols (${params.start || 'no start'} to ${params.end || 'no end'})`, { type: 'info' });
|
|
13863
|
+
while (hasMorePages) {
|
|
13864
|
+
pageCount++;
|
|
13865
|
+
const requestParams = {
|
|
13866
|
+
...params,
|
|
13867
|
+
symbols: symbolsStr,
|
|
13868
|
+
...(pageToken && { page_token: pageToken }),
|
|
13869
|
+
};
|
|
13870
|
+
const response = await this.makeRequest('/crypto/us/quotes', 'GET', requestParams, 'v1beta3');
|
|
13871
|
+
if (!response.quotes) {
|
|
13872
|
+
log(`No crypto quotes data found in response for ${symbols.length} symbols`, { type: 'warn' });
|
|
13873
|
+
break;
|
|
13874
|
+
}
|
|
13875
|
+
// Combine quotes for each symbol
|
|
13876
|
+
let pageQuotesCount = 0;
|
|
13877
|
+
Object.entries(response.quotes).forEach(([symbol, quotes]) => {
|
|
13878
|
+
if (quotes && quotes.length > 0) {
|
|
13879
|
+
allQuotes[symbol] = [...allQuotes[symbol], ...quotes];
|
|
13880
|
+
pageQuotesCount += quotes.length;
|
|
13881
|
+
}
|
|
13882
|
+
});
|
|
13883
|
+
totalQuotesCount += pageQuotesCount;
|
|
13884
|
+
pageToken = response.next_page_token || null;
|
|
13885
|
+
hasMorePages = !!pageToken;
|
|
13886
|
+
log(`Page ${pageCount}: Fetched ${pageQuotesCount.toLocaleString()} crypto quotes (total: ${totalQuotesCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ', more pages available' : ', complete'}`);
|
|
13887
|
+
// Prevent infinite loops
|
|
13888
|
+
if (pageCount > 1000) {
|
|
13889
|
+
log(`Stopping crypto quotes pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
13890
|
+
break;
|
|
13891
|
+
}
|
|
13892
|
+
}
|
|
13893
|
+
log(`Crypto historical quotes fetch complete: ${totalQuotesCount.toLocaleString()} total quotes across ${pageCount} pages`, { type: 'info' });
|
|
13894
|
+
return {
|
|
13895
|
+
quotes: allQuotes,
|
|
13896
|
+
next_page_token: null, // Always null since we fetch all pages
|
|
13897
|
+
};
|
|
13898
|
+
}
|
|
13899
|
+
/**
|
|
13900
|
+
* Get the most recent quotes for requested crypto symbols
|
|
13901
|
+
* @param symbols Array of crypto symbols to query
|
|
13902
|
+
* @returns Latest quote data for each symbol
|
|
13903
|
+
*/
|
|
13904
|
+
async getCryptoLatestQuotes(symbols) {
|
|
13905
|
+
if (!symbols || symbols.length === 0) {
|
|
13906
|
+
log('No symbols provided to getCryptoLatestQuotes, returning empty response', { type: 'warn' });
|
|
13907
|
+
return { quotes: {} };
|
|
13908
|
+
}
|
|
13909
|
+
return this.makeRequest('/crypto/us/latest/quotes', 'GET', { symbols: symbols.join(',') }, 'v1beta3');
|
|
13910
|
+
}
|
|
13911
|
+
/**
|
|
13912
|
+
* Get historical trades for crypto symbols
|
|
13913
|
+
* Automatically handles pagination to fetch all available data
|
|
13914
|
+
* @param params Parameters for crypto historical trades request
|
|
13915
|
+
* @returns Historical trades data with all pages combined
|
|
13916
|
+
*/
|
|
13917
|
+
async getCryptoHistoricalTrades(params) {
|
|
13918
|
+
const symbols = params.symbols;
|
|
13919
|
+
const symbolsStr = symbols.join(',');
|
|
13920
|
+
let allTrades = {};
|
|
13921
|
+
let pageToken = null;
|
|
13922
|
+
let hasMorePages = true;
|
|
13923
|
+
let totalTradesCount = 0;
|
|
13924
|
+
let pageCount = 0;
|
|
13925
|
+
// Initialize trades arrays for each symbol
|
|
13926
|
+
symbols.forEach((symbol) => {
|
|
13927
|
+
allTrades[symbol] = [];
|
|
13928
|
+
});
|
|
13929
|
+
log(`Starting crypto historical trades fetch for ${symbols.length} symbols (${params.start || 'no start'} to ${params.end || 'no end'})`, { type: 'info' });
|
|
13930
|
+
while (hasMorePages) {
|
|
13931
|
+
pageCount++;
|
|
13932
|
+
const requestParams = {
|
|
13933
|
+
...params,
|
|
13934
|
+
symbols: symbolsStr,
|
|
13935
|
+
...(pageToken && { page_token: pageToken }),
|
|
13936
|
+
};
|
|
13937
|
+
const response = await this.makeRequest('/crypto/us/trades', 'GET', requestParams, 'v1beta3');
|
|
13938
|
+
if (!response.trades) {
|
|
13939
|
+
log(`No crypto trades data found in response for ${symbols.length} symbols`, { type: 'warn' });
|
|
13940
|
+
break;
|
|
13941
|
+
}
|
|
13942
|
+
// Combine trades for each symbol
|
|
13943
|
+
let pageTradesCount = 0;
|
|
13944
|
+
Object.entries(response.trades).forEach(([symbol, trades]) => {
|
|
13945
|
+
if (trades && trades.length > 0) {
|
|
13946
|
+
allTrades[symbol] = [...allTrades[symbol], ...trades];
|
|
13947
|
+
pageTradesCount += trades.length;
|
|
13948
|
+
}
|
|
13949
|
+
});
|
|
13950
|
+
totalTradesCount += pageTradesCount;
|
|
13951
|
+
pageToken = response.next_page_token || null;
|
|
13952
|
+
hasMorePages = !!pageToken;
|
|
13953
|
+
log(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} crypto trades (total: ${totalTradesCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ', more pages available' : ', complete'}`);
|
|
13954
|
+
// Prevent infinite loops
|
|
13955
|
+
if (pageCount > 1000) {
|
|
13956
|
+
log(`Stopping crypto trades pagination after ${pageCount} pages to prevent infinite loop`, { type: 'warn' });
|
|
13957
|
+
break;
|
|
13958
|
+
}
|
|
13959
|
+
}
|
|
13960
|
+
log(`Crypto historical trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages`, { type: 'info' });
|
|
13961
|
+
return {
|
|
13962
|
+
trades: allTrades,
|
|
13963
|
+
next_page_token: null, // Always null since we fetch all pages
|
|
13964
|
+
};
|
|
13965
|
+
}
|
|
13966
|
+
/**
|
|
13967
|
+
* Get the most recent trades for requested crypto symbols
|
|
13968
|
+
* @param symbols Array of crypto symbols to query
|
|
13969
|
+
* @returns Latest trade data for each symbol
|
|
13970
|
+
*/
|
|
13971
|
+
async getCryptoLatestTrades(symbols) {
|
|
13972
|
+
if (!symbols || symbols.length === 0) {
|
|
13973
|
+
log('No symbols provided to getCryptoLatestTrades, returning empty response', { type: 'warn' });
|
|
13974
|
+
return { trades: {} };
|
|
13975
|
+
}
|
|
13976
|
+
return this.makeRequest('/crypto/us/latest/trades', 'GET', { symbols: symbols.join(',') }, 'v1beta3');
|
|
13977
|
+
}
|
|
13978
|
+
/**
|
|
13979
|
+
* Get snapshots for crypto symbols
|
|
13980
|
+
* Returns the latest trade, latest quote, latest minute bar, latest daily bar, and previous daily bar data
|
|
13981
|
+
* @param symbols Array of crypto symbols to query
|
|
13982
|
+
* @returns Snapshot data for each symbol
|
|
13983
|
+
*/
|
|
13984
|
+
async getCryptoSnapshots(symbols) {
|
|
13985
|
+
if (!symbols || symbols.length === 0) {
|
|
13986
|
+
log('No symbols provided to getCryptoSnapshots, returning empty response', { type: 'warn' });
|
|
13987
|
+
return { snapshots: {} };
|
|
13988
|
+
}
|
|
13989
|
+
return this.makeRequest('/crypto/us/snapshots', 'GET', { symbols: symbols.join(',') }, 'v1beta3');
|
|
13990
|
+
}
|
|
13991
|
+
/**
|
|
13992
|
+
* Get the latest orderbook for requested crypto symbols
|
|
13993
|
+
* @param symbols Array of crypto symbols to query
|
|
13994
|
+
* @returns Latest orderbook data for each symbol
|
|
13995
|
+
*/
|
|
13996
|
+
async getCryptoLatestOrderbooks(symbols) {
|
|
13997
|
+
if (!symbols || symbols.length === 0) {
|
|
13998
|
+
log('No symbols provided to getCryptoLatestOrderbooks, returning empty response', { type: 'warn' });
|
|
13999
|
+
return { orderbooks: {} };
|
|
14000
|
+
}
|
|
14001
|
+
return this.makeRequest('/crypto/us/latest/orderbooks', 'GET', { symbols: symbols.join(',') }, 'v1beta3');
|
|
14002
|
+
}
|
|
14003
|
+
/**
|
|
14004
|
+
* Analyzes an array of crypto bars and returns a summary string
|
|
14005
|
+
* @param bars Array of crypto bars to analyze
|
|
14006
|
+
* @returns A string summarizing the crypto price data
|
|
14007
|
+
*/
|
|
14008
|
+
static analyzeCryptoBars(bars) {
|
|
14009
|
+
if (!bars || bars.length === 0) {
|
|
14010
|
+
return 'No crypto price data available';
|
|
14011
|
+
}
|
|
14012
|
+
const firstBar = bars[0];
|
|
14013
|
+
const lastBar = bars[bars.length - 1];
|
|
14014
|
+
const priceChange = lastBar.c - firstBar.o;
|
|
14015
|
+
const percentChange = (priceChange / firstBar.o) * 100;
|
|
14016
|
+
const volumeChange = lastBar.v - firstBar.v;
|
|
14017
|
+
const percentVolumeChange = firstBar.v > 0 ? (volumeChange / firstBar.v) * 100 : 0;
|
|
14018
|
+
const high = Math.max(...bars.map((bar) => bar.h));
|
|
14019
|
+
const low = Math.min(...bars.map((bar) => bar.l));
|
|
14020
|
+
const totalVolume = bars.reduce((sum, bar) => sum + bar.v, 0);
|
|
14021
|
+
const avgVolume = totalVolume / bars.length;
|
|
14022
|
+
return (`Crypto Price: $${firstBar.o.toFixed(6)} -> $${lastBar.c.toFixed(6)} (${percentChange.toFixed(2)}%), ` +
|
|
14023
|
+
`Volume: ${firstBar.v.toLocaleString()} -> ${lastBar.v.toLocaleString()} (${percentVolumeChange.toFixed(2)}%), ` +
|
|
14024
|
+
`High: $${high.toFixed(6)}, Low: $${low.toFixed(6)}, ` +
|
|
14025
|
+
`Avg Volume: ${avgVolume.toLocaleString()}`);
|
|
14026
|
+
}
|
|
14347
14027
|
}
|
|
14348
14028
|
// Export the singleton instance
|
|
14349
|
-
AlpacaMarketDataAPI.getInstance();
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
getNextMarketDay: getNextMarketDay,
|
|
14357
|
-
getPreviousMarketDay: getPreviousMarketDay,
|
|
14358
|
-
getMarketTimePeriod: getMarketTimePeriod,
|
|
14359
|
-
getMarketStatus: getMarketStatus,
|
|
14360
|
-
getNYTimeZone: getNYTimeZone,
|
|
14361
|
-
getTradingDate: getTradingDate,
|
|
14362
|
-
getTradingStartAndEndDates: getTradingStartAndEndDates,
|
|
14363
|
-
isMarketDay: isMarketDay,
|
|
14364
|
-
isWithinMarketHours: isWithinMarketHours,
|
|
14365
|
-
countTradingDays: countTradingDays,
|
|
14366
|
-
MARKET_TIMES: MARKET_TIMES,
|
|
14367
|
-
}};
|
|
14029
|
+
const marketDataAPI = AlpacaMarketDataAPI.getInstance();
|
|
14030
|
+
|
|
14031
|
+
var alpacaMarketDataApi = /*#__PURE__*/Object.freeze({
|
|
14032
|
+
__proto__: null,
|
|
14033
|
+
AlpacaMarketDataAPI: AlpacaMarketDataAPI,
|
|
14034
|
+
marketDataAPI: marketDataAPI
|
|
14035
|
+
});
|
|
14368
14036
|
|
|
14369
14037
|
// Test file for context functionality
|
|
14370
|
-
//
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
}
|
|
14400
|
-
}
|
|
14401
|
-
{
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
}
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
}
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14441
|
-
|
|
14442
|
-
|
|
14443
|
-
|
|
14444
|
-
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14455
|
-
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
//
|
|
14459
|
-
|
|
14460
|
-
|
|
14461
|
-
|
|
14462
|
-
console.
|
|
14038
|
+
//testGetPortfolioDailyHistory();
|
|
14039
|
+
//testOpenRouter();
|
|
14040
|
+
//testGetMarketStatus();
|
|
14041
|
+
// ===== CRYPTO API TESTS =====
|
|
14042
|
+
async function testCryptoMarketData() {
|
|
14043
|
+
console.log('\n--- Testing Crypto Market Data API ---');
|
|
14044
|
+
const log = (message, options = { type: 'info' }) => {
|
|
14045
|
+
log$1(message, { ...options, source: 'Test' });
|
|
14046
|
+
};
|
|
14047
|
+
if (!process.env.ALPACA_API_KEY || !process.env.ALPACA_SECRET_KEY) {
|
|
14048
|
+
console.log('Skipping Alpaca crypto tests: Missing environment variables (ALPACA_API_KEY, ALPACA_SECRET_KEY)');
|
|
14049
|
+
return;
|
|
14050
|
+
}
|
|
14051
|
+
try {
|
|
14052
|
+
const { marketDataAPI } = await Promise.resolve().then(function () { return alpacaMarketDataApi; });
|
|
14053
|
+
const { ALPACA_CRYPTO_PAIRS } = await import('./alpaca-crypto-pairs-DMOvRyzw.js');
|
|
14054
|
+
// Test symbols - using popular crypto pairs
|
|
14055
|
+
const testSymbols = ['BTC/USD', 'ETH/USD', 'DOGE/USD'];
|
|
14056
|
+
log(`Testing with crypto symbols: ${testSymbols.join(', ')}`);
|
|
14057
|
+
// 1. Test latest crypto bars
|
|
14058
|
+
console.log('\n[Crypto] Testing latest bars...');
|
|
14059
|
+
try {
|
|
14060
|
+
const latestBars = await marketDataAPI.getCryptoLatestBars(testSymbols);
|
|
14061
|
+
console.log(`✓ Retrieved latest bars for ${Object.keys(latestBars.bars).length} symbols`);
|
|
14062
|
+
// Show sample data for first symbol
|
|
14063
|
+
const firstSymbol = Object.keys(latestBars.bars)[0];
|
|
14064
|
+
if (firstSymbol) {
|
|
14065
|
+
const bar = latestBars.bars[firstSymbol];
|
|
14066
|
+
console.log(` ${firstSymbol}: Open=$${bar.o.toFixed(4)}, Close=$${bar.c.toFixed(4)}, Volume=${bar.v.toLocaleString()}`);
|
|
14067
|
+
}
|
|
14068
|
+
}
|
|
14069
|
+
catch (error) {
|
|
14070
|
+
console.error('✗ Latest bars test failed:', error);
|
|
14071
|
+
}
|
|
14072
|
+
// 2. Test latest crypto quotes
|
|
14073
|
+
console.log('\n[Crypto] Testing latest quotes...');
|
|
14074
|
+
try {
|
|
14075
|
+
const latestQuotes = await marketDataAPI.getCryptoLatestQuotes(testSymbols);
|
|
14076
|
+
console.log(`✓ Retrieved latest quotes for ${Object.keys(latestQuotes.quotes).length} symbols`);
|
|
14077
|
+
// Show sample data for first symbol
|
|
14078
|
+
const firstSymbol = Object.keys(latestQuotes.quotes)[0];
|
|
14079
|
+
if (firstSymbol) {
|
|
14080
|
+
const quote = latestQuotes.quotes[firstSymbol];
|
|
14081
|
+
console.log(` ${firstSymbol}: Bid=$${quote.bp.toFixed(4)}, Ask=$${quote.ap.toFixed(4)}, Spread=$${(quote.ap - quote.bp).toFixed(4)}`);
|
|
14082
|
+
}
|
|
14083
|
+
}
|
|
14084
|
+
catch (error) {
|
|
14085
|
+
console.error('✗ Latest quotes test failed:', error);
|
|
14086
|
+
}
|
|
14087
|
+
// 3. Test latest crypto trades
|
|
14088
|
+
console.log('\n[Crypto] Testing latest trades...');
|
|
14089
|
+
try {
|
|
14090
|
+
const latestTrades = await marketDataAPI.getCryptoLatestTrades(testSymbols);
|
|
14091
|
+
console.log(`✓ Retrieved latest trades for ${Object.keys(latestTrades.trades).length} symbols`);
|
|
14092
|
+
// Show sample data for first symbol
|
|
14093
|
+
const firstSymbol = Object.keys(latestTrades.trades)[0];
|
|
14094
|
+
if (firstSymbol) {
|
|
14095
|
+
const trade = latestTrades.trades[firstSymbol];
|
|
14096
|
+
console.log(` ${firstSymbol}: Price=$${trade.p.toFixed(4)}, Size=${trade.s.toLocaleString()}, Taker=${trade.tks}`);
|
|
14097
|
+
}
|
|
14098
|
+
}
|
|
14099
|
+
catch (error) {
|
|
14100
|
+
console.error('✗ Latest trades test failed:', error);
|
|
14101
|
+
}
|
|
14102
|
+
// 4. Test crypto snapshots
|
|
14103
|
+
console.log('\n[Crypto] Testing snapshots...');
|
|
14104
|
+
try {
|
|
14105
|
+
const snapshots = await marketDataAPI.getCryptoSnapshots(testSymbols);
|
|
14106
|
+
console.log(`✓ Retrieved snapshots for ${Object.keys(snapshots.snapshots).length} symbols`);
|
|
14107
|
+
// Show sample data for first symbol
|
|
14108
|
+
const firstSymbol = Object.keys(snapshots.snapshots)[0];
|
|
14109
|
+
if (firstSymbol) {
|
|
14110
|
+
const snapshot = snapshots.snapshots[firstSymbol];
|
|
14111
|
+
console.log(` ${firstSymbol} snapshot:`);
|
|
14112
|
+
if (snapshot.latestTrade) {
|
|
14113
|
+
console.log(` Latest trade: $${snapshot.latestTrade.p.toFixed(4)}`);
|
|
14114
|
+
}
|
|
14115
|
+
if (snapshot.latestQuote) {
|
|
14116
|
+
console.log(` Latest quote: $${snapshot.latestQuote.bp.toFixed(4)} x $${snapshot.latestQuote.ap.toFixed(4)}`);
|
|
14117
|
+
}
|
|
14118
|
+
if (snapshot.dailyBar) {
|
|
14119
|
+
console.log(` Daily bar: Open=$${snapshot.dailyBar.o.toFixed(4)}, Close=$${snapshot.dailyBar.c.toFixed(4)}`);
|
|
14120
|
+
}
|
|
14121
|
+
}
|
|
14122
|
+
}
|
|
14123
|
+
catch (error) {
|
|
14124
|
+
console.error('✗ Snapshots test failed:', error);
|
|
14125
|
+
}
|
|
14126
|
+
// 5. Test crypto orderbooks
|
|
14127
|
+
console.log('\n[Crypto] Testing orderbooks...');
|
|
14128
|
+
try {
|
|
14129
|
+
const orderbooks = await marketDataAPI.getCryptoLatestOrderbooks(testSymbols);
|
|
14130
|
+
console.log(`✓ Retrieved orderbooks for ${Object.keys(orderbooks.orderbooks).length} symbols`);
|
|
14131
|
+
// Show sample data for first symbol
|
|
14132
|
+
const firstSymbol = Object.keys(orderbooks.orderbooks)[0];
|
|
14133
|
+
if (firstSymbol) {
|
|
14134
|
+
const orderbook = orderbooks.orderbooks[firstSymbol];
|
|
14135
|
+
console.log(` ${firstSymbol} orderbook:`);
|
|
14136
|
+
console.log(` Best bid: $${orderbook.b[0]?.p.toFixed(4)} (size: ${orderbook.b[0]?.s.toLocaleString()})`);
|
|
14137
|
+
console.log(` Best ask: $${orderbook.a[0]?.p.toFixed(4)} (size: ${orderbook.a[0]?.s.toLocaleString()})`);
|
|
14138
|
+
console.log(` Bid depth: ${orderbook.b.length} levels, Ask depth: ${orderbook.a.length} levels`);
|
|
14139
|
+
}
|
|
14140
|
+
}
|
|
14141
|
+
catch (error) {
|
|
14142
|
+
console.error('✗ Orderbooks test failed:', error);
|
|
14143
|
+
}
|
|
14144
|
+
// 6. Test historical crypto bars (small sample)
|
|
14145
|
+
console.log('\n[Crypto] Testing historical bars...');
|
|
14146
|
+
try {
|
|
14147
|
+
const endDate = new Date();
|
|
14148
|
+
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000); // 24 hours ago
|
|
14149
|
+
const historicalBars = await marketDataAPI.getCryptoHistoricalBars({
|
|
14150
|
+
symbols: ['BTC/USD'], // Just test one symbol to avoid too much data
|
|
14151
|
+
timeframe: '1Hour',
|
|
14152
|
+
start: startDate.toISOString(),
|
|
14153
|
+
end: endDate.toISOString(),
|
|
14154
|
+
limit: 10
|
|
14155
|
+
});
|
|
14156
|
+
console.log(`✓ Retrieved historical bars for ${Object.keys(historicalBars.bars).length} symbols`);
|
|
14157
|
+
const btcBars = historicalBars.bars['BTC/USD'];
|
|
14158
|
+
if (btcBars && btcBars.length > 0) {
|
|
14159
|
+
console.log(` BTC/USD: ${btcBars.length} bars retrieved`);
|
|
14160
|
+
const { AlpacaMarketDataAPI } = await Promise.resolve().then(function () { return alpacaMarketDataApi; });
|
|
14161
|
+
const analysis = AlpacaMarketDataAPI.analyzeCryptoBars(btcBars);
|
|
14162
|
+
console.log(` Analysis: ${analysis}`);
|
|
14163
|
+
}
|
|
14164
|
+
}
|
|
14165
|
+
catch (error) {
|
|
14166
|
+
console.error('✗ Historical bars test failed:', error);
|
|
14167
|
+
}
|
|
14168
|
+
// 7. Test historical crypto quotes (small sample)
|
|
14169
|
+
console.log('\n[Crypto] Testing historical quotes...');
|
|
14170
|
+
try {
|
|
14171
|
+
const endDate = new Date();
|
|
14172
|
+
const startDate = new Date(endDate.getTime() - 2 * 60 * 60 * 1000); // 2 hours ago
|
|
14173
|
+
const historicalQuotes = await marketDataAPI.getCryptoHistoricalQuotes({
|
|
14174
|
+
symbols: ['ETH/USD'], // Just test one symbol
|
|
14175
|
+
start: startDate.toISOString(),
|
|
14176
|
+
end: endDate.toISOString(),
|
|
14177
|
+
limit: 10
|
|
14178
|
+
});
|
|
14179
|
+
console.log(`✓ Retrieved historical quotes for ${Object.keys(historicalQuotes.quotes).length} symbols`);
|
|
14180
|
+
const ethQuotes = historicalQuotes.quotes['ETH/USD'];
|
|
14181
|
+
if (ethQuotes && ethQuotes.length > 0) {
|
|
14182
|
+
console.log(` ETH/USD: ${ethQuotes.length} quotes retrieved`);
|
|
14183
|
+
const firstQuote = ethQuotes[0];
|
|
14184
|
+
const lastQuote = ethQuotes[ethQuotes.length - 1];
|
|
14185
|
+
console.log(` First quote: Bid=$${firstQuote.bp.toFixed(4)}, Ask=$${firstQuote.ap.toFixed(4)}`);
|
|
14186
|
+
console.log(` Last quote: Bid=$${lastQuote.bp.toFixed(4)}, Ask=$${lastQuote.ap.toFixed(4)}`);
|
|
14187
|
+
}
|
|
14188
|
+
}
|
|
14189
|
+
catch (error) {
|
|
14190
|
+
console.error('✗ Historical quotes test failed:', error);
|
|
14191
|
+
}
|
|
14192
|
+
// 8. Test historical crypto trades (small sample)
|
|
14193
|
+
console.log('\n[Crypto] Testing historical trades...');
|
|
14194
|
+
try {
|
|
14195
|
+
const endDate = new Date();
|
|
14196
|
+
const startDate = new Date(endDate.getTime() - 2 * 60 * 60 * 1000); // 2 hours ago
|
|
14197
|
+
const historicalTrades = await marketDataAPI.getCryptoHistoricalTrades({
|
|
14198
|
+
symbols: ['DOGE/USD'], // Just test one symbol
|
|
14199
|
+
start: startDate.toISOString(),
|
|
14200
|
+
end: endDate.toISOString(),
|
|
14201
|
+
limit: 10
|
|
14202
|
+
});
|
|
14203
|
+
console.log(`✓ Retrieved historical trades for ${Object.keys(historicalTrades.trades).length} symbols`);
|
|
14204
|
+
const dogeTrades = historicalTrades.trades['DOGE/USD'];
|
|
14205
|
+
if (dogeTrades && dogeTrades.length > 0) {
|
|
14206
|
+
console.log(` DOGE/USD: ${dogeTrades.length} trades retrieved`);
|
|
14207
|
+
const totalVolume = dogeTrades.reduce((sum, trade) => sum + trade.s, 0);
|
|
14208
|
+
const avgPrice = dogeTrades.reduce((sum, trade) => sum + trade.p, 0) / dogeTrades.length;
|
|
14209
|
+
console.log(` Total volume: ${totalVolume.toLocaleString()}, Avg price: $${avgPrice.toFixed(6)}`);
|
|
14210
|
+
}
|
|
14211
|
+
}
|
|
14212
|
+
catch (error) {
|
|
14213
|
+
console.error('✗ Historical trades test failed:', error);
|
|
14214
|
+
}
|
|
14215
|
+
// 9. Test with available crypto pairs from our types
|
|
14216
|
+
console.log('\n[Crypto] Testing with available crypto pairs...');
|
|
14217
|
+
try {
|
|
14218
|
+
const availablePairs = ALPACA_CRYPTO_PAIRS.slice(0, 5).map(pair => pair.symbol); // Test first 5 pairs
|
|
14219
|
+
console.log(`Testing with pairs: ${availablePairs.join(', ')}`);
|
|
14220
|
+
const pairsBars = await marketDataAPI.getCryptoLatestBars(availablePairs);
|
|
14221
|
+
console.log(`✓ Retrieved latest bars for ${Object.keys(pairsBars.bars).length} pairs from ALPACA_CRYPTO_PAIRS`);
|
|
14222
|
+
// Show info about each pair
|
|
14223
|
+
availablePairs.forEach(symbol => {
|
|
14224
|
+
const pair = ALPACA_CRYPTO_PAIRS.find(p => p.symbol === symbol);
|
|
14225
|
+
const bar = pairsBars.bars[symbol];
|
|
14226
|
+
if (pair && bar) {
|
|
14227
|
+
console.log(` ${pair.name}: $${bar.c.toFixed(6)} (min order: ${pair.min_order_size})`);
|
|
14228
|
+
}
|
|
14229
|
+
});
|
|
14230
|
+
}
|
|
14231
|
+
catch (error) {
|
|
14232
|
+
console.error('✗ Available crypto pairs test failed:', error);
|
|
14233
|
+
}
|
|
14234
|
+
console.log('\n✓ All crypto market data tests completed!');
|
|
14235
|
+
}
|
|
14236
|
+
catch (error) {
|
|
14237
|
+
console.error('✗ Crypto tests setup failed:', error);
|
|
14238
|
+
if (error instanceof Error) {
|
|
14239
|
+
console.error('Error message:', error.message);
|
|
14463
14240
|
}
|
|
14464
14241
|
}
|
|
14465
14242
|
}
|
|
14466
|
-
//
|
|
14467
|
-
|
|
14468
|
-
testGetMarketStatus();
|
|
14243
|
+
// Run crypto tests
|
|
14244
|
+
testCryptoMarketData();
|
|
14469
14245
|
//# sourceMappingURL=test.js.map
|