Skip to content
All strategies

Strategies

CCI/O REIT Pair: Rolling Maximum-Likelihood Ornstein-Uhlenbeck Spread Reversion, Dollar-Neutral Long/Short, Flat Unless |z_OU| >= 2 (USEQ DAILY, 3 parameters)

Outcome: Abandoned

CciORealtyOUMleSpreadReversion

Outcome Summary

CciORealtyOUMleSpreadReversion set out to replicate, more honestly, the best out-of-sample pair (CCI/O) from Huang et al.'s optimal mean-reversion paper. It used a rolling weekly maximum-likelihood OU fit, fixed 2-sigma entry and mean exit thresholds, and a half-life validity check. The strategy never got past code verification: after 3 iterations it was still failing Layer 1 static analysis because the code overwrote the framework's `_bar_ts` method with an instance attribute. It was abandoned before any backtest ran, so the hypothesis remains untested.

Hypothesis

Source: Huang et al., 'On the Profitability of Optimal Mean Reversion Trading Strategies' (arXiv:1602.05858, abstract). Mechanism: build a two-stock portfolio X = ln(CCI) - B*ln(O). Choose the hedge ratio B by maximum likelihood so that X best fits an Ornstein-Uhlenbeck process. Trade contrarian signals from the fitted parameters. CCI/O is the paper's best out-of-sample pair (Sharpe 2.405 in-sample, 2.903 out-of-sample, not verified). Three changes make the test honest. (1) The model is re-fit every week on a trailing 252-session window, using only data available at that point. (2) Thresholds are fixed in advance rather than optimized: enter at 2 sigma_eq, exit at the mean. (3) The model must be valid before any entry: half-life <= 60 sessions. Instruments: CCI.USEQ and O.USEQ, about 25 years of daily history. We drop the HCP pair because the DOC ticker now carries a different company's history. The full paper would have to confirm the exact signal rule, the in-sample length and whether prices are log or level. Expected time flat: about 65-75% of sessions.

Iteration 3 only fixes the Layer-3 sandbox timeout. Signal, entry/exit and sizing logic are unchanged. Per-bar cost is now O(1) and does not grow with the backtest span. Aligned log prices go into fixed-size deques, filled once from the capped bar buffers and then appended one bar at a time. Before, each re-fit rebuilt a timestamp dict and lists from the bar buffers. The weekly re-fit now scores the likelihood for all 59 grid hedge ratios in one closed-form step, using ten second moments computed once per re-fit (the spread's variances and lag-covariance are quadratic in B). This replaces 59 separate numpy fits and gives the same OLS/conditional-MLE result. The O leg is only used when its bar timestamp equals the primary bar's, so there is no look-ahead. If the timeout persists after this, the problem is most likely outside the strategy code, probably loading USEQ data for the two legs. Treat it as a plumbing/data issue, not a reason to change the signal.

verification_loop: Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `_bar_ts(cls, bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 90)

Implementation

Dollar-neutral CCI/O REIT pair traded on USEQ daily bars. Every calendar week the strategy re-fits an Ornstein-Uhlenbeck model by maximum likelihood to X = ln(CCI) - B ln(O) over the trailing 252 sessions, searching a grid of hedge ratios B. It trades the z-score when |z| >= 2 and the fitted half-life is at most 60 sessions. It exits at the fitted mean, or when a weekly re-fit says the model is no longer valid. The two legs are sized 1:B in notional, in whole shares, with gross exposure = equity x gross_exposure.

Verification Results

Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `_bar_ts(cls, bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 90)

Iteration History

Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `_bar_ts(cls, bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 70)

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (2400 days of CCI.USEQ-1-DAY-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Outcome Summary

Never assign instance attributes whose names match FactoryStrategy base-class methods such as `_bar_ts`; use distinct names for cached state so the code passes Layer 1 static analysis.

The strategy was abandoned in a verification loop after 3 iterations. Layer 1 static analysis failed because the code assigned a value to `self._bar_ts`, which overwrote the framework method `_bar_ts(cls, bar)` that the strategy itself calls.

A dollar-neutral CCI/O REIT pair trade on USEQ daily bars: the log-price spread's hedge ratio is re-fit weekly by maximum likelihood to an Ornstein-Uhlenbeck process over a trailing 252-session window. It enters at |z_OU| >= 2, exits at the mean, and trades only when the fitted half-life is 60 sessions or less.

No performance data exists. The backtest report is empty, and no optimization, backtest-review, analyst or risk stage ran.

Abandon Reason

verification_loop: Verification failed (Layer 1 — static analysis): - `self._bar_ts = ...` binds a value over framework method `_bar_ts(cls, bar)` — every later call raises "'<type>' object is not callable" (or the framework loses the member). Use a different attribute name. (line 90)

Analysis

On the Profitability of Optimal Mean Reversion Trading Strategies

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