@discomedia/utils 1.0.5 → 1.0.7

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.
Files changed (87) hide show
  1. package/README.md +95 -3
  2. package/dist/index-frontend.cjs +16027 -0
  3. package/dist/index-frontend.cjs.map +1 -0
  4. package/dist/index-frontend.mjs +16023 -0
  5. package/dist/index-frontend.mjs.map +1 -0
  6. package/dist/index.cjs +1188 -921
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.mjs +1190 -921
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/package.json +8 -2
  11. package/dist/test.js +835 -731
  12. package/dist/test.js.map +1 -1
  13. package/dist/types/alpaca-market-data-api.d.ts +3 -15
  14. package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
  15. package/dist/types/alpaca-trading-api.d.ts +3 -6
  16. package/dist/types/alpaca-trading-api.d.ts.map +1 -1
  17. package/dist/types/index-frontend.d.ts +15 -0
  18. package/dist/types/index-frontend.d.ts.map +1 -0
  19. package/dist/types/index.d.ts +3 -28
  20. package/dist/types/index.d.ts.map +1 -1
  21. package/dist/types/market-time.d.ts +187 -117
  22. package/dist/types/market-time.d.ts.map +1 -1
  23. package/dist/types/old-test.d.ts +2 -0
  24. package/dist/types/old-test.d.ts.map +1 -0
  25. package/dist/types/testing/market-time-refactor-test.d.ts +1 -0
  26. package/dist/types/testing/market-time-refactor-test.d.ts.map +1 -0
  27. package/dist/types-frontend/alpaca-market-data-api.d.ts +372 -0
  28. package/dist/types-frontend/alpaca-market-data-api.d.ts.map +1 -0
  29. package/dist/types-frontend/alpaca-trading-api.d.ts +315 -0
  30. package/dist/types-frontend/alpaca-trading-api.d.ts.map +1 -0
  31. package/dist/types-frontend/format-tools.d.ts +46 -0
  32. package/dist/types-frontend/format-tools.d.ts.map +1 -0
  33. package/dist/types-frontend/index-frontend.d.ts +15 -0
  34. package/dist/types-frontend/index-frontend.d.ts.map +1 -0
  35. package/dist/types-frontend/index.d.ts +125 -0
  36. package/dist/types-frontend/index.d.ts.map +1 -0
  37. package/dist/types-frontend/json-tools.d.ts +33 -0
  38. package/dist/types-frontend/json-tools.d.ts.map +1 -0
  39. package/dist/types-frontend/llm-config.d.ts +36 -0
  40. package/dist/types-frontend/llm-config.d.ts.map +1 -0
  41. package/dist/types-frontend/llm-deepseek.d.ts +12 -0
  42. package/dist/types-frontend/llm-deepseek.d.ts.map +1 -0
  43. package/dist/types-frontend/llm-images.d.ts +49 -0
  44. package/dist/types-frontend/llm-images.d.ts.map +1 -0
  45. package/dist/types-frontend/llm-openai.d.ts +64 -0
  46. package/dist/types-frontend/llm-openai.d.ts.map +1 -0
  47. package/dist/types-frontend/llm-utils.d.ts +16 -0
  48. package/dist/types-frontend/llm-utils.d.ts.map +1 -0
  49. package/dist/types-frontend/logging.d.ts +12 -0
  50. package/dist/types-frontend/logging.d.ts.map +1 -0
  51. package/dist/types-frontend/market-hours.d.ts +24 -0
  52. package/dist/types-frontend/market-hours.d.ts.map +1 -0
  53. package/dist/types-frontend/market-time.d.ts +254 -0
  54. package/dist/types-frontend/market-time.d.ts.map +1 -0
  55. package/dist/types-frontend/misc-utils.d.ts +49 -0
  56. package/dist/types-frontend/misc-utils.d.ts.map +1 -0
  57. package/dist/types-frontend/old-test.d.ts +2 -0
  58. package/dist/types-frontend/old-test.d.ts.map +1 -0
  59. package/dist/types-frontend/polygon-indices.d.ts +85 -0
  60. package/dist/types-frontend/polygon-indices.d.ts.map +1 -0
  61. package/dist/types-frontend/polygon.d.ts +126 -0
  62. package/dist/types-frontend/polygon.d.ts.map +1 -0
  63. package/dist/types-frontend/technical-analysis.d.ts +90 -0
  64. package/dist/types-frontend/technical-analysis.d.ts.map +1 -0
  65. package/dist/types-frontend/test.d.ts +2 -0
  66. package/dist/types-frontend/test.d.ts.map +1 -0
  67. package/dist/types-frontend/testing/market-time-refactor-test.d.ts +1 -0
  68. package/dist/types-frontend/testing/market-time-refactor-test.d.ts.map +1 -0
  69. package/dist/types-frontend/types/alpaca-types.d.ts +962 -0
  70. package/dist/types-frontend/types/alpaca-types.d.ts.map +1 -0
  71. package/dist/types-frontend/types/index.d.ts +7 -0
  72. package/dist/types-frontend/types/index.d.ts.map +1 -0
  73. package/dist/types-frontend/types/llm-types.d.ts +82 -0
  74. package/dist/types-frontend/types/llm-types.d.ts.map +1 -0
  75. package/dist/types-frontend/types/logging-types.d.ts +10 -0
  76. package/dist/types-frontend/types/logging-types.d.ts.map +1 -0
  77. package/dist/types-frontend/types/market-time-types.d.ts +59 -0
  78. package/dist/types-frontend/types/market-time-types.d.ts.map +1 -0
  79. package/dist/types-frontend/types/polygon-indices-types.d.ts +190 -0
  80. package/dist/types-frontend/types/polygon-indices-types.d.ts.map +1 -0
  81. package/dist/types-frontend/types/polygon-types.d.ts +204 -0
  82. package/dist/types-frontend/types/polygon-types.d.ts.map +1 -0
  83. package/dist/types-frontend/types/ta-types.d.ts +89 -0
  84. package/dist/types-frontend/types/ta-types.d.ts.map +1 -0
  85. package/package.json +8 -2
  86. package/dist/types/time-utils.d.ts +0 -17
  87. package/dist/types/time-utils.d.ts.map +0 -1
package/dist/test.js CHANGED
@@ -1,15 +1,538 @@
1
- import { format, sub, set, add, startOfDay, endOfDay, isBefore } from 'date-fns';
2
- import { formatInTimeZone, toZonedTime, fromZonedTime } from 'date-fns-tz';
3
- import require$$0$3, { EventEmitter } from 'events';
1
+ import require$$0 from 'fs';
2
+ import require$$1 from 'path';
3
+ import require$$2 from 'os';
4
+ import require$$3 from 'crypto';
5
+ import { startOfDay, set, endOfDay, add, sub } from 'date-fns';
6
+ import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz';
7
+ import require$$0$4, { EventEmitter } from 'events';
4
8
  import require$$1$1 from 'https';
5
- import require$$2 from 'http';
6
- import require$$3 from 'net';
7
- import require$$4 from 'tls';
8
- import require$$1 from 'crypto';
9
- import require$$0$2 from 'stream';
9
+ import require$$2$1 from 'http';
10
+ import require$$3$1 from 'net';
11
+ import require$$4$1 from 'tls';
12
+ import require$$0$3 from 'stream';
10
13
  import require$$7 from 'url';
11
- import require$$0 from 'zlib';
12
- import require$$0$1 from 'buffer';
14
+ import require$$0$1 from 'zlib';
15
+ import require$$0$2 from 'buffer';
16
+
17
+ function getDefaultExportFromCjs (x) {
18
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
19
+ }
20
+
21
+ var config = {};
22
+
23
+ var main = {exports: {}};
24
+
25
+ var version = "17.1.0";
26
+ var require$$4 = {
27
+ version: version};
28
+
29
+ var hasRequiredMain;
30
+
31
+ function requireMain () {
32
+ if (hasRequiredMain) return main.exports;
33
+ hasRequiredMain = 1;
34
+ const fs = require$$0;
35
+ const path = require$$1;
36
+ const os = require$$2;
37
+ const crypto = require$$3;
38
+ const packageJson = require$$4;
39
+
40
+ const version = packageJson.version;
41
+
42
+ // Array of tips to display randomly
43
+ const TIPS = [
44
+ '🔐 encrypt with dotenvx: https://dotenvx.com',
45
+ '🔐 prevent committing .env to code: https://dotenvx.com/precommit',
46
+ '🔐 prevent building .env in docker: https://dotenvx.com/prebuild',
47
+ '🛠️ run anywhere with `dotenvx run -- yourcommand`',
48
+ '⚙️ specify custom .env file path with { path: \'/custom/path/.env\' }',
49
+ '⚙️ enable debug logging with { debug: true }',
50
+ '⚙️ override existing env vars with { override: true }',
51
+ '⚙️ suppress all logs with { quiet: true }',
52
+ '⚙️ write to custom object with { processEnv: myObject }',
53
+ '⚙️ load multiple .env files with { path: [\'.env.local\', \'.env\'] }'
54
+ ];
55
+
56
+ // Get a random tip from the tips array
57
+ function _getRandomTip () {
58
+ return TIPS[Math.floor(Math.random() * TIPS.length)]
59
+ }
60
+
61
+ function supportsAnsi () {
62
+ return process.stdout.isTTY // && process.env.TERM !== 'dumb'
63
+ }
64
+
65
+ function dim (text) {
66
+ return supportsAnsi() ? `\x1b[2m${text}\x1b[0m` : text
67
+ }
68
+
69
+ const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
70
+
71
+ // Parse src into an Object
72
+ function parse (src) {
73
+ const obj = {};
74
+
75
+ // Convert buffer to string
76
+ let lines = src.toString();
77
+
78
+ // Convert line breaks to same format
79
+ lines = lines.replace(/\r\n?/mg, '\n');
80
+
81
+ let match;
82
+ while ((match = LINE.exec(lines)) != null) {
83
+ const key = match[1];
84
+
85
+ // Default undefined or null to empty string
86
+ let value = (match[2] || '');
87
+
88
+ // Remove whitespace
89
+ value = value.trim();
90
+
91
+ // Check if double quoted
92
+ const maybeQuote = value[0];
93
+
94
+ // Remove surrounding quotes
95
+ value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
96
+
97
+ // Expand newlines if double quoted
98
+ if (maybeQuote === '"') {
99
+ value = value.replace(/\\n/g, '\n');
100
+ value = value.replace(/\\r/g, '\r');
101
+ }
102
+
103
+ // Add to object
104
+ obj[key] = value;
105
+ }
106
+
107
+ return obj
108
+ }
109
+
110
+ function _parseVault (options) {
111
+ options = options || {};
112
+
113
+ const vaultPath = _vaultPath(options);
114
+ options.path = vaultPath; // parse .env.vault
115
+ const result = DotenvModule.configDotenv(options);
116
+ if (!result.parsed) {
117
+ const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
118
+ err.code = 'MISSING_DATA';
119
+ throw err
120
+ }
121
+
122
+ // handle scenario for comma separated keys - for use with key rotation
123
+ // example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
124
+ const keys = _dotenvKey(options).split(',');
125
+ const length = keys.length;
126
+
127
+ let decrypted;
128
+ for (let i = 0; i < length; i++) {
129
+ try {
130
+ // Get full key
131
+ const key = keys[i].trim();
132
+
133
+ // Get instructions for decrypt
134
+ const attrs = _instructions(result, key);
135
+
136
+ // Decrypt
137
+ decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
138
+
139
+ break
140
+ } catch (error) {
141
+ // last key
142
+ if (i + 1 >= length) {
143
+ throw error
144
+ }
145
+ // try next key
146
+ }
147
+ }
148
+
149
+ // Parse decrypted .env string
150
+ return DotenvModule.parse(decrypted)
151
+ }
152
+
153
+ function _warn (message) {
154
+ console.error(`[dotenv@${version}][WARN] ${message}`);
155
+ }
156
+
157
+ function _debug (message) {
158
+ console.log(`[dotenv@${version}][DEBUG] ${message}`);
159
+ }
160
+
161
+ function _log (message) {
162
+ console.log(`[dotenv@${version}] ${message}`);
163
+ }
164
+
165
+ function _dotenvKey (options) {
166
+ // prioritize developer directly setting options.DOTENV_KEY
167
+ if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
168
+ return options.DOTENV_KEY
169
+ }
170
+
171
+ // secondary infra already contains a DOTENV_KEY environment variable
172
+ if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
173
+ return process.env.DOTENV_KEY
174
+ }
175
+
176
+ // fallback to empty string
177
+ return ''
178
+ }
179
+
180
+ function _instructions (result, dotenvKey) {
181
+ // Parse DOTENV_KEY. Format is a URI
182
+ let uri;
183
+ try {
184
+ uri = new URL(dotenvKey);
185
+ } catch (error) {
186
+ if (error.code === 'ERR_INVALID_URL') {
187
+ const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
188
+ err.code = 'INVALID_DOTENV_KEY';
189
+ throw err
190
+ }
191
+
192
+ throw error
193
+ }
194
+
195
+ // Get decrypt key
196
+ const key = uri.password;
197
+ if (!key) {
198
+ const err = new Error('INVALID_DOTENV_KEY: Missing key part');
199
+ err.code = 'INVALID_DOTENV_KEY';
200
+ throw err
201
+ }
202
+
203
+ // Get environment
204
+ const environment = uri.searchParams.get('environment');
205
+ if (!environment) {
206
+ const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
207
+ err.code = 'INVALID_DOTENV_KEY';
208
+ throw err
209
+ }
210
+
211
+ // Get ciphertext payload
212
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
213
+ const ciphertext = result.parsed[environmentKey]; // DOTENV_VAULT_PRODUCTION
214
+ if (!ciphertext) {
215
+ const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
216
+ err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
217
+ throw err
218
+ }
219
+
220
+ return { ciphertext, key }
221
+ }
222
+
223
+ function _vaultPath (options) {
224
+ let possibleVaultPath = null;
225
+
226
+ if (options && options.path && options.path.length > 0) {
227
+ if (Array.isArray(options.path)) {
228
+ for (const filepath of options.path) {
229
+ if (fs.existsSync(filepath)) {
230
+ possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
231
+ }
232
+ }
233
+ } else {
234
+ possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
235
+ }
236
+ } else {
237
+ possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
238
+ }
239
+
240
+ if (fs.existsSync(possibleVaultPath)) {
241
+ return possibleVaultPath
242
+ }
243
+
244
+ return null
245
+ }
246
+
247
+ function _resolveHome (envPath) {
248
+ return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
249
+ }
250
+
251
+ function _configVault (options) {
252
+ const debug = Boolean(options && options.debug);
253
+ const quiet = Boolean(options && options.quiet);
254
+
255
+ if (debug || !quiet) {
256
+ _log('Loading env from encrypted .env.vault');
257
+ }
258
+
259
+ const parsed = DotenvModule._parseVault(options);
260
+
261
+ let processEnv = process.env;
262
+ if (options && options.processEnv != null) {
263
+ processEnv = options.processEnv;
264
+ }
265
+
266
+ DotenvModule.populate(processEnv, parsed, options);
267
+
268
+ return { parsed }
269
+ }
270
+
271
+ function configDotenv (options) {
272
+ const dotenvPath = path.resolve(process.cwd(), '.env');
273
+ let encoding = 'utf8';
274
+ const debug = Boolean(options && options.debug);
275
+ const quiet = Boolean(options && options.quiet);
276
+
277
+ if (options && options.encoding) {
278
+ encoding = options.encoding;
279
+ } else {
280
+ if (debug) {
281
+ _debug('No encoding is specified. UTF-8 is used by default');
282
+ }
283
+ }
284
+
285
+ let optionPaths = [dotenvPath]; // default, look for .env
286
+ if (options && options.path) {
287
+ if (!Array.isArray(options.path)) {
288
+ optionPaths = [_resolveHome(options.path)];
289
+ } else {
290
+ optionPaths = []; // reset default
291
+ for (const filepath of options.path) {
292
+ optionPaths.push(_resolveHome(filepath));
293
+ }
294
+ }
295
+ }
296
+
297
+ // Build the parsed data in a temporary object (because we need to return it). Once we have the final
298
+ // parsed data, we will combine it with process.env (or options.processEnv if provided).
299
+ let lastError;
300
+ const parsedAll = {};
301
+ for (const path of optionPaths) {
302
+ try {
303
+ // Specifying an encoding returns a string instead of a buffer
304
+ const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
305
+
306
+ DotenvModule.populate(parsedAll, parsed, options);
307
+ } catch (e) {
308
+ if (debug) {
309
+ _debug(`Failed to load ${path} ${e.message}`);
310
+ }
311
+ lastError = e;
312
+ }
313
+ }
314
+
315
+ let processEnv = process.env;
316
+ if (options && options.processEnv != null) {
317
+ processEnv = options.processEnv;
318
+ }
319
+
320
+ const populated = DotenvModule.populate(processEnv, parsedAll, options);
321
+
322
+ if (debug || !quiet) {
323
+ const keysCount = Object.keys(populated).length;
324
+ const shortPaths = [];
325
+ for (const filePath of optionPaths) {
326
+ try {
327
+ const relative = path.relative(process.cwd(), filePath);
328
+ shortPaths.push(relative);
329
+ } catch (e) {
330
+ if (debug) {
331
+ _debug(`Failed to load ${filePath} ${e.message}`);
332
+ }
333
+ lastError = e;
334
+ }
335
+ }
336
+
337
+ _log(`injecting env (${keysCount}) from ${shortPaths.join(',')} ${dim(`(tip: ${_getRandomTip()})`)}`);
338
+ }
339
+
340
+ if (lastError) {
341
+ return { parsed: parsedAll, error: lastError }
342
+ } else {
343
+ return { parsed: parsedAll }
344
+ }
345
+ }
346
+
347
+ // Populates process.env from .env file
348
+ function config (options) {
349
+ // fallback to original dotenv if DOTENV_KEY is not set
350
+ if (_dotenvKey(options).length === 0) {
351
+ return DotenvModule.configDotenv(options)
352
+ }
353
+
354
+ const vaultPath = _vaultPath(options);
355
+
356
+ // dotenvKey exists but .env.vault file does not exist
357
+ if (!vaultPath) {
358
+ _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
359
+
360
+ return DotenvModule.configDotenv(options)
361
+ }
362
+
363
+ return DotenvModule._configVault(options)
364
+ }
365
+
366
+ function decrypt (encrypted, keyStr) {
367
+ const key = Buffer.from(keyStr.slice(-64), 'hex');
368
+ let ciphertext = Buffer.from(encrypted, 'base64');
369
+
370
+ const nonce = ciphertext.subarray(0, 12);
371
+ const authTag = ciphertext.subarray(-16);
372
+ ciphertext = ciphertext.subarray(12, -16);
373
+
374
+ try {
375
+ const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
376
+ aesgcm.setAuthTag(authTag);
377
+ return `${aesgcm.update(ciphertext)}${aesgcm.final()}`
378
+ } catch (error) {
379
+ const isRange = error instanceof RangeError;
380
+ const invalidKeyLength = error.message === 'Invalid key length';
381
+ const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data';
382
+
383
+ if (isRange || invalidKeyLength) {
384
+ const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
385
+ err.code = 'INVALID_DOTENV_KEY';
386
+ throw err
387
+ } else if (decryptionFailed) {
388
+ const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
389
+ err.code = 'DECRYPTION_FAILED';
390
+ throw err
391
+ } else {
392
+ throw error
393
+ }
394
+ }
395
+ }
396
+
397
+ // Populate process.env with parsed values
398
+ function populate (processEnv, parsed, options = {}) {
399
+ const debug = Boolean(options && options.debug);
400
+ const override = Boolean(options && options.override);
401
+ const populated = {};
402
+
403
+ if (typeof parsed !== 'object') {
404
+ const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
405
+ err.code = 'OBJECT_REQUIRED';
406
+ throw err
407
+ }
408
+
409
+ // Set process.env
410
+ for (const key of Object.keys(parsed)) {
411
+ if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
412
+ if (override === true) {
413
+ processEnv[key] = parsed[key];
414
+ populated[key] = parsed[key];
415
+ }
416
+
417
+ if (debug) {
418
+ if (override === true) {
419
+ _debug(`"${key}" is already defined and WAS overwritten`);
420
+ } else {
421
+ _debug(`"${key}" is already defined and was NOT overwritten`);
422
+ }
423
+ }
424
+ } else {
425
+ processEnv[key] = parsed[key];
426
+ populated[key] = parsed[key];
427
+ }
428
+ }
429
+
430
+ return populated
431
+ }
432
+
433
+ const DotenvModule = {
434
+ configDotenv,
435
+ _configVault,
436
+ _parseVault,
437
+ config,
438
+ decrypt,
439
+ parse,
440
+ populate
441
+ };
442
+
443
+ main.exports.configDotenv = DotenvModule.configDotenv;
444
+ main.exports._configVault = DotenvModule._configVault;
445
+ main.exports._parseVault = DotenvModule._parseVault;
446
+ main.exports.config = DotenvModule.config;
447
+ main.exports.decrypt = DotenvModule.decrypt;
448
+ main.exports.parse = DotenvModule.parse;
449
+ main.exports.populate = DotenvModule.populate;
450
+
451
+ main.exports = DotenvModule;
452
+ return main.exports;
453
+ }
454
+
455
+ var envOptions;
456
+ var hasRequiredEnvOptions;
457
+
458
+ function requireEnvOptions () {
459
+ if (hasRequiredEnvOptions) return envOptions;
460
+ hasRequiredEnvOptions = 1;
461
+ // ../config.js accepts options via environment variables
462
+ const options = {};
463
+
464
+ if (process.env.DOTENV_CONFIG_ENCODING != null) {
465
+ options.encoding = process.env.DOTENV_CONFIG_ENCODING;
466
+ }
467
+
468
+ if (process.env.DOTENV_CONFIG_PATH != null) {
469
+ options.path = process.env.DOTENV_CONFIG_PATH;
470
+ }
471
+
472
+ if (process.env.DOTENV_CONFIG_QUIET != null) {
473
+ options.quiet = process.env.DOTENV_CONFIG_QUIET;
474
+ }
475
+
476
+ if (process.env.DOTENV_CONFIG_DEBUG != null) {
477
+ options.debug = process.env.DOTENV_CONFIG_DEBUG;
478
+ }
479
+
480
+ if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
481
+ options.override = process.env.DOTENV_CONFIG_OVERRIDE;
482
+ }
483
+
484
+ if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
485
+ options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
486
+ }
487
+
488
+ envOptions = options;
489
+ return envOptions;
490
+ }
491
+
492
+ var cliOptions;
493
+ var hasRequiredCliOptions;
494
+
495
+ function requireCliOptions () {
496
+ if (hasRequiredCliOptions) return cliOptions;
497
+ hasRequiredCliOptions = 1;
498
+ const re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/;
499
+
500
+ cliOptions = function optionMatcher (args) {
501
+ const options = args.reduce(function (acc, cur) {
502
+ const matches = cur.match(re);
503
+ if (matches) {
504
+ acc[matches[1]] = matches[2];
505
+ }
506
+ return acc
507
+ }, {});
508
+
509
+ if (!('quiet' in options)) {
510
+ options.quiet = 'true';
511
+ }
512
+
513
+ return options
514
+ };
515
+ return cliOptions;
516
+ }
517
+
518
+ var hasRequiredConfig;
519
+
520
+ function requireConfig () {
521
+ if (hasRequiredConfig) return config;
522
+ hasRequiredConfig = 1;
523
+ (function () {
524
+ requireMain().config(
525
+ Object.assign(
526
+ {},
527
+ requireEnvOptions(),
528
+ requireCliOptions()(process.argv)
529
+ )
530
+ );
531
+ })();
532
+ return config;
533
+ }
534
+
535
+ requireConfig();
13
536
 
14
537
  /**
15
538
  * Logs a message to the console.
@@ -143,89 +666,65 @@ const marketEarlyCloses = {
143
666
  },
144
667
  };
145
668
 
146
- // market-time.ts
669
+ // market-time.ts - Refactored for better organization and usability
670
+ // ===== CONFIGURATION =====
147
671
  /**
148
- * Market times for NYSE
149
- * Regular market hours are 9:30am-4:00pm
150
- * Early market hours are 9:30am-10:00am (first 30 minutes)
151
- * Extended market hours are 4:00am to 9:30am and 4:00pm-8:00pm
152
- * On days before some holidays, the market closes early at 1:00pm
153
- * Early extended market hours are 1:00pm-5:00pm on early close days
672
+ * Market configuration constants
154
673
  */
155
- const MARKET_TIMES = {
674
+ const MARKET_CONFIG = {
156
675
  TIMEZONE: 'America/New_York',
157
- REGULAR: { START: { HOUR: 9, MINUTE: 30}, END: { HOUR: 16, MINUTE: 0} },
158
- EXTENDED: { START: { HOUR: 4, MINUTE: 0}, END: { HOUR: 20, MINUTE: 0} },
676
+ TIMES: {
677
+ EXTENDED_START: { hour: 4, minute: 0 },
678
+ MARKET_OPEN: { hour: 9, minute: 30 },
679
+ MARKET_CLOSE: { hour: 16, minute: 0 },
680
+ EARLY_CLOSE: { hour: 13, minute: 0 },
681
+ EXTENDED_END: { hour: 20, minute: 0 },
682
+ EARLY_EXTENDED_END: { hour: 17, minute: 0 },
683
+ },
159
684
  };
685
+ // ===== MARKET CALENDAR SERVICE =====
160
686
  /**
161
- * Utility class for handling market time-related operations
687
+ * Service for handling market calendar operations (holidays, early closes, market days)
162
688
  */
163
- class MarketTimeUtil {
689
+ class MarketCalendar {
164
690
  timezone;
165
- intradayReporting;
166
- /**
167
- * Creates a new MarketTimeUtil instance
168
- * @param {string} [timezone='America/New_York'] - The timezone to use for market time calculations
169
- * @param {IntradayReporting} [intradayReporting='market_hours'] - The intraday reporting mode
170
- */
171
- constructor(timezone = MARKET_TIMES.TIMEZONE, intradayReporting = 'market_hours') {
172
- this.validateTimezone(timezone);
691
+ constructor(timezone = MARKET_CONFIG.TIMEZONE) {
173
692
  this.timezone = timezone;
174
- this.intradayReporting = intradayReporting;
175
693
  }
176
694
  /**
177
- * Validates the provided timezone
178
- * @private
179
- * @param {string} timezone - The timezone to validate
180
- * @throws {Error} If the timezone is invalid
695
+ * Check if a date is a weekend
181
696
  */
182
- validateTimezone(timezone) {
183
- try {
184
- Intl.DateTimeFormat(undefined, { timeZone: timezone });
185
- }
186
- catch (error) {
187
- throw new Error(`Invalid timezone: ${timezone}`);
188
- }
189
- }
190
- formatDate(date, outputFormat = 'iso') {
191
- switch (outputFormat) {
192
- case 'unix-seconds':
193
- return Math.floor(date.getTime() / 1000);
194
- case 'unix-ms':
195
- return date.getTime();
196
- case 'iso':
197
- default:
198
- // return with timezone offset
199
- return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
200
- }
201
- }
202
697
  isWeekend(date) {
203
698
  const day = date.getDay();
204
- return day === 0 || day === 6;
699
+ return day === 0 || day === 6; // Sunday or Saturday
205
700
  }
701
+ /**
702
+ * Check if a date is a market holiday
703
+ */
206
704
  isHoliday(date) {
207
- const formattedDate = format(date, 'yyyy-MM-dd');
208
- const yearHolidays = marketHolidays[date.getFullYear()];
209
- for (const holiday in yearHolidays) {
210
- if (yearHolidays[holiday].date === formattedDate) {
211
- return true;
212
- }
213
- }
214
- return false;
705
+ const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
706
+ const year = toZonedTime(date, this.timezone).getFullYear();
707
+ const yearHolidays = marketHolidays[year];
708
+ if (!yearHolidays)
709
+ return false;
710
+ return Object.values(yearHolidays).some(holiday => holiday.date === formattedDate);
215
711
  }
712
+ /**
713
+ * Check if a date is an early close day
714
+ */
216
715
  isEarlyCloseDay(date) {
217
- const formattedDate = format(date, 'yyyy-MM-dd');
218
- const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
716
+ const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
717
+ const year = toZonedTime(date, this.timezone).getFullYear();
718
+ const yearEarlyCloses = marketEarlyCloses[year];
219
719
  return yearEarlyCloses && yearEarlyCloses[formattedDate] !== undefined;
220
720
  }
221
721
  /**
222
- * Get the early close time for a given date
223
- * @param date - The date to get the early close time for
224
- * @returns The early close time in minutes from midnight, or null if there is no early close
722
+ * Get the early close time for a date (in minutes from midnight)
225
723
  */
226
724
  getEarlyCloseTime(date) {
227
- const formattedDate = format(date, 'yyyy-MM-dd');
228
- const yearEarlyCloses = marketEarlyCloses[date.getFullYear()];
725
+ const formattedDate = formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
726
+ const year = toZonedTime(date, this.timezone).getFullYear();
727
+ const yearEarlyCloses = marketEarlyCloses[year];
229
728
  if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
230
729
  const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(':').map(Number);
231
730
  return hours * 60 + minutes;
@@ -233,357 +732,280 @@ class MarketTimeUtil {
233
732
  return null;
234
733
  }
235
734
  /**
236
- * Check if a given date is a market day
237
- * @param date - The date to check
238
- * @returns true if the date is a market day, false otherwise
735
+ * Check if a date is a market day (not weekend or holiday)
239
736
  */
240
737
  isMarketDay(date) {
241
- const isWeekendDay = this.isWeekend(date);
242
- const isHolidayDay = this.isHoliday(date);
243
- const returner = !isWeekendDay && !isHolidayDay;
244
- return returner;
738
+ return !this.isWeekend(date) && !this.isHoliday(date);
245
739
  }
246
740
  /**
247
- * Check if a given date is within market hours
248
- * @param date - The date to check
249
- * @returns true if the date is within market hours, false otherwise
741
+ * Get the next market day from a given date
250
742
  */
251
- isWithinMarketHours(date) {
252
- // Check for holidays first
253
- if (this.isHoliday(date)) {
254
- return false;
743
+ getNextMarketDay(date) {
744
+ let nextDay = add(date, { days: 1 });
745
+ while (!this.isMarketDay(nextDay)) {
746
+ nextDay = add(nextDay, { days: 1 });
255
747
  }
256
- const timeInMinutes = date.getHours() * 60 + date.getMinutes();
257
- // Check for early closure
258
- if (this.isEarlyCloseDay(date)) {
259
- const earlyCloseMinutes = this.getEarlyCloseTime(date);
260
- if (earlyCloseMinutes !== null && timeInMinutes > earlyCloseMinutes) {
261
- return false;
262
- }
748
+ return nextDay;
749
+ }
750
+ /**
751
+ * Get the previous market day from a given date
752
+ */
753
+ getPreviousMarketDay(date) {
754
+ let prevDay = sub(date, { days: 1 });
755
+ while (!this.isMarketDay(prevDay)) {
756
+ prevDay = sub(prevDay, { days: 1 });
263
757
  }
264
- // Regular market hours logic
265
- let returner;
266
- switch (this.intradayReporting) {
267
- case 'extended_hours': {
268
- const extendedStartMinutes = MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE;
269
- const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
270
- // Comprehensive handling of times crossing midnight
271
- const adjustedDate = timeInMinutes < extendedStartMinutes ? sub(date, { days: 1 }) : date;
272
- const adjustedTimeInMinutes = adjustedDate.getHours() * 60 + adjustedDate.getMinutes();
273
- returner = adjustedTimeInMinutes >= extendedStartMinutes && adjustedTimeInMinutes <= extendedEndMinutes;
274
- break;
275
- }
276
- case 'continuous':
277
- returner = true;
278
- break;
279
- default: {
280
- // market_hours
281
- const regularStartMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
282
- const regularEndMinutes = MARKET_TIMES.REGULAR.END.HOUR * 60 + MARKET_TIMES.REGULAR.END.MINUTE;
283
- returner = timeInMinutes >= regularStartMinutes && timeInMinutes <= regularEndMinutes;
284
- break;
285
- }
758
+ return prevDay;
759
+ }
760
+ }
761
+ // ===== TIME FORMATTER SERVICE =====
762
+ /**
763
+ * Service for formatting time outputs
764
+ */
765
+ class TimeFormatter {
766
+ timezone;
767
+ constructor(timezone = MARKET_CONFIG.TIMEZONE) {
768
+ this.timezone = timezone;
769
+ }
770
+ /**
771
+ * Format a date based on the output format
772
+ */
773
+ formatDate(date, outputFormat = 'iso') {
774
+ switch (outputFormat) {
775
+ case 'unix-seconds':
776
+ return Math.floor(date.getTime() / 1000);
777
+ case 'unix-ms':
778
+ return date.getTime();
779
+ case 'iso':
780
+ default:
781
+ return formatInTimeZone(date, this.timezone, "yyyy-MM-dd'T'HH:mm:ssXXX");
286
782
  }
287
- return returner;
288
783
  }
289
784
  /**
290
- * Check if a given date is before market hours
291
- * @param date - The date to check
292
- * @returns true if the date is before market hours, false otherwise
785
+ * Get New York timezone offset
293
786
  */
294
- isBeforeMarketHours(date) {
295
- const timeInMinutes = date.getHours() * 60 + date.getMinutes();
296
- const startMinutes = this.intradayReporting === 'extended_hours'
297
- ? MARKET_TIMES.EXTENDED.START.HOUR * 60 + MARKET_TIMES.EXTENDED.START.MINUTE
298
- : MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
299
- return timeInMinutes < startMinutes;
787
+ getNYTimeZone(date = new Date()) {
788
+ const dtf = new Intl.DateTimeFormat('en-US', {
789
+ timeZone: this.timezone,
790
+ timeZoneName: 'shortOffset',
791
+ });
792
+ const parts = dtf.formatToParts(date);
793
+ const tz = parts.find(p => p.type === 'timeZoneName')?.value;
794
+ if (!tz) {
795
+ throw new Error('Could not determine New York offset');
796
+ }
797
+ const shortOffset = tz.replace('GMT', '');
798
+ if (shortOffset === '-4') {
799
+ return '-04:00';
800
+ }
801
+ else if (shortOffset === '-5') {
802
+ return '-05:00';
803
+ }
804
+ else {
805
+ throw new Error(`Unexpected timezone offset: ${shortOffset}`);
806
+ }
300
807
  }
301
808
  /**
302
- * Get the last trading date, i.e. the last date that was a market day
303
- * @param currentDate - The current date
304
- * @returns The last trading date
809
+ * Get trading date in YYYY-MM-DD format
305
810
  */
306
- getLastTradingDate(currentDate = new Date()) {
307
- const nowET = toZonedTime(currentDate, this.timezone);
308
- const isMarketDayToday = this.isMarketDay(nowET);
309
- const currentMinutes = nowET.getHours() * 60 + nowET.getMinutes();
310
- const marketOpenMinutes = MARKET_TIMES.REGULAR.START.HOUR * 60 + MARKET_TIMES.REGULAR.START.MINUTE;
311
- if (isMarketDayToday && currentMinutes >= marketOpenMinutes) {
312
- // After market open on a market day, return today
313
- return nowET;
811
+ getTradingDate(time) {
812
+ let date;
813
+ if (typeof time === 'number') {
814
+ date = new Date(time);
815
+ }
816
+ else if (typeof time === 'string') {
817
+ date = new Date(time);
314
818
  }
315
819
  else {
316
- // Before market open, or not a market day, return previous trading day
317
- let lastTradingDate = sub(nowET, { days: 1 });
318
- while (!this.isMarketDay(lastTradingDate)) {
319
- lastTradingDate = sub(lastTradingDate, { days: 1 });
320
- }
321
- return lastTradingDate;
820
+ date = time;
322
821
  }
822
+ return formatInTimeZone(date, this.timezone, 'yyyy-MM-dd');
823
+ }
824
+ }
825
+ // ===== MARKET TIME CALCULATOR =====
826
+ /**
827
+ * Service for core market time calculations
828
+ */
829
+ class MarketTimeCalculator {
830
+ calendar;
831
+ formatter;
832
+ timezone;
833
+ constructor(timezone = MARKET_CONFIG.TIMEZONE) {
834
+ this.timezone = timezone;
835
+ this.calendar = new MarketCalendar(timezone);
836
+ this.formatter = new TimeFormatter(timezone);
323
837
  }
324
- getLastMarketDay(date) {
325
- let currentDate = sub(date, { days: 1 });
326
- while (!this.isMarketDay(currentDate)) {
327
- currentDate = sub(currentDate, { days: 1 });
838
+ /**
839
+ * Get market open/close times for a date
840
+ */
841
+ getMarketTimes(date) {
842
+ const zonedDate = toZonedTime(date, this.timezone);
843
+ // Market closed on weekends and holidays
844
+ if (!this.calendar.isMarketDay(zonedDate)) {
845
+ return {
846
+ marketOpen: false,
847
+ open: null,
848
+ close: null,
849
+ openExt: null,
850
+ closeExt: null,
851
+ };
852
+ }
853
+ const dayStart = startOfDay(zonedDate);
854
+ // Regular market times
855
+ const open = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour, minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute }), this.timezone);
856
+ let close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute }), this.timezone);
857
+ // Extended hours
858
+ const openExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute }), this.timezone);
859
+ let closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute }), this.timezone);
860
+ // Handle early close days
861
+ if (this.calendar.isEarlyCloseDay(zonedDate)) {
862
+ close = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour, minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute }), this.timezone);
863
+ closeExt = fromZonedTime(set(dayStart, { hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour, minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute }), this.timezone);
864
+ }
865
+ return {
866
+ marketOpen: true,
867
+ open,
868
+ close,
869
+ openExt,
870
+ closeExt,
871
+ };
872
+ }
873
+ /**
874
+ * Check if a time is within market hours based on intraday reporting mode
875
+ */
876
+ isWithinMarketHours(date, intradayReporting = 'market_hours') {
877
+ const zonedDate = toZonedTime(date, this.timezone);
878
+ // Not a market day
879
+ if (!this.calendar.isMarketDay(zonedDate)) {
880
+ return false;
881
+ }
882
+ const timeInMinutes = zonedDate.getHours() * 60 + zonedDate.getMinutes();
883
+ switch (intradayReporting) {
884
+ case 'extended_hours': {
885
+ const startMinutes = MARKET_CONFIG.TIMES.EXTENDED_START.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_START.minute;
886
+ let endMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
887
+ // Handle early close
888
+ if (this.calendar.isEarlyCloseDay(zonedDate)) {
889
+ endMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
890
+ }
891
+ return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
892
+ }
893
+ case 'continuous':
894
+ return true;
895
+ default: {
896
+ // 'market_hours'
897
+ const startMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
898
+ let endMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
899
+ // Handle early close
900
+ if (this.calendar.isEarlyCloseDay(zonedDate)) {
901
+ endMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
902
+ }
903
+ return timeInMinutes >= startMinutes && timeInMinutes <= endMinutes;
904
+ }
328
905
  }
329
- return currentDate;
330
906
  }
907
+ /**
908
+ * Get the last full trading date
909
+ */
331
910
  getLastFullTradingDate(currentDate = new Date()) {
332
911
  const nowET = toZonedTime(currentDate, this.timezone);
333
- // If today is a market day and we're after extended hours close
334
- // then return today since it's a completed trading day
335
- if (this.isMarketDay(nowET)) {
912
+ // If today is a market day and we're after extended hours close, return today
913
+ if (this.calendar.isMarketDay(nowET)) {
336
914
  const timeInMinutes = nowET.getHours() * 60 + nowET.getMinutes();
337
- const extendedEndMinutes = MARKET_TIMES.EXTENDED.END.HOUR * 60 + MARKET_TIMES.EXTENDED.END.MINUTE;
338
- // Check if we're after market close (including extended hours)
915
+ let extendedEndMinutes = MARKET_CONFIG.TIMES.EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EXTENDED_END.minute;
916
+ if (this.calendar.isEarlyCloseDay(nowET)) {
917
+ extendedEndMinutes = MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour * 60 + MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute;
918
+ }
339
919
  if (timeInMinutes >= extendedEndMinutes) {
340
- // Set to midnight ET while preserving the date
341
920
  return fromZonedTime(set(nowET, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
342
921
  }
343
922
  }
344
- // In all other cases (during trading hours, before market open, holidays, weekends),
345
- // we want the last completed trading day
346
- let lastFullDate = this.getLastMarketDay(nowET);
347
- // Set to midnight ET while preserving the date
348
- return fromZonedTime(set(lastFullDate, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
923
+ // Return the last completed trading day
924
+ const lastMarketDay = this.calendar.getPreviousMarketDay(nowET);
925
+ return fromZonedTime(set(lastMarketDay, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }), this.timezone);
349
926
  }
350
927
  /**
351
- * Gets the next market day from a reference date
352
- * @param {Object} [options] - Options object
353
- * @param {Date} [options.referenceDate] - The reference date (defaults to current date)
354
- * @returns {Object} The next market day information
355
- * @property {Date} date - The date object (start of day in NY time)
356
- * @property {string} yyyymmdd - The date in YYYY-MM-DD format
357
- * @property {string} dateISOString - Full ISO date string
928
+ * Get day boundaries based on intraday reporting mode
358
929
  */
359
- getNextMarketDay(date) {
360
- let currentDate = add(date, { days: 1 });
361
- while (!this.isMarketDay(currentDate)) {
362
- currentDate = add(currentDate, { days: 1 });
363
- }
364
- return currentDate;
365
- }
366
- getDayBoundaries(date) {
930
+ getDayBoundaries(date, intradayReporting = 'market_hours') {
931
+ const zonedDate = toZonedTime(date, this.timezone);
367
932
  let start;
368
933
  let end;
369
- switch (this.intradayReporting) {
934
+ switch (intradayReporting) {
370
935
  case 'extended_hours': {
371
- start = set(date, {
372
- hours: MARKET_TIMES.EXTENDED.START.HOUR,
373
- minutes: MARKET_TIMES.EXTENDED.START.MINUTE,
936
+ start = set(zonedDate, {
937
+ hours: MARKET_CONFIG.TIMES.EXTENDED_START.hour,
938
+ minutes: MARKET_CONFIG.TIMES.EXTENDED_START.minute,
374
939
  seconds: 0,
375
940
  milliseconds: 0,
376
941
  });
377
- end = set(date, {
378
- hours: MARKET_TIMES.EXTENDED.END.HOUR,
379
- minutes: MARKET_TIMES.EXTENDED.END.MINUTE,
942
+ end = set(zonedDate, {
943
+ hours: MARKET_CONFIG.TIMES.EXTENDED_END.hour,
944
+ minutes: MARKET_CONFIG.TIMES.EXTENDED_END.minute,
380
945
  seconds: 59,
381
946
  milliseconds: 999,
382
947
  });
948
+ // Handle early close
949
+ if (this.calendar.isEarlyCloseDay(zonedDate)) {
950
+ end = set(zonedDate, {
951
+ hours: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.hour,
952
+ minutes: MARKET_CONFIG.TIMES.EARLY_EXTENDED_END.minute,
953
+ seconds: 59,
954
+ milliseconds: 999,
955
+ });
956
+ }
383
957
  break;
384
958
  }
385
959
  case 'continuous': {
386
- start = startOfDay(date);
387
- end = endOfDay(date);
960
+ start = startOfDay(zonedDate);
961
+ end = endOfDay(zonedDate);
388
962
  break;
389
963
  }
390
964
  default: {
391
- // market_hours
392
- start = set(date, {
393
- hours: MARKET_TIMES.REGULAR.START.HOUR,
394
- minutes: MARKET_TIMES.REGULAR.START.MINUTE,
965
+ // 'market_hours'
966
+ start = set(zonedDate, {
967
+ hours: MARKET_CONFIG.TIMES.MARKET_OPEN.hour,
968
+ minutes: MARKET_CONFIG.TIMES.MARKET_OPEN.minute,
395
969
  seconds: 0,
396
970
  milliseconds: 0,
397
971
  });
398
- // Check for early close
399
- if (this.isEarlyCloseDay(date)) {
400
- const earlyCloseMinutes = this.getEarlyCloseTime(date);
401
- if (earlyCloseMinutes !== null) {
402
- const earlyCloseHours = Math.floor(earlyCloseMinutes / 60);
403
- const earlyCloseMinutesRemainder = earlyCloseMinutes % 60;
404
- end = set(date, {
405
- hours: earlyCloseHours,
406
- minutes: earlyCloseMinutesRemainder,
407
- seconds: 59,
408
- milliseconds: 999,
409
- });
410
- break;
411
- }
412
- }
413
- end = set(date, {
414
- hours: MARKET_TIMES.REGULAR.END.HOUR,
415
- minutes: MARKET_TIMES.REGULAR.END.MINUTE,
972
+ end = set(zonedDate, {
973
+ hours: MARKET_CONFIG.TIMES.MARKET_CLOSE.hour,
974
+ minutes: MARKET_CONFIG.TIMES.MARKET_CLOSE.minute,
416
975
  seconds: 59,
417
976
  milliseconds: 999,
418
977
  });
978
+ // Handle early close
979
+ if (this.calendar.isEarlyCloseDay(zonedDate)) {
980
+ end = set(zonedDate, {
981
+ hours: MARKET_CONFIG.TIMES.EARLY_CLOSE.hour,
982
+ minutes: MARKET_CONFIG.TIMES.EARLY_CLOSE.minute,
983
+ seconds: 59,
984
+ milliseconds: 999,
985
+ });
986
+ }
419
987
  break;
420
988
  }
421
989
  }
422
- return { start, end };
423
- }
424
- calculatePeriodStartDate(endDate, period) {
425
- let startDate;
426
- switch (period) {
427
- case 'YTD':
428
- startDate = set(endDate, { month: 0, date: 1 });
429
- break;
430
- case '1D':
431
- startDate = this.getLastMarketDay(endDate);
432
- break;
433
- case '3D':
434
- startDate = sub(endDate, { days: 3 });
435
- break;
436
- case '1W':
437
- startDate = sub(endDate, { weeks: 1 });
438
- break;
439
- case '2W':
440
- startDate = sub(endDate, { weeks: 2 });
441
- break;
442
- case '1M':
443
- startDate = sub(endDate, { months: 1 });
444
- break;
445
- case '3M':
446
- startDate = sub(endDate, { months: 3 });
447
- break;
448
- case '6M':
449
- startDate = sub(endDate, { months: 6 });
450
- break;
451
- case '1Y':
452
- startDate = sub(endDate, { years: 1 });
453
- break;
454
- default:
455
- throw new Error(`Invalid period: ${period}`);
456
- }
457
- while (!this.isMarketDay(startDate)) {
458
- startDate = this.getNextMarketDay(startDate);
459
- }
460
- return startDate;
461
- }
462
- getMarketTimePeriod({ period, end = new Date(), intraday_reporting, outputFormat = 'iso', }) {
463
- if (!period) {
464
- throw new Error('Period is required');
465
- }
466
- if (intraday_reporting) {
467
- this.intradayReporting = intraday_reporting;
468
- }
469
- // Convert end date to specified timezone
470
- const zonedEndDate = toZonedTime(end, this.timezone);
471
- let startDate;
472
- let endDate;
473
- const isCurrentMarketDay = this.isMarketDay(zonedEndDate);
474
- const isWithinHours = this.isWithinMarketHours(zonedEndDate);
475
- const isBeforeHours = this.isBeforeMarketHours(zonedEndDate);
476
- // First determine the end date based on current market conditions
477
- if (isCurrentMarketDay) {
478
- if (isBeforeHours) {
479
- // Case 1: Market day before open hours - use previous full trading day
480
- const lastMarketDay = this.getLastMarketDay(zonedEndDate);
481
- const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
482
- endDate = dayEnd;
483
- }
484
- else if (isWithinHours) {
485
- // Case 2: Market day during hours - use current time
486
- endDate = zonedEndDate;
487
- }
488
- else {
489
- // Case 3: Market day after close - use today's close
490
- const { end: dayEnd } = this.getDayBoundaries(zonedEndDate);
491
- endDate = dayEnd;
492
- }
493
- }
494
- else {
495
- // Case 4: Not a market day - use previous market day's close
496
- const lastMarketDay = this.getLastMarketDay(zonedEndDate);
497
- const { end: dayEnd } = this.getDayBoundaries(lastMarketDay);
498
- endDate = dayEnd;
499
- }
500
- // Now calculate the start date based on the period
501
- const periodStartDate = this.calculatePeriodStartDate(endDate, period);
502
- const { start: dayStart } = this.getDayBoundaries(periodStartDate);
503
- startDate = dayStart;
504
- // Convert boundaries back to UTC for final output
505
- const utcStart = fromZonedTime(startDate, this.timezone);
506
- const utcEnd = fromZonedTime(endDate, this.timezone);
507
- // Ensure start is not after end
508
- if (isBefore(utcEnd, utcStart)) {
509
- throw new Error('Start date cannot be after end date');
510
- }
511
- return {
512
- start: this.formatDate(utcStart, outputFormat),
513
- end: this.formatDate(utcEnd, outputFormat),
514
- };
515
- }
516
- getMarketOpenClose(options = {}) {
517
- const { date = new Date() } = options;
518
- const zonedDate = toZonedTime(date, this.timezone);
519
- // Check if market is closed for the day
520
- if (this.isWeekend(zonedDate) || this.isHoliday(zonedDate)) {
521
- return {
522
- marketOpen: false,
523
- open: null,
524
- close: null,
525
- openExt: null,
526
- closeExt: null,
527
- };
528
- }
529
- const dayStart = startOfDay(zonedDate);
530
- const regularOpenTime = MARKET_TIMES.REGULAR.START;
531
- let regularCloseTime = MARKET_TIMES.REGULAR.END;
532
- const extendedOpenTime = MARKET_TIMES.EXTENDED.START;
533
- let extendedCloseTime = MARKET_TIMES.EXTENDED.END;
534
- // Check for early close
535
- const isEarlyClose = this.isEarlyCloseDay(zonedDate);
536
- if (isEarlyClose) {
537
- const earlyCloseMinutes = this.getEarlyCloseTime(zonedDate);
538
- if (earlyCloseMinutes !== null) {
539
- // For regular hours, use the early close time
540
- regularCloseTime = {
541
- HOUR: Math.floor(earlyCloseMinutes / 60),
542
- MINUTE: earlyCloseMinutes % 60,
543
- MINUTES: earlyCloseMinutes,
544
- };
545
- // For extended hours on early close days, close at 5:00 PM
546
- extendedCloseTime = {
547
- HOUR: 17,
548
- MINUTE: 0,
549
- MINUTES: 1020,
550
- };
551
- }
552
- }
553
- const open = fromZonedTime(set(dayStart, { hours: regularOpenTime.HOUR, minutes: regularOpenTime.MINUTE }), this.timezone);
554
- const close = fromZonedTime(set(dayStart, { hours: regularCloseTime.HOUR, minutes: regularCloseTime.MINUTE }), this.timezone);
555
- const openExt = fromZonedTime(set(dayStart, { hours: extendedOpenTime.HOUR, minutes: extendedOpenTime.MINUTE }), this.timezone);
556
- const closeExt = fromZonedTime(set(dayStart, { hours: extendedCloseTime.HOUR, minutes: extendedCloseTime.MINUTE }), this.timezone);
557
990
  return {
558
- marketOpen: true,
559
- open,
560
- close,
561
- openExt,
562
- closeExt,
991
+ start: fromZonedTime(start, this.timezone),
992
+ end: fromZonedTime(end, this.timezone),
563
993
  };
564
994
  }
565
995
  }
996
+ const marketTimeCalculator = new MarketTimeCalculator();
997
+ const timeFormatter = new TimeFormatter();
566
998
  /**
567
- * Gets the last full trading date
568
- * @param {Date} [currentDate] - The current date (defaults to now)
569
- * @returns {Object} The last full trading date
570
- * @property {Date} date - The date object
571
- * @property {string} YYYYMMDD - The date in YYYY-MM-DD format
999
+ * Get the last full trading date
572
1000
  */
573
1001
  function getLastFullTradingDate(currentDate = new Date()) {
574
- const util = new MarketTimeUtil();
575
- const date = util.getLastFullTradingDate(currentDate);
576
- // Format the date in NY timezone to ensure consistency
1002
+ const date = marketTimeCalculator.getLastFullTradingDate(currentDate);
577
1003
  return {
578
1004
  date,
579
- YYYYMMDD: formatInTimeZone(date, MARKET_TIMES.TIMEZONE, 'yyyy-MM-dd'),
1005
+ YYYYMMDD: timeFormatter.getTradingDate(date),
580
1006
  };
581
1007
  }
582
1008
 
583
- function getDefaultExportFromCjs (x) {
584
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
585
- }
586
-
587
1009
  var bufferUtil = {exports: {}};
588
1010
 
589
1011
  var constants;
@@ -820,7 +1242,7 @@ function requirePermessageDeflate () {
820
1242
  if (hasRequiredPermessageDeflate) return permessageDeflate;
821
1243
  hasRequiredPermessageDeflate = 1;
822
1244
 
823
- const zlib = require$$0;
1245
+ const zlib = require$$0$1;
824
1246
 
825
1247
  const bufferUtil = requireBufferUtil();
826
1248
  const Limiter = requireLimiter();
@@ -1357,7 +1779,7 @@ function requireValidation () {
1357
1779
  if (hasRequiredValidation) return validation.exports;
1358
1780
  hasRequiredValidation = 1;
1359
1781
 
1360
- const { isUtf8 } = require$$0$1;
1782
+ const { isUtf8 } = require$$0$2;
1361
1783
 
1362
1784
  const { hasBlob } = requireConstants();
1363
1785
 
@@ -1517,7 +1939,7 @@ function requireReceiver () {
1517
1939
  if (hasRequiredReceiver) return receiver;
1518
1940
  hasRequiredReceiver = 1;
1519
1941
 
1520
- const { Writable } = require$$0$2;
1942
+ const { Writable } = require$$0$3;
1521
1943
 
1522
1944
  const PerMessageDeflate = requirePermessageDeflate();
1523
1945
  const {
@@ -2233,8 +2655,8 @@ function requireSender () {
2233
2655
  if (hasRequiredSender) return sender;
2234
2656
  hasRequiredSender = 1;
2235
2657
 
2236
- const { Duplex } = require$$0$2;
2237
- const { randomFillSync } = require$$1;
2658
+ const { Duplex } = require$$0$3;
2659
+ const { randomFillSync } = require$$3;
2238
2660
 
2239
2661
  const PerMessageDeflate = requirePermessageDeflate();
2240
2662
  const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
@@ -3354,13 +3776,13 @@ function requireWebsocket () {
3354
3776
  if (hasRequiredWebsocket) return websocket;
3355
3777
  hasRequiredWebsocket = 1;
3356
3778
 
3357
- const EventEmitter = require$$0$3;
3779
+ const EventEmitter = require$$0$4;
3358
3780
  const https = require$$1$1;
3359
- const http = require$$2;
3360
- const net = require$$3;
3361
- const tls = require$$4;
3362
- const { randomBytes, createHash } = require$$1;
3363
- const { Duplex, Readable } = require$$0$2;
3781
+ const http = require$$2$1;
3782
+ const net = require$$3$1;
3783
+ const tls = require$$4$1;
3784
+ const { randomBytes, createHash } = require$$3;
3785
+ const { Duplex, Readable } = require$$0$3;
3364
3786
  const { URL } = require$$7;
3365
3787
 
3366
3788
  const PerMessageDeflate = requirePermessageDeflate();
@@ -4751,7 +5173,7 @@ function requireStream () {
4751
5173
  hasRequiredStream = 1;
4752
5174
 
4753
5175
  requireWebsocket();
4754
- const { Duplex } = require$$0$2;
5176
+ const { Duplex } = require$$0$3;
4755
5177
 
4756
5178
  /**
4757
5179
  * Emits the `'close'` event on a stream.
@@ -4999,10 +5421,10 @@ function requireWebsocketServer () {
4999
5421
  if (hasRequiredWebsocketServer) return websocketServer;
5000
5422
  hasRequiredWebsocketServer = 1;
5001
5423
 
5002
- const EventEmitter = require$$0$3;
5003
- const http = require$$2;
5004
- const { Duplex } = require$$0$2;
5005
- const { createHash } = require$$1;
5424
+ const EventEmitter = require$$0$4;
5425
+ const http = require$$2$1;
5426
+ const { Duplex } = require$$0$3;
5427
+ const { createHash } = require$$3;
5006
5428
 
5007
5429
  const extension = requireExtension();
5008
5430
  const PerMessageDeflate = requirePermessageDeflate();
@@ -5573,22 +5995,16 @@ class AlpacaMarketDataAPI extends EventEmitter {
5573
5995
  optionWs = null;
5574
5996
  stockSubscriptions = { trades: [], quotes: [], bars: [] };
5575
5997
  optionSubscriptions = { trades: [], quotes: [], bars: [] };
5576
- apiKey;
5577
- secretKey;
5578
- accountType;
5579
5998
  setMode(mode = 'production') {
5580
- if (mode === 'sandbox') {
5581
- // sandbox mode
5999
+ if (mode === 'sandbox') { // sandbox mode
5582
6000
  this.stockStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v2/sip';
5583
6001
  this.optionStreamUrl = 'wss://stream.data.sandbox.alpaca.markets/v1beta3/options';
5584
6002
  }
5585
- else if (mode === 'test') {
5586
- // test mode, can only use ticker FAKEPACA
6003
+ else if (mode === 'test') { // test mode, can only use ticker FAKEPACA
5587
6004
  this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/test';
5588
6005
  this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options'; // there's no test mode for options
5589
6006
  }
5590
- else {
5591
- // production
6007
+ else { // production
5592
6008
  this.stockStreamUrl = 'wss://stream.data.alpaca.markets/v2/sip';
5593
6009
  this.optionStreamUrl = 'wss://stream.data.alpaca.markets/v1beta3/options';
5594
6010
  }
@@ -5604,30 +6020,24 @@ class AlpacaMarketDataAPI extends EventEmitter {
5604
6020
  return 'production';
5605
6021
  }
5606
6022
  }
5607
- constructor(config) {
6023
+ constructor() {
5608
6024
  super();
5609
- this.apiKey = config.apiKey;
5610
- this.secretKey = config.secretKey;
5611
- this.accountType = config.accountType || 'LIVE';
5612
6025
  this.dataURL = 'https://data.alpaca.markets/v2';
5613
6026
  this.apiURL =
5614
- this.accountType === 'PAPER'
6027
+ process.env.ALPACA_ACCOUNT_TYPE === 'PAPER'
5615
6028
  ? 'https://paper-api.alpaca.markets/v2'
5616
6029
  : 'https://api.alpaca.markets/v2'; // used by some, e.g. getAssets
5617
6030
  this.v1beta1url = 'https://data.alpaca.markets/v1beta1'; // used for options endpoints
5618
6031
  this.setMode('production'); // sets stockStreamUrl and optionStreamUrl
5619
6032
  this.headers = {
5620
- 'APCA-API-KEY-ID': this.apiKey,
5621
- 'APCA-API-SECRET-KEY': this.secretKey,
6033
+ 'APCA-API-KEY-ID': process.env.ALPACA_API_KEY,
6034
+ 'APCA-API-SECRET-KEY': process.env.ALPACA_SECRET_KEY,
5622
6035
  'Content-Type': 'application/json',
5623
6036
  };
5624
6037
  }
5625
- static getInstance(config) {
6038
+ static getInstance() {
5626
6039
  if (!AlpacaMarketDataAPI.instance) {
5627
- if (!config) {
5628
- throw new Error('AlpacaMarketDataAPI config is required for first initialization');
5629
- }
5630
- AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI(config);
6040
+ AlpacaMarketDataAPI.instance = new AlpacaMarketDataAPI();
5631
6041
  }
5632
6042
  return AlpacaMarketDataAPI.instance;
5633
6043
  }
@@ -5650,8 +6060,8 @@ class AlpacaMarketDataAPI extends EventEmitter {
5650
6060
  log$1(`${streamType} stream connected`, { type: 'info' });
5651
6061
  const authMessage = {
5652
6062
  action: 'auth',
5653
- key: this.apiKey,
5654
- secret: this.secretKey,
6063
+ key: process.env.ALPACA_API_KEY,
6064
+ secret: process.env.ALPACA_SECRET_KEY,
5655
6065
  };
5656
6066
  ws.send(JSON.stringify(authMessage));
5657
6067
  });
@@ -5742,7 +6152,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
5742
6152
  const currentSubscriptions = streamType === 'stock' ? this.stockSubscriptions : this.optionSubscriptions;
5743
6153
  Object.entries(subscriptions).forEach(([key, value]) => {
5744
6154
  if (value) {
5745
- currentSubscriptions[key] = (currentSubscriptions[key] || []).filter((s) => !value.includes(s));
6155
+ currentSubscriptions[key] = (currentSubscriptions[key] || []).filter(s => !value.includes(s));
5746
6156
  }
5747
6157
  });
5748
6158
  const unsubMessage = {
@@ -5805,11 +6215,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
5805
6215
  let pageCount = 0;
5806
6216
  let currency = '';
5807
6217
  // Initialize bar arrays for each symbol
5808
- symbols.forEach((symbol) => {
6218
+ symbols.forEach(symbol => {
5809
6219
  allBars[symbol] = [];
5810
6220
  });
5811
6221
  log$1(`Starting historical bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
5812
- type: 'info',
6222
+ type: 'info'
5813
6223
  });
5814
6224
  while (hasMorePages) {
5815
6225
  pageCount++;
@@ -5837,7 +6247,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
5837
6247
  allBars[symbol] = [...allBars[symbol], ...bars];
5838
6248
  pageBarsCount += bars.length;
5839
6249
  // Track date range for this page
5840
- bars.forEach((bar) => {
6250
+ bars.forEach(bar => {
5841
6251
  const barDate = new Date(bar.t);
5842
6252
  if (!earliestTimestamp || barDate < earliestTimestamp) {
5843
6253
  earliestTimestamp = barDate;
@@ -5856,7 +6266,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
5856
6266
  ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
5857
6267
  : 'unknown range';
5858
6268
  log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
5859
- type: 'info',
6269
+ type: 'info'
5860
6270
  });
5861
6271
  // Prevent infinite loops
5862
6272
  if (pageCount > 1000) {
@@ -5865,11 +6275,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
5865
6275
  }
5866
6276
  }
5867
6277
  // Final summary
5868
- const symbolCounts = Object.entries(allBars)
5869
- .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
5870
- .join(', ');
6278
+ const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
5871
6279
  log$1(`Historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
5872
- type: 'info',
6280
+ type: 'info'
5873
6281
  });
5874
6282
  return {
5875
6283
  bars: allBars,
@@ -6137,11 +6545,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6137
6545
  let totalBarsCount = 0;
6138
6546
  let pageCount = 0;
6139
6547
  // Initialize bar arrays for each symbol
6140
- symbols.forEach((symbol) => {
6548
+ symbols.forEach(symbol => {
6141
6549
  allBars[symbol] = [];
6142
6550
  });
6143
6551
  log$1(`Starting historical options bars fetch for ${symbolsStr} (${params.timeframe}, ${params.start || 'no start'} to ${params.end || 'no end'})`, {
6144
- type: 'info',
6552
+ type: 'info'
6145
6553
  });
6146
6554
  while (hasMorePages) {
6147
6555
  pageCount++;
@@ -6163,7 +6571,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6163
6571
  allBars[symbol] = [...allBars[symbol], ...bars];
6164
6572
  pageBarsCount += bars.length;
6165
6573
  // Track date range for this page
6166
- bars.forEach((bar) => {
6574
+ bars.forEach(bar => {
6167
6575
  const barDate = new Date(bar.t);
6168
6576
  if (!earliestTimestamp || barDate < earliestTimestamp) {
6169
6577
  earliestTimestamp = barDate;
@@ -6182,7 +6590,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6182
6590
  ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
6183
6591
  : 'unknown range';
6184
6592
  log$1(`Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
6185
- type: 'info',
6593
+ type: 'info'
6186
6594
  });
6187
6595
  // Prevent infinite loops
6188
6596
  if (pageCount > 1000) {
@@ -6191,11 +6599,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
6191
6599
  }
6192
6600
  }
6193
6601
  // Final summary
6194
- const symbolCounts = Object.entries(allBars)
6195
- .map(([symbol, bars]) => `${symbol}: ${bars.length}`)
6196
- .join(', ');
6602
+ const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(', ');
6197
6603
  log$1(`Historical options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`, {
6198
- type: 'info',
6604
+ type: 'info'
6199
6605
  });
6200
6606
  return {
6201
6607
  bars: allBars,
@@ -6219,11 +6625,11 @@ class AlpacaMarketDataAPI extends EventEmitter {
6219
6625
  let totalTradesCount = 0;
6220
6626
  let pageCount = 0;
6221
6627
  // Initialize trades arrays for each symbol
6222
- symbols.forEach((symbol) => {
6628
+ symbols.forEach(symbol => {
6223
6629
  allTrades[symbol] = [];
6224
6630
  });
6225
6631
  log$1(`Starting historical options trades fetch for ${symbolsStr} (${params.start || 'no start'} to ${params.end || 'no end'})`, {
6226
- type: 'info',
6632
+ type: 'info'
6227
6633
  });
6228
6634
  while (hasMorePages) {
6229
6635
  pageCount++;
@@ -6245,7 +6651,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6245
6651
  allTrades[symbol] = [...allTrades[symbol], ...trades];
6246
6652
  pageTradesCount += trades.length;
6247
6653
  // Track date range for this page
6248
- trades.forEach((trade) => {
6654
+ trades.forEach(trade => {
6249
6655
  const tradeDate = new Date(trade.t);
6250
6656
  if (!earliestTimestamp || tradeDate < earliestTimestamp) {
6251
6657
  earliestTimestamp = tradeDate;
@@ -6264,7 +6670,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6264
6670
  ? `${earliestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })} to ${latestTimestamp.toLocaleDateString('en-US', { timeZone: 'America/New_York' })}`
6265
6671
  : 'unknown range';
6266
6672
  log$1(`Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbolsStr}, date range: ${dateRangeStr}${hasMorePages ? ', more pages available' : ', complete'}`, {
6267
- type: 'info',
6673
+ type: 'info'
6268
6674
  });
6269
6675
  // Prevent infinite loops
6270
6676
  if (pageCount > 1000) {
@@ -6273,11 +6679,9 @@ class AlpacaMarketDataAPI extends EventEmitter {
6273
6679
  }
6274
6680
  }
6275
6681
  // Final summary
6276
- const symbolCounts = Object.entries(allTrades)
6277
- .map(([symbol, trades]) => `${symbol}: ${trades.length}`)
6278
- .join(', ');
6682
+ const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(', ');
6279
6683
  log$1(`Historical options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`, {
6280
- type: 'info',
6684
+ type: 'info'
6281
6685
  });
6282
6686
  return {
6283
6687
  trades: allTrades,
@@ -6422,9 +6826,7 @@ class AlpacaMarketDataAPI extends EventEmitter {
6422
6826
  ...(symbol && { symbols: symbol }),
6423
6827
  ...(mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() }),
6424
6828
  ...(mergedParams.sort && { sort: mergedParams.sort }),
6425
- ...(mergedParams.include_content !== undefined
6426
- ? { include_content: mergedParams.include_content.toString() }
6427
- : {}),
6829
+ ...(mergedParams.include_content !== undefined ? { include_content: mergedParams.include_content.toString() } : {}),
6428
6830
  ...(pageToken && { page_token: pageToken }),
6429
6831
  });
6430
6832
  const url = `${this.v1beta1url}/news?${queryParams}`;
@@ -6468,316 +6870,18 @@ class AlpacaMarketDataAPI extends EventEmitter {
6468
6870
  return newsArticles;
6469
6871
  }
6470
6872
  }
6471
-
6472
- // format-tools.ts
6473
- /**
6474
- * Capitalizes the first letter of a string
6475
- * @param {string} str - The string to capitalize
6476
- * @returns {string} The capitalized string, or original value if not a string
6477
- * @example
6478
- * capitalize('hello') // 'Hello'
6479
- * capitalize(123) // 123
6480
- */
6481
- /**
6482
- * Formats a number as US currency
6483
- * @param {number} value - The number to format
6484
- * @returns {string} The formatted currency string (e.g. '$1,234.56')
6485
- * @example
6486
- * formatCurrency(1234.56) // '$1,234.56'
6487
- * formatCurrency(NaN) // '$0.00'
6488
- */
6489
- function formatCurrency(value) {
6490
- if (isNaN(value)) {
6491
- return '$0.00';
6492
- }
6493
- return new Intl.NumberFormat('en-US', {
6494
- style: 'currency',
6495
- currency: 'USD',
6496
- }).format(value);
6497
- }
6498
- /**
6499
- * Formats a number with commas
6500
- * @param {number} value - The number to format
6501
- * @returns {string} The formatted number string (e.g. '1,234.56')
6502
- * @example
6503
- * formatNumber(1234.56) // '1,234.56'
6504
- * formatNumber(NaN) // '0'
6505
- */
6506
- function formatNumber(value) {
6507
- if (isNaN(value)) {
6508
- return '0';
6509
- }
6510
- return new Intl.NumberFormat('en-US').format(value);
6511
- }
6873
+ // Export the singleton instance
6874
+ AlpacaMarketDataAPI.getInstance();
6512
6875
 
6513
6876
  const log = (message) => {
6514
6877
  console.log(`[${new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })}] ${message}`);
6515
6878
  };
6516
- // async function testCreateEquitiesTrade() {
6517
- // try {
6518
- // log('Starting createEquitiesTrade test...');
6519
- // // Create credentials using environment variables
6520
- // const credentials: AlpacaCredentials = {
6521
- // accountName: 'test',
6522
- // apiKey: process.env.ALPACA_API_KEY || '',
6523
- // apiSecret: process.env.ALPACA_SECRET_KEY || '',
6524
- // type: 'PAPER',
6525
- // orderType: 'market',
6526
- // engine: 'quant'
6527
- // };
6528
- // if (!credentials.apiKey || !credentials.apiSecret) {
6529
- // throw new Error('ALPACA_API_KEY and ALPACA_SECRET_KEY environment variables must be set');
6530
- // }
6531
- // // Create a new instance of the trading API
6532
- // const alpacaAPI = createAlpacaTradingAPI(credentials);
6533
- // // Get current account details
6534
- // const accountDetails = await alpacaAPI.getAccountDetails();
6535
- // log(`Account equity: $${parseFloat(accountDetails.equity).toFixed(2)}`);
6536
- // log(`Buying power: $${parseFloat(accountDetails.buying_power).toFixed(2)}`);
6537
- // const testSymbols = ['AAPL', 'MSFT', 'GOOGL', 'TSLA', 'NVDA', 'META', 'AMZN']; // Multiple symbols to avoid wash trade detection
6538
- // const referencePrice = 150.00; // Mock reference price for testing
6539
- // log('=== Testing createEquitiesTrade function ===');
6540
- // // Test 1: Simple market order (long position)
6541
- // log('\n1. Testing simple market order (long)...');
6542
- // try {
6543
- // const order1 = await alpacaAPI.createEquitiesTrade(
6544
- // { symbol: testSymbols[0], qty: 1, side: 'buy' }
6545
- // );
6546
- // log(`✅ Created simple market buy order: ${order1.id}`);
6547
- // } catch (error) {
6548
- // log(`❌ Failed to create simple market order: ${error}`);
6549
- // }
6550
- // // Test 2: Simple market order (short position)
6551
- // log('\n2. Testing simple market order (short)...');
6552
- // try {
6553
- // const order2 = await alpacaAPI.createEquitiesTrade(
6554
- // { symbol: testSymbols[1], qty: 1, side: 'sell' }
6555
- // );
6556
- // log(`✅ Created simple market sell order: ${order2.id}`);
6557
- // } catch (error) {
6558
- // log(`❌ Failed to create simple market sell order: ${error}`);
6559
- // }
6560
- // // Test 3: Limit order with percentage-based stop loss (long)
6561
- // log('\n3. Testing limit order with percentage-based stop loss (long)...');
6562
- // try {
6563
- // const order3 = await alpacaAPI.createEquitiesTrade(
6564
- // { symbol: testSymbols[2], qty: 1, side: 'buy', referencePrice },
6565
- // {
6566
- // type: 'limit',
6567
- // limitPrice: 149.50,
6568
- // useStopLoss: true,
6569
- // stopPercent100: 3.0 // 3% stop loss
6570
- // }
6571
- // );
6572
- // log(`✅ Created limit buy order with stop loss: ${order3.id}`);
6573
- // } catch (error) {
6574
- // log(`❌ Failed to create limit order with stop loss: ${error}`);
6575
- // }
6576
- // // Test 4: Limit order with percentage-based take profit (short)
6577
- // log('\n4. Testing limit order with percentage-based take profit (short)...');
6578
- // try {
6579
- // const order4 = await alpacaAPI.createEquitiesTrade(
6580
- // { symbol: testSymbols[3], qty: 1, side: 'sell', referencePrice },
6581
- // {
6582
- // type: 'limit',
6583
- // limitPrice: 150.50,
6584
- // useTakeProfit: true,
6585
- // takeProfitPercent100: 2.5 // 2.5% take profit
6586
- // }
6587
- // );
6588
- // log(`✅ Created limit sell order with take profit: ${order4.id}`);
6589
- // } catch (error) {
6590
- // log(`❌ Failed to create limit order with take profit: ${error}`);
6591
- // }
6592
- // // Test 5: Bracket order with both stop loss and take profit (long)
6593
- // log('\n5. Testing bracket order with both stop loss and take profit (long)...');
6594
- // try {
6595
- // const order5 = await alpacaAPI.createEquitiesTrade(
6596
- // { symbol: testSymbols[4], qty: 1, side: 'buy', referencePrice },
6597
- // {
6598
- // type: 'limit',
6599
- // limitPrice: 149.00,
6600
- // useStopLoss: true,
6601
- // stopPercent100: 4.0, // 4% stop loss
6602
- // useTakeProfit: true,
6603
- // takeProfitPercent100: 6.0 // 6% take profit
6604
- // }
6605
- // );
6606
- // log(`✅ Created bracket order (long): ${order5.id}`);
6607
- // } catch (error) {
6608
- // log(`❌ Failed to create bracket order: ${error}`);
6609
- // }
6610
- // // Test 6: Bracket order with fixed prices (short)
6611
- // log('\n6. Testing bracket order with fixed prices (short)...');
6612
- // try {
6613
- // const order6 = await alpacaAPI.createEquitiesTrade(
6614
- // { symbol: testSymbols[5], qty: 1, side: 'sell' },
6615
- // {
6616
- // type: 'limit',
6617
- // limitPrice: 151.00,
6618
- // useStopLoss: true,
6619
- // stopPrice: 155.00, // Fixed stop price
6620
- // useTakeProfit: true,
6621
- // takeProfitPrice: 145.00 // Fixed take profit price
6622
- // }
6623
- // );
6624
- // log(`✅ Created bracket order with fixed prices (short): ${order6.id}`);
6625
- // } catch (error) {
6626
- // log(`❌ Failed to create bracket order with fixed prices: ${error}`);
6627
- // }
6628
- // // Test 7: Extended hours limit order
6629
- // log('\n7. Testing extended hours limit order...');
6630
- // try {
6631
- // const order7 = await alpacaAPI.createEquitiesTrade(
6632
- // { symbol: testSymbols[6], qty: 1, side: 'buy' },
6633
- // {
6634
- // type: 'limit',
6635
- // limitPrice: 148.00,
6636
- // extendedHours: true
6637
- // }
6638
- // );
6639
- // log(`✅ Created extended hours limit order: ${order7.id}`);
6640
- // } catch (error) {
6641
- // log(`❌ Failed to create extended hours order: ${error}`);
6642
- // }
6643
- // // Test 8: Error condition - market order with extended hours (should fail)
6644
- // log('\n8. Testing error condition - market order with extended hours (should fail)...');
6645
- // try {
6646
- // await alpacaAPI.createEquitiesTrade(
6647
- // { symbol: testSymbols[0], qty: 1, side: 'buy' },
6648
- // {
6649
- // type: 'market',
6650
- // extendedHours: true // This should trigger an error
6651
- // }
6652
- // );
6653
- // log(`❌ Unexpected success - should have failed!`);
6654
- // } catch (error) {
6655
- // log(`✅ Correctly caught error: ${error instanceof Error ? error.message : error}`);
6656
- // }
6657
- // // Test 9: Error condition - missing referencePrice for percentage (should fail)
6658
- // log('\n9. Testing error condition - missing referencePrice for percentage (should fail)...');
6659
- // try {
6660
- // await alpacaAPI.createEquitiesTrade(
6661
- // { symbol: testSymbols[0], qty: 1, side: 'buy' }, // No referencePrice
6662
- // {
6663
- // useStopLoss: true,
6664
- // stopPercent100: 3.0 // Requires referencePrice
6665
- // }
6666
- // );
6667
- // log(`❌ Unexpected success - should have failed!`);
6668
- // } catch (error) {
6669
- // log(`✅ Correctly caught error: ${error instanceof Error ? error.message : error}`);
6670
- // }
6671
- // // Wait a moment for all orders to process
6672
- // await new Promise(resolve => setTimeout(resolve, 2000));
6673
- // // Get all orders to see what was created
6674
- // log('\n=== Checking created orders ===');
6675
- // const orders = await alpacaAPI.getOrders({ status: 'open', limit: 20 });
6676
- // log(`Found ${orders.length} open orders`);
6677
- // orders.forEach((order, index) => {
6678
- // log(`Order ${index + 1}: ${order.order_class} ${order.type} ${order.side} ${order.qty} ${order.symbol} @ $${order.limit_price || 'market'}`);
6679
- // if (order.legs && order.legs.length > 0) {
6680
- // log(` Stop Loss: $${order.legs.find(leg => leg.type === 'stop')?.stop_price || 'N/A'}`);
6681
- // log(` Take Profit: $${order.legs.find(leg => leg.type === 'limit')?.limit_price || 'N/A'}`);
6682
- // }
6683
- // });
6684
- // // Cancel all orders to clean up
6685
- // log('\n=== Cleaning up - canceling all orders ===');
6686
- // await alpacaAPI.cancelAllOrders();
6687
- // log('✅ All orders canceled');
6688
- // log('\n=== Test completed successfully! ===');
6689
- // } catch (error) {
6690
- // log(`❌ Error in createEquitiesTrade test: ${error instanceof Error ? error.message : 'Unknown error'}`);
6691
- // process.exit(1);
6692
- // }
6693
- // }
6694
- // async function testAlpacaWebSocket() {
6695
- // try {
6696
- // log('Starting Alpaca WebSocket test...');
6697
- // // Create credentials using environment variables
6698
- // const credentials: AlpacaCredentials = {
6699
- // accountName: 'test',
6700
- // apiKey: process.env.ALPACA_API_KEY || '',
6701
- // apiSecret: process.env.ALPACA_SECRET_KEY || '',
6702
- // type: 'PAPER',
6703
- // orderType: 'market',
6704
- // engine: 'quant'
6705
- // };
6706
- // if (!credentials.apiKey || !credentials.apiSecret) {
6707
- // throw new Error('ALPACA_API_KEY and ALPACA_SECRET_KEY environment variables must be set');
6708
- // }
6709
- // // Create a new instance of the trading API
6710
- // const alpacaAPI = createAlpacaTradingAPI(credentials); // type AlpacaCredentials
6711
- // // Set up trade update callback
6712
- // alpacaAPI.onTradeUpdate((update: TradeUpdate) => {
6713
- // log(`Received trade update: event ${update.event} for an order to ${update.order.side} ${update.order.qty} of ${update.order.symbol}`);
6714
- // });
6715
- // // Connect to WebSocket
6716
- // alpacaAPI.connectWebsocket(); // necessary to connect to the WebSocket
6717
- // // create an order
6718
- // const order = await alpacaAPI.createMarketOrder('AAPL', 1, 'buy', 'buy_to_open');
6719
- // // cancel the order
6720
- // await alpacaAPI.cancelAllOrders();
6721
- // // Keep the process running
6722
- // log('WebSocket connected and listening for trade updates...');
6723
- // log('Press Ctrl+C to exit');
6724
- // // Keep the process running
6725
- // await new Promise(() => {});
6726
- // } catch (error) {
6727
- // log(`Error in WebSocket test: ${error instanceof Error ? error.message : 'Unknown error'}`);
6728
- // process.exit(1);
6729
- // }
6730
- // }
6731
- // // Run the test
6732
- // testAlpacaWebSocket();
6733
- // Run the createEquitiesTrade test
6734
- //testCreateEquitiesTrade();
6735
- // testing retrieving pre-market data (just 9:00am to 9:30am on 1 july 2025 for SPY) using the market data api
6736
- async function testPreMarketData() {
6737
- try {
6738
- log('Starting pre-market data test for SPY (9:00am-9:30am, July 1, 2025)...');
6739
- // Create market data API config (you would typically get these from environment variables)
6740
- const marketDataConfig = {
6741
- apiKey: 'your-api-key',
6742
- secretKey: 'your-secret-key',
6743
- accountType: 'PAPER'
6744
- };
6745
- const marketDataAPI = AlpacaMarketDataAPI.getInstance(marketDataConfig);
6746
- // Set up the time range in America/New_York, convert to UTC ISO strings
6747
- const symbol = 'SPY';
6748
- const nyTimeZone = 'America/New_York';
6749
- // 9:00am and 9:30am in NY time
6750
- const startNY = new Date('2025-07-01T09:00:00-04:00');
6751
- const endNY = new Date('2025-07-01T09:30:00-04:00');
6752
- const startUTC = startNY.toISOString();
6753
- const endUTC = endNY.toISOString();
6754
- const barsResponse = await marketDataAPI.getHistoricalBars({
6755
- symbols: [symbol],
6756
- timeframe: '1Min',
6757
- start: startUTC,
6758
- end: endUTC,
6759
- limit: 1000,
6760
- });
6761
- const bars = barsResponse.bars[symbol] || [];
6762
- log(`Fetched ${bars.length} 1-min bars for SPY from 9:00am to 9:30am (NY) on 2025-07-01.`);
6763
- if (bars.length === 0) {
6764
- log('No pre-market bars returned.');
6765
- return;
6766
- }
6767
- // Print each bar
6768
- bars.forEach((bar, i) => {
6769
- const barTime = new Date(bar.t).toLocaleString('en-US', { timeZone: nyTimeZone });
6770
- log(`Bar ${i + 1}: ${barTime} | O: ${formatCurrency(bar.o)} H: ${formatCurrency(bar.h)} L: ${formatCurrency(bar.l)} C: ${formatCurrency(bar.c)} V: ${formatNumber(bar.v)} VWAP: ${formatCurrency(bar.vw)} N: ${bar.n}`);
6771
- });
6772
- // Print summary
6773
- const summary = AlpacaMarketDataAPI.analyzeBars(bars);
6774
- if (summary)
6775
- log(`Summary: ${summary}`);
6776
- log('Pre-market data test complete.');
6777
- }
6778
- catch (error) {
6779
- log(`❌ Error in testPreMarketData: ${error instanceof Error ? error.message : error}`);
6780
- }
6781
- }
6782
- testPreMarketData();
6879
+ const marketDataAPI = AlpacaMarketDataAPI.getInstance();
6880
+ const response = await marketDataAPI.getHistoricalBars({
6881
+ symbols: ['AAPL'],
6882
+ timeframe: '1Min',
6883
+ start: '2025-07-08T09:30:00-04:00',
6884
+ end: '2025-07-08T10:30:00-04:00',
6885
+ });
6886
+ log(`Received ${response.bars.length} response: ${JSON.stringify(response.bars)}`);
6783
6887
  //# sourceMappingURL=test.js.map