104 lines
2.4 KiB
TypeScript
104 lines
2.4 KiB
TypeScript
import type { EquityPrint, EquityPrintJoin, EquityQuote } from "@islandflow/types";
|
|
|
|
export type EquityQuoteJoin = {
|
|
quote: EquityQuote | null;
|
|
ageMs: number;
|
|
stale: boolean;
|
|
};
|
|
|
|
export type QuotePlacement = "AA" | "A" | "B" | "BB" | "MID" | "MISSING" | "STALE";
|
|
|
|
const roundTo = (value: number, digits = 4): number => {
|
|
if (!Number.isFinite(value)) {
|
|
return 0;
|
|
}
|
|
return Number(value.toFixed(digits));
|
|
};
|
|
|
|
export const classifyQuotePlacement = (
|
|
price: number,
|
|
join: EquityQuoteJoin
|
|
): QuotePlacement => {
|
|
if (!Number.isFinite(price)) {
|
|
return "MISSING";
|
|
}
|
|
if (!join.quote) {
|
|
return "MISSING";
|
|
}
|
|
if (join.stale) {
|
|
return "STALE";
|
|
}
|
|
|
|
const bid = join.quote.bid;
|
|
const ask = join.quote.ask;
|
|
if (!Number.isFinite(bid) || !Number.isFinite(ask) || ask <= 0) {
|
|
return "MISSING";
|
|
}
|
|
|
|
const spread = Math.max(0, ask - bid);
|
|
const epsilon = Math.max(0.01, spread * 0.05);
|
|
|
|
if (price > ask + epsilon) {
|
|
return "AA";
|
|
}
|
|
if (price >= ask - epsilon) {
|
|
return "A";
|
|
}
|
|
if (price < bid - epsilon) {
|
|
return "BB";
|
|
}
|
|
if (price <= bid + epsilon) {
|
|
return "B";
|
|
}
|
|
|
|
return "MID";
|
|
};
|
|
|
|
export const buildEquityPrintJoin = (
|
|
print: EquityPrint,
|
|
join: EquityQuoteJoin
|
|
): EquityPrintJoin => {
|
|
const joinQuality: Record<string, number> = {};
|
|
const placement = classifyQuotePlacement(print.price, join);
|
|
const features: Record<string, string | number | boolean> = {
|
|
underlying_id: print.underlying_id,
|
|
price: print.price,
|
|
size: print.size,
|
|
off_exchange_flag: print.offExchangeFlag,
|
|
print_ts: print.ts,
|
|
quote_placement: placement
|
|
};
|
|
|
|
if (!join.quote) {
|
|
joinQuality.quote_missing = 1;
|
|
} else {
|
|
joinQuality.quote_age_ms = join.ageMs;
|
|
if (join.stale) {
|
|
joinQuality.quote_stale = 1;
|
|
} else {
|
|
const bid = join.quote.bid;
|
|
const ask = join.quote.ask;
|
|
const mid = (bid + ask) / 2;
|
|
const spread = ask - bid;
|
|
features.quote_ts = join.quote.ts;
|
|
features.quote_bid = bid;
|
|
features.quote_ask = ask;
|
|
features.quote_mid = roundTo(mid);
|
|
features.quote_spread = roundTo(spread);
|
|
}
|
|
}
|
|
|
|
const joinId = `equityjoin:${print.trace_id}`;
|
|
|
|
return {
|
|
source_ts: print.source_ts,
|
|
ingest_ts: print.ingest_ts,
|
|
seq: print.seq,
|
|
trace_id: joinId,
|
|
id: joinId,
|
|
print_trace_id: print.trace_id,
|
|
quote_trace_id: join.quote?.trace_id ?? "",
|
|
features,
|
|
join_quality: joinQuality
|
|
};
|
|
};
|