Expand synthetic data across S&P 500

Add S&P 500 symbol list and expand synthetic options/equities feeds with varied contracts, sizes, and bursts to keep alerts firing.
This commit is contained in:
dirtydishes 2025-12-29 19:09:15 -05:00
parent 41bdd2c73a
commit 7996d00677
4 changed files with 643 additions and 26 deletions

View file

@ -1 +1,2 @@
export * from "./events"; export * from "./events";
export * from "./sp500";

507
packages/types/src/sp500.ts Normal file
View file

@ -0,0 +1,507 @@
export const SP500_SYMBOLS = [
"MMM",
"AOS",
"ABT",
"ABBV",
"ACN",
"ADBE",
"AMD",
"AES",
"AFL",
"A",
"APD",
"ABNB",
"AKAM",
"ALB",
"ARE",
"ALGN",
"ALLE",
"LNT",
"ALL",
"GOOGL",
"GOOG",
"MO",
"AMZN",
"AMCR",
"AEE",
"AEP",
"AXP",
"AIG",
"AMT",
"AWK",
"AMP",
"AME",
"AMGN",
"APH",
"ADI",
"AON",
"APA",
"APO",
"AAPL",
"AMAT",
"APTV",
"ACGL",
"ADM",
"ANET",
"AJG",
"AIZ",
"T",
"ATO",
"ADSK",
"ADP",
"AZO",
"AVB",
"AVY",
"AXON",
"BKR",
"BALL",
"BAC",
"BAX",
"BDX",
"BRK.B",
"BBY",
"TECH",
"BIIB",
"BLK",
"BX",
"XYZ",
"BK",
"BA",
"BKNG",
"BSX",
"BMY",
"AVGO",
"BR",
"BRO",
"BF.B",
"BLDR",
"BG",
"BXP",
"CHRW",
"CDNS",
"CZR",
"CPT",
"CPB",
"COF",
"CAH",
"KMX",
"CCL",
"CARR",
"CAT",
"CBOE",
"CBRE",
"CDW",
"COR",
"CNC",
"CNP",
"CF",
"CRL",
"SCHW",
"CHTR",
"CVX",
"CMG",
"CB",
"CHD",
"CI",
"CINF",
"CTAS",
"CSCO",
"C",
"CFG",
"CLX",
"CME",
"CMS",
"KO",
"CTSH",
"COIN",
"CL",
"CMCSA",
"CAG",
"COP",
"ED",
"STZ",
"CEG",
"COO",
"CPRT",
"GLW",
"CPAY",
"CTVA",
"CSGP",
"COST",
"CTRA",
"CRWD",
"CCI",
"CSX",
"CMI",
"CVS",
"DHR",
"DRI",
"DDOG",
"DVA",
"DAY",
"DECK",
"DE",
"DELL",
"DAL",
"DVN",
"DXCM",
"FANG",
"DLR",
"DG",
"DLTR",
"D",
"DPZ",
"DASH",
"DOV",
"DOW",
"DHI",
"DTE",
"DUK",
"DD",
"EMN",
"ETN",
"EBAY",
"ECL",
"EIX",
"EW",
"EA",
"ELV",
"EMR",
"ENPH",
"ETR",
"EOG",
"EPAM",
"EQT",
"EFX",
"EQIX",
"EQR",
"ERIE",
"ESS",
"EL",
"EG",
"EVRG",
"ES",
"EXC",
"EXE",
"EXPE",
"EXPD",
"EXR",
"XOM",
"FFIV",
"FDS",
"FICO",
"FAST",
"FRT",
"FDX",
"FIS",
"FITB",
"FSLR",
"FE",
"FI",
"F",
"FTNT",
"FTV",
"FOXA",
"FOX",
"BEN",
"FCX",
"GRMN",
"IT",
"GE",
"GEHC",
"GEV",
"GEN",
"GNRC",
"GD",
"GIS",
"GM",
"GPC",
"GILD",
"GPN",
"GL",
"GDDY",
"GS",
"HAL",
"HIG",
"HAS",
"HCA",
"DOC",
"HSIC",
"HSY",
"HPE",
"HLT",
"HOLX",
"HD",
"HON",
"HRL",
"HST",
"HWM",
"HPQ",
"HUBB",
"HUM",
"HBAN",
"HII",
"IBM",
"IEX",
"IDXX",
"ITW",
"INCY",
"IR",
"PODD",
"INTC",
"ICE",
"IFF",
"IP",
"IPG",
"INTU",
"ISRG",
"IVZ",
"INVH",
"IQV",
"IRM",
"JBHT",
"JBL",
"JKHY",
"J",
"JNJ",
"JCI",
"JPM",
"K",
"KVUE",
"KDP",
"KEY",
"KEYS",
"KMB",
"KIM",
"KMI",
"KKR",
"KLAC",
"KHC",
"KR",
"LHX",
"LH",
"LRCX",
"LW",
"LVS",
"LDOS",
"LEN",
"LII",
"LLY",
"LIN",
"LYV",
"LKQ",
"LMT",
"L",
"LOW",
"LULU",
"LYB",
"MTB",
"MPC",
"MKTX",
"MAR",
"MMC",
"MLM",
"MAS",
"MA",
"MTCH",
"MKC",
"MCD",
"MCK",
"MDT",
"MRK",
"META",
"MET",
"MTD",
"MGM",
"MCHP",
"MU",
"MSFT",
"MAA",
"MRNA",
"MHK",
"MOH",
"TAP",
"MDLZ",
"MPWR",
"MNST",
"MCO",
"MS",
"MOS",
"MSI",
"MSCI",
"NDAQ",
"NTAP",
"NFLX",
"NEM",
"NWSA",
"NWS",
"NEE",
"NKE",
"NI",
"NDSN",
"NSC",
"NTRS",
"NOC",
"NCLH",
"NRG",
"NUE",
"NVDA",
"NVR",
"NXPI",
"ORLY",
"OXY",
"ODFL",
"OMC",
"ON",
"OKE",
"ORCL",
"OTIS",
"PCAR",
"PKG",
"PLTR",
"PANW",
"PSKY",
"PH",
"PAYX",
"PAYC",
"PYPL",
"PNR",
"PEP",
"PFE",
"PCG",
"PM",
"PSX",
"PNW",
"PNC",
"POOL",
"PPG",
"PPL",
"PFG",
"PG",
"PGR",
"PLD",
"PRU",
"PEG",
"PTC",
"PSA",
"PHM",
"PWR",
"QCOM",
"DGX",
"RL",
"RJF",
"RTX",
"O",
"REG",
"REGN",
"RF",
"RSG",
"RMD",
"RVTY",
"ROK",
"ROL",
"ROP",
"ROST",
"RCL",
"SPGI",
"CRM",
"SBAC",
"SLB",
"STX",
"SRE",
"NOW",
"SHW",
"SPG",
"SWKS",
"SJM",
"SW",
"SNA",
"SOLV",
"SO",
"LUV",
"SWK",
"SBUX",
"STT",
"STLD",
"STE",
"SYK",
"SMCI",
"SYF",
"SNPS",
"SYY",
"TMUS",
"TROW",
"TTWO",
"TPR",
"TRGP",
"TGT",
"TEL",
"TDY",
"TER",
"TSLA",
"TXN",
"TPL",
"TXT",
"TMO",
"TJX",
"TKO",
"TTD",
"TSCO",
"TT",
"TDG",
"TRV",
"TRMB",
"TFC",
"TYL",
"TSN",
"USB",
"UBER",
"UDR",
"ULTA",
"UNP",
"UAL",
"UPS",
"URI",
"UNH",
"UHS",
"VLO",
"VTR",
"VLTO",
"VRSN",
"VRSK",
"VZ",
"VRTX",
"VTRS",
"VICI",
"V",
"VST",
"VMC",
"WRB",
"GWW",
"WAB",
"WBA",
"WMT",
"DIS",
"WBD",
"WM",
"WAT",
"WEC",
"WFC",
"WELL",
"WST",
"WDC",
"WY",
"WSM",
"WMB",
"WTW",
"WDAY",
"WYNN",
"XEL",
"XYL",
"YUM",
"ZBRA",
"ZBH",
"ZTS",
] as const;
export type Sp500Symbol = typeof SP500_SYMBOLS[number];

View file

@ -1,22 +1,40 @@
import type { EquityPrint } from "@islandflow/types"; import { SP500_SYMBOLS, type EquityPrint } from "@islandflow/types";
import type { EquityIngestAdapter, EquityIngestHandlers } from "./types"; import type { EquityIngestAdapter, EquityIngestHandlers } from "./types";
type SyntheticEquitiesAdapterConfig = { type SyntheticEquitiesAdapterConfig = {
emitIntervalMs: number; emitIntervalMs: number;
}; };
const buildSyntheticPrint = (seq: number, now: number): EquityPrint => { const EXCHANGES = ["NYSE", "NASDAQ", "ARCA", "BATS", "IEX", "TEST"];
const hashSymbol = (value: string): number => {
let hash = 0;
for (let i = 0; i < value.length; i += 1) {
hash = (hash * 31 + value.charCodeAt(i)) >>> 0;
}
return hash;
};
const buildSyntheticPrint = (
seq: number,
now: number,
symbol: string,
price: number,
size: number,
exchange: string,
offExchangeFlag: boolean
): EquityPrint => {
return { return {
source_ts: now, source_ts: now,
ingest_ts: now, ingest_ts: now,
seq, seq,
trace_id: `ingest-equities-${seq}`, trace_id: `synthetic-equities-${seq}`,
ts: now, ts: now,
underlying_id: "SPY", underlying_id: symbol,
price: 450.1, price,
size: 100, size,
exchange: "TEST", exchange,
offExchangeFlag: false offExchangeFlag
}; };
}; };
@ -35,10 +53,21 @@ export const createSyntheticEquitiesAdapter = (
return; return;
} }
seq += 1;
const now = Date.now(); const now = Date.now();
const print = buildSyntheticPrint(seq, now); const batchSize = 3;
void handlers.onTrade(print);
for (let i = 0; i < batchSize; i += 1) {
seq += 1;
const symbol = SP500_SYMBOLS[(seq + i) % SP500_SYMBOLS.length];
const symbolHash = hashSymbol(symbol);
const basePrice = 25 + (symbolHash % 475);
const price = Number((basePrice + ((seq % 40) - 20) * 0.05).toFixed(2));
const size = 10 + (seq % 600);
const exchange = EXCHANGES[(seq + symbolHash) % EXCHANGES.length];
const offExchangeFlag = (seq + i) % 6 === 0;
const print = buildSyntheticPrint(seq, now + i * 4, symbol, price, size, exchange, offExchangeFlag);
void handlers.onTrade(print);
}
}; };
timer = setInterval(emit, config.emitIntervalMs); timer = setInterval(emit, config.emitIntervalMs);

View file

@ -1,22 +1,72 @@
import type { OptionPrint } from "@islandflow/types"; import { SP500_SYMBOLS, type OptionPrint } from "@islandflow/types";
import type { OptionIngestAdapter, OptionIngestHandlers } from "./types"; import type { OptionIngestAdapter, OptionIngestHandlers } from "./types";
type SyntheticOptionsAdapterConfig = { type SyntheticOptionsAdapterConfig = {
emitIntervalMs: number; emitIntervalMs: number;
}; };
const buildSyntheticPrint = (seq: number, now: number): OptionPrint => { type Burst = {
contractId: string;
basePrice: number;
baseSize: number;
exchange: string;
conditions?: string[];
burstSize: number;
};
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const EXPIRY_OFFSETS = [7, 14, 28, 45, 60, 90];
const EXCHANGES = ["CBOE", "PHLX", "ISE", "ARCA", "BOX", "MIAX"];
const CONDITIONS = ["SWEEP", "ISO", "FILL", "TEST"];
const pick = <T,>(items: T[], seed: number): T => {
return items[Math.abs(seed) % items.length];
};
const hashSymbol = (value: string): number => {
let hash = 0;
for (let i = 0; i < value.length; i += 1) {
hash = (hash * 31 + value.charCodeAt(i)) >>> 0;
}
return hash;
};
const formatStrike = (strike: number): string => {
const fixed = strike.toFixed(3);
return fixed.replace(/\.?0+$/, "");
};
const formatExpiry = (now: number, offsetDays: number): string => {
const expiryDate = new Date(now + offsetDays * MS_PER_DAY);
return expiryDate.toISOString().slice(0, 10);
};
const buildBurst = (burstIndex: number, now: number): Burst => {
const symbol = SP500_SYMBOLS[burstIndex % SP500_SYMBOLS.length];
const symbolHash = hashSymbol(symbol);
const basePrice = 30 + (symbolHash % 470);
const expiryOffset = pick(EXPIRY_OFFSETS, symbolHash + burstIndex);
const expiry = formatExpiry(now, expiryOffset);
const strikeStep = basePrice >= 200 ? 10 : 5;
const strikeOffset = ((burstIndex % 7) - 3) * strikeStep;
const strike = Math.max(1, Math.round(basePrice / strikeStep) * strikeStep + strikeOffset);
const right = burstIndex % 2 === 0 ? "C" : "P";
const contractId = `${symbol}-${expiry}-${formatStrike(strike)}-${right}`;
const exchange = pick(EXCHANGES, burstIndex + symbolHash);
const isBlock = burstIndex % 4 === 0;
const burstSize = isBlock ? 4 : burstIndex % 3 === 0 ? 2 : 1;
const baseSize = isBlock ? 1200 + (symbolHash % 1800) : 5 + (symbolHash % 180);
const distance = Math.abs(strike - basePrice);
const basePricePer = isBlock ? 12 + distance / strikeStep : 0.5 + distance / 30;
const conditions = isBlock ? [pick(CONDITIONS, burstIndex)] : undefined;
return { return {
source_ts: now, contractId,
ingest_ts: now, basePrice: basePricePer,
seq, baseSize,
trace_id: `ingest-options-${seq}`, exchange,
ts: now, conditions,
option_contract_id: "SPY-2025-01-17-450-C", burstSize
price: 1.25,
size: 10,
exchange: "TEST",
conditions: ["TEST"]
}; };
}; };
@ -27,6 +77,9 @@ export const createSyntheticOptionsAdapter = (
name: "synthetic", name: "synthetic",
start: (handlers: OptionIngestHandlers) => { start: (handlers: OptionIngestHandlers) => {
let seq = 0; let seq = 0;
let burstIndex = 0;
let currentBurst: Burst | null = null;
let remaining = 0;
let timer: ReturnType<typeof setInterval> | null = null; let timer: ReturnType<typeof setInterval> | null = null;
let stopped = false; let stopped = false;
@ -35,10 +88,37 @@ export const createSyntheticOptionsAdapter = (
return; return;
} }
seq += 1;
const now = Date.now(); const now = Date.now();
const print = buildSyntheticPrint(seq, now); if (!currentBurst || remaining <= 0) {
void handlers.onTrade(print); burstIndex += 1;
currentBurst = buildBurst(burstIndex, now);
remaining = currentBurst.burstSize;
}
const burst = currentBurst;
const printsToEmit = remaining;
for (let i = 0; i < printsToEmit; i += 1) {
seq += 1;
const priceJitter = (i % 3) - 1;
const sizeJitter = (i % 4) - 1;
const print: OptionPrint = {
source_ts: now + i * 5,
ingest_ts: now + i * 5,
seq,
trace_id: `synthetic-options-${seq}`,
ts: now + i * 5,
option_contract_id: burst.contractId,
price: Math.max(0.05, Number((burst.basePrice * (1 + priceJitter * 0.02)).toFixed(2))),
size: Math.max(1, Math.round(burst.baseSize * (1 + sizeJitter * 0.05))),
exchange: burst.exchange,
conditions: burst.conditions
};
void handlers.onTrade(print);
}
remaining = 0;
}; };
timer = setInterval(emit, config.emitIntervalMs); timer = setInterval(emit, config.emitIntervalMs);