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)