feat: add PIT-aware tradable universe mask
This commit is contained in:
53
research/us_universe.py
Normal file
53
research/us_universe.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def build_tradable_mask(
|
||||
close: pd.DataFrame,
|
||||
volume: pd.DataFrame,
|
||||
pit_membership: pd.DataFrame | None,
|
||||
min_price: float,
|
||||
min_dollar_volume: float,
|
||||
min_history_days: int,
|
||||
min_valid_volume_days: int,
|
||||
liquidity_window: int = 60,
|
||||
) -> pd.DataFrame:
|
||||
"""Build a point-in-time tradable universe mask using only lagged inputs."""
|
||||
close = close.sort_index()
|
||||
volume = volume.reindex(index=close.index, columns=close.columns).sort_index()
|
||||
if pit_membership is None:
|
||||
pit_mask = pd.DataFrame(True, index=close.index, columns=close.columns)
|
||||
else:
|
||||
pit_mask = pit_membership.reindex(
|
||||
index=close.index,
|
||||
columns=close.columns,
|
||||
fill_value=False,
|
||||
)
|
||||
pit_mask = pit_mask.where(pit_mask.notna(), False).astype(bool)
|
||||
|
||||
eligible_close = close.where(pit_mask)
|
||||
eligible_volume = volume.where(pit_mask)
|
||||
|
||||
lagged_close = eligible_close.shift(1)
|
||||
lagged_volume = eligible_volume.shift(1)
|
||||
lagged_dollar_volume = lagged_close * lagged_volume
|
||||
|
||||
price_ok = lagged_close.gt(min_price)
|
||||
liquidity_ok = (
|
||||
lagged_dollar_volume.rolling(window=liquidity_window, min_periods=1).median().gt(min_dollar_volume)
|
||||
)
|
||||
history_ok = (
|
||||
lagged_close.notna()
|
||||
.rolling(window=min_history_days, min_periods=min_history_days)
|
||||
.sum()
|
||||
.ge(min_history_days)
|
||||
)
|
||||
valid_volume_ok = (
|
||||
lagged_dollar_volume.notna()
|
||||
.rolling(window=liquidity_window, min_periods=1)
|
||||
.sum()
|
||||
.ge(min_valid_volume_days)
|
||||
)
|
||||
|
||||
mask = price_ok & liquidity_ok & history_ok & valid_volume_ok
|
||||
mask = mask & pit_mask
|
||||
return mask.astype(bool)
|
||||
Reference in New Issue
Block a user