Apex++GAF-DC2: specification
A momentum rotation in S&P 500 and Nasdaq-100 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 cubed, then scaled so the basket targets 70% annualized volatility (scale capped at 1.5). Market cap (daily, point-in-time, all share classes of the company) is used only for weighting, never for selection.
- Rebalancing: 4 sleeves of 1/4 equity each. Each sleeve rebalances every 20 sessions, staggered 5 sessions apart. The backtest 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.
Each trading day
One run before the open:
- About 9:00 AM ET: update the data to the prior close and check it; compute the TQQQ gate and which sleeves rebalance today; read the account; build the day's order list before the 9:30 open. If the data are stale or invalid, the order list is not ready by 9:30, or any check raises an alert, there is no trading that day.
- 9:30 to 10:00 AM ET: buys and sells are worked during the first 30 minutes to beat the opening price, within fixed price and size limits. Sells go first. Anything still open at 10:00 is completed then.
- About 10:30 AM ET: fills are compared with the official open and the trade log is updated.
- Stock sleeves: 4 sleeves, each rebalancing every 20 trading days, staggered 5 trading days apart, so one sleeve trades about once a week.
- TQQQ sleeve: while the gate is on, resized to 0.5x equity on the 11th trading day of each month. The gate turns off when QQQ's 20-day volatility closes above 32% (sell all TQQQ at the next session) and back on when it closes below 28% (buy back to 0.5x equity at the next session).
- A missed day does not change the rules; the next run works from the account as it stands.
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-cubed 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 covers only this regime. Rules and policies in effect during it:
- 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 cut the federal funds target range to 0-0.25% on 2008-12-16 and kept it there until 2015-12-16. It cut to 0-0.25% again on 2020-03-15 and kept it there until 2022-03-16. On 2020-03-23 it added open-ended asset purchases and corporate credit facilities. Fed 2008-12-16, Fed 2015-12-16, Fed 2020-03-15, Fed 2020-03-23, Fed 2022-03-16.
A daily-reset 3x ETF loses most in long, choppy declines, where volatility decay compounds. The trading rules above are designed to slow disorderly selling in single stocks and the whole market. The gate takes the sleeve out when QQQ's volatility rises.
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%, stepping down to + 1.05% and then + 0.75% as the loan grows, with a small first slice of the loan 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 | 97.8% | -59.2% (2011-02-14 to 2011-11-25) | 85.0% | 61.1% | +40.3% | 1 |
| Same, Robinhood-style margin | 93.8% | -57.3% (2024-12-24 to 2025-04-08) | 81.7% | 57.2% | +38.5% | 0 |
The headline model's one margin breach is at the daily low of 2021-03-05; the backtest sells pro rata at the next open to restore maintenance (section 6) and continues.
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 as a multiple of starting capital (start = 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 |
|---|---|---|---|---|---|---|---|---|
| +144% | -35% | +123% | +404% | +33% | +89% | +7% | +170% | -11% |
| 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 (to 09-18) |
|---|---|---|---|---|---|---|---|
| +86% | +348% | +138% | -40% | +114% | +439% | +44% | +267% |
2022 onward. From 2022-01-03 to 2026-09-18 the CAGR is 115.9% (Robinhood-style margin: 111.5%). The rank buffer, the volatility target and the weighting power were chosen on the full history, including these years, so this is not a clean out-of-sample test.
Sensitivity tests. Each change applied separately (base 98.4%).
| Test | CAGR |
|---|---|
| Trading costs of 10 bps per side | 97.0% |
| Trading costs of 25 bps per side | 92.9% |
| All borrowing at fed funds + 3.00%, zero interest on cash | 95.0% |
| NVDA excluded | 86.6% |
| Five largest contributors excluded | 48.0% |
| Half target exposure | 51.2% (MaxDD -32.7%) |
How it was tested
- Period: 2010-02-11 (TQQQ's first trading day) to 2026-09-18 only, so every result comes from the rate and market-structure regime described above. Earlier years are used only to check the TQQQ gate, on a simulated 3x series.
- Universe: survivorship-bias-free, point-in-time S&P 500 and Nasdaq-100 membership, including later-delisted names. Prices are adjusted for splits and dividends.
- Filings: quarterly figures as originally reported, each used only from the first trading session after it was filed.
- Delistings: five held names were delisted, all acquisitions, cashed at their last close. Any other kind of delisting counts as a total loss.
- Costs and margin: 5 bps per side, margin interest, and maintenance checked at every close and at the daily lows under two broker margin models. Robinhood-style margin: no breach in the full run or in any fresh four-year start below. Headline model: one breach in the full run (2021-03-05), and at least one in 47 of the 151 fresh four-year starts.
- Train, then check: the rules were tuned on 2010-2021 and checked on 2022 onward (CAGR 115.9% since 2022-01-03). The rank buffer, the volatility target and the weighting power were later chosen on the full history, so 2022 onward is no longer a clean test.
- Fresh starts: 151 four-year windows, each a new account, starting every month from 2010-03 to 2022-09: median 84.0% a year, worst +39.2% (Robinhood-style margin: 81.6%, +37.3%).
- Walk-forward: re-choosing the rules each January from 2014 to 2025, using only earlier years, gives 77.0% a year, against 80.2% for the current rules held fixed. The rank buffer, the stock volatility target and the weighting power were chosen on the full history.
- Nearby settings: TQQQ gate thresholds from 26% to 34%, with 0 to 4 points of hysteresis, all give 92-97% CAGR (Robinhood-style margin). The volatility target trades return for drawdown smoothly. Raising the weighting power adds return smoothly and puts more weight in the largest pick. The rank buffer is less smooth: buffers 6 and 7 sit about 5 points above their neighbours.
- Multiple testing: about 380 variants were tested in the latest research rounds and 377 were rejected. Results were deflated for the number of trials (deflated Sharpe ratio); no new signal passed. The three adopted changes (rank buffer 7, 70% volatility target, cubed weighting) are a holding rule, a leverage setting and a weighting setting. The cubed weighting did not pass the deflated test either; it rests on smooth results across nearby settings and the walk-forward.
- Concentration: five names (NVDA, MU, PLTR, TSLA, MRNA) supply 55% of the stock book's positive log-return contribution. Without them the CAGR is 48.0%. Most of the gain from cubed weighting comes from five years (2013, 2020, 2021, 2024, 2026).
4. Risks
- Drawdowns: -59% in the backtest; -40% 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 (AMZN reached 193% in 2015; 184% with squared weighting). Cubed weighting puts more in the largest pick. 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 daily 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 | Daily point-in-time company market cap (all share classes); used only for weighting |
| 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
- One account from the start. 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%, stepping down to + 1.00% and then + 0.75% as the loan grows. Blended, 360-day year.
- Cash credit: fed funds - 0.50% on cash above a small threshold, scaled down while equity is below starting equity.
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^3 / sum of cap^3 over the picks
(cap = daily point-in-time company market cap, all share
classes; used only for weighting, never for selection)
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.70 / 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 fundamentals 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 fundamentals 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
- 2026-10-11: weighting power raised from squared to cubed (market cap^3). Backtest CAGR 93.1% -> 97.8% (Robinhood-style margin 88.9% -> 93.8%).
- 2026-10-11: stock volatility target 60% -> 70%.
- 2026-10-10: rank buffer 12 to 7 (a held name stays while it ranks 7 or better).
- 2026-10-10: delisting rule cause-aware (acquisitions cashed at the last close, other delistings a total loss).
- 2026-10-10: cash and distress filters compare each stock only with filings available at the time.