# 1. 1시간봉을 America/New_York 타임존으로 변환하고 세션 라벨(pre, main, post) 부여
con.execute("""
CREATE OR REPLACE TABLE session_bars AS
SELECT
symbol,
timezone('America/New_York', timestamp) AS ts_ny,
CAST(timezone('America/New_York', timestamp) AS DATE) AS trade_date,
date_part('hour', timezone('America/New_York', timestamp)) AS hr,
open, high, low, close, volume,
(high + low + close) / 3.0 AS typical_price,
((high + low + close) / 3.0) * volume AS tp_vol,
CASE
WHEN date_part('hour', timezone('America/New_York', timestamp)) >= 4
AND date_part('hour', timezone('America/New_York', timestamp)) < 9 THEN 'pre'
WHEN date_part('hour', timezone('America/New_York', timestamp)) >= 9
AND date_part('hour', timezone('America/New_York', timestamp)) < 16 THEN 'main'
WHEN date_part('hour', timezone('America/New_York', timestamp)) >= 16
AND date_part('hour', timezone('America/New_York', timestamp)) < 20 THEN 'post'
ELSE 'other'
END AS session
FROM raw_bars
WHERE session != 'other';
""")
# 2. 일별/세션별 집계: Open, Close, Volume, VWAP 산출
con.execute("""
CREATE OR REPLACE TABLE daily_sessions AS
SELECT
symbol,
trade_date,
session,
arg_min(open, ts_ny) AS session_open,
arg_max(close, ts_ny) AS session_close,
SUM(volume) AS session_vol,
CASE
WHEN SUM(volume) > 0 THEN SUM(tp_vol) / SUM(volume)
ELSE arg_max(close, ts_ny)
END AS session_vwap
FROM session_bars
GROUP BY symbol, trade_date, session;
""")
# 3. 피벗 및 수익률 계산 테이블 생성
con.execute("""
CREATE OR REPLACE TABLE session_returns AS
WITH pivoted AS (
SELECT
symbol,
trade_date,
MAX(CASE WHEN session = 'pre' THEN session_open END) AS pre_open,
MAX(CASE WHEN session = 'pre' THEN session_close END) AS pre_close,
MAX(CASE WHEN session = 'pre' THEN session_vwap END) AS pre_vwap,
MAX(CASE WHEN session = 'pre' THEN session_vol END) AS pre_vol,
MAX(CASE WHEN session = 'main' THEN session_open END) AS main_open,
MAX(CASE WHEN session = 'main' THEN session_close END) AS main_close,
MAX(CASE WHEN session = 'main' THEN session_vwap END) AS main_vwap,
MAX(CASE WHEN session = 'main' THEN session_vol END) AS main_vol,
MAX(CASE WHEN session = 'post' THEN session_open END) AS post_open,
MAX(CASE WHEN session = 'post' THEN session_close END) AS post_close,
MAX(CASE WHEN session = 'post' THEN session_vwap END) AS post_vwap,
MAX(CASE WHEN session = 'post' THEN session_vol END) AS post_vol
FROM daily_sessions
GROUP BY symbol, trade_date
)
SELECT
symbol,
trade_date,
-- 가격 기반 수익률
(pre_close - pre_open) / NULLIF(pre_open, 0) AS ret_pre_price,
(main_close - pre_close) / NULLIF(pre_close, 0) AS ret_main_price,
(post_close - main_close) / NULLIF(main_close, 0) AS ret_post_price,
LAG(post_close) OVER (PARTITION BY symbol ORDER BY trade_date) AS prev_post_close,
(pre_open - LAG(post_close) OVER (PARTITION BY symbol ORDER BY trade_date))
/ NULLIF(LAG(post_close) OVER (PARTITION BY symbol ORDER BY trade_date), 0) AS ret_overnight_price,
-- VWAP 기반 수익률
LAG(post_vwap) OVER (PARTITION BY symbol ORDER BY trade_date) AS prev_post_vwap,
(pre_vwap - LAG(post_vwap) OVER (PARTITION BY symbol ORDER BY trade_date))
/ NULLIF(LAG(post_vwap) OVER (PARTITION BY symbol ORDER BY trade_date), 0) AS ret_pre_vwap,
(main_vwap - pre_vwap) / NULLIF(pre_vwap, 0) AS ret_main_vwap,
(post_vwap - main_vwap) / NULLIF(main_vwap, 0) AS ret_post_vwap,
pre_vol, main_vol, post_vol
FROM pivoted
ORDER BY symbol, trade_date;
""")
df_returns = con.execute("SELECT * FROM session_returns WHERE ret_pre_price IS NOT NULL AND ret_main_price IS NOT NULL").df()
print(f"가공 완료: 총 {len(df_returns)}행의 세션별 수익률 데이터 적재됨.")
df_returns.head(3)