258 lines
8.3 KiB
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;
|