add alpaca news wire across ingest api and web

This commit is contained in:
dirtydishes 2026-05-18 16:55:31 -04:00
parent 62aae70878
commit 906fe411c9
31 changed files with 1407 additions and 50 deletions

View file

@ -262,3 +262,26 @@ export const InferredDarkEventSchema = EventMetaSchema.merge(
);
export type InferredDarkEvent = z.infer<typeof InferredDarkEventSchema>;
export const NewsSymbolResolutionSchema = z.enum(["provider", "derived", "mixed", "none"]);
export type NewsSymbolResolution = z.infer<typeof NewsSymbolResolutionSchema>;
export const NewsStorySchema = EventMetaSchema.merge(
z.object({
story_id: z.number().int().nonnegative(),
provider: z.string().min(1),
source: z.string().min(1),
headline: z.string().min(1),
summary: z.string(),
content_html: z.string(),
url: z.string().url().or(z.literal("")),
published_ts: z.number().int().nonnegative(),
updated_ts: z.number().int().nonnegative(),
provider_symbols: z.array(z.string().min(1)),
resolved_symbols: z.array(z.string().min(1)),
symbol_resolution: NewsSymbolResolutionSchema
})
);
export type NewsStory = z.infer<typeof NewsStorySchema>;

View file

@ -8,6 +8,7 @@ import {
EquityQuoteSchema,
FlowPacketSchema,
InferredDarkEventSchema,
NewsStorySchema,
OptionNBBOSchema,
OptionPrintSchema,
SmartMoneyEventSchema
@ -34,7 +35,8 @@ export const LiveGenericChannelSchema = z.enum([
"smart-money",
"classifier-hits",
"alerts",
"inferred-dark"
"inferred-dark",
"news"
]);
export const LiveChannelSchema = z.enum([
@ -48,6 +50,7 @@ export const LiveChannelSchema = z.enum([
"classifier-hits",
"alerts",
"inferred-dark",
"news",
"equity-candles",
"equity-overlay"
]);
@ -91,7 +94,7 @@ export const LiveSubscriptionSchema = z.discriminatedUnion("channel", [
snapshot_limit: z.number().int().positive().optional()
}),
z.object({
channel: z.enum(["nbbo", "equity-quotes", "equity-joins", "classifier-hits", "alerts", "inferred-dark"]),
channel: z.enum(["nbbo", "equity-quotes", "equity-joins", "classifier-hits", "alerts", "inferred-dark", "news"]),
snapshot_limit: z.number().int().positive().optional()
}),
z.object({
@ -123,6 +126,7 @@ const livePayloadSchemas = {
"classifier-hits": ClassifierHitEventSchema,
alerts: AlertEventSchema,
"inferred-dark": InferredDarkEventSchema,
news: NewsStorySchema,
"equity-candles": EquityCandleSchema,
"equity-overlay": EquityPrintSchema
} as const;

View file

@ -9,6 +9,7 @@ import {
describe("live protocol types", () => {
it("builds stable keys for generic and parameterized subscriptions", () => {
expect(getSubscriptionKey({ channel: "flow" })).toBe("flow|{}");
expect(getSubscriptionKey({ channel: "news" })).toBe("news");
expect(
getSubscriptionKey({
channel: "options",
@ -53,12 +54,13 @@ describe("live protocol types", () => {
op: "subscribe",
subscriptions: [
{ channel: "flow", filters: { nbboSides: ["AA", "A"], minNotional: 50000 } },
{ channel: "news", snapshot_limit: 100 },
{ channel: "equity-candles", underlying_id: "SPY", interval_ms: 60000 }
]
});
expect(parsed.op).toBe("subscribe");
expect(parsed.subscriptions).toHaveLength(2);
expect(parsed.subscriptions).toHaveLength(3);
});
it("validates snapshot and event server messages", () => {
@ -74,18 +76,24 @@ describe("live protocol types", () => {
});
const event = LiveServerMessageSchema.parse({
op: "event",
subscription: { channel: "equity-overlay", underlying_id: "SPY" },
subscription: { channel: "news" },
item: {
source_ts: 100,
ingest_ts: 101,
seq: 1,
trace_id: "eq-1",
ts: 100,
underlying_id: "SPY",
price: 500,
size: 10,
exchange: "X",
offExchangeFlag: true
trace_id: "alpaca:1",
story_id: 1,
provider: "alpaca",
source: "Benzinga",
headline: "TSLA rises",
summary: "",
content_html: "<p>TSLA rises</p>",
url: "https://example.com/story",
published_ts: 100,
updated_ts: 100,
provider_symbols: ["TSLA"],
resolved_symbols: ["TSLA"],
symbol_resolution: "provider"
},
watermark: cursor
});