Evaluate weights against next-period returns to avoid look-ahead
Weights formed from close[t] now earn the t→t+1 return: forward returns are computed on the full market calendar before selecting signal dates, so a sparse signal grid earns the next available return rather than the next signal date, and the final signal date (no forward return) is dropped. Signal pct_change uses fill_method=None so suspended names do not inherit stale prices. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+79
-2
@@ -51,7 +51,7 @@ def test_reversal_sign_matches_negative_trailing_return():
|
||||
data = _make_data()
|
||||
alpha = compute_alpha(data, "rev5", "reversal", lookback=5)
|
||||
close = data.pivot_table(index="date", columns="symbol_id", values="close").sort_index()
|
||||
raw = -close.pct_change(5)
|
||||
raw = -close.pct_change(5, fill_method=None)
|
||||
last = raw.index[-1]
|
||||
expected_top = raw.loc[last].idxmax()
|
||||
got = alpha[alpha["date"] == last].set_index("symbol_id")["weight"].idxmax()
|
||||
@@ -74,6 +74,83 @@ def test_evaluate_alpha_keys():
|
||||
assert key in metrics
|
||||
|
||||
|
||||
def test_evaluate_alpha_uses_next_period_returns():
|
||||
dates = pd.date_range("2024-01-01", periods=4)
|
||||
data = pd.concat([
|
||||
pd.DataFrame({
|
||||
"symbol_id": "sh600000",
|
||||
"symbol_name": "sh600000",
|
||||
"date": dates,
|
||||
"open": [100.0, 200.0, 200.0, 200.0],
|
||||
"high": [100.0, 200.0, 200.0, 200.0],
|
||||
"low": [100.0, 200.0, 200.0, 200.0],
|
||||
"close": [100.0, 200.0, 200.0, 200.0],
|
||||
"volume": 1_000.0,
|
||||
"amount": 1_000.0,
|
||||
}),
|
||||
pd.DataFrame({
|
||||
"symbol_id": "sz000001",
|
||||
"symbol_name": "sz000001",
|
||||
"date": dates,
|
||||
"open": [100.0, 100.0, 200.0, 200.0],
|
||||
"high": [100.0, 100.0, 200.0, 200.0],
|
||||
"low": [100.0, 100.0, 200.0, 200.0],
|
||||
"close": [100.0, 100.0, 200.0, 200.0],
|
||||
"volume": 1_000.0,
|
||||
"amount": 1_000.0,
|
||||
}),
|
||||
], ignore_index=True)
|
||||
alpha = pd.DataFrame({
|
||||
"symbol_id": ["sh600000", "sz000001", "sh600000", "sz000001"],
|
||||
"date": [dates[1], dates[1], dates[2], dates[2]],
|
||||
"alpha_name": ["toy"] * 4,
|
||||
"weight": [-1.0, 1.0, 1.0, -1.0],
|
||||
})
|
||||
|
||||
metrics = evaluate_alpha(alpha, data)
|
||||
|
||||
assert metrics["n_dates"] == 2
|
||||
assert np.isclose(metrics["cumulative_return"], 0.5)
|
||||
|
||||
|
||||
def test_evaluate_alpha_excludes_signal_without_forward_return():
|
||||
dates = pd.date_range("2024-01-01", periods=3)
|
||||
data = pd.concat([
|
||||
pd.DataFrame({
|
||||
"symbol_id": "sh600000",
|
||||
"symbol_name": "sh600000",
|
||||
"date": dates,
|
||||
"open": [100.0, 100.0, 200.0],
|
||||
"high": [100.0, 100.0, 200.0],
|
||||
"low": [100.0, 100.0, 200.0],
|
||||
"close": [100.0, 100.0, 200.0],
|
||||
"volume": 1_000.0,
|
||||
"amount": 1_000.0,
|
||||
}),
|
||||
pd.DataFrame({
|
||||
"symbol_id": "sz000001",
|
||||
"symbol_name": "sz000001",
|
||||
"date": dates,
|
||||
"open": [100.0, 100.0, 100.0],
|
||||
"high": [100.0, 100.0, 100.0],
|
||||
"low": [100.0, 100.0, 100.0],
|
||||
"close": [100.0, 100.0, 100.0],
|
||||
"volume": 1_000.0,
|
||||
"amount": 1_000.0,
|
||||
}),
|
||||
], ignore_index=True)
|
||||
alpha = pd.DataFrame({
|
||||
"symbol_id": ["sh600000", "sz000001", "sh600000", "sz000001"],
|
||||
"date": [dates[1], dates[1], dates[2], dates[2]],
|
||||
"alpha_name": ["toy"] * 4,
|
||||
"weight": [1.0, -1.0, -1.0, 1.0],
|
||||
})
|
||||
|
||||
metrics = evaluate_alpha(alpha, data)
|
||||
|
||||
assert metrics["n_dates"] == 1
|
||||
|
||||
|
||||
def test_equal_weight_is_mean_of_alphas():
|
||||
data = _make_data()
|
||||
a = compute_alpha(data, "rev", "reversal", lookback=5)
|
||||
@@ -163,7 +240,7 @@ def test_load_external_alpha_module(tmp_path):
|
||||
self.span = span
|
||||
|
||||
def signal(self, close: pd.DataFrame) -> pd.DataFrame:
|
||||
return -close.pct_change(self.span)
|
||||
return -close.pct_change(self.span, fill_method=None)
|
||||
'''))
|
||||
|
||||
load_alpha_module(str(module_path))
|
||||
|
||||
Reference in New Issue
Block a user