Skip to content

Commit

Permalink
feat: Add optionSeries/quotes message support (#62)
Browse files Browse the repository at this point in the history
* feat: Add optionSeries/quotes message support

* update readme
  • Loading branch information
felipecsl authored Aug 27, 2023
1 parent 8c801f5 commit 771a141
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ yarn start
- ✅ Option chains
- ✅ Alert lookup
- ✅ Option chain details
- ✅ Option chain quotes
- ✅ Option quotes
- ✅ Order events

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
{
"name": "tda-wsjson-client",
"version": "0.3.8",
"version": "0.3.9",
"description": "WebSocket client for the TD Ameritrade wsjson API",
"main": "dist/web.bundle.js",
"types": "dist/web.d.ts",
"exports": {
"./wsJsonClient": "./dist/client/wsJsonClient.js",
"./wsJsonClientAuth": "./dist/client/wsJsonClientAuth.js",
"./alertTypes": "./dist/client/types/alertTypes.js",
"./tdaWsJsonTypes": "./dist/client/tdaWsJsonTypes.js",
"./wsJsonClientAuth": "./dist/client/wsJsonClientAuth.js",
"./messageTypeHelpers": "./dist/client/messageTypeHelpers.js",
"./quotesMessageHandler": "./dist/client/services/quotesMessageHandler.js",
"./chartMessageHandler": "./dist/client/services/chartMessageHandler.js",
"./createAlertMessageHandler": "./dist/client/services/createAlertMessageHandler.js",
"./quotesMessageHandler": "./dist/client/services/quotesMessageHandler.js",
"./positionsMessageHandler": "./dist/client/services/positionsMessageHandler.js",
"./placeOrderMessageHandler": "./dist/client/services/placeOrderMessageHandler.js",
"./createAlertMessageHandler": "./dist/client/services/createAlertMessageHandler.js",
"./orderEventsMessageHandler": "./dist/client/services/orderEventsMessageHandler.js",
"./optionSeriesMessageHandler": "./dist/client/services/optionSeriesMessageHandler.js",
"./optionQuotesMessageHandler": "./dist/client/services/optionQuotesMessageHandler.js",
"./userPropertiesMessageHandler": "./dist/client/services/userPropertiesMessageHandler.js",
"./instrumentSearchMessageHandler": "./dist/client/services/instrumentSearchMessageHandler.js",
"./optionSeriesQuotesMessageHandler": "./dist/client/services/optionSeriesQuotesMessageHandler.js",
"./optionChainDetailsMessageHandler": "./dist/client/services/optionChainDetailsMessageHandler.js"
},
"scripts": {
Expand Down
35 changes: 31 additions & 4 deletions src/client/messageTypeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PositionsResponse } from "./services/positionsMessageHandler";
import { PlaceOrderSnapshotResponse } from "./services/placeOrderMessageHandler";
import {
OrderEventsPatchResponse,
OrderEventsResponse,
OrderEventsSnapshotResponse,
} from "./services/orderEventsMessageHandler";
import { UserPropertiesResponse } from "./services/userPropertiesMessageHandler";
Expand All @@ -19,6 +20,10 @@ import { OptionChainResponse } from "./services/optionSeriesMessageHandler";
import { RawLoginResponse } from "./services/loginMessageHandler";
import { OptionQuotesResponse } from "./services/optionQuotesMessageHandler";
import { CancelOrderResponse } from "./services/cancelOrderMessageHandler";
import {
OptionSeriesQuotesPatchResponse,
OptionSeriesQuotesSnapshotResponse,
} from "./services/optionSeriesQuotesMessageHandler";

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

export function isOrderEventsResponse(
response: ParsedWebSocketResponse
): response is OrderEventsResponse {
const isSnapshotResponse = (
response: ParsedWebSocketResponse
): response is OrderEventsSnapshotResponse =>
"orders" in response && response.service === "order_events";

return isSnapshotResponse(response) || isOrderEventsPatchResponse(response);
}

// TODO: add support for patch responses
export function isOptionQuotesResponse(
response: ParsedWebSocketResponse
): response is OptionQuotesResponse {
return "service" in response && response.service === "quotes/options";
}

export function isOrderEventsSnapshotResponse(
export function isOptionSeriesQuotesResponse(
response: ParsedWebSocketResponse
): response is OrderEventsSnapshotResponse {
return "orders" in response && response.service === "order_events";
): response is OptionSeriesQuotesSnapshotResponse {
const isSnapshotResponse = (
response: ParsedWebSocketResponse
): response is OptionSeriesQuotesSnapshotResponse =>
"series" in response && response.service === "optionSeries/quotes";

const isPatchResponse = (
response: ParsedWebSocketResponse
): response is OptionSeriesQuotesPatchResponse =>
"patches" in response && response.service === "optionSeries/quotes";

return isSnapshotResponse(response) || isPatchResponse(response);
}

export function isAlertsResponse(
Expand All @@ -110,5 +137,5 @@ export function isInstrumentsResponse(
export function isOptionChainResponse(
response: ParsedWebSocketResponse
): response is OptionChainResponse {
return "series" in response;
return "series" in response && response.service === "optionSeries";
}
3 changes: 2 additions & 1 deletion src/client/services/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export type ApiService =
| "alerts/create"
| "alerts/cancel"
| "alerts/subscribe"
| "alerts/lookup";
| "alerts/lookup"
| "optionSeries/quotes";
4 changes: 3 additions & 1 deletion src/client/services/optionSeriesMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type RawOptionSeriesResponse = {

export type OptionChainResponse = {
series: OptionChainItem[];
service: "optionSeries";
};

export type OptionChainItem = {
Expand All @@ -43,6 +44,7 @@ export default class OptionSeriesMessageHandler
const { series } = body as RawOptionSeriesResponse;
if (series) {
return {
service: "optionSeries",
series: series.map((s) => ({
underlying: s.underlying,
name: s.name,
Expand All @@ -54,7 +56,7 @@ export default class OptionSeriesMessageHandler
})),
};
} else {
return { series: [] };
return { series: [], service: "optionSeries" };
}
}

Expand Down
68 changes: 68 additions & 0 deletions src/client/services/optionSeriesQuotesMessageHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import WebSocketApiMessageHandler, {
newPayload,
} from "./webSocketApiMessageHandler";
import { RawPayloadRequest, RawPayloadResponse } from "../tdaWsJsonTypes";
import { ApiService } from "./apiService";

export type OptionSeriesQuote = {
name: string;
values: {
IMPLIED_VOLATILITY: number;
SERIES_EXPECTED_MOVE: number;
};
};

export type OptionSeriesQuotesPatchResponse = {
patches: {
op: string;
path: string;
value: any;

Check warning on line 19 in src/client/services/optionSeriesQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Unexpected any. Specify a different type

Check warning on line 19 in src/client/services/optionSeriesQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Unexpected any. Specify a different type

Check warning on line 19 in src/client/services/optionSeriesQuotesMessageHandler.ts

View workflow job for this annotation

GitHub Actions / build (19.x)

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

export type OptionSeriesQuotesSnapshotResponse = {
series: OptionSeriesQuote[];
service: "optionSeries/quotes";
};

export type OptionSeriesQuotesResponse =
| OptionSeriesQuotesSnapshotResponse
| OptionSeriesQuotesPatchResponse;

export default class OptionSeriesQuotesMessageHandler
implements WebSocketApiMessageHandler<string, OptionSeriesQuotesResponse>
{
buildRequest(symbol: string): RawPayloadRequest {
return newPayload({
header: {
service: "optionSeries/quotes",
id: "optionSeriesQuotes",
ver: 0,
},
params: {
underlying: symbol,
exchange: "BEST",
fields: ["IMPLIED_VOLATILITY", "SERIES_EXPECTED_MOVE"],
},
});
}

parseResponse(message: RawPayloadResponse): OptionSeriesQuotesResponse {
const { payload } = message;
const [{ body }] = payload;
if ("series" in body) {
// snapshot response
const { series } = body as unknown as { series: OptionSeriesQuote[] };
return { series, service: "optionSeries/quotes" };
} else {
// patch response
const { patches } = body as unknown as {
patches: OptionSeriesQuotesPatchResponse["patches"];
};
return { patches, service: "optionSeries/quotes" };
}
}

service: ApiService = "optionSeries/quotes";
}
4 changes: 3 additions & 1 deletion src/client/tdaWsJsonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { OptionChainDetailsResponse } from "./services/optionChainDetailsMessage
import { RawLoginResponse } from "./services/loginMessageHandler";
import { OptionQuotesResponse } from "./services/optionQuotesMessageHandler";
import { CancelOrderResponse } from "./services/cancelOrderMessageHandler";
import { OptionSeriesQuotesResponse } from "./services/optionSeriesQuotesMessageHandler";

export type RawPayloadResponseItemBody =
| RawPayloadResponseQuotesSnapshot
Expand Down Expand Up @@ -115,4 +116,5 @@ export type ParsedWebSocketResponse =
| PlaceOrderPatchResponse
| OptionChainResponse
| OptionChainDetailsResponse
| OptionQuotesResponse;
| OptionQuotesResponse
| OptionSeriesQuotesResponse;
15 changes: 12 additions & 3 deletions src/client/wsJsonClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
isLoginResponse,
isOptionChainResponse,
isOptionQuotesResponse,
isOptionSeriesQuotesResponse,
isOrderEventsPatchResponse,
isOrderEventsSnapshotResponse,
isOrderEventsResponse,
isPlaceOrderResponse,
isPositionsResponse,
isQuotesResponse,
Expand Down Expand Up @@ -79,6 +80,9 @@ import LoginMessageHandler, {
} from "./services/loginMessageHandler";
import SubmitOrderMessageHandler from "./services/submitOrderMessageHandler";
import WorkingOrdersMessageHandler from "./services/workingOrdersMessageHandler";
import OptionSeriesQuotesMessageHandler, {
OptionSeriesQuotesResponse,
} from "./services/optionSeriesQuotesMessageHandler";

export const CONNECTION_REQUEST_MESSAGE = {
ver: "27.*.*",
Expand All @@ -105,6 +109,7 @@ const messageHandlers: WebSocketApiMessageHandler<never, any>[] = [
new ChartMessageHandler(),
new InstrumentSearchMessageHandler(),
new OptionSeriesMessageHandler(),
new OptionSeriesQuotesMessageHandler(),
new OrderEventsMessageHandler(),
new PlaceOrderMessageHandler(),
new PositionsMessageHandler(),
Expand Down Expand Up @@ -233,6 +238,12 @@ export default class WsJsonClient {
.promise() as Promise<OptionChainResponse>;
}

optionChainQuotes(symbol: string): AsyncIterable<OptionSeriesQuotesResponse> {
return this.dispatchHandler(OptionSeriesQuotesMessageHandler, symbol)
.filter(isOptionSeriesQuotesResponse)
.iterable() as AsyncIterable<OptionSeriesQuotesResponse>;
}

optionChainDetails(
request: OptionChainDetailsRequest
): Promise<OptionChainDetailsResponse> {
Expand Down Expand Up @@ -272,8 +283,6 @@ export default class WsJsonClient {

workingOrders(accountNumber: string): AsyncIterable<OrderEventsResponse> {
const handler = new WorkingOrdersMessageHandler();
const isOrderEventsResponse = (r: ParsedWebSocketResponse) =>
isOrderEventsSnapshotResponse(r) || isOrderEventsPatchResponse(r);
return this.dispatch(handler, accountNumber)
.filter(isOrderEventsResponse)
.iterable() as AsyncIterable<OrderEventsResponse>;
Expand Down
10 changes: 9 additions & 1 deletion src/testApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ class TestApp {
logger("optionChain() %O", optionChain);
}

async optionChainQuotes(symbol: string) {
logger(" --- optionChainQuotes() requesting option chain quotes ---");
const events = this.client.optionChainQuotes(symbol);
for await (const event of events) {
logger("optionChainQuotes() : " + JSON.stringify(event));
}
}

async optionChainDetails(symbol: string, seriesNames: string[]) {
logger(" --- optionChainDetails() requesting option chain details ---");
const optionChainDetails = await this.client.optionChainDetails({
Expand Down Expand Up @@ -124,7 +132,7 @@ async function run() {
const authClient = new WsJsonClientAuth(clientId, fetch);
const { client } = await authClient.authenticateWithRetry(token);
const app = new TestApp(client);
await app.workingOrders();
await app.optionChainQuotes("ABNB");
}

run().catch(console.error);

0 comments on commit 771a141

Please sign in to comment.