gammanexus/shared/schema.ts

146 lines
4.6 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, pgEnum } 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(),
createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
});
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<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>;