gammanexus/shared/schema.ts

264 lines
8.9 KiB
TypeScript

// GammaDesk shared data model
// ORATS-style options analytics types + Drizzle tables for auth.
// Database: PostgreSQL
import { z } from "zod";
import { pgTable, text, serial, timestamp, boolean } from "drizzle-orm/pg-core";
// ---------------------------------------------------------------------------
// Auth - Users table (Drizzle + PostgreSQL)
// ---------------------------------------------------------------------------
export const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
passwordHash: text("password_hash").notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
verificationToken: text("verification_token"),
verificationTokenExpiry: timestamp("verification_token_expiry", { mode: "date" }),
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
// ---------------------------------------------------------------------------
// Custom Symbols - User-added tickers stored in DB
// ---------------------------------------------------------------------------
export const customSymbols = pgTable("custom_symbols", {
id: serial("id").primaryKey(),
ticker: text("ticker").notNull().unique(),
name: text("name").notNull(),
type: text("type").default("etf").notNull(), // etf, equity, index
sector: text("sector"),
userId: text("user_id"), // null = global, otherwise tied to a user
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
});
export type CustomSymbol = typeof customSymbols.$inferSelect;
export type NewCustomSymbol = typeof customSymbols.$inferInsert;
// ---------------------------------------------------------------------------
// Symbols
// ---------------------------------------------------------------------------
export const symbolSchema = z.object({
ticker: z.string(),
name: z.string(),
type: z.enum(["index", "etf", "equity"]),
sector: z.string().optional(),
});
export type SymbolInfo = z.infer<typeof symbolSchema>;
// ---------------------------------------------------------------------------
// Summary card payload — what the Dashboard top row shows
// ---------------------------------------------------------------------------
export const summarySchema = z.object({
ticker: z.string(),
spot: z.number(),
spotChange: z.number(), // absolute $ change vs prior close
spotChangePct: z.number(),
netGex: z.number(), // signed, in $ per 1% move
gammaRegime: z.enum(["positive", "negative"]),
hvl: z.number(), // High Volume Level / gamma flip
callWall: z.number(),
putWall: z.number(),
ivRank: z.number(), // 0-100
ivx: z.number(), // implied vol index, percent
expectedMove: z.number(), // absolute $ expected move next session
expectedMovePct: z.number(),
skew: z.number(), // put skew minus call skew, percent points
asOf: z.string(), // ISO timestamp
});
export type Summary = z.infer<typeof summarySchema>;
// ---------------------------------------------------------------------------
// GEX profile — bars by strike for the gamma-by-strike visualization
// ---------------------------------------------------------------------------
export const gexBarSchema = z.object({
strike: z.number(),
callGex: z.number(), // $ gamma exposure attributable to call OI
putGex: z.number(), // negative or positive depending on sign convention
netGex: z.number(),
});
export const gexProfileSchema = z.object({
ticker: z.string(),
spot: z.number(),
hvl: z.number(),
callWall: z.number(),
putWall: z.number(),
bars: z.array(gexBarSchema),
asOf: z.string(),
});
export type GexBar = z.infer<typeof gexBarSchema>;
export type GexProfile = z.infer<typeof gexProfileSchema>;
// ---------------------------------------------------------------------------
// Expirations matrix
// ---------------------------------------------------------------------------
export const expirationRowSchema = z.object({
expiry: z.string(), // ISO date
dte: z.number(),
netGex: z.number(),
ivx: z.number(),
skew: z.number(),
expectedMove: z.number(),
expectedMovePct: z.number(),
callWall: z.number(),
putWall: z.number(),
callSkew: z.number(), // for IV/skew curve chart
putSkew: z.number(),
});
export const expirationsResponseSchema = z.object({
ticker: z.string(),
spot: z.number(),
rows: z.array(expirationRowSchema),
asOf: z.string(),
});
export type ExpirationRow = z.infer<typeof expirationRowSchema>;
export type ExpirationsResponse = z.infer<typeof expirationsResponseSchema>;
// ---------------------------------------------------------------------------
// Screener
// ---------------------------------------------------------------------------
export const screenerPresetSchema = z.enum([
"all",
"negative-gamma",
"high-iv-rank",
"near-call-wall",
"near-put-wall",
"zero-dte",
]);
export type ScreenerPreset = z.infer<typeof screenerPresetSchema>;
export const screenerRowSchema = z.object({
ticker: z.string(),
name: z.string(),
spot: z.number(),
spotChangePct: z.number(),
netGex: z.number(),
gammaRegime: z.enum(["positive", "negative"]),
ivRank: z.number(),
ivx: z.number(),
callWall: z.number(),
putWall: z.number(),
distanceToCallWall: z.number(), // % away from spot (signed: + if wall above)
distanceToPutWall: z.number(),
expectedMovePct: z.number(),
skew: z.number(),
zeroDteNetGex: z.number(),
});
export type ScreenerRow = z.infer<typeof screenerRowSchema>;
// ---------------------------------------------------------------------------
// Futures conversion: ETF/Index → E-mini futures equivalents
// ---------------------------------------------------------------------------
// ETF/Index -> Futures mapping with conversion ratio
export const futuresConversionSchema = z.object({
sourceTicker: z.string(), // e.g., "SPY", "QQQ", "SPX"
targetTicker: z.string(), // e.g., "ES", "NQ", "YM"
targetName: z.string(), // e.g., "E-mini S&P 500", "E-mini Nasdaq-100"
multiplier: z.number(), // multiply ETF price to get futures price
});
export type FuturesConversion = z.infer<typeof futuresConversionSchema>;
// Known mappings (ETF/Index price → futures price)
export const FUTURES_CONVERSIONS: FuturesConversion[] = [
{ sourceTicker: "SPY", targetTicker: "ES", targetName: "E-mini S&P 500 (ES)", multiplier: 10 },
{ sourceTicker: "SPX", targetTicker: "ES", targetName: "E-mini S&P 500 (ES)", multiplier: 1 },
{ sourceTicker: "QQQ", targetTicker: "NQ", targetName: "E-mini Nasdaq-100 (NQ)", multiplier: 20 },
{ sourceTicker: "IWM", targetTicker: "YM", targetName: "E-mini DJIA (YM)", multiplier: 10 },
];
// ---------------------------------------------------------------------------
// Gamma levels mapped to futures
// ---------------------------------------------------------------------------
export const futuresLevelSchema = z.object({
sourceTicker: z.string(),
targetTicker: z.string(),
targetName: z.string(),
sourceSpot: z.number(),
futuresSpot: z.number(),
hvl: z.number(),
callWall: z.number(),
putWall: z.number(),
sourceHvl: z.number(),
sourceCallWall: z.number(),
sourcePutWall: z.number(),
topStrikes: z.array(z.object({
sourceStrike: z.number(),
futuresStrike: z.number(),
netGex: z.number(),
magnitude: z.number(),
})),
asOf: z.string(),
});
export type FuturesLevel = z.infer<typeof futuresLevelSchema>;
// ---------------------------------------------------------------------------
// Tanuki Trades — 5-level gamma profile + extended levels
// ---------------------------------------------------------------------------
export const tanukiLevelSchema = z.object({
role: z.string(), // "Key Resistance", "Minor Resistance", "Gamma Flip", "Minor Support", "Key Support"
strike: z.number(),
label: z.string(), // "C1", "cTrans", "HVL", "pTrans", "P1"
});
export const tanukiExtendedLevelSchema = z.object({
label: z.string(), // "C2", "C3", "P2", "P3"
strike: z.number().optional(),
});
export const tanukiRegimeSchema = z.enum([
"Positive Gamma",
"Negative Gamma",
"Positive Extension",
"Transition",
"Negative Transition",
]);
export const tanukiLevelsSchema = z.object({
symbol: z.string(),
spotPrice: z.number(),
expiry: z.string(), // ISO date
expiryDte: z.number(),
regime: tanukiRegimeSchema,
updatedAt: z.string(), // ISO timestamp
levels: z.array(tanukiLevelSchema),
extendedLevels: z.array(tanukiExtendedLevelSchema),
netGex: z.number(),
netDex: z.number(),
});
export type TanukiLevel = z.infer<typeof tanukiLevelSchema>;
export type TanukiExtendedLevel = z.infer<typeof tanukiExtendedLevelSchema>;
export type TanukiRegime = z.infer<typeof tanukiRegimeSchema>;
export type TanukiLevels = z.infer<typeof tanukiLevelsSchema>;
// Tanuki config stored server-side
export const tanukiConfigSchema = z.object({
email: z.string().email(),
tvUser: z.string(),
});
export type TanukiConfig = z.infer<typeof tanukiConfigSchema>;