// 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; // --------------------------------------------------------------------------- // 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; // --------------------------------------------------------------------------- // 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; export type GexProfile = z.infer; // --------------------------------------------------------------------------- // 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; export type ExpirationsResponse = z.infer; // --------------------------------------------------------------------------- // 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; 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; // --------------------------------------------------------------------------- // 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; // 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; // --------------------------------------------------------------------------- // 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; export type TanukiExtendedLevel = z.infer; export type TanukiRegime = z.infer; export type TanukiLevels = z.infer; // Tanuki config stored server-side export const tanukiConfigSchema = z.object({ email: z.string().email(), tvUser: z.string(), }); export type TanukiConfig = z.infer;