Skip to content

Commit

Permalink
feat: Add option quotes (#48)
Browse files Browse the repository at this point in the history
* feat: Add option quotes

* prettier

* bump deps
  • Loading branch information
felipecsl authored Aug 7, 2023
1 parent 23f5f50 commit 61972e1
Show file tree
Hide file tree
Showing 21 changed files with 1,100 additions and 1,016 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ yarn start
- ✅ Option chains
- ✅ Alert lookup
- ✅ Option chain details
- ✅ Option quotes

# Not yet implemented

- ❌ Option quotes
- ❌ Alert subscription
- ❌ Order events
- ❌ And many others 😀
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tda-wsjson-client",
"version": "0.3.1",
"version": "0.3.2",
"description": "WebSocket client for the TD Ameritrade wsjson API",
"main": "dist/web.bundle.js",
"types": "dist/web.d.ts",
Expand Down
7 changes: 7 additions & 0 deletions src/client/messageTypeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { InstrumentSearchResponse } from "./services/instrumentSearchMessageHand
import { AlertsResponse } from "./types/alertTypes";
import { OptionChainResponse } from "./services/optionSeriesMessageHandler";
import { RawLoginResponse } from "./services/loginMessageHandler";
import { OptionQuotesResponse } from "./services/optionQuotesMessageHandler";

export function isPayloadResponse(
response: WsJsonRawMessage
Expand Down Expand Up @@ -82,6 +83,12 @@ export function isOrderEventsPatchResponse(
return "patches" in response && response.service === "order_events";
}

export function isOptionQuotesResponse(
response: ParsedWebSocketResponse
): response is OptionQuotesResponse {
return "service" in response && response.service === "quotes/options";
}

export function isOrderEventsSnapshotResponse(
response: ParsedWebSocketResponse
): response is OrderEventsSnapshotResponse {
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/alertLookupMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type RawAlertLookupResponse = {
};

export default class AlertLookupMessageHandler
implements WebSocketApiMessageHandler<never, AlertsResponse>
implements WebSocketApiMessageHandler<never, AlertsResponse | null>
{
parseResponse({
payload: [{ body }],
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/cancelAlertMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { debugLog } from "../util";
import { ApiService } from "./apiService";

export default class CancelAlertMessageHandler
implements WebSocketApiMessageHandler<number, AlertsResponse>
implements WebSocketApiMessageHandler<number, AlertsResponse | null>
{
parseResponse(message: RawPayloadResponse): AlertsResponse | null {
const [{ body }] = message.payload;
Expand Down
3 changes: 2 additions & 1 deletion src/client/services/chartMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export type ChartRequestParams = {
};

export default class ChartMessageHandler
implements WebSocketApiMessageHandler<ChartRequestParams, ChartResponse>
implements
WebSocketApiMessageHandler<ChartRequestParams, ChartResponse | null>
{
parseResponse(message: RawPayloadResponse): ChartResponse | null {
const body = message.payload[0].body as RawPayloadResponseChart;
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/createAlertMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type CreateAlertRequestParams = {

export default class CreateAlertMessageHandler
implements
WebSocketApiMessageHandler<CreateAlertRequestParams, AlertsResponse>
WebSocketApiMessageHandler<CreateAlertRequestParams, AlertsResponse | null>
{
parseResponse(message: RawPayloadResponse): AlertsResponse | null {
const [{ body }] = message.payload;
Expand Down
6 changes: 4 additions & 2 deletions src/client/services/optionChainDetailsMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export default class OptionChainDetailsMessageHandler
implements
WebSocketApiMessageHandler<
OptionChainDetailsRequest,
OptionChainDetailsResponse
OptionChainDetailsResponse | null
>
{
parseResponse(message: RawPayloadResponse): OptionChainDetailsResponse {
parseResponse(
message: RawPayloadResponse
): OptionChainDetailsResponse | null {
const [{ body }] = message.payload;
const { optionSeries } = body as RawOptionChainDetailsResponse;
return { seriesDetails: optionSeries };
Expand Down
90 changes: 83 additions & 7 deletions src/client/services/optionQuotesMessageHandler.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,93 @@
import WebSocketApiMessageHandler from "./webSocketApiMessageHandler";
import WebSocketApiMessageHandler, {
newPayload,
} from "./webSocketApiMessageHandler";
import { RawPayloadRequest, RawPayloadResponse } from "../tdaWsJsonTypes";
import { throwError } from "../util";
import { ApiService } from "./apiService";

export type OptionQuotesRequestParams = {
underlyingSymbol: string;
seriesNames: string[];
minStrike: number;
maxStrike: number;
};

type RawOptionQuotesSnapshotBodyResponse = {
exchanges: string[];
items: { symbol: string; values: any[] }[];

Check warning on line 16 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Unexpected any. Specify a different type

Check warning on line 16 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected any. Specify a different type

Check warning on line 16 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (19.x)

Unexpected any. Specify a different type
};

type RawOptionQuotesPatchBodyResponse = {
patches: {
op: string;
path: string;
value: any;

Check warning on line 23 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Unexpected any. Specify a different type

Check warning on line 23 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected any. Specify a different type

Check warning on line 23 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (19.x)

Unexpected any. Specify a different type
}[];
};

export type OptionQuotesResponse =
| OptionQuotesSnapshotResponse
| OptionQuotesPatchResponse;

export type OptionQuotesSnapshotResponse = {
items: { symbol: string; values: any[] }[];

Check warning on line 32 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Unexpected any. Specify a different type

Check warning on line 32 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected any. Specify a different type

Check warning on line 32 in src/client/services/optionQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (19.x)

Unexpected any. Specify a different type
service: "quotes/options";
};

export type OptionQuotesPatchResponse = {
patches: {
op: string;
path: string;
value: any;
}[];
service: "quotes/options";
};

export default class OptionQuotesMessageHandler
implements WebSocketApiMessageHandler<any, any>
implements
WebSocketApiMessageHandler<
OptionQuotesRequestParams,
OptionQuotesResponse | null
>
{
parseResponse(_: RawPayloadResponse): any {
throwError("Not implemented");
parseResponse(message: RawPayloadResponse): OptionQuotesResponse | null {
const [{ header, body }] = message.payload;
switch (header.type) {
case "snapshot": {
const { items } = body as RawOptionQuotesSnapshotBodyResponse;
return { items, service: "quotes/options" };
}
case "patch": {
const { patches } = body as RawOptionQuotesPatchBodyResponse;
return { patches, service: "quotes/options" };
}
default:
console.warn("Unexpected quotes/options response", message);
return null;
}
}

buildRequest(_: any): RawPayloadRequest {
throwError("Not implemented");
buildRequest({
underlyingSymbol,
seriesNames,
minStrike,
maxStrike,
}: OptionQuotesRequestParams): RawPayloadRequest {
return newPayload({
header: { service: "quotes/options", id: "quotes/options", ver: 0 },
params: {
underlyingSymbol,
exchange: "BEST",
fields: [
"BID",
"ASK",
"PROBABILITY_ITM",
"DELTA",
"OPEN_INT",
"VOLUME",
],
filter: { seriesNames, minStrike, maxStrike },
},
});
}

service: ApiService = "quotes/options";
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/orderEventsMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export type OrderEventsResponse =
| OrderEventsPatchResponse;

export default class OrderEventsMessageHandler
implements WebSocketApiMessageHandler<never, OrderEventsResponse>
implements WebSocketApiMessageHandler<never, OrderEventsResponse | null>
{
parseResponse(message: RawPayloadResponse): OrderEventsResponse | null {
const [{ header, body }] = message.payload;
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/placeOrderMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default class PlaceOrderMessageHandler
implements
WebSocketApiMessageHandler<
PlaceLimitOrderRequestParams,
PlaceOrderResponse
PlaceOrderResponse | null
>
{
parseResponse(message: RawPayloadResponse): PlaceOrderResponse | null {
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/positionsMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export type PositionsResponse = {
};

export default class PositionsMessageHandler
implements WebSocketApiMessageHandler<string, PositionsResponse>
implements WebSocketApiMessageHandler<string, PositionsResponse | null>
{
parseResponse(message: RawPayloadResponse): PositionsResponse | null {
const [{ header, body }] = message.payload;
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/quotesMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export type QuotesResponseItem = {
};

export default class QuotesMessageHandler
implements WebSocketApiMessageHandler<string[], QuotesResponse>
implements WebSocketApiMessageHandler<string[], QuotesResponse | null>
{
parseResponse({
payload: [{ header, body }],
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/subscribeToAlertMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const DEFAULT_ALERT_TYPES = [
];

export default class SubscribeToAlertMessageHandler
implements WebSocketApiMessageHandler<string[], AlertsResponse>
implements WebSocketApiMessageHandler<string[], AlertsResponse | null>
{
parseResponse(message: RawPayloadResponse): AlertsResponse | null {
const [{ body }] = message.payload;
Expand Down
2 changes: 1 addition & 1 deletion src/client/services/webSocketApiMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ApiService } from "./apiService";
export default interface WebSocketApiMessageHandler<ReqType, ResType> {
service: ApiService;
buildRequest: (args: ReqType) => RawPayloadRequest;
parseResponse: (message: RawPayloadResponse) => ResType | null;
parseResponse: (message: RawPayloadResponse) => ResType;
}

export function newPayload(item: RawPayloadRequestItem) {
Expand Down
4 changes: 3 additions & 1 deletion src/client/tdaWsJsonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
import { CancelOrderResponse } from "./types/placeOrderTypes";
import { OptionChainDetailsResponse } from "./services/optionChainDetailsMessageHandler";
import { RawLoginResponse } from "./services/loginMessageHandler";
import { OptionQuotesResponse } from "./services/optionQuotesMessageHandler";

export type RawPayloadResponseItemBody =
| RawPayloadResponseQuotesSnapshot
Expand Down Expand Up @@ -113,4 +114,5 @@ export type ParsedWebSocketResponse =
| PlaceOrderSnapshotResponse
| PlaceOrderPatchResponse
| OptionChainResponse
| OptionChainDetailsResponse;
| OptionChainDetailsResponse
| OptionQuotesResponse;
2 changes: 1 addition & 1 deletion src/client/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function debugLog(...args: any[]) {
}
}

type Constructor<T> = new (...args: any[]) => T;
export type Constructor<T> = new (...args: any[]) => T;

// https://stackoverflow.com/questions/17392349/how-can-i-check-if-element-is-an-instanceof-u
// Filters the array to only elements of the specified type.
Expand Down
Loading

0 comments on commit 61972e1

Please sign in to comment.