Apex++GAF-DC2: specification
A momentum rotation in large US stocks with accelerating revenue, plus a gated 3x Nasdaq-100 ETF sleeve, run in a US margin account. Rules, backtest, risks and a daily trade log.
1. What it does
- Universe: stocks that are S&P 500 or Nasdaq-100 members on that date. One share class per company.
- Trend filter: close above its 200-day average, and the 200-day average higher than 20 sessions ago.
- Growth filter: year-over-year revenue growth is higher than in the prior quarter.
- Balance-sheet filters: cash/assets in the top half of recent filers; not in the 10% most distressed.
- Ranking: 50-day over 200-day exponential average of the close, highest first.
- Picks: 4 names per sleeve. A held name stays while it passes the filters and ranks 7 or better. Empty slots go to the highest-ranked names not held.
- Weighting: market cap squared, then scaled so the basket targets 60% annualized volatility (scale capped at 1.5).
- Rebalancing: 4 sleeves of 1/4 equity each. Each sleeve rebalances every 20 sessions, staggered 5 sessions apart. Trades at the next open.
- Leverage: at most 2.0x equity at a rebalance (1.5x stocks + 0.5x TQQQ). Positions drift between rebalances.
- TQQQ sleeve: 0.5x equity while the gate is on, resized monthly. Gate turns off when QQQ's 20-day volatility rises above 32% and back on below 28%.
- Delisting: a held stock whose series ends is cashed at its last close if it was acquired. Any other cause counts as a total loss.
The exact specification is in section 6.
2. Why it works
Momentum and growth. Stocks in strong trends tend to keep going for months. Requiring accelerating revenue drops trends that the business does not back. The cash and distress filters drop fragile balance sheets. Cap-squared weights tilt the book toward the largest leaders.
The post-2008 regime. The backtest starts on 2010-02-11, TQQQ's first trading day, and uses only this regime. Several rules adopted since 2008 slow disorderly selling and favor fast index recoveries:
- Short-sale circuit breaker (Reg SHO Rule 201, the alternative uptick rule). After a stock falls 10% in a day, short sales must be above the national best bid for the rest of that day and the next. Adopted 2010-02-24; compliance from 2011-02-28. SEC release, compliance date.
- Single-stock pauses after the 2010-05-06 Flash Crash: a 5-minute pause after a 10% move in 5 minutes, approved 2010-06-10. Limit Up-Limit Down bands replaced them from 2013-04-08. SEC 2010, SEC 2012, SEC bulletin.
- Market-wide circuit breakers at 7%, 13% and 20% declines in the S&P 500, approved 2012-05-31 and in effect from 2013-04-08. NYSE, FINRA filing.
- Zero rates and a Fed backstop: the fed funds target went to 0-0.25% on 2008-12-16 and again on 2020-03-15; on 2020-03-23 the Fed added open-ended asset purchases and corporate credit facilities. Fed 2008, Fed 2020-03-15, Fed 2020-03-23.
A daily-reset 3x ETF loses most in long, choppy declines, where volatility decay compounds. These rules damp cascades and shorten crashes, so index paths have been smoother and recoveries faster. The gate also exits when volatility spikes. In this regime the TQQQ sleeve is a structural bet, not luck.
Limits: these rules do not stop bear markets. In 2022 the Nasdaq-100 fell about 33% and TQQQ about 79%. Regimes can change.
3. Results
In-sample backtest, 2010-02-11 to 2026-09-18. Returns include 5 bps per side trading costs, borrowing costs and the margin checks below. Strategy rules and the evaluation period were selected using these data, so results carry selection bias.
Two margin models. Headline: the concentration-stress margin model in section 6. Second: Robinhood-style standard margin (25% maintenance on stocks, 75% on TQQQ, 50% initial; borrowing at the fed funds target upper bound + 1.25% to $50,000, + 1.05% to $100,000, + 0.75% to $1,000,000; first $1,000 free; no interest on cash). Both check maintenance at every close and at the daily lows.
| CAGR | MaxDD | Rolling 4y median | Rolling 4y 10th pct | Worst 4y | Margin breaches | |
|---|---|---|---|---|---|---|
| Apex++GAF-DC2 | 89.1% | -59.0% (2011-02-14 to 2011-11-25) | 77.4% | 58.0% | +39.7% | 0 |
| Same, Robinhood-style margin | 85.1% | -56.0% (2011-02-14 to 2011-11-25) | 74.3% | 53.9% | +37.6% | 0 |
Rolling windows: 151 four-year windows, one starting on the first session of each month from 2010-03 to 2022-09. They overlap.
Daily closing values start at $1. ETF benchmarks show buy-and-hold total returns.
Calendar-year returns (headline model):
| 2010 (from 02-11) | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | 2017 | 2018 |
|---|---|---|---|---|---|---|---|---|
| +140% | -34% | +124% | +366% | +37% | +87% | +3% | +173% | -12% |
| 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 (to 09-18) |
|---|---|---|---|---|---|---|---|
| +89% | +270% | +117% | -42% | +119% | +343% | +39% | +214% |
2022 onward (out-of-sample for the 2026-10 signal search). From 2022-01-03 to 2026-09-18 the CAGR is 98.5% (Robinhood-style margin: 96.4%). The rank buffer was chosen on the full history, including these years, so this is not a clean out-of-sample test.
Sensitivity tests. An independent implementation applies each change separately (base 89.7%).
| Test | CAGR |
|---|---|
| Trading costs of 10 bps per side | 88.4% |
| Trading costs of 25 bps per side | 84.6% |
| All borrowing at fed funds + 3.00%, zero interest on cash | 86.5% |
| NVDA excluded | 79.8% |
| Five largest contributors excluded | 46.4% |
| Half target exposure | 47.0% (MaxDD -32.5%) |
Data integrity
- Survivorship: the universe is point-in-time S&P 500 and Nasdaq-100 membership from Sharadar, including later-delisted names.
- Prices: daily open, high, low and close, adjusted for splits and dividends (total return).
- Fundamentals: quarterly figures as originally reported. A report is used from the first trading session after filing.
- Delistings: five held names delisted in the backtest, all acquisitions, cashed at their last close: BDK (2010-03-15), FRX1 (2014-07-01), PLL1 (2015-08-31), ESRX (2018-12-21), XLNX (2022-02-14).
- Costs: 5 bps per side on every trade, margin interest, and maintenance checks at the daily lows.
- In-sample: rules, parameters and the start date were chosen on these data.
- Concentration: five names (NVDA, MU, PLTR, TSLA, MRNA) supply 52% of the stock book's positive log-return contribution. Without them the CAGR is 46.4%.
4. Risks
- Drawdowns: -59% in the backtest; -42% in 2022. Expect deeper ones.
- Leverage: up to 2x at rebalances, more after drift. A gap down past the margin buffer forces sales at the open.
- Concentration: one stock can exceed 100% of equity (ESRX reached 115% in 2018). A single-name crash hits hard.
- TQQQ: daily reset. A choppy decline while the gate is on can lose most of the sleeve.
- Regime: the backtest covers one rate and market-structure regime (2010+). It may not repeat.
- Overfitting: the rules were selected on the same data. Live returns are likely lower.
- Delisting: a non-acquisition delisting is a total loss of that position.
5. Trade log
Daily trade log: every backtest trading day (date, ticker, buy or sell, weight before and after as % of equity, reason) and the live log, updated after each nightly run. Weights only; no prices, share counts or account amounts.
6. Exact specification
Data
| Item | Required data |
|---|---|
| Stock prices | Daily open, high, low, close, adjusted for splits and dividends (total return) |
| Universe | Point-in-time S&P 500 and Nasdaq-100 membership (constituents as of each date, including later-delisted names) |
| Market cap | Point-in-time company market cap (all share classes) |
| QQQ, TQQQ | Daily total-return open, high, low, close |
| Fundamentals | Quarterly figures as originally reported: revenue, assets, cash, debt, net income, current ratio, calendar quarter and filing date |
| Fed funds | Effective federal funds rate |
| Period | 2010-02-11 (first TQQQ session) to 2026-09-18 |
Account and costs
- Starting equity $100,000. On 2010-02-11 (session 0) the TQQQ sleeve and stock sleeve 0 are filled at the open, decided on the 2010-02-10 close. Sleeves 1-3 hold cash until their first rebalance (2010-02-19, 2010-02-26, 2010-03-05).
- Trading cost: 5 bps per side on traded notional. No taxes.
- Modeled borrowing cost: fed funds + 1.50% on the first $100,000 borrowed, + 1.00% up to $1,000,000, + 0.75% above. Blended, 360-day year.
- Cash credit: fed funds - 0.50% on cash above $10,000; when equity is below $100,000, scaled by equity / $100,000.
Timing
| Item | Decided on | Trades at |
|---|---|---|
| Stock sleeve picks, weights, vol scale | close of day t-1 | open of day t |
| Stock sleeve rebalance | every 20 sessions per sleeve | open |
| TQQQ vol gate | every session, close of day t-1 | open of day t, the session after it flips |
| TQQQ resize to 0.5 x equity while on | 11th session of each month | open |
Rules
STOCK BOOK: 4 sleeves, each 1/4 of equity
Sleeve k (k = 0..3) rebalances on sessions 5k, 5k+20, 5k+40, ...
counted from 2010-02-11 (session 0).
On a sleeve's rebalance, using data through the prior close:
universe = point-in-time S&P 500 + Nasdaq-100 members; one share
class per company (the higher-ranked class)
eligible = close > SMA200 and SMA200 > its value 20 sessions earlier
and growth_accel > 0 (see Growth-acceleration filter)
and cash_ok and distress_ok (see Cash and distress filters)
rank = EMA50 / EMA200 of the close, highest first, eligible names only
picks = keep each name the sleeve already holds while it is eligible
and ranked 7 or better; fill the remaining slots, up to 4
names, with the highest-ranked eligible names not yet held
w_i = cap_i^2 / sum of cap^2 over the picks
(cap = point-in-time company market cap)
basket_vol = sample stdev (ddof 1) of the last 20 daily returns of the
portfolio sum(w_i * r_i), annualized x sqrt(252)
(r = simple daily total returns of the picks)
scale = clip(0.60 / basket_vol, 0.0, 1.5)
target_i = (equity at today's open / 4) * w_i * scale
Between rebalances, sleeve positions are not traded (they drift).
TQQQ SLEEVE
qqq_vol = sample stdev (ddof 1) of the last 20 daily QQQ total returns
x sqrt(252), on the prior close
gate starts ON
if ON and qqq_vol > 0.32: OFF -> sell all TQQQ at the open
if OFF and qqq_vol < 0.28: ON -> buy TQQQ to 0.50 x equity at the open
(between 0.28 and 0.32 the gate keeps its state)
While ON, also trade TQQQ to 0.50 x equity on the 11th session of
each month.
CASH
cash = equity - stocks - TQQQ. Negative cash is a margin loan.
At each rebalance at most 2.0x equity (1.5 stocks + 0.5 TQQQ); positions
drift between rebalances.
DELISTING
A held stock whose price series ends is converted to cash at the next
open: at its last close when the delisting is an acquisition or merger
(the deal consideration, cash and/or acquirer stock), and at 0 for any
other cause (bankruptcy, regulatory or voluntary delisting, unknown),
per the corporate-actions records. A stock with a gap in its
prices that later trades again is converted at its last close.
Growth-acceleration filter
Use revenue as originally reported for each calendar quarter. In the formulas, calendardate identifies the quarter and datekey is the filing date. A report becomes available on the first trading session after filing.
For each company, one report per calendardate quarter (if duplicated, keep the earliest datekey).
growth(q) = revenue(q) / revenue(q-4) - 1 (requires revenue(q-4) > 0)
growth_accel(q) = growth(q) - growth(q-1)
q-k = the report whose calendardate is exactly k quarters before q, and
whose datekey is on or before q's datekey. Otherwise growth_accel is missing.
Point in time:
A report's value is usable from the first trading session strictly after its datekey.
It is carried forward until the company's next report becomes usable,
for at most 315 sessions; after that it is missing.
A secondary share class with no SF1 rows uses its company key's rows (GOOG -> GOOGL).
Eligibility: growth_accel > 0.
A missing value makes the stock ineligible.
The filter only removes names; the rank stays EMA50 / EMA200.
Cash and distress filters
Use quarterly reports as originally filed. The comparison pool is reports filed in the preceding 90 calendar days by companies already in the index universe by the filing date. Share classes map to one company; GOOG uses GOOGL's reports.
For each company, one report per calendardate quarter (if duplicated, keep the earliest date).
Per report (dk = its filing date):
A = assets (missing unless assets > 0)
cash = cashneq / A
distress = debt / A - netinc / A - currentratio
Any missing input -> that value is missing.
Flag, fixed once at filing:
known(c, dk) = company c (its company key, the primary share class) was in the
point-in-time stock universe -- an S&P 500 or Nasdaq-100 member
with a price on that session, exactly the universe the stock
selection uses -- on at least one trading session dated on or
before dk (any share class of c counts)
pool_x(dk) = the reports of the report universe with a non-missing x, a filing
date in [dk - 90 calendar days, dk], and a company c with
known(c, dk) (the report being flagged is in its own pool only
if its company is known by dk; it is flagged either way)
cash_ok = cash > 50th percentile of pool_cash(dk)
distress_ok = distress < 90th percentile of pool_distress(dk)
Percentile: linear interpolation between closest ranks (numpy.percentile default).
A report with a missing cash (distress) value has no cash (distress) flag; it is
skipped, and the company's previous report with a value keeps applying.
A report whose pool is empty has no flag (possible only before 1998-01-02).
Point in time (as GAF, per flag):
A report's flag is usable from the first trading session strictly after dk.
It is carried forward until the company's next report with that value becomes
usable, for at most 315 sessions; after that it is missing.
A secondary share class with no SF1 rows uses its company key's rows.
Eligibility (added to Apex++GAF's): cash_ok AND distress_ok.
A missing flag makes the stock ineligible.
A filtered stock is neither bought nor kept by the buffer.
Margin and concentration assumptions
These formulas set the headline backtest's limits on borrowing and concentrated positions. Real brokers' leverage and financing can differ.
maintenance = max( 0.25 x stock value + 0.75 x TQQQ value,
div(n) x sum_i stress_i x stock_value_i )
n = number of distinct companies held
div(n) = 1.00 for n <= 2, 0.95 for 3-5, 0.75 for 6-9, 0.60 for 10+
stress_i = min(1, max(0.30,
0.30 + 0.065 x max(0, ret1y_i - 0.50),
0.50 x vol30_i))
ret1y_i = 252-session total return
vol30_i = population stdev (ddof 0) of the last 30 daily log returns x sqrt(252)
initial margin on any purchase:
equity >= max(0.50 x long market value, maintenance rule sum, 1.1 x stress term)
after the trade; purchases are scaled down pro rata to satisfy it.
maintenance check: at every close, and at the daily lows (all positions
marked at their low). A deficit is a breach: at the next open, sell all
positions pro rata until equity >= 1.10 x maintenance.
7. Changelog
Rule change, 2026-10-10: rank buffer 12 to 7. A held name now stays only while it ranks 7 or better. A sweep of buffers 4 to 15 (Robinhood-style margin, to 2026-10-02) gave full-period CAGRs of 77-81% except at 6 and 7 (85.7%, 85.9%), a narrow bump. Buffer 7 beat buffer 12 in 77% of rolling four-year windows (median 74.4% vs 70.4%; 10th percentile 53.5% vs 46.2%). Without the five largest contributors, buffer 7 did slightly worse (43.5% vs 45.7%), so the gain leans on the top names. Buffer 7 was selected on historical data; treat the improvement as in-sample. Headline CAGR moves from 85.0% to 89.1%; maximum drawdown from -57.2% to -59.0%.