Files
momentum-system/research/query.py
T
momentum devandClaude Opus 5.5 81e505b845 fix: verify squeezes on a median volume baseline and ingest from the premarket
KITT on 2026-09-25 (+557%, 9.3M shares, the day's biggest mover) was benched
as "gap 557% squeeze unverified (rvol 1.54)". Two separate faults:

1. The rvol baseline was the 20-day MEAN, and two 11-14M-share days on
   09-01/09-02 put it at 1.66M against a 333k median. Squeeze verification
   (gap >= 150% needs rvol >= 5) now takes the stronger of the mean-baseline
   reading and the median-baseline one. Both are cumulative volume over a
   full day's baseline, not prorated, so they share the units the rvol >= 5
   boundary was calibrated in. A reverse-split fake still reads ~0.01 on
   both. This applies in the watcher pre-filter, the trigger book's squeeze
   exception and the fast-path population gate. The ranking and the tod-rvol
   arm signal are unchanged.

2. A name's first ingest reached back only to ~09:15 (09:30 + 15 min, or
   the 20-minute refresh floor). KITT was found at 09:35, so its stored day
   began at 09:13 and missed the 09:00 squeeze and ~6M premarket shares.
   The first pass now covers the session from 04:00 ET.

With both fixed, KITT's volume at 09:35 is ~20x its median day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:08:13 -05:00

100 lines
3.1 KiB
Python

"""Read-side queries over the research market database."""
from __future__ import annotations
from datetime import date
import pandas as pd
def query_session_symbols(
con,
session_date: date,
price_min: float = 1.0,
price_max: float = 20.0,
limit: int = 50,
) -> list[dict]:
"""Symbols with minute bars today whose latest close is inside the band.
Pass price_min=0 / price_max=inf to disable the band.
"""
rows = con.execute(
"""
WITH latest AS (
SELECT symbol, close, volume,
ROW_NUMBER() OVER (
PARTITION BY symbol ORDER BY timestamp DESC
) AS rn,
COUNT(*) OVER (PARTITION BY symbol) AS bar_count
FROM minute_bars
WHERE session_date = ?
)
SELECT symbol, close AS last_price, bar_count
FROM latest
WHERE rn = 1 AND close BETWEEN ? AND ?
ORDER BY bar_count DESC
LIMIT ?
""",
[session_date, price_min, price_max, limit],
).fetchall()
return [
{"symbol": r[0], "last_price": float(r[1]), "bar_count": int(r[2])}
for r in rows
]
def query_minute_bars(con, symbol: str, session_date: date) -> pd.DataFrame:
"""All of today's minute bars for a symbol, chronological."""
return con.execute(
"""
SELECT symbol, timestamp, session_date, is_premarket, is_regular_hours,
open, high, low, close, volume, vwap, quality_score
FROM minute_bars
WHERE symbol = ? AND session_date = ?
ORDER BY timestamp ASC
""",
[symbol, session_date],
).df()
def query_previous_close(con, symbol: str, session_date: date) -> float | None:
row = con.execute(
"""
SELECT close FROM daily_bars
WHERE symbol = ? AND trade_date < ?
ORDER BY trade_date DESC LIMIT 1
""",
[symbol, session_date],
).fetchone()
return float(row[0]) if row else None
def query_median_daily_volume(con, symbol: str, session_date: date, days: int = 20) -> float | None:
"""Median of the last ``days`` daily volumes — the spike-robust baseline for
squeeze verification (one or two prior squeeze days can multiply the mean)."""
row = con.execute(
"""
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY volume) FROM (
SELECT volume FROM daily_bars
WHERE symbol = ? AND trade_date < ?
ORDER BY trade_date DESC LIMIT ?
) recent
""",
[symbol, session_date, days],
).fetchone()
return float(row[0]) if row and row[0] is not None else None
def query_avg_daily_volume(con, symbol: str, session_date: date, days: int = 20) -> float | None:
row = con.execute(
"""
SELECT AVG(volume) FROM (
SELECT volume FROM daily_bars
WHERE symbol = ? AND trade_date < ?
ORDER BY trade_date DESC LIMIT ?
)
""",
[symbol, session_date, days],
).fetchone()
return float(row[0]) if row and row[0] is not None else None