ConvexPi

Momentum + Value — a Strategy That Survives Out of Sample

— Baseline —strategymomentumvaluemultifactorout-of-sample

Momentum + Value — a Strategy That Survives Out of Sample

Most of the classic single factors look great in a backtest and then evaporate out of sample (see the other examples). This one is different: short-horizon momentum (last month's winners keep winning, briefly) blended with value (cheap beats expensive) is one of the most durable combinations in equities. We hold a wide dollar-neutral book and let weekly rebalancing keep turnover in check — and we check that the edge survives out of sample across many markets, not just one lucky draw.

In [1]:
try:
    import convexpi.lab  # noqa
except ImportError:
    import subprocess, sys
    subprocess.run([sys.executable, "-m", "pip", "install", "-q", "convexpi-lab"])
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
print("ready")
ready

The strategy

Combine two real edges into one score and trade the spread. We weight short-horizon momentum (mom_1m) a bit more than value (val_bm), and go long the top half / short the bottom half — a wide book diversifies away single-name noise, and weekly rebalancing (the grader's default) keeps the high-turnover momentum leg from being eaten by costs.

In [2]:
import numpy as np
from convexpi.lab import Strategy

class MyStrategy(Strategy):
    """Short-horizon momentum + value, dollar-neutral, wide book (top/bottom half)."""
    weights = {"mom_1m": 1.0, "val_bm": 0.5}
    frac = 0.5

    def on_day(self, day, features, prices, portfolio):
        n = len(prices)
        score = np.zeros(n)
        for name, wt in self.weights.items():
            score += wt * np.nan_to_num(features.get(name, np.zeros(n)))
        k = max(1, int(n * self.frac))
        order = np.argsort(score)
        w = np.zeros(n)
        w[order[-k:]] = 1.0 / k     # long the strongest
        w[order[:k]] = -1.0 / k     # short the weakest
        return w

Does it survive? Check across many markets

A single backtest can be lucky. The honest test is robustness: evaluate out of sample on many independent synthetic markets and look at the distribution of OOS Sharpe — not one number.

In [3]:
from convexpi.lab import SyntheticMarket, Grader

oos = []
for seed in range(1, 21):
    r = Grader(SyntheticMarket(n_stocks=200, n_days=1260, seed=seed)).evaluate(MyStrategy())
    oos.append(r.oos_sharpe)
oos = np.array(oos)

print(f"out-of-sample Sharpe across 20 markets:")
print(f"  mean      : {oos.mean():+.2f}")
print(f"  win rate  : {np.mean(oos > 0):.0%}  (fraction of markets with positive OOS)")
print(f"  range     : [{oos.min():+.2f}, {oos.max():+.2f}]")

fig, ax = plt.subplots(figsize=(8, 3))
ax.hist(oos, bins=10, color="steelblue"); ax.axvline(0, color="grey", lw=1)
ax.axvline(oos.mean(), color="darkorange", lw=2, label=f"mean {oos.mean():+.2f}")
ax.set_title("OOS Sharpe distribution (20 markets)"); ax.set_xlabel("OOS Sharpe"); ax.legend()
plt.tight_layout(); plt.show()
out-of-sample Sharpe across 20 markets:
  mean      : +0.84
  win rate  : 80%  (fraction of markets with positive OOS)
  range     : [-2.28, +3.09]
No description has been provided for this image

The honest part

Positive on average and most of the time — but look at the left tail: some markets still hand it a losing draw. That's the truth about even good strategies: an edge is a tilt in the distribution, not a guarantee. The permanent leaderboard grades this on one hidden market, so the badge you see is one draw from that histogram.

What I'd try next

  • Add a third lightly-weighted factor and see if it tightens the distribution (lifts the left tail).
  • Vary the book width (frac) and rebalance horizon to trade off noise vs. turnover cost.
  • Compare against the single-factor examples — diversification is what turns a coin-flip into an edge.

Make it yours: open in Colab, then File → Save a copy in Drive (or in GitHub) to get your own editable copy.

Discussion (0)

Keep feedback constructive: what worked, what you'd try next, or a specific question.

Sign in to join the discussion.

No comments yet — be the first.