forecasting-v3-checkpoints / source /project /data /reference_level_features.py
tmmycruise's picture
Publish final forecasting-v3 three-seed checkpoint bundle
d31670b verified
Raw
History Blame Contribute Delete
12.8 kB
"""Causal anchor-time historical-reference features for forecasting."""
from __future__ import annotations
from collections import deque
from dataclasses import dataclass
import numpy as np
REFERENCE_WINDOWS = (1, 5, 20)
WINDOW_FEATURES = (
"distance_to_peak_bps",
"distance_to_bottom_bps",
"range_width_bps",
"range_position",
"available",
)
REFERENCE_FEATURE_NAMES = tuple(
f"prior_{window}_session_{feature}"
for window in REFERENCE_WINDOWS
for feature in WINDOW_FEATURES
)
SESSION_FEATURE_NAMES = (
"distance_to_session_open_bps",
"distance_to_session_max_close_so_far_bps",
"distance_to_session_min_close_so_far_bps",
"session_range_position_so_far",
"session_history_available",
)
GENERIC_WINDOW_FEATURES = (
"net_return_bps",
"rms_observed_return_bps",
"range_width_bps",
"terminal_drawdown_bps",
"available",
)
GENERIC_FEATURE_NAMES = tuple(
f"prior_{window}_session_{feature}"
for window in REFERENCE_WINDOWS
for feature in GENERIC_WINDOW_FEATURES
)
VISIBLE_FEATURE_NAMES = (
"visible_128_distance_to_peak_bps",
"visible_128_distance_to_bottom_bps",
"visible_128_range_width_bps",
"visible_128_range_position",
"visible_128_history_available",
)
REFERENCE_TREATMENTS = (
"P0",
"Pmask",
"P1",
"Pmulti",
"Pgeneric",
"P128",
"Pstale",
"Psession",
)
@dataclass(frozen=True)
class ReferenceFeatureBundle:
reference_values: np.ndarray
generic_values: np.ndarray
stale_reference_values: np.ndarray
visible_values: np.ndarray
session_values: np.ndarray
relative_log_close: np.ndarray
def __post_init__(self) -> None:
row_count = len(self.relative_log_close)
if self.reference_values.shape != (
row_count,
len(REFERENCE_FEATURE_NAMES),
):
raise ValueError("reference feature shape is invalid")
if self.generic_values.shape != (
row_count,
len(GENERIC_FEATURE_NAMES),
):
raise ValueError("generic feature shape is invalid")
if self.stale_reference_values.shape != (
row_count,
len(REFERENCE_FEATURE_NAMES),
):
raise ValueError("stale reference feature shape is invalid")
if self.visible_values.shape != (
row_count,
len(VISIBLE_FEATURE_NAMES),
):
raise ValueError("visible feature shape is invalid")
if self.session_values.shape != (
row_count,
len(SESSION_FEATURE_NAMES),
):
raise ValueError("session feature shape is invalid")
if not (
np.isfinite(self.reference_values).all()
and np.isfinite(self.generic_values).all()
and np.isfinite(self.stale_reference_values).all()
and np.isfinite(self.visible_values).all()
and np.isfinite(self.session_values).all()
and np.isfinite(self.relative_log_close).all()
):
raise ValueError("reference feature bundle contains nonfinite values")
def _relative_log_close(features: dict[str, np.ndarray]) -> np.ndarray:
values = np.asarray(features["X"])
if values.ndim != 2 or values.shape[1] < 2:
raise ValueError("token feature matrix is invalid")
returns = values[:, 0].astype(np.float64, copy=False)
observed = values[:, 1] > 0.5
if not np.isfinite(returns).all():
raise ValueError("close-return stream contains nonfinite values")
if np.any((~observed) & (returns != 0.0)):
raise ValueError("unobserved rows contain nonzero close returns")
return np.cumsum(returns, dtype=np.float64)
def _session_runs(session_date: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
dates = np.asarray(session_date)
if dates.ndim != 1 or not len(dates):
raise ValueError("session dates must be a nonempty vector")
changes = np.flatnonzero(dates[1:] != dates[:-1]) + 1
starts = np.concatenate(([0], changes)).astype(np.int64)
stops = np.concatenate((changes, [len(dates)])).astype(np.int64)
labels = dates[starts]
if np.any(labels[1:] <= labels[:-1]):
raise ValueError("session dates are not strictly increasing")
return starts, stops
def _geometry(
anchor: np.ndarray,
peak: float,
bottom: float,
) -> np.ndarray:
width = float(peak - bottom)
denominator = max(width, np.finfo(np.float64).eps)
return np.column_stack(
(
10_000.0 * (anchor - peak),
10_000.0 * (anchor - bottom),
np.full(len(anchor), 10_000.0 * width),
(anchor - bottom) / denominator,
)
)
def _generic_summary(values: np.ndarray) -> np.ndarray:
observed_returns = np.diff(values)
rms_return = (
float(np.sqrt(np.mean(np.square(observed_returns))))
if len(observed_returns)
else 0.0
)
peak = float(np.max(values))
bottom = float(np.min(values))
return np.array(
(
10_000.0 * float(values[-1] - values[0]),
10_000.0 * rms_return,
10_000.0 * (peak - bottom),
10_000.0 * float(values[-1] - peak),
),
dtype=np.float64,
)
def _visible_geometry(
relative_close: np.ndarray,
observed: np.ndarray,
*,
context_length: int = 128,
) -> np.ndarray:
result = np.zeros(
(len(relative_close), len(VISIBLE_FEATURE_NAMES)),
dtype=np.float64,
)
maximum: deque[int] = deque()
minimum: deque[int] = deque()
for index, value in enumerate(relative_close):
first = index + 1 - context_length
while maximum and maximum[0] < first:
maximum.popleft()
while minimum and minimum[0] < first:
minimum.popleft()
if not observed[index]:
continue
while maximum and relative_close[maximum[-1]] <= value:
maximum.pop()
while minimum and relative_close[minimum[-1]] >= value:
minimum.pop()
maximum.append(index)
minimum.append(index)
peak = float(relative_close[maximum[0]])
bottom = float(relative_close[minimum[0]])
result[index, :4] = _geometry(
np.asarray([value]),
peak,
bottom,
)[0]
result[index, 4] = 1.0
return result
def materialize_reference_features(
features: dict[str, np.ndarray],
) -> ReferenceFeatureBundle:
"""Build close-only features available at each completed minute anchor."""
relative_close = _relative_log_close(features)
dates = np.asarray(features["session_date"])
minute = np.asarray(features["minute_of_session"])
if len(dates) != len(relative_close) or len(minute) != len(relative_close):
raise ValueError("feature row metadata has inconsistent lengths")
observed = np.asarray(features["X"])[:, 1] > 0.5
starts, stops = _session_runs(dates)
session_peak = np.full(len(starts), np.nan, dtype=np.float64)
session_bottom = np.full(len(starts), np.nan, dtype=np.float64)
session_available = np.zeros(len(starts), dtype=np.bool_)
for index, (start, stop) in enumerate(zip(starts, stops, strict=True)):
local = observed[start:stop]
if np.any(local):
values = relative_close[start:stop][local]
session_peak[index] = float(np.max(values))
session_bottom[index] = float(np.min(values))
session_available[index] = True
reference = np.zeros(
(len(relative_close), len(REFERENCE_FEATURE_NAMES)),
dtype=np.float64,
)
generic = np.zeros(
(len(relative_close), len(GENERIC_FEATURE_NAMES)),
dtype=np.float64,
)
stale_reference = np.zeros_like(reference)
visible = _visible_geometry(relative_close, observed)
session = np.zeros(
(len(relative_close), len(SESSION_FEATURE_NAMES)),
dtype=np.float64,
)
for session_index, (start, stop) in enumerate(
zip(starts, stops, strict=True)
):
local_observed = observed[start:stop]
if np.any(local_observed):
rows = np.arange(start, stop)[local_observed]
values = relative_close[rows]
running_peak = np.maximum.accumulate(values)
running_bottom = np.minimum.accumulate(values)
opening = values[0]
width = running_peak - running_bottom
session[rows, :4] = np.column_stack(
(
10_000.0 * (values - opening),
10_000.0 * (values - running_peak),
10_000.0 * (values - running_bottom),
(values - running_bottom)
/ np.maximum(width, np.finfo(np.float64).eps),
)
)
session[rows, 4] = 1.0
for window_index, window in enumerate(REFERENCE_WINDOWS):
first = session_index - window
if first < 0:
continue
window_available = session_available[first:session_index]
if not np.all(window_available) or not np.any(local_observed):
continue
peak = float(np.max(session_peak[first:session_index]))
bottom = float(np.min(session_bottom[first:session_index]))
rows = np.arange(start, stop)[local_observed]
offset = window_index * len(WINDOW_FEATURES)
reference[rows, offset : offset + 4] = _geometry(
relative_close[rows],
peak,
bottom,
)
reference[rows, offset + 4] = 1.0
source_start = starts[first]
source_stop = stops[session_index - 1]
source_observed = observed[source_start:source_stop]
source_values = relative_close[source_start:source_stop][
source_observed
]
generic[rows, offset : offset + 4] = _generic_summary(
source_values
)
generic[rows, offset + 4] = 1.0
stale_first = first - 1
stale_stop = session_index - 1
if stale_first < 0:
continue
stale_available = session_available[stale_first:stale_stop]
if not np.all(stale_available):
continue
stale_peak = float(
np.max(session_peak[stale_first:stale_stop])
)
stale_bottom = float(
np.min(session_bottom[stale_first:stale_stop])
)
stale_reference[rows, offset : offset + 4] = _geometry(
relative_close[rows],
stale_peak,
stale_bottom,
)
stale_reference[rows, offset + 4] = 1.0
return ReferenceFeatureBundle(
reference_values=reference.astype(np.float32),
generic_values=generic.astype(np.float32),
stale_reference_values=stale_reference.astype(np.float32),
visible_values=visible.astype(np.float32),
session_values=session.astype(np.float32),
relative_log_close=relative_close,
)
def treatment_values(
bundle: ReferenceFeatureBundle,
treatment: str,
) -> tuple[np.ndarray, tuple[str, ...]]:
if treatment not in REFERENCE_TREATMENTS:
raise ValueError(f"unknown reference treatment: {treatment}")
if treatment == "Psession":
return bundle.session_values.copy(), SESSION_FEATURE_NAMES
if treatment == "P128":
return bundle.visible_values.copy(), VISIBLE_FEATURE_NAMES
if treatment == "Pgeneric":
return bundle.generic_values.copy(), GENERIC_FEATURE_NAMES
if treatment == "Pstale":
return (
bundle.stale_reference_values.copy(),
REFERENCE_FEATURE_NAMES,
)
if treatment == "P1":
width = len(WINDOW_FEATURES)
return (
bundle.reference_values[:, :width].copy(),
REFERENCE_FEATURE_NAMES[:width],
)
values = np.zeros_like(bundle.reference_values)
if treatment == "Pmask":
mask_indices = np.arange(
len(WINDOW_FEATURES) - 1,
len(REFERENCE_FEATURE_NAMES),
len(WINDOW_FEATURES),
)
values[:, mask_indices] = bundle.reference_values[:, mask_indices]
elif treatment == "Pmulti":
values[:] = bundle.reference_values
return values, REFERENCE_FEATURE_NAMES
__all__ = [
"GENERIC_FEATURE_NAMES",
"REFERENCE_FEATURE_NAMES",
"REFERENCE_TREATMENTS",
"REFERENCE_WINDOWS",
"SESSION_FEATURE_NAMES",
"VISIBLE_FEATURE_NAMES",
"ReferenceFeatureBundle",
"materialize_reference_features",
"treatment_values",
]