@tastytrade/api 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +5 -0
- package/.eslintrc.cjs +18 -0
- package/.vscode/launch.json +24 -0
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/account-streamer.d.ts +92 -0
- package/dist/account-streamer.js +394 -0
- package/dist/models/tastytrade-session.d.ts +5 -0
- package/dist/models/tastytrade-session.js +23 -0
- package/dist/quote-streamer.d.ts +11 -0
- package/dist/quote-streamer.js +63 -0
- package/dist/services/account-status-service.d.ts +6 -0
- package/dist/services/account-status-service.js +64 -0
- package/dist/services/accounts-and-customers-service.d.ts +10 -0
- package/dist/services/accounts-and-customers-service.js +116 -0
- package/dist/services/balances-and-positions-service.d.ts +8 -0
- package/dist/services/balances-and-positions-service.js +93 -0
- package/dist/services/instruments-service.d.ts +28 -0
- package/dist/services/instruments-service.js +370 -0
- package/dist/services/margin-requirements-service.d.ts +7 -0
- package/dist/services/margin-requirements-service.js +76 -0
- package/dist/services/market-metrics-service.d.ts +8 -0
- package/dist/services/market-metrics-service.js +91 -0
- package/dist/services/net-liquidating-value-history-service.d.ts +7 -0
- package/dist/services/net-liquidating-value-history-service.js +77 -0
- package/dist/services/orders-service.d.ts +17 -0
- package/dist/services/orders-service.js +208 -0
- package/dist/services/risk-parameters-service.d.ts +7 -0
- package/dist/services/risk-parameters-service.js +76 -0
- package/dist/services/session-service.d.ts +9 -0
- package/dist/services/session-service.js +113 -0
- package/dist/services/symbol-search-service.d.ts +6 -0
- package/dist/services/symbol-search-service.js +63 -0
- package/dist/services/tastytrade-http-client.d.ts +13 -0
- package/dist/services/tastytrade-http-client.js +137 -0
- package/dist/services/transactions-service.d.ts +8 -0
- package/dist/services/transactions-service.js +91 -0
- package/dist/services/watchlists-service.d.ts +14 -0
- package/dist/services/watchlists-service.js +170 -0
- package/dist/tastytrade-api.d.ts +40 -0
- package/dist/tastytrade-api.js +56 -0
- package/dist/utils/json-util.d.ts +14 -0
- package/dist/utils/json-util.js +43 -0
- package/dist/utils/response-util.d.ts +1 -0
- package/dist/utils/response-util.js +21 -0
- package/lib/account-streamer.ts +394 -0
- package/lib/models/tastytrade-session.ts +13 -0
- package/lib/quote-streamer.ts +40 -0
- package/lib/services/account-status-service.ts +15 -0
- package/lib/services/accounts-and-customers-service.ts +35 -0
- package/lib/services/balances-and-positions-service.ts +29 -0
- package/lib/services/instruments-service.ts +138 -0
- package/lib/services/margin-requirements-service.ts +19 -0
- package/lib/services/market-metrics-service.ts +24 -0
- package/lib/services/net-liquidating-value-history-service.ts +20 -0
- package/lib/services/orders-service.ts +80 -0
- package/lib/services/risk-parameters-service.ts +19 -0
- package/lib/services/session-service.ts +35 -0
- package/lib/services/symbol-search-service.ts +14 -0
- package/lib/services/tastytrade-http-client.ts +65 -0
- package/lib/services/transactions-service.ts +27 -0
- package/lib/services/watchlists-service.ts +58 -0
- package/lib/tastytrade-api.ts +65 -0
- package/lib/utils/json-util.ts +44 -0
- package/lib/utils/response-util.ts +15 -0
- package/package.json +35 -0
- package/tsconfig.json +71 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import type { JsonMap, JsonValue } from './utils/json-util'
|
|
3
|
+
import { JsonBuilder } from './utils/json-util'
|
|
4
|
+
import TastytradeSession from './models/tastytrade-session'
|
|
5
|
+
|
|
6
|
+
export enum STREAMER_STATE {
|
|
7
|
+
Open = 0,
|
|
8
|
+
Closed = 1,
|
|
9
|
+
Error = 2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
enum MessageAction {
|
|
13
|
+
ACCOUNT_SUBSCRIBE = 'account-subscribe',
|
|
14
|
+
CONNECT = 'connect', // Send this instead of `account-subscribe`
|
|
15
|
+
HEARTBEAT = 'heartbeat',
|
|
16
|
+
PUBLIC_WATCHLISTS_SUBSCRIBE = 'public-watchlists-subscribe',
|
|
17
|
+
QUOTE_ALERTS_SUBSCRIBE = 'quote-alerts-subscribe',
|
|
18
|
+
USER_MESSAGE_SUBSCRIBE = 'user-message-subscribe'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const HEARTBEAT_INTERVAL = 20000 // 20 seconds
|
|
22
|
+
|
|
23
|
+
const SOURCE = 'tastytrade-api-js-sdk'
|
|
24
|
+
|
|
25
|
+
export type Disposer = () => void
|
|
26
|
+
|
|
27
|
+
export type StreamerStateObserver = (streamerState: STREAMER_STATE) => void
|
|
28
|
+
|
|
29
|
+
export type StreamerMessageObserver = (messageType: string, action: string, status: string) => void
|
|
30
|
+
|
|
31
|
+
const REQUEST_ID = 'request-id'
|
|
32
|
+
|
|
33
|
+
function removeElement<T>(array: T[], element: T): void {
|
|
34
|
+
const index = array.indexOf(element)
|
|
35
|
+
if (index < 0) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
array.splice(index, 1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class AccountStreamer {
|
|
43
|
+
private websocket: WebSocket | null = null
|
|
44
|
+
private startResolve: ((result: boolean) => void) | null = null
|
|
45
|
+
private startReject: ((reason?: any) => void) | null = null
|
|
46
|
+
private requestCounter: number = 0
|
|
47
|
+
private queued: string[] = []
|
|
48
|
+
|
|
49
|
+
private heartbeatTimerId: number | NodeJS.Timeout | null = null
|
|
50
|
+
|
|
51
|
+
lastCloseEvent: any = null
|
|
52
|
+
lastErrorEvent: any = null
|
|
53
|
+
private _streamerState: STREAMER_STATE = STREAMER_STATE.Closed
|
|
54
|
+
|
|
55
|
+
private readonly streamerStateObservers: StreamerStateObserver[] = []
|
|
56
|
+
|
|
57
|
+
private readonly streamerMessageObservers: StreamerMessageObserver[] = []
|
|
58
|
+
|
|
59
|
+
private startPromise: Promise<boolean> | null = null
|
|
60
|
+
|
|
61
|
+
private readonly requestPromises: Map<
|
|
62
|
+
number,
|
|
63
|
+
[(status: string) => void, (error: string) => void]
|
|
64
|
+
> = new Map()
|
|
65
|
+
|
|
66
|
+
private readonly logger = console
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param url Url of the account streamer service
|
|
71
|
+
*/
|
|
72
|
+
constructor(private readonly url: string, private readonly session: TastytradeSession) {}
|
|
73
|
+
|
|
74
|
+
get streamerState(): STREAMER_STATE {
|
|
75
|
+
return this._streamerState
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
set streamerState(streamerState: STREAMER_STATE) {
|
|
79
|
+
this._streamerState = streamerState
|
|
80
|
+
|
|
81
|
+
this.streamerStateObservers.forEach(observer => {
|
|
82
|
+
observer(streamerState)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private get authToken() {
|
|
87
|
+
return this.session.authToken
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Adds a custom callback that fires when the streamer state changes
|
|
92
|
+
* @param observer
|
|
93
|
+
* @returns
|
|
94
|
+
*/
|
|
95
|
+
addStreamerStateObserver(observer: StreamerStateObserver): Disposer {
|
|
96
|
+
this.streamerStateObservers.push(observer)
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
removeElement(this.streamerStateObservers, observer)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
get isOpen(): boolean {
|
|
104
|
+
return this.streamerState === STREAMER_STATE.Open
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get isClosed(): boolean {
|
|
108
|
+
return this.streamerState === STREAMER_STATE.Closed
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get isError(): boolean {
|
|
112
|
+
return this.streamerState === STREAMER_STATE.Error
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Entrypoint for beginning a websocket session
|
|
117
|
+
* You must have a valid tastytrade authToken before calling this method
|
|
118
|
+
* @returns Promise that resolves when the "opened" message is received (see handleOpen)
|
|
119
|
+
*/
|
|
120
|
+
async start(): Promise<boolean> {
|
|
121
|
+
if (this.startPromise !== null) {
|
|
122
|
+
return this.startPromise
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const websocket = (this.websocket = new WebSocket(this.url))
|
|
126
|
+
this.lastCloseEvent = null
|
|
127
|
+
this.lastErrorEvent = null
|
|
128
|
+
websocket.addEventListener('open', this.handleOpen)
|
|
129
|
+
websocket.addEventListener('close', this.handleClose)
|
|
130
|
+
websocket.addEventListener('error', this.handleError)
|
|
131
|
+
websocket.addEventListener('message', this.handleMessage)
|
|
132
|
+
|
|
133
|
+
this.logger.info('AccountStreamer - starting')
|
|
134
|
+
this.startPromise = new Promise<boolean>((resolve, reject) => {
|
|
135
|
+
this.startResolve = resolve
|
|
136
|
+
this.startReject = reject
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return this.startPromise
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
stop() {
|
|
143
|
+
this.teardown()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private teardown() {
|
|
147
|
+
const websocket = this.websocket
|
|
148
|
+
if (websocket === null) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.startPromise = null
|
|
153
|
+
|
|
154
|
+
this.cancelHeartbeatTimer()
|
|
155
|
+
|
|
156
|
+
websocket.close()
|
|
157
|
+
websocket.removeEventListener('open', this.handleOpen)
|
|
158
|
+
websocket.removeEventListener('close', this.handleClose)
|
|
159
|
+
websocket.removeEventListener('message', this.handleMessage)
|
|
160
|
+
websocket.removeEventListener('error', this.handleError)
|
|
161
|
+
|
|
162
|
+
this.websocket = null
|
|
163
|
+
|
|
164
|
+
this.logger.info('AccountStreamer - teardown')
|
|
165
|
+
this.streamerState = STREAMER_STATE.Closed // Manually update status for convenience
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
readonly sendHeartbeat = () => {
|
|
169
|
+
this.clearHeartbeatTimerId()
|
|
170
|
+
this.send(new JsonBuilder({ action: MessageAction.HEARTBEAT }))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private scheduleHeartbeatTimer() {
|
|
174
|
+
if (this.isHeartbeatScheduled) {
|
|
175
|
+
// Heartbeat already scheduled
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.logger.info('Scheduling heartbeat with interval: ', HEARTBEAT_INTERVAL)
|
|
180
|
+
const scheduler = typeof window === 'undefined' ? setTimeout : window.setTimeout
|
|
181
|
+
this.heartbeatTimerId = scheduler(
|
|
182
|
+
this.sendHeartbeat,
|
|
183
|
+
HEARTBEAT_INTERVAL
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
get isHeartbeatScheduled() {
|
|
188
|
+
return !_.isNil(this.heartbeatTimerId)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private cancelHeartbeatTimer() {
|
|
192
|
+
if (!this.isHeartbeatScheduled) {
|
|
193
|
+
return // Nothing to cancel
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (typeof window === 'undefined') {
|
|
197
|
+
clearTimeout(this.heartbeatTimerId! as number)
|
|
198
|
+
} else {
|
|
199
|
+
clearTimeout(this.heartbeatTimerId! as NodeJS.Timeout)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.clearHeartbeatTimerId()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private clearHeartbeatTimerId() {
|
|
206
|
+
this.heartbeatTimerId = null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Send a message via websocket
|
|
211
|
+
* @param json JsonBuilder
|
|
212
|
+
* @param includeSessionToken Attaches session token to message if true
|
|
213
|
+
* @returns
|
|
214
|
+
*/
|
|
215
|
+
send(json: JsonBuilder, includeSessionToken = true): number {
|
|
216
|
+
this.requestCounter += 1
|
|
217
|
+
json.add(REQUEST_ID, this.requestCounter)
|
|
218
|
+
json.add('source', SOURCE)
|
|
219
|
+
|
|
220
|
+
if (includeSessionToken) {
|
|
221
|
+
const sessionToken = this.authToken
|
|
222
|
+
if (!sessionToken) {
|
|
223
|
+
throw new Error('sessionToken not set')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
json.add('auth-token', sessionToken)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const message = JSON.stringify(json.json)
|
|
230
|
+
const websocket = this.websocket
|
|
231
|
+
if (websocket === null) {
|
|
232
|
+
// Queue up and send on open
|
|
233
|
+
this.queued.push(message)
|
|
234
|
+
} else {
|
|
235
|
+
websocket.send(message)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return this.requestCounter
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Used by other methods to send a specific `action` message
|
|
243
|
+
* @param action
|
|
244
|
+
* @param value
|
|
245
|
+
* @returns
|
|
246
|
+
*/
|
|
247
|
+
public subscribeTo(action: string, value?: JsonValue): number {
|
|
248
|
+
const json = new JsonBuilder()
|
|
249
|
+
json.add('action', action)
|
|
250
|
+
if (!_.isUndefined(value)) {
|
|
251
|
+
json.add('value', value)
|
|
252
|
+
}
|
|
253
|
+
return this.send(json)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Subscribes to all user-level messages for given user external id
|
|
258
|
+
* @param userExternalId "external-id" from login response
|
|
259
|
+
* @returns Promise that resolves when ack is received
|
|
260
|
+
*/
|
|
261
|
+
public subscribeToUser(userExternalId: string) {
|
|
262
|
+
if (!userExternalId) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.subscribeTo(MessageAction.USER_MESSAGE_SUBSCRIBE, userExternalId)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Subscribes to all account-level messages for given account numbers
|
|
271
|
+
* @param accountNumbers List of account numbers to subscribe to
|
|
272
|
+
* @returns Promise that resolves when an ack is received
|
|
273
|
+
*/
|
|
274
|
+
public async subscribeToAccounts(accountNumbers: string[]): Promise<string> {
|
|
275
|
+
if (accountNumbers.length === 0) {
|
|
276
|
+
return Promise.reject('no account numbers')
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const value: JsonValue =
|
|
280
|
+
accountNumbers.length > 1 ? accountNumbers : accountNumbers[0]
|
|
281
|
+
const requestId = this.subscribeTo(MessageAction.CONNECT, value)
|
|
282
|
+
|
|
283
|
+
return new Promise<string>((resolve, reject) => {
|
|
284
|
+
this.requestPromises.set(requestId, [resolve, reject])
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private sendQueuedMessages() {
|
|
289
|
+
const queued = this.queued
|
|
290
|
+
if (queued.length === 0 || this.websocket === null) {
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const websocket = this.websocket
|
|
295
|
+
queued.forEach(msg => {
|
|
296
|
+
websocket.send(msg)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
this.queued = []
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private readonly handleOpen = (event: Event) => {
|
|
303
|
+
if (this.startResolve === null) {
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.logger.info('AccountStreamer opened', event)
|
|
308
|
+
|
|
309
|
+
this.startResolve(true)
|
|
310
|
+
this.startResolve = this.startReject = null
|
|
311
|
+
|
|
312
|
+
this.streamerState = STREAMER_STATE.Open
|
|
313
|
+
this.sendQueuedMessages()
|
|
314
|
+
this.scheduleHeartbeatTimer()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private readonly handleClose = (event: CloseEvent) => {
|
|
318
|
+
this.logger.info('AccountStreamer closed', event)
|
|
319
|
+
if (this.websocket === null) {
|
|
320
|
+
return
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
this.lastCloseEvent = event
|
|
324
|
+
this.streamerState = STREAMER_STATE.Closed
|
|
325
|
+
this.teardown()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private readonly handleError = (event: Event) => {
|
|
329
|
+
if (this.websocket === null) {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
this.logger.warn('AccountStreamer error', event)
|
|
334
|
+
|
|
335
|
+
this.lastErrorEvent = event
|
|
336
|
+
this.streamerState = STREAMER_STATE.Error
|
|
337
|
+
|
|
338
|
+
if (this.startReject !== null) {
|
|
339
|
+
this.startReject(new Error('Failed to connect'))
|
|
340
|
+
this.startReject = this.startResolve = null
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
this.teardown()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private readonly handleMessage = (event: MessageEvent) => {
|
|
347
|
+
const json = JSON.parse(event.data as string) as JsonMap
|
|
348
|
+
|
|
349
|
+
if (json.results !== undefined) {
|
|
350
|
+
const results: JsonValue[] = json.results as JsonValue[]
|
|
351
|
+
for (const result of results) {
|
|
352
|
+
this.handleOneMessage(result as JsonMap)
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
this.handleOneMessage(json)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
addMessageObserver(observer: StreamerMessageObserver): Disposer {
|
|
360
|
+
this.streamerMessageObservers.push(observer)
|
|
361
|
+
|
|
362
|
+
return () => {
|
|
363
|
+
removeElement(this.streamerMessageObservers, observer)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private readonly handleOneMessage = (json: JsonMap) => {
|
|
368
|
+
this.logger.info(json)
|
|
369
|
+
|
|
370
|
+
const action = json.action as string
|
|
371
|
+
this.streamerMessageObservers.forEach(observer => observer(json.type as string, action, json.status as string))
|
|
372
|
+
if (action) {
|
|
373
|
+
if (action === MessageAction.HEARTBEAT) {
|
|
374
|
+
// schedule next heartbeat
|
|
375
|
+
this.scheduleHeartbeatTimer()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const promiseCallbacks = this.requestPromises.get(
|
|
379
|
+
json[REQUEST_ID] as number
|
|
380
|
+
)
|
|
381
|
+
if (promiseCallbacks) {
|
|
382
|
+
const [resolve, reject] = promiseCallbacks
|
|
383
|
+
const status = json.status as string
|
|
384
|
+
if (status === 'ok') {
|
|
385
|
+
resolve(json.action as string)
|
|
386
|
+
} else {
|
|
387
|
+
reject(json.message as string)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Feed, { EventType, IEvent } from '@dxfeed/api'
|
|
2
|
+
import _ from 'lodash'
|
|
3
|
+
|
|
4
|
+
export const SupportedEventTypes = [
|
|
5
|
+
EventType.Quote,
|
|
6
|
+
EventType.Trade,
|
|
7
|
+
EventType.Summary,
|
|
8
|
+
EventType.Greeks,
|
|
9
|
+
EventType.Profile
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
export default class QuoteStreamer {
|
|
13
|
+
private feed: Feed | null = null
|
|
14
|
+
|
|
15
|
+
constructor(private readonly token: string, private readonly url: string) {}
|
|
16
|
+
|
|
17
|
+
connect() {
|
|
18
|
+
this.feed = new Feed()
|
|
19
|
+
this.feed.setAuthToken(this.token)
|
|
20
|
+
this.feed.connect(this.url)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
disconnect() {
|
|
24
|
+
if (!_.isNil(this.feed)) {
|
|
25
|
+
this.feed.disconnect()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subscribe(dxfeedSymbol: string, eventHandler: (event: IEvent) => void): () => void {
|
|
30
|
+
if (_.isNil(this.feed)) {
|
|
31
|
+
return _.noop
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return this.feed.subscribe(
|
|
35
|
+
SupportedEventTypes,
|
|
36
|
+
[dxfeedSymbol],
|
|
37
|
+
eventHandler
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import extractResponseData from "../utils/response-util";
|
|
2
|
+
import TastytradeHttpClient from "./tastytrade-http-client";
|
|
3
|
+
|
|
4
|
+
// create the central class that aggregates all services from dmoss
|
|
5
|
+
export default class AccountStatusService {
|
|
6
|
+
constructor(private httpClient: TastytradeHttpClient) {
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//Trading Status: Allows an API client to request information about the basic trade status of an account. This includes information about the strategies an account can trade.
|
|
10
|
+
async getAccountStatus(accountNumber: string){
|
|
11
|
+
//Returns current trading status for an account.
|
|
12
|
+
const accountStatus = (await this.httpClient.getData(`/accounts/${accountNumber}/trading-status`, {}, {}))
|
|
13
|
+
return extractResponseData(accountStatus)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import extractResponseData from "../utils/response-util";
|
|
2
|
+
import TastytradeHttpClient from "./tastytrade-http-client";
|
|
3
|
+
|
|
4
|
+
export default class AccountsAndCustomersService {
|
|
5
|
+
constructor(private httpClient: TastytradeHttpClient) {
|
|
6
|
+
}
|
|
7
|
+
async getCustomerAccounts(){
|
|
8
|
+
const accountNumber = (await this.httpClient.getData('/customers/me/accounts', {}, {}))
|
|
9
|
+
return extractResponseData(accountNumber)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//Customers: Operations about customers
|
|
13
|
+
async getCustomerResource(customerId: string){
|
|
14
|
+
//Get a full customer resource.
|
|
15
|
+
const customerResource = (await this.httpClient.getData(`/customers/${customerId}`, {}, {}))
|
|
16
|
+
return extractResponseData(customerResource)
|
|
17
|
+
}
|
|
18
|
+
async getCustomerAccountResources(customerId: string){
|
|
19
|
+
//Get a list of all the customer account resources attached to the current customer.
|
|
20
|
+
const customerAccountResources = (await this.httpClient.getData(`/customers/${customerId}/accounts`, {}, {}))
|
|
21
|
+
return extractResponseData(customerAccountResources)
|
|
22
|
+
}
|
|
23
|
+
async getFullCustomerAccountResource(customerId: string, accountNumber: string){
|
|
24
|
+
//Get a full customer account resource.
|
|
25
|
+
const fullCustomerAccountResource = (await this.httpClient.getData(`/customers/${customerId}/accounts/${accountNumber}`, {}, {}))
|
|
26
|
+
return extractResponseData(fullCustomerAccountResource)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//Quote-streamer-tokens: Operations about quote-streamer-tokens
|
|
30
|
+
async getQuoteStreamerTokens(){
|
|
31
|
+
//Returns the appropriate quote streamer endpoint, level and identification token for the current customer to receive market data.
|
|
32
|
+
const quoteStreamerTokens = (await this.httpClient.getData('/quote-streamer-tokens', {}, {}))
|
|
33
|
+
return extractResponseData(quoteStreamerTokens)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import extractResponseData from "../utils/response-util";
|
|
2
|
+
import TastytradeHttpClient from "./tastytrade-http-client";
|
|
3
|
+
|
|
4
|
+
export default class BalancesAndPositionsService {
|
|
5
|
+
constructor(private httpClient: TastytradeHttpClient) {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//Positions: Operations about positions
|
|
9
|
+
async getPositionsList(accountNumber: string, queryParams = {}){
|
|
10
|
+
//Returns a list of the account's positions.
|
|
11
|
+
//Can be filtered by symbol, underlying_symbol
|
|
12
|
+
const positionsList = (await this.httpClient.getData(`/accounts/${accountNumber}/positions`, {}, queryParams))
|
|
13
|
+
return extractResponseData(positionsList)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//Accounts: Operations about accounts
|
|
17
|
+
async getAccountBalanceValues(accountNumber: string){
|
|
18
|
+
//Returns the current balance values for an account
|
|
19
|
+
const accountBalanceValues = (await this.httpClient.getData(`/accounts/${accountNumber}/balances`, {}, {}))
|
|
20
|
+
return extractResponseData(accountBalanceValues)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//Balance-snapshots Operations about balance-snapshots
|
|
24
|
+
async getBalanceSnapshots(accountNumber: string, queryParams = {}){
|
|
25
|
+
//Returns most recent snapshot and current balance for an account
|
|
26
|
+
const balanceSnapshot = (await this.httpClient.getData(`/accounts/${accountNumber}/balance-snapshots`, {}, queryParams))
|
|
27
|
+
return extractResponseData(balanceSnapshot)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import extractResponseData from "../utils/response-util";
|
|
2
|
+
import TastytradeHttpClient from "./tastytrade-http-client";
|
|
3
|
+
import _ from 'lodash'
|
|
4
|
+
|
|
5
|
+
export default class InstrumentsService {
|
|
6
|
+
constructor(private httpClient: TastytradeHttpClient) {
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//Instruments: Allows an API client to fetch data about instruments.
|
|
10
|
+
async getCryptocurrencies(symbols: string[] = []){
|
|
11
|
+
//Retrieve a set of cryptocurrencies given an array of one or more symbols.
|
|
12
|
+
const queryParams = { symbol: symbols }
|
|
13
|
+
const cryptocurrencies = (await this.httpClient.getData(`/instruments/cryptocurrencies`, {}, queryParams))
|
|
14
|
+
return extractResponseData(cryptocurrencies)
|
|
15
|
+
}
|
|
16
|
+
async getSingleCryptocurrency(symbol: string){
|
|
17
|
+
//Retrieve a cryptocurrency given a symbol.
|
|
18
|
+
const encodedSymbol = encodeURIComponent(symbol)
|
|
19
|
+
const singleCryptocurrency = (await this.httpClient.getData(`/instruments/cryptocurrencies/${encodedSymbol}`, {}, {}))
|
|
20
|
+
return extractResponseData(singleCryptocurrency)
|
|
21
|
+
}
|
|
22
|
+
async getActiveEquities(queryParams = {}){
|
|
23
|
+
//Returns all active equities in a paginated fashion
|
|
24
|
+
const activeEquities = (await this.httpClient.getData(`/instruments/equities/active`, {}, queryParams))
|
|
25
|
+
return extractResponseData(activeEquities)
|
|
26
|
+
}
|
|
27
|
+
async getEquityDefinitions(queryParams = {}){
|
|
28
|
+
//Returns a set of equity definitions given an array of one or more symbols
|
|
29
|
+
const equityDefinitions = (await this.httpClient.getData(`/instruments/equities`, {}, queryParams))
|
|
30
|
+
return extractResponseData(equityDefinitions)
|
|
31
|
+
}
|
|
32
|
+
async getSingleEquity(symbol: string){
|
|
33
|
+
//Returns a single equity definition for the provided symbol
|
|
34
|
+
const singleEquity = (await this.httpClient.getData(`/instruments/equities/${symbol}`, {}, {}))
|
|
35
|
+
return extractResponseData(singleEquity)
|
|
36
|
+
}
|
|
37
|
+
async getEquityOptions(symbols: string[], active = true, withExpired = false) {
|
|
38
|
+
if (_.isEmpty(symbols)) {
|
|
39
|
+
throw new Error('Symbols are required for InstrumentService.getEquityOptions')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//Returns a set of equity options given one or more symbols
|
|
43
|
+
const queryParams = { symbols, active, withExpired }
|
|
44
|
+
const equityOptions = (await this.httpClient.getData(`/instruments/equity-options`, {}, queryParams))
|
|
45
|
+
return extractResponseData(equityOptions)
|
|
46
|
+
}
|
|
47
|
+
async getSingleEquityOption(symbol: string, queryParams = {}){
|
|
48
|
+
//Get equity option by symbol
|
|
49
|
+
const singleOption = await this.httpClient.getData(`/instruments/equity-options/${symbol}`, {}, queryParams)
|
|
50
|
+
return extractResponseData(singleOption)
|
|
51
|
+
}
|
|
52
|
+
async getFutures(queryParams = {}){
|
|
53
|
+
//Returns a set of outright futures given an array of one or more symbols.
|
|
54
|
+
const futures = (await this.httpClient.getData(`/instruments/futures`, {}, queryParams))
|
|
55
|
+
return extractResponseData(futures)
|
|
56
|
+
}
|
|
57
|
+
async getSingleFuture(symbol: string){
|
|
58
|
+
//Returns an outright future given a symbol.
|
|
59
|
+
const singleFuture = await this.httpClient.getData(`/instruments/futures/${symbol}`, {}, {})
|
|
60
|
+
return extractResponseData(singleFuture)
|
|
61
|
+
}
|
|
62
|
+
async getFutureOptionsProducts(){
|
|
63
|
+
//Returns metadata for all supported future option products
|
|
64
|
+
const futureOptionsProducts = (await this.httpClient.getData(`/instruments/future-option-products`, {}, {}))
|
|
65
|
+
return extractResponseData(futureOptionsProducts)
|
|
66
|
+
}
|
|
67
|
+
async getSingleFutureOptionProduct(exchange: string, rootSymbol: string){
|
|
68
|
+
//Get a future option product by exchange and root symbol
|
|
69
|
+
const singleFutureOptionProduct = (await this.httpClient.getData(`/instruments/future-option-products/${exchange}/${rootSymbol}`, {}, {}))
|
|
70
|
+
return extractResponseData(singleFutureOptionProduct)
|
|
71
|
+
}
|
|
72
|
+
async getFutureOptions(queryParams = {}){
|
|
73
|
+
//Returns a set of future option(s) given an array of one or more symbols.
|
|
74
|
+
//Uses TW symbology: [./ESZ9 EW4U9 190927P2975]
|
|
75
|
+
const futureOptions = (await this.httpClient.getData(`/instruments/future-options`, {}, queryParams))
|
|
76
|
+
return extractResponseData(futureOptions)
|
|
77
|
+
}
|
|
78
|
+
async getSingleFutureOption(symbol: string){
|
|
79
|
+
//Returns a future option given a symbol. Uses TW symbology: ./ESZ9 EW4U9 190927P2975
|
|
80
|
+
const singleFutureOption = await this.httpClient.getData(`/instruments/future-options/${symbol}`, {}, {})
|
|
81
|
+
return extractResponseData(singleFutureOption)
|
|
82
|
+
}
|
|
83
|
+
async getFuturesProducts(){
|
|
84
|
+
//Returns metadata for all supported futures products
|
|
85
|
+
const futuresProducts = (await this.httpClient.getData(`/instruments/future-products`, {}, {}))
|
|
86
|
+
return extractResponseData(futuresProducts)
|
|
87
|
+
}
|
|
88
|
+
async getSingleFutureProduct(exchange: string, code: string){
|
|
89
|
+
//Get future product from exchange and product code
|
|
90
|
+
const singleFutureProduct = (await this.httpClient.getData(`/instruments/future-products/${exchange}/${code}`, {}, {}))
|
|
91
|
+
return extractResponseData(singleFutureProduct)
|
|
92
|
+
}
|
|
93
|
+
async getQuantityDecimalPrecisions(){
|
|
94
|
+
//Retrieve all quantity decimal precisions.
|
|
95
|
+
const quantityDecimalPrecisions = (await this.httpClient.getData(`/instruments/quantity-decimal-precisions`, {}, {}))
|
|
96
|
+
return extractResponseData(quantityDecimalPrecisions)
|
|
97
|
+
}
|
|
98
|
+
async getWarrants(queryParams = {}){
|
|
99
|
+
//Returns a set of warrant definitions that can be filtered by parameters
|
|
100
|
+
const warrants = (await this.httpClient.getData(`/instruments/warrants`, {}, queryParams))
|
|
101
|
+
return extractResponseData(warrants)
|
|
102
|
+
}
|
|
103
|
+
async getSingleWarrant(symbol: string){
|
|
104
|
+
//Returns a single warrant definition for the provided symbol
|
|
105
|
+
const singleWarrant = (await this.httpClient.getData(`/instruments/warrants/${symbol}`, {}, {}))
|
|
106
|
+
return extractResponseData(singleWarrant)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
//Futures-option-chains: Allows an API client to fetch futures option chains.
|
|
110
|
+
async getNestedFutureOptionChains(symbol: string){
|
|
111
|
+
//Returns a futures option chain given a futures product code in a nested form to minimize redundant processing
|
|
112
|
+
const nestedFutureOptionChains = (await this.httpClient.getData(`/futures-option-chains/${symbol}/nested`, {}, {}))
|
|
113
|
+
return extractResponseData(nestedFutureOptionChains)
|
|
114
|
+
}
|
|
115
|
+
async getFutureOptionChain(symbol: string){
|
|
116
|
+
//Returns a futures option chain given a futures product code, i.e. ES
|
|
117
|
+
const futureOptionChain = (await this.httpClient.getData(`/futures-option-chains/${symbol}`, {}, {}))
|
|
118
|
+
return extractResponseData(futureOptionChain)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
//Option-chains: Allows an API client to fetch futures option chains.
|
|
122
|
+
async getNestedOptionChain(symbol: string){
|
|
123
|
+
//Returns an option chain given an underlying symbol,
|
|
124
|
+
//i.e. AAPL in a nested form to minimize redundant processing
|
|
125
|
+
const nestedOptionChain = (await this.httpClient.getData(`/option-chains/${symbol}/nested`, {}, {}))
|
|
126
|
+
return extractResponseData(nestedOptionChain)
|
|
127
|
+
}
|
|
128
|
+
async getCompactOptionChain(symbol: string){
|
|
129
|
+
//Returns an option chain given an underlying symbol, i.e. AAPL in a compact form to minimize content size
|
|
130
|
+
const compactOptionChain = (await this.httpClient.getData(`/option-chains/${symbol}/compact`, {}, {}))
|
|
131
|
+
return extractResponseData(compactOptionChain)
|
|
132
|
+
}
|
|
133
|
+
async getOptionChain(symbol: string){
|
|
134
|
+
//Returns an option chain given an underlying symbol, i.e. AAPL
|
|
135
|
+
const optionChain = (await this.httpClient.getData(`/option-chains/${symbol}`, {}, {}))
|
|
136
|
+
return extractResponseData(optionChain)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import extractResponseData from "../utils/response-util";
|
|
2
|
+
import TastytradeHttpClient from "./tastytrade-http-client";
|
|
3
|
+
|
|
4
|
+
export default class MarginRequirementsService {
|
|
5
|
+
constructor(private httpClient: TastytradeHttpClient) {
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//Margin-requirements: Allows a client to fetch margin-requirements for positions and orders
|
|
9
|
+
async getMarginRequirements(accountNumber: string){
|
|
10
|
+
//Fetch current margin/captial requirements report for an account
|
|
11
|
+
const marginRequirements = (await this.httpClient.getData(`/margin/accounts/${accountNumber}/requirements`))
|
|
12
|
+
return extractResponseData(marginRequirements)
|
|
13
|
+
}
|
|
14
|
+
async postMarginRequirements(accountNumber: string, order: object){
|
|
15
|
+
//Estimate margin requirements for an order given an account
|
|
16
|
+
const marginRequirements = (await this.httpClient.postData(`/margin/accounts/${accountNumber}/dry-run`, order, {}))
|
|
17
|
+
return extractResponseData(marginRequirements)
|
|
18
|
+
}
|
|
19
|
+
}
|