From 44f8342f6eee803c68e0abb0843e6d3690642707 Mon Sep 17 00:00:00 2001 From: Investor Flow Build Date: Tue, 11 Aug 2026 23:13:15 -0400 Subject: [PATCH] feat: add confluence.signalHistory API route Returns signal fire history for a symbol (last 6 months) with slot names and body types. Used by the upcoming confluence chart to plot signal markers on price data. --- app/server/src/trpc/router.ts | 28 +++++++++++++++++++++++++++- app/src/lib/trpc.ts | 10 ++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/app/server/src/trpc/router.ts b/app/server/src/trpc/router.ts index d009efc..fe044fa 100644 --- a/app/server/src/trpc/router.ts +++ b/app/server/src/trpc/router.ts @@ -33,7 +33,7 @@ import { computeOwnershipPercentages, fetchAndPersistFloat } from '../services/s import { EdgarAdapter } from '../adapters/EdgarAdapter.ts'; import { OptionsAdapter, parseOptionChainRows } from '../adapters/OptionsAdapter.ts'; import type { OptionChainRow, OptionGreeks } from '../adapters/OptionsAdapter.ts'; -import { CONFLUENCE_SLOTS, slotsByFamily, type SlotFamily } from '../confluence/confluenceSlots.ts'; +import { CONFLUENCE_SLOTS, slotsByFamily, type SlotFamily, confluenceslotById } from '../confluence/confluenceSlots.ts'; import { CONFLUENCE_SLOT_IDS } from '../confluence/confluenceSlots.ts'; import { ConfluenceRepository, rackFromSlots } from '../db/confluenceRepository.ts'; import { runSlotBacktest, signalHistoryToStats, type ConfluenceFireEvent } from '../confluence/confluenceBacktest.ts'; @@ -5473,6 +5473,32 @@ const confluenceRouter = router({ }; }), + /** Signal fire history for a symbol (last 6 months). Used by the confluence chart. */ + signalHistory: publicProcedure + .input(z.object({ symbol: z.string().min(1).max(12) })) + .query(({ ctx, input }) => { + const symbol = input.symbol.toUpperCase(); + const repo = new ConfluenceRepository(ctx.db); + const allFires = repo.listSignalsForSymbol(symbol); + // Filter to last 6 months + const cutoff = new Date(); + cutoff.setMonth(cutoff.getMonth() - 6); + const cutoffStr = cutoff.toISOString(); + const fires = allFires + .filter((f) => f.firedAt >= cutoffStr) + .map((f) => { + const meta = confluenceslotById.get(f.slotId); + return { + slotId: f.slotId, + slotName: meta?.name ?? f.slotId, + body: (meta?.body ?? 'bull') as 'bull' | 'bear' | 'exit', + firedAt: f.firedAt, + qualityAtFire: f.qualityAtFire, + }; + }); + return { fires }; + }), + /** Create or update a user-owned rack. */ saveRack: protectedProcedure .input(z.object({ diff --git a/app/src/lib/trpc.ts b/app/src/lib/trpc.ts index aa824bc..869bd21 100644 --- a/app/src/lib/trpc.ts +++ b/app/src/lib/trpc.ts @@ -1472,6 +1472,16 @@ export const api = { }>("confluence.backtest", { symbol, ...(opts?.weeks ? { weeks: opts.weeks } : {}) }), scorecard: (symbol?: string) => trpcQuery<{ slots: unknown[]; note: string }>("confluence.scorecard", symbol ? { symbol } : {}), + signalHistory: (symbol: string) => + trpcQuery<{ + fires: Array<{ + slotId: string; + slotName: string; + body: 'bull' | 'bear' | 'exit'; + firedAt: string; + qualityAtFire: string; + }>; + }>("confluence.signalHistory", { symbol }), saveRack: (input: { id?: string; name: string; description?: string; slotIds: string[] }) => trpcMutate<{ id: string; name: string; slotIds: string[]; isSystem: boolean; description: string | null }>("confluence.saveRack", input), corridorSnapshot: (symbol: string) =>