54 lines
1.7 KiB
Python
54 lines
1.7 KiB
Python
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)
|