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.
This commit is contained in:
Investor Flow Build
2026-08-11 23:13:15 -04:00
parent 1a063d199a
commit 44f8342f6e
2 changed files with 37 additions and 1 deletions
+27 -1
View File
@@ -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({
+10
View File
@@ -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) =>