Skip to content
All strategies

Strategies

PEP/KO Heteroskedastic Filtered-Spread Re-entry

Outcome: Abandoned

PepKoHeteroskedasticSpreadReentry

Outcome Summary

This strategy turned Zhang's Model II filtered-spread Strategy C into a hedged PEP/KO daily pairs trade on split-adjusted (not dividend-adjusted) USEQ prices. It took three iterations, the last of which replaced frozen 1972-75 coefficients with a yearly causal refit. On the full 1992-2024 history it lost 2.74% over 107 baskets, with Sharpe -0.17, profit factor 0.81 and a signal IC of 0.002, and it lost in both the calm and stressed regimes. The backtest-review gate abandoned it before optimization for negative expectancy not explained by costs. That rejects only this platform version, not Zhang's dividend-adjusted results with simulation-selected boundaries.

Hypothesis

Derived by the Research Lead from Guang Zhang's paper: test Strategy C, which enters a hedged pair when a filtered spread returns inside its trading boundaries and exits at equilibrium or renewed divergence. Use PEP and KO daily bars on USEQ, retaining the paper's empirical Model II: linear mean-reverting drift with state-dependent Gaussian innovation variance. This is a distilled implementation, not a reproduction of the reported returns. Research Lead additions are a causal 756-session estimation window,……Show moreShow less

Derived by the Research Lead from Guang Zhang's paper: test Strategy C, which enters a hedged pair when a filtered spread returns inside its trading boundaries and exits at equilibrium or renewed divergence. Use PEP and KO daily bars on USEQ, retaining the paper's empirical Model II: linear mean-reverting drift with state-dependent Gaussian innovation variance. This is a distilled implementation, not a reproduction of the reported returns. Research Lead additions are a causal 756-session estimation window, explicitly defined volatility-dependent boundaries, equity-relative sizing and protective exits. Replace the paper's simulation-selected boundaries with the platform's training-only, net-of-cost walk-forward selection; do not introduce a second simulation-based parameter search. Execute market orders after both completed daily bars arrive, at the modeled session close. No next-session-open execution is required. Fit model coefficients using only the pre-evaluation training window, then freeze them throughout each evaluation segment. Deployment freezes coefficients and trading parameters. USEQ prices are split-adjusted rather than dividend-adjusted, so this tests price convergence and does not reproduce the paper's adjusted-price economics.

The source's distinctive mechanism is re-entry from an extreme filtered spread under heteroskedasticity, rather than immediate fading of an extreme raw return. Preserve the original equity pair instead of substituting a crypto pair. PEP and KO are included in the configured equity universe. The proposal reduces the portfolio's single-instrument and long-only concentration, although it does not fill its options or cross-venue deficits. Corpus concentration was not supplied, so no instrument-specific share is assumed. Historical paper coefficients and returns are not adopted as evidence or current parameters.

no_edge: negative_expectancy (fee-floor check: -0.186% vs the USEQ floor of +0.05%; commission is zero and impact plus borrow total about $370 of a $2,738 loss, so fees are not the cause). With the yearly causal refit fixed, the Research Lead's PEP/KO daily operationalization of Zhang's Strategy C loses on 107 hedged baskets from 1992 to 2024: PF 0.81, Sharpe -0.17, 29% of baskets win, executed-signal IC 0.002 (t=0.05), and it loses in the calm and stressed regimes. QA's concern is borne out on the full history: the thin positive sandbox result does not hold up. Optimization would only tune lower_mult and upper_mult. The baseline signal carries no information and the gross price edge is negative, so a boundary search would mostly fit noise. Scope: this rejects only the platform's daily, split-adjusted (not dividend-adjusted), next-open-fill version with a 756-session yearly refit. It does not test Zhang's paper results on dividend-adjusted prices with simulation-selected boundaries.

Implementation

Zhang Model II (linear mean-reverting drift, state-dependent Gaussian innovation variance) on PEP = gamma*KO + x, filtered by a Gaussian moment filter. Strategy C: long the spread basket (long PEP / short gamma KO) when the normalised filtered deviation crosses back inside +lower_mult from above, short when it crosses back inside -upper_mult from below; exit at equilibrium, renewed divergence, 60-session time stop, 1% basket equity stop or 5% leg stops. Coefficients are re-estimated on the trailing 756 aligned sessions once per calendar year (only while flat) and frozen until the next refit.

Iteration 3 fixes: (1) Causal rolling refit: the trailing 756-session deque is kept permanently and the model (gamma, equilibrium, rho, omega, alpha, observation variance, price_scale) is re-estimated at the first flat paired bar of each calendar year (calendar-anchored, so restarts and backtests agree; deferred while a basket is open), then frozen. After each refit the filter is re-run over the training window from the NEW equilibrium with the NEW coefficients (old state never carried over), and the training-end……Show moreShow less

Iteration 3 fixes: (1) Causal rolling refit: the trailing 756-session deque is kept permanently and the model (gamma, equilibrium, rho, omega, alpha, observation variance, price_scale) is re-estimated at the first flat paired bar of each calendar year (calendar-anchored, so restarts and backtests agree; deferred while a basket is open), then frozen. After each refit the filter is re-run over the training window from the NEW equilibrium with the NEW coefficients (old state never carried over), and the training-end filtered mean/variance seed evaluation. Each refit logs date, gamma, rho, alpha, omega, half-life ln(0.5)/ln(rho) and price_scale. A failed fit (non-positive gamma, rho outside (0,1), non-finite likelihood) logs a warning and blocks new baskets until the next refit instead of crashing. (2) Data-validity guard: no entries (and no refits) unless both split-adjusted closes are >= $10 (min_leg_price, fixed, not in the tunable set). (3) Both legs are submitted in the same on_bar call on the decision bar (PEP via _submit_entry, KO via _submit_entry_instrument with planned_ko_shares = round(gamma*planned_pep_shares)); the fill-callback KO hedge was removed. If a submission guard declines or caps either leg, the basket is flattened immediately; the next-bar cancel-and-flatten check for partial/mismatched fills is kept (now compared against planned share counts). (4) Both prices are divided by the same price_scale, so the fitted gamma IS the Model II share ratio on price levels (KO shares per PEP share) — no conversion needed; KO notional ~ gamma*KO/PEP of PEP notional is therefore a property of the paper's price-level observation equation, which is kept. Each closed basket logs net PnL (settled-equity change, fees included) divided by gross entry notional |PEP|+|KO| and a running average, for per-basket economics vs the USEQ floor. Not adopted from the library: the half-life gate and the 0.30% expected-convergence filter (extra AND-gates on top of the hypothesis, risk of zero trades; half-life is logged at every refit so the analyst can judge), and the 3-sigma spread stop (redundant: the existing renewed-divergence exit at the entry boundary already fires inside it). Note: on USEQ market orders decided at bar N's close fill at bar N+1's open; both legs fill together there.

Verification Results

State the annual-refit policy explicitly in the description so the analyst knows coefficients roll yearly inside long evaluation segments. If the Research Lead wants a strict single fit per segment, gate the refit on segment start instead.

Coefficients are re-estimated on the trailing 756 sessions on the first flat paired bar of each calendar year. The hypothesis says 'fit on the pre-evaluation training window, then freeze throughout each evaluation segment' (model_fit_policy training_only_then_frozen). The refit is strictly causal: the window ends at the current completed close, and it is calendar-anchored, deferred while a basket is open, and the filter is re-run from the new equilibrium. So there is no look-ahead and no check-20 issue. But a walk-forward evaluation segment that spans 1 January will contain one in-segment refit. The hypothesis also names a 'causal 756-session estimation window' and calls this iteration's change a fix, so I read it as a faithful reading of a causal rolling estimation window, not a different strategy.

Leave it, or ask the Research Lead to add it to the fixed set for contract completeness.

min_leg_price ($10) is a Research-Lead-unlisted fixed guard on entries and refits, and it is not in research_contract.optimization_plan.fixed. Split-adjusted PEP (~$60+) and KO (~$25+) stay above $10 over the USEQ daily history, so in practice it never binds and does not change trades.

No code change needed. The description could state next-open fills explicitly.

Execution timing: the hypothesis text says 'market orders ... at the modeled session close'. On USEQ the platform fills a bar-close decision at the next session's open (next_open_fill is supported, same_close_fill is unsupported). The code submits plain market orders at the decision bar and its rationale expects a next-open fill, which is correct engine behaviour. Both legs go in on the same decision bar and fill together at the next open. This is not a defect, only a note that the hypothesis wording describes an unsupported fill.

None required.

Recovery paths for partially filled or mismatched baskets (the next-bar cancel-and-flatten check and the on_order_canceled/rejected/denied/expired handlers) cover simulated-venue paths that cannot occur for full-fill market orders. They are harmless: _submit_exit_position is guarded by _close_pending, so repeated _close_basket calls do not create duplicate or flipping closes.

The sandbox is a smoke test: 12 baskets (24 legs) over about 141 post-warmup sessions. Average per-leg return was 0.054% of notional, barely above the ~0.05% USEQ spread/impact floor. Impact was 36% of gross PnL, and nearly all profit came from the stressed-vol third (calm and normal regimes were negative). Half-lives are logged at each refit; check those first. If the PEP/KO filtered spread half-life is long relative to the 60-session time stop, Strategy C re-entries will mostly exit on time or on renewed divergence rather than at equilibrium. Prices are split-adjusted but not dividend-adjusted, so the PEP/KO dividend-yield gap adds a slow drift to the price spread that the annual refit only partly absorbs.

Backtest Review

Sharpe
-0.17
Total return
-2.74%
Max drawdown
3.54%
Trades
214
Win rate
40.7%
Profit factor
0.81

The iteration-2 defect is fixed. The model now refits causally each calendar year on the trailing 756 sessions instead of freezing 1972-75 coefficients.

Accounting is valid, both legs are hedged and fill together (107 long and 107 short legs), and drawdown is small (3.5% vs the 12% contract).

Trading starts in 1992. Before that the min_leg_price $10 guard blocks entries, because split-adjusted 1970s-80s prices are below $10. That leaves about 32 years and 107 baskets of usable sample.

Expectancy is negative: avg_trade_return_pct is -0.186% against the USEQ floor of +0.05%, PF is 0.81, Sharpe -0.17 (95% CI -0.46 to +0.12), and total return is -2.74%.

Costs do not explain the loss. Commission is zero, impact is about $292 and borrow about $77, against a summed price PnL of -$2,738. At basket level, 31 of 107 baskets win (29%): +$6,059 gross wins vs -$8,796 gross losses.

Executed-signal IC is 0.002 (t=0.05, n=612): no information.

Losses are spread across regimes: calm Sharpe -0.43, normal +0.02, stressed -0.20. Most active years are negative (1993, 1996-2001, 2003-04, 2011-14, 2019, 2023); 2020 is the one large positive month.

Median basket hold is 5 days and no basket reaches the 60-session time stop. Re-entries are mostly closed early by renewed divergence or stops, not by convergence to equilibrium.

Analysis

Do not optimize yet: the baseline never tested the mechanism in a usable regime. Fix these code and evaluation defects without changing any fixed contract value: (1) The estimation is dormant after 1980. The model is fitted once on the first 756 aligned sessions (1972-75) and applied frozen to 2024 with a fixed price_scale, so the filtered state drifts away from equilibrium and no crossing ever fires again. Keep 'training_only_then_frozen' within each evaluation segment, but make the full-history baseline……Show moreShow less

Do not optimize yet: the baseline never tested the mechanism in a usable regime. Fix these code and evaluation defects without changing any fixed contract value: (1) The estimation is dormant after 1980. The model is fitted once on the first 756 aligned sessions (1972-75) and applied frozen to 2024 with a fixed price_scale, so the filtered state drifts away from equilibrium and no crossing ever fires again. Keep 'training_only_then_frozen' within each evaluation segment, but make the full-history baseline exercise it the way the evaluation contract does (boundary_policy reset_with_warmup): re-estimate from the trailing 756 sessions at a defined causal cadence, e.g. every 252 sessions or each segment start, then freeze until the next refit. Log each refit date and its gamma, rho and alpha, so the analyst can see the estimates are stable. (2) Price quantization. Do not trade while either leg's split-adjusted close is below a level where one tick is under about 0.1% of price, i.e. require price >= $10 on both legs. Without this gate, the 1970s $0.70-1.60 prices make 5% leg stops trigger on 1-2 ticks. In practice this restricts trading to roughly the 1990s onward. That is a data-validity guard, not a tunable parameter. (3) Leg synchronization. Submit the PEP and KO entry orders in the same on_bar call at the same decision bar, sized together from planned_pep_shares and planned_ko_shares, instead of submitting KO from the PEP fill callback. Each basket currently holds about 6.5 h of naked PEP between the open and close fills. Keep the cancel-and-flatten path for partial fills. (4) Hedge notional. Confirm and document that gamma is the paper's Model II share ratio on price levels. With gamma about 0.42, KO notional is about 20% of PEP notional, so most of the basket's P&L is directional PEP. If the paper's observation equation is in price levels, keep it, but report basket-level return per gross notional so economics can be judged per basket rather than per leg. After these fixes, the re-run should produce baskets across 1990-2024. Judge it on basket-level net return per gross notional against the USEQ 0.05% floor (prefer 0.10%). If baskets still lose net on a multi-decade sample, the operationalization should be abandoned as negative_expectancy. ## Library refinements (from the knowledge library; test them, do not assume them) The library has only the abstracts of Zhang's paper and of a 2026 PEP-KO case study, which reports that PEP-KO mean reversion weakened out-of-sample. It supports re-estimating the spread model on a rolling window, trading only while the fitted half-life fits the holding horizon, stopping on spread deviation rather than leg price, entering both legs on the same bar, and raising entry thresholds for costs. 1. [regime] Rolling causal refit of the spread model: Re-estimate gamma, equilibrium, rho, omega, alpha, observation variance and price_scale on the trailing 756 aligned sessions every 252 sessions, and at each evaluation-segment start. Freeze them until the next refit. Refit only while flat. If a basket is open, defer the refit to the first flat bar. After each refit, re-initialise the filter: mean = new equilibrium, variance = the training-end filtered variance. Do not carry the old state across refits. Log the date, gamma, rho, alpha and the implied half-life ln(0.5)/ln(rho) at every refit. — The baseline was fitted once on 1972-75 and stopped producing crossings after 1980. Graziano's PEP-KO study analyses time-varying hedge ratios with rolling OLS and a Kalman filter, and finds that weaker mean reversion in the spread undermined the strategy out-of-sample. A coefficient set fixed for 45 years therefore cannot be trusted. Quantpedia notes that pairs which stop moving together should be dropped, and that pairs-trading returns have shrunk over time. Both argue for regular re-estimation with no look-ahead. This matches the analyst's fix (1) and the reset_with_warmup evaluation policy. (source: From Cointegration to Out-of-Sample Failure: A Pairs-Trading Case Study on PEP-KO (Graziano, abstract) p.1; Pairs Trading with Stocks - Quantpedia p.1; How to select optimal look back period for statistical arbitrage? p.1) 2. [filter] Half-life gate: trade only when the fitted spread reverts within the holding horizon: At each refit, compute half_life = ln(0.5)/ln(rho). Allow new baskets until the next refit only if 0 < rho < 0.9885, i.e. half_life <= 60 sessions (= max_hold_sessions). Otherwise stay flat for that segment. If a refit fails the gate while a basket is open, close the basket at that bar's close. The threshold is derived from the fixed max_hold_sessions, so it is not a new tunable. — QuantInsti says that if cointegration breaks while a pair is on, the positions should be cut, because the trade's basic assumption no longer holds. On QSE 18511, one answer says the half-life is roughly how long you should expect to hold the spread, and another ties the lookback to the intended holding period. The 2109.10662 paper uses the OU half-life to select assets. The baseline lost on average (avg trade -0.53%, win rate 36%), which fits trading a spread that reverts more slowly than the 60-session time stop. Graziano reports exactly this weakening of PEP-KO mean reversion after 2023. (source: Pairs Trading for Beginners: Correlation, Cointegration, Examples, and Strategy Steps (QuantInsti) p.1; How to select optimal look back period for statistical arbitrage? p.1; Evaluation of Dynamic Cointegration-Based Pairs Trading Strategy in the Cryptocurrency Market (abstract) p.1; From Cointegration to Out-of-Sample Failure: A Pairs-Trading Case Study on PEP-KO (abstract) p.1) 3. [stop] Spread-sigma divergence stop in addition to the leg and basket stops: Add an exit when the filtered deviation |x_t - equilibrium| exceeds 3.0 x sqrt(conditional innovation variance) for the current state, i.e. sqrt(omega + alpha*x_t^2) under Model II. Evaluate it on the bar close after both legs' bars have arrived. Keep leg_stop_fraction=0.05 and basket_stop_fraction=0.01 unchanged, since they are fixed contract values. Set the stop multiple at 3.0, or at upper_mult + 1.5 if that is larger, so the stop always lies outside the entry boundary. — QuantInsti's rule example enters at 2 sigma and stops at 3 sigma, scaled in spread units. Leung et al. show that with a stop-loss the optimal entry region lies strictly inside the stop level. Robot Wealth scales its trade levels by the filter's innovation standard deviation sqrt(Q). A 5% leg-price stop ignores the spread's heteroskedastic state, so it can fire on market moves that hit both legs while the spread itself is fine. A sigma-based stop exits on the 'renewed divergence' that Strategy C names, measured in the model's own units. (source: Pairs Trading for Beginners (QuantInsti), 'Stop loss' section p.1; Optimal Mean Reversion Trading with Transaction Costs and Stop-Loss Exit (Leung et al., abstract) p.1; Kalman Filter Pairs Trading with Zorro and R - Robot Wealth p.1) 4. [entry] Enter and exit both legs on the same decision bar, with share counts from the hedge ratio: In the on_bar call where both PEP and KO bars for the session have arrived, submit both market orders together. Size PEP with planned_pep_shares, computed from equity, gross_exposure_fraction=0.5 and risk_fraction=0.005. Size KO with planned_ko_shares = round(gamma_shares * planned_pep_shares), where gamma_shares converts the fitted gamma back from price_scale units to a share ratio. Exits close both legs in the same call. Keep the cancel-and-flatten path for partial fills. Report P&L per basket divided by gross notional (|PEP notional| + |KO notional|). — In Robot Wealth's Kalman pairs script, every entry and exit trades Asset1 with Lots = Portfolio_Units and Asset2 with Lots = Portfolio_Units*beta in the same bar's logic block. The baseline submits KO from the PEP fill callback, which leaves PEP unhedged for hours. Hudson & Thames frames market neutrality as coming from the hedge ratio, so a delayed or missing hedge leg turns each basket into a directional PEP bet. Judging per-basket return on gross notional is the analyst's required metric. (source: Kalman Filter Pairs Trading with Zorro and R - Robot Wealth p.1; The Comprehensive Introduction to Pairs Trading - Hudson & Thames p.1) 5. [cost] Cost-aware minimum expected convergence at entry: When a re-entry crossing fires, enter only if the expected convergence gain covers costs with a 3x margin. The gain is |x_t - equilibrium| x price_scale x PEP shares, i.e. the spread dollars that full reversion to equilibrium would earn. It must be >= 0.30% of basket gross notional: 3x the 0.10% floor the analyst prefers for USEQ, which already includes the ~0.02% round-trip spread on two legs. Skip crossings below that size. — Leung et al. derive optimal entry and exit intervals for an OU spread under transaction costs. Those intervals depend on the cost level, so a fixed boundary that ignores costs admits trades too small to pay for themselves. QuantInsti warns that a pair trade pays roughly double the commission of a single trade. The baseline averaged -0.53% per trade with a 36% win rate. The analyst floor is 0.05%, preferably 0.10%, of gross notional per basket. Filtering out shallow crossings, where the remaining distance to equilibrium is small after a re-entry, targets that floor directly without adding a tunable. (source: Optimal Mean Reversion Trading with Transaction Costs and Stop-Loss Exit (Leung et al., abstract) p.1; Pairs Trading for Beginners (QuantInsti), commission note p.1)

Iteration History

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The sandbox reports -0.266% average return per closed leg and PF 0.448. These are advisory smoke-test results; full-history evaluation must assess pair economics using USEQ spread, impact and borrow costs. - [critical] _ensure_leg_stops() accepts any existing reduce-only STOP_MARKET without checking its quantity. on_order_filled() invokes it after every entry fill, so a partial fill creates a stop covering only the initial position……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The sandbox reports -0.266% average return per closed leg and PF 0.448. These are advisory smoke-test results; full-history evaluation must assess pair economics using USEQ spread, impact and borrow costs. - [critical] _ensure_leg_stops() accepts any existing reduce-only STOP_MARKET without checking its quantity. on_order_filled() invokes it after every entry fill, so a partial fill creates a stop covering only the initial position quantity. Subsequent fills increase the position but leave that stop unchanged. This applies to both PEP and KO and leaves the additional shares without the specified intrabar protective stop. (line 328) — fix: Reconcile protective stop quantity against the current position after every fill. Modify or replace the stop when quantity changes, and reconcile its trigger with the completed position's average entry price. - [critical] The first PEP fill submits the entire planned KO hedge and permanently sets hedge_requested. OrderFilled can represent a partial execution, but this branch neither checks completion nor sizes the hedge from actual filled PEP shares. A partially filled or subsequently canceled PEP entry therefore carries the full KO hedge. The later presence-only check accepts this incorrectly hedged basket because both positions exist. (line 363) — fix: Track cumulative executed shares and reconcile KO exposure to gamma times actual PEP exposure. Handle incomplete, canceled and rejected entries by canceling outstanding entry quantities and correcting or flattening unmatched exposure.

Reconcile protective stop quantity against the current position after every fill. Modify or replace the stop when quantity changes, and reconcile its trigger with the completed position's average entry price.

_ensure_leg_stops() accepts any existing reduce-only STOP_MARKET without checking its quantity. on_order_filled() invokes it after every entry fill, so a partial fill creates a stop covering only the initial position quantity. Subsequent fills increase the position but leave that stop unchanged. This applies to both PEP and KO and leaves the additional shares without the specified intrabar protective stop.

Track cumulative executed shares and reconcile KO exposure to gamma times actual PEP exposure. Handle incomplete, canceled and rejected entries by canceling outstanding entry quantities and correcting or flattening unmatched exposure.

The first PEP fill submits the entire planned KO hedge and permanently sets hedge_requested. OrderFilled can represent a partial execution, but this branch neither checks completion nor sizes the hedge from actual filled PEP shares. A partially filled or subsequently canceled PEP entry therefore carries the full KO hedge. The later presence-only check accepts this incorrectly hedged basket because both positions exist.

Outcome Summary

Fixing the model-fit defect (the yearly causal refit) showed the gross price-convergence edge itself was negative and unrelated to costs. A thin positive sandbox result should not be read as evidence until the full-history signal IC is confirmed.

After iteration 3 the backtest-review gate returned 'abandon' for no_edge: negative expectancy of -0.186% against the USEQ floor of +0.05%. Costs did not cause the loss: commission was zero and impact plus borrow came to about $370 of a $2,738 loss. The signal showed no information, so optimizing the two boundary multipliers would only have fit noise.

A hedged PEP/KO daily pairs trade on USEQ based on Zhang's Strategy C. A heteroskedastic Model II filtered spread, refit each year on a causal 756-session window, opens a basket when the spread re-enters its volatility-dependent boundaries. The basket closes at equilibrium, on renewed divergence, or at a protective stop.

From 1992 to 2024 it traded 107 hedged baskets (214 trades) and returned -2.74% in total. Sharpe was -0.17 (95% CI -0.46 to +0.12), profit factor 0.81, avg_trade_return_pct -0.186%, and only 29% of baskets won. Max drawdown was 3.54%, and the executed-signal IC was 0.002 (t=0.05).

Analysis

Pairs Trading with Nonlinear and Non-Gaussian State Space Models

Backtest and paper results are hypothetical. Trading involves risk of loss.