Identical to S3 but driven by SOI instead of ONI

The same learn-as-you-go rule as the previous experiment, but driven by the atmospheric pressure measure rather than the ocean temperature one. Two readings of the same climate system disagreed sharply, which is the finding: a real effect could not do that.

Conclusion: Failed

Failed: the 95% range spans zero. Indicative performance of (2.1%) a year at 10.4% volatility, return for risk taken (0.20) (95% range (0.48) to 0.05).

Figure 1 · Growth of $1

1980–2026 · net of 15bp round-trip costs
What one dollar became, after costs: $0.37 by 2026. Shown against S&P 500, rebased to the same starting dollar.
Return for risk
(0.20)
range (0.48) to 0.05
Ann. return
(2.1%)
volatility 10.4%
Max drawdown
157.7%
over 557 months
Beats the search
0.0%
chance, after correction

Method

Hypothesis

Consistency check, not an independent idea. SOI and ONI measure the same coupled system (corr -0.84). If S3 is real, this must agree with it.

Rule

# identical to the previous experiment, driven by the atmospheric
# pressure measure rather than the ocean temperature one
function positions(bar):
signal = features.soi_3m
for symbol in UNIVERSE:
beta = slope(returns(symbol) against signal, up to bar.prev)
w[symbol] = sign(beta) / vol_36m(symbol, bar.prev)
size = clip(zscore(signal, expanding) / 2, -1, 1)
return w * size
# ── 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.

Evaluation

Table 1 · Performance

1980-02 – 2026-06 · 557 months, 557 invested
MeasureValue
Annualised return(2.1%)
Annualised volatility10.4%
Return for risk taken(0.20)
95% range(0.48) to 0.05
t-statistic(1.39)
Maximum drawdown157.7%
Hit rate47.9%
Annual turnover4.52×
Return skew(0.17)
Return kurtosis6.04
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.28)return for risk≤ 00 – 0.40≥ 0.40
Holdout half (2015+)0.30return for risk≤ 00 – 0.40≥ 0.40
Top-5-month share of profit(49.7%)how much rode on a few months — lower is better≥ 40%25 – 40%< 25%
Top-2-year share of profit(32.8%)concentration
Distinct trading episodes1how many independent runs this really is< 5050 – 100≥ 100
Chance the edge is real8.2%non-normality corrected
Chance it beats the whole search0.0%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 · Net return by calendar year

after costs
+23%-23%
YearNet return
1980(5.0%)
19812.1%
198217.8%
1983(18.0%)
198411.8%
198514.6%
19866.0%
1987(17.1%)
1988(22.6%)
1989(12.1%)
19904.4%
199113.7%
1992(12.1%)
1993(3.2%)
1994(8.2%)
1995(4.8%)
19963.6%
1997(3.8%)
19987.3%
1999(2.3%)
2000(5.8%)
20018.9%
20021.3%
2003(5.4%)
2004(18.7%)
2005(12.1%)
20062.6%
2007(10.2%)
2008(14.4%)
2009(11.1%)
2010(7.3%)
20110.0%
2012(4.9%)
2013(5.2%)
2014(3.2%)
2015(5.1%)
20160.2%
2017(4.5%)
20187.2%
20190.2%
20209.0%
20215.6%
20227.0%
2023(8.0%)
2024(1.4%)
2025(1.0%)
20265.6%

Table 4 · Concentration detail

MeasureValue
Best years1982:+18%, 1985:+15%, 1991:+14%
Worst year1988:-23%

Reference

Instruments

last price and one-month change, live from Yahoo Finance
SymbolNotes
GNC.AXGrainCorp — listed on ASX, in AUD.
ELD.AXElders — listed on ASX, in AUD.
NUF.AXNufarm — listed on ASX, in AUD.
DNL.AXDyno Nobel — listed on ASX, in AUD.
RIC.AXRidley Corporation — listed on ASX, in AUD.
AAC.AXAustralian Agricultural Company — listed on ASX, in AUD.
BALRAMCHIN.NSNot in the instrument table.
TRIVENI.NSTriveni Engineering & Industries — listed on NSE, in INR.
DHAMPURSUG.NSNot in the instrument table.
COROMANDEL.NSNot in the instrument table.
CHAMBLFERT.NSNot in the instrument table.
UPL.NSUPL Limited — listed on NSE, in INR.
M&M.NSMahindra & Mahindra — listed on NSE, in INR.
ESCORTS.NSEscorts Kubota — listed on NSE, in INR.
list truncated in the source record

Sample

1980-02 – 2026-06 · 557 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
02TestedFailedBenchmark, not scored against the criteria
algorithmic-trading-anthology-july-2026-s4-soi-adaptive | d/rksci