// GammaDesk shared data model // ORATS-style options analytics types + Drizzle tables for auth. import { z } from "zod"; import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core"; // --------------------------------------------------------------------------- // Auth - Users table (Drizzle) // --------------------------------------------------------------------------- export const users = sqliteTable("users", { id: integer("id").primaryKey({ autoIncrement: true }), name: text("name").notNull(), email: text("email").notNull().unique(), passwordHash: text("password_hash").notNull(), createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn(() => new Date()), }); export type User = typeof users.$inferSelect; export type NewUser = typeof users.$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;