2026-06-29 17:51:54 -04:00
|
|
|
|
// Thin typed client over the tRPC backend (DESIGN.md §2). Slice 1 uses a fetch wrapper
|
|
|
|
|
|
// rather than @trpc/client to keep the Next app decoupled from the server's Node-native
|
|
|
|
|
|
// type graph; the server is a real tRPC server and full end-to-end client types can be
|
|
|
|
|
|
// layered later without changing this surface.
|
2026-07-23 18:02:24 -04:00
|
|
|
|
|
|
|
|
|
|
import type { OptionChainRow, OptionGreeks } from "@/types/options";
|
|
|
|
|
|
|
2026-06-29 17:51:54 -04:00
|
|
|
|
export interface Quote {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
price: number;
|
|
|
|
|
|
bid?: number | null;
|
|
|
|
|
|
ask?: number | null;
|
|
|
|
|
|
change?: number | null;
|
|
|
|
|
|
changePercent?: number | null;
|
|
|
|
|
|
iv?: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
/** Session of the displayed last print. */
|
|
|
|
|
|
session?: 'regular' | 'pre' | 'post' | null;
|
|
|
|
|
|
/** RTH last when price is an extended print. */
|
|
|
|
|
|
regularPrice?: number | null;
|
2026-06-29 17:51:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
export interface PriceCandle {
|
|
|
|
|
|
ts: string;
|
|
|
|
|
|
o: number;
|
|
|
|
|
|
h: number;
|
|
|
|
|
|
l: number;
|
|
|
|
|
|
c: number;
|
|
|
|
|
|
v: number;
|
|
|
|
|
|
adjClose?: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
export interface SymbolMeta {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name?: string | null;
|
|
|
|
|
|
sector?: string | null;
|
|
|
|
|
|
industry?: string | null;
|
|
|
|
|
|
exchange?: string | null;
|
|
|
|
|
|
tickerKind: string;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
description?: string | null;
|
|
|
|
|
|
website?: string | null;
|
|
|
|
|
|
marketCap?: number | null;
|
|
|
|
|
|
currency?: string | null;
|
|
|
|
|
|
employees?: number | null;
|
|
|
|
|
|
country?: string | null;
|
|
|
|
|
|
city?: string | null;
|
2026-06-29 17:51:54 -04:00
|
|
|
|
peers?: string[] | null;
|
|
|
|
|
|
}
|
2026-07-23 18:02:24 -04:00
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
export interface SymbolSearchResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string | null;
|
|
|
|
|
|
sector: string | null;
|
|
|
|
|
|
industry: string | null;
|
|
|
|
|
|
exchange: string | null;
|
|
|
|
|
|
ticker_kind: string;
|
|
|
|
|
|
cik: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FundHoldersResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
tracked: Array<{
|
|
|
|
|
|
fundId: string; fundName: string; shares: number | null; valueUsd: number | null;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
asOf: string; source: string; notes: string | null; weightPct: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
}>;
|
|
|
|
|
|
institutionCount: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
|
export interface XPost {
|
|
|
|
|
|
post_id: string;
|
|
|
|
|
|
author_handle: string;
|
|
|
|
|
|
body_text: string | null;
|
|
|
|
|
|
posted_at: string;
|
|
|
|
|
|
engagement: number;
|
|
|
|
|
|
cashtag?: string | null;
|
|
|
|
|
|
attribution?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface XAccountRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
handle: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
created_at: string;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
pullStatus?: string | null;
|
|
|
|
|
|
pullAt?: string | null;
|
|
|
|
|
|
pullError?: string | null;
|
|
|
|
|
|
lastPostAt?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FundFreshness {
|
|
|
|
|
|
xHandle: string | null;
|
|
|
|
|
|
xPull: {
|
|
|
|
|
|
status: "pending" | "in_flight" | "done" | "backoff" | "failed" | "none";
|
|
|
|
|
|
lastAttempt: string | null;
|
|
|
|
|
|
error: string | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
lastPostAt: string | null;
|
|
|
|
|
|
lastCaptureAt: string | null;
|
|
|
|
|
|
last13fAt: string | null;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
// ─── Dealer Flow types ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapCell {
|
|
|
|
|
|
strike: number;
|
|
|
|
|
|
expiry: string;
|
|
|
|
|
|
gex: number;
|
|
|
|
|
|
vex: number;
|
|
|
|
|
|
oi: number;
|
|
|
|
|
|
volume: number;
|
|
|
|
|
|
polarity: "pos" | "neg" | "near_zero";
|
|
|
|
|
|
isKing: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapLevels {
|
|
|
|
|
|
king: { strike: number; expiry: string; gex: number } | null;
|
|
|
|
|
|
gatekeepers: Array<{ strike: number; absGex: number }>;
|
|
|
|
|
|
flip: number | null;
|
|
|
|
|
|
callWall: number | null;
|
|
|
|
|
|
putWall: number | null;
|
|
|
|
|
|
airPockets: Array<{ from: number; to: number }>;
|
|
|
|
|
|
clusters: Array<{ strikes: number[]; absGexSum: number }>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapEducator {
|
|
|
|
|
|
headline: string;
|
|
|
|
|
|
regime: string;
|
|
|
|
|
|
levels: string[];
|
|
|
|
|
|
scenario: string;
|
|
|
|
|
|
patterns: string[];
|
|
|
|
|
|
questions: string[];
|
|
|
|
|
|
caveats: string[];
|
|
|
|
|
|
footer: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapPayload {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
spot: number;
|
|
|
|
|
|
asOf: string;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
quoteAsOf?: string | null;
|
|
|
|
|
|
chainAsOf?: string | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
delayNote: string;
|
|
|
|
|
|
delaySeconds: number | null;
|
|
|
|
|
|
oiFreshness: string;
|
|
|
|
|
|
providerId: string;
|
|
|
|
|
|
convention: string;
|
|
|
|
|
|
unit: string;
|
|
|
|
|
|
computation: string;
|
|
|
|
|
|
expiries: string[];
|
|
|
|
|
|
cells: DealerMapCell[];
|
|
|
|
|
|
byStrike: Array<{ strike: number; gex: number; vex: number; absGex: number }>;
|
|
|
|
|
|
netVexByStrike: Array<{ strike: number; vex: number }>;
|
|
|
|
|
|
levels: DealerMapLevels;
|
|
|
|
|
|
regimeAtSpot: "positive" | "negative" | "mixed";
|
|
|
|
|
|
scenario: string;
|
|
|
|
|
|
patterns: Array<{ id: string; confidence?: number; [k: string]: unknown }>;
|
|
|
|
|
|
velocity?: {
|
|
|
|
|
|
asOfPrev: string;
|
|
|
|
|
|
nodes: Array<{ strike: number; growing: boolean; deltaAbsGex: number }>;
|
|
|
|
|
|
growingNearAirPocket: boolean;
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
opexHints: { isNearOpex: boolean; note: string };
|
|
|
|
|
|
coveragePct: number;
|
|
|
|
|
|
bsmFillPct: number;
|
|
|
|
|
|
quality?: {
|
|
|
|
|
|
greekQuality: "ok" | "degraded" | "poor";
|
|
|
|
|
|
oiEligible: number;
|
|
|
|
|
|
included: number;
|
|
|
|
|
|
skippedBadIv: number;
|
|
|
|
|
|
bsmFills: number;
|
|
|
|
|
|
fallbackIvUses: number;
|
|
|
|
|
|
bsmFillPct: number;
|
|
|
|
|
|
badIvSkipPct: number;
|
|
|
|
|
|
fallbackIvPct: number;
|
|
|
|
|
|
notes: string[];
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerIntegrityReport {
|
|
|
|
|
|
status: "complete" | "degraded" | "incomplete";
|
|
|
|
|
|
checks: Array<{ id: string; level: "hard" | "soft"; ok: boolean; detail: string }>;
|
|
|
|
|
|
hardFailed: string[];
|
|
|
|
|
|
softFailed: string[];
|
|
|
|
|
|
metrics: {
|
|
|
|
|
|
expiries: number;
|
|
|
|
|
|
oiEligible: number;
|
|
|
|
|
|
included: number;
|
|
|
|
|
|
cells: number;
|
|
|
|
|
|
byStrike: number;
|
|
|
|
|
|
nearSpotOiStrikes: number;
|
|
|
|
|
|
minOiStrikesPerExpiry: number;
|
|
|
|
|
|
includePct: number;
|
|
|
|
|
|
badIvSkipPct: number;
|
|
|
|
|
|
bsmFillPct: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Han-style day script under the active GEX convention. */
|
|
|
|
|
|
export interface HanStyleLevels {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
spot: number;
|
|
|
|
|
|
asOf: string;
|
|
|
|
|
|
convention: string;
|
|
|
|
|
|
script: string;
|
|
|
|
|
|
chips: string[];
|
|
|
|
|
|
longSupport: number | null;
|
|
|
|
|
|
shortKing: number | null;
|
|
|
|
|
|
resistance: number | null;
|
|
|
|
|
|
opexPath: number[];
|
|
|
|
|
|
regimeLabel: "long_gamma" | "short_gamma" | "mixed";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapGetResult {
|
|
|
|
|
|
map: DealerMapPayload | null;
|
|
|
|
|
|
educator: DealerMapEducator | null;
|
|
|
|
|
|
/** Support / resist / short king / OPEX path (updates with Classic vs Dealer toggle). */
|
|
|
|
|
|
hanScript?: HanStyleLevels | null;
|
|
|
|
|
|
status: "ok" | "stale" | "pending" | "empty" | "incomplete";
|
|
|
|
|
|
isStale: boolean;
|
|
|
|
|
|
scheduled: boolean;
|
|
|
|
|
|
providerId: string;
|
|
|
|
|
|
message?: string;
|
|
|
|
|
|
integrity?: DealerIntegrityReport;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapLevelsResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
scheduled: boolean;
|
|
|
|
|
|
levels: DealerMapLevels | null;
|
|
|
|
|
|
spot: number | null;
|
|
|
|
|
|
delayNote: string | null;
|
|
|
|
|
|
providerId: string;
|
|
|
|
|
|
regimeAtSpot?: string;
|
|
|
|
|
|
scenario?: string;
|
|
|
|
|
|
patterns?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapScenarioResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
scenario: string;
|
|
|
|
|
|
regimeAtSpot: string;
|
|
|
|
|
|
patterns: Array<{ id: string; confidence?: number }>;
|
|
|
|
|
|
educator: DealerMapEducator | null;
|
|
|
|
|
|
delayNote: string | null;
|
|
|
|
|
|
providerId: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapVelocityResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
velocity: DealerMapPayload["velocity"];
|
|
|
|
|
|
message: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerMapExplainResult {
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
source: "llm" | "layer0_fallback" | "rate_limited" | "no_endpoint" | "error";
|
|
|
|
|
|
headline: string;
|
|
|
|
|
|
sections: {
|
|
|
|
|
|
whatMapShows: string;
|
|
|
|
|
|
keyLevels: string[];
|
|
|
|
|
|
scenarioInPlainEnglish: string;
|
|
|
|
|
|
questionsToAsk: string[];
|
|
|
|
|
|
caveats: string[];
|
|
|
|
|
|
dataFreshness: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
model?: string;
|
|
|
|
|
|
cached?: boolean;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Study Desk practice setups. */
|
|
|
|
|
|
export interface DealerStudyProposal {
|
|
|
|
|
|
hypothesisId: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
bias: "educational_long" | "educational_short" | "educational_range";
|
|
|
|
|
|
mapReading: string;
|
|
|
|
|
|
studyQuestion: string;
|
|
|
|
|
|
entry: number;
|
|
|
|
|
|
invalidation: number;
|
|
|
|
|
|
target: number;
|
|
|
|
|
|
horizonDays: number;
|
|
|
|
|
|
confidence: number;
|
|
|
|
|
|
disclaimer: string;
|
|
|
|
|
|
histWinRate?: number | null;
|
|
|
|
|
|
histN?: number;
|
|
|
|
|
|
histAvgScore?: number | null;
|
|
|
|
|
|
mentorWinRate?: number | null;
|
|
|
|
|
|
mentorN?: number;
|
|
|
|
|
|
mentorClaimType?: string | null;
|
|
|
|
|
|
rankScore?: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerStudyRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
hypothesis_id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
bias: string;
|
|
|
|
|
|
map_reading: string;
|
|
|
|
|
|
study_question: string;
|
|
|
|
|
|
entry: number;
|
|
|
|
|
|
invalidation: number;
|
|
|
|
|
|
target: number;
|
|
|
|
|
|
horizon_days: number;
|
|
|
|
|
|
confidence: number | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
grade: string | null;
|
|
|
|
|
|
score: number | null;
|
|
|
|
|
|
grade_summary: string | null;
|
|
|
|
|
|
grade_path: string | null;
|
|
|
|
|
|
graded_at: string | null;
|
|
|
|
|
|
logged_at: string;
|
|
|
|
|
|
disclaimer: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DealerStudyScorecardRow {
|
|
|
|
|
|
hypothesisId: string;
|
|
|
|
|
|
n: number;
|
|
|
|
|
|
wins: number;
|
|
|
|
|
|
losses: number;
|
|
|
|
|
|
expired: number;
|
|
|
|
|
|
incomplete: number;
|
|
|
|
|
|
winRate: number | null;
|
|
|
|
|
|
avgScore: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MentorCallRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
handle: string;
|
|
|
|
|
|
post_id: string;
|
|
|
|
|
|
posted_at: string | null;
|
|
|
|
|
|
symbol: string | null;
|
|
|
|
|
|
bias: string;
|
|
|
|
|
|
claim_type: string;
|
|
|
|
|
|
entry: number;
|
|
|
|
|
|
invalidation: number;
|
|
|
|
|
|
target: number;
|
|
|
|
|
|
horizon_days: number;
|
|
|
|
|
|
excerpt: string | null;
|
|
|
|
|
|
extract_confidence: number | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
grade: string | null;
|
|
|
|
|
|
score: number | null;
|
|
|
|
|
|
grade_summary: string | null;
|
|
|
|
|
|
logged_at: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface UserLlmStatus {
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
baseUrl: string | null;
|
|
|
|
|
|
model: string | null;
|
|
|
|
|
|
keyHint: string | null;
|
|
|
|
|
|
updatedAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
|
export interface XCredentialsStatus {
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
healthy: 'healthy' | 'degraded';
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
updatedAt: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SectorRotationItem {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
/** Legacy: inflow ≈ leading, outflow ≈ lagging (RS vs SPY, not abs flow). */
|
|
|
|
|
|
group: 'outflow' | 'inflow';
|
|
|
|
|
|
subGroup: string;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
kind?: 'sector' | 'style' | 'region' | 'thematic' | 'custom' | 'auto';
|
|
|
|
|
|
/** True when this ETF is a sector/theme proxy for at least one open portfolio holding. */
|
|
|
|
|
|
portfolioLinked?: boolean;
|
|
|
|
|
|
/** Holding symbols that map into this ETF (Auto filter provenance). */
|
|
|
|
|
|
portfolioHoldings?: string[];
|
2026-07-23 18:02:24 -04:00
|
|
|
|
leadership?: 'leading' | 'lagging' | 'inline' | 'unknown';
|
|
|
|
|
|
earlyWatch?: boolean;
|
|
|
|
|
|
rank1M?: number | null;
|
|
|
|
|
|
rank1W?: number | null;
|
|
|
|
|
|
relVol?: number | null;
|
|
|
|
|
|
oneWeek: number | null;
|
|
|
|
|
|
oneMonth: number | null;
|
|
|
|
|
|
threeMonth: number | null;
|
|
|
|
|
|
sixMonth: number | null;
|
|
|
|
|
|
oneYear: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
oneDay?: number | null;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
rsOneWeek?: number | null;
|
|
|
|
|
|
rsOneMonth?: number | null;
|
|
|
|
|
|
rsThreeMonth?: number | null;
|
|
|
|
|
|
rsSixMonth?: number | null;
|
|
|
|
|
|
rsOneYear?: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
rsOneDay?: number | null;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AlertEventRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
severity: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
symbol?: string;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
acknowledged: boolean;
|
|
|
|
|
|
dedupKey: string;
|
|
|
|
|
|
payload: Record<string, unknown>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 20:51:47 -04:00
|
|
|
|
export interface AlertSubscriptionRow {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
owner_id: string;
|
|
|
|
|
|
watchlist_id: string | null;
|
|
|
|
|
|
symbol: string | null;
|
|
|
|
|
|
alert_type: string;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
params: string;
|
|
|
|
|
|
last_fired: string | null;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
export interface AlertTypeToggleRow {
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProducerRunStatusRow {
|
|
|
|
|
|
producer: string;
|
|
|
|
|
|
frequency: string;
|
|
|
|
|
|
lastRunAt: string | null;
|
|
|
|
|
|
lastEventsFired: number;
|
|
|
|
|
|
lastDurationMs: number;
|
|
|
|
|
|
lastError: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AlertStatusRow {
|
|
|
|
|
|
producers: ProducerRunStatusRow[];
|
|
|
|
|
|
subscriptionsByType: Array<{ alertType: string; enabledCount: number }>;
|
|
|
|
|
|
freshness: {
|
|
|
|
|
|
vixLevelAgeSec: number | null;
|
|
|
|
|
|
latestScFilingAt: string | null;
|
|
|
|
|
|
latestInsiderAt: string | null;
|
|
|
|
|
|
oldestQuoteAgeSec: number | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 20:51:47 -04:00
|
|
|
|
export interface SmtpConfigRow {
|
|
|
|
|
|
host: string;
|
|
|
|
|
|
port: number;
|
|
|
|
|
|
secure: boolean;
|
|
|
|
|
|
user: string | null;
|
|
|
|
|
|
passEnc: string | null;
|
|
|
|
|
|
fromName: string;
|
|
|
|
|
|
fromEmail: string;
|
|
|
|
|
|
enabled: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SmtpConfigUpdate {
|
|
|
|
|
|
host?: string;
|
|
|
|
|
|
port?: number;
|
|
|
|
|
|
secure?: boolean;
|
|
|
|
|
|
user?: string | null;
|
|
|
|
|
|
pass?: string | null;
|
|
|
|
|
|
fromName?: string;
|
|
|
|
|
|
fromEmail?: string;
|
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
|
export interface RotationData {
|
|
|
|
|
|
sectors: SectorRotationItem[];
|
|
|
|
|
|
signal: {
|
|
|
|
|
|
strength: 'none' | 'weak' | 'moderate' | 'strong';
|
|
|
|
|
|
spread: number;
|
|
|
|
|
|
inflowAvg: number;
|
|
|
|
|
|
outflowAvg: number;
|
|
|
|
|
|
leadingSubGroup: string;
|
|
|
|
|
|
laggingSubGroup?: string;
|
|
|
|
|
|
inflowConfirmation: string;
|
|
|
|
|
|
outflowConfirmation: string;
|
|
|
|
|
|
earlyWatchCount?: number;
|
|
|
|
|
|
daysSince: number | null;
|
|
|
|
|
|
summary?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
benchmark?: string;
|
|
|
|
|
|
coveredSymbols?: number;
|
|
|
|
|
|
totalSymbols?: number;
|
|
|
|
|
|
earlyHistory?: Array<{ date: string; symbol: string; name: string; rs1w: number | null }>;
|
|
|
|
|
|
lesson?: string;
|
|
|
|
|
|
fetchedAt: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface MarketCondition {
|
|
|
|
|
|
regime: 'trending-up' | 'trending-down' | 'range-bound';
|
|
|
|
|
|
regimePlain?: string;
|
|
|
|
|
|
confidence: number;
|
|
|
|
|
|
factors: string[];
|
|
|
|
|
|
explanation: string;
|
|
|
|
|
|
metrics: {
|
|
|
|
|
|
spy1M: number | null;
|
|
|
|
|
|
vix: number | null;
|
|
|
|
|
|
treasury10Y: number | null;
|
|
|
|
|
|
treasury2Y: number | null;
|
|
|
|
|
|
curve10y2y: number | null;
|
|
|
|
|
|
unemployment: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
consumerSentiment?: number | null;
|
|
|
|
|
|
context?: Record<string,string>;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
};
|
|
|
|
|
|
risks: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
severity: 'info' | 'warning' | 'elevated';
|
|
|
|
|
|
detail: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
lesson: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SeasonalityMonth {
|
|
|
|
|
|
month: number;
|
|
|
|
|
|
monthName: string;
|
|
|
|
|
|
avgReturnPct: number;
|
|
|
|
|
|
winRate: number;
|
|
|
|
|
|
sampleYears: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface HorizonReturns {
|
|
|
|
|
|
oneWeek: number | null;
|
|
|
|
|
|
oneMonth: number | null;
|
|
|
|
|
|
threeMonth: number | null;
|
|
|
|
|
|
sixMonth: number | null;
|
|
|
|
|
|
oneYear: number | null;
|
|
|
|
|
|
threeYear: number | null;
|
|
|
|
|
|
fiveYear: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TickerContext {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string | null;
|
|
|
|
|
|
sector: string | null;
|
|
|
|
|
|
industry: string | null;
|
|
|
|
|
|
tickerKind: string;
|
|
|
|
|
|
performance: {
|
|
|
|
|
|
symbol: HorizonReturns;
|
|
|
|
|
|
vsMarket: HorizonReturns;
|
|
|
|
|
|
vsSector: HorizonReturns | null;
|
|
|
|
|
|
vsPeers1M: number | null;
|
|
|
|
|
|
marketStance1M: "outperforming" | "underperforming" | "inline" | "unknown";
|
|
|
|
|
|
sectorStance1M: "outperforming" | "underperforming" | "inline" | "unknown";
|
|
|
|
|
|
peerStance1M: "outperforming" | "underperforming" | "inline" | "unknown";
|
|
|
|
|
|
};
|
|
|
|
|
|
market: {
|
|
|
|
|
|
regime: string | null;
|
|
|
|
|
|
confidence: number | null;
|
|
|
|
|
|
spyReturns: HorizonReturns;
|
|
|
|
|
|
};
|
|
|
|
|
|
sectorContext: {
|
|
|
|
|
|
label: string | null;
|
|
|
|
|
|
etf: string | null;
|
|
|
|
|
|
etfReturns: HorizonReturns | null;
|
|
|
|
|
|
leadership: string | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
themeContext: {
|
|
|
|
|
|
label: string | null;
|
|
|
|
|
|
etf: string | null;
|
|
|
|
|
|
etfReturns: HorizonReturns | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
classification?: {
|
|
|
|
|
|
note: string | null;
|
|
|
|
|
|
vendorSector: string | null;
|
|
|
|
|
|
vendorIndustry: string | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
peers: Array<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string | null;
|
|
|
|
|
|
oneMonth: number | null;
|
|
|
|
|
|
rsVsMarket1M: number | null;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
summary: {
|
|
|
|
|
|
headline: string;
|
|
|
|
|
|
market: string;
|
|
|
|
|
|
sector: string;
|
|
|
|
|
|
peers: string;
|
|
|
|
|
|
synthesis: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SeasonalityData {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
months: SeasonalityMonth[];
|
|
|
|
|
|
currentMonth: number;
|
|
|
|
|
|
currentMonthAvgPct: number | null;
|
|
|
|
|
|
currentMonthWinRate: number | null;
|
|
|
|
|
|
currentMonthSampleYears: number;
|
|
|
|
|
|
halfYear: {
|
|
|
|
|
|
winterAvgPct: number | null;
|
|
|
|
|
|
summerAvgPct: number | null;
|
|
|
|
|
|
whichHalf: 'winter' | 'summer';
|
|
|
|
|
|
};
|
|
|
|
|
|
electionCycle: {
|
|
|
|
|
|
year: number;
|
|
|
|
|
|
yearInCycle: 1 | 2 | 3 | 4;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
calendar: {
|
|
|
|
|
|
dayOfMonth: number;
|
|
|
|
|
|
nearTurnOfMonth: boolean;
|
|
|
|
|
|
quarter: 1 | 2 | 3 | 4;
|
|
|
|
|
|
nearQuarterEnd: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
events: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
when: string;
|
|
|
|
|
|
impact: 'high' | 'medium';
|
|
|
|
|
|
plainWhy: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
hasEnoughHistory: boolean;
|
|
|
|
|
|
lesson: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RotationAlertCheckResult {
|
|
|
|
|
|
signal: string;
|
|
|
|
|
|
spread: number;
|
|
|
|
|
|
inflowAvg: number;
|
|
|
|
|
|
outflowAvg: number;
|
|
|
|
|
|
prevSignal: string | null;
|
|
|
|
|
|
alertsCreated: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SectorHolding {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
weight: number;
|
|
|
|
|
|
price: number | null;
|
|
|
|
|
|
change: number | null;
|
|
|
|
|
|
changePercent: number | null;
|
|
|
|
|
|
volume: number | null;
|
|
|
|
|
|
marketCap: number | null;
|
|
|
|
|
|
returns1W: number | null;
|
|
|
|
|
|
returns1M: number | null;
|
|
|
|
|
|
returns3M: number | null;
|
|
|
|
|
|
returns6M: number | null;
|
|
|
|
|
|
returns1Y: number | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SectorHoldingsResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
holdings: SectorHolding[];
|
|
|
|
|
|
/** composition source: live Yahoo, 24h cache, static fallback, or empty */
|
|
|
|
|
|
source?: "live" | "cache" | "static" | "empty";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TruckSalesObservation {
|
|
|
|
|
|
date: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface TruckSalesData {
|
|
|
|
|
|
observations: TruckSalesObservation[];
|
|
|
|
|
|
seriesTitle: string;
|
|
|
|
|
|
units: string;
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ManufacturingPmiData {
|
|
|
|
|
|
observations: TruckSalesObservation[];
|
|
|
|
|
|
seriesTitle: string;
|
|
|
|
|
|
units: string;
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
error?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface XFedFeed {
|
|
|
|
|
|
cashtagPosts: XPost[];
|
|
|
|
|
|
accountPosts: XPost[];
|
|
|
|
|
|
nextCursor: string | null;
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
health?: { healthy: 'healthy' | 'degraded'; lastError: string | null } | null;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
lastPullAt?: string | null;
|
|
|
|
|
|
queuedHandles?: number;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
}
|
|
|
|
|
|
export interface MacroCommentary {
|
|
|
|
|
|
shortTerm: string;
|
|
|
|
|
|
longTerm: string;
|
|
|
|
|
|
disclaimer: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 17:51:54 -04:00
|
|
|
|
export interface Snapshot {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
quote: Quote | null;
|
|
|
|
|
|
candles: PriceCandle[] | null;
|
|
|
|
|
|
sector: SymbolMeta | null;
|
|
|
|
|
|
stale: { quote: boolean; candles: boolean; sector: boolean };
|
2026-08-18 14:10:02 -04:00
|
|
|
|
observed?: { quote: string | null; candles: string | null; sector: string | null };
|
2026-06-29 17:51:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
export interface AuthUser {
|
|
|
|
|
|
userId: string;
|
|
|
|
|
|
email?: string;
|
|
|
|
|
|
complexity?: string;
|
|
|
|
|
|
riskTolerance?: string;
|
|
|
|
|
|
convexityPosture?: string;
|
2026-06-29 21:31:59 -04:00
|
|
|
|
onboarded?: boolean;
|
2026-07-25 11:38:23 -04:00
|
|
|
|
modules?: string[];
|
2026-08-10 13:36:26 -04:00
|
|
|
|
experienceStage?: string | null;
|
|
|
|
|
|
goal?: string | null;
|
|
|
|
|
|
horizon?: string | null;
|
|
|
|
|
|
density?: string | null;
|
|
|
|
|
|
jargonComfort?: string | null;
|
2026-06-29 17:51:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
// Default short timeout for normal cache/UI calls. LLM paths need much longer
|
|
|
|
|
|
// (Ornith reasoning models often take 30–120s).
|
2026-06-30 08:54:33 -04:00
|
|
|
|
const TRPC_TIMEOUT_MS = 8_000;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
/** OpenAI-compatible explain / test against local reasoning models. */
|
|
|
|
|
|
const TRPC_LLM_TIMEOUT_MS = 180_000;
|
2026-06-30 08:54:33 -04:00
|
|
|
|
|
2026-06-29 17:51:54 -04:00
|
|
|
|
async function parse<T>(res: Response): Promise<T> {
|
2026-06-30 08:54:33 -04:00
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
const body = await res.text();
|
2026-08-10 13:36:26 -04:00
|
|
|
|
// Prefer structured proxy / tRPC error message over raw HTML "Internal Server Error".
|
|
|
|
|
|
try {
|
|
|
|
|
|
const j = JSON.parse(body) as {
|
|
|
|
|
|
error?: { message?: string; data?: { code?: string } };
|
|
|
|
|
|
message?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
const msg =
|
|
|
|
|
|
j?.error?.message ??
|
|
|
|
|
|
j?.message ??
|
|
|
|
|
|
(body.trim() || res.statusText);
|
|
|
|
|
|
throw new Error(`tRPC ${res.status}: ${msg}`);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (e instanceof Error && e.message.startsWith("tRPC ")) throw e;
|
|
|
|
|
|
const snippet = body.replace(/\s+/g, " ").trim().slice(0, 200);
|
|
|
|
|
|
throw new Error(`tRPC ${res.status}: ${snippet || res.statusText}`);
|
|
|
|
|
|
}
|
2026-06-30 08:54:33 -04:00
|
|
|
|
}
|
2026-06-29 17:51:54 -04:00
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
if (json?.error) {
|
2026-06-30 08:54:33 -04:00
|
|
|
|
throw new Error(`tRPC error: ${json.error.message ?? json.error.data?.code ?? "unknown"}`);
|
2026-06-29 17:51:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
return json.result.data as T;
|
|
|
|
|
|
}
|
2026-06-30 08:54:33 -04:00
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
async function trpcQuery<T>(
|
|
|
|
|
|
procedure: string,
|
|
|
|
|
|
input?: unknown,
|
|
|
|
|
|
timeoutMs: number = TRPC_TIMEOUT_MS,
|
|
|
|
|
|
): Promise<T> {
|
2026-06-30 08:54:33 -04:00
|
|
|
|
const controller = new AbortController();
|
2026-08-10 13:36:26 -04:00
|
|
|
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
2026-06-30 08:54:33 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const search = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
|
|
|
|
|
|
const res = await fetch(`/api/trpc/${procedure}${search}`, {
|
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
signal: controller.signal,
|
|
|
|
|
|
});
|
|
|
|
|
|
return parse<T>(res);
|
2026-07-25 11:42:33 -04:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (e instanceof DOMException && e.name === "AbortError") {
|
2026-08-10 13:36:26 -04:00
|
|
|
|
throw new Error(`Request timed out (${Math.round(timeoutMs / 1000)}s)`);
|
2026-07-25 11:42:33 -04:00
|
|
|
|
}
|
|
|
|
|
|
throw e;
|
2026-06-30 08:54:33 -04:00
|
|
|
|
} finally {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
}
|
2026-06-29 21:31:59 -04:00
|
|
|
|
}
|
2026-06-30 08:54:33 -04:00
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
async function trpcMutate<T>(
|
|
|
|
|
|
procedure: string,
|
|
|
|
|
|
input: unknown,
|
|
|
|
|
|
timeoutMs: number = TRPC_TIMEOUT_MS,
|
|
|
|
|
|
): Promise<T> {
|
2026-06-30 08:54:33 -04:00
|
|
|
|
const controller = new AbortController();
|
2026-08-10 13:36:26 -04:00
|
|
|
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
2026-06-30 08:54:33 -04:00
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`/api/trpc/${procedure}`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { "content-type": "application/json" },
|
|
|
|
|
|
credentials: "include",
|
|
|
|
|
|
body: JSON.stringify(input),
|
|
|
|
|
|
signal: controller.signal,
|
|
|
|
|
|
});
|
|
|
|
|
|
return parse<T>(res);
|
2026-07-25 11:42:33 -04:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (e instanceof DOMException && e.name === "AbortError") {
|
2026-08-10 13:36:26 -04:00
|
|
|
|
throw new Error(`Request timed out (${Math.round(timeoutMs / 1000)}s)`);
|
2026-07-25 11:42:33 -04:00
|
|
|
|
}
|
|
|
|
|
|
throw e;
|
2026-06-30 08:54:33 -04:00
|
|
|
|
} finally {
|
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
}
|
2026-06-29 17:51:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 21:31:59 -04:00
|
|
|
|
export type Complexity = "beginner" | "intermediate" | "advanced";
|
|
|
|
|
|
export interface StarterEntry { symbol: string; tickerKind: string; reason: string; }
|
|
|
|
|
|
export interface PortfolioEntry { symbol: string; qty: number; avgCost: number; acquiredAt: string; }
|
2026-06-29 17:51:54 -04:00
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
|
export interface IndicatorPeriods {
|
|
|
|
|
|
ema: number[];
|
|
|
|
|
|
rsi: number;
|
|
|
|
|
|
relvol: number;
|
|
|
|
|
|
}
|
2026-06-30 08:46:47 -04:00
|
|
|
|
export interface IndicatorOptions {
|
2026-07-23 18:02:24 -04:00
|
|
|
|
periods?: IndicatorPeriods;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
timeframe?: '1m' | '5m' | '1d' | '1wk' | '1mo';
|
2026-06-30 08:46:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
export interface IndicatorResult {
|
2026-07-23 18:02:24 -04:00
|
|
|
|
ema?: Record<number, (number | null)[]>;
|
|
|
|
|
|
rsi?: (number | null)[];
|
|
|
|
|
|
relvol?: (number | null)[];
|
|
|
|
|
|
macd?: { macdLine: (number | null)[]; signalLine: (number | null)[]; histogram: (number | null)[] };
|
2026-06-30 08:46:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-05 08:50:12 -04:00
|
|
|
|
// --- EDGAR / SEC Filings types (Slice 6 / M6) -------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
export interface EdgarFiling {
|
|
|
|
|
|
form: string;
|
|
|
|
|
|
dateReporter: string;
|
|
|
|
|
|
accessionNumber: string;
|
|
|
|
|
|
accessionNormalization: string;
|
|
|
|
|
|
reportDate: string;
|
|
|
|
|
|
reportFile: string;
|
|
|
|
|
|
primaryDocument: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FilerMeta {
|
|
|
|
|
|
cik: string;
|
|
|
|
|
|
name: string | null;
|
|
|
|
|
|
sic: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Form13fHolding {
|
|
|
|
|
|
cusip: string;
|
|
|
|
|
|
issuerName: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
sshPrnamt: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Form13fResult {
|
|
|
|
|
|
holdings: Form13fHolding[];
|
2026-07-12 20:19:12 -04:00
|
|
|
|
total: number;
|
2026-07-05 08:50:12 -04:00
|
|
|
|
accession: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Form4Transaction {
|
|
|
|
|
|
reporter: string;
|
|
|
|
|
|
relationship: string;
|
|
|
|
|
|
securityTitle: string;
|
|
|
|
|
|
transactionDate: string;
|
|
|
|
|
|
transactionCode: string;
|
|
|
|
|
|
shares: number;
|
|
|
|
|
|
price: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Form4Result {
|
|
|
|
|
|
transactions: Form4Transaction[];
|
|
|
|
|
|
accession: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface FilingsSearchResult {
|
|
|
|
|
|
filings: Array<Record<string, unknown>>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-29 17:51:54 -04:00
|
|
|
|
export const api = {
|
2026-06-30 08:46:47 -04:00
|
|
|
|
market: {
|
|
|
|
|
|
snapshot: (symbol: string) => trpcQuery<Snapshot>("market.snapshot", { symbol }),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
/** Batched snapshot for multiple symbols — reduces N+1 API calls */
|
|
|
|
|
|
snapshots: (symbols: string[]) => trpcQuery<Array<{ symbol: string; quote: Snapshot['quote'] | null; candles: PriceCandle[] | null; sector: SymbolMeta | null; stale: { quote: boolean; candles: boolean; sector: boolean } }>>("market.snapshots", { symbols }),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
tickerContext: (symbol: string) => trpcQuery<TickerContext>("market.tickerContext", { symbol }),
|
2026-08-18 14:10:02 -04:00
|
|
|
|
candles: async (symbol: string, timeframe = "1d") => trpcQuery<{ symbol: string; timeframe: string; candles: PriceCandle[]; isStale: boolean; fetchedAt: string | null }>("market.candles", { symbol, timeframe }).then((r) => r?.candles ?? []),
|
|
|
|
|
|
candlesWithMeta: (symbol: string, timeframe = "1d") =>
|
|
|
|
|
|
trpcQuery<{ symbol: string; timeframe: string; candles: PriceCandle[]; isStale: boolean; fetchedAt: string | null }>("market.candles", { symbol, timeframe }),
|
2026-06-30 08:46:47 -04:00
|
|
|
|
indicators: (symbol: string, opts: IndicatorOptions) =>
|
|
|
|
|
|
trpcQuery<IndicatorResult>("market.indicators", { symbol, ...opts }),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
truckSales: () => trpcQuery<TruckSalesData>("market.truckSales"),
|
|
|
|
|
|
manufacturingPmi: () => trpcQuery<ManufacturingPmiData>("market.manufacturingPmi"),
|
|
|
|
|
|
condition: () => trpcQuery<MarketCondition>("market.condition"),
|
|
|
|
|
|
rotation: () => trpcQuery<RotationData>("market.rotation"),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
addCustomEtf: (symbol: string, opts?: { name?: string; group?: string }) =>
|
|
|
|
|
|
trpcMutate<{ added: boolean }>("market.addCustomEtf", { symbol, ...opts }),
|
|
|
|
|
|
removeCustomEtf: (symbol: string) =>
|
|
|
|
|
|
trpcMutate<{ removed: boolean }>("market.removeCustomEtf", { symbol }),
|
|
|
|
|
|
listCustomEtfs: () =>
|
|
|
|
|
|
trpcQuery<Array<{ symbol: string; name: string; grp: string; created_at: string }>>("market.listCustomEtfs"),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
rotationCheckForAlert: () => trpcMutate<RotationAlertCheckResult>("market.rotationCheckForAlert", {}),
|
|
|
|
|
|
seasonality: (symbol?: string) =>
|
|
|
|
|
|
trpcQuery<SeasonalityData>("market.seasonality", symbol ? { symbol } : {}),
|
|
|
|
|
|
sectorHoldings: (etfSymbol: string) => trpcQuery<SectorHoldingsResult>("market.sectorHoldings", { etfSymbol }),
|
|
|
|
|
|
commentary: () => trpcQuery<MacroCommentary>("macro.commentary"),
|
2026-06-30 08:46:47 -04:00
|
|
|
|
},
|
2026-07-05 08:50:12 -04:00
|
|
|
|
edgar: {
|
|
|
|
|
|
filings_index: (input: { cik: string; formTypes?: string[]; dateRange?: { from?: string; to?: string } }) =>
|
|
|
|
|
|
trpcQuery<{ filings: EdgarFiling[]; cik: string }>("edgar.filings_index", input),
|
|
|
|
|
|
company_facts: (cik: string) =>
|
|
|
|
|
|
trpcQuery<{ facts: Record<string, unknown>; cik: string }>("edgar.company_facts", { cik }),
|
|
|
|
|
|
filer_cik_meta: (cik: string) =>
|
|
|
|
|
|
trpcQuery<{ meta: FilerMeta; cik: string }>("edgar.filer_cik_meta", { cik }),
|
|
|
|
|
|
full_text_search: (q: string) =>
|
|
|
|
|
|
trpcQuery<FilingsSearchResult>("edgar.full_text_search", { q }),
|
2026-07-12 20:19:12 -04:00
|
|
|
|
form13f_holdings: (cik: string, accession: string, opts?: { limit?: number; offset?: number }) =>
|
|
|
|
|
|
trpcQuery<Form13fResult>("edgar.form13f_holdings", {
|
|
|
|
|
|
cik,
|
|
|
|
|
|
accession,
|
|
|
|
|
|
...(opts?.limit != null ? { limit: opts.limit } : {}),
|
|
|
|
|
|
...(opts?.offset != null ? { offset: opts.offset } : {}),
|
|
|
|
|
|
}),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
form4_tx: (cik: string, accession: string) =>
|
|
|
|
|
|
trpcQuery<{ transactions: Form4Result }>("edgar.form4_tx", { cik, accession }),
|
|
|
|
|
|
},
|
2026-08-10 13:36:26 -04:00
|
|
|
|
funds: {
|
|
|
|
|
|
list: () => trpcQuery<Array<{
|
|
|
|
|
|
id: string; ci_key: string; fund_name: string; manager_name: string;
|
|
|
|
|
|
x_handle: string | null; paywall_status: string; enabled: boolean;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
freshness: FundFreshness | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
}>>("funds.list", {}),
|
|
|
|
|
|
get: (input: { id: string }) => trpcQuery<{
|
|
|
|
|
|
id: string; ci_key: string; fund_name: string; manager_name: string;
|
|
|
|
|
|
x_handle: string | null; paywall_status: string; enabled: boolean;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
freshness: FundFreshness | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
} | null>("funds.get", input),
|
|
|
|
|
|
liveBook: (input: { fundId: string }) =>
|
|
|
|
|
|
trpcQuery<Array<{
|
|
|
|
|
|
symbol: string; shares: number | null; value_usd: number | null;
|
|
|
|
|
|
cost_basis: number | null; as_of: string; source: string; evidence_url: string | null;
|
|
|
|
|
|
notes: string | null; post_text: string | null;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
status: "held" | "exited"; last_held_shares: number | null;
|
|
|
|
|
|
prior_shares: number | null; share_delta: number | null;
|
|
|
|
|
|
change: "add" | "cut" | "flat" | "new" | null;
|
|
|
|
|
|
x_only: boolean;
|
|
|
|
|
|
evidence_status?: "ok" | "deleted" | "unknown";
|
2026-08-10 13:36:26 -04:00
|
|
|
|
}>>("funds.liveBook", input),
|
|
|
|
|
|
adminCreate: (input: {
|
|
|
|
|
|
ci_key: string; fund_name: string; manager_name: string; x_handle?: string;
|
|
|
|
|
|
paywall_status?: "unknown" | "open" | "paywalled"; enabled?: boolean;
|
|
|
|
|
|
}) => trpcMutate<{ id: string; ci_key: string; fund_name: string; manager_name: string; x_handle: string | null; paywall_status: string; enabled: boolean }>("funds.adminCreate", input),
|
|
|
|
|
|
adminDelete: (input: { id: string }) => trpcMutate<{ deleted: boolean }>("funds.adminDelete", input),
|
|
|
|
|
|
adminSetEnabled: (input: { id: string; enabled: boolean }) => trpcMutate<{ updated: boolean }>("funds.adminSetEnabled", input),
|
|
|
|
|
|
adminSync13f: (input: { fundId: string }) => trpcMutate<{
|
|
|
|
|
|
fetched_filings: number; holdings_seen: number; symbols_upserted: number;
|
|
|
|
|
|
records_inserted: number; unresolved: Array<{ cusip: string; issuerName: string }>;
|
|
|
|
|
|
}>("funds.adminSync13f", input),
|
|
|
|
|
|
adminIngestCaptures: (input: { fundId: string }) => trpcMutate<{
|
|
|
|
|
|
fundId: string; postsScanned: number; captures: number; inserted: number;
|
|
|
|
|
|
refreshed: number; claims: number; skipped: number; disabled: boolean;
|
|
|
|
|
|
}>("funds.adminIngestCaptures", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
symbols: {
|
|
|
|
|
|
search: (input: { q: string; limit?: number }) => trpcQuery<{
|
|
|
|
|
|
q: string;
|
|
|
|
|
|
results: Array<{
|
|
|
|
|
|
symbol: string; name: string | null; sector: string | null; industry: string | null;
|
|
|
|
|
|
exchange: string | null; ticker_kind: string; cik: string | null;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
}>("symbols.search", input),
|
|
|
|
|
|
holders: (input: { symbol: string }) => trpcQuery<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
tracked: Array<{
|
|
|
|
|
|
fundId: string; fundName: string; shares: number | null; valueUsd: number | null;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
asOf: string; source: string; notes: string | null; weightPct: number | null;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
}>;
|
|
|
|
|
|
institutionCount: number;
|
|
|
|
|
|
}>("symbols.holders", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
mirror: {
|
|
|
|
|
|
diff: (input: {
|
|
|
|
|
|
fundId: string; base?: number; locked?: boolean;
|
|
|
|
|
|
minPositionUsd?: number; minWeightPct?: number; tickSize?: number;
|
|
|
|
|
|
}) => trpcQuery<{
|
|
|
|
|
|
base: number; locked: boolean; book_value: number; placed_value: number;
|
|
|
|
|
|
rows: Array<{
|
|
|
|
|
|
symbol: string; weight: number; fund_avg_cost: number | null; target_value: number;
|
|
|
|
|
|
target_qty: number | null; actual_qty: number; actual_value: number;
|
|
|
|
|
|
delta_qty: number; delta_value: number; message: string; excluded: boolean;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
excluded: Array<{ symbol: string; weight: number; fund_avg_cost: number | null; target_value: number; target_qty: number | null; actual_qty: number; actual_value: number; delta_qty: number; delta_value: number; message: string; excluded: boolean }>;
|
|
|
|
|
|
excluded_tail: { count: number; value_usd: number } | null;
|
|
|
|
|
|
}>("mirror.diff", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
sectorCrosslink: {
|
|
|
|
|
|
confirm: (input: { strategyId: string; sector: string }) => trpcQuery<{
|
|
|
|
|
|
strategyId: string;
|
|
|
|
|
|
sector: string;
|
|
|
|
|
|
matchedSymbols: Array<{
|
|
|
|
|
|
symbol: string; convictionStrength: number; conditionsFired: string[]; oneTapWorkbenchLink: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
rotationSignal: { fromSector: string; toSector: string; confidence: number; date: string } | null;
|
|
|
|
|
|
sectorConfirmationReason: string;
|
|
|
|
|
|
educationalDisclaimer: string;
|
|
|
|
|
|
}>("sectorCrosslink.confirm", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
reddit: {
|
|
|
|
|
|
subreddit: (input: { subreddit: string; limit?: number }) => trpcQuery<{
|
|
|
|
|
|
posts: Array<{
|
|
|
|
|
|
post_id: string; author_handle: string; subreddit: string; body_text: string | null;
|
|
|
|
|
|
posted_at: string; engagement: number; sentiment_score?: number | null; attribution?: string | null;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
isStale: boolean;
|
|
|
|
|
|
}>("reddit.subreddit", input),
|
|
|
|
|
|
search: (input: { q: string; limit?: number }) => trpcQuery<{
|
|
|
|
|
|
posts: Array<{
|
|
|
|
|
|
post_id: string; author_handle: string; subreddit: string; body_text: string | null;
|
|
|
|
|
|
posted_at: string; engagement: number; sentiment_score?: number | null; attribution?: string | null;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
isStale: boolean;
|
|
|
|
|
|
}>("reddit.search", input),
|
|
|
|
|
|
},
|
2026-06-29 17:51:54 -04:00
|
|
|
|
auth: {
|
|
|
|
|
|
signup: (email: string, password: string) => trpcMutate<{ userId: string }>("auth.signup", { email, password }),
|
2026-06-29 21:21:09 -04:00
|
|
|
|
login: (email: string, password: string, totp?: string) => trpcMutate<{ userId: string }>("auth.login", { email, password, ...(totp ? { totp } : {}) }),
|
2026-06-29 17:51:54 -04:00
|
|
|
|
logout: () => trpcMutate<{ ok: boolean }>("auth.logout", {}),
|
|
|
|
|
|
me: () => trpcQuery<AuthUser | null>("auth.me"),
|
2026-06-29 21:21:09 -04:00
|
|
|
|
enable2fa: () => trpcMutate<{ totpSecret: string; qrUrl: string; backupCodes: string[] }>("auth.enable2fa", {}),
|
|
|
|
|
|
confirm2fa: (totp: string) => trpcMutate<{ ok: boolean }>("auth.confirm2fa", { totp }),
|
|
|
|
|
|
oauthStart: (provider: "github" | "google", redirectUri: string) => trpcMutate<{ redirectUrl: string; state: string }>("auth.oauthStart", { provider, redirectUri }),
|
2026-06-29 17:51:54 -04:00
|
|
|
|
},
|
2026-06-29 21:31:59 -04:00
|
|
|
|
onboarding: {
|
2026-08-10 13:36:26 -04:00
|
|
|
|
starter: (input?: { density?: "focused" | "standard" | "full"; experienceStage?: "never_invested" | "some_stocks" | "active_self_directed" }) =>
|
|
|
|
|
|
trpcQuery<{ watchlist: StarterEntry[]; density: string; disclaimer: string }>("onboarding.starter", input ?? {}),
|
|
|
|
|
|
complete: (input: {
|
|
|
|
|
|
experienceStage?: "never_invested" | "some_stocks" | "active_self_directed";
|
|
|
|
|
|
goal?: "grow" | "income" | "protect";
|
|
|
|
|
|
horizon?: "short" | "medium" | "long";
|
|
|
|
|
|
density?: "focused" | "standard" | "full";
|
|
|
|
|
|
jargonComfort?: "plain" | "mixed" | "technical";
|
|
|
|
|
|
complexity?: Complexity;
|
|
|
|
|
|
drawdownTolerancePct?: number;
|
|
|
|
|
|
firstWatchlistSymbols?: string[];
|
|
|
|
|
|
portfolio?: PortfolioEntry[];
|
|
|
|
|
|
}) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; watchlistId: string; density?: string; complexity?: string; experienceStage?: string }>(
|
|
|
|
|
|
"onboarding.complete",
|
|
|
|
|
|
input,
|
|
|
|
|
|
),
|
|
|
|
|
|
updateProfile: (input: {
|
|
|
|
|
|
experienceStage?: "never_invested" | "some_stocks" | "active_self_directed";
|
|
|
|
|
|
goal?: "grow" | "income" | "protect";
|
|
|
|
|
|
horizon?: "short" | "medium" | "long";
|
|
|
|
|
|
density?: "focused" | "standard" | "full";
|
|
|
|
|
|
jargonComfort?: "plain" | "mixed" | "technical";
|
|
|
|
|
|
}) =>
|
|
|
|
|
|
trpcMutate<{
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
experienceStage: string;
|
|
|
|
|
|
goal: string;
|
|
|
|
|
|
horizon: string;
|
|
|
|
|
|
density: string;
|
|
|
|
|
|
jargonComfort: string;
|
|
|
|
|
|
complexity: string;
|
|
|
|
|
|
}>("onboarding.updateProfile", input),
|
2026-06-29 21:31:59 -04:00
|
|
|
|
},
|
2026-07-05 08:50:12 -04:00
|
|
|
|
watchlists: {
|
|
|
|
|
|
list: () => trpcQuery<WatchlistEntry[]>("watchlists.list"),
|
2026-07-23 20:51:47 -04:00
|
|
|
|
listByWatchlist: (name: string) => trpcQuery<WatchlistEntry[]>("watchlists.listByWatchlist", { name }),
|
2026-07-25 11:38:23 -04:00
|
|
|
|
listWatchlists: () => trpcQuery<Array<{ id: string; name: string; symbol_count: number; sort_order: number; created_at: string; kind?: string; class_key?: string | null; class_label?: string | null }>>("watchlists.listWatchlists"),
|
|
|
|
|
|
create: (name: string) => trpcMutate<{ id: string; name: string; symbol_count: number; sort_order: number; created_at: string; kind?: string }>("watchlists.create", { name }),
|
2026-07-23 20:51:47 -04:00
|
|
|
|
delete: (name: string) => trpcMutate<{ deleted: boolean }>("watchlists.delete", { name }),
|
|
|
|
|
|
rename: (oldName: string, newName: string) => trpcMutate<{ renamed: boolean }>("watchlists.rename", { oldName, newName }),
|
|
|
|
|
|
reorder: (orders: { id: string; sort_order: number }[]) => trpcMutate<{ ok: boolean }>("watchlists.reorder", { orders }),
|
2026-07-25 11:38:23 -04:00
|
|
|
|
moveSymbol: (symbol: string, fromWatchlist?: string, toWatchlist?: string) =>
|
|
|
|
|
|
trpcMutate<{ moved: boolean }>("watchlists.moveSymbol", { symbol, ...(fromWatchlist ? { fromWatchlist } : {}), ...(toWatchlist ? { toWatchlist } : {}) }),
|
2026-07-23 20:51:47 -04:00
|
|
|
|
addSymbol: (symbol: string, watchlistName?: string, notes?: string) =>
|
|
|
|
|
|
trpcMutate<{ added: boolean }>("watchlists.addSymbol", { symbol, ...(watchlistName ? { watchlistName } : {}), ...(notes ? { notes } : {}) }),
|
|
|
|
|
|
removeSymbol: (symbol: string, watchlistName?: string) =>
|
|
|
|
|
|
trpcMutate<{ removed: boolean }>("watchlists.removeSymbol", { symbol, ...(watchlistName ? { watchlistName } : {}) }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
|
|
|
|
|
options: {
|
|
|
|
|
|
chain: (symbol: string, opts?: { expiry?: string }) =>
|
2026-07-23 18:02:24 -04:00
|
|
|
|
trpcQuery<{ symbol: string; expiration: string | null; rows: OptionChainRow[] }>("options.chain", { symbol, ...opts }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
greeks: (symbol: string, opts?: { expiry?: string; strike?: number }) =>
|
2026-07-23 18:02:24 -04:00
|
|
|
|
trpcQuery<{ symbol: string; greeks: OptionGreeks | null; strike?: number; right?: "call" | "put"; lastPrice?: number; impliedVolatility?: number }>("options.greeks", { symbol, ...opts }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
2026-08-10 13:36:26 -04:00
|
|
|
|
dealerMap: {
|
|
|
|
|
|
get: (
|
|
|
|
|
|
symbol: string,
|
|
|
|
|
|
opts?: {
|
|
|
|
|
|
forceSchedule?: boolean;
|
|
|
|
|
|
maxExpiries?: number;
|
|
|
|
|
|
convention?: "classic_call_pos_put_neg" | "dealer_inventory";
|
|
|
|
|
|
},
|
|
|
|
|
|
) => trpcQuery<DealerMapGetResult>("dealerMap.get", { symbol, ...opts }),
|
|
|
|
|
|
levels: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<DealerMapLevelsResult>("dealerMap.levels", { symbol }),
|
|
|
|
|
|
scenario: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<DealerMapScenarioResult>("dealerMap.scenario", { symbol }),
|
|
|
|
|
|
velocity: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<DealerMapVelocityResult>("dealerMap.velocity", { symbol }),
|
|
|
|
|
|
explain: (symbol: string) =>
|
|
|
|
|
|
trpcMutate<DealerMapExplainResult>(
|
|
|
|
|
|
"dealerMap.explain",
|
|
|
|
|
|
{ symbol },
|
|
|
|
|
|
TRPC_LLM_TIMEOUT_MS,
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
dealerStudy: {
|
|
|
|
|
|
propose: (symbol: string, opts?: { useMentorHistory?: boolean }) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
proposals: DealerStudyProposal[];
|
|
|
|
|
|
message: string | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
useMentorHistory?: boolean;
|
|
|
|
|
|
}>("dealerStudy.propose", {
|
|
|
|
|
|
symbol,
|
|
|
|
|
|
useMentorHistory: opts?.useMentorHistory === true,
|
|
|
|
|
|
}),
|
|
|
|
|
|
log: (input: DealerStudyProposal & { symbol: string }) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; id: string; loggedAt: string }>("dealerStudy.log", {
|
|
|
|
|
|
symbol: input.symbol,
|
|
|
|
|
|
hypothesisId: input.hypothesisId,
|
|
|
|
|
|
title: input.title,
|
|
|
|
|
|
bias: input.bias,
|
|
|
|
|
|
mapReading: input.mapReading,
|
|
|
|
|
|
studyQuestion: input.studyQuestion,
|
|
|
|
|
|
entry: input.entry,
|
|
|
|
|
|
invalidation: input.invalidation,
|
|
|
|
|
|
target: input.target,
|
|
|
|
|
|
horizonDays: input.horizonDays,
|
|
|
|
|
|
confidence: input.confidence,
|
|
|
|
|
|
disclaimer: input.disclaimer,
|
|
|
|
|
|
}),
|
|
|
|
|
|
list: (opts?: { symbol?: string; limit?: number }) =>
|
|
|
|
|
|
trpcQuery<DealerStudyRow[]>("dealerStudy.list", opts ?? {}),
|
|
|
|
|
|
grade: (id: string) =>
|
|
|
|
|
|
trpcMutate<{
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
grade: string;
|
|
|
|
|
|
score: number;
|
|
|
|
|
|
summary: string;
|
|
|
|
|
|
pathNotes: string[];
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
}>("dealerStudy.grade", { id }),
|
|
|
|
|
|
gradeDue: () =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; considered: number; graded: number; incomplete: number }>(
|
|
|
|
|
|
"dealerStudy.gradeDue",
|
|
|
|
|
|
{},
|
|
|
|
|
|
),
|
|
|
|
|
|
scorecard: () => trpcQuery<DealerStudyScorecardRow[]>("dealerStudy.scorecard"),
|
|
|
|
|
|
promoteToJournal: (studyId: string) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; tradeId: string; message: string }>("dealerStudy.promoteToJournal", {
|
|
|
|
|
|
studyId,
|
|
|
|
|
|
}),
|
|
|
|
|
|
exportCsv: (opts?: { symbol?: string; limit?: number }) =>
|
|
|
|
|
|
trpcQuery<{ filename: string; csv: string; rowCount: number }>(
|
|
|
|
|
|
"dealerStudy.exportCsv",
|
|
|
|
|
|
opts ?? {},
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
mentorLedger: {
|
|
|
|
|
|
importFromHarvest: (opts?: { handle?: string; maxFiles?: number }) =>
|
|
|
|
|
|
trpcMutate<{
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
imported: number;
|
|
|
|
|
|
skipped: number;
|
|
|
|
|
|
filesSeen?: number;
|
|
|
|
|
|
message: string | null;
|
|
|
|
|
|
}>("mentorLedger.importFromHarvest", opts ?? {}),
|
|
|
|
|
|
list: (opts?: { handle?: string; status?: string; limit?: number }) =>
|
|
|
|
|
|
trpcQuery<MentorCallRow[]>("mentorLedger.list", opts ?? {}),
|
|
|
|
|
|
confirm: (input: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
entry?: number;
|
|
|
|
|
|
invalidation?: number;
|
|
|
|
|
|
target?: number;
|
|
|
|
|
|
bias?: "educational_long" | "educational_short" | "educational_range";
|
|
|
|
|
|
horizonDays?: number;
|
|
|
|
|
|
symbol?: string;
|
|
|
|
|
|
}) => trpcMutate<{ ok: boolean }>("mentorLedger.confirm", input),
|
|
|
|
|
|
discard: (id: string) => trpcMutate<{ ok: boolean }>("mentorLedger.discard", { id }),
|
|
|
|
|
|
grade: (id: string) =>
|
|
|
|
|
|
trpcMutate<{
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
grade: string;
|
|
|
|
|
|
score: number;
|
|
|
|
|
|
summary: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
}>("mentorLedger.grade", { id }),
|
|
|
|
|
|
gradeDue: () =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; considered: number; graded: number; incomplete: number }>(
|
|
|
|
|
|
"mentorLedger.gradeDue",
|
|
|
|
|
|
{},
|
|
|
|
|
|
),
|
|
|
|
|
|
scorecard: (handle?: string) =>
|
|
|
|
|
|
trpcQuery<
|
|
|
|
|
|
Array<{
|
|
|
|
|
|
handle: string;
|
|
|
|
|
|
claimType: string;
|
|
|
|
|
|
n: number;
|
|
|
|
|
|
wins: number;
|
|
|
|
|
|
losses: number;
|
|
|
|
|
|
expired: number;
|
|
|
|
|
|
incomplete: number;
|
|
|
|
|
|
winRate: number | null;
|
|
|
|
|
|
avgScore: number | null;
|
|
|
|
|
|
}>
|
|
|
|
|
|
>("mentorLedger.scorecard", handle ? { handle } : {}),
|
|
|
|
|
|
},
|
|
|
|
|
|
userLlm: {
|
|
|
|
|
|
status: () => trpcQuery<UserLlmStatus>("userLlm.status"),
|
|
|
|
|
|
upsertEndpoint: (input: { baseUrl: string; apiKey?: string; model?: string; keepExistingKey?: boolean }) =>
|
|
|
|
|
|
trpcMutate<UserLlmStatus>("userLlm.upsertEndpoint", input),
|
|
|
|
|
|
clear: () => trpcMutate<{ ok: boolean }>("userLlm.clear", {}),
|
|
|
|
|
|
test: (input?: { baseUrl?: string; apiKey?: string; model?: string }) =>
|
|
|
|
|
|
trpcMutate<{ ok: true; model: string; sample: string } | { ok: false; error: string }>(
|
|
|
|
|
|
"userLlm.test",
|
|
|
|
|
|
input ?? {},
|
|
|
|
|
|
TRPC_LLM_TIMEOUT_MS,
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
2026-07-05 08:50:12 -04:00
|
|
|
|
portfolio: {
|
|
|
|
|
|
holdings: () => trpcQuery<PortfolioHolding[]>("portfolio.holdings"),
|
|
|
|
|
|
addHolding: (symbol: string, shares: number, avgCost: number) => trpcMutate<{ created: boolean }>("portfolio.addHolding", { symbol, shares, avgCost }),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
updateHolding: (symbol: string, opts: { shares?: number; avgCost?: number }) =>
|
|
|
|
|
|
trpcMutate<{ updated: boolean }>("portfolio.updateHolding", { symbol, ...opts }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
removeHolding: (symbol: string) => trpcMutate<{ removed: boolean }>("portfolio.removeHolding", { symbol }),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
optionLegs: () => trpcQuery<PortfolioOptionLeg[]>("portfolio.optionLegs"),
|
|
|
|
|
|
addOptionLeg: (input: AddOptionLegInput) =>
|
|
|
|
|
|
trpcMutate<{ id: string; legs: PortfolioOptionLeg[] }>("portfolio.addOptionLeg", input),
|
|
|
|
|
|
removeOptionLeg: (id: string) => trpcMutate<{ removed: boolean }>("portfolio.removeOptionLeg", { id }),
|
|
|
|
|
|
},
|
|
|
|
|
|
sizing: {
|
|
|
|
|
|
compute: (input: {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
tier: "A_STAR" | "A" | "B" | "C";
|
|
|
|
|
|
riskFraction: number;
|
|
|
|
|
|
stopPerShare: number;
|
|
|
|
|
|
equity: number;
|
|
|
|
|
|
complexity?: "beginner" | "intermediate" | "advanced";
|
|
|
|
|
|
regime?: "trending-up" | "trending-down" | "range-bound";
|
|
|
|
|
|
aStarUnlocked?: boolean;
|
|
|
|
|
|
macroOverrideReason?: string;
|
|
|
|
|
|
}) =>
|
|
|
|
|
|
trpcQuery<SizingResult>("sizing.compute", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
risk: {
|
|
|
|
|
|
posture: (input: {
|
|
|
|
|
|
equity: number;
|
|
|
|
|
|
peakEquity?: number;
|
|
|
|
|
|
drawdownTolerancePct?: number;
|
|
|
|
|
|
regime?: "trending-up" | "trending-down" | "range-bound";
|
|
|
|
|
|
plans?: Array<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
stopPrice?: number;
|
|
|
|
|
|
rewardTarget?: number;
|
|
|
|
|
|
cluster?: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
}) => trpcQuery<RiskPostureResult>("risk.posture", input),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
|
|
|
|
|
dashboard: {
|
2026-07-12 12:55:59 -04:00
|
|
|
|
rollup: () => trpcQuery<Record<string, unknown>[]>("dashboard.rollup", {}),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
|
|
|
|
|
institutional: {
|
|
|
|
|
|
flow: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{ symbol: string; flow: Array<Record<string, unknown>>; quarters: string[] }>("institutional.flow", { symbol }),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
insiderStream: (symbol: string, limit = 200) =>
|
2026-07-05 08:50:12 -04:00
|
|
|
|
trpcQuery<{ symbol: string; events: Array<Record<string, unknown>>; netShares: number; count: number }>("institutional.insiderStream", { symbol, limit }),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
ownershipHistory: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{ symbol: string; months: Array<{ month: string; total_shares: number; num_filers: number }> }>("institutional.ownershipHistory", { symbol }),
|
|
|
|
|
|
buyEvents: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{ symbol: string; events: Array<{ filerName: string | null; sharesAdded: number; estimatedPrice: number; date: string }> }>("institutional.buyEvents", { symbol }),
|
|
|
|
|
|
analystRatings: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{ symbol: string; ratings: Array<{ firm: string; action: string | null; gradeFrom: string | null; gradeTo: string | null; ratingDate: string; targetFrom: number | null; targetTo: number | null }>; consensus: { strongBuy: number; buy: number; hold: number; sell: number; strongSell: number } | null; error?: string }>("institutional.analystRatings", { symbol }),
|
2026-07-25 12:09:21 -04:00
|
|
|
|
shortInterest: (symbol: string) =>
|
2026-08-10 13:36:26 -04:00
|
|
|
|
trpcQuery<{ symbol: string; sharesShort: number | null; sharesShortPriorMonth: number | null; sharesPercentSharesOut: number | null; shortRatio: number | null; shortPercentOfFloat: number | null; dateShortInterest: string | null; floatShares: number | null; sharesOutstanding: number | null; daysToCover: number | null; settlementDate: string | null; history: Array<{ settlementDate: string; sharesShort: number | null; avgDailyVolume: number | null; daysToCover: number }> | null; finraBiweeklyHistory: Array<{ settlementDate: string; currentShortPosition: number; previousShortPosition: number | null; daysToCover: number | null; changePercent: number | null }>; finraWeekly: Array<{ weekEnding: string; shortVolume: number; totalVolume: number; shortPct: number }>; discrepancyPct: number | null; discrepancyBetween: string | null; sources: { yfinance: { available: boolean; stale: boolean }; nasdaq: { available: boolean; stale: boolean }; finra: { available: boolean; stale: boolean } }; stale: boolean }>("institutional.shortInterest", { symbol }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
|
|
|
|
|
emotionLogger: {
|
|
|
|
|
|
add: (input: { tradeExecutionId: string; priceAtEvent?: number; emotion: string; note?: string }) =>
|
|
|
|
|
|
trpcMutate<{ success: boolean; log: Record<string, unknown> }>("emotionLogger.add", input),
|
|
|
|
|
|
getByTrade: (tradeExecutionId: string) =>
|
|
|
|
|
|
trpcQuery<{ logs: Record<string, unknown>[] }>("emotionLogger.getByTrade", { tradeExecutionId }),
|
|
|
|
|
|
delete: (id: string) =>
|
|
|
|
|
|
trpcMutate<{ success: boolean }>("emotionLogger.delete", { id }),
|
|
|
|
|
|
},
|
|
|
|
|
|
alerts: {
|
2026-07-23 18:02:24 -04:00
|
|
|
|
list: (limit = 50) => trpcQuery<AlertEventRow[]>("alerts.list", { limit }),
|
|
|
|
|
|
acknowledge: (alertId: string) => trpcMutate<{ ok: boolean }>("alerts.acknowledge", { alertId }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
unackedCount: () => trpcQuery<{ count: number }>("alerts.unackedCount"),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
acknowledgeAll: () => trpcMutate<{ ok: boolean }>("alerts.acknowledgeAll", {}),
|
2026-08-12 22:39:19 -04:00
|
|
|
|
clearAll: () => trpcMutate<{ ok: boolean }>("alerts.clearAll", {}),
|
2026-07-23 20:51:47 -04:00
|
|
|
|
createSubscription: (input: { alertType: string; watchlistId?: string; symbol?: string; params?: string }) =>
|
|
|
|
|
|
trpcMutate<AlertSubscriptionRow>("alerts.createSubscription", input),
|
|
|
|
|
|
listSubscriptions: (symbol?: string) =>
|
|
|
|
|
|
trpcQuery<AlertSubscriptionRow[]>("alerts.listSubscriptions", symbol ? { symbol } : {}),
|
|
|
|
|
|
updateSubscription: (id: string, updates: { enabled?: boolean; params?: string }) =>
|
|
|
|
|
|
trpcMutate<AlertSubscriptionRow>("alerts.updateSubscription", { id, ...updates }),
|
|
|
|
|
|
deleteSubscription: (id: string) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean }>("alerts.deleteSubscription", { id }),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
listTypes: () => trpcQuery<AlertTypeToggleRow[]>("alerts.listTypes"),
|
|
|
|
|
|
toggleType: (alertType: string, enabled: boolean) =>
|
|
|
|
|
|
trpcMutate<AlertTypeToggleRow[]>("alerts.toggleType", { alertType, enabled }),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
},
|
|
|
|
|
|
x: {
|
|
|
|
|
|
cashtag_search: (symbol: string, limit = 30) => trpcQuery<{ posts: XPost[]; configured: boolean }>("x.cashtag_search", { symbol, limit }),
|
|
|
|
|
|
feed: (symbol: string, cursor?: string) => trpcQuery<XFedFeed>("x.feed", { symbol, limit: 10, cursor }),
|
|
|
|
|
|
accountsForSymbol: (symbol: string) => trpcQuery<Array<{id:string; handle:string; label:string}>>("x.accountsForSymbol", { symbol }),
|
2026-07-05 08:50:12 -04:00
|
|
|
|
},
|
2026-07-12 12:55:59 -04:00
|
|
|
|
admin: {
|
2026-07-25 11:38:23 -04:00
|
|
|
|
usersList: () => trpcQuery<Array<{ id: string; email: string; complexity: string; created_at: string; is_admin: number; status: string; modules: string }>>("admin.usersList"),
|
|
|
|
|
|
setUserModules: (userId: string, modules: string[]) => trpcMutate<{ ok: boolean }>("admin.setUserModules", { userId, modules }),
|
|
|
|
|
|
disableUser: (userId: string) => trpcMutate<{ ok: boolean }>("admin.disableUser", { userId }),
|
|
|
|
|
|
enableUser: (userId: string) => trpcMutate<{ ok: boolean }>("admin.enableUser", { userId }),
|
|
|
|
|
|
deleteUser: (userId: string) => trpcMutate<{ ok: boolean }>("admin.deleteUser", { userId }),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
userSessions: (userId: string) => trpcQuery<Array<{ id: string; user_id: string; expires_at: string; created_at: string }>>("admin.userSessions", { userId }),
|
|
|
|
|
|
resetPassword: (email: string, tempPassword: string) => trpcMutate<{ userId: string }>("admin.resetPassword", { email, tempPassword }),
|
|
|
|
|
|
queueHealth: () => trpcQuery<Array<{ source: string; status: string; count: number; last_attempt: string | null; retry_count: number; backoff_until: string | null }>>("admin.queueHealth"),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
alertStatus: () => trpcQuery<AlertStatusRow>("admin.alertStatus"),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
resetBackoff: (sourceKind: string) => trpcMutate<{ cleared: number }>("admin.resetBackoff", { sourceKind }),
|
|
|
|
|
|
queueSecFetch: (symbol: string) => trpcMutate<{ ok: boolean }>("admin.queueSecFetch", { symbol }),
|
2026-08-10 13:36:26 -04:00
|
|
|
|
dealerMapIntegrity: (input: { symbol: string; asOf?: string; recompute?: boolean }) =>
|
|
|
|
|
|
trpcMutate<{
|
|
|
|
|
|
mode: "live" | "replay";
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
ok: boolean;
|
|
|
|
|
|
summary: string;
|
|
|
|
|
|
integrityText: string;
|
|
|
|
|
|
integrity: DealerIntegrityReport;
|
|
|
|
|
|
king: { strike: number; expiry: string; gex: number } | null;
|
|
|
|
|
|
spot: number | null;
|
|
|
|
|
|
asOf?: string;
|
|
|
|
|
|
spotSource?: string;
|
|
|
|
|
|
surfaceRowCount?: number;
|
|
|
|
|
|
keptPriorMap?: boolean;
|
|
|
|
|
|
cells?: number;
|
|
|
|
|
|
quality?: unknown;
|
|
|
|
|
|
snapshotCompare?: unknown;
|
|
|
|
|
|
}>("admin.dealerMapIntegrity", input),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
queueStatus: () => trpcQuery<QueueStatus>("admin.queueStatus"),
|
|
|
|
|
|
queuePause: () => trpcMutate<{ paused: boolean }>("admin.queuePause", undefined),
|
|
|
|
|
|
queueResume: () => trpcMutate<{ paused: boolean }>("admin.queueResume", undefined),
|
|
|
|
|
|
queueClearDone: (olderThanHours: number) => trpcMutate<{ cleared: number }>("admin.queueClearDone", { olderThanHours }),
|
|
|
|
|
|
queueSchedules: () => trpcQuery<ScheduleEntry[]>("admin.queueSchedules"),
|
|
|
|
|
|
queueSetSchedule: (sourceKind: string, intervalMs: number) => trpcMutate<{ ok: boolean }>("admin.queueSetSchedule", { sourceKind, intervalMs }),
|
|
|
|
|
|
queueDeleteSchedule: (sourceKind: string) => trpcMutate<{ ok: boolean }>("admin.queueDeleteSchedule", { sourceKind }),
|
|
|
|
|
|
queueLogs: (key: string, limit: number) => trpcQuery<ErrorLogEntry[]>("admin.queueLogs", { key, limit }),
|
|
|
|
|
|
queueRetryJob: (key: string) => trpcMutate<{ ok: boolean }>("admin.queueRetryJob", { key }),
|
|
|
|
|
|
queueRetrySource: (sourceKind: string) => trpcMutate<{ cleared: number }>("admin.queueRetrySource", { sourceKind }),
|
2026-08-18 14:10:02 -04:00
|
|
|
|
clearSourceCooldown: (sourceKind: string) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; sourceKind: string }>("admin.clearSourceCooldown", { sourceKind }),
|
2026-08-12 22:39:23 -04:00
|
|
|
|
queueSourcePause: (source: string) => trpcMutate<{ ok: boolean; source: string }>("admin.queueSourcePause", { source }),
|
|
|
|
|
|
queueSourceResume: (source: string) => trpcMutate<{ ok: boolean; source: string }>("admin.queueSourceResume", { source }),
|
|
|
|
|
|
queueSourceStop: (source: string) => trpcMutate<{ ok: boolean; source: string; cleared: number }>("admin.queueSourceStop", { source }),
|
|
|
|
|
|
queueSourceStart: (source: string) => trpcMutate<{ ok: boolean; source: string }>("admin.queueSourceStart", { source }),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
auditLog: (limit = 50, offset = 0, actor?: string, action?: string) =>
|
|
|
|
|
|
trpcQuery<Array<{ id: string; actor: string; action: string; target: string | null; ts: string; meta: string | null }>>("admin.auditLog", { limit, offset, ...(actor ? { actor } : {}), ...(action ? { action } : {}) }),
|
2026-07-12 20:19:12 -04:00
|
|
|
|
queueLint: (symbol: string, kind: 'sec-lint-holders' | 'sec-lint-insiders') =>
|
|
|
|
|
|
trpcMutate<LintResult>("admin.queueLint", { symbol, kind }),
|
|
|
|
|
|
queueLintAll: (input: { kind: 'sec-lint-holders' | 'sec-lint-insiders' }) =>
|
2026-08-10 13:36:26 -04:00
|
|
|
|
trpcMutate<{ total: number; enqueued: number }>("admin.queueLintAll", input),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
xCredentialsStatus: () => trpcQuery<XCredentialsStatus>("admin.xCredentialsStatus"),
|
|
|
|
|
|
xCredentialsSet: (ct0: string, auth_token: string) => trpcMutate<{ ok: boolean }>("admin.xCredentialsSet", { ct0, auth_token }),
|
|
|
|
|
|
fredKeyStatus: () => trpcQuery<{ configured: boolean }>("admin.fredKeyStatus"),
|
|
|
|
|
|
fredKeySet: (apiKey: string) => trpcMutate<{ ok: boolean }>("admin.fredKeySet", { apiKey }),
|
|
|
|
|
|
xAccountsList: () => trpcQuery<XAccountRow[]>("admin.xAccountsList"),
|
|
|
|
|
|
xAccountAdd: (symbol: string, handle: string, label?: string) => trpcMutate<{ id: string }>("admin.xAccountAdd", { symbol, handle, ...(label ? { label } : {}) }),
|
|
|
|
|
|
xAccountRemove: (id: string) => trpcMutate<{ ok: boolean }>("admin.xAccountRemove", { id }),
|
|
|
|
|
|
xPrune: (olderThanDays?: number) => trpcMutate<{ deleted: number }>("admin.xPrune", { olderThanDays: olderThanDays ?? 30 }),
|
|
|
|
|
|
pendingUsers: () => trpcQuery<Array<{ id: string; email: string; complexity: string; created_at: string }>>("admin.pendingUsers"),
|
|
|
|
|
|
approveUser: (userId: string) => trpcMutate<{ ok: boolean }>("admin.approveUser", { userId }),
|
|
|
|
|
|
rejectUser: (userId: string) => trpcMutate<{ ok: boolean }>("admin.rejectUser", { userId }),
|
2026-07-12 20:19:12 -04:00
|
|
|
|
dataQualityList: (input?: { symbol?: string; kind?: 'institution_filings' | 'insider_transactions' }) =>
|
|
|
|
|
|
trpcQuery<DataQualityRow[]>("admin.dataQualityList", input ?? {}),
|
2026-07-23 18:02:24 -04:00
|
|
|
|
serverRestart: (target: 'backend' | 'frontend' | 'all' = 'all') =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean; backend: string; frontend: string }>("admin.serverRestart", { target }),
|
2026-07-23 20:51:47 -04:00
|
|
|
|
smtpConfig: () => trpcQuery<SmtpConfigRow>("admin.smtpConfig"),
|
|
|
|
|
|
smtpConfigUpdate: (input: SmtpConfigUpdate) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean }>("admin.smtpConfigUpdate", input),
|
|
|
|
|
|
smtpConfigTest: () => trpcMutate<{ ok: boolean }>("admin.smtpConfigTest", {}),
|
2026-07-12 12:55:59 -04:00
|
|
|
|
},
|
2026-08-10 13:36:26 -04:00
|
|
|
|
strategies: {
|
|
|
|
|
|
list: () => trpcQuery<Array<{ id: string; name: string; components: string; unlocked: boolean; created_at: string }>>("strategies.list"),
|
|
|
|
|
|
create: (input: { name: string; components?: unknown[] }) => trpcMutate<{ id: string }>("strategies.create", input),
|
|
|
|
|
|
get: (id: string) => trpcQuery<Record<string, unknown>>("strategies.get", { id }),
|
|
|
|
|
|
listPresets: () => trpcQuery<StrategyPreset[]>("strategies.listPresets"),
|
|
|
|
|
|
getPreset: (id: string) => trpcQuery<StrategyPreset>("strategies.getPreset", { id }),
|
|
|
|
|
|
forkPreset: (presetId: string) => trpcMutate<{ id: string; name: string }>("strategies.forkPreset", { presetId }),
|
|
|
|
|
|
suggestTickers: (presetId: string) => trpcQuery<TickerSuggestion[]>("strategies.suggestTickers", { presetId }),
|
|
|
|
|
|
},
|
|
|
|
|
|
backtest: {
|
|
|
|
|
|
run: (input: { strategyId: string; symbol: string; timeframe?: string }) =>
|
|
|
|
|
|
trpcQuery<{ equityCurve: Array<{ ts: string; equity: number }>; exitReasons: string[]; sampleSize: number }>("backtest.run", input),
|
|
|
|
|
|
evaluateLatest: (input: { strategyId: string; symbol: string }) =>
|
|
|
|
|
|
trpcQuery<{ fires: boolean; conditions: Record<string, boolean | null> }>("backtest.evaluateLatest", input),
|
|
|
|
|
|
runPortfolio: (input: { allocations: Array<{ symbol: string; targetPct: number }>; rebalanceFreq?: string; driftThreshold?: number }) =>
|
|
|
|
|
|
trpcQuery<PortfolioBacktestResult>("backtest.runPortfolio", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
screener: {
|
|
|
|
|
|
filter: (input: { expression: string; scope?: "watchlist" | "sector"; sector?: string }) =>
|
|
|
|
|
|
trpcQuery<{ results: Array<{ symbol: string; matched: boolean; why?: string[] } | string>; scope: string }>("screener.filter", input),
|
|
|
|
|
|
strategy: (input: { strategyId: string }) =>
|
|
|
|
|
|
trpcQuery<{ results: Array<{ symbol: string; matched: boolean; why?: string[] } | string> }>("screener.strategy", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
reports: {
|
|
|
|
|
|
generate: (input: {
|
|
|
|
|
|
scope: "symbol" | "watchlist" | "portfolio" | "rotation" | "sizing_year" | "risk_posture";
|
|
|
|
|
|
symbol?: string;
|
|
|
|
|
|
data?: Record<string, unknown>;
|
|
|
|
|
|
}) => trpcQuery<{ html?: string; markdown?: string; title?: string; [k: string]: unknown }>("reports.generate", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
derisking: {
|
|
|
|
|
|
suggest: (input: { symbol: string; currentPrice: number; avgCost: number; shares: number; ema21?: number; ema50?: number; thesisStatus?: string; currentRegime?: string; portfolioCorrelation?: number; profitTargets?: number[]; dividendYield?: number; payoutRatio?: number }) =>
|
|
|
|
|
|
trpcQuery<{ suggestions: Array<{ strategy: { id: string; name: string; description: string; alfredFraming: string; requiresOptionsSleeve: boolean }; applicable: boolean; reason: string; details: string }> }>("derisking.suggest", input),
|
|
|
|
|
|
dividendHealth: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{ data: Record<string, unknown> | null }>("derisking.dividendHealth", { symbol }),
|
|
|
|
|
|
},
|
|
|
|
|
|
thesisMonitor: {
|
|
|
|
|
|
assess: (input: { thesis: string; events: Array<{ type: string; date: string; detail?: string }> }) => trpcQuery<{ status: string; confidence: number; reasoning: string[] }>("thesisMonitor.assess", input),
|
|
|
|
|
|
},
|
|
|
|
|
|
trades: {
|
|
|
|
|
|
list: () => trpcQuery<Array<Record<string, unknown>>>("trades.list"),
|
|
|
|
|
|
get: (id: string) => trpcQuery<Record<string, unknown>>("trades.get", { id }),
|
|
|
|
|
|
save: (input: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
date: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
tier: string;
|
|
|
|
|
|
environment?: string;
|
|
|
|
|
|
strategyId?: string;
|
|
|
|
|
|
confluenceScore?: number;
|
|
|
|
|
|
riskPct?: number;
|
|
|
|
|
|
positionSize?: number;
|
|
|
|
|
|
entryPrice?: number;
|
|
|
|
|
|
stopLoss?: number;
|
|
|
|
|
|
targets?: string;
|
|
|
|
|
|
justifications?: string;
|
|
|
|
|
|
checklistCompleted?: number;
|
|
|
|
|
|
allocationPlanId?: string;
|
|
|
|
|
|
allocationRole?: string;
|
|
|
|
|
|
tradeId?: string;
|
|
|
|
|
|
}) => trpcMutate<{ ok: boolean; id: string }>("trades.save", input),
|
|
|
|
|
|
close: (id: string, realizedPnl: number, reflection?: string) =>
|
|
|
|
|
|
trpcMutate<{ ok: boolean }>("trades.close", { id, realizedPnl, ...(reflection ? { reflection } : {}) }),
|
|
|
|
|
|
stats: () => trpcQuery<{ totalClosed: number; profitable: number }>("trades.stats"),
|
|
|
|
|
|
},
|
|
|
|
|
|
theses: {
|
|
|
|
|
|
list: () => trpcQuery<Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
user_id: string;
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
statement: string;
|
|
|
|
|
|
invalidation_criteria: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
updated_at: string;
|
|
|
|
|
|
}>>("theses.list"),
|
|
|
|
|
|
get: (id: string) => trpcQuery<Record<string, unknown>>("theses.get", { id }),
|
|
|
|
|
|
create: (input: {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
statement: string;
|
|
|
|
|
|
invalidationCriteria?: string[];
|
|
|
|
|
|
status?: "intact" | "weakening" | "broken";
|
|
|
|
|
|
}) => trpcMutate<{ id: string; symbol: string }>("theses.create", input),
|
|
|
|
|
|
update: (input: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
statement?: string;
|
|
|
|
|
|
invalidationCriteria?: string[];
|
|
|
|
|
|
status?: "intact" | "weakening" | "broken";
|
|
|
|
|
|
}) => trpcMutate<{ ok: boolean }>("theses.update", input),
|
|
|
|
|
|
delete: (id: string) => trpcMutate<{ ok: boolean }>("theses.delete", { id }),
|
|
|
|
|
|
},
|
|
|
|
|
|
macro: {
|
|
|
|
|
|
commentary: () => trpcQuery<{ shortTerm: string; longTerm: string; disclaimer: string }>("macro.commentary"),
|
|
|
|
|
|
regimeClassify: () => trpcQuery<{ regime: string; confidence?: number; factors?: unknown }>("macro.regimeClassify"),
|
|
|
|
|
|
},
|
2026-08-10 16:07:19 -04:00
|
|
|
|
confluence: {
|
|
|
|
|
|
slots: () => trpcQuery<{
|
|
|
|
|
|
slots: Array<{ id: string; name: string; family: string; body: string; granularity: string; explain: string }>;
|
|
|
|
|
|
slotIds: string[];
|
|
|
|
|
|
families: string[];
|
|
|
|
|
|
}>("confluence.slots", {}),
|
|
|
|
|
|
racks: () => trpcQuery<{
|
|
|
|
|
|
system: Array<{ id: string; ownerId: string | null; name: string; slotIds: string[]; description: string | null; isSystem: boolean; createdAt: string }>;
|
|
|
|
|
|
user: Array<{ id: string; ownerId: string | null; name: string; slotIds: string[]; description: string | null; isSystem: boolean; createdAt: string }>;
|
|
|
|
|
|
}>("confluence.racks", {}),
|
|
|
|
|
|
evaluation: (symbol: string, opts?: { rackId?: string; limit?: number }) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
rackId: string | null;
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
latest: Record<string, unknown> | null;
|
|
|
|
|
|
history: Array<Record<string, unknown>>;
|
|
|
|
|
|
change: {
|
|
|
|
|
|
changed: boolean;
|
|
|
|
|
|
changeType: string;
|
|
|
|
|
|
current: string;
|
|
|
|
|
|
previous: string | null;
|
|
|
|
|
|
netEvidenceShift: number;
|
|
|
|
|
|
flippedSlotIds: string[];
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
}>("confluence.evaluation", { symbol, ...(opts?.rackId ? { rackId: opts.rackId } : {}), ...(opts?.limit ? { limit: opts.limit } : {}) }),
|
|
|
|
|
|
backtest: (symbol: string, opts?: { weeks?: number }) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
fires: number;
|
|
|
|
|
|
horizonDays: number;
|
|
|
|
|
|
slots: Array<{
|
|
|
|
|
|
slotId: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
direction: string;
|
|
|
|
|
|
fires: number;
|
|
|
|
|
|
resolvedFires: number;
|
|
|
|
|
|
confirmedFires: number;
|
|
|
|
|
|
falseAlarms: number;
|
|
|
|
|
|
hitRate: number | null;
|
|
|
|
|
|
reliabilityWeight: number;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
sampleSizeCaveat: string;
|
|
|
|
|
|
}>("confluence.backtest", { symbol, ...(opts?.weeks ? { weeks: opts.weeks } : {}) }),
|
|
|
|
|
|
scorecard: (symbol?: string) =>
|
|
|
|
|
|
trpcQuery<{ slots: unknown[]; note: string }>("confluence.scorecard", symbol ? { symbol } : {}),
|
2026-08-11 23:13:15 -04:00
|
|
|
|
signalHistory: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
fires: Array<{
|
|
|
|
|
|
slotId: string;
|
|
|
|
|
|
slotName: string;
|
|
|
|
|
|
body: 'bull' | 'bear' | 'exit';
|
|
|
|
|
|
firedAt: string;
|
|
|
|
|
|
qualityAtFire: string;
|
2026-08-18 14:10:02 -04:00
|
|
|
|
verdict: 'real' | 'false_alarm' | null;
|
|
|
|
|
|
priceConfirmed: boolean;
|
2026-08-11 23:13:15 -04:00
|
|
|
|
}>;
|
|
|
|
|
|
}>("confluence.signalHistory", { symbol }),
|
2026-08-18 14:10:02 -04:00
|
|
|
|
recentZones: (symbol: string, opts?: { rackId?: string; entries?: number; exits?: number }) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
rackId: string | null;
|
|
|
|
|
|
entries: Array<{
|
|
|
|
|
|
kind: 'entry' | 'exit';
|
|
|
|
|
|
startAsOf: string;
|
|
|
|
|
|
endAsOf: string;
|
|
|
|
|
|
open: boolean;
|
|
|
|
|
|
qualityAtStart: string;
|
|
|
|
|
|
netEvidenceAtStart: number;
|
|
|
|
|
|
firedSlotIds: string[];
|
|
|
|
|
|
forwardReturn: number | null;
|
|
|
|
|
|
verdict: 'confirmed' | 'false_alarm' | 'open';
|
|
|
|
|
|
}>;
|
|
|
|
|
|
exits: Array<{
|
|
|
|
|
|
kind: 'entry' | 'exit';
|
|
|
|
|
|
startAsOf: string;
|
|
|
|
|
|
endAsOf: string;
|
|
|
|
|
|
open: boolean;
|
|
|
|
|
|
qualityAtStart: string;
|
|
|
|
|
|
netEvidenceAtStart: number;
|
|
|
|
|
|
firedSlotIds: string[];
|
|
|
|
|
|
forwardReturn: number | null;
|
|
|
|
|
|
verdict: 'confirmed' | 'false_alarm' | 'open';
|
|
|
|
|
|
}>;
|
|
|
|
|
|
entryRule: {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
source: string;
|
|
|
|
|
|
derivedAt: string | null;
|
|
|
|
|
|
sampleCaveat: string;
|
|
|
|
|
|
train: { hitRate: number | null; resolved: number; baselineHitRate: number | null } | null;
|
|
|
|
|
|
validate: { hitRate: number | null; resolved: number; baselineHitRate: number | null } | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
exitRule: {
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
source: string;
|
|
|
|
|
|
derivedAt: string | null;
|
|
|
|
|
|
sampleCaveat: string;
|
|
|
|
|
|
train: { hitRate: number | null; resolved: number; baselineHitRate: number | null } | null;
|
|
|
|
|
|
validate: { hitRate: number | null; resolved: number; baselineHitRate: number | null } | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
coverage: { evaluatedDays: number; lookbackDays: number; replayComplete: boolean };
|
|
|
|
|
|
symbolScore: {
|
|
|
|
|
|
entry: { zones: number; resolved: number; confirmed: number; hitRate: number | null };
|
|
|
|
|
|
exit: { zones: number; resolved: number; confirmed: number; hitRate: number | null };
|
|
|
|
|
|
};
|
|
|
|
|
|
learningLedger: { ready: boolean; filled: number; total: number };
|
|
|
|
|
|
}>("confluence.recentZones", {
|
|
|
|
|
|
symbol,
|
|
|
|
|
|
...(opts?.rackId ? { rackId: opts.rackId } : {}),
|
|
|
|
|
|
...(opts?.entries ? { entries: opts.entries } : {}),
|
|
|
|
|
|
...(opts?.exits ? { exits: opts.exits } : {}),
|
|
|
|
|
|
}),
|
2026-08-10 16:07:19 -04:00
|
|
|
|
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),
|
2026-08-11 22:25:17 -04:00
|
|
|
|
corridorSnapshot: (symbol: string) =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
snapshot: Record<string, unknown> | null;
|
|
|
|
|
|
spy: Record<string, unknown> | null;
|
|
|
|
|
|
series: Array<Record<string, unknown>>;
|
|
|
|
|
|
corridor: {
|
|
|
|
|
|
low: number | null;
|
|
|
|
|
|
high: number | null;
|
|
|
|
|
|
median: number | null;
|
|
|
|
|
|
currentPE: number | null;
|
|
|
|
|
|
fairValue1y: number | null;
|
|
|
|
|
|
impliedUpside1y: number | null;
|
|
|
|
|
|
pePercentile1y: number | null;
|
|
|
|
|
|
} | null;
|
|
|
|
|
|
}>("confluence.corridorSnapshot", { symbol }),
|
|
|
|
|
|
corridorWatchlist: () =>
|
|
|
|
|
|
trpcQuery<{ symbols: string[] }>("confluence.corridorWatchlist"),
|
|
|
|
|
|
corridorWatchlistAdd: (symbol: string) =>
|
|
|
|
|
|
trpcMutate<{ symbols: string[] }>("confluence.corridorWatchlistAdd", { symbol }),
|
|
|
|
|
|
corridorWatchlistRemove: (symbol: string) =>
|
|
|
|
|
|
trpcMutate<{ symbols: string[] }>("confluence.corridorWatchlistRemove", { symbol }),
|
|
|
|
|
|
corridorBacktest: () =>
|
|
|
|
|
|
trpcQuery<{
|
|
|
|
|
|
rows: Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
articleDate: string;
|
|
|
|
|
|
articleId: string;
|
|
|
|
|
|
rankingType: string;
|
|
|
|
|
|
topSymbols: string[];
|
|
|
|
|
|
bottomSymbols: string[];
|
|
|
|
|
|
topAvgReturn: number | null;
|
|
|
|
|
|
bottomAvgReturn: number | null;
|
|
|
|
|
|
spread: number | null;
|
|
|
|
|
|
isWin: boolean;
|
|
|
|
|
|
horizonDays: number;
|
|
|
|
|
|
gradedAt: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
aggregate: {
|
|
|
|
|
|
entries: number;
|
|
|
|
|
|
resolved: number;
|
|
|
|
|
|
resolved1y: number;
|
|
|
|
|
|
resolved90d: number;
|
|
|
|
|
|
hitRate: number | null;
|
|
|
|
|
|
avgSpread: number | null;
|
|
|
|
|
|
bestSpread: number | null;
|
|
|
|
|
|
worstSpread: number | null;
|
|
|
|
|
|
avgTopReturn: number | null;
|
|
|
|
|
|
avgBottomReturn: number | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
}>("confluence.corridorBacktest"),
|
|
|
|
|
|
corridorBacktestRun: () =>
|
|
|
|
|
|
trpcMutate<{ grades: number; resolved: number; aggregate: Record<string, unknown> }>("confluence.corridorBacktestRun", {}),
|
|
|
|
|
|
runEvaluationNow: (opts?: { symbol?: string }) =>
|
|
|
|
|
|
trpcMutate<Record<string, unknown>>("confluence.runEvaluationNow", opts ?? {}),
|
2026-08-10 16:07:19 -04:00
|
|
|
|
},
|
2026-06-29 17:51:54 -04:00
|
|
|
|
};
|
2026-07-05 08:50:12 -04:00
|
|
|
|
|
2026-08-10 13:36:26 -04:00
|
|
|
|
export interface TickerSuggestion {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
name: string | null;
|
|
|
|
|
|
role: string;
|
|
|
|
|
|
allocationPct: number;
|
|
|
|
|
|
reason: string;
|
|
|
|
|
|
source: 'live' | 'default';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface StrategyPreset {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
category: string;
|
|
|
|
|
|
complexity: string;
|
|
|
|
|
|
scope: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
entry_rules: string;
|
|
|
|
|
|
exit_profile: string;
|
|
|
|
|
|
risk_policy: string;
|
|
|
|
|
|
instrument_type: string;
|
|
|
|
|
|
portfolio_template: string | null;
|
|
|
|
|
|
conviction_tier_default: string | null;
|
|
|
|
|
|
requires_options_unlock: number;
|
|
|
|
|
|
forkable: number;
|
|
|
|
|
|
example_tickers: string | null;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortfolioBacktestResult {
|
|
|
|
|
|
equityCurve: Array<{ date: string; value: number }>;
|
|
|
|
|
|
totalReturn: number;
|
|
|
|
|
|
cagr: number;
|
|
|
|
|
|
maxDrawdown: number;
|
|
|
|
|
|
positions: Array<{ symbol: string; targetPct: number; totalReturn: number; finalValue: number }>;
|
|
|
|
|
|
rebalanceEvents: Array<{ date: string; type: string }>;
|
|
|
|
|
|
correlationMatrix: Record<string, Record<string, number>>;
|
|
|
|
|
|
sampleSize: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 20:19:12 -04:00
|
|
|
|
export interface LintResult {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
kind: string;
|
|
|
|
|
|
discoveredCount: number;
|
|
|
|
|
|
storedCount: number;
|
|
|
|
|
|
missingCount: number;
|
|
|
|
|
|
backfilled: number;
|
|
|
|
|
|
stale: number;
|
|
|
|
|
|
status: 'ok' | 'gaps_found' | 'stale' | 'error';
|
|
|
|
|
|
detail: Record<string, unknown>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DataQualityRow {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
kind: string;
|
|
|
|
|
|
last_checked_at: string;
|
|
|
|
|
|
stored_count: number;
|
|
|
|
|
|
discovered_count: number;
|
|
|
|
|
|
missing_count: number;
|
|
|
|
|
|
stale: number;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
detail: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-05 08:50:12 -04:00
|
|
|
|
export interface WatchlistEntry {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
added_at: string;
|
|
|
|
|
|
notes?: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortfolioHolding {
|
|
|
|
|
|
symbol: string;
|
|
|
|
|
|
shares: number;
|
|
|
|
|
|
avg_cost: number;
|
|
|
|
|
|
added_at: string;
|
|
|
|
|
|
}
|
2026-07-12 12:55:59 -04:00
|
|
|
|
|
2026-07-23 18:02:24 -04:00
|
|
|
|
export type OptionRight = "call" | "put";
|
|
|
|
|
|
export type OptionSide = "long" | "short";
|
|
|
|
|
|
export type OptionRole =
|
|
|
|
|
|
| "long_call"
|
|
|
|
|
|
| "long_put"
|
|
|
|
|
|
| "covered_call"
|
|
|
|
|
|
| "cash_secured_put"
|
|
|
|
|
|
| "other";
|
|
|
|
|
|
|
|
|
|
|
|
export interface PortfolioOptionLeg {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
underlying: string;
|
|
|
|
|
|
right: OptionRight;
|
|
|
|
|
|
side: OptionSide;
|
|
|
|
|
|
strike: number;
|
|
|
|
|
|
expiry: string;
|
|
|
|
|
|
contracts: number;
|
|
|
|
|
|
premium: number;
|
|
|
|
|
|
multiplier: number;
|
|
|
|
|
|
role: OptionRole;
|
|
|
|
|
|
acquired_at: string;
|
|
|
|
|
|
note: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface AddOptionLegInput {
|
|
|
|
|
|
underlying: string;
|
|
|
|
|
|
right: OptionRight;
|
|
|
|
|
|
side: OptionSide;
|
|
|
|
|
|
strike: number;
|
|
|
|
|
|
expiry: string;
|
|
|
|
|
|
contracts: number;
|
|
|
|
|
|
premium: number;
|
|
|
|
|
|
multiplier?: number;
|
|
|
|
|
|
role?: OptionRole;
|
|
|
|
|
|
note?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SizingLayer {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
value: number;
|
|
|
|
|
|
explanation: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SizingResult {
|
|
|
|
|
|
shares: number;
|
|
|
|
|
|
layers: SizingLayer[];
|
|
|
|
|
|
explanations: string[];
|
|
|
|
|
|
blocked: boolean;
|
|
|
|
|
|
blockReason: string | null;
|
|
|
|
|
|
override: "macro" | "cluster" | null;
|
|
|
|
|
|
halted?: boolean;
|
|
|
|
|
|
footer?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RiskRecommendation {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
tradeOff: string;
|
|
|
|
|
|
explanation: string;
|
|
|
|
|
|
severity: "warning" | "info";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RiskPostureResult {
|
|
|
|
|
|
asymmetry: number;
|
|
|
|
|
|
clusterExposure: Record<string, number>;
|
|
|
|
|
|
clusterCaps?: Record<string, number>;
|
|
|
|
|
|
drawdownVsTolerance: {
|
|
|
|
|
|
currentDrawdownPct: number;
|
|
|
|
|
|
tolerancePct: number;
|
|
|
|
|
|
status: -1 | 0 | 1;
|
|
|
|
|
|
};
|
|
|
|
|
|
halted: boolean;
|
|
|
|
|
|
recommendedActions: RiskRecommendation[];
|
|
|
|
|
|
halt: { haltedUntil: string; triggeredBy: string; ts: string } | null;
|
|
|
|
|
|
holdingsCount: number;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
optionLegsCount?: number;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
totalExposureUsd: number;
|
2026-08-10 13:36:26 -04:00
|
|
|
|
optionRisk?: {
|
|
|
|
|
|
premiumAtRiskUsd: number;
|
|
|
|
|
|
cashReservedUsd: number;
|
|
|
|
|
|
creditReceivedUsd: number;
|
|
|
|
|
|
capitalCommittedUsd: number;
|
|
|
|
|
|
uncoveredShortCallCount: number;
|
|
|
|
|
|
};
|
2026-07-23 18:02:24 -04:00
|
|
|
|
footer: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface SourceCooldown {
|
|
|
|
|
|
source: string;
|
|
|
|
|
|
until: string | null;
|
|
|
|
|
|
remainingMs: number;
|
|
|
|
|
|
consecutiveHits: number;
|
|
|
|
|
|
active: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-12 12:55:59 -04:00
|
|
|
|
export interface QueueStatus {
|
|
|
|
|
|
paused: boolean;
|
|
|
|
|
|
queued: number;
|
|
|
|
|
|
in_flight: number;
|
|
|
|
|
|
failed: number;
|
|
|
|
|
|
backoff: number;
|
|
|
|
|
|
done: number;
|
|
|
|
|
|
backoff_until: string | null;
|
|
|
|
|
|
last_error: string | undefined;
|
|
|
|
|
|
counts: Record<string, number>;
|
|
|
|
|
|
lastErrors: Array<{ key: string; error: string | null }>;
|
2026-07-23 18:02:24 -04:00
|
|
|
|
/** Active vendor cool-downs after 429 (ADR-0009). */
|
|
|
|
|
|
sourceCooldowns?: SourceCooldown[];
|
2026-08-12 22:39:23 -04:00
|
|
|
|
/** Operator-paused sources (drain frozen, jobs retained). */
|
|
|
|
|
|
pausedSources?: string[];
|
|
|
|
|
|
/** Operator-stopped sources (drain frozen, jobs cleared, enqueue blocked). */
|
|
|
|
|
|
stoppedSources?: string[];
|
2026-08-18 14:10:02 -04:00
|
|
|
|
pendingByKind?: Record<string, number>;
|
|
|
|
|
|
demandSize?: number;
|
|
|
|
|
|
dataPlaneHealthy?: boolean;
|
|
|
|
|
|
dataPlaneNotes?: string[];
|
2026-07-12 12:55:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ScheduleEntry {
|
|
|
|
|
|
sourceKind: string;
|
|
|
|
|
|
intervalMs: number;
|
|
|
|
|
|
lastEnqueued: string | null;
|
|
|
|
|
|
nextEnqueue: string | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ErrorLogEntry {
|
|
|
|
|
|
attempt: number;
|
|
|
|
|
|
errorMessage: string | null;
|
|
|
|
|
|
errorStack: string | null;
|
|
|
|
|
|
ts: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|