Agricultural calendar seasonality

Buy the crops that have historically done well in the coming month and sell those that have done badly. It looked like the best result in the project until the price data itself turned out to produce the pattern.

Conclusion: Voided

Withdrawn after audit. Repeating the same trades on exchange-traded funds, which hold real contracts and pay real costs, cut return-for-risk from 0.80 to 0.34. The pattern was largely an artefact of how free price histories stitch expiring contracts together.

Figure 1 · Growth of $1

2008–2026 · net of 15bp round-trip costs
What one dollar became, after costs: $8.48 by 2026. Shown against Control, S&P 500, rebased to the same starting dollar.
Return for risk
1.16
range 0.74 to 1.60
Ann. return
12.0%
volatility 10.4%
Max drawdown
12.8%
over 213 months
Beats the search
97.6%
chance, after correction

Method

Hypothesis

Agricultural futures carry persistent calendar-month seasonality that can be harvested cross-sectionally.

Rule

UNIVERSE = 15 agricultural futures
MIN_YEARS = 8
N_SIDE = 4
function positions(bar):
m = month(bar.next)
for symbol in UNIVERSE:
history = returns(symbol) in month m, years before bar.year
if count(history) < MIN_YEARS:
continue # not enough seasons to judge
score[symbol] = mean(history)
return long_short(score, N_SIDE, bar)
function long_short(score, n, bar):
longs = top n symbols by score
shorts = bottom n symbols by score
return equal_risk(longs, bar) - equal_risk(shorts, bar)
# ── execution ────────────────────────────────────────────────────────
COST_PER_TRADE = 15 bps of the notional that changes hands
function on_bar(bar, book):
if not bar.is_month_end:
return # decisions are made monthly only
target = positions(bar) # the rule above
target = scale_to_gross(target, 1.0)
rebalance(book, target, bar)
function scale_to_gross(w, limit):
gross = sum(abs(w)) # total capital at work
if gross == 0:
return w # flat is a valid target
return w * limit / gross # leverage fixed, never implicit
function rebalance(book, target, bar):
for symbol in union(book.symbols, target.symbols):
delta = target[symbol] - book.weight(symbol)
if delta > 0:
buy(symbol, delta, fill = next_bar.close)
else if delta < 0:
sell(symbol, -delta, fill = next_bar.close)
book.charge(COST_PER_TRADE * abs(delta))
# Orders are filled at the next month's close, never the one the decision
# was made on. There is no stop_loss(), take_profit(), limit order or
# position cap: a holding changes only when positions() returns a
# different target at the next month end.

Analysis

Initial result

Sharpe 1.16, confidence interval [0.74, 1.60], design half 1.26 and holdout 1.10, best five months only 17.6% of profit. It survived deflation against all 560 trials the project had run — the harshest correction available here.

That result warranted scrutiny, and the concern has a specific form. A futures contract expires every few months, so a long price history is really many short ones stitched together. The stitches happen on a calendar. A strategy that trades the calendar could easily be trading the stitches.

So the same four commodities were checked against exchange-traded funds that hold the real contracts and pay the real cost of rolling from one to the next.

Audit against investable funds

Table A · Spliced futures against the investable ETF

monthly return difference, same underlying commodity
PairMonthsMean annual gapANOVA p (calendar structure)
ZW=F / WEAT178+10.9%< 0.0001
ZC=F / CORN193+3.9%< 0.0001
SB=F / CANE178+2.3%< 0.0001
ZS=F / SOYB178−0.8%< 0.0001
All four show calendar-month structure in the gap. December corn: t = +8.8. September wheat: t = +7.0. The spliced wheat series overstates investable returns by roughly eleven points a year.

Performance on investable instruments

A new rule was added afterwards: a result does not count if the returns could not actually have been earned. Changing the rules after seeing the answer is usually cheating, so this one is allowed to do only one thing — take results away. It can never hand one out.

What remains is not nothing. The investable version is weakly positive, and in line with what others have reported for crop seasonality. Fifteen years of fund history is simply too little to prove it either way. That is a limit of the data, not a verdict on the idea.

Table B · Where the edge went

identical seasonal long/short signal, 2011-10 onward
Earned onMonthsAnn. returnSharpet
Spliced futures (what the backtest saw)178+17.7%+0.803.09
ETFs (what an investor could hold)178+5.5%+0.341.31
Correlation between the two return streams is 0.87 — these are the same trades. Most of the measured edge was the splice.
The strategy did not find seasonality in crops. It found seasonality in how the price series was assembled.

Evaluation

Table 1 · Performance

2008-10 – 2026-06 · 213 months, 213 invested
MeasureValue
Annualised return12.0%
Annualised volatility10.4%
Return for risk taken1.16
95% range0.74 to 1.60
t-statistic4.90
Maximum drawdown12.8%
Hit rate64.3%
Annual turnover19.39×
Return skew(0.05)
Return kurtosis2.99
The range is measured by resampling the history in blocks, so runs of good and bad months stay intact.

Table 2 · Robustness

DiagnosticValueReads asBadOkayYayHmm
Design half (pre-2015)1.26return for risk≤ 00 – 0.40≥ 0.40
Holdout half (2015+)1.10return for risk≤ 00 – 0.40≥ 0.40
Top-5-month share of profit17.6%how much rode on a few months — lower is better≥ 40%25 – 40%< 25%
Top-2-year share of profit24.7%concentration
Distinct trading episodes1how many independent runs this really is< 5050 – 100≥ 100
Chance the edge is real100.0%non-normality corrected
Chance it beats the whole search97.6%chance of beating the whole search by luck
bad/ok boundary is the pre-registered criterion · ok/good is a margin above it, not a criterion · blank where none was registered
The last row asks whether the result would stand out from the hundred-odd ideas tried here, rather than being judged alone.

Table 3 · Pre-registered criteria

measured against C3_RP_LONG
1PASS2PASS3PASS4PASS5PASS6PASS
CheckWhat it asksResult
1Reward large enough for the riskpass
2Confidence range clear of zeropass
3Profit not concentrated in a few monthspass
4Worked in both halves of the historypass
5Beat buying and holdingpass
6Stood out from the whole searchpass
These six checks were written down before the strategy was run.

Table 4 · Net return by calendar year

after costs
2008200920102011201220132014201520162017201820192020202120222023202420252026+27%-27%
YearNet return
20081.0%
200913.3%
201017.8%
20111.9%
201214.2%
201314.1%
201425.2%
201521.4%
2016(0.7%)
2017(3.0%)
20183.4%
20199.5%
202018.2%
20217.1%
202226.6%
202312.8%
202426.3%
20259.1%
2026(4.4%)

Table 5 · Concentration detail

MeasureValue
Best years2022:+27%, 2024:+26%, 2014:+25%
Worst year2026:-4%

Reference

Instruments

last price and one-month change, live from Yahoo Finance
SymbolNotes
ZW=FChicago SRW Wheat — continuous futures on CBOT, quoted in US cents.
KE=FKC HRW Wheat — continuous futures on CBOT, quoted in US cents.
ZC=FCorn — continuous futures on CBOT, quoted in US cents.
ZS=FSoybeans — continuous futures on CBOT, quoted in US cents.
ZM=FSoybean Meal — continuous futures on CBOT, in USD.
ZL=FSoybean Oil — continuous futures on CBOT, quoted in US cents.
ZR=FRough Rice — continuous futures on CBOT, in USD.
SB=FSugar No. 11 — continuous futures on ICE Futures, quoted in US cents.
KC=FCoffee C — continuous futures on ICE Futures, quoted in US cents.
CC=FCocoa — continuous futures on ICE Futures, in USD.
CT=FCotton No. 2 — continuous futures on ICE Futures, quoted in US cents.
OJ=FOrange Juice — continuous futures on ICE Futures, quoted in US cents.
LE=FLive Cattle — continuous futures on CME, quoted in US cents.
GF=FFeeder Cattle — continuous futures on CME, quoted in US cents.
list truncated in the source record

Sample

2008-10 – 2026-06 · 213 months

Sources

NOAA Physical Sciences Laboratory. Climate indices. ONI, Niño 3.4, MEI v2, DMI, PDO, AMO, TNA, QBO, solar flux. psl.noaa.gov/data/climateindices/list
Queensland Department of Agriculture and Fisheries. Southern Oscillation Index. The Long Paddock. Monthly, 1876–. www.longpaddock.qld.gov.au/soi/soi-data-files
Yahoo Finance. Historical market prices. Adjusted close, month-end. Retrieved with yfinance. finance.yahoo.com

Revision history

RevStageStatusChange
01DraftDirection and pass/fail criteria fixed before the run
02TestedValidatedPassed every check, including the penalty for how many ideas were tried
03AuditedVoidedRepeating the trades on real funds showed the returns could not actually have been earned
algorithmic-trading-anthology-july-2026-seasonality-voided-by-audit | d/rksci