89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""Daily summary data derived from raw minute bars."""
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
from pipeline.derived.base import BaseDerivedData
|
|
from pipeline.derived.registry import register_derived
|
|
|
|
|
|
@register_derived
|
|
class MinuteDailySummaryDerived(BaseDerivedData):
|
|
"""Aggregate intraday bars into daily summary columns."""
|
|
|
|
name = "minute_daily_summary"
|
|
|
|
def compute(
|
|
self,
|
|
daily: pd.DataFrame | None = None,
|
|
minute: pd.DataFrame | None = None,
|
|
) -> pd.DataFrame:
|
|
if minute is None:
|
|
raise ValueError("minute_daily_summary requires minute input")
|
|
|
|
minute = minute.copy()
|
|
minute["date"] = pd.to_datetime(minute["date"]).dt.normalize()
|
|
sort_cols = ["symbol_id", "date"]
|
|
if "datetime" in minute.columns:
|
|
minute["datetime"] = pd.to_datetime(minute["datetime"])
|
|
sort_cols.append("datetime")
|
|
elif "time" in minute.columns:
|
|
sort_cols.append("time")
|
|
minute = minute.sort_values(sort_cols)
|
|
|
|
grouped = minute.groupby(["symbol_id", "date"], sort=True)
|
|
summary = grouped.agg(
|
|
minute_bar_count=("close", "count"),
|
|
first_open=("open", "first"),
|
|
last_close=("close", "last"),
|
|
high=("high", "max"),
|
|
low=("low", "min"),
|
|
volume_sum=("volume", "sum"),
|
|
amount_sum=("amount", "sum"),
|
|
)
|
|
summary["minute_intraday_return"] = (
|
|
summary["last_close"] / summary["first_open"] - 1.0
|
|
)
|
|
summary["minute_intraday_range"] = summary["high"] / summary["low"] - 1.0
|
|
summary["minute_vwap"] = (
|
|
summary["amount_sum"] / summary["volume_sum"].where(summary["volume_sum"] > 0)
|
|
)
|
|
summary = summary.reset_index()
|
|
|
|
if daily is not None:
|
|
daily_keys = daily[["symbol_id", "date"]].copy()
|
|
daily_keys["date"] = pd.to_datetime(daily_keys["date"]).dt.normalize()
|
|
daily_keys = daily_keys.drop_duplicates(["symbol_id", "date"])
|
|
result = daily_keys.merge(summary, on=["symbol_id", "date"], how="left")
|
|
if "close" in daily.columns:
|
|
daily_close = daily[["symbol_id", "date", "close"]].copy()
|
|
daily_close["date"] = pd.to_datetime(daily_close["date"]).dt.normalize()
|
|
daily_close = daily_close.drop_duplicates(["symbol_id", "date"])
|
|
result = result.merge(
|
|
daily_close.rename(columns={"close": "daily_close"}),
|
|
on=["symbol_id", "date"],
|
|
how="left",
|
|
)
|
|
reference_close = result["daily_close"].fillna(result["last_close"])
|
|
else:
|
|
reference_close = result["last_close"]
|
|
else:
|
|
result = summary
|
|
reference_close = result["last_close"]
|
|
|
|
result["minute_vwap_deviation"] = (
|
|
result["minute_vwap"] / reference_close.replace(0.0, np.nan) - 1.0
|
|
)
|
|
return result[
|
|
[
|
|
"symbol_id",
|
|
"date",
|
|
"minute_bar_count",
|
|
"minute_intraday_return",
|
|
"minute_intraday_range",
|
|
"minute_vwap",
|
|
"minute_vwap_deviation",
|
|
]
|
|
]
|
|
|