@discomedia/utils 1.0.77 → 1.0.78

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 (86) hide show
  1. package/dist/index-frontend.cjs +2024 -15270
  2. package/dist/index-frontend.cjs.map +7 -1
  3. package/dist/index-frontend.mjs +1985 -15260
  4. package/dist/index-frontend.mjs.map +7 -1
  5. package/dist/index.cjs +5368 -24387
  6. package/dist/index.cjs.map +7 -1
  7. package/dist/index.mjs +5326 -24374
  8. package/dist/index.mjs.map +7 -1
  9. package/dist/package.json +10 -11
  10. package/dist/test.mjs +1977 -0
  11. package/dist/test.mjs.map +7 -0
  12. package/dist/types/alpaca-market-data-api.d.ts.map +1 -1
  13. package/dist/types/alpaca-trading-api.d.ts.map +1 -1
  14. package/dist/types/index-frontend.d.ts +2 -1
  15. package/dist/types/index-frontend.d.ts.map +1 -1
  16. package/dist/types/index.d.ts +7 -4
  17. package/dist/types/index.d.ts.map +1 -1
  18. package/dist/types/llm-config.d.ts +4 -2
  19. package/dist/types/llm-config.d.ts.map +1 -1
  20. package/dist/types/llm-deepseek.d.ts.map +1 -1
  21. package/dist/types/llm-images.d.ts.map +1 -1
  22. package/dist/types/llm-openai.d.ts +7 -4
  23. package/dist/types/llm-openai.d.ts.map +1 -1
  24. package/dist/types/llm-openrouter.d.ts.map +1 -1
  25. package/dist/types/market-time.d.ts +1 -1
  26. package/dist/types/market-time.d.ts.map +1 -1
  27. package/dist/types/misc-utils.d.ts +1 -1
  28. package/dist/types/misc-utils.d.ts.map +1 -1
  29. package/dist/types/performance-timer.d.ts.map +1 -1
  30. package/dist/types/types/llm-types.d.ts +8 -5
  31. package/dist/types/types/llm-types.d.ts.map +1 -1
  32. package/package.json +10 -11
  33. package/dist/test.js +0 -17500
  34. package/dist/test.js.map +0 -1
  35. package/dist/types-frontend/alpaca-market-data-api.d.ts +0 -451
  36. package/dist/types-frontend/alpaca-market-data-api.d.ts.map +0 -1
  37. package/dist/types-frontend/alpaca-trading-api.d.ts +0 -407
  38. package/dist/types-frontend/alpaca-trading-api.d.ts.map +0 -1
  39. package/dist/types-frontend/format-tools.d.ts +0 -46
  40. package/dist/types-frontend/format-tools.d.ts.map +0 -1
  41. package/dist/types-frontend/index-frontend.d.ts +0 -18
  42. package/dist/types-frontend/index-frontend.d.ts.map +0 -1
  43. package/dist/types-frontend/index.d.ts +0 -59
  44. package/dist/types-frontend/index.d.ts.map +0 -1
  45. package/dist/types-frontend/json-tools.d.ts +0 -31
  46. package/dist/types-frontend/json-tools.d.ts.map +0 -1
  47. package/dist/types-frontend/llm-config.d.ts +0 -51
  48. package/dist/types-frontend/llm-config.d.ts.map +0 -1
  49. package/dist/types-frontend/llm-deepseek.d.ts +0 -12
  50. package/dist/types-frontend/llm-deepseek.d.ts.map +0 -1
  51. package/dist/types-frontend/llm-images.d.ts +0 -52
  52. package/dist/types-frontend/llm-images.d.ts.map +0 -1
  53. package/dist/types-frontend/llm-openai.d.ts +0 -79
  54. package/dist/types-frontend/llm-openai.d.ts.map +0 -1
  55. package/dist/types-frontend/llm-openrouter.d.ts +0 -28
  56. package/dist/types-frontend/llm-openrouter.d.ts.map +0 -1
  57. package/dist/types-frontend/llm-utils.d.ts +0 -16
  58. package/dist/types-frontend/llm-utils.d.ts.map +0 -1
  59. package/dist/types-frontend/logging.d.ts +0 -12
  60. package/dist/types-frontend/logging.d.ts.map +0 -1
  61. package/dist/types-frontend/market-hours.d.ts +0 -24
  62. package/dist/types-frontend/market-hours.d.ts.map +0 -1
  63. package/dist/types-frontend/market-time.d.ts +0 -270
  64. package/dist/types-frontend/market-time.d.ts.map +0 -1
  65. package/dist/types-frontend/misc-utils.d.ts +0 -43
  66. package/dist/types-frontend/misc-utils.d.ts.map +0 -1
  67. package/dist/types-frontend/performance-timer.d.ts +0 -73
  68. package/dist/types-frontend/performance-timer.d.ts.map +0 -1
  69. package/dist/types-frontend/test.d.ts +0 -2
  70. package/dist/types-frontend/test.d.ts.map +0 -1
  71. package/dist/types-frontend/testing/market-time-refactor-test.d.ts +0 -1
  72. package/dist/types-frontend/testing/market-time-refactor-test.d.ts.map +0 -1
  73. package/dist/types-frontend/types/alpaca-crypto-pairs.d.ts +0 -11
  74. package/dist/types-frontend/types/alpaca-crypto-pairs.d.ts.map +0 -1
  75. package/dist/types-frontend/types/alpaca-types.d.ts +0 -1181
  76. package/dist/types-frontend/types/alpaca-types.d.ts.map +0 -1
  77. package/dist/types-frontend/types/index.d.ts +0 -6
  78. package/dist/types-frontend/types/index.d.ts.map +0 -1
  79. package/dist/types-frontend/types/llm-types.d.ts +0 -174
  80. package/dist/types-frontend/types/llm-types.d.ts.map +0 -1
  81. package/dist/types-frontend/types/logging-types.d.ts +0 -10
  82. package/dist/types-frontend/types/logging-types.d.ts.map +0 -1
  83. package/dist/types-frontend/types/market-time-types.d.ts +0 -64
  84. package/dist/types-frontend/types/market-time-types.d.ts.map +0 -1
  85. package/dist/types-frontend/types/ta-types.d.ts +0 -89
  86. package/dist/types-frontend/types/ta-types.d.ts.map +0 -1
package/dist/test.mjs ADDED
@@ -0,0 +1,1977 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res, err) => function __init() {
3
+ if (err) throw err[0];
4
+ try {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ } catch (e) {
7
+ throw err = [e], e;
8
+ }
9
+ };
10
+
11
+ // src/logging.ts
12
+ function log(message, options = { source: "App", type: "info" }) {
13
+ const date = /* @__PURE__ */ new Date();
14
+ const timestamp = date.toLocaleString("en-US", { timeZone: "America/New_York" });
15
+ const account = options?.account;
16
+ const symbol = options?.symbol;
17
+ const logMessage = `[${timestamp}]${options?.source ? ` [${options.source}] ` : ""}${account ? ` [${account}] ` : ""}${symbol ? ` [${symbol}] ` : ""}${message}`;
18
+ if (options?.type === "error") {
19
+ console.error(logMessage);
20
+ } else if (options?.type === "warn") {
21
+ console.warn(logMessage);
22
+ } else {
23
+ console.log(logMessage);
24
+ }
25
+ }
26
+ var init_logging = __esm({
27
+ "src/logging.ts"() {
28
+ "use strict";
29
+ }
30
+ });
31
+
32
+ // src/market-hours.ts
33
+ var marketHolidays, marketEarlyCloses;
34
+ var init_market_hours = __esm({
35
+ "src/market-hours.ts"() {
36
+ "use strict";
37
+ marketHolidays = {
38
+ 2024: {
39
+ "New Year's Day": { date: "2024-01-01" },
40
+ "Martin Luther King, Jr. Day": { date: "2024-01-15" },
41
+ "Washington's Birthday": { date: "2024-02-19" },
42
+ "Good Friday": { date: "2024-03-29" },
43
+ "Memorial Day": { date: "2024-05-27" },
44
+ "Juneteenth National Independence Day": { date: "2024-06-19" },
45
+ "Independence Day": { date: "2024-07-04" },
46
+ "Labor Day": { date: "2024-09-02" },
47
+ "Thanksgiving Day": { date: "2024-11-28" },
48
+ "Christmas Day": { date: "2024-12-25" }
49
+ },
50
+ 2025: {
51
+ "New Year's Day": { date: "2025-01-01" },
52
+ "Jimmy Carter Memorial Day": { date: "2025-01-09" },
53
+ "Martin Luther King, Jr. Day": { date: "2025-01-20" },
54
+ "Washington's Birthday": { date: "2025-02-17" },
55
+ "Good Friday": { date: "2025-04-18" },
56
+ "Memorial Day": { date: "2025-05-26" },
57
+ "Juneteenth National Independence Day": { date: "2025-06-19" },
58
+ "Independence Day": { date: "2025-07-04" },
59
+ "Labor Day": { date: "2025-09-01" },
60
+ "Thanksgiving Day": { date: "2025-11-27" },
61
+ "Christmas Day": { date: "2025-12-25" }
62
+ },
63
+ 2026: {
64
+ "New Year's Day": { date: "2026-01-01" },
65
+ "Martin Luther King, Jr. Day": { date: "2026-01-19" },
66
+ "Washington's Birthday": { date: "2026-02-16" },
67
+ "Good Friday": { date: "2026-04-03" },
68
+ "Memorial Day": { date: "2026-05-25" },
69
+ "Juneteenth National Independence Day": { date: "2026-06-19" },
70
+ "Independence Day": { date: "2026-07-03" },
71
+ "Labor Day": { date: "2026-09-07" },
72
+ "Thanksgiving Day": { date: "2026-11-26" },
73
+ "Christmas Day": { date: "2026-12-25" }
74
+ }
75
+ };
76
+ marketEarlyCloses = {
77
+ 2024: {
78
+ "2024-07-03": {
79
+ date: "2024-07-03",
80
+ time: "13:00",
81
+ optionsTime: "13:15",
82
+ notes: "Market closes early on Wednesday, July 3, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
83
+ },
84
+ "2024-11-29": {
85
+ date: "2024-11-29",
86
+ time: "13:00",
87
+ optionsTime: "13:15",
88
+ notes: "Market closes early on Friday, November 29, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
89
+ },
90
+ "2024-12-24": {
91
+ date: "2024-12-24",
92
+ time: "13:00",
93
+ optionsTime: "13:15",
94
+ notes: "Market closes early on Tuesday, December 24, 2024 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
95
+ }
96
+ },
97
+ 2025: {
98
+ "2025-07-03": {
99
+ date: "2025-07-03",
100
+ time: "13:00",
101
+ optionsTime: "13:15",
102
+ notes: "Market closes early on Thursday, July 3, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
103
+ },
104
+ "2025-11-28": {
105
+ date: "2025-11-28",
106
+ time: "13:00",
107
+ optionsTime: "13:15",
108
+ notes: "Market closes early on Friday, November 28, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
109
+ },
110
+ "2025-12-24": {
111
+ date: "2025-12-24",
112
+ time: "13:00",
113
+ optionsTime: "13:15",
114
+ notes: "Market closes early on Wednesday, December 24, 2025 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
115
+ }
116
+ },
117
+ 2026: {
118
+ "2026-07-02": {
119
+ date: "2026-07-02",
120
+ time: "13:00",
121
+ optionsTime: "13:15",
122
+ notes: "Independence Day observed, market closes early at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
123
+ },
124
+ "2026-11-27": {
125
+ date: "2026-11-27",
126
+ time: "13:00",
127
+ optionsTime: "13:15",
128
+ notes: "Market closes early on Friday, November 27, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
129
+ },
130
+ "2026-12-24": {
131
+ date: "2026-12-24",
132
+ time: "13:00",
133
+ optionsTime: "13:15",
134
+ notes: "Market closes early on Thursday, December 24, 2026 at 1:00 p.m. (1:15 p.m. for eligible options). NYSE American Equities, NYSE Arca Equities, NYSE Chicago, and NYSE National late trading sessions will close at 5:00 p.m. Eastern Time."
135
+ }
136
+ }
137
+ };
138
+ }
139
+ });
140
+
141
+ // src/market-time.ts
142
+ function getNYOffset(date) {
143
+ const year = date.getUTCFullYear();
144
+ const dstStart = getNthWeekdayOfMonth(year, 3, 0, 2);
145
+ const dstEnd = getNthWeekdayOfMonth(year, 11, 0, 1);
146
+ const utcTime = date.getTime();
147
+ if (utcTime >= dstStart.getTime() && utcTime < dstEnd.getTime()) {
148
+ return MARKET_CONFIG.UTC_OFFSET_DST;
149
+ }
150
+ return MARKET_CONFIG.UTC_OFFSET_STANDARD;
151
+ }
152
+ function getNthWeekdayOfMonth(year, month, weekday, n) {
153
+ const firstDay = new Date(Date.UTC(year, month - 1, 1));
154
+ let count = 0;
155
+ for (let d = 1; d <= 31; d++) {
156
+ const date = new Date(Date.UTC(year, month - 1, d));
157
+ if (date.getUTCMonth() !== month - 1) break;
158
+ if (date.getUTCDay() === weekday) {
159
+ count++;
160
+ if (count === n) return date;
161
+ }
162
+ }
163
+ return new Date(Date.UTC(year, month - 1, 28));
164
+ }
165
+ function toNYTime(date) {
166
+ const offset = getNYOffset(date);
167
+ const utcMillis = date.getTime();
168
+ const nyMillis = utcMillis + offset * 60 * 60 * 1e3;
169
+ return new Date(nyMillis);
170
+ }
171
+ function fromNYTime(date) {
172
+ const offset = getNYOffset(date);
173
+ const nyMillis = date.getTime();
174
+ const utcMillis = nyMillis - offset * 60 * 60 * 1e3;
175
+ return new Date(utcMillis);
176
+ }
177
+ function getLastFullTradingDateImpl(currentDate = /* @__PURE__ */ new Date()) {
178
+ const calendar = new MarketCalendar();
179
+ const nyDate = toNYTime(currentDate);
180
+ const minutes = nyDate.getUTCHours() * 60 + nyDate.getUTCMinutes();
181
+ const marketOpenMinutes = MARKET_CONFIG.TIMES.MARKET_OPEN.hour * 60 + MARKET_CONFIG.TIMES.MARKET_OPEN.minute;
182
+ let marketCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
183
+ if (calendar.isEarlyCloseDay(currentDate)) {
184
+ marketCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
185
+ }
186
+ if (!calendar.isMarketDay(currentDate) || minutes < marketOpenMinutes || minutes >= marketOpenMinutes && minutes < marketCloseMinutes) {
187
+ const prevMarketDay = calendar.getPreviousMarketDay(currentDate);
188
+ let prevCloseMinutes = MARKET_CONFIG.TIMES.MARKET_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.MARKET_CLOSE.minute;
189
+ if (calendar.isEarlyCloseDay(prevMarketDay)) {
190
+ prevCloseMinutes = MARKET_CONFIG.TIMES.EARLY_CLOSE.hour * 60 + MARKET_CONFIG.TIMES.EARLY_CLOSE.minute;
191
+ }
192
+ const prevNYDate = toNYTime(prevMarketDay);
193
+ const year2 = prevNYDate.getUTCFullYear();
194
+ const month2 = prevNYDate.getUTCMonth();
195
+ const day2 = prevNYDate.getUTCDate();
196
+ const closeHour2 = Math.floor(prevCloseMinutes / 60);
197
+ const closeMinute2 = prevCloseMinutes % 60;
198
+ return fromNYTime(new Date(Date.UTC(year2, month2, day2, closeHour2, closeMinute2, 0, 0)));
199
+ }
200
+ const year = nyDate.getUTCFullYear();
201
+ const month = nyDate.getUTCMonth();
202
+ const day = nyDate.getUTCDate();
203
+ const closeHour = Math.floor(marketCloseMinutes / 60);
204
+ const closeMinute = marketCloseMinutes % 60;
205
+ return fromNYTime(new Date(Date.UTC(year, month, day, closeHour, closeMinute, 0, 0)));
206
+ }
207
+ function getLastFullTradingDate(currentDate = /* @__PURE__ */ new Date()) {
208
+ return getLastFullTradingDateImpl(currentDate);
209
+ }
210
+ var MARKET_CONFIG, MarketCalendar, MARKET_TIMES;
211
+ var init_market_time = __esm({
212
+ "src/market-time.ts"() {
213
+ "use strict";
214
+ init_logging();
215
+ init_market_hours();
216
+ MARKET_CONFIG = {
217
+ TIMEZONE: "America/New_York",
218
+ UTC_OFFSET_STANDARD: -5,
219
+ // EST
220
+ UTC_OFFSET_DST: -4,
221
+ // EDT
222
+ TIMES: {
223
+ EXTENDED_START: { hour: 4, minute: 0 },
224
+ MARKET_OPEN: { hour: 9, minute: 30 },
225
+ EARLY_MARKET_END: { hour: 10, minute: 0 },
226
+ MARKET_CLOSE: { hour: 16, minute: 0 },
227
+ EARLY_CLOSE: { hour: 13, minute: 0 },
228
+ EXTENDED_END: { hour: 20, minute: 0 },
229
+ EARLY_EXTENDED_END: { hour: 17, minute: 0 }
230
+ }
231
+ };
232
+ MarketCalendar = class {
233
+ /**
234
+ * Checks if a date is a weekend in NY time.
235
+ * @param date - Date object
236
+ * @returns true if weekend, false otherwise
237
+ */
238
+ isWeekend(date) {
239
+ const day = toNYTime(date).getUTCDay();
240
+ return day === 0 || day === 6;
241
+ }
242
+ /**
243
+ * Checks if a date is a market holiday in NY time.
244
+ * @param date - Date object
245
+ * @returns true if holiday, false otherwise
246
+ */
247
+ isHoliday(date) {
248
+ const nyDate = toNYTime(date);
249
+ const year = nyDate.getUTCFullYear();
250
+ const month = nyDate.getUTCMonth() + 1;
251
+ const day = nyDate.getUTCDate();
252
+ const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
253
+ const yearHolidays = marketHolidays[year];
254
+ if (!yearHolidays) return false;
255
+ return Object.values(yearHolidays).some((holiday) => holiday.date === formattedDate);
256
+ }
257
+ /**
258
+ * Checks if a date is an early close day in NY time.
259
+ * @param date - Date object
260
+ * @returns true if early close, false otherwise
261
+ */
262
+ isEarlyCloseDay(date) {
263
+ const nyDate = toNYTime(date);
264
+ const year = nyDate.getUTCFullYear();
265
+ const month = nyDate.getUTCMonth() + 1;
266
+ const day = nyDate.getUTCDate();
267
+ const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
268
+ const yearEarlyCloses = marketEarlyCloses[year];
269
+ return yearEarlyCloses && yearEarlyCloses[formattedDate] !== void 0;
270
+ }
271
+ /**
272
+ * Gets the early close time (in minutes from midnight) for a given date.
273
+ * @param date - Date object
274
+ * @returns minutes from midnight or null
275
+ */
276
+ getEarlyCloseTime(date) {
277
+ const nyDate = toNYTime(date);
278
+ const year = nyDate.getUTCFullYear();
279
+ const month = nyDate.getUTCMonth() + 1;
280
+ const day = nyDate.getUTCDate();
281
+ const formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
282
+ const yearEarlyCloses = marketEarlyCloses[year];
283
+ if (yearEarlyCloses && yearEarlyCloses[formattedDate]) {
284
+ const [hours, minutes] = yearEarlyCloses[formattedDate].time.split(":").map(Number);
285
+ return hours * 60 + minutes;
286
+ }
287
+ return null;
288
+ }
289
+ /**
290
+ * Checks if a date is a market day (not weekend or holiday).
291
+ * @param date - Date object
292
+ * @returns true if market day, false otherwise
293
+ */
294
+ isMarketDay(date) {
295
+ return !this.isWeekend(date) && !this.isHoliday(date);
296
+ }
297
+ /**
298
+ * Gets the next market day after the given date.
299
+ * @param date - Date object
300
+ * @returns Date object for next market day
301
+ */
302
+ getNextMarketDay(date) {
303
+ let nextDay = new Date(date.getTime() + 24 * 60 * 60 * 1e3);
304
+ while (!this.isMarketDay(nextDay)) {
305
+ nextDay = new Date(nextDay.getTime() + 24 * 60 * 60 * 1e3);
306
+ }
307
+ return nextDay;
308
+ }
309
+ /**
310
+ * Gets the previous market day before the given date.
311
+ * @param date - Date object
312
+ * @returns Date object for previous market day
313
+ */
314
+ getPreviousMarketDay(date) {
315
+ let prevDay = new Date(date.getTime() - 24 * 60 * 60 * 1e3);
316
+ while (!this.isMarketDay(prevDay)) {
317
+ prevDay = new Date(prevDay.getTime() - 24 * 60 * 60 * 1e3);
318
+ }
319
+ return prevDay;
320
+ }
321
+ };
322
+ MARKET_TIMES = {
323
+ TIMEZONE: MARKET_CONFIG.TIMEZONE,
324
+ PRE: {
325
+ START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
326
+ END: { HOUR: 9, MINUTE: 30, MINUTES: 570 }
327
+ },
328
+ EARLY_MORNING: {
329
+ START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
330
+ END: { HOUR: 10, MINUTE: 0, MINUTES: 600 }
331
+ },
332
+ EARLY_CLOSE_BEFORE_HOLIDAY: {
333
+ START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
334
+ END: { HOUR: 13, MINUTE: 0, MINUTES: 780 }
335
+ },
336
+ EARLY_EXTENDED_BEFORE_HOLIDAY: {
337
+ START: { HOUR: 13, MINUTE: 0, MINUTES: 780 },
338
+ END: { HOUR: 17, MINUTE: 0, MINUTES: 1020 }
339
+ },
340
+ REGULAR: {
341
+ START: { HOUR: 9, MINUTE: 30, MINUTES: 570 },
342
+ END: { HOUR: 16, MINUTE: 0, MINUTES: 960 }
343
+ },
344
+ EXTENDED: {
345
+ START: { HOUR: 4, MINUTE: 0, MINUTES: 240 },
346
+ END: { HOUR: 20, MINUTE: 0, MINUTES: 1200 }
347
+ }
348
+ };
349
+ }
350
+ });
351
+
352
+ // src/alpaca-market-data-api.ts
353
+ import "dotenv/config";
354
+ import { EventEmitter } from "events";
355
+ import WebSocket from "ws";
356
+ var DEBUG_LOGGING, log2, DEFAULT_ADJUSTMENT, DEFAULT_FEED, DEFAULT_CURRENCY, AlpacaMarketDataAPI, marketDataAPI;
357
+ var init_alpaca_market_data_api = __esm({
358
+ "src/alpaca-market-data-api.ts"() {
359
+ "use strict";
360
+ init_logging();
361
+ init_market_time();
362
+ DEBUG_LOGGING = process.env["DEBUG"] === "true" || false;
363
+ log2 = (message, options = { type: "info" }) => {
364
+ if (!DEBUG_LOGGING && options.type === "debug") {
365
+ return;
366
+ }
367
+ log(message, { ...options, source: "AlpacaMarketDataAPI" });
368
+ };
369
+ DEFAULT_ADJUSTMENT = "all";
370
+ DEFAULT_FEED = "sip";
371
+ DEFAULT_CURRENCY = "USD";
372
+ AlpacaMarketDataAPI = class _AlpacaMarketDataAPI extends EventEmitter {
373
+ static instance;
374
+ headers;
375
+ dataURL;
376
+ apiURL;
377
+ v1beta1url;
378
+ v1beta3url;
379
+ stockStreamUrl = "wss://stream.data.alpaca.markets/v2/sip";
380
+ // production values
381
+ optionStreamUrl = "wss://stream.data.alpaca.markets/v1beta3/options";
382
+ // production values
383
+ stockWs = null;
384
+ optionWs = null;
385
+ stockSubscriptions = { trades: [], quotes: [], bars: [] };
386
+ optionSubscriptions = { trades: [], quotes: [], bars: [] };
387
+ autoReconnect = true;
388
+ stockReconnectAttempts = 0;
389
+ optionReconnectAttempts = 0;
390
+ stockReconnectTimeout = null;
391
+ optionReconnectTimeout = null;
392
+ setMode(mode = "production") {
393
+ if (mode === "sandbox") {
394
+ this.stockStreamUrl = "wss://stream.data.sandbox.alpaca.markets/v2/sip";
395
+ this.optionStreamUrl = "wss://stream.data.sandbox.alpaca.markets/v1beta3/options";
396
+ } else if (mode === "test") {
397
+ this.stockStreamUrl = "wss://stream.data.alpaca.markets/v2/test";
398
+ this.optionStreamUrl = "wss://stream.data.alpaca.markets/v1beta3/options";
399
+ } else {
400
+ this.stockStreamUrl = "wss://stream.data.alpaca.markets/v2/sip";
401
+ this.optionStreamUrl = "wss://stream.data.alpaca.markets/v1beta3/options";
402
+ }
403
+ }
404
+ getMode() {
405
+ if (this.stockStreamUrl.includes("sandbox")) {
406
+ return "sandbox";
407
+ } else if (this.stockStreamUrl.includes("test")) {
408
+ return "test";
409
+ } else {
410
+ return "production";
411
+ }
412
+ }
413
+ setAutoReconnect(enabled) {
414
+ this.autoReconnect = enabled;
415
+ log2(`Auto-reconnect ${enabled ? "enabled" : "disabled"}`);
416
+ }
417
+ getAutoReconnect() {
418
+ return this.autoReconnect;
419
+ }
420
+ constructor() {
421
+ super();
422
+ this.dataURL = "https://data.alpaca.markets/v2";
423
+ this.apiURL = process.env.ALPACA_ACCOUNT_TYPE === "PAPER" ? "https://paper-api.alpaca.markets/v2" : "https://api.alpaca.markets/v2";
424
+ this.v1beta1url = "https://data.alpaca.markets/v1beta1";
425
+ this.v1beta3url = "https://data.alpaca.markets/v1beta3";
426
+ this.setMode("production");
427
+ this.headers = {
428
+ "APCA-API-KEY-ID": process.env.ALPACA_API_KEY,
429
+ "APCA-API-SECRET-KEY": process.env.ALPACA_SECRET_KEY,
430
+ "Content-Type": "application/json"
431
+ };
432
+ }
433
+ static getInstance() {
434
+ if (!_AlpacaMarketDataAPI.instance) {
435
+ _AlpacaMarketDataAPI.instance = new _AlpacaMarketDataAPI();
436
+ }
437
+ return _AlpacaMarketDataAPI.instance;
438
+ }
439
+ getReconnectDelay(attempt) {
440
+ const delays = [0, 5e3, 1e4, 15e3, 6e4];
441
+ return attempt < delays.length ? delays[attempt] : 6e4;
442
+ }
443
+ scheduleReconnect(streamType) {
444
+ if (!this.autoReconnect) {
445
+ log2(`Auto-reconnect disabled, not reconnecting ${streamType} stream`);
446
+ return;
447
+ }
448
+ const attempts = streamType === "stock" ? this.stockReconnectAttempts : this.optionReconnectAttempts;
449
+ const delay = this.getReconnectDelay(attempts);
450
+ log2(`Scheduling ${streamType} stream reconnect (attempt ${attempts + 1}) in ${delay}ms`);
451
+ const timeout = setTimeout(() => {
452
+ log2(`Attempting to reconnect ${streamType} stream (attempt ${attempts + 1})`);
453
+ if (streamType === "stock") {
454
+ this.stockReconnectAttempts++;
455
+ } else {
456
+ this.optionReconnectAttempts++;
457
+ }
458
+ this.connect(streamType);
459
+ }, delay);
460
+ if (streamType === "stock") {
461
+ this.stockReconnectTimeout = timeout;
462
+ } else {
463
+ this.optionReconnectTimeout = timeout;
464
+ }
465
+ }
466
+ on(event, listener) {
467
+ return super.on(event, listener);
468
+ }
469
+ emit(event, ...args) {
470
+ return super.emit(event, ...args);
471
+ }
472
+ connect(streamType) {
473
+ const url = streamType === "stock" ? this.stockStreamUrl : this.optionStreamUrl;
474
+ const ws = new WebSocket(url);
475
+ if (streamType === "stock") {
476
+ this.stockWs = ws;
477
+ } else {
478
+ this.optionWs = ws;
479
+ }
480
+ ws.on("open", () => {
481
+ log2(`${streamType} stream connected`);
482
+ if (streamType === "stock") {
483
+ this.stockReconnectAttempts = 0;
484
+ if (this.stockReconnectTimeout) {
485
+ clearTimeout(this.stockReconnectTimeout);
486
+ this.stockReconnectTimeout = null;
487
+ }
488
+ } else {
489
+ this.optionReconnectAttempts = 0;
490
+ if (this.optionReconnectTimeout) {
491
+ clearTimeout(this.optionReconnectTimeout);
492
+ this.optionReconnectTimeout = null;
493
+ }
494
+ }
495
+ const authMessage = {
496
+ action: "auth",
497
+ key: process.env.ALPACA_API_KEY,
498
+ secret: process.env.ALPACA_SECRET_KEY
499
+ };
500
+ ws.send(JSON.stringify(authMessage));
501
+ });
502
+ ws.on("message", (data) => {
503
+ const messages = JSON.parse(data.toString());
504
+ for (const message of messages) {
505
+ if (message.T === "success" && message.msg === "authenticated") {
506
+ log2(`${streamType} stream authenticated`);
507
+ this.sendSubscription(streamType);
508
+ } else if (message.T === "error") {
509
+ log2(`${streamType} stream error: ${message.msg}`, { type: "error" });
510
+ } else if (message.S) {
511
+ super.emit(`${streamType}-${message.T}`, message);
512
+ super.emit(`${streamType}-data`, message);
513
+ }
514
+ }
515
+ });
516
+ ws.on("close", (code, reason) => {
517
+ const reasonStr = reason.toString() || "No reason provided";
518
+ const codeDesc = this.getCloseCodeDescription(code);
519
+ log2(`${streamType} stream disconnected - Code: ${code} (${codeDesc}), Reason: ${reasonStr}`, { type: "warn" });
520
+ if (streamType === "stock") {
521
+ this.stockWs = null;
522
+ } else {
523
+ this.optionWs = null;
524
+ }
525
+ this.scheduleReconnect(streamType);
526
+ });
527
+ ws.on("error", (error) => {
528
+ log2(`${streamType} stream error: ${error.message}`, { type: "error" });
529
+ });
530
+ }
531
+ getCloseCodeDescription(code) {
532
+ const codes = {
533
+ 1e3: "Normal Closure",
534
+ 1001: "Going Away",
535
+ 1002: "Protocol Error",
536
+ 1003: "Unsupported Data",
537
+ 1005: "No Status Received",
538
+ 1006: "Abnormal Closure",
539
+ 1007: "Invalid Frame Payload Data",
540
+ 1008: "Policy Violation",
541
+ 1009: "Message Too Big",
542
+ 1010: "Mandatory Extension",
543
+ 1011: "Internal Server Error",
544
+ 1012: "Service Restart",
545
+ 1013: "Try Again Later",
546
+ 1014: "Bad Gateway",
547
+ 1015: "TLS Handshake"
548
+ };
549
+ return codes[code] || "Unknown";
550
+ }
551
+ sendSubscription(streamType) {
552
+ const ws = streamType === "stock" ? this.stockWs : this.optionWs;
553
+ const subscriptions = streamType === "stock" ? this.stockSubscriptions : this.optionSubscriptions;
554
+ if (ws && ws.readyState === WebSocket.OPEN) {
555
+ const subMessagePayload = {};
556
+ if (subscriptions.trades.length > 0) {
557
+ subMessagePayload.trades = subscriptions.trades;
558
+ }
559
+ if (subscriptions.quotes.length > 0) {
560
+ subMessagePayload.quotes = subscriptions.quotes;
561
+ }
562
+ if (subscriptions.bars.length > 0) {
563
+ subMessagePayload.bars = subscriptions.bars;
564
+ }
565
+ if (Object.keys(subMessagePayload).length > 0) {
566
+ const subMessage = {
567
+ action: "subscribe",
568
+ ...subMessagePayload
569
+ };
570
+ ws.send(JSON.stringify(subMessage));
571
+ }
572
+ }
573
+ }
574
+ connectStockStream() {
575
+ if (!this.stockWs) {
576
+ this.connect("stock");
577
+ }
578
+ }
579
+ connectOptionStream() {
580
+ if (!this.optionWs) {
581
+ this.connect("option");
582
+ }
583
+ }
584
+ disconnectStockStream() {
585
+ if (this.stockReconnectTimeout) {
586
+ clearTimeout(this.stockReconnectTimeout);
587
+ this.stockReconnectTimeout = null;
588
+ }
589
+ this.stockReconnectAttempts = 0;
590
+ if (this.stockWs) {
591
+ this.stockWs.close();
592
+ }
593
+ }
594
+ disconnectOptionStream() {
595
+ if (this.optionReconnectTimeout) {
596
+ clearTimeout(this.optionReconnectTimeout);
597
+ this.optionReconnectTimeout = null;
598
+ }
599
+ this.optionReconnectAttempts = 0;
600
+ if (this.optionWs) {
601
+ this.optionWs.close();
602
+ }
603
+ }
604
+ subscribe(streamType, subscriptions) {
605
+ const currentSubscriptions = streamType === "stock" ? this.stockSubscriptions : this.optionSubscriptions;
606
+ Object.entries(subscriptions).forEach(([key, value]) => {
607
+ if (value) {
608
+ currentSubscriptions[key] = [.../* @__PURE__ */ new Set([...currentSubscriptions[key] || [], ...value])];
609
+ }
610
+ });
611
+ this.sendSubscription(streamType);
612
+ }
613
+ unsubscribe(streamType, subscriptions) {
614
+ const currentSubscriptions = streamType === "stock" ? this.stockSubscriptions : this.optionSubscriptions;
615
+ Object.entries(subscriptions).forEach(([key, value]) => {
616
+ if (value) {
617
+ currentSubscriptions[key] = (currentSubscriptions[key] || []).filter((s) => !value.includes(s));
618
+ }
619
+ });
620
+ const unsubMessage = {
621
+ action: "unsubscribe",
622
+ ...subscriptions
623
+ };
624
+ const ws = streamType === "stock" ? this.stockWs : this.optionWs;
625
+ if (ws && ws.readyState === WebSocket.OPEN) {
626
+ ws.send(JSON.stringify(unsubMessage));
627
+ }
628
+ }
629
+ async makeRequest(endpoint, method = "GET", params, baseUrlName = "data") {
630
+ const baseUrl = baseUrlName === "data" ? this.dataURL : baseUrlName === "api" ? this.apiURL : baseUrlName === "v1beta1" ? this.v1beta1url : this.v1beta3url;
631
+ const url = new URL(`${baseUrl}${endpoint}`);
632
+ try {
633
+ if (params) {
634
+ Object.entries(params).forEach(([key, value]) => {
635
+ if (Array.isArray(value)) {
636
+ url.searchParams.append(key, value.join(","));
637
+ } else if (value !== void 0) {
638
+ url.searchParams.append(key, value.toString());
639
+ }
640
+ });
641
+ }
642
+ const response = await fetch(url.toString(), {
643
+ method,
644
+ headers: this.headers
645
+ });
646
+ if (!response.ok) {
647
+ const errorText = await response.text();
648
+ log2(`Market Data API error (${response.status}): ${errorText}`, { type: "error" });
649
+ throw new Error(`Market Data API error (${response.status}): ${errorText}`);
650
+ }
651
+ const data = await response.json();
652
+ return data;
653
+ } catch (err) {
654
+ const error = err;
655
+ log2(`Error in makeRequest: ${error.message}. Endpoint: ${endpoint}. Url: ${url.toString()}`, { type: "error" });
656
+ if (error instanceof TypeError) {
657
+ log2(`Network error details: ${error.stack}`, { type: "error" });
658
+ }
659
+ throw error;
660
+ }
661
+ }
662
+ /**
663
+ * Get historical OHLCV bars for specified symbols, including pre-market and post-market data
664
+ * Automatically handles pagination to fetch all available data
665
+ * @param params Parameters for historical bars request
666
+ * @returns Historical bars data with all pages combined
667
+ */
668
+ async getHistoricalBars(params) {
669
+ const symbols = params.symbols;
670
+ const symbolsStr = symbols.join(",");
671
+ let allBars = {};
672
+ let pageToken = null;
673
+ let hasMorePages = true;
674
+ let totalBarsCount = 0;
675
+ let pageCount = 0;
676
+ let currency = "";
677
+ symbols.forEach((symbol) => {
678
+ allBars[symbol] = [];
679
+ });
680
+ log2(
681
+ `Starting ${params.timeframe}bars fetch for ${symbols.length} symbols (${symbolsStr}), ${params.start || "no start"} to ${params.end || "no end"})`,
682
+ { symbol: symbolsStr }
683
+ );
684
+ while (hasMorePages) {
685
+ pageCount++;
686
+ const requestParams = {
687
+ ...params,
688
+ adjustment: params.adjustment ?? DEFAULT_ADJUSTMENT,
689
+ feed: DEFAULT_FEED,
690
+ ...pageToken && { page_token: pageToken }
691
+ };
692
+ const response = await this.makeRequest("/stocks/bars", "GET", requestParams);
693
+ if (!response.bars) {
694
+ log2(`No bars data found in response for ${symbols.length} symbols`, { symbol: symbolsStr, type: "warn" });
695
+ break;
696
+ }
697
+ if (!currency) {
698
+ currency = response.currency;
699
+ }
700
+ let pageBarsCount = 0;
701
+ let earliestTimestamp = null;
702
+ let latestTimestamp = null;
703
+ Object.entries(response.bars).forEach(([symbol, bars]) => {
704
+ if (bars && bars.length > 0) {
705
+ allBars[symbol] = [...allBars[symbol], ...bars];
706
+ pageBarsCount += bars.length;
707
+ bars.forEach((bar) => {
708
+ const barDate = new Date(bar.t);
709
+ if (!earliestTimestamp || barDate < earliestTimestamp) {
710
+ earliestTimestamp = barDate;
711
+ }
712
+ if (!latestTimestamp || barDate > latestTimestamp) {
713
+ latestTimestamp = barDate;
714
+ }
715
+ });
716
+ }
717
+ });
718
+ totalBarsCount += pageBarsCount;
719
+ pageToken = response.next_page_token || null;
720
+ hasMorePages = !!pageToken;
721
+ const dateRangeStr = earliestTimestamp && latestTimestamp ? `${new Date(earliestTimestamp).toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${new Date(
722
+ latestTimestamp
723
+ ).toLocaleDateString("en-US", { timeZone: "America/New_York" })}` : "unknown range";
724
+ log2(
725
+ `Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} bars (total: ${totalBarsCount.toLocaleString()}) for ${symbols.length} symbols, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`,
726
+ { symbol: symbolsStr, type: "debug" }
727
+ );
728
+ if (pageCount > 1e3) {
729
+ log2(`Stopping pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
730
+ break;
731
+ }
732
+ }
733
+ const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(", ");
734
+ log2(
735
+ `${params.timeframe} bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages for ${symbols.length} symbols (${symbolCounts})`,
736
+ { symbol: symbolsStr }
737
+ );
738
+ return {
739
+ bars: allBars,
740
+ next_page_token: null,
741
+ // Always null since we fetch all pages
742
+ currency: currency || DEFAULT_CURRENCY
743
+ };
744
+ }
745
+ /**
746
+ * Get the most recent minute bar for requested symbols
747
+ * @param symbols Array of stock symbols to query
748
+ * @param currency Optional currency in ISO 4217 format
749
+ * @returns Latest bar data for each symbol
750
+
751
+ */
752
+ async getLatestBars(symbols, currency) {
753
+ return this.makeRequest("/stocks/bars/latest", "GET", {
754
+ symbols,
755
+ feed: DEFAULT_FEED,
756
+ currency: currency || DEFAULT_CURRENCY
757
+ });
758
+ }
759
+ /**
760
+ * Get the last trade for a single symbol
761
+ * @param symbol The stock symbol to query
762
+ * @returns Last trade details including price, size, exchange, and conditions
763
+ */
764
+ async getLastTrade(symbol) {
765
+ return this.makeRequest(`/v1/last/stocks/${symbol}`, "GET");
766
+ }
767
+ /**
768
+ * Get the most recent trades for requested symbols
769
+ * @param symbols Array of stock symbols to query
770
+ * @param feed Optional data source (sip/iex/delayed_sip)
771
+ * @param currency Optional currency in ISO 4217 format
772
+ * @returns Latest trade data for each symbol
773
+
774
+ */
775
+ async getLatestTrades(symbols, feed, currency) {
776
+ return this.makeRequest("/stocks/trades/latest", "GET", {
777
+ symbols,
778
+ feed: feed || DEFAULT_FEED,
779
+ currency: currency || DEFAULT_CURRENCY
780
+ });
781
+ }
782
+ /**
783
+ * Get the most recent quotes for requested symbols
784
+ * @param symbols Array of stock symbols to query
785
+ * @param feed Optional data source (sip/iex/delayed_sip)
786
+ * @param currency Optional currency in ISO 4217 format
787
+ * @returns Latest quote data for each symbol
788
+ */
789
+ async getLatestQuotes(symbols, feed, currency) {
790
+ if (!symbols || symbols.length === 0) {
791
+ log2("No symbols provided to getLatestQuotes, returning empty response", { type: "warn" });
792
+ return {
793
+ quotes: {},
794
+ currency: currency || DEFAULT_CURRENCY
795
+ };
796
+ }
797
+ return this.makeRequest("/stocks/quotes/latest", "GET", {
798
+ symbols,
799
+ feed: feed || DEFAULT_FEED,
800
+ currency: currency || DEFAULT_CURRENCY
801
+ });
802
+ }
803
+ /**
804
+ * Get the latest quote for a single symbol
805
+ * @param symbol The stock symbol to query
806
+ * @param feed Optional data source (sip/iex/delayed_sip)
807
+ * @param currency Optional currency in ISO 4217 format
808
+ * @returns Latest quote data with symbol and currency information
809
+ */
810
+ async getLatestQuote(symbol, feed, currency) {
811
+ return this.makeRequest(`/stocks/${symbol}/quotes/latest`, "GET", {
812
+ feed: feed || DEFAULT_FEED,
813
+ currency
814
+ });
815
+ }
816
+ /**
817
+ * Get the previous day's closing price for a symbol
818
+ * @param symbol The stock symbol to query
819
+ * @param referenceDate Optional reference date to get the previous close for
820
+ * @returns Previous day's closing price data
821
+ */
822
+ async getPreviousClose(symbol, referenceDate) {
823
+ const date = referenceDate || /* @__PURE__ */ new Date();
824
+ const prevMarketDate = getLastFullTradingDate(date);
825
+ const response = await this.getHistoricalBars({
826
+ symbols: [symbol],
827
+ timeframe: "1Day",
828
+ start: prevMarketDate.toISOString(),
829
+ end: prevMarketDate.toISOString(),
830
+ limit: 1
831
+ });
832
+ if (!response.bars[symbol] || response.bars[symbol].length === 0) {
833
+ log2(`No previous close data available for ${symbol}`, { type: "error", symbol });
834
+ return null;
835
+ }
836
+ return response.bars[symbol][0];
837
+ }
838
+ /**
839
+ * Get hourly price data for a symbol
840
+ * @param symbol The stock symbol to query
841
+ * @param start Start time in milliseconds
842
+ * @param end End time in milliseconds
843
+ * @returns Array of hourly price bars
844
+ */
845
+ async getHourlyPrices(symbol, start, end) {
846
+ const response = await this.getHistoricalBars({
847
+ symbols: [symbol],
848
+ timeframe: "1Hour",
849
+ start: new Date(start).toISOString(),
850
+ end: new Date(end).toISOString(),
851
+ limit: 96
852
+ // Last 96 hours (4 days)
853
+ });
854
+ return response.bars[symbol] || [];
855
+ }
856
+ /**
857
+ * Get half-hourly price data for a symbol
858
+ * @param symbol The stock symbol to query
859
+ * @param start Start time in milliseconds
860
+ * @param end End time in milliseconds
861
+ * @returns Array of half-hourly price bars
862
+ */
863
+ async getHalfHourlyPrices(symbol, start, end) {
864
+ const response = await this.getHistoricalBars({
865
+ symbols: [symbol],
866
+ timeframe: "30Min",
867
+ start: new Date(start).toISOString(),
868
+ end: new Date(end).toISOString(),
869
+ limit: 16 * 2 * 4
870
+ // last 4 days, 16 hours per day, 2 bars per hour
871
+ });
872
+ return response.bars[symbol] || [];
873
+ }
874
+ /**
875
+ * Get daily price data for a symbol
876
+ * @param symbol The stock symbol to query
877
+ * @param start Start time in milliseconds
878
+ * @param end End time in milliseconds
879
+ * @returns Array of daily price bars
880
+ */
881
+ async getDailyPrices(symbol, start, end) {
882
+ const response = await this.getHistoricalBars({
883
+ symbols: [symbol],
884
+ timeframe: "1Day",
885
+ start: new Date(start).toISOString(),
886
+ end: new Date(end).toISOString(),
887
+ limit: 100
888
+ // Last 100 days
889
+ });
890
+ return response.bars[symbol] || [];
891
+ }
892
+ /**
893
+ * Get intraday price data for a symbol
894
+ * @param symbol The stock symbol to query
895
+ * @param minutePeriod Minutes per bar (1, 5, 15, etc.)
896
+ * @param start Start time in milliseconds
897
+ * @param end End time in milliseconds
898
+ * @returns Array of intraday price bars
899
+ */
900
+ async getIntradayPrices(symbol, minutePeriod, start, end) {
901
+ const timeframe = `${minutePeriod}Min`;
902
+ const response = await this.getHistoricalBars({
903
+ symbols: [symbol],
904
+ timeframe,
905
+ start: new Date(start).toISOString(),
906
+ end: new Date(end).toISOString()
907
+ });
908
+ return response.bars[symbol] || [];
909
+ }
910
+ /**
911
+ * Analyzes an array of price bars and returns a summary string
912
+ * @param bars Array of price bars to analyze
913
+ * @returns A string summarizing the price data
914
+ */
915
+ static analyzeBars(bars) {
916
+ if (!bars || bars.length === 0) {
917
+ return "No price data available";
918
+ }
919
+ const firstBar = bars[0];
920
+ const lastBar = bars[bars.length - 1];
921
+ const priceChange = lastBar.c - firstBar.o;
922
+ const percentChange = priceChange / firstBar.o * 100;
923
+ const volumeChange = lastBar.v - firstBar.v;
924
+ const percentVolumeChange = volumeChange / firstBar.v * 100;
925
+ const high = Math.max(...bars.map((bar) => bar.h));
926
+ const low = Math.min(...bars.map((bar) => bar.l));
927
+ const totalVolume = bars.reduce((sum, bar) => sum + bar.v, 0);
928
+ const avgVolume = totalVolume / bars.length;
929
+ return `Price: $${firstBar.o.toFixed(2)} -> $${lastBar.c.toFixed(2)} (${percentChange.toFixed(2)}%), Volume: ${firstBar.v.toLocaleString()} -> ${lastBar.v.toLocaleString()} (${percentVolumeChange.toFixed(2)}%), High: $${high.toFixed(2)}, Low: $${low.toFixed(2)}, Avg Volume: ${avgVolume.toLocaleString()}`;
930
+ }
931
+ /**
932
+ * Get assets available for trade and data consumption from Alpaca
933
+ * @param params Optional query params
934
+ * - status: 'active' | 'inactive' (default 'active')
935
+ * - asset_class: 'us_equity' | 'us_option' | 'crypto' (default 'us_equity')
936
+ * - shortable: if true (default), filters to assets with shortable=true and easy_to_borrow=true
937
+ * @returns Array of AlpacaAsset objects
938
+ * @see https://docs.alpaca.markets/reference/get-v2-assets-1
939
+ */
940
+ async getAssets(params) {
941
+ const {
942
+ status = "active",
943
+ asset_class = "us_equity",
944
+ shortable = true
945
+ } = {
946
+ status: params?.status ?? "active",
947
+ asset_class: params?.asset_class ?? "us_equity",
948
+ shortable: params?.shortable ?? true
949
+ };
950
+ const assets = await this.makeRequest(
951
+ "/assets",
952
+ "GET",
953
+ { status, asset_class },
954
+ "api"
955
+ );
956
+ if (!shortable) return assets;
957
+ return assets.filter((a) => a.shortable === true && a.easy_to_borrow === true);
958
+ }
959
+ /**
960
+ * Get a single asset by symbol or asset_id
961
+ * @param symbolOrAssetId Symbol or asset_id
962
+ * @returns AlpacaAsset object
963
+ * @see https://docs.alpaca.markets/reference/get-v2-assets-symbol_or_asset_id
964
+ */
965
+ async getAsset(symbolOrAssetId) {
966
+ return this.makeRequest(`/assets/${encodeURIComponent(symbolOrAssetId)}`, "GET", void 0, "api");
967
+ }
968
+ // ===== OPTIONS MARKET DATA METHODS =====
969
+ /**
970
+ * Get options chain for an underlying symbol
971
+ * Provides the latest trade, latest quote, and greeks for each contract symbol of the underlying symbol
972
+ * @param params Options chain request parameters
973
+ * @returns Options chain data with snapshots for each contract
974
+ * @see https://docs.alpaca.markets/reference/optionchain
975
+ */
976
+ async getOptionsChain(params) {
977
+ const { underlying_symbol, ...queryParams } = params;
978
+ return this.makeRequest(
979
+ `/options/snapshots/${encodeURIComponent(underlying_symbol)}`,
980
+ "GET",
981
+ queryParams,
982
+ "v1beta1"
983
+ );
984
+ }
985
+ /**
986
+ * Get the most recent trades for requested option contract symbols
987
+ * @param params Latest options trades request parameters
988
+ * @returns Latest trade data for each option contract symbol
989
+
990
+ * @see https://docs.alpaca.markets/reference/optionlatesttrades
991
+ */
992
+ async getLatestOptionsTrades(params) {
993
+ const { limit, page_token, ...requestParams } = params;
994
+ return this.makeRequest("/options/trades/latest", "GET", requestParams, "v1beta1");
995
+ }
996
+ /**
997
+ * Get the most recent quotes for requested option contract symbols
998
+ * @param params Latest options quotes request parameters
999
+ * @returns Latest quote data for each option contract symbol
1000
+
1001
+ * @see https://docs.alpaca.markets/reference/optionlatestquotes
1002
+ */
1003
+ async getLatestOptionsQuotes(params) {
1004
+ const { limit, page_token, ...requestParams } = params;
1005
+ return this.makeRequest("/options/quotes/latest", "GET", requestParams, "v1beta1");
1006
+ }
1007
+ /**
1008
+ * Get historical OHLCV bars for option contract symbols
1009
+ * Automatically handles pagination to fetch all available data
1010
+ * @param params Historical options bars request parameters
1011
+ * @returns Historical bar data for each option contract symbol with all pages combined
1012
+
1013
+ * @see https://docs.alpaca.markets/reference/optionbars
1014
+ */
1015
+ async getHistoricalOptionsBars(params) {
1016
+ const symbols = params.symbols;
1017
+ const symbolsStr = symbols.join(",");
1018
+ let allBars = {};
1019
+ let pageToken = null;
1020
+ let hasMorePages = true;
1021
+ let totalBarsCount = 0;
1022
+ let pageCount = 0;
1023
+ symbols.forEach((symbol) => {
1024
+ allBars[symbol] = [];
1025
+ });
1026
+ log2(
1027
+ `Starting historical options bars fetch for ${symbols.length} symbols (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`
1028
+ );
1029
+ while (hasMorePages) {
1030
+ pageCount++;
1031
+ const requestParams = {
1032
+ ...params,
1033
+ ...pageToken && { page_token: pageToken }
1034
+ };
1035
+ const response = await this.makeRequest(
1036
+ "/options/bars",
1037
+ "GET",
1038
+ requestParams,
1039
+ "v1beta1"
1040
+ );
1041
+ if (!response.bars) {
1042
+ log2(`No options bars data found in response for ${symbols.length} symbols`, { type: "warn" });
1043
+ break;
1044
+ }
1045
+ let pageBarsCount = 0;
1046
+ let earliestTimestamp = null;
1047
+ let latestTimestamp = null;
1048
+ Object.entries(response.bars).forEach(([symbol, bars]) => {
1049
+ if (bars && bars.length > 0) {
1050
+ allBars[symbol] = [...allBars[symbol], ...bars];
1051
+ pageBarsCount += bars.length;
1052
+ bars.forEach((bar) => {
1053
+ const barDate = new Date(bar.t);
1054
+ if (!earliestTimestamp || barDate < earliestTimestamp) {
1055
+ earliestTimestamp = barDate;
1056
+ }
1057
+ if (!latestTimestamp || barDate > latestTimestamp) {
1058
+ latestTimestamp = barDate;
1059
+ }
1060
+ });
1061
+ }
1062
+ });
1063
+ totalBarsCount += pageBarsCount;
1064
+ pageToken = response.next_page_token || null;
1065
+ hasMorePages = !!pageToken;
1066
+ const dateRangeStr = earliestTimestamp && latestTimestamp ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}` : "unknown range";
1067
+ log2(
1068
+ `Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} option bars (total: ${totalBarsCount.toLocaleString()}) for ${symbols.length} symbols, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`,
1069
+ {
1070
+ type: "debug"
1071
+ }
1072
+ );
1073
+ if (pageCount > 1e3) {
1074
+ log2(`Stopping options bars pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
1075
+ break;
1076
+ }
1077
+ }
1078
+ const symbolCounts = Object.entries(allBars).map(([symbol, bars]) => `${symbol}: ${bars.length}`).join(", ");
1079
+ log2(
1080
+ `Options bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages (${symbolCounts})`
1081
+ );
1082
+ return {
1083
+ bars: allBars,
1084
+ next_page_token: void 0
1085
+ // Always undefined since we fetch all pages
1086
+ };
1087
+ }
1088
+ /**
1089
+ * Get historical trades for option contract symbols
1090
+ * Automatically handles pagination to fetch all available data
1091
+ * @param params Historical options trades request parameters
1092
+ * @returns Historical trade data for each option contract symbol with all pages combined
1093
+
1094
+ * @see https://docs.alpaca.markets/reference/optiontrades
1095
+ */
1096
+ async getHistoricalOptionsTrades(params) {
1097
+ const symbols = params.symbols;
1098
+ const symbolsStr = symbols.join(",");
1099
+ let allTrades = {};
1100
+ let pageToken = null;
1101
+ let hasMorePages = true;
1102
+ let totalTradesCount = 0;
1103
+ let pageCount = 0;
1104
+ symbols.forEach((symbol) => {
1105
+ allTrades[symbol] = [];
1106
+ });
1107
+ log2(
1108
+ `Starting historical options trades fetch for ${symbols.length} symbols (${params.start || "no start"} to ${params.end || "no end"})`
1109
+ );
1110
+ while (hasMorePages) {
1111
+ pageCount++;
1112
+ const requestParams = {
1113
+ ...params,
1114
+ ...pageToken && { page_token: pageToken }
1115
+ };
1116
+ const response = await this.makeRequest(
1117
+ "/options/trades",
1118
+ "GET",
1119
+ requestParams,
1120
+ "v1beta1"
1121
+ );
1122
+ if (!response.trades) {
1123
+ log2(`No options trades data found in response for ${symbols.length} symbols`, { type: "warn" });
1124
+ break;
1125
+ }
1126
+ let pageTradesCount = 0;
1127
+ let earliestTimestamp = null;
1128
+ let latestTimestamp = null;
1129
+ Object.entries(response.trades).forEach(([symbol, trades]) => {
1130
+ if (trades && trades.length > 0) {
1131
+ allTrades[symbol] = [...allTrades[symbol], ...trades];
1132
+ pageTradesCount += trades.length;
1133
+ trades.forEach((trade) => {
1134
+ const tradeDate = new Date(trade.t);
1135
+ if (!earliestTimestamp || tradeDate < earliestTimestamp) {
1136
+ earliestTimestamp = tradeDate;
1137
+ }
1138
+ if (!latestTimestamp || tradeDate > latestTimestamp) {
1139
+ latestTimestamp = tradeDate;
1140
+ }
1141
+ });
1142
+ }
1143
+ });
1144
+ totalTradesCount += pageTradesCount;
1145
+ pageToken = response.next_page_token || null;
1146
+ hasMorePages = !!pageToken;
1147
+ const dateRangeStr = earliestTimestamp && latestTimestamp ? `${earliestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })} to ${latestTimestamp.toLocaleDateString("en-US", { timeZone: "America/New_York" })}` : "unknown range";
1148
+ log2(
1149
+ `Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} option trades (total: ${totalTradesCount.toLocaleString()}) for ${symbols.length} symbols, date range: ${dateRangeStr}${hasMorePages ? ", more pages available" : ", complete"}`,
1150
+ {
1151
+ type: "debug"
1152
+ }
1153
+ );
1154
+ if (pageCount > 1e3) {
1155
+ log2(`Stopping options trades pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
1156
+ break;
1157
+ }
1158
+ }
1159
+ const symbolCounts = Object.entries(allTrades).map(([symbol, trades]) => `${symbol}: ${trades.length}`).join(", ");
1160
+ log2(
1161
+ `Options trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages (${symbolCounts})`
1162
+ );
1163
+ return {
1164
+ trades: allTrades,
1165
+ next_page_token: void 0
1166
+ // Always undefined since we fetch all pages
1167
+ };
1168
+ }
1169
+ /**
1170
+ * Get snapshots for option contract symbols
1171
+ * Provides latest trade, latest quote, and greeks for each contract symbol
1172
+ * @param params Options snapshots request parameters
1173
+ * @returns Snapshot data for each option contract symbol
1174
+
1175
+ * @see https://docs.alpaca.markets/reference/optionsnapshots
1176
+ */
1177
+ async getOptionsSnapshot(params) {
1178
+ const { limit, page_token, ...requestParams } = params;
1179
+ return this.makeRequest("/options/snapshots", "GET", requestParams, "v1beta1");
1180
+ }
1181
+ /**
1182
+ * Get condition codes for options trades or quotes
1183
+ * Returns the mapping between condition codes and their descriptions
1184
+ * @param tickType The type of tick data ('trade' or 'quote')
1185
+ * @returns Mapping of condition codes to descriptions
1186
+
1187
+ * @see https://docs.alpaca.markets/reference/optionmetaconditions
1188
+ */
1189
+ async getOptionsConditionCodes(tickType) {
1190
+ return this.makeRequest(`/options/meta/conditions/${tickType}`, "GET", void 0, "v1beta1");
1191
+ }
1192
+ /**
1193
+ * Get exchange codes for options
1194
+ * Returns the mapping between option exchange codes and exchange names
1195
+ * @returns Mapping of exchange codes to exchange names
1196
+
1197
+ * @see https://docs.alpaca.markets/reference/optionmetaexchanges
1198
+ */
1199
+ async getOptionsExchangeCodes() {
1200
+ return this.makeRequest("/options/meta/exchanges", "GET", void 0, "v1beta1");
1201
+ }
1202
+ /**
1203
+ * Analyzes an array of option bars and returns a summary string
1204
+ * @param bars Array of option bars to analyze
1205
+ * @returns A string summarizing the option price data
1206
+ */
1207
+ static analyzeOptionBars(bars) {
1208
+ if (!bars || bars.length === 0) {
1209
+ return "No option price data available";
1210
+ }
1211
+ const firstBar = bars[0];
1212
+ const lastBar = bars[bars.length - 1];
1213
+ const priceChange = lastBar.c - firstBar.o;
1214
+ const percentChange = priceChange / firstBar.o * 100;
1215
+ const volumeChange = lastBar.v - firstBar.v;
1216
+ const percentVolumeChange = firstBar.v > 0 ? volumeChange / firstBar.v * 100 : 0;
1217
+ const high = Math.max(...bars.map((bar) => bar.h));
1218
+ const low = Math.min(...bars.map((bar) => bar.l));
1219
+ const totalVolume = bars.reduce((sum, bar) => sum + bar.v, 0);
1220
+ const avgVolume = totalVolume / bars.length;
1221
+ return `Option Price: $${firstBar.o.toFixed(2)} -> $${lastBar.c.toFixed(2)} (${percentChange.toFixed(2)}%), Volume: ${firstBar.v.toLocaleString()} -> ${lastBar.v.toLocaleString()} (${percentVolumeChange.toFixed(2)}%), High: $${high.toFixed(2)}, Low: $${low.toFixed(2)}, Avg Volume: ${avgVolume.toLocaleString()}`;
1222
+ }
1223
+ /**
1224
+ * Formats option greeks for display
1225
+ * @param greeks Option greeks object
1226
+ * @returns Formatted string with greek values
1227
+ */
1228
+ static formatOptionGreeks(greeks) {
1229
+ if (!greeks) {
1230
+ return "No greeks data available";
1231
+ }
1232
+ const parts = [];
1233
+ if (greeks.delta !== void 0) parts.push(`Delta: ${greeks.delta.toFixed(4)}`);
1234
+ if (greeks.gamma !== void 0) parts.push(`Gamma: ${greeks.gamma.toFixed(4)}`);
1235
+ if (greeks.theta !== void 0) parts.push(`Theta: ${greeks.theta.toFixed(4)}`);
1236
+ if (greeks.vega !== void 0) parts.push(`Vega: ${greeks.vega.toFixed(4)}`);
1237
+ if (greeks.rho !== void 0) parts.push(`Rho: ${greeks.rho.toFixed(4)}`);
1238
+ return parts.length > 0 ? parts.join(", ") : "No greeks data available";
1239
+ }
1240
+ /**
1241
+ * Interprets condition codes using the provided condition codes mapping
1242
+ * @param conditionCodes Array of condition codes from trade or quote
1243
+ * @param conditionCodesMap Mapping of condition codes to descriptions
1244
+ * @returns Formatted string with condition descriptions
1245
+ */
1246
+ static interpretConditionCodes(conditionCodes, conditionCodesMap) {
1247
+ if (!conditionCodes || conditionCodes.length === 0) {
1248
+ return "No conditions";
1249
+ }
1250
+ const descriptions = conditionCodes.map((code) => conditionCodesMap[code] || `Unknown (${code})`).filter((desc) => desc !== void 0);
1251
+ return descriptions.length > 0 ? descriptions.join(", ") : "No condition descriptions available";
1252
+ }
1253
+ /**
1254
+ * Gets the exchange name from exchange code using the provided exchange codes mapping
1255
+ * @param exchangeCode Exchange code from trade or quote
1256
+ * @param exchangeCodesMap Mapping of exchange codes to names
1257
+ * @returns Exchange name or formatted unknown exchange
1258
+ */
1259
+ static getExchangeName(exchangeCode, exchangeCodesMap) {
1260
+ return exchangeCodesMap[exchangeCode] || `Unknown Exchange (${exchangeCode})`;
1261
+ }
1262
+ /**
1263
+ * Fetches news articles from Alpaca API for a symbol, paginating through all results.
1264
+ * @param symbol The symbol to fetch news for (e.g., 'AAPL')
1265
+ * @param params Optional parameters: start, end, limit, sort, include_content
1266
+ * @returns Array of SimpleNews articles
1267
+ */
1268
+ async fetchNews(symbol, params) {
1269
+ const defaultParams = {
1270
+ start: new Date(Date.now() - 24 * 60 * 60 * 1e3),
1271
+ end: /* @__PURE__ */ new Date(),
1272
+ limit: 10,
1273
+ sort: "desc",
1274
+ include_content: true
1275
+ };
1276
+ const mergedParams = { ...defaultParams, ...params };
1277
+ let newsArticles = [];
1278
+ let pageToken = null;
1279
+ let hasMorePages = true;
1280
+ let fetchedCount = 0;
1281
+ const maxLimit = mergedParams.limit;
1282
+ function cleanContent(content) {
1283
+ if (!content) return void 0;
1284
+ return content.replace(/\s+/g, " ").trim();
1285
+ }
1286
+ while (hasMorePages) {
1287
+ const queryParams = new URLSearchParams({
1288
+ ...mergedParams.start && { start: new Date(mergedParams.start).toISOString() },
1289
+ ...mergedParams.end && { end: new Date(mergedParams.end).toISOString() },
1290
+ ...symbol && { symbols: symbol },
1291
+ ...mergedParams.limit && { limit: Math.min(50, maxLimit - fetchedCount).toString() },
1292
+ ...mergedParams.sort && { sort: mergedParams.sort },
1293
+ ...mergedParams.include_content !== void 0 ? { include_content: mergedParams.include_content.toString() } : {},
1294
+ ...pageToken && { page_token: pageToken }
1295
+ });
1296
+ const url = `${this.v1beta1url}/news?${queryParams}`;
1297
+ log2(`Fetching news from: ${url}`, { type: "debug", symbol });
1298
+ const response = await fetch(url, {
1299
+ method: "GET",
1300
+ headers: this.headers
1301
+ });
1302
+ if (!response.ok) {
1303
+ const errorText = await response.text();
1304
+ log2(`Alpaca news API error (${response.status}): ${errorText}`, { type: "error", symbol });
1305
+ throw new Error(`Alpaca news API error (${response.status}): ${errorText}`);
1306
+ }
1307
+ const data = await response.json();
1308
+ if (!data.news || !Array.isArray(data.news)) {
1309
+ log2(`No news data found in Alpaca response for ${symbol}`, { type: "warn", symbol });
1310
+ break;
1311
+ }
1312
+ const transformedNews = data.news.map((article) => ({
1313
+ symbols: article.symbols,
1314
+ title: article.headline,
1315
+ summary: cleanContent(article.summary) ?? "",
1316
+ content: article.content ? cleanContent(article.content) : void 0,
1317
+ url: article.url,
1318
+ source: article.source,
1319
+ author: article.author,
1320
+ date: article.updated_at || article.created_at,
1321
+ updatedDate: article.updated_at || article.created_at,
1322
+ sentiment: 0
1323
+ }));
1324
+ newsArticles = newsArticles.concat(transformedNews);
1325
+ fetchedCount = newsArticles.length;
1326
+ pageToken = data.next_page_token || null;
1327
+ hasMorePages = !!pageToken && (!maxLimit || fetchedCount < maxLimit);
1328
+ log2(
1329
+ `Fetched ${transformedNews.length} news articles (total: ${fetchedCount}) for ${symbol}. More pages: ${hasMorePages}`,
1330
+ { type: "debug", symbol }
1331
+ );
1332
+ if (maxLimit && fetchedCount >= maxLimit) {
1333
+ newsArticles = newsArticles.slice(0, maxLimit);
1334
+ break;
1335
+ }
1336
+ }
1337
+ return newsArticles;
1338
+ }
1339
+ // ===== CRYPTO MARKET DATA METHODS =====
1340
+ /**
1341
+ * Get historical OHLCV bars for crypto symbols
1342
+ * Automatically handles pagination to fetch all available data
1343
+ * @param params Parameters for crypto historical bars request
1344
+ * @returns Historical bars data with all pages combined
1345
+ */
1346
+ async getCryptoHistoricalBars(params) {
1347
+ const symbols = params.symbols;
1348
+ const symbolsStr = symbols.join(",");
1349
+ let allBars = {};
1350
+ let pageToken = null;
1351
+ let hasMorePages = true;
1352
+ let totalBarsCount = 0;
1353
+ let pageCount = 0;
1354
+ symbols.forEach((symbol) => {
1355
+ allBars[symbol] = [];
1356
+ });
1357
+ log2(
1358
+ `Starting crypto historical bars fetch for ${symbols.length} symbols (${params.timeframe}, ${params.start || "no start"} to ${params.end || "no end"})`
1359
+ );
1360
+ while (hasMorePages) {
1361
+ pageCount++;
1362
+ const requestParams = {
1363
+ ...params,
1364
+ symbols: symbolsStr,
1365
+ ...pageToken && { page_token: pageToken }
1366
+ };
1367
+ const response = await this.makeRequest(
1368
+ "/crypto/us/bars",
1369
+ "GET",
1370
+ requestParams,
1371
+ "v1beta3"
1372
+ );
1373
+ if (!response.bars) {
1374
+ log2(`No crypto bars data found in response for ${symbols.length} symbols`, { type: "warn" });
1375
+ break;
1376
+ }
1377
+ let pageBarsCount = 0;
1378
+ Object.entries(response.bars).forEach(([symbol, bars]) => {
1379
+ if (bars && bars.length > 0) {
1380
+ allBars[symbol] = [...allBars[symbol], ...bars];
1381
+ pageBarsCount += bars.length;
1382
+ }
1383
+ });
1384
+ totalBarsCount += pageBarsCount;
1385
+ pageToken = response.next_page_token || null;
1386
+ hasMorePages = !!pageToken;
1387
+ log2(
1388
+ `Page ${pageCount}: Fetched ${pageBarsCount.toLocaleString()} crypto bars (total: ${totalBarsCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ", more pages available" : ", complete"}`,
1389
+ { type: "debug" }
1390
+ );
1391
+ if (pageCount > 1e3) {
1392
+ log2(`Stopping crypto bars pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
1393
+ break;
1394
+ }
1395
+ }
1396
+ log2(
1397
+ `Crypto historical bars fetch complete: ${totalBarsCount.toLocaleString()} total bars across ${pageCount} pages`
1398
+ );
1399
+ return {
1400
+ bars: allBars,
1401
+ next_page_token: null
1402
+ // Always null since we fetch all pages
1403
+ };
1404
+ }
1405
+ /**
1406
+ * Get the most recent minute bar for requested crypto symbols
1407
+ * @param symbols Array of crypto symbols to query
1408
+ * @returns Latest bar data for each symbol
1409
+ */
1410
+ async getCryptoLatestBars(symbols) {
1411
+ return this.makeRequest("/crypto/us/latest/bars", "GET", { symbols: symbols.join(",") }, "v1beta3");
1412
+ }
1413
+ /**
1414
+ * Get historical quotes for crypto symbols
1415
+ * Automatically handles pagination to fetch all available data
1416
+ * @param params Parameters for crypto historical quotes request
1417
+ * @returns Historical quotes data with all pages combined
1418
+ */
1419
+ async getCryptoHistoricalQuotes(params) {
1420
+ const symbols = params.symbols;
1421
+ const symbolsStr = symbols.join(",");
1422
+ let allQuotes = {};
1423
+ let pageToken = null;
1424
+ let hasMorePages = true;
1425
+ let totalQuotesCount = 0;
1426
+ let pageCount = 0;
1427
+ symbols.forEach((symbol) => {
1428
+ allQuotes[symbol] = [];
1429
+ });
1430
+ log2(
1431
+ `Starting crypto historical quotes fetch for ${symbols.length} symbols (${params.start || "no start"} to ${params.end || "no end"})`
1432
+ );
1433
+ while (hasMorePages) {
1434
+ pageCount++;
1435
+ const requestParams = {
1436
+ ...params,
1437
+ symbols: symbolsStr,
1438
+ ...pageToken && { page_token: pageToken }
1439
+ };
1440
+ const response = await this.makeRequest(
1441
+ "/crypto/us/quotes",
1442
+ "GET",
1443
+ requestParams,
1444
+ "v1beta3"
1445
+ );
1446
+ if (!response.quotes) {
1447
+ log2(`No crypto quotes data found in response for ${symbols.length} symbols`, { type: "warn" });
1448
+ break;
1449
+ }
1450
+ let pageQuotesCount = 0;
1451
+ Object.entries(response.quotes).forEach(([symbol, quotes]) => {
1452
+ if (quotes && quotes.length > 0) {
1453
+ allQuotes[symbol] = [...allQuotes[symbol], ...quotes];
1454
+ pageQuotesCount += quotes.length;
1455
+ }
1456
+ });
1457
+ totalQuotesCount += pageQuotesCount;
1458
+ pageToken = response.next_page_token || null;
1459
+ hasMorePages = !!pageToken;
1460
+ log2(
1461
+ `Page ${pageCount}: Fetched ${pageQuotesCount.toLocaleString()} crypto quotes (total: ${totalQuotesCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ", more pages available" : ", complete"}`,
1462
+ { type: "debug" }
1463
+ );
1464
+ if (pageCount > 1e3) {
1465
+ log2(`Stopping crypto quotes pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
1466
+ break;
1467
+ }
1468
+ }
1469
+ log2(
1470
+ `Crypto historical quotes fetch complete: ${totalQuotesCount.toLocaleString()} total quotes across ${pageCount} pages`
1471
+ );
1472
+ return {
1473
+ quotes: allQuotes,
1474
+ next_page_token: null
1475
+ // Always null since we fetch all pages
1476
+ };
1477
+ }
1478
+ /**
1479
+ * Get the most recent quotes for requested crypto symbols
1480
+ * @param symbols Array of crypto symbols to query
1481
+ * @returns Latest quote data for each symbol
1482
+ */
1483
+ async getCryptoLatestQuotes(symbols) {
1484
+ if (!symbols || symbols.length === 0) {
1485
+ log2("No symbols provided to getCryptoLatestQuotes, returning empty response", { type: "warn" });
1486
+ return { quotes: {} };
1487
+ }
1488
+ return this.makeRequest("/crypto/us/latest/quotes", "GET", { symbols: symbols.join(",") }, "v1beta3");
1489
+ }
1490
+ /**
1491
+ * Get historical trades for crypto symbols
1492
+ * Automatically handles pagination to fetch all available data
1493
+ * @param params Parameters for crypto historical trades request
1494
+ * @returns Historical trades data with all pages combined
1495
+ */
1496
+ async getCryptoHistoricalTrades(params) {
1497
+ const symbols = params.symbols;
1498
+ const symbolsStr = symbols.join(",");
1499
+ let allTrades = {};
1500
+ let pageToken = null;
1501
+ let hasMorePages = true;
1502
+ let totalTradesCount = 0;
1503
+ let pageCount = 0;
1504
+ symbols.forEach((symbol) => {
1505
+ allTrades[symbol] = [];
1506
+ });
1507
+ log2(
1508
+ `Starting crypto historical trades fetch for ${symbols.length} symbols (${params.start || "no start"} to ${params.end || "no end"})`
1509
+ );
1510
+ while (hasMorePages) {
1511
+ pageCount++;
1512
+ const requestParams = {
1513
+ ...params,
1514
+ symbols: symbolsStr,
1515
+ ...pageToken && { page_token: pageToken }
1516
+ };
1517
+ const response = await this.makeRequest(
1518
+ "/crypto/us/trades",
1519
+ "GET",
1520
+ requestParams,
1521
+ "v1beta3"
1522
+ );
1523
+ if (!response.trades) {
1524
+ log2(`No crypto trades data found in response for ${symbols.length} symbols`, { type: "warn" });
1525
+ break;
1526
+ }
1527
+ let pageTradesCount = 0;
1528
+ Object.entries(response.trades).forEach(([symbol, trades]) => {
1529
+ if (trades && trades.length > 0) {
1530
+ allTrades[symbol] = [...allTrades[symbol], ...trades];
1531
+ pageTradesCount += trades.length;
1532
+ }
1533
+ });
1534
+ totalTradesCount += pageTradesCount;
1535
+ pageToken = response.next_page_token || null;
1536
+ hasMorePages = !!pageToken;
1537
+ log2(
1538
+ `Page ${pageCount}: Fetched ${pageTradesCount.toLocaleString()} crypto trades (total: ${totalTradesCount.toLocaleString()}) for ${symbols.length} symbols${hasMorePages ? ", more pages available" : ", complete"}`,
1539
+ { type: "debug" }
1540
+ );
1541
+ if (pageCount > 1e3) {
1542
+ log2(`Stopping crypto trades pagination after ${pageCount} pages to prevent infinite loop`, { type: "warn" });
1543
+ break;
1544
+ }
1545
+ }
1546
+ log2(
1547
+ `Crypto historical trades fetch complete: ${totalTradesCount.toLocaleString()} total trades across ${pageCount} pages`
1548
+ );
1549
+ return {
1550
+ trades: allTrades,
1551
+ next_page_token: null
1552
+ // Always null since we fetch all pages
1553
+ };
1554
+ }
1555
+ /**
1556
+ * Get the most recent trades for requested crypto symbols
1557
+ * @param symbols Array of crypto symbols to query
1558
+ * @returns Latest trade data for each symbol
1559
+ */
1560
+ async getCryptoLatestTrades(symbols) {
1561
+ if (!symbols || symbols.length === 0) {
1562
+ log2("No symbols provided to getCryptoLatestTrades, returning empty response", { type: "warn" });
1563
+ return { trades: {} };
1564
+ }
1565
+ return this.makeRequest("/crypto/us/latest/trades", "GET", { symbols: symbols.join(",") }, "v1beta3");
1566
+ }
1567
+ /**
1568
+ * Get snapshots for crypto symbols
1569
+ * Returns the latest trade, latest quote, latest minute bar, latest daily bar, and previous daily bar data
1570
+ * @param symbols Array of crypto symbols to query
1571
+ * @returns Snapshot data for each symbol
1572
+ */
1573
+ async getCryptoSnapshots(symbols) {
1574
+ if (!symbols || symbols.length === 0) {
1575
+ log2("No symbols provided to getCryptoSnapshots, returning empty response", { type: "warn" });
1576
+ return { snapshots: {} };
1577
+ }
1578
+ return this.makeRequest("/crypto/us/snapshots", "GET", { symbols: symbols.join(",") }, "v1beta3");
1579
+ }
1580
+ /**
1581
+ * Get the latest orderbook for requested crypto symbols
1582
+ * @param symbols Array of crypto symbols to query
1583
+ * @returns Latest orderbook data for each symbol
1584
+ */
1585
+ async getCryptoLatestOrderbooks(symbols) {
1586
+ if (!symbols || symbols.length === 0) {
1587
+ log2("No symbols provided to getCryptoLatestOrderbooks, returning empty response", { type: "warn" });
1588
+ return { orderbooks: {} };
1589
+ }
1590
+ return this.makeRequest("/crypto/us/latest/orderbooks", "GET", { symbols: symbols.join(",") }, "v1beta3");
1591
+ }
1592
+ /**
1593
+ * Analyzes an array of crypto bars and returns a summary string
1594
+ * @param bars Array of crypto bars to analyze
1595
+ * @returns A string summarizing the crypto price data
1596
+ */
1597
+ static analyzeCryptoBars(bars) {
1598
+ if (!bars || bars.length === 0) {
1599
+ return "No crypto price data available";
1600
+ }
1601
+ const firstBar = bars[0];
1602
+ const lastBar = bars[bars.length - 1];
1603
+ const priceChange = lastBar.c - firstBar.o;
1604
+ const percentChange = priceChange / firstBar.o * 100;
1605
+ const volumeChange = lastBar.v - firstBar.v;
1606
+ const percentVolumeChange = firstBar.v > 0 ? volumeChange / firstBar.v * 100 : 0;
1607
+ const high = Math.max(...bars.map((bar) => bar.h));
1608
+ const low = Math.min(...bars.map((bar) => bar.l));
1609
+ const totalVolume = bars.reduce((sum, bar) => sum + bar.v, 0);
1610
+ const avgVolume = totalVolume / bars.length;
1611
+ return `Crypto Price: $${firstBar.o.toFixed(6)} -> $${lastBar.c.toFixed(6)} (${percentChange.toFixed(2)}%), Volume: ${firstBar.v.toLocaleString()} -> ${lastBar.v.toLocaleString()} (${percentVolumeChange.toFixed(2)}%), High: $${high.toFixed(6)}, Low: $${low.toFixed(6)}, Avg Volume: ${avgVolume.toLocaleString()}`;
1612
+ }
1613
+ };
1614
+ marketDataAPI = AlpacaMarketDataAPI.getInstance();
1615
+ }
1616
+ });
1617
+
1618
+ // src/alpaca-trading-api.ts
1619
+ import WebSocket2 from "ws";
1620
+ var init_alpaca_trading_api = __esm({
1621
+ "src/alpaca-trading-api.ts"() {
1622
+ "use strict";
1623
+ init_logging();
1624
+ init_alpaca_market_data_api();
1625
+ init_market_time();
1626
+ }
1627
+ });
1628
+
1629
+ // src/index.ts
1630
+ init_market_time();
1631
+
1632
+ // src/types/llm-types.ts
1633
+ var OPENAI_MODELS = [
1634
+ "gpt-5.6",
1635
+ "gpt-5.6-sol",
1636
+ "gpt-5.6-terra",
1637
+ "gpt-5.6-luna",
1638
+ "gpt-5",
1639
+ "gpt-5-mini",
1640
+ "gpt-5.4-mini",
1641
+ "gpt-5.4-nano",
1642
+ "gpt-5-nano",
1643
+ "gpt-5.1",
1644
+ "gpt-5.4",
1645
+ "gpt-5.5",
1646
+ "gpt-5.5-pro",
1647
+ "gpt-5.2",
1648
+ "gpt-5.2-pro",
1649
+ "gpt-5.1-codex",
1650
+ "gpt-5.1-codex-max"
1651
+ ];
1652
+ function isOpenAIModel(model) {
1653
+ return OPENAI_MODELS.includes(model);
1654
+ }
1655
+ var OPENROUTER_MODELS = [
1656
+ "openai/gpt-5",
1657
+ "openai/gpt-5-mini",
1658
+ "openai/gpt-5.4-mini",
1659
+ "openai/gpt-5.4-nano",
1660
+ "openai/gpt-5-nano",
1661
+ "openai/gpt-5.1",
1662
+ "openai/gpt-5.4",
1663
+ "openai/gpt-5.4-pro",
1664
+ "openai/gpt-5.5",
1665
+ "openai/gpt-5.5-pro",
1666
+ "openai/gpt-5.2",
1667
+ "openai/gpt-5.2-pro",
1668
+ "openai/gpt-5.1-codex",
1669
+ "openai/gpt-5.1-codex-max",
1670
+ "openai/gpt-oss-120b",
1671
+ "z.ai/glm-4.5",
1672
+ "z.ai/glm-4.5-air",
1673
+ "google/gemini-2.5-flash",
1674
+ "google/gemini-2.5-flash-lite",
1675
+ "deepseek/deepseek-r1-0528",
1676
+ "deepseek/deepseek-chat-v3-0324"
1677
+ ];
1678
+ function isOpenRouterModel(model) {
1679
+ return OPENROUTER_MODELS.includes(model);
1680
+ }
1681
+
1682
+ // src/llm-openai.ts
1683
+ import OpenAI2 from "openai";
1684
+ import { zodTextFormat } from "openai/helpers/zod";
1685
+
1686
+ // src/llm-config.ts
1687
+ var DEFAULT_MODEL = "gpt-5.6-luna";
1688
+ var GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS = 272e3;
1689
+ var GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER = 2;
1690
+ var GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER = 1.5;
1691
+ var openAiModelCosts = {
1692
+ "gpt-5.6": {
1693
+ inputCost: 5 / 1e6,
1694
+ cacheHitCost: 0.5 / 1e6,
1695
+ cacheWriteCost: 6.25 / 1e6,
1696
+ outputCost: 30 / 1e6
1697
+ },
1698
+ "gpt-5.6-sol": {
1699
+ inputCost: 5 / 1e6,
1700
+ cacheHitCost: 0.5 / 1e6,
1701
+ cacheWriteCost: 6.25 / 1e6,
1702
+ outputCost: 30 / 1e6
1703
+ },
1704
+ "gpt-5.6-terra": {
1705
+ inputCost: 2.5 / 1e6,
1706
+ cacheHitCost: 0.25 / 1e6,
1707
+ cacheWriteCost: 3.125 / 1e6,
1708
+ outputCost: 15 / 1e6
1709
+ },
1710
+ "gpt-5.6-luna": {
1711
+ inputCost: 1 / 1e6,
1712
+ cacheHitCost: 0.1 / 1e6,
1713
+ cacheWriteCost: 1.25 / 1e6,
1714
+ outputCost: 6 / 1e6
1715
+ },
1716
+ "gpt-5": {
1717
+ inputCost: 1.25 / 1e6,
1718
+ cacheHitCost: 0.125 / 1e6,
1719
+ outputCost: 10 / 1e6
1720
+ },
1721
+ "gpt-5-mini": {
1722
+ inputCost: 0.25 / 1e6,
1723
+ cacheHitCost: 0.025 / 1e6,
1724
+ outputCost: 2 / 1e6
1725
+ },
1726
+ "gpt-5.4-mini": {
1727
+ inputCost: 0.75 / 1e6,
1728
+ cacheHitCost: 0.075 / 1e6,
1729
+ outputCost: 4.5 / 1e6
1730
+ },
1731
+ "gpt-5.4-nano": {
1732
+ inputCost: 0.2 / 1e6,
1733
+ cacheHitCost: 0.02 / 1e6,
1734
+ outputCost: 1.25 / 1e6
1735
+ },
1736
+ "gpt-5-nano": {
1737
+ inputCost: 0.05 / 1e6,
1738
+ cacheHitCost: 5e-3 / 1e6,
1739
+ outputCost: 0.4 / 1e6
1740
+ },
1741
+ "gpt-5.1": {
1742
+ inputCost: 1.25 / 1e6,
1743
+ cacheHitCost: 0.125 / 1e6,
1744
+ outputCost: 10 / 1e6
1745
+ },
1746
+ "gpt-5.4": {
1747
+ inputCost: 2.5 / 1e6,
1748
+ cacheHitCost: 0.25 / 1e6,
1749
+ outputCost: 15 / 1e6
1750
+ },
1751
+ "gpt-5.5": {
1752
+ inputCost: 5 / 1e6,
1753
+ cacheHitCost: 0.5 / 1e6,
1754
+ outputCost: 30 / 1e6
1755
+ },
1756
+ "gpt-5.5-pro": {
1757
+ inputCost: 30 / 1e6,
1758
+ outputCost: 180 / 1e6
1759
+ },
1760
+ "gpt-5.2": {
1761
+ inputCost: 1.75 / 1e6,
1762
+ cacheHitCost: 0.175 / 1e6,
1763
+ outputCost: 14 / 1e6
1764
+ },
1765
+ "gpt-5.2-pro": {
1766
+ inputCost: 21 / 1e6,
1767
+ outputCost: 168 / 1e6
1768
+ },
1769
+ "gpt-5.1-codex": {
1770
+ inputCost: 1.25 / 1e6,
1771
+ cacheHitCost: 0.125 / 1e6,
1772
+ outputCost: 10 / 1e6
1773
+ },
1774
+ "gpt-5.1-codex-max": {
1775
+ inputCost: 1.25 / 1e6,
1776
+ cacheHitCost: 0.125 / 1e6,
1777
+ outputCost: 10 / 1e6
1778
+ }
1779
+ };
1780
+ var deepseekModelCosts = {
1781
+ "deepseek-chat": {
1782
+ inputCost: 0.27 / 1e6,
1783
+ // $0.27 per 1M tokens (Cache miss price)
1784
+ cacheHitCost: 0.07 / 1e6,
1785
+ // $0.07 per 1M tokens (Cache hit price)
1786
+ outputCost: 1.1 / 1e6
1787
+ // $1.10 per 1M tokens
1788
+ },
1789
+ "deepseek-reasoner": {
1790
+ inputCost: 0.55 / 1e6,
1791
+ // $0.55 per 1M tokens (Cache miss price)
1792
+ cacheHitCost: 0.14 / 1e6,
1793
+ // $0.14 per 1M tokens (Cache hit price)
1794
+ outputCost: 2.19 / 1e6
1795
+ // $2.19 per 1M tokens
1796
+ }
1797
+ };
1798
+ function shouldUseGPT5HighContextPricing(model, inputTokens) {
1799
+ return (model === "gpt-5.4" || model === "gpt-5.5" || model === "gpt-5.6" || model === "gpt-5.6-sol" || model === "gpt-5.6-terra" || model === "gpt-5.6-luna") && inputTokens > GPT_5_HIGH_CONTEXT_THRESHOLD_TOKENS;
1800
+ }
1801
+ var DEFAULT_IMAGE_PRICING_QUALITY = "high";
1802
+ var DEFAULT_IMAGE_PRICING_SIZE = "1024x1024";
1803
+ var openAiImageCostByQualityAndSize = {
1804
+ "gpt-image-2": {
1805
+ low: {
1806
+ "1024x1024": 6e-3,
1807
+ "1024x1536": 5e-3,
1808
+ "1536x1024": 5e-3
1809
+ },
1810
+ medium: {
1811
+ "1024x1024": 0.053,
1812
+ "1024x1536": 0.041,
1813
+ "1536x1024": 0.041
1814
+ },
1815
+ high: {
1816
+ "1024x1024": 0.211,
1817
+ "1024x1536": 0.165,
1818
+ "1536x1024": 0.165
1819
+ }
1820
+ },
1821
+ "gpt-image-2-2026-04-21": {
1822
+ low: {
1823
+ "1024x1024": 6e-3,
1824
+ "1024x1536": 5e-3,
1825
+ "1536x1024": 5e-3
1826
+ },
1827
+ medium: {
1828
+ "1024x1024": 0.053,
1829
+ "1024x1536": 0.041,
1830
+ "1536x1024": 0.041
1831
+ },
1832
+ high: {
1833
+ "1024x1024": 0.211,
1834
+ "1024x1536": 0.165,
1835
+ "1536x1024": 0.165
1836
+ }
1837
+ },
1838
+ "gpt-image-1.5": {
1839
+ low: {
1840
+ "1024x1024": 9e-3,
1841
+ "1024x1536": 0.013,
1842
+ "1536x1024": 0.013
1843
+ },
1844
+ medium: {
1845
+ "1024x1024": 0.034,
1846
+ "1024x1536": 0.05,
1847
+ "1536x1024": 0.05
1848
+ },
1849
+ high: {
1850
+ "1024x1024": 0.133,
1851
+ "1024x1536": 0.2,
1852
+ "1536x1024": 0.2
1853
+ }
1854
+ },
1855
+ "gpt-image-1": {
1856
+ low: {
1857
+ "1024x1024": 0.011,
1858
+ "1024x1536": 0.016,
1859
+ "1536x1024": 0.016
1860
+ },
1861
+ medium: {
1862
+ "1024x1024": 0.042,
1863
+ "1024x1536": 0.063,
1864
+ "1536x1024": 0.063
1865
+ },
1866
+ high: {
1867
+ "1024x1024": 0.167,
1868
+ "1024x1536": 0.25,
1869
+ "1536x1024": 0.25
1870
+ }
1871
+ },
1872
+ "gpt-image-1-mini": {
1873
+ low: {
1874
+ "1024x1024": 5e-3,
1875
+ "1024x1536": 6e-3,
1876
+ "1536x1024": 6e-3
1877
+ },
1878
+ medium: {
1879
+ "1024x1024": 0.011,
1880
+ "1024x1536": 0.015,
1881
+ "1536x1024": 0.015
1882
+ },
1883
+ high: {
1884
+ "1024x1024": 0.036,
1885
+ "1024x1536": 0.052,
1886
+ "1536x1024": 0.052
1887
+ }
1888
+ }
1889
+ };
1890
+ var openAiImageCosts = {
1891
+ "gpt-image-2": openAiImageCostByQualityAndSize["gpt-image-2"][DEFAULT_IMAGE_PRICING_QUALITY][DEFAULT_IMAGE_PRICING_SIZE],
1892
+ "gpt-image-2-2026-04-21": openAiImageCostByQualityAndSize["gpt-image-2-2026-04-21"][DEFAULT_IMAGE_PRICING_QUALITY][DEFAULT_IMAGE_PRICING_SIZE],
1893
+ "gpt-image-1.5": openAiImageCostByQualityAndSize["gpt-image-1.5"][DEFAULT_IMAGE_PRICING_QUALITY][DEFAULT_IMAGE_PRICING_SIZE],
1894
+ "gpt-image-1": openAiImageCostByQualityAndSize["gpt-image-1"][DEFAULT_IMAGE_PRICING_QUALITY][DEFAULT_IMAGE_PRICING_SIZE],
1895
+ "gpt-image-1-mini": openAiImageCostByQualityAndSize["gpt-image-1-mini"][DEFAULT_IMAGE_PRICING_QUALITY][DEFAULT_IMAGE_PRICING_SIZE]
1896
+ };
1897
+ function calculateCost(provider, model, inputTokens, outputTokens, reasoningTokens, cacheHitTokens, cacheWriteTokens) {
1898
+ if (typeof provider !== "string" || typeof model !== "string" || typeof inputTokens !== "number" || typeof outputTokens !== "number" || reasoningTokens !== void 0 && typeof reasoningTokens !== "number" || cacheHitTokens !== void 0 && typeof cacheHitTokens !== "number" || cacheWriteTokens !== void 0 && typeof cacheWriteTokens !== "number") {
1899
+ return 0;
1900
+ }
1901
+ const modelCosts = provider === "deepseek" ? deepseekModelCosts[model] : openAiModelCosts[model];
1902
+ if (!modelCosts) return 0;
1903
+ const boundedCacheHitTokens = Math.min(Math.max(cacheHitTokens || 0, 0), inputTokens);
1904
+ const boundedCacheWriteTokens = Math.min(Math.max(cacheWriteTokens || 0, 0), inputTokens - boundedCacheHitTokens);
1905
+ const uncachedInputTokens = inputTokens - boundedCacheHitTokens - boundedCacheWriteTokens;
1906
+ const inputCost = uncachedInputTokens * modelCosts.inputCost + boundedCacheHitTokens * (modelCosts.cacheHitCost || modelCosts.inputCost) + boundedCacheWriteTokens * (modelCosts.cacheWriteCost || modelCosts.inputCost);
1907
+ let outputCost = outputTokens * modelCosts.outputCost;
1908
+ let reasoningCost = (reasoningTokens || 0) * modelCosts.outputCost;
1909
+ if (provider === "openai" && shouldUseGPT5HighContextPricing(model, inputTokens)) {
1910
+ return inputCost * GPT_5_HIGH_CONTEXT_INPUT_MULTIPLIER + outputCost * GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER + reasoningCost * GPT_5_HIGH_CONTEXT_OUTPUT_MULTIPLIER;
1911
+ }
1912
+ return inputCost + outputCost + reasoningCost;
1913
+ }
1914
+
1915
+ // src/json-tools.ts
1916
+ import OpenAI from "openai";
1917
+
1918
+ // src/llm-images.ts
1919
+ import OpenAI3 from "openai";
1920
+ var MULTIMODAL_VISION_MODELS = new Set(OPENAI_MODELS);
1921
+
1922
+ // src/llm-deepseek.ts
1923
+ import OpenAI4 from "openai";
1924
+
1925
+ // src/llm-openrouter.ts
1926
+ import OpenAI5 from "openai";
1927
+
1928
+ // src/index.ts
1929
+ init_alpaca_trading_api();
1930
+ init_alpaca_market_data_api();
1931
+ init_alpaca_trading_api();
1932
+ init_alpaca_market_data_api();
1933
+
1934
+ // src/test.ts
1935
+ init_logging();
1936
+ init_alpaca_market_data_api();
1937
+ init_alpaca_trading_api();
1938
+ import { z } from "zod";
1939
+ var DEBUG_LOGGING2 = process.env["DEBUG"] === "true" || false;
1940
+ var log3 = (message, options = { type: "info" }) => {
1941
+ if (!DEBUG_LOGGING2 && options.type === "debug") {
1942
+ return;
1943
+ }
1944
+ log(message, { ...options, source: "Test" });
1945
+ };
1946
+ function assertNear(actual, expected, label) {
1947
+ const tolerance = 1e-12;
1948
+ if (Math.abs(actual - expected) > tolerance) {
1949
+ throw new Error(`${label} expected ${expected}, received ${actual}`);
1950
+ }
1951
+ }
1952
+ function testOpenAIModelRegistryAndPricing() {
1953
+ const directModel = "gpt-5.6-terra";
1954
+ const openRouterModel = "openai/gpt-5.4-nano";
1955
+ const gpt56Models = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
1956
+ for (const model of gpt56Models) {
1957
+ if (!isOpenAIModel(model) || !OPENAI_MODELS.includes(model)) {
1958
+ throw new Error(`${model} is missing from direct OpenAI model exports`);
1959
+ }
1960
+ }
1961
+ if (!isOpenRouterModel(openRouterModel) || !OPENROUTER_MODELS.includes(openRouterModel)) {
1962
+ throw new Error(`${openRouterModel} is missing from OpenRouter model exports`);
1963
+ }
1964
+ assertNear(calculateCost("openai", directModel, 1e5, 0), 0.25, "gpt-5.6-terra input pricing");
1965
+ assertNear(calculateCost("openai", directModel, 1e5, 0, 0, 1e5), 0.025, "gpt-5.6-terra cached input pricing");
1966
+ assertNear(calculateCost("openai", directModel, 0, 1e6), 15, "gpt-5.6-terra output pricing");
1967
+ assertNear(calculateCost("openai", directModel, 1e5, 0, 0, 0, 1e5), 0.3125, "gpt-5.6-terra cache-write pricing");
1968
+ assertNear(calculateCost("openai", "gpt-5.6-luna", 1e5, 1e5), 0.7, "gpt-5.6-luna pricing");
1969
+ assertNear(calculateCost("openai", directModel, 1e6, 0), 5, "gpt-5.6-terra long-context input pricing");
1970
+ assertNear(calculateCost("openai", "gpt-5.4-mini", 1e6, 1e6), 5.25, "gpt-5.4-mini pricing");
1971
+ if (DEFAULT_MODEL !== "gpt-5.6-luna") {
1972
+ throw new Error(`Expected gpt-5.6-luna as the default model, received ${DEFAULT_MODEL}`);
1973
+ }
1974
+ log3(`${directModel} registry and pricing test passed`);
1975
+ }
1976
+ testOpenAIModelRegistryAndPricing();
1977
+ //# sourceMappingURL=test.mjs.map