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

The exact specification is in section 6.

Each trading day

One run before the open:

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:

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.

CAGRMaxDDRolling 4y medianRolling 4y 10th pctWorst 4yMargin breaches
Apex++GAF-DC297.8%-59.2% (2011-02-14 to 2011-11-25)85.0%61.1%+40.3%1
Same, Robinhood-style margin93.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.

Growth (multiple of starting capital)2010-02-11 to 2026-09-18. Daily closing values as a multiple of the first close. Strategy includes trading costs and financing; ETFs show buy-and-hold total returns. image/svg+xml Independent strategy charts 2010 2012 2014 2016 2018 2020 2022 2024 2026 1× 10× 100× 1,000× 10,000× 100,000× Multiple of starting capital (log scale) Growth (multiple of starting capital) Apex++GAF-DC2 SPY QQQ TQQQ

Daily closing values as a multiple of starting capital (start = 1×). ETF benchmarks show buy-and-hold total returns.

Rolling four-year CAGRMonthly starts from March 2010 to September 2022. Each window uses the existing portfolio at the first session of the month and ends on the first session at least four calendar years later. Overlapping windows are dependent. image/svg+xml Independent strategy charts 2012 2014 2016 2018 2020 2022 Window start 0% 20% 40% 60% 80% 100% 120% 140% 160% Annualized return Rolling four-year CAGR Apex++GAF-DC2 SPY QQQ TQQQ

Calendar-year returns (headline model):

2010 (from 02-11)20112012201320142015201620172018
+144%-35%+123%+404%+33%+89%+7%+170%-11%
20192020202120222023202420252026 (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%).

TestCAGR
Trading costs of 10 bps per side97.0%
Trading costs of 25 bps per side92.9%
All borrowing at fed funds + 3.00%, zero interest on cash95.0%
NVDA excluded86.6%
Five largest contributors excluded48.0%
Half target exposure51.2% (MaxDD -32.7%)

How it was tested

4. Risks

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

ItemRequired data
Stock pricesDaily open, high, low, close, adjusted for splits and dividends (total return)
UniversePoint-in-time S&P 500 and Nasdaq-100 membership (constituents as of each date, including later-delisted names)
Market capDaily point-in-time company market cap (all share classes); used only for weighting
QQQ, TQQQDaily total-return open, high, low, close
FundamentalsQuarterly figures as originally reported: revenue, assets, cash, debt, net income, current ratio, calendar quarter and filing date
Fed fundsEffective federal funds rate
Period2010-02-11 (first TQQQ session) to 2026-09-18

Account and costs

Timing

ItemDecided onTrades at
Stock sleeve picks, weights, vol scaleclose of day t-1open of day t
Stock sleeve rebalanceevery 20 sessions per sleeveopen
TQQQ vol gateevery session, close of day t-1open of day t, the session after it flips
TQQQ resize to 0.5 x equity while on11th session of each monthopen

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