Optimization target that shows consistency of returns

Hello friends,
For decades I've been using CAR/MDD as my optimization target and like a good system tester I try to use as many years of data as I can possibly get my hands on. However, I've noticed one potential drawback of using my current target and that is certain systems show incredibly strong performance (e.g. late 90's internet boom) at one time and then might just go flat for the remaining 20 years. However, those 100-200% gains from 1996-2000 are carrying the performance of the overall system. I guess what I'm looking for would be something like a Compound Median Return/MDD, or some other expression that would show a solid consistency of returns versus a burst of performance and then flat activity.
I hope that's clear as mud for everyone.
Thanks for any suggestions
Tony

Sometimes all you have to do is look at a graph. I suggest you view a logarithmic scaled equity curve which is a good way to view consistency of performance.

Quantitatively, you can look at logarithmic regression and variability around it. Additionally, if you want to embrace longer winning trade duration, like you would with trend following and metrics like MAR, then you should seek to embrace upside volatility in equity.

1 Like

Try doing your optimization with a fixed amount of capital (do not use % of equity for position), after you elected the best optimization with fixed amount you can use % of equity

2 Likes

Anderson is correct.

There is also another benefit to Anderson's recommendation, which involves looking at robustness of profitability "robustness" across all the instrument symbols in your portfolio that were traded. This is because if you look at them with variable bet sizing enabled, you might see many of the instruments traded, each have an aggregate loss over the test period. This is usually due to latest trades in these symbols, (within a a profitable system/model), where these symbols had their latest chronological trades with the larger trade size (Due to larger equity base), which were loosing trade/trades and turns the gross profit for that symbol into the negative; but if in a constant bet sizing environment, the specific symbol would have had a gross profit over the test period. This is very common in futures, FX and other highly leveraged systems.

The better way to handle this, instead of running another test, is just back adjust and calculate metrics for each traded symbol, like average profit/loss per each 1 contract in ticks, or profit/loss per 1 share in cents. This can show how much room there is for skid, as well as how robust the system is across instruments, i.e., "Is all the profit coming from just one or a few symbols?"

3 Likes

Ah, yes! good thought! I'll do that, thank you @awilson and @Sean

1 Like

You may find the following code useful. It calculates the profit table considering the initial Equity
save it under Report Charts folder with name

"3. Profit Table Initial Equity"

EnableTextOutput( 3 ); // enable HTML output into report (Version 5.84 or higher!)

eq = C;

yr = Year();
mo = Month();

YearChange = yr != Ref( yr, 1 );
MonChange = mo != Ref( mo, 1 );

FirstYr = 0;
LastYr = 0;

startbar = 0;

////////////////////////////
// SKIP non-trading bars
////////////////////////////

for ( i = 0; i < BarCount; i++ )
{
    if ( eq[ i ] )
    {
        startbar = i;
        break;
    }
}

////////////////////////////
// collect yearly / monthly changes in equity
// into dynamic variables
////////////////////////////
InitialEquity =  eq[ startbar  ];
LastYrValue = eq[ startbar  ];
LastMoValue = eq[ startbar  ];
MaxYrProfit = MinYrProfit = 0;
MaxMoProfit = MinMoProfit = 0;

for ( i = startbar + 1; i < BarCount; i++ )
{
    if ( YearChange[ i ] || i == BarCount - 1 )
    {
        //Chg = 100 * ( -1 + eq[ i ] / LastYrValue );
        Chg = 100 * ( (eq[ i ] - LastYrValue) / InitialEquity );
        VarSet( "ChgYear" + yr[ i ], Chg );

        MaxYrProfit = Max( MaxYrProfit, Chg );
        MinYrProfit = Min( MinYrProfit, Chg );

        if ( FirstYr == 0 )
            FirstYr = yr[ i ];

        LastYr = yr[ i ];

        LastYrValue = eq[ i ];
    }

    if ( MonChange [ i ] || i == BarCount - 1 )
    {
        mon = mo[ i ];

        //Chg = 100 * ( -1 + eq[ i ] / LastMoValue );
        Chg = 100 * ( (eq[ i ] - LastMoValue) / InitialEquity );

        VarSet( "ChgMon" + yr[ i ] + "_" + mon, Chg );
        VarSet( "SumChgMon" + mon, Chg + Nz( VarGet( "SumChgMon" + mon ) ) );
        VarSet( "SumMon" + mon, 1 + Nz( VarGet( "SumMon" + mon ) ) );

        MaxMoProfit = Max( MaxMoProfit, Chg );
        MinMoProfit = Min( MinMoProfit, Chg );

        LastMoValue = eq[ i ];
    }
}

MonthNames = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec";


function GenProfitTableHTML( )
{
    printf( "<table border='1' bordercolor='#000000' cellspacing='0' cellpadding='3'style='border-collapse:collapse;'>\n" );

    printf( "<tr bgcolor='#eeffee' >\n" );

    Header = "Year," + MonthNames + ",Yr%%";

    for ( Col = 0; ( Colname = StrExtract( Header, Col ) ) != ""; Col++ )
    {
        printf( "<td><b>" + Colname + "</b></td>" );
    }

    printf( "</tr>\n" );

    for ( y = FirstYr; y <= LastYr; y++ )
    {
        //Color =  ColorRGB( IIf( row == 0 || col == 0 || col == 13, 220, 255 ), 255, IIf( row % 2, 255, 220 ) );

        // new row
        if ( y % 2 )
            printf( "<tr bgcolor='#ffffff'>\n<td bgcolor='#eeffff'>" );
        else
            printf( "<tr bgcolor='#ffffee'>\n<td bgcolor='#eeffee'>" );

        printf( "<b>%g</b></td>", y );

        for ( m = 1; m <= 12; m++ )
        {
            Chg = VarGet( "ChgMon" + y + "_" + m );

            if ( NOT IsNull( Chg ) )
            {
                if ( Chg >= 0 )
                    printf( "<td nowrap>%.1f%%</td>", Chg );
                else
                    printf( "<td nowrap><font color='880000'>%.1f%%</font></td>", Chg );
            }
            else
                printf( "<td>N/A</td>" );
        }

        if ( y % 2 )
            printf( "<td nowrap bgcolor='#eeffff'>" );
        else
            printf( "<td nowrap bgcolor='#eeffee'>" );

        x = VarGet( "ChgYear" + y );

        if ( x >= 0 )
            printf( "<b>%.1f%%</b></td>", x );
        else
            printf( "<font color='880000'><b>%.1f%%</b></font></td>", x );

        printf( "</tr>\n" ); // end row
    }


    printf( "<tr bgcolor='#eeffee' >\n" ); // new row

    printf( "<td><b>Avg</b></td>" );

    for ( m = 1; m <= 12; m++ )
    {
        x = Nz( VarGet( "SumChgMon" + m ) / VarGet( "SumMon" + m ) );

        if ( x >= 0 )
            printf( "<td nowrap><b>%.1f%%</b></td>", x );
        else
            printf( "<td nowrap><font color='880000'><b>%.1f%%</b></font></td>", x );
    }

    printf( "<td>&nbsp;</td>" );

    printf( "</tr></table>\n" );

}

///////////////////////////
// This function checks if currently selected symbol
// is portfolio equity
//////////////////////////
function CheckSymbol()
{
    if ( Name() != "~~~EQUITY" AND Name() != "~~~OSEQUITY" )
    {
        printf( "For accurate results switch to ~~~EQUITY symbol<br>" );
    }
}

CheckSymbol();

////////////////////////////
// Main program
////////////////////////////
GenProfitTableHTML();

6 Likes

That's really sharp! I like that! thank you very much for sharing!
Tony

Dr. Bandy had a good discussion of this in Quantitative Technical Analysis. He recommends using CAR25 / DD95, which uses Monte Carlo to calculate the 25th percentile of CAR, and the 95th percentile of drawdown.

1 Like

You may also like the drawdown chart calculated not as percentage

Also save it under report folder with name

"2. Drawdown"

// Underwater Equity chart
// (C)2009 AmiBroker.com
// Should be used only on ~~~EQUITY or ~~~OSEQUITY symbol

// altered by andwilson to use the highest equity

EQ = C;
MaxEQ = Highest( EQ );
DD = Eq - MaxEQ;
MaxDD = Lowest( DD );

Title = StrFormat("Drawdown = %.2g, Max. drawdown %g", DD, LastValue( MaxDD ) );
SetGradientFill( GetChartBkColor(), colorBlue, 0 );

Plot( DD, "Drawdown ", colorBlue, styleGradient | styleLine );

Plot( MaxDD, "Max DD", colorRed, styleNoLabel );

//SetChartOptions( 2, 0, chartGridPercent );

if( Name() != "~~~EQUITY" AND Name() != "~~~OSEQUITY"  ) Title = "Warning: wrong ticker! This chart should be used on ~~~EQUITY or ~~~OSEQUITY only";
3 Likes

Make a distribution of annual returns and minimize the excess kurtosis. This will eliminate strategies with extreme annual return outliers. It takes work. I'm good with statistics but bad with programming in CBT. :joy: