@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.
- package/README.md +95 -3
- package/dist/index-frontend.cjs +16027 -0
- package/dist/index-frontend.cjs.map +1 -0
- package/dist/index-frontend.mjs +16023 -0
- package/dist/index-frontend.mjs.map +1 -0
- package/dist/index.cjs +1188 -921
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1190 -921
- package/dist/index.mjs.map +1 -1
- package/dist/package.json +8 -2
- package/dist/test.js +835 -731
- package/dist/test.js.map +1 -1
- package/dist/types/alpaca-market-data-api.d.ts +3 -15
- package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
- package/dist/types/alpaca-trading-api.d.ts +3 -6
- package/dist/types/alpaca-trading-api.d.ts.map +1 -1
- package/dist/types/index-frontend.d.ts +15 -0
- package/dist/types/index-frontend.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -28
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/market-time.d.ts +187 -117
- package/dist/types/market-time.d.ts.map +1 -1
- package/dist/types/old-test.d.ts +2 -0
- package/dist/types/old-test.d.ts.map +1 -0
- package/dist/types/testing/market-time-refactor-test.d.ts +1 -0
- package/dist/types/testing/market-time-refactor-test.d.ts.map +1 -0
- package/dist/types-frontend/alpaca-market-data-api.d.ts +372 -0
- package/dist/types-frontend/alpaca-market-data-api.d.ts.map +1 -0
- package/dist/types-frontend/alpaca-trading-api.d.ts +315 -0
- package/dist/types-frontend/alpaca-trading-api.d.ts.map +1 -0
- package/dist/types-frontend/format-tools.d.ts +46 -0
- package/dist/types-frontend/format-tools.d.ts.map +1 -0
- package/dist/types-frontend/index-frontend.d.ts +15 -0
- package/dist/types-frontend/index-frontend.d.ts.map +1 -0
- package/dist/types-frontend/index.d.ts +125 -0
- package/dist/types-frontend/index.d.ts.map +1 -0
- package/dist/types-frontend/json-tools.d.ts +33 -0
- package/dist/types-frontend/json-tools.d.ts.map +1 -0
- package/dist/types-frontend/llm-config.d.ts +36 -0
- package/dist/types-frontend/llm-config.d.ts.map +1 -0
- package/dist/types-frontend/llm-deepseek.d.ts +12 -0
- package/dist/types-frontend/llm-deepseek.d.ts.map +1 -0
- package/dist/types-frontend/llm-images.d.ts +49 -0
- package/dist/types-frontend/llm-images.d.ts.map +1 -0
- package/dist/types-frontend/llm-openai.d.ts +64 -0
- package/dist/types-frontend/llm-openai.d.ts.map +1 -0
- package/dist/types-frontend/llm-utils.d.ts +16 -0
- package/dist/types-frontend/llm-utils.d.ts.map +1 -0
- package/dist/types-frontend/logging.d.ts +12 -0
- package/dist/types-frontend/logging.d.ts.map +1 -0
- package/dist/types-frontend/market-hours.d.ts +24 -0
- package/dist/types-frontend/market-hours.d.ts.map +1 -0
- package/dist/types-frontend/market-time.d.ts +254 -0
- package/dist/types-frontend/market-time.d.ts.map +1 -0
- package/dist/types-frontend/misc-utils.d.ts +49 -0
- package/dist/types-frontend/misc-utils.d.ts.map +1 -0
- package/dist/types-frontend/old-test.d.ts +2 -0
- package/dist/types-frontend/old-test.d.ts.map +1 -0
- package/dist/types-frontend/polygon-indices.d.ts +85 -0
- package/dist/types-frontend/polygon-indices.d.ts.map +1 -0
- package/dist/types-frontend/polygon.d.ts +126 -0
- package/dist/types-frontend/polygon.d.ts.map +1 -0
- package/dist/types-frontend/technical-analysis.d.ts +90 -0
- package/dist/types-frontend/technical-analysis.d.ts.map +1 -0
- package/dist/types-frontend/test.d.ts +2 -0
- package/dist/types-frontend/test.d.ts.map +1 -0
- package/dist/types-frontend/testing/market-time-refactor-test.d.ts +1 -0
- package/dist/types-frontend/testing/market-time-refactor-test.d.ts.map +1 -0
- package/dist/types-frontend/types/alpaca-types.d.ts +962 -0
- package/dist/types-frontend/types/alpaca-types.d.ts.map +1 -0
- package/dist/types-frontend/types/index.d.ts +7 -0
- package/dist/types-frontend/types/index.d.ts.map +1 -0
- package/dist/types-frontend/types/llm-types.d.ts +82 -0
- package/dist/types-frontend/types/llm-types.d.ts.map +1 -0
- package/dist/types-frontend/types/logging-types.d.ts +10 -0
- package/dist/types-frontend/types/logging-types.d.ts.map +1 -0
- package/dist/types-frontend/types/market-time-types.d.ts +59 -0
- package/dist/types-frontend/types/market-time-types.d.ts.map +1 -0
- package/dist/types-frontend/types/polygon-indices-types.d.ts +190 -0
- package/dist/types-frontend/types/polygon-indices-types.d.ts.map +1 -0
- package/dist/types-frontend/types/polygon-types.d.ts +204 -0
- package/dist/types-frontend/types/polygon-types.d.ts.map +1 -0
- package/dist/types-frontend/types/ta-types.d.ts +89 -0
- package/dist/types-frontend/types/ta-types.d.ts.map +1 -0
- package/package.json +8 -2
- package/dist/types/time-utils.d.ts +0 -17
- package/dist/types/time-utils.d.ts.map +0 -1
package/dist/test.js
CHANGED
|
@@ -1,15 +1,538 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import require$$
|
|
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$$
|
|
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$
|
|
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
|
|
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
|
|
674
|
+
const MARKET_CONFIG = {
|
|
156
675
|
TIMEZONE: 'America/New_York',
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
*
|
|
687
|
+
* Service for handling market calendar operations (holidays, early closes, market days)
|
|
162
688
|
*/
|
|
163
|
-
class
|
|
689
|
+
class MarketCalendar {
|
|
164
690
|
timezone;
|
|
165
|
-
|
|
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
|
-
*
|
|
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 =
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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 =
|
|
218
|
-
const
|
|
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
|
|
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 =
|
|
228
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
743
|
+
getNextMarketDay(date) {
|
|
744
|
+
let nextDay = add(date, { days: 1 });
|
|
745
|
+
while (!this.isMarketDay(nextDay)) {
|
|
746
|
+
nextDay = add(nextDay, { days: 1 });
|
|
255
747
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
303
|
-
* @param currentDate - The current date
|
|
304
|
-
* @returns The last trading date
|
|
809
|
+
* Get trading date in YYYY-MM-DD format
|
|
305
810
|
*/
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (
|
|
312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
360
|
-
|
|
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 (
|
|
934
|
+
switch (intradayReporting) {
|
|
370
935
|
case 'extended_hours': {
|
|
371
|
-
start = set(
|
|
372
|
-
hours:
|
|
373
|
-
minutes:
|
|
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(
|
|
378
|
-
hours:
|
|
379
|
-
minutes:
|
|
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(
|
|
387
|
-
end = endOfDay(
|
|
960
|
+
start = startOfDay(zonedDate);
|
|
961
|
+
end = endOfDay(zonedDate);
|
|
388
962
|
break;
|
|
389
963
|
}
|
|
390
964
|
default: {
|
|
391
|
-
// market_hours
|
|
392
|
-
start = set(
|
|
393
|
-
hours:
|
|
394
|
-
minutes:
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
559
|
-
|
|
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
|
-
*
|
|
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
|
|
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:
|
|
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$
|
|
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$
|
|
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$
|
|
2237
|
-
const { randomFillSync } = require$$
|
|
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$
|
|
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$$
|
|
3363
|
-
const { Duplex, Readable } = require$$0$
|
|
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$
|
|
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$
|
|
5003
|
-
const http = require$$2;
|
|
5004
|
-
const { Duplex } = require$$0$
|
|
5005
|
-
const { createHash } = require$$
|
|
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(
|
|
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
|
-
|
|
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':
|
|
5621
|
-
'APCA-API-SECRET-KEY':
|
|
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(
|
|
6038
|
+
static getInstance() {
|
|
5626
6039
|
if (!AlpacaMarketDataAPI.instance) {
|
|
5627
|
-
|
|
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:
|
|
5654
|
-
secret:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
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
|