Portfolio System with Relative Strength Levy and Cast Out Rank




Hello everyone,

I’m relatively new and currently trying to build a simple relative strength model based on Levy for the Russell 1000 universe.

My goal is straightforward:

  • Use a relative strength metric defined as Close / SMA(130)
  • Apply a market filter using SPY with two moving averages to avoid major drawdowns
  • Select the Top 10 stocks based on relative strength
  • Use a Cast-Out Rank of 20%
  • The Russell 1000 stocks are organized in a watchlist, which also includes SPY for the market filter

Unfortunately, I’m currently not getting any results in my backtest.

I’m sharing my code and settings (screenshots) and would really appreciate any help. Feel free to adjust or improve the code as needed.

Thanks in advance for your support!

// ============================================================
// Relative Strength System after Levy (Close / SMA130)
// Universe  : Russell 1000 Watchlist (set in AA window)
// Entry     : Top 10 stocks by Levy RS score
// Exit 1    : Cast-Out-Rank (rank drops below threshold)
// Exit 2    : Crisis filter - SPY SMA5 < SPY SMA200
// ============================================================

// --- Parameters ---
MaxPositions     = 10;
CastOutPct       = Optimize( "CastOutPct", 20, 5, 50, 5 );
LevyPeriod       = 130;

// --- Levy Relative Strength ---
LevyRS = Close / MA( Close, LevyPeriod );

// --- Cross-sectional Ranking (Rank 1 = strongest) ---
StaticVarSet( "LevyRS_" + Name(), LevyRS );
StaticVarGenerateRanks( "rank_LevyRS_", "LevyRS_", 0, 1 );
CurrentRank      = StaticVarGet( "rank_LevyRS_" + Name() );
CastOutThreshold = MaxPositions * ( 1 + CastOutPct / 100 );

// --- Crisis Filter: SPY SMA5 > SMA200 ---
SpyClose  = Foreign( "SPY", "C" );
MarketOK  = IIf( BarIndex() > 200, MA( SpyClose, 5 ) > MA( SpyClose, 200 ), 0 );

// --- Signals ---
Buy   = CurrentRank <= MaxPositions AND MarketOK;
Sell  = CurrentRank > CastOutThreshold OR NOT MarketOK;
Short = 0;
Cover = 0;

// --- Execution & Sizing ---
BuyPrice     = Open;
SellPrice    = Open;
PositionSize  = -10;
PositionScore = LevyRS;

// --- Portfolio Settings ---
SetOption( "MaxOpenPositions",       MaxPositions );
SetOption( "AllowPositionShrinking", True );

You are not using StaticVarGenerateRanks correctly. You should set the static variables for all symbols before that function gets called. Look for examples in the AmiBroker Help pages or in this forum.

If you're still not getting trades after fixing that issue, then use an Exploration to check that the values of the variables that affect entry and exit are what you expect them to be.

2 Likes

@mradtke thank you can i also use the. Rotational system for this?

I don’t think the built-in rotational mode will handle all your rules exactly as written, for example the 20% Cast Off rule. AmiBroker uses Worst Rank Held, which I believe needs to be a single numeric value rather than a potentially different value on each bar.

You can do pretty much everything in custom backtester and yes custom backtester works in rotational mode too.

As to the code - you need to understand what you wrote (or what AI wrote for you). To do so you much learn basic debugging / analysis skills.

To get better understanding of what is happening in your code and how functions work, use advice given here:

1 Like

I read the help in amibroker and tried this code:

// ============================================================
// Relative Strength System after Levy - enhanced
// Universe : Russell 1000 Watchlist (set in AA window)
// Entry : Top 10 stocks by smoothed Levy RS score
// Exit 1 : Rank drops below WorstRankHeld (Cast-Out)
// Exit 2 : Crisis filter - SPY SMA5 < SPY SMA200
// Filter : Minimum volume to exclude frozen/takeover stocks
// Mode : Rotational - no manual buy/sell signals needed
// ============================================================

// --- Rotational Mode ---
SetBacktestMode( backtestRotational );

// --- Optimizable Parameters ---
CastOutRank = Optimize( "CastOutRank", 200, 50, 300, 10 );
LevyPeriod = Optimize( "LevyPeriod", 130, 50, 250, 10 );

// --- Fixed Parameters ---
MaxPositions = 10;
Smoothing = 5; // SMA smoothing for Levy RS
CrisisShort = 5; // fixed short SMA for crisis filter
CrisisLong = 200; // fixed long SMA for crisis filter
MinVolume = 500000; // minimum avg daily volume
MinPrice = 5; // minimum price

// --- Portfolio Settings ---
SetOption( "MaxOpenPositions", MaxPositions );
SetOption( "WorstRankHeld", CastOutRank );
SetPositionSize( 10, spsPercentOfEquity );

// --- Levy Relative Strength with Smoothing ---
RawLevyRS = Close / MA( Close, LevyPeriod );
LevyRS = MA( RawLevyRS, Smoothing );

// --- Frozen / Takeover Stock Filter ---
AvgRangePct = MA( High - Low, 20 ) / Close * 100;

StockOK = MA( Volume, 20 ) > MinVolume
AND AvgRangePct > 0.5
AND Close > MinPrice;

// --- Crisis Filter: SPY SMA5 > SMA200 (fixed) ---
SpyClose = Foreign( "SPY", "C" );
MarketOK = IIf( BarIndex() > CrisisLong,
MA( SpyClose, CrisisShort ) > MA( SpyClose, CrisisLong ),
0 );

// --- Position Score ---
PositionScore = IIf( MarketOK AND StockOK, LevyRS, 0 );

Don't forget those code tags!

You said you tried that code. Did it work? Do you have a question?

2 Likes

I will answer in Weekend - amibroker is very!!! Powerful.

I have to spend more time here to learn!

Thanks for all help by now i will answer more detailed.

And i will Tag the Code right.

This is my final code - i was missunderstanding the rotational mode.

any idea how i can reduce the draw down of 42 % ?

// ============================================================
// Relative Strength System after Levy - FINAL
// Universe  : Russell 1000 Watchlist (set in AA window)
// Entry     : Top 10 stocks by Levy RS score
// Exit 1    : Rank drops below CastOutRank
// Exit 2    : Crisis filter - SPY SMA5 < SPY SMA200
// Filter    : Illiquidity (High = Low = frozen/takeover stock)
// Mode      : Rotational - LONG ONLY
// Investment: 10% equity per position
// ============================================================

// --- Rotational Mode ---
SetBacktestMode( backtestRotational );

// --- Optimizable Parameters ---
LevyPeriod  = Optimize( "LevyPeriod",  130,  50, 250, 10 );
CastOutRank = Optimize( "CastOutRank", 230, 100, 300, 10 );

// --- Fixed Parameters ---
MaxPositions    = 10;
CrisisShort     = 5;    // fixed short SMA for crisis filter
CrisisLong      = 200;  // fixed long  SMA for crisis filter
IlliqPeriod     = 20;   // lookback for illiquidity check
IlliqMaxDays    = 3;    // max allowed illiquid days in period

// --- Portfolio Settings ---
SetOption( "MaxOpenPositions", MaxPositions );
SetOption( "WorstRankHeld",    CastOutRank  );
SetOption( "AllowSameBarExit", False        );
SetOption( "MaxOpenLong",      MaxPositions );
SetOption( "MaxOpenShort",     0            );
SetPositionSize( 10, spsPercentOfEquity     );

// --- Levy Relative Strength ---
LevyRS = Close / MA( Close, LevyPeriod );

// --- Illiquidity Filter ---
// High = Low means no price movement = frozen/takeover stock
Illiq       = High == Low;
Summe_Illiq = Sum( Illiq, IlliqPeriod );
StockOK     = Summe_Illiq <= IlliqMaxDays;

// --- Crisis Filter: SPY SMA5 > SMA200 ---
SpyClose = Foreign( "SPY", "C" );
MarketOK = IIf( BarIndex() > CrisisLong,
                MA( SpyClose, CrisisShort ) > MA( SpyClose, CrisisLong ),
                0 );

// --- Position Score ---
PositionScore = IIf( MarketOK AND StockOK, Max( LevyRS, 0 ), 0 );

I tested the 3 D Optimation Graph... next i will learn to make a walk forward and test the system of clenow.

Your code comments say that you tested against the Russell 1000 index, but there's nothing in your code that checks whether a stock is a member of the index on the day of entry. Based on this, I believe you are testing against the current Russell 1000 constituents, which introduces a lot of lookahead bias into your backtest results. If you use data from Norgate Data, you can easily test for index membership on a historical basis. I do not know of any other AmiBroker-friendly data providers that offer this feature.

The smallest value you tested for Worst Rank Held was 100. That still seems very high to me for a rotational strategy that only holds 10 positions at a time. If you want to reduce drawdowns, I would test WRH values between 10 and 100.

1 Like

Hello, @mradtke thank you very much for your message and for the information about survivorship bias.
I am currently using a German data provider from InFront, which does not allow me to exclude survivorship bias. To address this, I would need Northgate data.
How much do you think the results would differ if I were able to test using historical index memberships?
Thank you very much for your input and feedback.

The impact of the bias will depend on when your backtest starts. The further back in time that you go, the more unrealistic the current index membership becomes.

For example, here are the results for four backtests:

  1. 2020-Present using current R1000
  2. 2020-Present using historically accurate R1000 members
  3. 2010-Present using current R1000
  4. 2010-Present using historically accurate R1000 members

1 Like

Wow thats crazy! The DrawDown looks relativ stable but the effect on CAR is crazy impact. So i think i need norgate data - without i train the system on the historic winners.... and the robust 3D tool gives me wrong stable results cause i use wrong data. (survivors)

thank you very much for this point to share with me.

Make sure to purchase the Norgate package that includes historical index constituency. I believe it's their Platinum package or higher.

1 Like


this right?

Yes - Platinum for Historical Index Constituents.

1 Like

Practical Guide: Finding Optimal Parameters for the Levy RSL System in AmiBroker
Available Data & Split
Total data available : 1995 - 2026

Split:
In-Sample (IS) : 01/01/1995 - 31/12/2015 (20 years)
Out-of-Sample (OOS): 01/01/2015 - today (11 years)

IMPORTANT: Do NOT look at OOS data until Step 6!
β†’ OOS data is sacred - only used once at the very end
Step 1 β€” AmiBroker Settings
AA Window:
β†’ Apply to : SP500 Watchlist
β†’ Range : 01/01/1995 to 31/12/2015
β†’ Periodicity : Daily

Settings β†’ General Tab:
β†’ Initial Equity : 1,000,000 USD
β†’ Positions : Long only
β†’ Allow same bar exit: OFF

Settings β†’ Portfolio Tab:
β†’ Max Open Positions : 10
β†’ Limit trade size : 0 (no limit)

Settings β†’ Trades Tab:
β†’ Buy Delay: 1 Price: Open
β†’ Sell Delay: 1 Price: Open
Step 2 β€” Robustness Analysis (Optimization)
Start the optimization:
AA Window β†’ Optimize (dropdown arrow) β†’ Settings:
β†’ Method : Exhaustive
β†’ Optimization Target : Ulcer Performance Index (UPI)

Parameters to optimize:
β†’ LevyPeriod : Start 50, Stop 300, Step 10 (26 values)
β†’ CastOutRank : Start 10, Stop 250, Step 10 (25 values)
β†’ Total combinations: 26 Γ— 25 = 650 β†’ fast!

β†’ Click Optimize
Step 3 β€” Plateau Analysis
After optimization is complete:
AA Window β†’ Results Tab β†’
β†’ Right-click β†’ 3D Surface Chart

X-Axis : LevyPeriod
Y-Axis : CastOutRank
Z-Axis : UPI (Ulcer Performance Index)

What you are looking for:
β†’ Flat plateau = robust parameters βœ“
β†’ Sharp peak = overfitting βœ—
β†’ Never pick the absolute peak value!
β†’ Always pick the CENTER of the plateau
Manual stability check:
Test these 9 combinations individually:

LevyPeriod : 90 / 100 / 110
CastOutRank : 130 / 140 / 150

β†’ UPI values similar across all 9 = robust βœ“
β†’ UPI values vary widely = fragile βœ—

Example:
LevyPeriod=90, CastOutRank=130 β†’ UPI 2.8
LevyPeriod=100, CastOutRank=140 β†’ UPI 2.9 ← peak
LevyPeriod=110, CastOutRank=150 β†’ UPI 2.7
β†’ all similar = plateau confirmed = robust βœ“
Step 4 β€” Walk Forward Test
Settings in AmiBroker:
AA Window β†’ Walk Forward Tab:

In-Sample Period : 10 years (120 months)
Out-of-Sample : 2 years (24 months)
Step : 2 years (24 months)
Anchored : NO (rolling window)

Optimization Target: UPI
How it runs:
IS: 1995-2005 β†’ OOS: 2005-2007
IS: 1997-2007 β†’ OOS: 2007-2009 ← contains financial crisis
IS: 1999-2009 β†’ OOS: 2009-2011
IS: 2001-2011 β†’ OOS: 2011-2013
IS: 2003-2013 β†’ OOS: 2013-2015
β†’ OOS periods chained together = real forward test
What to evaluate:
Walk Forward Efficiency = OOS UPI / IS UPI * 100

70% = good β†’ use these parameters βœ“
50% = acceptable β†’ use these parameters βœ“
< 50% = overfitting β†’ reject system βœ—

Additionally check:
β†’ Are OOS parameters stable across periods?
(not completely different every time)
β†’ Is OOS equity curve profitable?
β†’ No extended losing periods in OOS?
Step 5 β€” Select Final Parameters
From Walk Forward Report:
β†’ Check which parameters were selected
most frequently across all OOS periods

Example:
OOS 2005-2007 : LevyPeriod=100, CastOutRank=140
OOS 2007-2009 : LevyPeriod=110, CastOutRank=150
OOS 2009-2011 : LevyPeriod=100, CastOutRank=140
OOS 2011-2013 : LevyPeriod=90, CastOutRank=140
OOS 2013-2015 : LevyPeriod=100, CastOutRank=130

β†’ LevyPeriod 100 = most frequent β†’ select 100
β†’ CastOutRank 140 = most frequent β†’ select 140
β†’ These are your final parameters
Step 6 β€” Final OOS Test (once only!)
Now open the OOS data for the first time:
β†’ Range: 01/01/2015 to today
β†’ Run backtest with final parameters
β†’ NO further adjustments allowed!

Evaluation criteria:
β†’ OOS UPI similar to IS UPI = system is robust βœ“
β†’ OOS equity curve profitable = system is tradeable βœ“
β†’ OOS drawdown acceptable = system is safe βœ“
β†’ OOS Annual Return > 0 = system works forward βœ“
Step 7 β€” Monte Carlo Simulation
AA Window β†’ after final backtest β†’
Report β†’ Monte Carlo Tab:

Settings:
β†’ Simulations : 1,000
β†’ Confidence : 95%

What to check:
Percentile 5% β†’ Worst Case scenario
β†’ Is drawdown still acceptable?
β†’ Is return still positive?

Percentile 50% β†’ realistic normal case
β†’ this is your real expectation for live trading

Decision:
β†’ Worst Case drawdown acceptable? β†’ system ready βœ“
β†’ Worst Case drawdown too large? β†’ reduce position size
Decision Tree Summary
Step 1: Robustness Analysis
β†’ Flat plateau found?
NO β†’ widen parameter range, repeat
YES β†’ continue

Step 2: Walk Forward
β†’ WF Efficiency > 50%?
NO β†’ reject system, redesign
YES β†’ continue

Step 3: OOS Test
β†’ Profitable with acceptable drawdown?
NO β†’ reject system, do not trade
YES β†’ continue

Step 4: Monte Carlo
β†’ Worst Case (5%) still acceptable?
NO β†’ reduce position size to 5,000 USD
YES β†’ system is ready for live trading βœ“
Question to Experienced Trading System Experts
The workflow described above represents my current approach to developing and validating the Levy RSL momentum system in AmiBroker. I would greatly appreciate feedback from experienced systematic traders and system developers on the following questions:

  1. Is this workflow practical and complete?
    Am I missing any critical validation steps before
    going live with this momentum rotation system?
  2. Walk Forward settings
    Is a 10-year IS / 2-year OOS rolling window
    appropriate for a weekly momentum system?
    Or would a different ratio be more suitable
    given the weekly trading frequency?
  3. Optimization target
    I am using UPI (Ulcer Performance Index) as
    the optimization target. Would you recommend
    a different metric for a momentum rotation
    system with a long-only bias?
  4. Parameter space
    With only 2 parameters (LevyPeriod and CastOutRank)
    and 650 combinations, is Exhaustive optimization
    sufficient or would you recommend
    Smart Optimization (CMA-ES) regardless?
  5. Data split
    Given data from 1995 to 2026, I am using
    1995-2015 as IS and 2015-2026 as OOS.
    Is this split appropriate or would you
    recommend a different approach given that
    market structure has changed significantly
    since 1995?
  6. Crisis filter validation
    The system uses SPY SMA5 > SMA200 as a
    crisis filter. Should this filter also be
    validated separately through its own
    robustness analysis, or is it sufficient
    to keep it fixed at these classic values?
  7. Live trading transition
    After all validation steps are passed,
    what additional precautions would you
    recommend before committing real capital
    to this system?
    Any specific position sizing or drawdown
    limits you would suggest for a system
    of this type?

Any additional suggestions or alternative approaches from experienced practitioners would be highly valued. The goal is a robust, practically tradeable momentum system that performs consistently across different market regimes.