institutional-trader/backend/database/intradaysignalscorer.sql

258 lines
8.3 KiB
SQL

WITH
params AS (
SELECT
(now() AT TIME ZONE 'America/Chicago') AS now_local,
/* lookbacks */
INTERVAL '20 minutes' AS win_darkblock,
INTERVAL '90 minutes' AS win_highs,
INTERVAL '45 minutes' AS win_lows,
INTERVAL '30 minutes' AS win_stealth,
INTERVAL '20 minutes' AS win_shock,
INTERVAL '10 minutes' AS win_options,
INTERVAL '20 minutes' AS win_recent,
/* opening hour (Central) */
TIME '08:30:00' AS orb_start,
TIME '09:30:00' AS orb_end,
/* thresholds */
1000000::numeric AS per_print_min_notional, -- $1M per print
5000000::numeric AS cluster_min_notional, -- $5M cluster
150::numeric AS orb_min_rvol,
2000000::numeric AS orb_min_notional,
120::numeric AS highs_min_rvol,
3000000::numeric AS stealth_min_notional,
100::numeric AS stealth_max_rvol,
2::int AS shock_min_events,
2::int AS opt_pair_min,
2000000::numeric AS opt_min_notional,
3::int AS min_score
),
/* ---------- Normalize: build event_ts_local from alert_date + alert_time_txt ---------- */
base AS (
SELECT
symbol ::text AS ticker,
alert_type ::text AS evt_type,
message ::text AS message,
notional ::numeric AS notional,
pct_of_avg30::numeric AS pct_of_avg30,
CASE
WHEN alert_time_txt ~* '(AM|PM)\s*$'
THEN to_timestamp(to_char(alert_date,'YYYY-MM-DD') || ' ' || alert_time_txt,
'YYYY-MM-DD HH12:MI:SS AM')::timestamp
ELSE to_timestamp(to_char(alert_date,'YYYY-MM-DD') || ' ' || alert_time_txt,
'YYYY-MM-DD HH24:MI:SS')::timestamp
END AS event_ts_local
FROM public.alertstream_monthly
WHERE symbol IS NOT NULL
),
/* ---------- S1: Dark/Block cluster (liquidity magnets) ---------- */
s1_agg AS (
SELECT b.ticker,
COUNT(*) AS prints,
SUM(b.notional) AS notional_usd,
ROUND(AVG(b.pct_of_avg30),1) AS avg_rvol
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_darkblock
AND b.evt_type IN ('DarkPool','Block')
AND b.notional >= p.per_print_min_notional
GROUP BY b.ticker
),
s1 AS (
SELECT s.*
FROM s1_agg s CROSS JOIN params p
WHERE s.notional_usd >= p.cluster_min_notional
),
/* ---------- S2: ORB momentum (opening hour, today) ---------- */
s2_agg AS (
SELECT b.ticker,
COUNT(*) AS orb_triggers,
SUM(b.notional) AS notional_usd,
MAX(b.pct_of_avg30) AS rvol_peak
FROM base b
CROSS JOIN params p
WHERE DATE(b.event_ts_local) = DATE(p.now_local)
AND b.event_ts_local::time BETWEEN p.orb_start AND p.orb_end
AND b.evt_type = 'Orb'
GROUP BY b.ticker
),
s2 AS (
SELECT s.*
FROM s2_agg s CROSS JOIN params p
WHERE s.rvol_peak >= p.orb_min_rvol
AND s.notional_usd >= p.orb_min_notional
),
/* ---------- S3: 52W High + "$ volume met" confirmation ---------- */
s3_agg AS (
SELECT b.ticker,
COUNT(*) AS hits,
MAX(b.pct_of_avg30) AS rvol_peak,
SUM(CASE WHEN b.message ILIKE 'Required Dollar Volume Met%' THEN 1 ELSE 0 END) AS dollar_vol_flags
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_highs
AND b.evt_type = 'AnnualHigh'
GROUP BY b.ticker
),
s3 AS (
SELECT s.*
FROM s3_agg s CROSS JOIN params p
WHERE s.rvol_peak >= p.highs_min_rvol
AND s.dollar_vol_flags >= 1
),
/* ---------- S4: Annual Lows + volume pop (reversal watch) ---------- */
s4_lows AS (
SELECT b.ticker, COUNT(*) AS low_hits, MAX(b.pct_of_avg30) AS rvol
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_lows
AND b.evt_type = 'AnnualLow'
GROUP BY b.ticker
),
s4_volpop AS (
SELECT DISTINCT b.ticker
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_lows
AND b.evt_type IN ('VolumeActive','PriceSpike')
),
s4 AS (
SELECT l.ticker, l.low_hits, l.rvol
FROM s4_lows l
JOIN s4_volpop v ON v.ticker = l.ticker
),
/* ---------- S5: Stealth Dark (big dark, low rVol) ---------- */
s5_agg AS (
SELECT b.ticker,
COUNT(*) AS prints,
SUM(b.notional) AS notional_usd,
ROUND(AVG(b.pct_of_avg30),1) AS avg_rvol
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_stealth
AND b.evt_type = 'DarkPool'
GROUP BY b.ticker
),
s5 AS (
SELECT s.*
FROM s5_agg s CROSS JOIN params p
WHERE s.notional_usd >= p.stealth_min_notional
AND s.avg_rvol < p.stealth_max_rvol
),
/* ---------- S6: Shock tape (spikes/flushes) ---------- */
s6_agg AS (
SELECT b.ticker,
SUM(CASE WHEN b.evt_type='PriceSpike' THEN 1 ELSE 0 END)::int AS spike_cnt,
SUM(CASE WHEN b.evt_type='RapidDecline' THEN 1 ELSE 0 END)::int AS flush_cnt,
SUM(b.notional) AS notional_usd
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_shock
AND b.evt_type IN ('PriceSpike','RapidDecline')
GROUP BY b.ticker
),
s6 AS (
SELECT s.*
FROM s6_agg s CROSS JOIN params p
WHERE (s.spike_cnt + s.flush_cnt) >= p.shock_min_events
),
/* ---------- S7: Options + (Block/Dark) convergence ---------- */
s7_agg AS (
SELECT b.ticker,
SUM(CASE WHEN b.evt_type='Options' THEN 1 ELSE 0 END)::int AS opt_hits,
SUM(CASE WHEN b.evt_type='Block' THEN 1 ELSE 0 END)::int AS block_hits,
SUM(CASE WHEN b.evt_type='DarkPool' THEN 1 ELSE 0 END)::int AS dark_hits,
SUM(b.notional) AS notional_usd
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_options
GROUP BY b.ticker
),
s7 AS (
SELECT s.*
FROM s7_agg s CROSS JOIN params p
WHERE s.opt_hits >= 1
AND (s.block_hits + s.dark_hits) >= p.opt_pair_min
AND s.notional_usd >= p.opt_min_notional
),
/* ---------- Recent context & last seen ---------- */
rr AS (
SELECT b.ticker,
SUM(b.notional) AS recent_notional,
MAX(b.pct_of_avg30) AS recent_rvol_peak
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_recent
GROUP BY b.ticker
),
last_seen AS (
SELECT DISTINCT ON (b.ticker)
b.ticker,
b.event_ts_local AS last_seen_ts
FROM base b
CROSS JOIN params p
WHERE b.event_ts_local >= p.now_local - p.win_recent
ORDER BY b.ticker, b.event_ts_local DESC
),
/* ---------- Universe & Reasons ---------- */
universe AS (
SELECT ticker FROM s1
UNION SELECT ticker FROM s2
UNION SELECT ticker FROM s3
UNION SELECT ticker FROM s4
UNION SELECT ticker FROM s5
UNION SELECT ticker FROM s6
UNION SELECT ticker FROM s7
),
signals AS (
SELECT ticker, 'Dark/Block cluster' AS sig, 3 AS w FROM s1
UNION ALL SELECT ticker, 'ORB momentum' , 3 FROM s2
UNION ALL SELECT ticker, '52W High + $Vol' , 2 FROM s3
UNION ALL SELECT ticker, 'Low + VolPop' , 2 FROM s4
UNION ALL SELECT ticker, 'Stealth Dark' , 2 FROM s5
UNION ALL SELECT ticker, 'Shock Tape' , 1 FROM s6
UNION ALL SELECT ticker, 'Options + Prints' , 3 FROM s7
),
/* ---------- Scoreboard ---------- */
scored AS (
SELECT
u.ticker,
COALESCE(rr.recent_notional,0)::numeric AS recent_notional,
COALESCE(rr.recent_rvol_peak,0)::numeric AS rvol_peak,
ls.last_seen_ts AS last_seen_ts,
SUM(s.w)::int AS score,
string_agg(s.sig, ' | ' ORDER BY s.w DESC, s.sig) AS reasons
FROM universe u
LEFT JOIN signals s ON s.ticker = u.ticker
LEFT JOIN rr ON rr.ticker = u.ticker
LEFT JOIN last_seen ls ON ls.ticker = u.ticker
GROUP BY u.ticker, rr.recent_notional, rr.recent_rvol_peak, ls.last_seen_ts
)
SELECT
ticker,
score,
CASE
WHEN recent_notional >= 1e9 THEN '$' || to_char(ROUND(recent_notional/1e9,1), 'FM999999990.0') || 'B'
WHEN recent_notional >= 1e6 THEN '$' || to_char(ROUND(recent_notional/1e6,1), 'FM999999990.0') || 'M'
WHEN recent_notional >= 1e3 THEN '$' || to_char(ROUND(recent_notional/1e3,0), 'FM999999990') || 'K'
ELSE '$' || to_char(ROUND(recent_notional,0), 'FM999999990')
END AS notional_20m,
ROUND(rvol_peak,1) AS rvol_peak,
to_char(last_seen_ts, 'HH24:MI:SS') AS last_seen_ct,
reasons
FROM scored, params p
WHERE score >= p.min_score
ORDER BY score DESC, recent_notional DESC, rvol_peak DESC
LIMIT 50;