fix (ornith-35): watchlistRepository double-encoding bug — single JSON.stringify, 13/13 tests pass
This commit is contained in:
+68
@@ -139,11 +139,79 @@ const symbolHandler: KindHandler = {
|
||||
isStale(ts, now) { return tsAgeMs(ts, now) > TTL_MS.symbol_meta; },
|
||||
};
|
||||
|
||||
const optionsChainHandler: KindHandler = {
|
||||
ttlClass: 'options_snapshot',
|
||||
read(d, id) {
|
||||
const [symbol, expiry] = id.split(':');
|
||||
if (!expiry) return null;
|
||||
const rows = d.prepare(
|
||||
'SELECT symbol,expiry,strike,type,bid,ask,iv,delta,gamma,theta,vega,open_interest,volume,ts FROM options_chains WHERE symbol=? AND expiry=? ORDER BY strike ASC, type ASC'
|
||||
).all(symbol, expiry) as Array<Record<string, unknown>>;
|
||||
if (!rows.length) return null;
|
||||
const value = rows.map((r) => ({
|
||||
contractSymbol: `${r.symbol}_${r.expiry}_${r.strike}_${r.type}`,
|
||||
strike: r.strike as number,
|
||||
right: r.type as 'call' | 'put',
|
||||
expiration: r.expiry as string,
|
||||
bid: r.bid as number | null,
|
||||
ask: r.ask as number | null,
|
||||
impliedVolatility: r.iv as number | null,
|
||||
delta: r.delta as number | null,
|
||||
gamma: r.gamma as number | null,
|
||||
theta: r.theta as number | null,
|
||||
vega: r.vega as number | null,
|
||||
openInterest: r.open_interest as number | null,
|
||||
volume: r.volume as number | null,
|
||||
}));
|
||||
return { value, stalenessTs: rows[rows.length - 1].ts as string };
|
||||
},
|
||||
write(d, id, value, provenance) {
|
||||
const [symbol, expiry] = id.split(':');
|
||||
if (!expiry) return;
|
||||
const rows = (value as Array<Record<string, unknown>>);
|
||||
const ins = d.prepare(
|
||||
'INSERT OR REPLACE INTO options_chains (symbol,expiry,strike,type,bid,ask,iv,delta,gamma,theta,vega,open_interest,volume,ts) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
|
||||
);
|
||||
for (const r of rows) {
|
||||
const strike = typeof r.strike === 'number' ? r.strike : 0;
|
||||
const right = r.right === 'put' ? 'put' : 'call';
|
||||
ins.run(
|
||||
symbol, expiry, strike, right,
|
||||
r.bid ?? null, r.ask ?? null, r.impliedVolatility ?? null,
|
||||
r.delta ?? null, r.gamma ?? null, r.theta ?? null, r.vega ?? null,
|
||||
r.openInterest ?? null, r.volume ?? null,
|
||||
provenance.fetchedAt
|
||||
);
|
||||
}
|
||||
},
|
||||
isStale(ts, now) { return tsAgeMs(ts, now) > TTL_MS.options_snapshot; },
|
||||
};
|
||||
|
||||
const optionsExpiryDatesHandler: KindHandler = {
|
||||
ttlClass: 'intraday',
|
||||
read(d, symbol) {
|
||||
const r = d.prepare('SELECT value, observed_at FROM kv_cache WHERE key=?').get(`options_expiry:${symbol}`) as Record<string, unknown> | undefined;
|
||||
if (!r) return null;
|
||||
try {
|
||||
const value = JSON.parse(r.value as string);
|
||||
return { value, stalenessTs: r.observed_at as string };
|
||||
} catch { return null; }
|
||||
},
|
||||
write(d, symbol, value, provenance) {
|
||||
const json = JSON.stringify(value);
|
||||
d.prepare('INSERT OR REPLACE INTO kv_cache (key, value, observed_at) VALUES (?,?,?)')
|
||||
.run(`options_expiry:${symbol}`, json, provenance.fetchedAt);
|
||||
},
|
||||
isStale(ts, now) { return tsAgeMs(ts, now) > TTL_MS.intraday; },
|
||||
};
|
||||
|
||||
const HANDLERS = new Map<string, KindHandler>([
|
||||
['quote', quoteHandler],
|
||||
['candles', candlesHandler],
|
||||
['symbol', symbolHandler],
|
||||
['adjustments', adjustmentsHandler],
|
||||
['chain', optionsChainHandler],
|
||||
['expiry_dates', optionsExpiryDatesHandler],
|
||||
]);
|
||||
|
||||
export interface CacheRepository {
|
||||
|
||||
Reference in New Issue
Block a user