Skip to content

Commit 80fd631

Browse files
authored
feat: watchlist/get service (#70)
1 parent 46deecd commit 80fd631

File tree

6 files changed

+131
-1
lines changed

6 files changed

+131
-1
lines changed

src/client/mockWsJsonClient.ts

+13
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
LookupAlertsResponse,
3232
} from "./types/alertTypes";
3333
import { MarketDepthResponse } from "./services/marketDepthMessageHandler";
34+
import { GetWatchlistResponse } from "./services/getWatchlistMessageHandler";
3435

3536
export default class MockWsJsonClient implements WsJsonClient {
3637
async *accountPositions(_: string): AsyncIterable<PositionsResponse> {
@@ -201,4 +202,16 @@ export default class MockWsJsonClient implements WsJsonClient {
201202
service: "market_depth",
202203
};
203204
}
205+
206+
watchlist(watchlistId: number): Promise<GetWatchlistResponse> {
207+
return Promise.resolve({
208+
service: "watchlist/get",
209+
watchlist: {
210+
id: watchlistId,
211+
name: "foo",
212+
type: "STATIC",
213+
symbols: ["AAPL", "GOOG", "MSFT"],
214+
},
215+
});
216+
}
204217
}

src/client/realWsJsonClient.ts

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ import {
7575
CreateAlertResponse,
7676
LookupAlertsResponse,
7777
} from "./types/alertTypes";
78+
import GetWatchlistMessageHandler, {
79+
GetWatchlistResponse,
80+
} from "./services/getWatchlistMessageHandler";
7881

7982
export const CONNECTION_REQUEST_MESSAGE = {
8083
ver: "27.*.*",
@@ -111,6 +114,7 @@ const messageHandlers: WebSocketApiMessageHandler<never, any>[] = [
111114
new LoginMessageHandler(),
112115
new SubmitOrderMessageHandler(),
113116
new MarketDepthMessageHandler(),
117+
new GetWatchlistMessageHandler(),
114118
];
115119

116120
export default class RealWsJsonClient implements WsJsonClient {
@@ -282,6 +286,13 @@ export default class RealWsJsonClient implements WsJsonClient {
282286
return this.dispatchHandler(CancelOrderMessageHandler, orderId).promise();
283287
}
284288

289+
watchlist(watchlistId: number): Promise<GetWatchlistResponse> {
290+
return this.dispatchHandler(
291+
GetWatchlistMessageHandler,
292+
watchlistId
293+
).promise();
294+
}
295+
285296
userProperties(): Promise<UserPropertiesResponse> {
286297
return this.dispatchHandler(
287298
UserPropertiesMessageHandler,

src/client/services/apiService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ export type ApiService =
1717
| "alerts/lookup"
1818
| "optionSeries/quotes"
1919
| "market_depth"
20+
| "watchlist/get"
2021
| "fake"; // testing only
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import WebSocketApiMessageHandler, {
2+
newPayload,
3+
} from "./webSocketApiMessageHandler";
4+
import { RawPayloadRequest, RawPayloadResponse } from "../tdaWsJsonTypes";
5+
import { ApiService } from "./apiService";
6+
import { isObject } from "lodash";
7+
8+
export type GetWatchListResponseItem = {
9+
id: number;
10+
name: string;
11+
type?: string;
12+
symbols?: string[];
13+
};
14+
15+
export type GetWatchlistSnapshotResponse = {
16+
watchlist: GetWatchListResponseItem;
17+
service: "watchlist/get";
18+
};
19+
20+
export type GetWatchlistPatchResponse = {
21+
patches: {
22+
op: string;
23+
path: string;
24+
value: { watchlist: GetWatchListResponseItem } | number | string | string[];
25+
}[];
26+
service: "watchlist/get";
27+
};
28+
29+
export type GetWatchlistResponse = GetWatchlistSnapshotResponse;
30+
31+
export default class GetWatchlistMessageHandler
32+
implements WebSocketApiMessageHandler<number, GetWatchlistResponse | null>
33+
{
34+
buildRequest(watchlistId: number): RawPayloadRequest {
35+
return newPayload({
36+
header: {
37+
service: "watchlist/get",
38+
id: "watchlist/get",
39+
ver: 0,
40+
},
41+
params: { watchlistId },
42+
});
43+
}
44+
45+
parseResponse(message: RawPayloadResponse): GetWatchlistResponse | null {
46+
const [{ header, body }] = message.payload;
47+
switch (header.type) {
48+
case "snapshot": {
49+
if ("watchlist" in body) {
50+
const { watchlist } = body as { watchlist: GetWatchListResponseItem };
51+
return { watchlist, service: "watchlist/get" };
52+
} else {
53+
console.warn(
54+
"Unexpected watchlist/get snapshot response with missing `watchlist` object",
55+
message
56+
);
57+
return null;
58+
}
59+
}
60+
case "patch": {
61+
return parseGetWatchlistPatchResponse(
62+
body as GetWatchlistPatchResponse
63+
);
64+
}
65+
default:
66+
console.warn("Unexpected watchlist/get response", message);
67+
return null;
68+
}
69+
}
70+
71+
service: ApiService = "watchlist/get";
72+
}
73+
74+
function parseGetWatchlistPatchResponse(
75+
body: GetWatchlistPatchResponse
76+
): GetWatchlistResponse {
77+
const { patches } = body as GetWatchlistPatchResponse;
78+
const { path, value } = patches[0];
79+
if (path === "" && isObject(value)) {
80+
const { watchlist } = value as {
81+
watchlist: GetWatchListResponseItem;
82+
};
83+
return { watchlist, service: "watchlist/get" };
84+
}
85+
const watchlist: Record<string, any> = {};
86+
patches.forEach(({ path, value }) => {
87+
const pathParts = path.split("/");
88+
const key = pathParts[pathParts.length - 1];
89+
watchlist[key] = value;
90+
});
91+
return {
92+
watchlist: watchlist as GetWatchListResponseItem,
93+
service: "watchlist/get",
94+
};
95+
}

src/client/wsJsonClient.ts

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
LookupAlertsResponse,
3131
} from "./types/alertTypes";
3232
import { MarketDepthResponse } from "./services/marketDepthMessageHandler";
33+
import { GetWatchlistResponse } from "./services/getWatchlistMessageHandler";
3334

3435
export interface WsJsonClient {
3536
authenticate(): Promise<RawLoginResponseBody | null>;
@@ -83,4 +84,6 @@ export interface WsJsonClient {
8384
disconnect(): void;
8485

8586
marketDepth(symbol: string): AsyncIterable<MarketDepthResponse>;
87+
88+
watchlist(watchlistId: number): Promise<GetWatchlistResponse>;
8689
}

src/example/testApp.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ class TestApp {
148148
logger("bid quotes: %O", stateUpdater.bidQuotes);
149149
}
150150
}
151+
152+
async getWatchlist(watchlistId: number) {
153+
logger(" --- getWatchlist() get watchlist ---");
154+
const watchlist = await this.client.watchlist(watchlistId);
155+
logger("getWatchlist() %O", watchlist);
156+
}
151157
}
152158

153159
async function run() {
@@ -170,7 +176,8 @@ async function run() {
170176
);
171177
const { client } = await authClient.authenticateWithRetry(token);
172178
const app = new TestApp(client);
173-
await app.marketDepth("/NQ");
179+
await app.getWatchlist(-3);
180+
await app.getWatchlist(126216147);
174181
}
175182

176183
run().catch(console.error);

0 commit comments

Comments
 (0)