@tastytrade/api 5.0.0 → 6.0.0

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 (81) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +87 -43
  3. package/dist/account-streamer.d.ts +6 -3
  4. package/dist/account-streamer.d.ts.map +1 -1
  5. package/dist/account-streamer.js +21 -15
  6. package/dist/account-streamer.js.map +1 -1
  7. package/dist/logger.d.ts +5 -1
  8. package/dist/logger.d.ts.map +1 -1
  9. package/dist/logger.js +4 -0
  10. package/dist/logger.js.map +1 -1
  11. package/dist/market-data-streamer.d.ts.map +1 -1
  12. package/dist/market-data-streamer.js +1 -0
  13. package/dist/market-data-streamer.js.map +1 -1
  14. package/dist/models/access-token.d.ts +12 -0
  15. package/dist/models/access-token.d.ts.map +1 -0
  16. package/dist/models/access-token.js +35 -0
  17. package/dist/models/access-token.js.map +1 -0
  18. package/dist/models/tastytrade-session.d.ts.map +1 -1
  19. package/dist/models/tastytrade-session.js.map +1 -1
  20. package/dist/quote-streamer.d.ts +58 -0
  21. package/dist/quote-streamer.d.ts.map +1 -0
  22. package/dist/quote-streamer.js +132 -0
  23. package/dist/quote-streamer.js.map +1 -0
  24. package/dist/services/account-status-service.js +0 -1
  25. package/dist/services/account-status-service.js.map +1 -1
  26. package/dist/services/accounts-and-customers-service.js +0 -1
  27. package/dist/services/accounts-and-customers-service.js.map +1 -1
  28. package/dist/services/balances-and-positions-service.js +0 -1
  29. package/dist/services/balances-and-positions-service.js.map +1 -1
  30. package/dist/services/instruments-service.js +1 -2
  31. package/dist/services/instruments-service.js.map +1 -1
  32. package/dist/services/margin-requirements-service.js +0 -1
  33. package/dist/services/margin-requirements-service.js.map +1 -1
  34. package/dist/services/market-metrics-service.js +0 -1
  35. package/dist/services/market-metrics-service.js.map +1 -1
  36. package/dist/services/net-liquidating-value-history-service.js +0 -1
  37. package/dist/services/net-liquidating-value-history-service.js.map +1 -1
  38. package/dist/services/orders-service.js +0 -1
  39. package/dist/services/orders-service.js.map +1 -1
  40. package/dist/services/risk-parameters-service.js +0 -1
  41. package/dist/services/risk-parameters-service.js.map +1 -1
  42. package/dist/services/session-service.d.ts +6 -1
  43. package/dist/services/session-service.d.ts.map +1 -1
  44. package/dist/services/session-service.js +10 -2
  45. package/dist/services/session-service.js.map +1 -1
  46. package/dist/services/symbol-search-service.js +0 -1
  47. package/dist/services/symbol-search-service.js.map +1 -1
  48. package/dist/services/tastytrade-http-client.d.ts +16 -2
  49. package/dist/services/tastytrade-http-client.d.ts.map +1 -1
  50. package/dist/services/tastytrade-http-client.js +71 -14
  51. package/dist/services/tastytrade-http-client.js.map +1 -1
  52. package/dist/services/transactions-service.js +0 -1
  53. package/dist/services/transactions-service.js.map +1 -1
  54. package/dist/services/watchlists-service.js +0 -1
  55. package/dist/services/watchlists-service.js.map +1 -1
  56. package/dist/tastytrade-api.d.ts +12 -2
  57. package/dist/tastytrade-api.d.ts.map +1 -1
  58. package/dist/tastytrade-api.js +22 -6
  59. package/dist/tastytrade-api.js.map +1 -1
  60. package/dist/utils/json-util.d.ts.map +1 -1
  61. package/dist/utils/json-util.js +0 -2
  62. package/dist/utils/json-util.js.map +1 -1
  63. package/dist/utils/response-util.d.ts.map +1 -1
  64. package/dist/utils/response-util.js +0 -1
  65. package/dist/utils/response-util.js.map +1 -1
  66. package/eslint.config.cjs +46 -0
  67. package/lib/account-streamer.ts +31 -23
  68. package/lib/logger.ts +6 -1
  69. package/lib/models/access-token.ts +40 -0
  70. package/lib/models/tastytrade-session.ts +1 -1
  71. package/lib/quote-streamer.ts +151 -0
  72. package/lib/services/instruments-service.ts +1 -1
  73. package/lib/services/session-service.ts +13 -1
  74. package/lib/services/tastytrade-http-client.ts +93 -15
  75. package/lib/tastytrade-api.ts +31 -4
  76. package/lib/utils/json-util.ts +0 -2
  77. package/lib/utils/response-util.ts +3 -4
  78. package/package.json +26 -18
  79. package/tsconfig.json +2 -2
  80. package/.eslintrc.cjs +0 -18
  81. package/lib/market-data-streamer.ts +0 -376
@@ -1,376 +0,0 @@
1
- import WebSocket from 'isomorphic-ws'
2
- import _ from 'lodash'
3
- import { v4 as uuidv4 } from 'uuid'
4
- import { MinTlsVersion } from './utils/constants.js'
5
-
6
- export enum MarketDataSubscriptionType {
7
- Candle = 'Candle',
8
- Quote = 'Quote',
9
- Trade = 'Trade',
10
- Summary = 'Summary',
11
- Profile = 'Profile',
12
- Greeks = 'Greeks',
13
- Underlying = 'Underlying'
14
- }
15
-
16
- export enum CandleType {
17
- Tick = 't',
18
- Second = 's',
19
- Minute = 'm',
20
- Hour = 'h',
21
- Day = 'd',
22
- Week = 'w',
23
- Month = 'mo',
24
- ThirdFriday = 'o',
25
- Year = 'y',
26
- Volume = 'v',
27
- Price = 'p'
28
- }
29
-
30
- export type MarketDataListener = (data: any) => void
31
- export type ErrorListener = (error: any) => void
32
- export type AuthStateListener = (isAuthorized: boolean) => void
33
-
34
- type QueuedSubscription = { symbol: string, subscriptionTypes: MarketDataSubscriptionType[], subscriptionArgs?: any }
35
- type SubscriptionOptions = { subscriptionTypes: MarketDataSubscriptionType[], channelId: number, subscriptionArgs?: any }
36
- export type CandleSubscriptionOptions = { period: number, type: CandleType, channelId: number }
37
- type Remover = () => void
38
-
39
- // List of all subscription types except for Candle
40
- const AllSubscriptionTypes = Object.values(MarketDataSubscriptionType)
41
-
42
- const KeepaliveInterval = 30000 // 30 seconds
43
-
44
- const DefaultChannelId = 1
45
-
46
- export default class MarketDataStreamer {
47
- private webSocket: WebSocket | null = null
48
- private token = ''
49
- private keepaliveIntervalId: any | null = null
50
- private dataListeners = new Map()
51
- private openChannels = new Set()
52
- private subscriptionsQueue: Map<number, QueuedSubscription[]> = new Map()
53
- private authState = ''
54
- private errorListeners = new Map()
55
- private authStateListeners = new Map()
56
-
57
- constructor() {
58
- console.warn('MarketDataStreamer is deprecated and will be removed in a future release of @tastytrade/api. Use @dxfeed/dxlink-api instead.')
59
- }
60
-
61
- addDataListener(dataListener: MarketDataListener, channelId: number | null = null): Remover {
62
- if (_.isNil(dataListener)) {
63
- return _.noop
64
- }
65
- const guid = uuidv4()
66
- this.dataListeners.set(guid, { listener: dataListener, channelId })
67
-
68
- return () => this.dataListeners.delete(guid)
69
- }
70
-
71
- addErrorListener(errorListener: ErrorListener): Remover {
72
- if (_.isNil(errorListener)) {
73
- return _.noop
74
- }
75
- const guid = uuidv4()
76
- this.errorListeners.set(guid, errorListener)
77
-
78
- return () => this.errorListeners.delete(guid)
79
- }
80
-
81
- addAuthStateChangeListener(authStateListener: AuthStateListener): Remover {
82
- if (_.isNil(authStateListener)) {
83
- return _.noop
84
- }
85
- const guid = uuidv4()
86
- this.authStateListeners.set(guid, authStateListener)
87
-
88
- return () => this.authStateListeners.delete(guid)
89
- }
90
-
91
- connect(url: string, token: string) {
92
- if (this.isConnected) {
93
- throw new Error('MarketDataStreamer is attempting to connect when an existing websocket is already connected')
94
- }
95
-
96
- this.token = token
97
- this.webSocket = new WebSocket(url, [], {
98
- minVersion: MinTlsVersion // TLS Config
99
- })
100
- this.webSocket.onopen = this.onOpen.bind(this)
101
- this.webSocket.onerror = this.onError.bind(this)
102
- this.webSocket.onmessage = this.handleMessageReceived.bind(this)
103
- this.webSocket.onclose = this.onClose.bind(this)
104
- }
105
-
106
- disconnect() {
107
- if (_.isNil(this.webSocket)) {
108
- return
109
- }
110
-
111
- this.clearKeepalive()
112
-
113
- this.webSocket.onopen = null
114
- this.webSocket.onerror = null
115
- this.webSocket.onmessage = null
116
- this.webSocket.onclose = null
117
-
118
- this.webSocket.close()
119
- this.webSocket = null
120
-
121
- this.openChannels.clear()
122
- this.subscriptionsQueue.clear()
123
- this.authState = ''
124
- }
125
-
126
- addSubscription(symbol: string, options = { subscriptionTypes: AllSubscriptionTypes, channelId: DefaultChannelId }): Remover {
127
- let { subscriptionTypes } = options
128
- // Don't allow candle subscriptions in this method. Use addCandleSubscription instead
129
- subscriptionTypes = _.without(subscriptionTypes, MarketDataSubscriptionType.Candle)
130
-
131
- const isOpen = this.isChannelOpened(options.channelId)
132
- if (isOpen) {
133
- this.sendSubscriptionMessage(symbol, subscriptionTypes, options.channelId, 'add')
134
- } else {
135
- this.queueSubscription(symbol, { subscriptionTypes, channelId: options.channelId })
136
- }
137
-
138
- return () => {
139
- this.removeSubscription(symbol, options)
140
- }
141
- }
142
-
143
- /**
144
- * Adds a candle subscription (historical data)
145
- * @param streamerSymbol Get this from an instrument's streamer-symbol json response field
146
- * @param fromTime Epoch timestamp from where you want to start
147
- * @param options Period and Type are the grouping you want to apply to the candle data
148
- * For example, a period/type of 5/m means you want each candle to represent 5 minutes of data
149
- * From there, setting fromTime to 24 hours ago would give you 24 hours of data grouped in 5 minute intervals
150
- * @returns
151
- */
152
- addCandleSubscription(streamerSymbol: string, fromTime: number, options: CandleSubscriptionOptions) {
153
- const subscriptionTypes = [MarketDataSubscriptionType.Candle]
154
- const channelId = options.channelId ?? DefaultChannelId
155
-
156
- // Example: AAPL{=5m} where each candle represents 5 minutes of data
157
- const candleSymbol = `${streamerSymbol}{=${options.period}${options.type}}`
158
- const isOpen = this.isChannelOpened(channelId)
159
- const subscriptionArgs = { fromTime }
160
- if (isOpen) {
161
- this.sendSubscriptionMessage(candleSymbol, subscriptionTypes, channelId, 'add', subscriptionArgs)
162
- } else {
163
- this.queueSubscription(candleSymbol, { subscriptionTypes, channelId, subscriptionArgs })
164
- }
165
-
166
- return () => {
167
- this.removeSubscription(candleSymbol, { subscriptionTypes, channelId })
168
- }
169
- }
170
-
171
- removeSubscription(symbol: string, options = { subscriptionTypes: AllSubscriptionTypes, channelId: DefaultChannelId }) {
172
- const { subscriptionTypes, channelId } = options
173
- const isOpen = this.isChannelOpened(channelId)
174
- if (isOpen) {
175
- this.sendSubscriptionMessage(symbol, subscriptionTypes, channelId, 'remove')
176
- } else {
177
- this.dequeueSubscription(symbol, options)
178
- }
179
- }
180
-
181
- removeAllSubscriptions(channelId = DefaultChannelId) {
182
- const isOpen = this.isChannelOpened(channelId)
183
- if (isOpen) {
184
- this.sendMessage({ "type": "FEED_SUBSCRIPTION", "channel": channelId, reset: true })
185
- } else {
186
- this.subscriptionsQueue.set(channelId, [])
187
- }
188
- }
189
-
190
- openFeedChannel(channelId: number) {
191
- if (!this.isReadyToOpenChannels) {
192
- throw new Error(`Unable to open channel ${channelId} due to DxLink authorization state: ${this.authState}`)
193
- }
194
-
195
- if (this.isChannelOpened(channelId)) {
196
- return
197
- }
198
-
199
- this.sendMessage({
200
- "type": "CHANNEL_REQUEST",
201
- "channel": channelId,
202
- "service": "FEED",
203
- "parameters": {
204
- "contract": "AUTO"
205
- }
206
- })
207
- }
208
-
209
- isChannelOpened(channelId: number) {
210
- return this.isConnected && this.openChannels.has(channelId)
211
- }
212
-
213
- get isReadyToOpenChannels() {
214
- return this.isConnected && this.isDxLinkAuthorized
215
- }
216
-
217
- get isConnected() {
218
- return !_.isNil(this.webSocket)
219
- }
220
-
221
- private scheduleKeepalive() {
222
- this.keepaliveIntervalId = setInterval(this.sendKeepalive, KeepaliveInterval)
223
- }
224
-
225
- private sendKeepalive() {
226
- if (_.isNil(this.keepaliveIntervalId)) {
227
- return
228
- }
229
-
230
- this.sendMessage({
231
- "type": "KEEPALIVE",
232
- "channel": 0
233
- })
234
- }
235
-
236
- private queueSubscription(symbol: string, options: SubscriptionOptions) {
237
- const { subscriptionTypes, channelId, subscriptionArgs } = options
238
- let queue = this.subscriptionsQueue.get(options.channelId)
239
- if (_.isNil(queue)) {
240
- queue = []
241
- this.subscriptionsQueue.set(channelId, queue)
242
- }
243
-
244
- queue.push({ symbol, subscriptionTypes, subscriptionArgs })
245
- }
246
-
247
- private dequeueSubscription(symbol: string, options: SubscriptionOptions) {
248
- const queue = this.subscriptionsQueue.get(options.channelId)
249
- if (_.isNil(queue) || _.isEmpty(queue)) {
250
- return
251
- }
252
-
253
- _.remove(queue, (queueItem: any) => queueItem.symbol === symbol)
254
- }
255
-
256
- private sendQueuedSubscriptions(channelId: number) {
257
- const queuedSubscriptions = this.subscriptionsQueue.get(channelId)
258
- if (_.isNil(queuedSubscriptions)) {
259
- return
260
- }
261
-
262
- // Clear out queue immediately
263
- this.subscriptionsQueue.set(channelId, [])
264
- queuedSubscriptions.forEach(subscription => {
265
- this.sendSubscriptionMessage(subscription.symbol, subscription.subscriptionTypes, channelId, 'add', subscription.subscriptionArgs)
266
- })
267
- }
268
-
269
- /**
270
- *
271
- * @param {*} symbol
272
- * @param {*} subscriptionTypes
273
- * @param {*} channelId
274
- * @param {*} direction add or remove
275
- */
276
- private sendSubscriptionMessage(symbol: string, subscriptionTypes: MarketDataSubscriptionType[], channelId: number, direction: string, subscriptionArgs: any = {}) {
277
- const subscriptions = subscriptionTypes.map(type => (Object.assign({}, { "symbol": symbol, "type": type }, subscriptionArgs ?? {})))
278
- this.sendMessage({
279
- "type": "FEED_SUBSCRIPTION",
280
- "channel": channelId,
281
- [direction]: subscriptions
282
- })
283
- }
284
-
285
- private onError(error: any) {
286
- console.error('Error received: ', error)
287
- this.notifyErrorListeners(error)
288
- }
289
-
290
- private onOpen() {
291
- this.openChannels.clear()
292
-
293
- this.sendMessage({
294
- "type": "SETUP",
295
- "channel": 0,
296
- "keepaliveTimeout": KeepaliveInterval,
297
- "acceptKeepaliveTimeout": KeepaliveInterval,
298
- "version": "0.1-js/1.0.0"
299
- })
300
-
301
- this.scheduleKeepalive()
302
- }
303
-
304
- private onClose() {
305
- this.webSocket = null
306
- this.clearKeepalive()
307
- }
308
-
309
- private clearKeepalive() {
310
- if (!_.isNil(this.keepaliveIntervalId)) {
311
- clearInterval(this.keepaliveIntervalId)
312
- }
313
-
314
- this.keepaliveIntervalId = null
315
- }
316
-
317
- get isDxLinkAuthorized() {
318
- return this.authState === 'AUTHORIZED'
319
- }
320
-
321
- private handleAuthStateMessage(data: any) {
322
- this.authState = data.state
323
- this.authStateListeners.forEach(listener => listener(this.isDxLinkAuthorized))
324
-
325
- if (this.isDxLinkAuthorized) {
326
- this.openFeedChannel(DefaultChannelId)
327
- } else {
328
- this.sendMessage({
329
- "type": "AUTH",
330
- "channel": 0,
331
- "token": this.token
332
- })
333
- }
334
- }
335
-
336
- private handleChannelOpened(jsonData: any) {
337
- this.openChannels.add(jsonData.channel)
338
- this.sendQueuedSubscriptions(jsonData.channel)
339
- }
340
-
341
- private notifyListeners(jsonData: any) {
342
- this.dataListeners.forEach(listenerData => {
343
- if (listenerData.channelId === jsonData.channel || _.isNil(listenerData.channelId)) {
344
- listenerData.listener(jsonData)
345
- }
346
- })
347
- }
348
-
349
- private notifyErrorListeners(error: any) {
350
- this.errorListeners.forEach(listener => listener(error))
351
- }
352
-
353
- private handleMessageReceived(data: WebSocket.MessageEvent) {
354
- const messageData = _.get(data, 'data', '{}')
355
- const jsonData = JSON.parse(messageData as string)
356
- switch (jsonData.type) {
357
- case 'AUTH_STATE':
358
- this.handleAuthStateMessage(jsonData)
359
- break
360
- case 'CHANNEL_OPENED':
361
- this.handleChannelOpened(jsonData)
362
- break
363
- case 'FEED_DATA':
364
- this.notifyListeners(jsonData)
365
- break
366
- }
367
- }
368
-
369
- private sendMessage(json: object) {
370
- if (_.isNil(this.webSocket)) {
371
- return
372
- }
373
-
374
- this.webSocket.send(JSON.stringify(json))
375
- }
376
- }