ASX Momentum · Reference Design v1
Out-of-the-box · long-only · daily bars · monthly rebalance · IBKR paper first

A momentum strategy for the ASX you can actually run — with the evidence, the failure modes and the gate that decides whether it deserves real money.

Rank liquid S&P/ASX 200 stocks by risk-adjusted 12-month momentum, hold the top 20 in equal weight, rebalance monthly, and go to cash whenever the index is below its 200-day average. One person, one small VPS, Python, an Interactive Brokers paper account. Every rule below was argued out in the GAIOS Brainroom and every number comes from a script you can download.

The honest claim. On prototype evidence this design delivers roughly index-like returns with about a third less maximum drawdown, fully automated, at retail size. It is not an outperformance claim. Whether any edge survives point-in-time data and live costs is exactly what the go-live gate exists to find out.
CAGR 2010–2026 · v1 at AUD 25k
4.6%
vs 3.4% for the S&P/ASX 200 price index (no dividends)
Max drawdown
−29.4%
vs −36.5% for the index
2020 crash, Feb–Mar
−15.3%
vs −36.5% for the index — the reason the regime filter is binary
Time invested
67%
the rest in cash, by rule, never by feel
Read this before any number. The backtest uses today's ASX 200 constituents (survivorship bias: every figure is an upper bound), is benchmarked against the price index (the accumulation index runs roughly four points a year higher), and uses yfinance daily data (not authoritative for corporate actions). It is prototype evidence, not a go-live validation. Costs are modelled per clip as max(0.08%, AUD 6) + 0.10% slippage. Period 4 Jan 2010 – 14 Sep 2026.

General information only — not personal financial advice, not a recommendation to buy or sell any security. Author: Sabour Khosravi, GoldNet Group. Use the Ask the agent button (bottom right) for verified answers, or the Hint buttons in each section for what to check next.

1. The rules (v1, frozen)

UniverseD1
Current S&P/ASX 200 constituents, screened at each rebalance: 20-day median dollar volume ≥ AUD 2m and price ≥ AUD 1.00. About 150–175 names pass today.
SignalD2
12-1 momentum ÷ volatility: total return from 252 to 21 trading days ago (skip the last month), divided by 252-day realised volatility, on dividend-adjusted closes. Higher is better.
Absolute legD19
Eligible only if the 12-1 return is positive. Practically redundant behind the rank and regime filter, kept so "dual momentum" is literal.
PortfolioD3
Top 20 by score, equal weight, cap 7.5% of NAV. Keep buffer: a holding stays while it remains in the top 40. No drift trades: nothing under max(AUD 500, 1% NAV) — the AUD 6 minimum commission makes tiny trades ruinous.
Regime filterD4 · binary
Index close above its 200-day SMA → 100% invested. Below → 100% cash. Never relaxed after losses. A graded variant (50% when off) runs as a pre-registered shadow only and may be promoted after the paper gate if its drawdown never exceeded binary's by more than 5 points.
RebalanceD5
First ASX trading day of the month. Signals from the prior close (nightly 17:30 Sydney). DAY limit orders in the continuous session — sells ~10:20, buys ~10:35, offset 0.2% from the prior close. Never in the opening or closing auctions. Unfilled buys are cancelled 15:45 and re-staged, not chased.
StopsD8
No single-name trailing stop (tested: 15% trailing cut CAGR ~4 points for 2 points of drawdown). Portfolio rule: at −20% from peak, no new entries until a review is recorded.
Sector capD9
Config flag, default off. Financials + Materials share of the book is logged monthly. A 40% cap tested neutral.
CostsD12
Per clip: max(0.08% of value, AUD 6) + 0.10% slippage, per side. That is IBKR Australia fixed pricing plus a slippage allowance.
CapitalD11
Practical floor AUD 25k (top 20, ~AUD 1,250 clips). AUD 20k → top 15 at 8% cap. Below AUD 20k the stock-level design is not recommended; an ETF-only variant would be the honest alternative (out of scope).

2. Prototype evidence

Growth of 1 unit (log scale) and drawdown from peak, monthly samples. Hover for values. Generated by asx_momentum.py backtest --capital 25000.

v1 strategy (binary regime, AUD 25k)S&P/ASX 200 price index
Growth of 1 unit, log scale, and drawdown: v1 strategy vs S&P/ASX 200 price index, 2010–2026 1x1.5x2x-10%-20%-30%20112013201520172019202120232025 Drawdown from peak

Variants, same period and cost model

VariantCAGRSharpe (rf 0)Max DDExposureTurnover /mo2020 crash DD
v1 binary regime, AUD 25k, top 204.6%0.39−29.4%67%16%−15.3%
v1 binary, AUD 50k5.5%0.44−27.9%67%16%−15.3%
v1 binary, AUD 20k, top 15 @ 8% cap5.0%0.39−32.8%66%17%
Shadow: graded 50% when regime off, AUD 25k9.4%0.64−26.9%83%14%−26.4%
No regime filter, AUD 25k13.9%0.77−38.5%99%12%−39.0%
S&P/ASX 200 price index3.4%0.30−36.5%100%−36.5%

Read the deltas between rows, not the levels. The 25k vs 50k rows differ only by the AUD 6 minimum. The binary vs graded vs none rows differ only by what happens when the index is under its 200-day average: binary gives up return in recoveries (2012, 2019, 2023) to survive fast crashes (2020). The room chose survival and auditability; the graded variant is tracked in shadow so that choice can be revisited with live data rather than a biased backtest.

Year by year — v1 binary at AUD 25k vs the price index

Year2011201220132014201520162017201820192020202120222023202420252026
v1-2.2%-1.3%+22.7%-1.3%-7.2%+7.0%+17.5%-1.1%+8.3%-3.0%+30.6%-16.5%-8.8%+28.8%+16.4%+6.7%
ASX 200 price-15.8%+14.4%+15.1%+0.4%-3.2%+7.0%+7.4%-6.9%+20.6%-1.5%+13.0%-5.5%+7.8%+7.5%+6.8%+0.3%

Robustness sweep (flat 0.20%/side cost model, graded variant unless stated)

6-month lookback: no better, more turnover. Top 30: slightly higher Sharpe than top 20. Top 10: worse (Sharpe 0.43, deeper drawdown). Doubling costs: about −1 point of CAGR. Trailing 15% stop: CAGR 10.7% → 6.6%. Trailing 25%: 9.4%. Sector cap 40%: neutral. Full numbers in robustness.json below.

Signal snapshot on the publication date (static)

As of the close on 14 September 2026: S&P/ASX 200 at 8,741.2 vs 200-day SMA 8,819.0 → regime OFF → v1 book is 100% cash. The model's top 20 that day, for information only and not a recommendation: SGM, PLS, S32, WOW, NWH, BHP, BSL, RIO, AZJ, REH, LSF, SFR, GGP, MIN, MND, APA, IGO, DBI, WDS, DNL.

3. ASX-specific failure modes and the defence for each

Index concentration

Top 10 constituents were 48.8% of the ASX 200 at 31 Aug 2026, dominated by banks and miners. Defence: 7.5% per-name cap, 20 names, concentration logged; sector cap flag available.

Liquidity and impact

AUD 2m median daily dollar volume gate, AUD 1 price floor, monthly cadence, limit orders in the continuous session only.

Brokerage and the AUD 500 parcel

Per-clip cost model, no drift trades, AUD 25k floor, hard AUD 500 minimum parcel in the executor.

Opening and closing auctions

Pre-open 07:00; opening auction from 09:59 in a randomised, letter-staggered sequence to ~10:09; continuous to 16:00; closing auction 16:10 ± 30 s. The bot never trades in an auction: sends at 10:20 and 10:35.

T+2 and CHESS D15

Sells first, buys second, sized to settled cash plus filled proceeds; 3% cash buffer; margin-type account with zero borrowing so proceeds can be reused. A cash account must wait T+2.

Franking and dividends

Ranking uses dividend-adjusted prices. Franking credits are in no price series; they are an unmodelled, holder-dependent tax item, never counted as return. Nothing here is "franking-aware".

Corporate actions D14

Suspended: held, excluded from ranking, sold after resumption unless re-ranked in. Splits: positions reconciled against IBKR quantities before every plan. Demerger entities and index-removed names: sold at the next rebalance.

Data integrity D10 · D16

Each name's last close is reconciled against the IBKR bar; >0.5% mismatch skips it for the month. Stale index (>3 days) or >10% of the universe failing → no plan, hold everything, alert. The regime flag cannot flip on missing data.

Operator absence D13

The system runs unattended; absence is not an override. One control: a PAUSE file. A missed send is re-run next day and logged; two missed rebalances in six months is a kill.

Crowding D17

12-1 momentum has been public since Jegadeesh & Titman (1993) and an ASX ETF runs a related process at scale. At AUD 25–50k in names trading AUD 2m+ a day, the footprint is negligible.

Secrets D18

.env chmod 600 under a dedicated user; IBKR API bound to 127.0.0.1; no keys in code or logs; account IDs redacted; Telegram token in .env only.

Behaviour

A binary filter sits in cash for months. It is designed to be hard. It is never relaxed after losses — that ruling is on the record.

4. Go-live gate and kill criteria (pre-registered)

Gate — all required before any live capital

  1. Point-in-time walk-forward. Re-run the engine on a historical ASX 200 constituent series (Norgate Data, ~USD 30/mo, inside the AUD 50/mo budget) and publish the result with the script. Until then everything on this page is prototype evidence.
  2. IBKR paper ≥ 6 months on the same code path, ≥ 6 monthly rebalances, ≥ 20 round-trip trades.
  3. Slippage median ≤ 0.15% per side, measured against the prior close on limit orders — not against paper fills, which IBKR itself says are simulated and may differ from live.
  4. Zero unresolved data-reconciliation incidents.
  5. Zero manual overrides.
  6. Then live at 25% of target capital for 3 months before scaling.

Kill — any one

Paper net expectancy ≤ 0 after 6 months · slippage > 2× the assumption · two missed rebalances · the regime filter failed to reduce drawdown versus the index during paper · any manual override.

5. Stack and runbook

Data
yfinance daily adjusted closes for research; IBKR historical bars for reconciliation; ASX L1/L2 via IBKR is AUD 25/mo for non-professionals; Norgate for point-in-time constituents. Total under AUD 50/mo.
Broker
Interactive Brokers Australia. Paper first (Gateway 4002 / TWS 7497); live (4001 / 7496) only after the gate. Fixed pricing ~0.08% with AUD 6 minimum. ib_insync. Live orders need --live and ASX_LIVE=1 — the two-key rule.
Compute
One small VPS (Sydney region), Python 3.11+, two systemd timers: asx-momentum.timer 17:30 Sydney weekdays (refresh, signals, stage the monthly plan) and asx-momentum-send.timer 10:20 Sydney (send the staged plan). Idempotent.
Alerting
events.jsonl (HEARTBEAT, ORDER_SUBMITTED, ORDER_STATUS, RECON MISMATCH, plan summaries) and Telegram via notify.sh on plan staged, failures, mismatches, the drawdown rule and regime flips.

Setup in order

  1. Open an IBKR Australia account and enable paper trading. Install IB Gateway on the VPS, API enabled, bound to 127.0.0.1.
  2. Copy the files below to /opt/asx-momentum, create a venv, pip install yfinance pandas numpy pyarrow ib_insync requests lxml.
  3. python asx_momentum.py fetch (first download, a few minutes) then python asx_momentum.py backtest --capital 25000 — check your numbers match this page.
  4. python asx_momentum.py signals > data/signals_today.json then python ibkr_executor.py --signals data/signals_today.json (dry run) — read the plan by hand.
  5. Create .env (chmod 600) with TG_TOKEN, TG_CHAT; run ./notify.sh hello and see it arrive.
  6. Install the two service/timer pairs, systemctl enable --now both timers. Watch the first nightly HEARTBEAT.
  7. Paper-trade for six months. Record the gate metrics monthly. Do not touch the thresholds.

6. Code and data

The engine in ten lines of intent

score  = (close.shift(21) / close.shift(252) - 1) / (daily_returns.rolling(252).std() * sqrt(252))
score  = score.where(liquid & (mom > 0))                # AUD 2m median $vol, price ≥ 1, absolute leg
regime = index.ffill() > index.ffill().rolling(200).mean()
each month (first trading day, signals from the prior close):
    if not regime: book = {}                              # 100% cash, no exceptions
    else: keep = held ∩ top40; book = (keep + top20)[:20]; w = min(1/20, 7.5%)
    skip any trade < max(AUD 500, 1% NAV)               # no drift trades
    cost per clip = max(0.08% × value, AUD 6) + 0.10%    # IBKR AU fixed + slippage
orders: DAY limit, continuous session only; sells 10:20, buys 10:35

7. How this was decided — the Brainroom record

The design was put to the GAIOS Brainroom (session bs-3824e276045e) on 14 September 2026 with a blind first round: Claude chaired, Grok and Codex took sealed positions, then a critique round and a red-team round. Twenty-six objections were raised and every one was ruled with evidence or an owner. The chair reversed its own initial preference for a graded regime filter after the 2020 crash test; the trailing stop, sector cap and cost model all changed because a member objected and the numbers agreed.

RulingDecision
D1–D3ASX 200 universe with liquidity gate; risk-adjusted 12-1 signal; top 20 equal weight, 7.5% cap, keep-in-top-40 buffer.
D4 (reversed)Binary index regime filter; graded 50% is a pre-registered shadow variant only.
D5Monthly; prior-close signals; limit orders in the continuous session; never auctions.
D8–D9No trailing stop; 20% portfolio drawdown review rule; sector cap flag off, concentration logged.
D10Six-month paper gate with pre-registered thresholds and kill criteria.
D11–D12AUD 25k practical floor; per-clip IBKR cost model; no drift trades.
D13–D16Unattended operation and PAUSE file; corporate-action rules; T+2 sequencing; data-integrity kill.
D17–D20Crowding accepted and labelled; secrets checklist; absolute leg explicit; non-script figures labelled "assumption".

Dissent on record: Grok holds that v1's edge may be thin after live costs and that the point-in-time walk-forward may not pass. The design agrees that is exactly what the gate exists to find out.

8. Self-check before you paper trade

Five questions the agent will also ask you. Click to reveal the ruling.

loading…
ASX Momentum design assistant
Answers only from this page's published design — every answer is independently verified before it is shown.
G'day — ask me anything about the rules, the evidence and its caveats, the failure modes, the go-live gate or the stack. I don't give personal financial advice or stock picks. Verified answers take up to a minute.
Educational assistant by Sabour Khosravi (GoldNet Group). Not financial advice. Questions may be reviewed to improve accuracy. 30 questions per hour.