ENSO onset as a discrete event trade

Wait for El Niño or La Niña to formally arrive, then buy the crops that the weather should affect, and hold for six months. The strongest climate result here — though almost all of the profit came from a handful of months.

Conclusion: Failed

Failed on profit concentration and standing out from the search. Indicative performance of 6.1% a year is 4.0 points above the 2.1% returned by holding the same universe continuously, return for risk taken sits at 0.41 against the benchmark's 0.09, and the worst fall of 41.2% is 30.8 points below the benchmark's 72.0%. Exposure was held in 130 of 288 months.

Figure 1 · Growth of $1

2002–2026 · net of 15bp round-trip costs
What one dollar became, after costs: $4.28 by 2026. Shown against Control, S&P 500, rebased to the same starting dollar.
Return for risk
0.41
range 0.04 to 0.78
Ann. return
6.1%
volatility 14.8%
Max drawdown
41.2%
over 288 months
Beats the search
9.4%
chance, after correction

Method

Hypothesis

The tradeable ENSO event is the onset of a sustained state, not the state itself, because a standing regime is already public information.

Rule

GRAINS = [ZW=F ZC=F ZS=F ZM=F ZR=F KE=F]
SOFTS = [SB=F KC=F CC=F]
THRESHOLD = 8 # index points defining a sustained state
HOLD = 6 months
function positions(bar):
soi = feature(soi_3m)
la_nina_began = crossed_above(soi, +THRESHOLD, within HOLD of bar)
el_nino_began = crossed_below(soi, -THRESHOLD, within HOLD of bar)
basket = []
if la_nina_began: basket += GRAINS
if el_nino_began: basket += SOFTS
if basket is empty:
return {} # no event open, stand aside
return equal_risk(basket, 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

Strongest climate result

Four of six criteria passed. The Sharpe is 0.41 with a confidence interval clear of zero, both halves of the split are positive, and it beats its always-long grain benchmark. Fifteen distinct episodes is a more respectable effective sample than most threshold strategies achieve.

It fails on concentration — the best five months supply 49% of profit against a 40% ceiling — and on deflation, where 0.81 falls short of 0.90 once the project's trial count is accounted for.

It is also long spliced grain futures in predominantly contango markets, which carries the same data problem that withdrew the seasonality result. Whatever survives here would need back-adjusted contract data before it could be believed.

Evaluation

Table 1 · Performance

2002-07 – 2026-06 · 288 months, 130 invested
MeasureValue
Annualised return6.1%
Annualised volatility14.8%
Return for risk taken0.41
95% range0.04 to 0.78
t-statistic2.01
Maximum drawdown41.2%
Hit rate56.2%
Annual turnover1.39×
Return skew0.30
Return kurtosis6.66
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)0.38return for risk≤ 00 – 0.40≥ 0.40
Holdout half (2015+)0.45return for risk≤ 00 – 0.40≥ 0.40
Top-5-month share of profit48.6%how much rode on a few months — lower is better≥ 40%25 – 40%< 25%
Top-2-year share of profit45.4%concentration
Distinct trading episodes15how many independent runs this really is< 5050 – 100≥ 100
Chance the edge is real97.8%non-normality corrected
Chance it beats the whole search9.4%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 C2_BUY_HOLD_GRAIN
1PASS2PASS3FAILED4PASS5PASS6FAILED
CheckWhat it asksResult
1Reward large enough for the riskpass
2Confidence range clear of zeropass
3Profit not concentrated in a few monthsFAIL
4Worked in both halves of the historypass
5Beat buying and holdingpass
6Stood out from the whole searchFAIL
These six checks were written down before the strategy was run.

Table 4 · Net return by calendar year

after costs
2002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026+36%-36%
YearNet return
200224.3%
2003(15.4%)
200414.9%
2005(0.8%)
200610.1%
2007(1.5%)
200829.9%
2009(3.8%)
201021.1%
201111.0%
20122.3%
2013(6.2%)
2014(9.9%)
2015(2.3%)
20160.0%
2017(1.2%)
201812.4%
20193.7%
2020(19.5%)
202118.2%
202210.5%
20235.0%
202436.2%
2025(4.2%)
202610.4%

Table 5 · Concentration detail

MeasureValue
Best years2024:+36%, 2008:+30%, 2002:+24%
Worst year2020:-19%

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.
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.

Sample

2002-07 – 2026-06 · 288 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 criteria fixed before the experiment ran
02TestedFailedFell short on profit concentration, standing out from the search; control C2_BUY_HOLD_GRAIN
algorithmic-trading-anthology-july-2026-enso-onset-event-trade | d/rksci