@discomedia/utils 1.0.58 → 1.0.60
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/README.md +0 -2
- package/dist/index-frontend.cjs +132 -9
- package/dist/index-frontend.cjs.map +1 -1
- package/dist/index-frontend.mjs +132 -9
- package/dist/index-frontend.mjs.map +1 -1
- package/dist/index.cjs +136 -1394
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +136 -1394
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +3 -3
- package/dist/test.js +136 -241
- package/dist/test.js.map +1 -1
- package/dist/types/index.d.ts +0 -82
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/misc-utils.d.ts +0 -6
- package/dist/types/misc-utils.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +0 -2
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types-frontend/index.d.ts +0 -82
- package/dist/types-frontend/index.d.ts.map +1 -1
- package/dist/types-frontend/misc-utils.d.ts +0 -6
- package/dist/types-frontend/misc-utils.d.ts.map +1 -1
- package/dist/types-frontend/types/index.d.ts +0 -2
- package/dist/types-frontend/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/types/polygon-indices.d.ts +0 -85
- package/dist/types/polygon-indices.d.ts.map +0 -1
- package/dist/types/polygon.d.ts +0 -126
- package/dist/types/polygon.d.ts.map +0 -1
- package/dist/types/technical-analysis.d.ts +0 -90
- package/dist/types/technical-analysis.d.ts.map +0 -1
- package/dist/types/types/polygon-indices-types.d.ts +0 -190
- package/dist/types/types/polygon-indices-types.d.ts.map +0 -1
- package/dist/types/types/polygon-types.d.ts +0 -204
- package/dist/types/types/polygon-types.d.ts.map +0 -1
- package/dist/types-frontend/polygon-indices.d.ts +0 -85
- package/dist/types-frontend/polygon-indices.d.ts.map +0 -1
- package/dist/types-frontend/polygon.d.ts +0 -126
- package/dist/types-frontend/polygon.d.ts.map +0 -1
- package/dist/types-frontend/technical-analysis.d.ts +0 -90
- package/dist/types-frontend/technical-analysis.d.ts.map +0 -1
- package/dist/types-frontend/types/polygon-indices-types.d.ts +0 -190
- package/dist/types-frontend/types/polygon-indices-types.d.ts.map +0 -1
- package/dist/types-frontend/types/polygon-types.d.ts +0 -204
- package/dist/types-frontend/types/polygon-types.d.ts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1380,933 +1380,6 @@ async function fetchWithRetry(url, options = {}, retries = 3, initialBackoff = 1
|
|
|
1380
1380
|
}
|
|
1381
1381
|
throw new Error('Failed to fetch after multiple attempts');
|
|
1382
1382
|
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Validates a Polygon.io API key by making a test request.
|
|
1385
|
-
* @param apiKey - The API key to validate.
|
|
1386
|
-
* @returns Promise that resolves to true if valid, false otherwise.
|
|
1387
|
-
*/
|
|
1388
|
-
async function validatePolygonApiKey(apiKey) {
|
|
1389
|
-
try {
|
|
1390
|
-
const response = await fetch(`https://api.polygon.io/v1/meta/symbols?apikey=${apiKey}&limit=1`);
|
|
1391
|
-
if (response.status === 401) {
|
|
1392
|
-
throw new Error('Invalid or expired Polygon.io API key');
|
|
1393
|
-
}
|
|
1394
|
-
if (response.status === 403) {
|
|
1395
|
-
throw new Error('Polygon.io API key lacks required permissions');
|
|
1396
|
-
}
|
|
1397
|
-
return response.ok;
|
|
1398
|
-
}
|
|
1399
|
-
catch (error) {
|
|
1400
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1401
|
-
console.error('Polygon.io API key validation failed:', errorMessage);
|
|
1402
|
-
return false;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
/*
|
|
1407
|
-
How it works:
|
|
1408
|
-
`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
|
|
1409
|
-
*/
|
|
1410
|
-
|
|
1411
|
-
class Node {
|
|
1412
|
-
value;
|
|
1413
|
-
next;
|
|
1414
|
-
|
|
1415
|
-
constructor(value) {
|
|
1416
|
-
this.value = value;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
class Queue {
|
|
1421
|
-
#head;
|
|
1422
|
-
#tail;
|
|
1423
|
-
#size;
|
|
1424
|
-
|
|
1425
|
-
constructor() {
|
|
1426
|
-
this.clear();
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
enqueue(value) {
|
|
1430
|
-
const node = new Node(value);
|
|
1431
|
-
|
|
1432
|
-
if (this.#head) {
|
|
1433
|
-
this.#tail.next = node;
|
|
1434
|
-
this.#tail = node;
|
|
1435
|
-
} else {
|
|
1436
|
-
this.#head = node;
|
|
1437
|
-
this.#tail = node;
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
this.#size++;
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
dequeue() {
|
|
1444
|
-
const current = this.#head;
|
|
1445
|
-
if (!current) {
|
|
1446
|
-
return;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
this.#head = this.#head.next;
|
|
1450
|
-
this.#size--;
|
|
1451
|
-
return current.value;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
peek() {
|
|
1455
|
-
if (!this.#head) {
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
return this.#head.value;
|
|
1460
|
-
|
|
1461
|
-
// TODO: Node.js 18.
|
|
1462
|
-
// return this.#head?.value;
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
clear() {
|
|
1466
|
-
this.#head = undefined;
|
|
1467
|
-
this.#tail = undefined;
|
|
1468
|
-
this.#size = 0;
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
get size() {
|
|
1472
|
-
return this.#size;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
* [Symbol.iterator]() {
|
|
1476
|
-
let current = this.#head;
|
|
1477
|
-
|
|
1478
|
-
while (current) {
|
|
1479
|
-
yield current.value;
|
|
1480
|
-
current = current.next;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
* drain() {
|
|
1485
|
-
while (this.#head) {
|
|
1486
|
-
yield this.dequeue();
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
function pLimit(concurrency) {
|
|
1492
|
-
let rejectOnClear = false;
|
|
1493
|
-
|
|
1494
|
-
if (typeof concurrency === 'object') {
|
|
1495
|
-
({concurrency, rejectOnClear = false} = concurrency);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
validateConcurrency(concurrency);
|
|
1499
|
-
|
|
1500
|
-
if (typeof rejectOnClear !== 'boolean') {
|
|
1501
|
-
throw new TypeError('Expected `rejectOnClear` to be a boolean');
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
const queue = new Queue();
|
|
1505
|
-
let activeCount = 0;
|
|
1506
|
-
|
|
1507
|
-
const resumeNext = () => {
|
|
1508
|
-
// Process the next queued function if we're under the concurrency limit
|
|
1509
|
-
if (activeCount < concurrency && queue.size > 0) {
|
|
1510
|
-
activeCount++;
|
|
1511
|
-
queue.dequeue().run();
|
|
1512
|
-
}
|
|
1513
|
-
};
|
|
1514
|
-
|
|
1515
|
-
const next = () => {
|
|
1516
|
-
activeCount--;
|
|
1517
|
-
resumeNext();
|
|
1518
|
-
};
|
|
1519
|
-
|
|
1520
|
-
const run = async (function_, resolve, arguments_) => {
|
|
1521
|
-
// Execute the function and capture the result promise
|
|
1522
|
-
const result = (async () => function_(...arguments_))();
|
|
1523
|
-
|
|
1524
|
-
// Resolve immediately with the promise (don't wait for completion)
|
|
1525
|
-
resolve(result);
|
|
1526
|
-
|
|
1527
|
-
// Wait for the function to complete (success or failure)
|
|
1528
|
-
// We catch errors here to prevent unhandled rejections,
|
|
1529
|
-
// but the original promise rejection is preserved for the caller
|
|
1530
|
-
try {
|
|
1531
|
-
await result;
|
|
1532
|
-
} catch {}
|
|
1533
|
-
|
|
1534
|
-
// Decrement active count and process next queued function
|
|
1535
|
-
next();
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
const enqueue = (function_, resolve, reject, arguments_) => {
|
|
1539
|
-
const queueItem = {reject};
|
|
1540
|
-
|
|
1541
|
-
// Queue the internal resolve function instead of the run function
|
|
1542
|
-
// to preserve the asynchronous execution context.
|
|
1543
|
-
new Promise(internalResolve => { // eslint-disable-line promise/param-names
|
|
1544
|
-
queueItem.run = internalResolve;
|
|
1545
|
-
queue.enqueue(queueItem);
|
|
1546
|
-
}).then(run.bind(undefined, function_, resolve, arguments_)); // eslint-disable-line promise/prefer-await-to-then
|
|
1547
|
-
|
|
1548
|
-
// Start processing immediately if we haven't reached the concurrency limit
|
|
1549
|
-
if (activeCount < concurrency) {
|
|
1550
|
-
resumeNext();
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
|
|
1554
|
-
const generator = (function_, ...arguments_) => new Promise((resolve, reject) => {
|
|
1555
|
-
enqueue(function_, resolve, reject, arguments_);
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
Object.defineProperties(generator, {
|
|
1559
|
-
activeCount: {
|
|
1560
|
-
get: () => activeCount,
|
|
1561
|
-
},
|
|
1562
|
-
pendingCount: {
|
|
1563
|
-
get: () => queue.size,
|
|
1564
|
-
},
|
|
1565
|
-
clearQueue: {
|
|
1566
|
-
value() {
|
|
1567
|
-
if (!rejectOnClear) {
|
|
1568
|
-
queue.clear();
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
const abortError = AbortSignal.abort().reason;
|
|
1573
|
-
|
|
1574
|
-
while (queue.size > 0) {
|
|
1575
|
-
queue.dequeue().reject(abortError);
|
|
1576
|
-
}
|
|
1577
|
-
},
|
|
1578
|
-
},
|
|
1579
|
-
concurrency: {
|
|
1580
|
-
get: () => concurrency,
|
|
1581
|
-
|
|
1582
|
-
set(newConcurrency) {
|
|
1583
|
-
validateConcurrency(newConcurrency);
|
|
1584
|
-
concurrency = newConcurrency;
|
|
1585
|
-
|
|
1586
|
-
queueMicrotask(() => {
|
|
1587
|
-
// eslint-disable-next-line no-unmodified-loop-condition
|
|
1588
|
-
while (activeCount < concurrency && queue.size > 0) {
|
|
1589
|
-
resumeNext();
|
|
1590
|
-
}
|
|
1591
|
-
});
|
|
1592
|
-
},
|
|
1593
|
-
},
|
|
1594
|
-
map: {
|
|
1595
|
-
async value(iterable, function_) {
|
|
1596
|
-
const promises = Array.from(iterable, (value, index) => this(function_, value, index));
|
|
1597
|
-
return Promise.all(promises);
|
|
1598
|
-
},
|
|
1599
|
-
},
|
|
1600
|
-
});
|
|
1601
|
-
|
|
1602
|
-
return generator;
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
function validateConcurrency(concurrency) {
|
|
1606
|
-
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
1607
|
-
throw new TypeError('Expected `concurrency` to be a number from 1 and up');
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
/**********************************************************************************
|
|
1612
|
-
* Polygon.io calls
|
|
1613
|
-
**********************************************************************************/
|
|
1614
|
-
// Constants from environment variables
|
|
1615
|
-
const POLYGON_API_KEY = process.env.POLYGON_API_KEY;
|
|
1616
|
-
// Define concurrency limits per API
|
|
1617
|
-
const POLYGON_CONCURRENCY_LIMIT = 100;
|
|
1618
|
-
const polygonLimit = pLimit(POLYGON_CONCURRENCY_LIMIT);
|
|
1619
|
-
// Use to update general information about stocks
|
|
1620
|
-
/**
|
|
1621
|
-
* Fetches general information about a stock ticker.
|
|
1622
|
-
* @param {string} symbol - The stock ticker symbol to fetch information for.
|
|
1623
|
-
* @param {Object} [options] - Optional parameters.
|
|
1624
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
1625
|
-
* @returns {Promise<PolygonTickerInfo | null>} The ticker information or null if not found.
|
|
1626
|
-
*/
|
|
1627
|
-
const fetchTickerInfo = async (symbol, options) => {
|
|
1628
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
1629
|
-
throw new Error('Polygon API key is missing');
|
|
1630
|
-
}
|
|
1631
|
-
const baseUrl = `https://api.polygon.io/v3/reference/tickers/${encodeURIComponent(symbol)}`;
|
|
1632
|
-
const params = new URLSearchParams({
|
|
1633
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
1634
|
-
});
|
|
1635
|
-
return polygonLimit(async () => {
|
|
1636
|
-
try {
|
|
1637
|
-
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
1638
|
-
const data = await response.json();
|
|
1639
|
-
// Check for "NOT_FOUND" status and return null
|
|
1640
|
-
if (data.status === 'NOT_FOUND') {
|
|
1641
|
-
console.warn(`Ticker not found: ${symbol}`);
|
|
1642
|
-
return null;
|
|
1643
|
-
}
|
|
1644
|
-
// Map the results to the required structure
|
|
1645
|
-
const results = data.results;
|
|
1646
|
-
if (!results) {
|
|
1647
|
-
throw new Error('No results in Polygon API response');
|
|
1648
|
-
}
|
|
1649
|
-
// Validate required fields
|
|
1650
|
-
const requiredFields = [
|
|
1651
|
-
'active',
|
|
1652
|
-
'currency_name',
|
|
1653
|
-
'locale',
|
|
1654
|
-
'market',
|
|
1655
|
-
'name',
|
|
1656
|
-
'primary_exchange',
|
|
1657
|
-
'ticker',
|
|
1658
|
-
'type',
|
|
1659
|
-
];
|
|
1660
|
-
for (const field of requiredFields) {
|
|
1661
|
-
if (results[field] === undefined) {
|
|
1662
|
-
throw new Error(`Missing required field in Polygon API response: ${field}`);
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
// Handle optional share_class_shares_outstanding field
|
|
1666
|
-
if (results.share_class_shares_outstanding === undefined) {
|
|
1667
|
-
results.share_class_shares_outstanding = null;
|
|
1668
|
-
}
|
|
1669
|
-
return {
|
|
1670
|
-
ticker: results.ticker,
|
|
1671
|
-
type: results.type,
|
|
1672
|
-
active: results.active,
|
|
1673
|
-
currency_name: results.currency_name,
|
|
1674
|
-
description: results.description ?? 'No description available',
|
|
1675
|
-
locale: results.locale,
|
|
1676
|
-
market: results.market,
|
|
1677
|
-
market_cap: results.market_cap ?? 0,
|
|
1678
|
-
name: results.name,
|
|
1679
|
-
primary_exchange: results.primary_exchange,
|
|
1680
|
-
share_class_shares_outstanding: results.share_class_shares_outstanding,
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
catch (error) {
|
|
1684
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1685
|
-
const contextualMessage = `Error fetching ticker info for ${symbol}`;
|
|
1686
|
-
console.error(`${contextualMessage}: ${errorMessage}`, {
|
|
1687
|
-
symbol,
|
|
1688
|
-
errorType: error instanceof Error && error.message.includes('AUTH_ERROR')
|
|
1689
|
-
? 'AUTH_ERROR'
|
|
1690
|
-
: error instanceof Error && error.message.includes('RATE_LIMIT')
|
|
1691
|
-
? 'RATE_LIMIT'
|
|
1692
|
-
: error instanceof Error && error.message.includes('NETWORK_ERROR')
|
|
1693
|
-
? 'NETWORK_ERROR'
|
|
1694
|
-
: 'UNKNOWN',
|
|
1695
|
-
url: hideApiKeyFromurl(`${baseUrl}?${params.toString()}`),
|
|
1696
|
-
source: 'PolygonAPI.fetchTickerInfo',
|
|
1697
|
-
timestamp: new Date().toISOString(),
|
|
1698
|
-
});
|
|
1699
|
-
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
1700
|
-
}
|
|
1701
|
-
});
|
|
1702
|
-
};
|
|
1703
|
-
// Fetch last trade using Polygon.io
|
|
1704
|
-
/**
|
|
1705
|
-
* Fetches the last trade for a given stock ticker.
|
|
1706
|
-
* @param {string} symbol - The stock ticker symbol to fetch the last trade for.
|
|
1707
|
-
* @param {Object} [options] - Optional parameters.
|
|
1708
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
1709
|
-
* @returns {Promise<PolygonQuote>} The last trade information.
|
|
1710
|
-
*/
|
|
1711
|
-
const fetchLastTrade = async (symbol, options) => {
|
|
1712
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
1713
|
-
throw new Error('Polygon API key is missing');
|
|
1714
|
-
}
|
|
1715
|
-
const baseUrl = `https://api.polygon.io/v2/last/trade/${encodeURIComponent(symbol)}`;
|
|
1716
|
-
const params = new URLSearchParams({
|
|
1717
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
1718
|
-
});
|
|
1719
|
-
return polygonLimit(async () => {
|
|
1720
|
-
try {
|
|
1721
|
-
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
1722
|
-
const data = await response.json();
|
|
1723
|
-
if (data.status !== 'OK' || !data.results) {
|
|
1724
|
-
throw new Error(`Polygon.io API error: ${data.status || 'No results'} ${data.error || ''}`);
|
|
1725
|
-
}
|
|
1726
|
-
const { p: price, s: vol, t: timestamp } = data.results;
|
|
1727
|
-
if (typeof price !== 'number' || typeof vol !== 'number' || typeof timestamp !== 'number') {
|
|
1728
|
-
throw new Error('Invalid trade data received from Polygon.io API');
|
|
1729
|
-
}
|
|
1730
|
-
return {
|
|
1731
|
-
price,
|
|
1732
|
-
vol,
|
|
1733
|
-
time: new Date(Math.floor(timestamp / 1000000)), // Convert nanoseconds to milliseconds
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
catch (error) {
|
|
1737
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1738
|
-
const contextualMessage = `Error fetching last trade for ${symbol}`;
|
|
1739
|
-
console.error(`${contextualMessage}: ${errorMessage}`, {
|
|
1740
|
-
symbol,
|
|
1741
|
-
errorType: error instanceof Error && error.message.includes('AUTH_ERROR')
|
|
1742
|
-
? 'AUTH_ERROR'
|
|
1743
|
-
: error instanceof Error && error.message.includes('RATE_LIMIT')
|
|
1744
|
-
? 'RATE_LIMIT'
|
|
1745
|
-
: error instanceof Error && error.message.includes('NETWORK_ERROR')
|
|
1746
|
-
? 'NETWORK_ERROR'
|
|
1747
|
-
: 'UNKNOWN',
|
|
1748
|
-
url: hideApiKeyFromurl(`${baseUrl}?${params.toString()}`),
|
|
1749
|
-
source: 'PolygonAPI.fetchLastTrade',
|
|
1750
|
-
timestamp: new Date().toISOString(),
|
|
1751
|
-
});
|
|
1752
|
-
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
1753
|
-
}
|
|
1754
|
-
});
|
|
1755
|
-
};
|
|
1756
|
-
// use Polygon for all price data fetching
|
|
1757
|
-
/**
|
|
1758
|
-
* Fetches price data for a given stock ticker.
|
|
1759
|
-
* @param {Object} params - The parameters for fetching price data.
|
|
1760
|
-
* @param {string} params.ticker - The stock ticker symbol.
|
|
1761
|
-
* @param {number} params.start - The start timestamp for fetching price data.
|
|
1762
|
-
* @param {number} [params.end] - The end timestamp for fetching price data.
|
|
1763
|
-
* @param {number} params.multiplier - The multiplier for the price data.
|
|
1764
|
-
* @param {string} params.timespan - The timespan for the price data.
|
|
1765
|
-
* @param {number} [params.limit] - The maximum number of price data points to fetch.
|
|
1766
|
-
* @param {Object} [options] - Optional parameters.
|
|
1767
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
1768
|
-
* @returns {Promise<PolygonPriceData[]>} The fetched price data.
|
|
1769
|
-
*/
|
|
1770
|
-
const fetchPrices = async (params, options) => {
|
|
1771
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
1772
|
-
throw new Error('Polygon API key is missing');
|
|
1773
|
-
}
|
|
1774
|
-
const { ticker, start, end = Date.now().valueOf(), multiplier, timespan, limit = 1000 } = params;
|
|
1775
|
-
const baseUrl = `https://api.polygon.io/v2/aggs/ticker/${encodeURIComponent(ticker)}/range/${multiplier}/${timespan}/${start}/${end}`;
|
|
1776
|
-
const urlParams = new URLSearchParams({
|
|
1777
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
1778
|
-
adjusted: 'true',
|
|
1779
|
-
sort: 'asc',
|
|
1780
|
-
limit: limit.toString(),
|
|
1781
|
-
});
|
|
1782
|
-
return polygonLimit(async () => {
|
|
1783
|
-
try {
|
|
1784
|
-
let allResults = [];
|
|
1785
|
-
let nextUrl = `${baseUrl}?${urlParams.toString()}`;
|
|
1786
|
-
while (nextUrl) {
|
|
1787
|
-
//console.log(`Debug: Fetching ${nextUrl}`);
|
|
1788
|
-
const response = await fetchWithRetry(nextUrl, {}, 3, 1000);
|
|
1789
|
-
const data = await response.json();
|
|
1790
|
-
if (data.status !== 'OK') {
|
|
1791
|
-
throw new Error(`Polygon.io API responded with status: ${data.status}`);
|
|
1792
|
-
}
|
|
1793
|
-
if (data.results) {
|
|
1794
|
-
allResults = [...allResults, ...data.results];
|
|
1795
|
-
}
|
|
1796
|
-
// Check if there's a next page and append API key
|
|
1797
|
-
nextUrl = data.next_url ? `${data.next_url}&apiKey=${options?.apiKey || POLYGON_API_KEY}` : '';
|
|
1798
|
-
}
|
|
1799
|
-
return allResults.map((entry) => ({
|
|
1800
|
-
date: new Date(entry.t).toLocaleString('en-US', {
|
|
1801
|
-
year: 'numeric',
|
|
1802
|
-
month: 'short',
|
|
1803
|
-
day: '2-digit',
|
|
1804
|
-
hour: '2-digit',
|
|
1805
|
-
minute: '2-digit',
|
|
1806
|
-
second: '2-digit',
|
|
1807
|
-
timeZone: 'America/New_York',
|
|
1808
|
-
timeZoneName: 'short',
|
|
1809
|
-
hourCycle: 'h23',
|
|
1810
|
-
}),
|
|
1811
|
-
timeStamp: entry.t,
|
|
1812
|
-
open: entry.o,
|
|
1813
|
-
high: entry.h,
|
|
1814
|
-
low: entry.l,
|
|
1815
|
-
close: entry.c,
|
|
1816
|
-
vol: entry.v,
|
|
1817
|
-
vwap: entry.vw,
|
|
1818
|
-
trades: entry.n,
|
|
1819
|
-
}));
|
|
1820
|
-
}
|
|
1821
|
-
catch (error) {
|
|
1822
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1823
|
-
const contextualMessage = `Error fetching price data for ${ticker}`;
|
|
1824
|
-
console.error(`${contextualMessage}: ${errorMessage}`, {
|
|
1825
|
-
ticker,
|
|
1826
|
-
errorType: error instanceof Error && error.message.includes('AUTH_ERROR')
|
|
1827
|
-
? 'AUTH_ERROR'
|
|
1828
|
-
: error instanceof Error && error.message.includes('RATE_LIMIT')
|
|
1829
|
-
? 'RATE_LIMIT'
|
|
1830
|
-
: error instanceof Error && error.message.includes('NETWORK_ERROR')
|
|
1831
|
-
? 'NETWORK_ERROR'
|
|
1832
|
-
: 'UNKNOWN',
|
|
1833
|
-
source: 'PolygonAPI.fetchPrices',
|
|
1834
|
-
timestamp: new Date().toISOString(),
|
|
1835
|
-
});
|
|
1836
|
-
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
1837
|
-
}
|
|
1838
|
-
});
|
|
1839
|
-
};
|
|
1840
|
-
/**
|
|
1841
|
-
* Analyzes the price data for a given stock.
|
|
1842
|
-
* @param {PolygonPriceData[]} priceData - The price data to analyze.
|
|
1843
|
-
* @returns {string} The analysis report.
|
|
1844
|
-
*/
|
|
1845
|
-
function analysePolygonPriceData(priceData) {
|
|
1846
|
-
if (!priceData || priceData.length === 0) {
|
|
1847
|
-
return 'No price data available for analysis.';
|
|
1848
|
-
}
|
|
1849
|
-
// Parse the dates into Date objects
|
|
1850
|
-
const parsedData = priceData.map((entry) => ({
|
|
1851
|
-
...entry,
|
|
1852
|
-
date: new Date(entry.date),
|
|
1853
|
-
}));
|
|
1854
|
-
// Sort the data by date
|
|
1855
|
-
parsedData.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
1856
|
-
// Extract start and end times
|
|
1857
|
-
const startTime = parsedData[0].date;
|
|
1858
|
-
const endTime = parsedData[parsedData.length - 1].date;
|
|
1859
|
-
// Calculate the total time in hours
|
|
1860
|
-
(endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60);
|
|
1861
|
-
// Calculate the interval between data points
|
|
1862
|
-
const intervals = parsedData
|
|
1863
|
-
.slice(1)
|
|
1864
|
-
.map((_, i) => (parsedData[i + 1].date.getTime() - parsedData[i].date.getTime()) / 1000); // in seconds
|
|
1865
|
-
const avgInterval = intervals.length > 0 ? intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length : 0;
|
|
1866
|
-
// Format the report
|
|
1867
|
-
const report = `
|
|
1868
|
-
Report:
|
|
1869
|
-
* Start time of data (US Eastern): ${startTime.toLocaleString('en-US', { timeZone: 'America/New_York' })}
|
|
1870
|
-
* End time of data (US Eastern): ${endTime.toLocaleString('en-US', { timeZone: 'America/New_York' })}
|
|
1871
|
-
* Number of data points: ${priceData.length}
|
|
1872
|
-
* Average interval between data points (seconds): ${avgInterval.toFixed(2)}
|
|
1873
|
-
`;
|
|
1874
|
-
return report.trim();
|
|
1875
|
-
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Fetches grouped daily price data for a specific date.
|
|
1878
|
-
* @param {string} date - The date to fetch grouped daily data for.
|
|
1879
|
-
* @param {Object} [options] - Optional parameters.
|
|
1880
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
1881
|
-
* @param {boolean} [options.adjusted] - Whether to adjust the data.
|
|
1882
|
-
* @param {boolean} [options.includeOTC] - Whether to include OTC data.
|
|
1883
|
-
* @returns {Promise<PolygonGroupedDailyResponse>} The grouped daily response.
|
|
1884
|
-
*/
|
|
1885
|
-
const fetchGroupedDaily = async (date, options) => {
|
|
1886
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
1887
|
-
throw new Error('Polygon API key is missing');
|
|
1888
|
-
}
|
|
1889
|
-
const baseUrl = `https://api.polygon.io/v2/aggs/grouped/locale/us/market/stocks/${date}`;
|
|
1890
|
-
const params = new URLSearchParams({
|
|
1891
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
1892
|
-
adjusted: options?.adjusted !== false ? 'true' : 'false',
|
|
1893
|
-
include_otc: options?.includeOTC ? 'true' : 'false',
|
|
1894
|
-
});
|
|
1895
|
-
return polygonLimit(async () => {
|
|
1896
|
-
try {
|
|
1897
|
-
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
1898
|
-
const data = await response.json();
|
|
1899
|
-
if (data.status !== 'OK') {
|
|
1900
|
-
throw new Error(`Polygon.io API responded with status: ${data.status}`);
|
|
1901
|
-
}
|
|
1902
|
-
return {
|
|
1903
|
-
adjusted: data.adjusted,
|
|
1904
|
-
queryCount: data.queryCount,
|
|
1905
|
-
request_id: data.request_id,
|
|
1906
|
-
resultsCount: data.resultsCount,
|
|
1907
|
-
status: data.status,
|
|
1908
|
-
results: data.results.map((result) => ({
|
|
1909
|
-
symbol: result.T,
|
|
1910
|
-
timeStamp: result.t,
|
|
1911
|
-
open: result.o,
|
|
1912
|
-
high: result.h,
|
|
1913
|
-
low: result.l,
|
|
1914
|
-
close: result.c,
|
|
1915
|
-
vol: result.v,
|
|
1916
|
-
vwap: result.vw,
|
|
1917
|
-
trades: result.n,
|
|
1918
|
-
})),
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
|
-
catch (error) {
|
|
1922
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
1923
|
-
const contextualMessage = `Error fetching grouped daily data for ${date}`;
|
|
1924
|
-
console.error(`${contextualMessage}: ${errorMessage}`, {
|
|
1925
|
-
date,
|
|
1926
|
-
errorType: error instanceof Error && error.message.includes('AUTH_ERROR')
|
|
1927
|
-
? 'AUTH_ERROR'
|
|
1928
|
-
: error instanceof Error && error.message.includes('RATE_LIMIT')
|
|
1929
|
-
? 'RATE_LIMIT'
|
|
1930
|
-
: error instanceof Error && error.message.includes('NETWORK_ERROR')
|
|
1931
|
-
? 'NETWORK_ERROR'
|
|
1932
|
-
: 'UNKNOWN',
|
|
1933
|
-
url: hideApiKeyFromurl(`${baseUrl}?${params.toString()}`),
|
|
1934
|
-
source: 'PolygonAPI.fetchGroupedDaily',
|
|
1935
|
-
timestamp: new Date().toISOString(),
|
|
1936
|
-
});
|
|
1937
|
-
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
1938
|
-
}
|
|
1939
|
-
});
|
|
1940
|
-
};
|
|
1941
|
-
/**
|
|
1942
|
-
* Formats the price data into a readable string.
|
|
1943
|
-
* @param {PolygonPriceData[]} priceData - The price data to format.
|
|
1944
|
-
* @returns {string} The formatted price data.
|
|
1945
|
-
*/
|
|
1946
|
-
function formatPriceData(priceData) {
|
|
1947
|
-
if (!priceData || priceData.length === 0)
|
|
1948
|
-
return 'No price data available';
|
|
1949
|
-
return priceData
|
|
1950
|
-
.map((d) => {
|
|
1951
|
-
// For daily data, remove the time portion if it's all zeros
|
|
1952
|
-
const dateStr = d.date.includes(', 00:00:00') ? d.date.split(', 00:00:00')[0] : d.date;
|
|
1953
|
-
return [
|
|
1954
|
-
dateStr,
|
|
1955
|
-
`O: ${formatCurrency(d.open)}`,
|
|
1956
|
-
`H: ${formatCurrency(d.high)}`,
|
|
1957
|
-
`L: ${formatCurrency(d.low)}`,
|
|
1958
|
-
`C: ${formatCurrency(d.close)}`,
|
|
1959
|
-
`Vol: ${d.vol}`,
|
|
1960
|
-
].join(' | ');
|
|
1961
|
-
})
|
|
1962
|
-
.join('\n');
|
|
1963
|
-
}
|
|
1964
|
-
const fetchDailyOpenClose = async (
|
|
1965
|
-
/**
|
|
1966
|
-
* Fetches the daily open and close data for a given stock ticker.
|
|
1967
|
-
* @param {string} symbol - The stock ticker symbol to fetch data for.
|
|
1968
|
-
* @param {Date} [date=new Date()] - The date to fetch data for.
|
|
1969
|
-
* @param {Object} [options] - Optional parameters.
|
|
1970
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
1971
|
-
* @param {boolean} [options.adjusted] - Whether to adjust the data.
|
|
1972
|
-
* @returns {Promise<PolygonDailyOpenClose>} The daily open and close data.
|
|
1973
|
-
*/
|
|
1974
|
-
symbol, date = new Date(), options) => {
|
|
1975
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
1976
|
-
throw new Error('Polygon API key is missing');
|
|
1977
|
-
}
|
|
1978
|
-
const formattedDate = date.toISOString().split('T')[0]; // Format as YYYY-MM-DD
|
|
1979
|
-
const baseUrl = `https://api.polygon.io/v1/open-close/${encodeURIComponent(symbol)}/${formattedDate}`;
|
|
1980
|
-
const params = new URLSearchParams({
|
|
1981
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
1982
|
-
adjusted: (options?.adjusted ?? true).toString(),
|
|
1983
|
-
});
|
|
1984
|
-
return polygonLimit(async () => {
|
|
1985
|
-
const response = await fetchWithRetry(`${baseUrl}?${params.toString()}`, {}, 3, 1000);
|
|
1986
|
-
const data = await response.json();
|
|
1987
|
-
if (data.status !== 'OK') {
|
|
1988
|
-
throw new Error(`Failed to fetch daily open/close data for ${symbol}: ${data.status}`);
|
|
1989
|
-
}
|
|
1990
|
-
return data;
|
|
1991
|
-
});
|
|
1992
|
-
};
|
|
1993
|
-
/**
|
|
1994
|
-
* Gets the previous close price for a given stock ticker.
|
|
1995
|
-
* @param {string} symbol - The stock ticker symbol to fetch the previous close for.
|
|
1996
|
-
* @param {Date} [referenceDate] - The reference date to use for fetching the previous close.
|
|
1997
|
-
* @returns {Promise<{ close: number; date: Date }>} The previous close price and date.
|
|
1998
|
-
*/
|
|
1999
|
-
async function getPreviousClose(symbol, referenceDate, options) {
|
|
2000
|
-
const previousDate = getLastFullTradingDate(referenceDate);
|
|
2001
|
-
const lastOpenClose = await fetchDailyOpenClose(symbol, previousDate, options);
|
|
2002
|
-
if (!lastOpenClose) {
|
|
2003
|
-
throw new Error(`Could not fetch last trade price for ${symbol}`);
|
|
2004
|
-
}
|
|
2005
|
-
return {
|
|
2006
|
-
close: lastOpenClose.close,
|
|
2007
|
-
date: previousDate,
|
|
2008
|
-
};
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* Fetches trade data for a given stock ticker.
|
|
2012
|
-
* @param {string} symbol - The stock ticker symbol to fetch trades for.
|
|
2013
|
-
* @param {Object} [options] - Optional parameters.
|
|
2014
|
-
* @param {string} [options.apiKey] - The API key to use for the request.
|
|
2015
|
-
* @param {string | number} [options.timestamp] - The timestamp for fetching trades.
|
|
2016
|
-
* @param {string | number} [options.timestampgt] - Greater than timestamp for fetching trades.
|
|
2017
|
-
* @param {string | number} [options.timestampgte] - Greater than or equal to timestamp for fetching trades.
|
|
2018
|
-
* @param {string | number} [options.timestamplt] - Less than timestamp for fetching trades.
|
|
2019
|
-
* @param {string | number} [options.timestamplte] - Less than or equal to timestamp for fetching trades.
|
|
2020
|
-
* @param {'asc' | 'desc'} [options.order] - The order of the trades.
|
|
2021
|
-
* @param {number} [options.limit] - The maximum number of trades to fetch.
|
|
2022
|
-
* @param {string} [options.sort] - The sort order for the trades.
|
|
2023
|
-
* @returns {Promise<PolygonTradesResponse>} The fetched trades response.
|
|
2024
|
-
*/
|
|
2025
|
-
const fetchTrades = async (symbol, options) => {
|
|
2026
|
-
if (!options?.apiKey && !POLYGON_API_KEY) {
|
|
2027
|
-
throw new Error('Polygon API key is missing');
|
|
2028
|
-
}
|
|
2029
|
-
const baseUrl = `https://api.polygon.io/v3/trades/${encodeURIComponent(symbol)}`;
|
|
2030
|
-
const params = new URLSearchParams({
|
|
2031
|
-
apiKey: options?.apiKey || POLYGON_API_KEY,
|
|
2032
|
-
});
|
|
2033
|
-
// Add optional parameters if they exist
|
|
2034
|
-
if (options?.timestamp)
|
|
2035
|
-
params.append('timestamp', options.timestamp.toString());
|
|
2036
|
-
if (options?.timestampgt)
|
|
2037
|
-
params.append('timestamp.gt', options.timestampgt.toString());
|
|
2038
|
-
if (options?.timestampgte)
|
|
2039
|
-
params.append('timestamp.gte', options.timestampgte.toString());
|
|
2040
|
-
if (options?.timestamplt)
|
|
2041
|
-
params.append('timestamp.lt', options.timestamplt.toString());
|
|
2042
|
-
if (options?.timestamplte)
|
|
2043
|
-
params.append('timestamp.lte', options.timestamplte.toString());
|
|
2044
|
-
if (options?.order)
|
|
2045
|
-
params.append('order', options.order);
|
|
2046
|
-
if (options?.limit)
|
|
2047
|
-
params.append('limit', options.limit.toString());
|
|
2048
|
-
if (options?.sort)
|
|
2049
|
-
params.append('sort', options.sort);
|
|
2050
|
-
return polygonLimit(async () => {
|
|
2051
|
-
const url = `${baseUrl}?${params.toString()}`;
|
|
2052
|
-
try {
|
|
2053
|
-
console.log(`[DEBUG] Fetching trades for ${symbol} from ${url}`);
|
|
2054
|
-
const response = await fetchWithRetry(url, {}, 3, 1000);
|
|
2055
|
-
const data = (await response.json());
|
|
2056
|
-
if ('message' in data) {
|
|
2057
|
-
// This is an error response
|
|
2058
|
-
throw new Error(`Polygon API Error: ${data.message}`);
|
|
2059
|
-
}
|
|
2060
|
-
return data;
|
|
2061
|
-
}
|
|
2062
|
-
catch (error) {
|
|
2063
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
2064
|
-
const contextualMessage = `Error fetching trades for ${symbol}`;
|
|
2065
|
-
console.error(`${contextualMessage}: ${errorMessage}`, {
|
|
2066
|
-
symbol,
|
|
2067
|
-
errorType: error instanceof Error && error.message.includes('AUTH_ERROR')
|
|
2068
|
-
? 'AUTH_ERROR'
|
|
2069
|
-
: error instanceof Error && error.message.includes('RATE_LIMIT')
|
|
2070
|
-
? 'RATE_LIMIT'
|
|
2071
|
-
: error instanceof Error && error.message.includes('NETWORK_ERROR')
|
|
2072
|
-
? 'NETWORK_ERROR'
|
|
2073
|
-
: 'UNKNOWN',
|
|
2074
|
-
url: hideApiKeyFromurl(url),
|
|
2075
|
-
source: 'PolygonAPI.fetchTrades',
|
|
2076
|
-
timestamp: new Date().toISOString(),
|
|
2077
|
-
});
|
|
2078
|
-
throw new Error(`${contextualMessage}: ${errorMessage}`);
|
|
2079
|
-
}
|
|
2080
|
-
});
|
|
2081
|
-
};
|
|
2082
|
-
|
|
2083
|
-
/**
|
|
2084
|
-
* Polygon Indices API Implementation
|
|
2085
|
-
*
|
|
2086
|
-
* This module provides functions to interact with the Polygon.io Indices API.
|
|
2087
|
-
*/
|
|
2088
|
-
// Constants from environment variables
|
|
2089
|
-
const { ALPACA_INDICES_API_KEY } = process.env;
|
|
2090
|
-
// Define concurrency limits for API
|
|
2091
|
-
const POLYGON_INDICES_CONCURRENCY_LIMIT = 5;
|
|
2092
|
-
const polygonIndicesLimit = pLimit(POLYGON_INDICES_CONCURRENCY_LIMIT);
|
|
2093
|
-
// Base URL for Polygon API
|
|
2094
|
-
const POLYGON_API_BASE_URL = 'https://api.polygon.io';
|
|
2095
|
-
/**
|
|
2096
|
-
* Validates that an API key is available
|
|
2097
|
-
* @param {string | undefined} apiKey - Optional API key to use
|
|
2098
|
-
* @throws {Error} If no API key is available
|
|
2099
|
-
*/
|
|
2100
|
-
const validateApiKey = (apiKey) => {
|
|
2101
|
-
const key = apiKey || ALPACA_INDICES_API_KEY;
|
|
2102
|
-
if (!key) {
|
|
2103
|
-
throw new Error('Polygon Indices API key is missing');
|
|
2104
|
-
}
|
|
2105
|
-
return key;
|
|
2106
|
-
};
|
|
2107
|
-
/**
|
|
2108
|
-
* Fetches aggregate bars for an index over a given date range in custom time window sizes.
|
|
2109
|
-
*
|
|
2110
|
-
* @param {PolygonIndicesAggregatesParams} params - Parameters for the aggregates request
|
|
2111
|
-
* @param {Object} [options] - Optional parameters
|
|
2112
|
-
* @param {string} [options.apiKey] - API key to use for the request
|
|
2113
|
-
* @returns {Promise<PolygonIndicesAggregatesResponse>} The aggregates response
|
|
2114
|
-
*/
|
|
2115
|
-
const fetchIndicesAggregates = async (params, options) => {
|
|
2116
|
-
const apiKey = validateApiKey(options?.apiKey);
|
|
2117
|
-
const { indicesTicker, multiplier, timespan, from, to, sort = 'asc', limit } = params;
|
|
2118
|
-
const url = new URL(`${POLYGON_API_BASE_URL}/v2/aggs/ticker/${encodeURIComponent(indicesTicker)}/range/${multiplier}/${timespan}/${from}/${to}`);
|
|
2119
|
-
const queryParams = new URLSearchParams();
|
|
2120
|
-
queryParams.append('apiKey', apiKey);
|
|
2121
|
-
if (sort) {
|
|
2122
|
-
queryParams.append('sort', sort);
|
|
2123
|
-
}
|
|
2124
|
-
if (limit) {
|
|
2125
|
-
queryParams.append('limit', limit.toString());
|
|
2126
|
-
}
|
|
2127
|
-
url.search = queryParams.toString();
|
|
2128
|
-
return polygonIndicesLimit(async () => {
|
|
2129
|
-
try {
|
|
2130
|
-
const response = await fetchWithRetry(url.toString(), {}, 3, 300);
|
|
2131
|
-
const data = await response.json();
|
|
2132
|
-
if (data.status === 'ERROR') {
|
|
2133
|
-
throw new Error(`Polygon API Error: ${data.error}`);
|
|
2134
|
-
}
|
|
2135
|
-
return data;
|
|
2136
|
-
}
|
|
2137
|
-
catch (error) {
|
|
2138
|
-
console.error('Error fetching indices aggregates:', error);
|
|
2139
|
-
throw error;
|
|
2140
|
-
}
|
|
2141
|
-
});
|
|
2142
|
-
};
|
|
2143
|
-
/**
|
|
2144
|
-
* Gets the previous day's open, high, low, and close (OHLC) for the specified index.
|
|
2145
|
-
*
|
|
2146
|
-
* @param {string} indicesTicker - The ticker symbol of the index
|
|
2147
|
-
* @param {Object} [options] - Optional parameters
|
|
2148
|
-
* @param {string} [options.apiKey] - API key to use for the request
|
|
2149
|
-
* @returns {Promise<PolygonIndicesPrevCloseResponse>} The previous close response
|
|
2150
|
-
*/
|
|
2151
|
-
const fetchIndicesPreviousClose = async (indicesTicker, options) => {
|
|
2152
|
-
const apiKey = validateApiKey(options?.apiKey);
|
|
2153
|
-
const url = new URL(`${POLYGON_API_BASE_URL}/v2/aggs/ticker/${encodeURIComponent(indicesTicker)}/prev`);
|
|
2154
|
-
const queryParams = new URLSearchParams();
|
|
2155
|
-
queryParams.append('apiKey', apiKey);
|
|
2156
|
-
url.search = queryParams.toString();
|
|
2157
|
-
return polygonIndicesLimit(async () => {
|
|
2158
|
-
try {
|
|
2159
|
-
const response = await fetchWithRetry(url.toString(), {}, 3, 300);
|
|
2160
|
-
const data = await response.json();
|
|
2161
|
-
if (data.status === 'ERROR') {
|
|
2162
|
-
throw new Error(`Polygon API Error: ${data.error}`);
|
|
2163
|
-
}
|
|
2164
|
-
return data;
|
|
2165
|
-
}
|
|
2166
|
-
catch (error) {
|
|
2167
|
-
console.error('Error fetching indices previous close:', error);
|
|
2168
|
-
throw error;
|
|
2169
|
-
}
|
|
2170
|
-
});
|
|
2171
|
-
};
|
|
2172
|
-
/**
|
|
2173
|
-
* Gets the open, close and afterhours values of an index symbol on a certain date.
|
|
2174
|
-
*
|
|
2175
|
-
* @param {string} indicesTicker - The ticker symbol of the index
|
|
2176
|
-
* @param {string} date - The date in YYYY-MM-DD format
|
|
2177
|
-
* @param {Object} [options] - Optional parameters
|
|
2178
|
-
* @param {string} [options.apiKey] - API key to use for the request
|
|
2179
|
-
* @returns {Promise<PolygonIndicesDailyOpenCloseResponse>} The daily open/close response
|
|
2180
|
-
*/
|
|
2181
|
-
const fetchIndicesDailyOpenClose = async (indicesTicker, date, options) => {
|
|
2182
|
-
const apiKey = validateApiKey(options?.apiKey);
|
|
2183
|
-
const url = new URL(`${POLYGON_API_BASE_URL}/v1/open-close/${encodeURIComponent(indicesTicker)}/${date}`);
|
|
2184
|
-
const queryParams = new URLSearchParams();
|
|
2185
|
-
queryParams.append('apiKey', apiKey);
|
|
2186
|
-
url.search = queryParams.toString();
|
|
2187
|
-
return polygonIndicesLimit(async () => {
|
|
2188
|
-
try {
|
|
2189
|
-
const response = await fetchWithRetry(url.toString(), {}, 3, 300);
|
|
2190
|
-
const data = await response.json();
|
|
2191
|
-
if (data.status === 'ERROR') {
|
|
2192
|
-
throw new Error(`Polygon API Error: ${data.error}`);
|
|
2193
|
-
}
|
|
2194
|
-
return data;
|
|
2195
|
-
}
|
|
2196
|
-
catch (error) {
|
|
2197
|
-
console.error('Error fetching indices daily open/close:', error);
|
|
2198
|
-
throw error;
|
|
2199
|
-
}
|
|
2200
|
-
});
|
|
2201
|
-
};
|
|
2202
|
-
/**
|
|
2203
|
-
* Gets a snapshot of indices data for specified tickers.
|
|
2204
|
-
*
|
|
2205
|
-
* @param {PolygonIndicesSnapshotParams} [params] - Parameters for the snapshot request
|
|
2206
|
-
* @param {Object} [options] - Optional parameters
|
|
2207
|
-
* @param {string} [options.apiKey] - API key to use for the request
|
|
2208
|
-
* @returns {Promise<PolygonIndicesSnapshotResponse>} The indices snapshot response
|
|
2209
|
-
*/
|
|
2210
|
-
const fetchIndicesSnapshot = async (params, options) => {
|
|
2211
|
-
const apiKey = validateApiKey(options?.apiKey);
|
|
2212
|
-
const url = new URL(`${POLYGON_API_BASE_URL}/v3/snapshot/indices`);
|
|
2213
|
-
const queryParams = new URLSearchParams();
|
|
2214
|
-
queryParams.append('apiKey', apiKey);
|
|
2215
|
-
if (params?.tickers?.length) {
|
|
2216
|
-
queryParams.append('ticker.any_of', params.tickers.join(','));
|
|
2217
|
-
}
|
|
2218
|
-
if (params?.order) {
|
|
2219
|
-
queryParams.append('order', params.order);
|
|
2220
|
-
}
|
|
2221
|
-
if (params?.limit) {
|
|
2222
|
-
queryParams.append('limit', params.limit.toString());
|
|
2223
|
-
}
|
|
2224
|
-
if (params?.sort) {
|
|
2225
|
-
queryParams.append('sort', params.sort);
|
|
2226
|
-
}
|
|
2227
|
-
url.search = queryParams.toString();
|
|
2228
|
-
return polygonIndicesLimit(async () => {
|
|
2229
|
-
try {
|
|
2230
|
-
const response = await fetchWithRetry(url.toString(), {}, 3, 300);
|
|
2231
|
-
const data = await response.json();
|
|
2232
|
-
if (data.status === 'ERROR') {
|
|
2233
|
-
throw new Error(`Polygon API Error: ${data.error}`);
|
|
2234
|
-
}
|
|
2235
|
-
return data;
|
|
2236
|
-
}
|
|
2237
|
-
catch (error) {
|
|
2238
|
-
console.error('Error fetching indices snapshot:', error);
|
|
2239
|
-
throw error;
|
|
2240
|
-
}
|
|
2241
|
-
});
|
|
2242
|
-
};
|
|
2243
|
-
/**
|
|
2244
|
-
* Gets snapshots for assets of all types, including indices.
|
|
2245
|
-
*
|
|
2246
|
-
* @param {string[]} tickers - Array of tickers to fetch snapshots for
|
|
2247
|
-
* @param {Object} [options] - Optional parameters
|
|
2248
|
-
* @param {string} [options.apiKey] - API key to use for the request
|
|
2249
|
-
* @param {string} [options.type] - Filter by asset type
|
|
2250
|
-
* @param {string} [options.order] - Order results
|
|
2251
|
-
* @param {number} [options.limit] - Limit the number of results
|
|
2252
|
-
* @param {string} [options.sort] - Sort field
|
|
2253
|
-
* @returns {Promise<any>} The universal snapshot response
|
|
2254
|
-
*/
|
|
2255
|
-
const fetchUniversalSnapshot = async (tickers, options) => {
|
|
2256
|
-
const apiKey = validateApiKey(options?.apiKey);
|
|
2257
|
-
const url = new URL(`${POLYGON_API_BASE_URL}/v3/snapshot`);
|
|
2258
|
-
const queryParams = new URLSearchParams();
|
|
2259
|
-
queryParams.append('apiKey', apiKey);
|
|
2260
|
-
if (tickers.length) {
|
|
2261
|
-
queryParams.append('ticker.any_of', tickers.join(','));
|
|
2262
|
-
}
|
|
2263
|
-
if (options?.type) {
|
|
2264
|
-
queryParams.append('type', options.type);
|
|
2265
|
-
}
|
|
2266
|
-
if (options?.order) {
|
|
2267
|
-
queryParams.append('order', options.order);
|
|
2268
|
-
}
|
|
2269
|
-
if (options?.limit) {
|
|
2270
|
-
queryParams.append('limit', options.limit.toString());
|
|
2271
|
-
}
|
|
2272
|
-
if (options?.sort) {
|
|
2273
|
-
queryParams.append('sort', options.sort);
|
|
2274
|
-
}
|
|
2275
|
-
url.search = queryParams.toString();
|
|
2276
|
-
return polygonIndicesLimit(async () => {
|
|
2277
|
-
try {
|
|
2278
|
-
const response = await fetchWithRetry(url.toString(), {}, 3, 300);
|
|
2279
|
-
const data = await response.json();
|
|
2280
|
-
if (data.status === 'ERROR') {
|
|
2281
|
-
throw new Error(`Polygon API Error: ${data.error}`);
|
|
2282
|
-
}
|
|
2283
|
-
return data;
|
|
2284
|
-
}
|
|
2285
|
-
catch (error) {
|
|
2286
|
-
console.error('Error fetching universal snapshot:', error);
|
|
2287
|
-
throw error;
|
|
2288
|
-
}
|
|
2289
|
-
});
|
|
2290
|
-
};
|
|
2291
|
-
/**
|
|
2292
|
-
* Converts Polygon Indices bar data to a more standardized format
|
|
2293
|
-
*
|
|
2294
|
-
* @param {PolygonIndicesAggregatesResponse} data - The raw aggregates response
|
|
2295
|
-
* @returns {Array<{date: string, open: number, high: number, low: number, close: number, timestamp: number}>} Formatted bar data
|
|
2296
|
-
*/
|
|
2297
|
-
const formatIndicesBarData = (data) => {
|
|
2298
|
-
return data.results.map((bar) => {
|
|
2299
|
-
const date = new Date(bar.t);
|
|
2300
|
-
return {
|
|
2301
|
-
date: date.toISOString().split('T')[0],
|
|
2302
|
-
open: bar.o,
|
|
2303
|
-
high: bar.h,
|
|
2304
|
-
low: bar.l,
|
|
2305
|
-
close: bar.c,
|
|
2306
|
-
timestamp: bar.t,
|
|
2307
|
-
};
|
|
2308
|
-
});
|
|
2309
|
-
};
|
|
2310
1383
|
|
|
2311
1384
|
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
|
2312
1385
|
if (typeof state === "function" ? receiver !== state || true : !state.has(receiver))
|
|
@@ -2539,7 +1612,7 @@ const safeJSON = (text) => {
|
|
|
2539
1612
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2540
1613
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2541
1614
|
|
|
2542
|
-
const VERSION = '6.
|
|
1615
|
+
const VERSION = '6.22.0'; // x-release-please-version
|
|
2543
1616
|
|
|
2544
1617
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
2545
1618
|
const isRunningInBrowser = () => {
|
|
@@ -3672,6 +2745,11 @@ async function defaultParseResponse(client, props) {
|
|
|
3672
2745
|
const mediaType = contentType?.split(';')[0]?.trim();
|
|
3673
2746
|
const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
|
|
3674
2747
|
if (isJSON) {
|
|
2748
|
+
const contentLength = response.headers.get('content-length');
|
|
2749
|
+
if (contentLength === '0') {
|
|
2750
|
+
// if there is no content we can't do anything
|
|
2751
|
+
return undefined;
|
|
2752
|
+
}
|
|
3675
2753
|
const json = await response.json();
|
|
3676
2754
|
return addRequestID(json, response);
|
|
3677
2755
|
}
|
|
@@ -7028,7 +6106,7 @@ class Completions extends APIResource {
|
|
|
7028
6106
|
}
|
|
7029
6107
|
|
|
7030
6108
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7031
|
-
class Content extends APIResource {
|
|
6109
|
+
let Content$2 = class Content extends APIResource {
|
|
7032
6110
|
/**
|
|
7033
6111
|
* Retrieve Container File Content
|
|
7034
6112
|
*/
|
|
@@ -7040,13 +6118,13 @@ class Content extends APIResource {
|
|
|
7040
6118
|
__binaryResponse: true,
|
|
7041
6119
|
});
|
|
7042
6120
|
}
|
|
7043
|
-
}
|
|
6121
|
+
};
|
|
7044
6122
|
|
|
7045
6123
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7046
6124
|
let Files$2 = class Files extends APIResource {
|
|
7047
6125
|
constructor() {
|
|
7048
6126
|
super(...arguments);
|
|
7049
|
-
this.content = new Content(this._client);
|
|
6127
|
+
this.content = new Content$2(this._client);
|
|
7050
6128
|
}
|
|
7051
6129
|
/**
|
|
7052
6130
|
* Create a Container File
|
|
@@ -7055,7 +6133,7 @@ let Files$2 = class Files extends APIResource {
|
|
|
7055
6133
|
* a JSON request with a file ID.
|
|
7056
6134
|
*/
|
|
7057
6135
|
create(containerID, body, options) {
|
|
7058
|
-
return this._client.post(path `/containers/${containerID}/files`,
|
|
6136
|
+
return this._client.post(path `/containers/${containerID}/files`, maybeMultipartFormRequestOptions({ body, ...options }, this._client));
|
|
7059
6137
|
}
|
|
7060
6138
|
/**
|
|
7061
6139
|
* Retrieve Container File
|
|
@@ -7084,7 +6162,7 @@ let Files$2 = class Files extends APIResource {
|
|
|
7084
6162
|
});
|
|
7085
6163
|
}
|
|
7086
6164
|
};
|
|
7087
|
-
Files$2.Content = Content;
|
|
6165
|
+
Files$2.Content = Content$2;
|
|
7088
6166
|
|
|
7089
6167
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7090
6168
|
class Containers extends APIResource {
|
|
@@ -8372,6 +7450,114 @@ class Responses extends APIResource {
|
|
|
8372
7450
|
Responses.InputItems = InputItems;
|
|
8373
7451
|
Responses.InputTokens = InputTokens;
|
|
8374
7452
|
|
|
7453
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7454
|
+
let Content$1 = class Content extends APIResource {
|
|
7455
|
+
/**
|
|
7456
|
+
* Get Skill Content
|
|
7457
|
+
*/
|
|
7458
|
+
retrieve(skillID, options) {
|
|
7459
|
+
return this._client.get(path `/skills/${skillID}/content`, {
|
|
7460
|
+
...options,
|
|
7461
|
+
headers: buildHeaders([{ Accept: 'application/binary' }, options?.headers]),
|
|
7462
|
+
__binaryResponse: true,
|
|
7463
|
+
});
|
|
7464
|
+
}
|
|
7465
|
+
};
|
|
7466
|
+
|
|
7467
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7468
|
+
class Content extends APIResource {
|
|
7469
|
+
/**
|
|
7470
|
+
* Get Skill Version Content
|
|
7471
|
+
*/
|
|
7472
|
+
retrieve(version, params, options) {
|
|
7473
|
+
const { skill_id } = params;
|
|
7474
|
+
return this._client.get(path `/skills/${skill_id}/versions/${version}/content`, {
|
|
7475
|
+
...options,
|
|
7476
|
+
headers: buildHeaders([{ Accept: 'application/binary' }, options?.headers]),
|
|
7477
|
+
__binaryResponse: true,
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7483
|
+
class Versions extends APIResource {
|
|
7484
|
+
constructor() {
|
|
7485
|
+
super(...arguments);
|
|
7486
|
+
this.content = new Content(this._client);
|
|
7487
|
+
}
|
|
7488
|
+
/**
|
|
7489
|
+
* Create Skill Version
|
|
7490
|
+
*/
|
|
7491
|
+
create(skillID, body = {}, options) {
|
|
7492
|
+
return this._client.post(path `/skills/${skillID}/versions`, maybeMultipartFormRequestOptions({ body, ...options }, this._client));
|
|
7493
|
+
}
|
|
7494
|
+
/**
|
|
7495
|
+
* Get Skill Version
|
|
7496
|
+
*/
|
|
7497
|
+
retrieve(version, params, options) {
|
|
7498
|
+
const { skill_id } = params;
|
|
7499
|
+
return this._client.get(path `/skills/${skill_id}/versions/${version}`, options);
|
|
7500
|
+
}
|
|
7501
|
+
/**
|
|
7502
|
+
* List Skill Versions
|
|
7503
|
+
*/
|
|
7504
|
+
list(skillID, query = {}, options) {
|
|
7505
|
+
return this._client.getAPIList(path `/skills/${skillID}/versions`, (CursorPage), {
|
|
7506
|
+
query,
|
|
7507
|
+
...options,
|
|
7508
|
+
});
|
|
7509
|
+
}
|
|
7510
|
+
/**
|
|
7511
|
+
* Delete Skill Version
|
|
7512
|
+
*/
|
|
7513
|
+
delete(version, params, options) {
|
|
7514
|
+
const { skill_id } = params;
|
|
7515
|
+
return this._client.delete(path `/skills/${skill_id}/versions/${version}`, options);
|
|
7516
|
+
}
|
|
7517
|
+
}
|
|
7518
|
+
Versions.Content = Content;
|
|
7519
|
+
|
|
7520
|
+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
7521
|
+
class Skills extends APIResource {
|
|
7522
|
+
constructor() {
|
|
7523
|
+
super(...arguments);
|
|
7524
|
+
this.content = new Content$1(this._client);
|
|
7525
|
+
this.versions = new Versions(this._client);
|
|
7526
|
+
}
|
|
7527
|
+
/**
|
|
7528
|
+
* Create Skill
|
|
7529
|
+
*/
|
|
7530
|
+
create(body = {}, options) {
|
|
7531
|
+
return this._client.post('/skills', maybeMultipartFormRequestOptions({ body, ...options }, this._client));
|
|
7532
|
+
}
|
|
7533
|
+
/**
|
|
7534
|
+
* Get Skill
|
|
7535
|
+
*/
|
|
7536
|
+
retrieve(skillID, options) {
|
|
7537
|
+
return this._client.get(path `/skills/${skillID}`, options);
|
|
7538
|
+
}
|
|
7539
|
+
/**
|
|
7540
|
+
* Update Skill Default Version
|
|
7541
|
+
*/
|
|
7542
|
+
update(skillID, body, options) {
|
|
7543
|
+
return this._client.post(path `/skills/${skillID}`, { body, ...options });
|
|
7544
|
+
}
|
|
7545
|
+
/**
|
|
7546
|
+
* List Skills
|
|
7547
|
+
*/
|
|
7548
|
+
list(query = {}, options) {
|
|
7549
|
+
return this._client.getAPIList('/skills', (CursorPage), { query, ...options });
|
|
7550
|
+
}
|
|
7551
|
+
/**
|
|
7552
|
+
* Delete Skill
|
|
7553
|
+
*/
|
|
7554
|
+
delete(skillID, options) {
|
|
7555
|
+
return this._client.delete(path `/skills/${skillID}`, options);
|
|
7556
|
+
}
|
|
7557
|
+
}
|
|
7558
|
+
Skills.Content = Content$1;
|
|
7559
|
+
Skills.Versions = Versions;
|
|
7560
|
+
|
|
8375
7561
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
|
8376
7562
|
class Parts extends APIResource {
|
|
8377
7563
|
/**
|
|
@@ -8981,6 +8167,7 @@ class OpenAI {
|
|
|
8981
8167
|
this.conversations = new Conversations(this);
|
|
8982
8168
|
this.evals = new Evals(this);
|
|
8983
8169
|
this.containers = new Containers(this);
|
|
8170
|
+
this.skills = new Skills(this);
|
|
8984
8171
|
this.videos = new Videos(this);
|
|
8985
8172
|
if (apiKey === undefined) {
|
|
8986
8173
|
throw new OpenAIError('Missing credentials. Please pass an `apiKey`, or set the `OPENAI_API_KEY` environment variable.');
|
|
@@ -9238,7 +8425,9 @@ class OpenAI {
|
|
|
9238
8425
|
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
|
|
9239
8426
|
}
|
|
9240
8427
|
getAPIList(path, Page, opts) {
|
|
9241
|
-
return this.requestAPIList(Page,
|
|
8428
|
+
return this.requestAPIList(Page, opts && 'then' in opts ?
|
|
8429
|
+
opts.then((opts) => ({ method: 'get', path, ...opts }))
|
|
8430
|
+
: { method: 'get', path, ...opts });
|
|
9242
8431
|
}
|
|
9243
8432
|
requestAPIList(Page, options) {
|
|
9244
8433
|
const request = this.makeRequest(options, null, undefined);
|
|
@@ -9246,9 +8435,10 @@ class OpenAI {
|
|
|
9246
8435
|
}
|
|
9247
8436
|
async fetchWithTimeout(url, init, ms, controller) {
|
|
9248
8437
|
const { signal, method, ...options } = init || {};
|
|
8438
|
+
const abort = this._makeAbort(controller);
|
|
9249
8439
|
if (signal)
|
|
9250
|
-
signal.addEventListener('abort',
|
|
9251
|
-
const timeout = setTimeout(
|
|
8440
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
8441
|
+
const timeout = setTimeout(abort, ms);
|
|
9252
8442
|
const isReadableBody = (globalThis.ReadableStream && options.body instanceof globalThis.ReadableStream) ||
|
|
9253
8443
|
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);
|
|
9254
8444
|
const fetchOptions = {
|
|
@@ -9379,6 +8569,11 @@ class OpenAI {
|
|
|
9379
8569
|
this.validateHeaders(headers);
|
|
9380
8570
|
return headers.values;
|
|
9381
8571
|
}
|
|
8572
|
+
_makeAbort(controller) {
|
|
8573
|
+
// note: we can't just inline this method inside `fetchWithTimeout()` because then the closure
|
|
8574
|
+
// would capture all request options, and cause a memory leak.
|
|
8575
|
+
return () => controller.abort();
|
|
8576
|
+
}
|
|
9382
8577
|
buildBody({ options: { body, headers: rawHeaders } }) {
|
|
9383
8578
|
if (!body) {
|
|
9384
8579
|
return { bodyHeaders: undefined, body: undefined };
|
|
@@ -9452,6 +8647,7 @@ OpenAI.Realtime = Realtime;
|
|
|
9452
8647
|
OpenAI.Conversations = Conversations;
|
|
9453
8648
|
OpenAI.Evals = Evals;
|
|
9454
8649
|
OpenAI.Containers = Containers;
|
|
8650
|
+
OpenAI.Skills = Skills;
|
|
9455
8651
|
OpenAI.Videos = Videos;
|
|
9456
8652
|
|
|
9457
8653
|
// llm-openai-config.ts
|
|
@@ -10936,428 +10132,6 @@ class PerformanceTimer {
|
|
|
10936
10132
|
}
|
|
10937
10133
|
}
|
|
10938
10134
|
|
|
10939
|
-
/**
|
|
10940
|
-
* Calculates Bollinger Bands for a given set of price data.
|
|
10941
|
-
* Bollinger Bands consist of a middle band (SMA) and two outer bands
|
|
10942
|
-
* that are standard deviations away from the middle band.
|
|
10943
|
-
*
|
|
10944
|
-
* @param priceData - An array of price data objects containing closing prices.
|
|
10945
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
10946
|
-
* @param params.period - The number of periods to use for the SMA (default is 20).
|
|
10947
|
-
* @param params.standardDeviations - The number of standard deviations for the outer bands (default is 2).
|
|
10948
|
-
* @returns An array of BollingerBandsData objects containing the calculated bands.
|
|
10949
|
-
*/
|
|
10950
|
-
function calculateBollingerBands(priceData, { period = 20, standardDeviations = 2 } = {}) {
|
|
10951
|
-
if (priceData.length < period) {
|
|
10952
|
-
logIfDebug(`Insufficient data for Bollinger Bands calculation: required periods: ${period}, but only received ${priceData.length} periods of data`);
|
|
10953
|
-
return [];
|
|
10954
|
-
}
|
|
10955
|
-
const result = [];
|
|
10956
|
-
for (let i = period - 1; i < priceData.length; i++) {
|
|
10957
|
-
const periodSlice = priceData.slice(i - period + 1, i + 1);
|
|
10958
|
-
const prices = periodSlice.map((d) => d.close);
|
|
10959
|
-
// Calculate middle band (SMA)
|
|
10960
|
-
const sum = prices.reduce((acc, price) => acc + price, 0);
|
|
10961
|
-
const sma = sum / period;
|
|
10962
|
-
// Calculate standard deviation
|
|
10963
|
-
const squaredDifferences = prices.map((price) => Math.pow(price - sma, 2));
|
|
10964
|
-
const variance = squaredDifferences.reduce((acc, val) => acc + val, 0) / period;
|
|
10965
|
-
const standardDeviation = Math.sqrt(variance);
|
|
10966
|
-
// Calculate bands
|
|
10967
|
-
const upperBand = sma + standardDeviation * standardDeviations;
|
|
10968
|
-
const lowerBand = sma - standardDeviation * standardDeviations;
|
|
10969
|
-
result.push({
|
|
10970
|
-
date: priceData[i].date,
|
|
10971
|
-
middle: parseFloat(sma.toFixed(2)),
|
|
10972
|
-
upper: parseFloat(upperBand.toFixed(2)),
|
|
10973
|
-
lower: parseFloat(lowerBand.toFixed(2)),
|
|
10974
|
-
close: priceData[i].close,
|
|
10975
|
-
});
|
|
10976
|
-
}
|
|
10977
|
-
// logIfDebug(`Calculated Bollinger Bands for ${result.length} periods`);
|
|
10978
|
-
return result;
|
|
10979
|
-
}
|
|
10980
|
-
/**
|
|
10981
|
-
* Calculates the Exponential Moving Average (EMA) for a given set of price data.
|
|
10982
|
-
* The EMA gives more weight to recent prices, making it more responsive to new information.
|
|
10983
|
-
*
|
|
10984
|
-
* @param priceData - An array of price data objects containing closing prices.
|
|
10985
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
10986
|
-
* @param params.period - The number of periods to use for the EMA (default is 20).
|
|
10987
|
-
* @param params.period2 - An optional second period for a second EMA (default is 9).
|
|
10988
|
-
* @returns An array of EMAData objects containing the calculated EMA values.
|
|
10989
|
-
*/
|
|
10990
|
-
function calculateEMA(priceData, { period = 20, period2 = 9 } = {}) {
|
|
10991
|
-
if (priceData.length < period || (period2 && priceData.length < period2)) {
|
|
10992
|
-
logIfDebug(`Insufficient data for EMA calculation: required periods: ${period}, ${period2}, but only received ${priceData.length} periods of data`);
|
|
10993
|
-
return [];
|
|
10994
|
-
}
|
|
10995
|
-
const result = [];
|
|
10996
|
-
const multiplier = 2 / (period + 1);
|
|
10997
|
-
const multiplier2 = period2 ? 2 / (period2 + 1) : 0;
|
|
10998
|
-
// Calculate initial SMA for first period
|
|
10999
|
-
let sum = 0;
|
|
11000
|
-
for (let i = 0; i < period; i++) {
|
|
11001
|
-
sum += priceData[i].close;
|
|
11002
|
-
}
|
|
11003
|
-
let prevEMA = sum / period;
|
|
11004
|
-
// Calculate initial SMA for second period if needed
|
|
11005
|
-
let prevEMA2;
|
|
11006
|
-
if (period2) {
|
|
11007
|
-
sum = 0;
|
|
11008
|
-
for (let i = 0; i < period2; i++) {
|
|
11009
|
-
sum += priceData[i].close;
|
|
11010
|
-
}
|
|
11011
|
-
prevEMA2 = sum / period2;
|
|
11012
|
-
}
|
|
11013
|
-
// Add first EMA(s)
|
|
11014
|
-
const firstEntry = {
|
|
11015
|
-
date: priceData[Math.max(period, period2 || 0) - 1].date,
|
|
11016
|
-
ema: parseFloat(prevEMA.toFixed(2)),
|
|
11017
|
-
close: priceData[Math.max(period, period2 || 0) - 1].close,
|
|
11018
|
-
};
|
|
11019
|
-
if (period2) {
|
|
11020
|
-
firstEntry.ema2 = parseFloat(prevEMA2.toFixed(2));
|
|
11021
|
-
}
|
|
11022
|
-
result.push(firstEntry);
|
|
11023
|
-
// Calculate EMA for remaining periods
|
|
11024
|
-
for (let i = Math.max(period, period2 || 0); i < priceData.length; i++) {
|
|
11025
|
-
const currentClose = priceData[i].close;
|
|
11026
|
-
const currentEMA = (currentClose - prevEMA) * multiplier + prevEMA;
|
|
11027
|
-
prevEMA = currentEMA;
|
|
11028
|
-
const entry = {
|
|
11029
|
-
date: priceData[i].date,
|
|
11030
|
-
ema: parseFloat(currentEMA.toFixed(2)),
|
|
11031
|
-
close: currentClose,
|
|
11032
|
-
};
|
|
11033
|
-
if (period2) {
|
|
11034
|
-
const currentEMA2 = (currentClose - prevEMA2) * multiplier2 + prevEMA2;
|
|
11035
|
-
prevEMA2 = currentEMA2;
|
|
11036
|
-
entry.ema2 = parseFloat(currentEMA2.toFixed(2));
|
|
11037
|
-
}
|
|
11038
|
-
result.push(entry);
|
|
11039
|
-
}
|
|
11040
|
-
// logIfDebug(`Calculated EMA for ${result.length} periods`);
|
|
11041
|
-
return result;
|
|
11042
|
-
}
|
|
11043
|
-
/**
|
|
11044
|
-
* Calculates Fibonacci retracement and extension levels based on price data.
|
|
11045
|
-
* Fibonacci levels are used to identify potential support and resistance levels.
|
|
11046
|
-
*
|
|
11047
|
-
* @param priceData - An array of price data objects containing high and low prices.
|
|
11048
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
11049
|
-
* @param params.lookbackPeriod - The number of periods to look back for swing high/low (default is 20).
|
|
11050
|
-
* @param params.retracementLevels - An array of retracement levels to calculate (default is [0.236, 0.382, 0.5, 0.618, 0.786]).
|
|
11051
|
-
* @param params.extensionLevels - An array of extension levels to calculate (default is [1.272, 1.618, 2.618]).
|
|
11052
|
-
* @param params.reverseDirection - A boolean indicating if the trend is reversed (default is false).
|
|
11053
|
-
* @returns An array of FibonacciData objects containing the calculated levels.
|
|
11054
|
-
*/
|
|
11055
|
-
function calculateFibonacciLevels(priceData, { lookbackPeriod = 20, retracementLevels = [0.236, 0.382, 0.5, 0.618, 0.786], extensionLevels = [1.272, 1.618, 2.618], reverseDirection = false, } = {}) {
|
|
11056
|
-
const result = [];
|
|
11057
|
-
for (let i = 0; i < priceData.length; i++) {
|
|
11058
|
-
const periodSlice = priceData.slice(Math.max(0, i - lookbackPeriod + 1), i + 1);
|
|
11059
|
-
const swingHigh = Math.max(...periodSlice.map((d) => d.high));
|
|
11060
|
-
const swingLow = Math.min(...periodSlice.map((d) => d.low));
|
|
11061
|
-
const priceRange = swingHigh - swingLow;
|
|
11062
|
-
const trend = reverseDirection ? 'downtrend' : 'uptrend';
|
|
11063
|
-
let levels = [];
|
|
11064
|
-
if (priceRange > 0) {
|
|
11065
|
-
// Calculate retracement levels
|
|
11066
|
-
retracementLevels.forEach((level) => {
|
|
11067
|
-
const price = reverseDirection ? swingLow + priceRange * level : swingHigh - priceRange * level;
|
|
11068
|
-
levels.push({
|
|
11069
|
-
level,
|
|
11070
|
-
price: parseFloat(price.toFixed(2)),
|
|
11071
|
-
type: 'retracement',
|
|
11072
|
-
});
|
|
11073
|
-
});
|
|
11074
|
-
// Calculate extension levels
|
|
11075
|
-
extensionLevels.forEach((level) => {
|
|
11076
|
-
const price = reverseDirection
|
|
11077
|
-
? swingHigh - priceRange * (level - 1) // For downtrend
|
|
11078
|
-
: swingHigh + priceRange * (level - 1); // For uptrend
|
|
11079
|
-
levels.push({
|
|
11080
|
-
level,
|
|
11081
|
-
price: parseFloat(price.toFixed(2)),
|
|
11082
|
-
type: 'extension',
|
|
11083
|
-
});
|
|
11084
|
-
});
|
|
11085
|
-
// Sort levels by price
|
|
11086
|
-
levels.sort((a, b) => (reverseDirection ? b.price - a.price : a.price - b.price));
|
|
11087
|
-
}
|
|
11088
|
-
else {
|
|
11089
|
-
logIfDebug(`Price range is zero on date ${priceData[i].date}; no levels calculated.`);
|
|
11090
|
-
}
|
|
11091
|
-
result.push({
|
|
11092
|
-
date: priceData[i].date,
|
|
11093
|
-
levels,
|
|
11094
|
-
swingHigh,
|
|
11095
|
-
swingLow,
|
|
11096
|
-
trend,
|
|
11097
|
-
close: priceData[i].close,
|
|
11098
|
-
});
|
|
11099
|
-
}
|
|
11100
|
-
// logIfDebug(`Calculated Fibonacci levels for ${result.length} periods`);
|
|
11101
|
-
return result;
|
|
11102
|
-
}
|
|
11103
|
-
/**
|
|
11104
|
-
* Calculates the Moving Average Convergence Divergence (MACD) for a given set of price data.
|
|
11105
|
-
* MACD is a trend-following momentum indicator that shows the relationship between two EMAs.
|
|
11106
|
-
*
|
|
11107
|
-
* @param priceData - An array of price data objects containing closing prices.
|
|
11108
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
11109
|
-
* @param params.shortPeriod - The short EMA period (default is 12).
|
|
11110
|
-
* @param params.longPeriod - The long EMA period (default is 26).
|
|
11111
|
-
* @param params.signalPeriod - The signal line period (default is 9).
|
|
11112
|
-
* @returns An array of MACDData objects containing the calculated MACD values.
|
|
11113
|
-
*/
|
|
11114
|
-
function calculateMACD(priceData, { shortPeriod = 12, longPeriod = 26, signalPeriod = 9 } = {}) {
|
|
11115
|
-
if (priceData.length < longPeriod + signalPeriod) {
|
|
11116
|
-
logIfDebug(`Insufficient data for MACD calculation: required periods: ${longPeriod + signalPeriod}, but only received ${priceData.length} periods of data`);
|
|
11117
|
-
return [];
|
|
11118
|
-
}
|
|
11119
|
-
const emaShort = calculateEMA(priceData, { period: shortPeriod });
|
|
11120
|
-
const emaLong = calculateEMA(priceData, { period: longPeriod });
|
|
11121
|
-
// Align EMAs by trimming the beginning of emaShort to match emaLong length
|
|
11122
|
-
if (emaShort.length < emaLong.length) {
|
|
11123
|
-
logIfDebug('Short EMA length is less than Long EMA length for MACD calculation');
|
|
11124
|
-
return [];
|
|
11125
|
-
}
|
|
11126
|
-
const emaShortAligned = emaShort.slice(emaShort.length - emaLong.length);
|
|
11127
|
-
const macdLine = emaShortAligned.map((short, i) => short.ema - emaLong[i].ema);
|
|
11128
|
-
const result = [];
|
|
11129
|
-
if (macdLine.length < signalPeriod) {
|
|
11130
|
-
logIfDebug(`Insufficient MACD data for Signal Line calculation: required periods: ${signalPeriod}, but only received ${macdLine.length} periods of data`);
|
|
11131
|
-
return [];
|
|
11132
|
-
}
|
|
11133
|
-
const signalMultiplier = 2 / (signalPeriod + 1);
|
|
11134
|
-
let signalEMA = macdLine.slice(0, signalPeriod).reduce((sum, val) => sum + val, 0) / signalPeriod;
|
|
11135
|
-
for (let i = signalPeriod; i < macdLine.length; i++) {
|
|
11136
|
-
const macdValue = macdLine[i];
|
|
11137
|
-
signalEMA = (macdValue - signalEMA) * signalMultiplier + signalEMA;
|
|
11138
|
-
const hist = macdValue - signalEMA;
|
|
11139
|
-
result.push({
|
|
11140
|
-
date: emaLong[i].date, // Use emaLong's date for alignment
|
|
11141
|
-
macd: parseFloat(macdValue.toFixed(2)),
|
|
11142
|
-
signal: parseFloat(signalEMA.toFixed(2)),
|
|
11143
|
-
histogram: parseFloat(hist.toFixed(2)),
|
|
11144
|
-
close: emaLong[i].close,
|
|
11145
|
-
});
|
|
11146
|
-
}
|
|
11147
|
-
// logIfDebug(`Calculated MACD for ${result.length} periods`);
|
|
11148
|
-
return result;
|
|
11149
|
-
}
|
|
11150
|
-
/**
|
|
11151
|
-
* Calculates the Relative Strength Index (RSI) for a given set of price data.
|
|
11152
|
-
* RSI is a momentum oscillator that measures the speed and change of price movements.
|
|
11153
|
-
*
|
|
11154
|
-
* @param priceData - An array of price data objects containing closing prices.
|
|
11155
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
11156
|
-
* @param params.period - The number of periods to use for the RSI (default is 14).
|
|
11157
|
-
* @returns An array of RSIData objects containing the calculated RSI values.
|
|
11158
|
-
*/
|
|
11159
|
-
function calculateRSI(priceData, { period = 14 } = {}) {
|
|
11160
|
-
if (priceData.length < period + 1) {
|
|
11161
|
-
logIfDebug(`Insufficient data for RSI calculation: required periods: ${period + 1}, but only received ${priceData.length} periods of data`);
|
|
11162
|
-
return [];
|
|
11163
|
-
}
|
|
11164
|
-
const result = [];
|
|
11165
|
-
let avgGain = 0;
|
|
11166
|
-
let avgLoss = 0;
|
|
11167
|
-
// Calculate first average gain and loss
|
|
11168
|
-
for (let i = 1; i <= period; i++) {
|
|
11169
|
-
const change = priceData[i].close - priceData[i - 1].close;
|
|
11170
|
-
if (change >= 0) {
|
|
11171
|
-
avgGain += change;
|
|
11172
|
-
}
|
|
11173
|
-
else {
|
|
11174
|
-
avgLoss += Math.abs(change);
|
|
11175
|
-
}
|
|
11176
|
-
}
|
|
11177
|
-
avgGain = avgGain / period;
|
|
11178
|
-
avgLoss = avgLoss / period;
|
|
11179
|
-
// Calculate RSI for the first period
|
|
11180
|
-
let rs = avgGain / avgLoss;
|
|
11181
|
-
let rsi = 100 - 100 / (1 + rs);
|
|
11182
|
-
result.push({
|
|
11183
|
-
date: priceData[period].date,
|
|
11184
|
-
rsi: parseFloat(rsi.toFixed(2)),
|
|
11185
|
-
close: priceData[period].close,
|
|
11186
|
-
});
|
|
11187
|
-
// Calculate subsequent periods using smoothed averages
|
|
11188
|
-
for (let i = period + 1; i < priceData.length; i++) {
|
|
11189
|
-
const change = priceData[i].close - priceData[i - 1].close;
|
|
11190
|
-
const gain = change >= 0 ? change : 0;
|
|
11191
|
-
const loss = change < 0 ? Math.abs(change) : 0;
|
|
11192
|
-
// Use smoothed averages
|
|
11193
|
-
avgGain = (avgGain * (period - 1) + gain) / period;
|
|
11194
|
-
avgLoss = (avgLoss * (period - 1) + loss) / period;
|
|
11195
|
-
rs = avgGain / avgLoss;
|
|
11196
|
-
rsi = 100 - 100 / (1 + rs);
|
|
11197
|
-
result.push({
|
|
11198
|
-
date: priceData[i].date,
|
|
11199
|
-
rsi: parseFloat(rsi.toFixed(2)),
|
|
11200
|
-
close: priceData[i].close,
|
|
11201
|
-
});
|
|
11202
|
-
}
|
|
11203
|
-
// logIfDebug(`Calculated RSI for ${result.length} periods`);
|
|
11204
|
-
return result;
|
|
11205
|
-
}
|
|
11206
|
-
/**
|
|
11207
|
-
* Calculates the Stochastic Oscillator for a given set of price data.
|
|
11208
|
-
* The Stochastic Oscillator compares a particular closing price of a security to a range of its prices over a certain period of time.
|
|
11209
|
-
*
|
|
11210
|
-
* @param priceData - An array of price data objects containing high, low, and closing prices.
|
|
11211
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
11212
|
-
* @param params.lookbackPeriod - The number of periods to look back for the calculation of %K (default is 5).
|
|
11213
|
-
* @param params.signalPeriod - The number of periods for the %D signal line (default is 3).
|
|
11214
|
-
* @param params.smoothingFactor - The smoothing factor for %K (default is 3).
|
|
11215
|
-
* @returns An array of StochData objects containing the calculated %K and %D values.
|
|
11216
|
-
*/
|
|
11217
|
-
function calculateStochasticOscillator(priceData, { lookbackPeriod = 5, signalPeriod = 3, smoothingFactor = 3 } = {}) {
|
|
11218
|
-
if (priceData.length < lookbackPeriod) {
|
|
11219
|
-
logIfDebug(`Insufficient data for Stochastic Oscillator calculation: required periods: ${lookbackPeriod}, but only received ${priceData.length} periods of data`);
|
|
11220
|
-
return [];
|
|
11221
|
-
}
|
|
11222
|
-
const kValues = [];
|
|
11223
|
-
const result = [];
|
|
11224
|
-
let kSum = 0;
|
|
11225
|
-
let dSum = 0;
|
|
11226
|
-
for (let i = lookbackPeriod - 1; i < priceData.length; i++) {
|
|
11227
|
-
const periodSlice = priceData.slice(i - lookbackPeriod + 1, i + 1);
|
|
11228
|
-
const currentClose = periodSlice[periodSlice.length - 1].close;
|
|
11229
|
-
const highPrices = periodSlice.map((d) => d.high);
|
|
11230
|
-
const lowPrices = periodSlice.map((d) => d.low);
|
|
11231
|
-
const highestHigh = Math.max(...highPrices);
|
|
11232
|
-
const lowestLow = Math.min(...lowPrices);
|
|
11233
|
-
const k = highestHigh === lowestLow ? 0 : ((currentClose - lowestLow) / (highestHigh - lowestLow)) * 100;
|
|
11234
|
-
kValues.push(k);
|
|
11235
|
-
kSum += k;
|
|
11236
|
-
if (kValues.length > smoothingFactor)
|
|
11237
|
-
kSum -= kValues[kValues.length - smoothingFactor - 1];
|
|
11238
|
-
const smoothedK = kSum / Math.min(kValues.length, smoothingFactor);
|
|
11239
|
-
dSum += smoothedK;
|
|
11240
|
-
if (kValues.length > smoothingFactor + signalPeriod - 1)
|
|
11241
|
-
dSum -= kValues[kValues.length - smoothingFactor - signalPeriod];
|
|
11242
|
-
const smoothedD = dSum / Math.min(kValues.length - smoothingFactor + 1, signalPeriod);
|
|
11243
|
-
if (kValues.length >= smoothingFactor + signalPeriod - 1) {
|
|
11244
|
-
result.push({
|
|
11245
|
-
date: priceData[i].date,
|
|
11246
|
-
slowK: parseFloat(smoothedK.toFixed(2)),
|
|
11247
|
-
slowD: parseFloat(smoothedD.toFixed(2)),
|
|
11248
|
-
close: currentClose,
|
|
11249
|
-
});
|
|
11250
|
-
}
|
|
11251
|
-
}
|
|
11252
|
-
// logIfDebug(`Calculated Stochastic Oscillator for ${result.length} periods`);
|
|
11253
|
-
return result;
|
|
11254
|
-
}
|
|
11255
|
-
/**
|
|
11256
|
-
* Calculates support and resistance levels based on price data.
|
|
11257
|
-
* Support and resistance levels are price levels at which a stock tends to stop and reverse.
|
|
11258
|
-
*
|
|
11259
|
-
* @param priceData - An array of price data objects containing high, low, and closing prices.
|
|
11260
|
-
* @param params - An object containing optional parameters for the calculation.
|
|
11261
|
-
* @param params.maxLevels - The maximum number of support/resistance levels to return (default is 5).
|
|
11262
|
-
* @param params.lookbackPeriod - The number of periods to look back for pivot points (default is 10).
|
|
11263
|
-
* @returns An array of SupportResistanceData objects containing the calculated levels.
|
|
11264
|
-
*/
|
|
11265
|
-
function calculateSupportAndResistance(priceData, { maxLevels = 5, lookbackPeriod = 10 } = {}) {
|
|
11266
|
-
const result = [];
|
|
11267
|
-
for (let i = 0; i < priceData.length; i++) {
|
|
11268
|
-
const startIdx = Math.max(0, i - lookbackPeriod);
|
|
11269
|
-
const analysisWindow = priceData.slice(startIdx, i + 1);
|
|
11270
|
-
const pivotPoints = [];
|
|
11271
|
-
// **Compute Volatility Metrics**
|
|
11272
|
-
const priceChanges = analysisWindow.slice(1).map((bar, idx) => Math.abs(bar.close - analysisWindow[idx].close));
|
|
11273
|
-
const avgPriceChange = priceChanges.reduce((sum, change) => sum + change, 0) / priceChanges.length;
|
|
11274
|
-
const volatility = avgPriceChange / analysisWindow[0].close; // Relative volatility
|
|
11275
|
-
// **Adjust Sensitivity and minGapBetweenLevels Dynamically**
|
|
11276
|
-
const sensitivity = volatility * 2; // Adjust the multiplier as needed
|
|
11277
|
-
const minGapBetweenLevels = volatility * 100; // Convert to percentage
|
|
11278
|
-
// Analyze each point in window for pivot status
|
|
11279
|
-
for (let j = 1; j < analysisWindow.length - 1; j++) {
|
|
11280
|
-
const curr = analysisWindow[j];
|
|
11281
|
-
const prevBar = analysisWindow[j - 1];
|
|
11282
|
-
const nextBar = analysisWindow[j + 1];
|
|
11283
|
-
// Check for high pivot
|
|
11284
|
-
if (curr.high > prevBar.high && curr.high > nextBar.high) {
|
|
11285
|
-
const existingPivot = pivotPoints.find((p) => Math.abs(p.price - curr.high) / curr.high < sensitivity);
|
|
11286
|
-
if (existingPivot) {
|
|
11287
|
-
existingPivot.count++;
|
|
11288
|
-
existingPivot.volume += curr.vol; // **Include Volume**
|
|
11289
|
-
}
|
|
11290
|
-
else {
|
|
11291
|
-
pivotPoints.push({ price: curr.high, count: 1, volume: curr.vol });
|
|
11292
|
-
}
|
|
11293
|
-
}
|
|
11294
|
-
// Check for low pivot
|
|
11295
|
-
if (curr.low < prevBar.low && curr.low < nextBar.low) {
|
|
11296
|
-
const existingPivot = pivotPoints.find((p) => Math.abs(p.price - curr.low) / curr.low < sensitivity);
|
|
11297
|
-
if (existingPivot) {
|
|
11298
|
-
existingPivot.count++;
|
|
11299
|
-
existingPivot.volume += curr.vol; // **Include Volume**
|
|
11300
|
-
}
|
|
11301
|
-
else {
|
|
11302
|
-
pivotPoints.push({ price: curr.low, count: 1, volume: curr.vol });
|
|
11303
|
-
}
|
|
11304
|
-
}
|
|
11305
|
-
}
|
|
11306
|
-
// Group nearby levels
|
|
11307
|
-
const currentPrice = priceData[i].close;
|
|
11308
|
-
const levels = [];
|
|
11309
|
-
// Sort pivots by price
|
|
11310
|
-
pivotPoints.sort((a, b) => a.price - b.price);
|
|
11311
|
-
// Group close pivots
|
|
11312
|
-
let currentGroup = [];
|
|
11313
|
-
for (let j = 0; j < pivotPoints.length; j++) {
|
|
11314
|
-
if (currentGroup.length === 0) {
|
|
11315
|
-
currentGroup.push(pivotPoints[j]);
|
|
11316
|
-
}
|
|
11317
|
-
else {
|
|
11318
|
-
const lastPrice = currentGroup[currentGroup.length - 1].price;
|
|
11319
|
-
if ((Math.abs(pivotPoints[j].price - lastPrice) / lastPrice) * 100 <= minGapBetweenLevels) {
|
|
11320
|
-
currentGroup.push(pivotPoints[j]);
|
|
11321
|
-
}
|
|
11322
|
-
else {
|
|
11323
|
-
// Process current group
|
|
11324
|
-
if (currentGroup.length > 0) {
|
|
11325
|
-
const totalVolume = currentGroup.reduce((sum, p) => sum + p.volume, 0);
|
|
11326
|
-
const avgPrice = currentGroup.reduce((sum, p) => sum + p.price * p.volume, 0) / totalVolume;
|
|
11327
|
-
const totalStrength = currentGroup.reduce((sum, p) => sum + p.count * (p.volume / totalVolume), 0);
|
|
11328
|
-
levels.push({
|
|
11329
|
-
price: parseFloat(avgPrice.toFixed(2)),
|
|
11330
|
-
strength: parseFloat(totalStrength.toFixed(2)),
|
|
11331
|
-
type: avgPrice > currentPrice ? 'resistance' : 'support',
|
|
11332
|
-
});
|
|
11333
|
-
}
|
|
11334
|
-
currentGroup = [pivotPoints[j]];
|
|
11335
|
-
}
|
|
11336
|
-
}
|
|
11337
|
-
}
|
|
11338
|
-
// Process final group
|
|
11339
|
-
if (currentGroup.length > 0) {
|
|
11340
|
-
const totalVolume = currentGroup.reduce((sum, p) => sum + p.volume, 0);
|
|
11341
|
-
const avgPrice = currentGroup.reduce((sum, p) => sum + p.price * p.volume, 0) / totalVolume;
|
|
11342
|
-
const totalStrength = currentGroup.reduce((sum, p) => sum + p.count * (p.volume / totalVolume), 0);
|
|
11343
|
-
levels.push({
|
|
11344
|
-
price: parseFloat(avgPrice.toFixed(2)),
|
|
11345
|
-
strength: parseFloat(totalStrength.toFixed(2)),
|
|
11346
|
-
type: avgPrice > currentPrice ? 'resistance' : 'support',
|
|
11347
|
-
});
|
|
11348
|
-
}
|
|
11349
|
-
// Sort by strength and limit
|
|
11350
|
-
const finalLevels = levels.sort((a, b) => b.strength - a.strength).slice(0, maxLevels);
|
|
11351
|
-
result.push({
|
|
11352
|
-
date: priceData[i].date,
|
|
11353
|
-
levels: finalLevels,
|
|
11354
|
-
close: currentPrice,
|
|
11355
|
-
});
|
|
11356
|
-
}
|
|
11357
|
-
logIfDebug(`Found ${result.reduce((sum, r) => sum + r.levels.length, 0)} support/resistance levels across ${result.length} periods`);
|
|
11358
|
-
return result;
|
|
11359
|
-
}
|
|
11360
|
-
|
|
11361
10135
|
function getDefaultExportFromCjs (x) {
|
|
11362
10136
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
11363
10137
|
}
|
|
@@ -16342,7 +15116,7 @@ var config = {};
|
|
|
16342
15116
|
|
|
16343
15117
|
var main = {exports: {}};
|
|
16344
15118
|
|
|
16345
|
-
var version = "17.
|
|
15119
|
+
var version = "17.3.1";
|
|
16346
15120
|
var require$$4 = {
|
|
16347
15121
|
version: version};
|
|
16348
15122
|
|
|
@@ -16364,12 +15138,9 @@ function requireMain () {
|
|
|
16364
15138
|
'🔐 encrypt with Dotenvx: https://dotenvx.com',
|
|
16365
15139
|
'🔐 prevent committing .env to code: https://dotenvx.com/precommit',
|
|
16366
15140
|
'🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
|
|
16367
|
-
'
|
|
16368
|
-
'
|
|
16369
|
-
'
|
|
16370
|
-
'✅ audit secrets and track compliance: https://dotenvx.com/ops',
|
|
16371
|
-
'🔄 add secrets lifecycle management: https://dotenvx.com/ops',
|
|
16372
|
-
'🔑 add access controls to secrets: https://dotenvx.com/ops',
|
|
15141
|
+
'🤖 agentic secret storage: https://dotenvx.com/as2',
|
|
15142
|
+
'⚡️ secrets for agents: https://dotenvx.com/as2',
|
|
15143
|
+
'🛡️ auth for agents: https://vestauth.com',
|
|
16373
15144
|
'🛠️ run anywhere with `dotenvx run -- yourcommand`',
|
|
16374
15145
|
'⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
|
|
16375
15146
|
'⚙️ enable debug logging with { debug: true }',
|
|
@@ -19766,40 +18537,12 @@ const disco = {
|
|
|
19766
18537
|
pct: formatPercentage,
|
|
19767
18538
|
dateTimeForGS: dateTimeForGS,
|
|
19768
18539
|
},
|
|
19769
|
-
indices: {
|
|
19770
|
-
fetchAggregates: fetchIndicesAggregates,
|
|
19771
|
-
fetchPreviousClose: fetchIndicesPreviousClose,
|
|
19772
|
-
fetchDailyOpenClose: fetchIndicesDailyOpenClose,
|
|
19773
|
-
fetchSnapshot: fetchIndicesSnapshot,
|
|
19774
|
-
fetchUniversalSnapshot: fetchUniversalSnapshot,
|
|
19775
|
-
formatBarData: formatIndicesBarData,
|
|
19776
|
-
},
|
|
19777
18540
|
llm: {
|
|
19778
18541
|
call: makeLLMCall,
|
|
19779
18542
|
seek: makeDeepseekCall,
|
|
19780
18543
|
images: makeImagesCall,
|
|
19781
18544
|
open: makeOpenRouterCall,
|
|
19782
18545
|
},
|
|
19783
|
-
polygon: {
|
|
19784
|
-
fetchTickerInfo: fetchTickerInfo,
|
|
19785
|
-
fetchGroupedDaily: fetchGroupedDaily,
|
|
19786
|
-
fetchLastTrade: fetchLastTrade,
|
|
19787
|
-
fetchTrades: fetchTrades,
|
|
19788
|
-
fetchPrices: fetchPrices,
|
|
19789
|
-
analysePolygonPriceData: analysePolygonPriceData,
|
|
19790
|
-
formatPriceData: formatPriceData,
|
|
19791
|
-
fetchDailyOpenClose: fetchDailyOpenClose,
|
|
19792
|
-
getPreviousClose: getPreviousClose,
|
|
19793
|
-
},
|
|
19794
|
-
ta: {
|
|
19795
|
-
calculateEMA: calculateEMA,
|
|
19796
|
-
calculateMACD: calculateMACD,
|
|
19797
|
-
calculateRSI: calculateRSI,
|
|
19798
|
-
calculateStochasticOscillator: calculateStochasticOscillator,
|
|
19799
|
-
calculateBollingerBands: calculateBollingerBands,
|
|
19800
|
-
calculateSupportAndResistance: calculateSupportAndResistance,
|
|
19801
|
-
calculateFibonacciLevels: calculateFibonacciLevels,
|
|
19802
|
-
},
|
|
19803
18546
|
time: {
|
|
19804
18547
|
convertDateToMarketTimeZone: convertDateToMarketTimeZone,
|
|
19805
18548
|
getStartAndEndDates: getStartAndEndDates,
|
|
@@ -19822,7 +18565,6 @@ const disco = {
|
|
|
19822
18565
|
utils: {
|
|
19823
18566
|
logIfDebug: logIfDebug,
|
|
19824
18567
|
fetchWithRetry: fetchWithRetry,
|
|
19825
|
-
validatePolygonApiKey: validatePolygonApiKey,
|
|
19826
18568
|
Timer: PerformanceTimer,
|
|
19827
18569
|
},
|
|
19828
18570
|
};
|