gammanexus/server/routes.ts

153 lines
4.9 KiB
TypeScript

import type { Express, Request, Response } from "express";
import { createServer } from "node:http";
import type { Server } from "node:http";
import session from "express-session";
import MemoryStore from "memorystore";
import {
SYMBOLS,
computeExpirations as mockExpirations,
computeGexProfile as mockGexProfile,
computeScreener as mockScreener,
computeSummary as mockSummary,
} from "./marketData";
import { oratsClient } from "./oratsClient";
import { registerAuthRoutes } from "./authRoutes";
import { initDb } from "./db";
const SessionMemoryStore = MemoryStore(session);
const KNOWN_TICKERS = new Set(SYMBOLS.map((s) => s.ticker));
function withTicker(req: Request, res: Response, fn: (ticker: string) => unknown) {
const raw = String(req.params.symbol || "").toUpperCase();
if (!KNOWN_TICKERS.has(raw)) {
res.status(404).json({ error: `Unknown symbol: ${raw}` });
return;
}
try {
res.json(fn(raw));
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
}
export async function registerRoutes(httpServer: Server, app: Express): Promise<Server> {
// Initialize database
await initDb();
// Session middleware
app.use(session({
store: new SessionMemoryStore({ checkPeriod: 86400000 }),
secret: process.env.SESSION_SECRET || "gammadesk-dev-secret-change-me",
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
maxAge: 24 * 60 * 60 * 1000,
},
}));
// Auth routes
registerAuthRoutes(app);
app.get("/api/symbols", (_req, res) => {
res.json(SYMBOLS);
});
app.get("/api/orats/status", (_req, res) => {
res.json({
configured: oratsClient.isConfigured(),
baseUrl: oratsClient.baseUrl,
apiKey: oratsClient.apiKey.slice(0, 8) + "...",
});
});
// Market data routes - use ORATS when configured, fall back to mocks
app.get("/api/market/:symbol/summary", async (req, res) => {
const ticker = String(req.params.symbol || "").toUpperCase();
if (!KNOWN_TICKERS.has(ticker)) {
return res.status(404).json({ error: `Unknown symbol: ${ticker}` });
}
try {
const summary = oratsClient.isConfigured()
? await oratsClient.computeSummary(ticker)
: mockSummary(ticker);
res.json(summary);
} catch (err) {
console.error(`ORATS summary error for ${ticker}:`, err);
res.json(mockSummary(ticker)); // Fallback to mock
}
});
app.get("/api/market/:symbol/gex", async (req, res) => {
const ticker = String(req.params.symbol || "").toUpperCase();
if (!KNOWN_TICKERS.has(ticker)) {
return res.status(404).json({ error: `Unknown symbol: ${ticker}` });
}
try {
const gex = oratsClient.isConfigured()
? await oratsClient.computeGexProfile(ticker)
: mockGexProfile(ticker);
res.json(gex);
} catch (err) {
console.error(`ORATS GEX error for ${ticker}:`, err);
res.json(mockGexProfile(ticker)); // Fallback to mock
}
});
app.get("/api/market/:symbol/expirations", async (req, res) => {
const ticker = String(req.params.symbol || "").toUpperCase();
if (!KNOWN_TICKERS.has(ticker)) {
return res.status(404).json({ error: `Unknown symbol: ${ticker}` });
}
try {
const exp = oratsClient.isConfigured()
? await oratsClient.computeExpirations(ticker)
: mockExpirations(ticker);
res.json(exp);
} catch (err) {
console.error(`ORATS expirations error for ${ticker}:`, err);
res.json(mockExpirations(ticker)); // Fallback to mock
}
});
app.get("/api/screener", async (_req, res) => {
try {
if (oratsClient.isConfigured()) {
// Fetch real data for all symbols
const summaries = await Promise.all(
SYMBOLS.map(s => oratsClient.computeSummary(s.ticker))
);
const screenerData = summaries.map((summary, i) => {
const symbol = SYMBOLS[i];
return {
ticker: symbol.ticker,
name: symbol.name,
spot: summary.spot,
spotChangePct: summary.spotChangePct,
netGex: summary.netGex,
gammaRegime: summary.gammaRegime,
ivRank: summary.ivRank,
ivx: summary.ivx,
callWall: summary.callWall,
putWall: summary.putWall,
distanceToCallWall: ((summary.callWall - summary.spot) / summary.spot) * 100,
distanceToPutWall: ((summary.putWall - summary.spot) / summary.spot) * 100,
expectedMovePct: summary.expectedMovePct,
skew: summary.skew,
zeroDteNetGex: summary.netGex, // Simplified for now
};
});
res.json(screenerData);
} else {
res.json(mockScreener());
}
} catch (err) {
console.error("ORATS screener error:", err);
res.json(mockScreener()); // Fallback to mock
}
});
return httpServer;
}