Skip to content

Commit

Permalink
better quotes response handling
Browse files Browse the repository at this point in the history
  • Loading branch information
felipecsl committed Aug 30, 2023
1 parent 6fdb0e9 commit eb7feeb
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 195 deletions.
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.5.0",
"version": "0.5.2",
"description": "WebSocket client for the TD Ameritrade wsjson API",
"main": "dist/web.bundle.js",
"types": "dist/web.d.ts",
Expand Down
268 changes: 76 additions & 192 deletions src/client/services/quotesMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,54 @@ import WebSocketApiMessageHandler, {
newPayload,
} from "./webSocketApiMessageHandler";
import { RawPayloadRequest, RawPayloadResponse } from "../tdaWsJsonTypes";
import { compact, isEmpty, isNumber } from "lodash";
import { compact, isEmpty, isNil, isNumber, omitBy } from "lodash";
import { ApiService } from "./apiService";

const ALL_FIELDS = [
"MARK",
"MARK_CHANGE",
"MARK_PERCENT_CHANGE",
"NET_CHANGE",
"NET_CHANGE_PERCENT",
"BID",
"ASK",
"BID_SIZE",
"ASK_SIZE",
"VOLUME",
"OPEN",
"HIGH",
"LOW",
"LAST",
"LAST_SIZE",
"CLOSE",
] as const;

type FieldType = (typeof ALL_FIELDS)[number];

export type RawPayloadResponseQuotesItem = {
symbol?: string;
isDelayed?: boolean;
values: {
[key in FieldType]?: number;
};
};

export type RawPayloadResponseQuotesSnapshot = {
items: {
isDelayed: boolean;
symbol: string;
values: { [key: string]: any };
}[];
items: RawPayloadResponseQuotesItem[];
};

type RawPayloadResponseQuotesPatchValue = {
items: {
symbol: string;
isDelayed: boolean;
values: {
ASK?: number;
ASK_SIZE?: number;
BID?: number;
BID_SIZE?: number;
LAST?: number;
LAST_SIZE?: number;
OPEN?: number;
CLOSE?: number;
HIGH?: number;
LOW?: number;
MARK?: number;
MARK_CHANGE?: number;
MARK_PERCENT_CHANGE?: number;
NET_CHANGE?: number;
NET_CHANGE_PERCENT?: number;
VOLUME?: number;
};
}[];
items: RawPayloadResponseQuotesItem[];
};

export type RawPayloadResponseQuotesPatch = {
patches: {
op: string;
path: string;
value: number | RawPayloadResponseQuotesPatchValue;
value:
| number
| RawPayloadResponseQuotesPatchValue
| RawPayloadResponseQuotesItem;
}[];
};

Expand Down Expand Up @@ -98,24 +105,7 @@ export default class QuotesMessageHandler
account: "COMBINED ACCOUNT",
symbols,
refreshRate: 300,
fields: [
"MARK",
"MARK_CHANGE",
"MARK_PERCENT_CHANGE",
"NET_CHANGE",
"NET_CHANGE_PERCENT",
"BID",
"ASK",
"BID_SIZE",
"ASK_SIZE",
"VOLUME",
"OPEN",
"HIGH",
"LOW",
"LAST",
"LAST_SIZE",
"CLOSE",
],
fields: ALL_FIELDS,
},
});
}
Expand All @@ -126,165 +116,59 @@ export default class QuotesMessageHandler
function parseSnapshotDataMessage({
items,
}: RawPayloadResponseQuotesSnapshot): QuotesResponse {
const quotes = items.map(({ values, symbol }) => {
const last = values.LAST;
const lastSize = values.LAST_SIZE;
const ask = values.ASK;
const bid = values.BID;
const askSize = values.ASK_SIZE;
const bidSize = values.BID_SIZE;
const mark = values.MARK;
const markChange = values.MARK_CHANGE;
const markChangePercent = values.MARK_PERCENT_CHANGE;
const low = values.LOW;
const high = values.HIGH;
const volume = values.VOLUME;
const open = values.OPEN;
const close = values.CLOSE;
const netChange = values.NET_CHANGE;
const netChangePercent = values.NET_CHANGE_PERCENT;
return {
symbol,
last,
lastSize,
ask,
bid,
askSize,
bidSize,
mark,
markChange,
markChangePercent,
low,
high,
volume,
open,
close,
netChange,
netChangePercent,
};
});
const quotes = items.map(parseQuoteItem);
return { quotes };
}

function parseQuoteItem({
symbol,
values,
}: RawPayloadResponseQuotesItem): QuotesResponseItem {
return {
last: values.LAST,
lastSize: values.LAST_SIZE,
ask: values.ASK,
askSize: values.ASK_SIZE,
bid: values.BID,
bidSize: values.BID_SIZE,
high: values.HIGH,
low: values.LOW,
open: values.OPEN,
close: values.CLOSE,
mark: values.MARK,
markChange: values.MARK_CHANGE,
markChangePercent: values.MARK_PERCENT_CHANGE,
netChange: values.NET_CHANGE,
netChangePercent: values.NET_CHANGE_PERCENT,
volume: values.VOLUME,
symbol,
};
}

function parsePatchQuotesDataMessage({
patches,
}: RawPayloadResponseQuotesPatch): QuotesResponse | null {
const valueIfPath = (value: number, path: string, suffix: string) =>
path.endsWith(suffix) ? value : undefined;
const quotes = patches.flatMap(({ path, value }) => {
if (path && isNumber(value)) {
const last = valueIfPath(value, path, "/LAST");
const lastSize = valueIfPath(value, path, "/LAST_SIZE");
const mark = valueIfPath(value, path, "/MARK");
const markChange = valueIfPath(value, path, "/MARK_CHANGE");
const markChangePercent = valueIfPath(
value,
path,
"/MARK_PERCENT_CHANGE"
const fieldValues = Object.fromEntries(
ALL_FIELDS.map((f) => [f, valueIfPath(value, path, `/${f}`)])
);
const ask = valueIfPath(value, path, "/ASK");
const askSize = valueIfPath(value, path, "/ASK_SIZE");
const bid = valueIfPath(value, path, "/BID");
const bidSize = valueIfPath(value, path, "/BID_SIZE");
const netChange = valueIfPath(value, path, "/NET_CHANGE");
const low = valueIfPath(value, path, "/LOW");
const high = valueIfPath(value, path, "/HIGH");
const open = valueIfPath(value, path, "/OPEN");
const close = valueIfPath(value, path, "/CLOSE");
const netChangePercent = valueIfPath(value, path, "/NET_CHANGE_PERCENT");
const symbolIndex = +path.split("/")[2];
if (
!isEmpty(
compact([
last,
lastSize,
ask,
bid,
askSize,
bidSize,
symbolIndex,
mark,
markChange,
markChangePercent,
netChange,
netChangePercent,
low,
high,
open,
close,
])
)
) {
return [
{
last,
lastSize,
ask,
bid,
askSize,
bidSize,
symbolIndex,
mark,
markChange,
markChangePercent,
netChange,
netChangePercent,
low,
high,
open,
close,
},
];
} else {
// no relevant data to return, omit
return [];
}
const quote = parseQuoteItem({ values: fieldValues });
return [{ ...quote, symbolIndex }];
} else if (!isNumber(value) && "items" in value) {
const { items } = value;
return items.map(parseQuoteItem);
} else if (!isNumber(value)) {
return [parseQuoteItem(value)];
} else {
const { items } = value as RawPayloadResponseQuotesPatchValue;
return items.map(
({
symbol,
values: {
LAST,
LAST_SIZE,
ASK,
ASK_SIZE,
BID,
BID_SIZE,
OPEN,
CLOSE,
HIGH,
LOW,
MARK,
MARK_CHANGE,
MARK_PERCENT_CHANGE,
NET_CHANGE,
NET_CHANGE_PERCENT,
VOLUME,
},
}) =>
({
last: LAST,
lastSize: LAST_SIZE,
ask: ASK,
askSize: ASK_SIZE,
bid: BID,
bidSize: BID_SIZE,
high: HIGH,
low: LOW,
open: OPEN,
close: CLOSE,
mark: MARK,
markChange: MARK_CHANGE,
markChangePercent: MARK_PERCENT_CHANGE,
netChange: NET_CHANGE,
netChangePercent: NET_CHANGE_PERCENT,
volume: VOLUME,
symbol,
} as QuotesResponseItem)
);
return [];
}
});
const finalQuotes = compact(quotes);
return !isEmpty(finalQuotes) ? { quotes: finalQuotes } : null;
return !isEmpty(finalQuotes)
? { quotes: finalQuotes.map((q) => omitBy(q, isNil)) }
: null;
}
9 changes: 7 additions & 2 deletions src/testApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { PlaceLimitOrderRequestParams } from "./client/services/placeOrderMessag
import { CreateAlertRequestParams } from "./client/services/createAlertMessageHandler";
import { OptionQuotesRequestParams } from "./client/services/optionQuotesMessageHandler";
import fetch from "node-fetch";
import { isNil, omitBy } from "lodash";

const logger = debug("testapp");

Expand Down Expand Up @@ -44,7 +43,13 @@ class TestApp {
for await (const quote of this.client.quotes(symbols)) {
logger(
"quotes() %O",
quote.quotes.map((q) => omitBy(q, isNil))
quote.quotes.map((q) => {
if (!q.symbol && q.symbolIndex) {
q.symbol = symbols[q.symbolIndex];
delete q.symbolIndex;
}
return q;
})
);
}
}
Expand Down

0 comments on commit eb7feeb

Please sign in to comment.