Custom Monte Carlo

Hello,

With my limited knowledge of AFL and the invaluable help of GPT (I'm done with Gemini because it doesn't follow instructions as well), I've tried to calculate a custom Monte Carlo analysis.

I want it to work like Amibroker's, but I'm also adding Losing Streaks and Risk of Ruin.

To do this, I created this AFL, which I use as a CBT:



SetCustomBacktestProc("");
if( Status("action") == actionPortfolio ) 
{ 
    bo = GetBacktesterObject(); 
    bo.Backtest( 1 ); 
    	
    StaticVarRemove( "ResultTradeMatrix*" ); 
    StaticVarRemove( "MC_*" );
    
    rows = 5000; 
    ResultTrade = Matrix( rows, 1 );
    NumTrade = 0;  
    
    for( trade = bo.GetFirstTrade(); trade AND NumTrade < rows; trade = bo.GetNextTrade() ) 
    {
        ResultTrade[NumTrade][0] = trade.GetProfit();      
        NumTrade++; 
    }

	bo.ListTrades(); 
    
    stat = bo.GetPerformanceStats(0); 
    InitialCapital  = stat.GetValue("InitialCapital"); 


	/* ====================== INICIO: MONTE CARLO (BOOTSTRAP TRADES) ====================== */

	// Duración base del Backtest
	dtFirst = LastValue( ValueWhen( BarIndex() == 0, DateTime() ) );
	dtLast  = LastValue( DateTime() );
	secs    = DateTimeDiff( dtLast, dtFirst );
	YearsBT = secs / ( 365.25 * 24 * 3600 );

	// Parámetros Monte Carlo
	NumSim       = 1000;   
	RuinPct      = 0.30;  
	UmbralRuina = InitialCapital * ( 1 - RuinPct );
	
	// Matrices de Resultados
    FinalEqSims = Matrix( NumSim, 1 );
    DDMaxSims   = Matrix( NumSim, 1 );
    DDPctSims   = Matrix( NumSim, 1 );
    StreakSims  = Matrix( NumSim, 1 );
    CARSims = Matrix( NumSim, 1 );
    LowestEqSims = Matrix( NumSim, 1 );

    cRuina = 0;

	// Monte Carlo (Bootstrap de Trades, Full Period)
    for ( s = 0; s < NumSim; s++ )
    {
        Eq   = InitialCapital;
        Pico = InitialCapital;
        LowestEq = Eq; 

        MaxDD    = 0;
        MaxDDPct = 0;

        CurStrk = 0;
        MaxStrk = 0;

        TocoRuina = 0;

        for ( t = 0; t < NumTrade; t++ ) //Cada simulación usa TODOS los trades
        {
            idx = floor( mtRandom() * NumTrade );  //Selección aleatoria de trades (bootstrap idéntico)
            if ( idx >= NumTrade ) idx = NumTrade - 1;

            p = ResultTrade[ idx ][ 0 ];

            Eq += p;
            
            if ( Eq < LowestEq ) LowestEq = Eq;

            if ( Eq > Pico ) Pico = Eq;

            dd = Eq - Pico;
            if ( dd < MaxDD ) MaxDD = dd;

            ddpct = 100 * dd / Pico;
            if ( ddpct < MaxDDPct ) MaxDDPct = ddpct;

            if ( p < 0 )
            {
                CurStrk++;
                if ( CurStrk > MaxStrk ) MaxStrk = CurStrk;
            }
            else 
            {
				CurStrk = 0;
			}

            if ( Eq <= UmbralRuina ) TocoRuina = 1;
        }

		// CAR = Annual Return
		if ( Eq > 0 AND InitialCapital > 0 )
		{
			// Fórmula estándar: CAR = ((Final/Initial)^(1/Years) - 1) * 100
			CAR_MC = 100 * ( ( ( Eq / InitialCapital ) ^ ( 1 / YearsBT ) ) - 1 );
		}
		else
		{
			// Si el capital es 0 o negativo, el retorno anual es -100%
			CAR_MC = -100;
		}
		// --- FIN CORRECCIÓN CÁLCULO CAR (%) ---

        FinalEqSims[ s ][ 0 ] = Eq;
        DDMaxSims[ s ][ 0 ]   = MaxDD;
        DDPctSims[ s ][ 0 ]   = MaxDDPct;
        StreakSims[ s ][ 0 ]  = MaxStrk;
        CARSims[ s ][ 0 ]     = CAR_MC;
        LowestEqSims[ s ][ 0 ] = LowestEq;

        if ( TocoRuina ) cRuina++;
    }

    StaticVarSet( "MC_FinalEqRuns", FinalEqSims );
    StaticVarSet( "MC_DDMaxRuns",   DDMaxSims );
    StaticVarSet( "MC_DDPctRuns",   DDPctSims );
    StaticVarSet( "MC_CARRuns",     CARSims );
    StaticVarSet( "MC_StreakRuns",  StreakSims );
    StaticVarSet( "MC_ProbRuin",    100 * cRuina / NumSim );
    StaticVarSet( "MC_NumSim",      NumSim );
    StaticVarSet( "MC_LowestEqRuns", LowestEqSims );

}

And this other AFL, which I use as a Report Chart:




// ---------- Recuperar resultados ----------
FinalEqMx = StaticVarGet( "MC_FinalEqRuns" );
StreakMx = StaticVarGet( "MC_StreakRuns" );  
DDMx     = StaticVarGet( "MC_DDMaxRuns" );    
DDPctMx = StaticVarGet( "MC_DDPctRuns" );   
NumSim   = StaticVarGet( "MC_NumSim" );
ProbRuin = StaticVarGet( "MC_ProbRuin" );
CARMx = StaticVarGet( "MC_CARRuns" );
LowestEqMx = StaticVarGet( "MC_LowestEqRuns" );

// ---------- Validación mínima ----------
rowsEq     = MxGetSize( FinalEqMx, 0 );
rowsStreak = MxGetSize( StreakMx, 0 );
rowsDD     = MxGetSize( DDMx, 0 );
rowsDDPct = MxGetSize( DDPctMx, 0 );
rowsCAR = MxGetSize( CARMx, 0 );
rowsLowest = MxGetSize( LowestEqMx, 0 );

ok = NumSim > 0 AND rowsEq > 0 AND rowsStreak > 0 AND rowsDD > 0 AND rowsDDPct > 0 AND rowsCAR > 0 AND rowsLowest > 0;

if ( !ok )
	{
		Title = "Monte Carlo anual no disponible";
	}
else
	{
		rows = MxGetSize( FinalEqMx, 0 );   // ESCALAR

		// ---------- Ordenar matrices ----------
		EqSorted     = MxSortRows( FinalEqMx, True, 0 );
		StreakSorted = MxSortRows( StreakMx, True, 0 );
		DDSorted     = MxSortRows( DDMx,     True, 0 );
		DDPctSorted  = MxSortRows( DDPctMx, True, 0 );
		CARSorted = MxSortRows( CARMx, True, 0 );
		LowestEqSorted = MxSortRows( LowestEqMx, True, 0 );

		// ---------- Índices de percentiles ----------
		p1  = floor( 0.01 * ( rows - 1 ) );
		p5  = floor( 0.05 * ( rows - 1 ) );
		p10 = floor( 0.1 * ( rows - 1 ) );
		p25 = floor( 0.25 * ( rows - 1 ) );
		p50 = floor( 0.50 * ( rows - 1 ) );
		p75 = floor( 0.75 * ( rows - 1 ) );
		p90 = floor( 0.90 * ( rows - 1 ) );
		p95 = floor( 0.95 * ( rows - 1 ) );
		p99 = floor( 0.99 * ( rows - 1 ) );

		// ---------- Extraer valores (ESCALARES) ----------
		Eq_P1  = EqSorted[ p1 ][0];
		Eq_P5  = EqSorted[ p5 ][0];
		Eq_P10  = EqSorted[ p10 ][0];
		Eq_P25 = EqSorted[ p25 ][0];
		Eq_P50 = EqSorted[ p50 ][0];
		Eq_P75 = EqSorted[ p75 ][0];
		Eq_P90 = EqSorted[ p90 ][0];
		Eq_P95 = EqSorted[ p95 ][0];
		Eq_P99 = EqSorted[ p99 ][0];
		
		Streak_P1  = StreakSorted[ p1  ][0];
		Streak_P5  = StreakSorted[ p5  ][0];
		Streak_P10  = StreakSorted[ p10  ][0];
		Streak_P25 = StreakSorted[ p25 ][0];
		Streak_P50 = StreakSorted[ p50 ][0];
		Streak_P75 = StreakSorted[ p75 ][0];
		Streak_P90 = StreakSorted[ p90 ][0];
		Streak_P95 = StreakSorted[ p95 ][0];
		Streak_P99 = StreakSorted[ p99 ][0];

		DD_P1  = DDSorted[ p1  ][0];
		DD_P5  = DDSorted[ p5  ][0];
		DD_P10  = DDSorted[ p10  ][0];
		DD_P25 = DDSorted[ p25 ][0];
		DD_P50 = DDSorted[ p50 ][0];
		DD_P75 = DDSorted[ p75 ][0];
		DD_P90  = DDSorted[ p90  ][0];
		DD_P95 = DDSorted[ p95 ][0];
		DD_P99 = DDSorted[ p99 ][0];
		
		DDPct_P1  = DDPctSorted[ p1  ][0];
		DDPct_P5  = DDPctSorted[ p5  ][0];
		DDPct_P10  = DDPctSorted[ p10  ][0];
		DDPct_P25 = DDPctSorted[ p25 ][0];
		DDPct_P50 = DDPctSorted[ p50 ][0];
		DDPct_P75 = DDPctSorted[ p75 ][0];
		DDPct_P90 = DDPctSorted[ p90 ][0];
		DDPct_P95 = DDPctSorted[ p95 ][0];
		DDPct_P99 = DDPctSorted[ p99 ][0];
		
		CAR_P1  = CARSorted[ p1 ][0];
		CAR_P5  = CARSorted[ p5 ][0];
		CAR_P10 = CARSorted[ p10 ][0];
		CAR_P25 = CARSorted[ p25 ][0];
		CAR_P50 = CARSorted[ p50 ][0];
		CAR_P75 = CARSorted[ p75 ][0];
		CAR_P90 = CARSorted[ p90 ][0];
		CAR_P95 = CARSorted[ p95 ][0];
		CAR_P99 = CARSorted[ p99 ][0];
		
		LowEq_P1  = LowestEqSorted[ p1 ][0];
		LowEq_P5  = LowestEqSorted[ p5 ][0];
		LowEq_P10 = LowestEqSorted[ p10 ][0];
		LowEq_P25 = LowestEqSorted[ p25 ][0];
		LowEq_P50 = LowestEqSorted[ p50 ][0];
		LowEq_P75 = LowestEqSorted[ p75 ][0];
		LowEq_P90 = LowestEqSorted[ p90 ][0];
		LowEq_P95 = LowestEqSorted[ p95 ][0];
		LowEq_P99 = LowestEqSorted[ p99 ][0];

		
		Title = 		
				
			"Percentile        Finnal Equity($)           Annual Return(%)          MaxDD($)            MaxDD(%)            Lowest Eq.                          \n" +
			"      1%"  + "                     " + NumToStr( Eq_P1,  1.0 ) + "                               " + NumToStr( CAR_P1,  1.2, False, True ) + "                           " + NumToStr( DD_P1,  1.0 ) + "                   "  + NumToStr( DDPct_P1,  1.2, False, True )  + "                   " + NumToStr( LowEq_P1,  1.0 ) + "\n" + 
		    "      5%"  + "                     " + NumToStr( Eq_P5,  1.0 ) + "                               " + NumToStr( CAR_P5,  1.2, False, True ) + "                           " + NumToStr( DD_P5,  1.0 ) + "                   "  +  NumToStr( DDPct_P5,  1.2, False, True ) + "                   " +  NumToStr( LowEq_P5,  1.0 )  +  "\n" +
			"      10%" + "                    " + NumToStr( Eq_P10, 1.0 ) + "                              " + NumToStr( CAR_P10, 1.2, False, True ) + "                           " + NumToStr( DD_P10,  1.0 ) + "                   "  +  NumToStr( DDPct_P10,  1.2, False, True ) + "                   " + NumToStr( LowEq_P10, 1.0 )  +    "\n" +
			"      25%" + "                    " + NumToStr( Eq_P25, 1.0 ) + "                              " + NumToStr( CAR_P25, 1.2, False, True ) + "                         " + NumToStr( DD_P25, 1.0 ) + "                   "  +    NumToStr( DDPct_P25, 1.2, False, True ) + "                   " +  NumToStr( LowEq_P25, 1.0 )  +   "\n" +
			"      50%" + "                    " + NumToStr( Eq_P50, 1.0 ) + "                              " + NumToStr( CAR_P50, 1.2, False, True ) + "                         " + NumToStr( DD_P50, 1.0 )  + "                   "  +    NumToStr( DDPct_P50, 1.2, False, True ) + "                   " +  NumToStr( LowEq_P50, 1.0 ) +   "\n" +
			"      75%" + "                    " + NumToStr( Eq_P75, 1.0 ) + "                              " + NumToStr( CAR_P75, 1.2, False, True ) + "                         " + NumToStr( DD_P75, 1.0 )  + "                   "  +   NumToStr( DDPct_P75, 1.2, False, True ) + "                   " + NumToStr( LowEq_P75, 1.0 )  +  "\n" +
			"      90%" + "                    " + NumToStr( Eq_P90, 1.0 ) + "                              " + NumToStr( CAR_P90, 1.2, False, True ) + "                         " + NumToStr( DD_P90, 1.0 ) + "                   "  +   NumToStr( DDPct_P90, 1.2, False, True ) + "                   " +  NumToStr( LowEq_P90, 1.0 ) +   "\n" +
			"      95%" + "                    " + NumToStr( Eq_P95, 1.0 ) + "                              " + NumToStr( CAR_P95, 1.2, False, True ) + "                         " + NumToStr( DD_P95, 1.0 ) + "                   "  +  NumToStr( DDPct_P95, 1.2, False, True ) + "                   " +  NumToStr( LowEq_P95, 1.0 ) +  "\n" +
			"      99%" + "                    " + NumToStr( Eq_P99, 1.0 ) + "                              " + NumToStr( CAR_P99, 1.2, False, True ) + "                         " + NumToStr( DD_P99, 1.0 ) + "                   "  +   NumToStr( DDPct_P99, 1.2, False, True ) + "                   " +   NumToStr( LowEq_P99, 1.0 ) +   "\n\n" +
			
		
			
		    "Losers Streaks (#trades) \n" +
			"P1 : "  + NumToStr( Streak_P1,  1.0 ) + "\n" +
			"P5 : "  + NumToStr( Streak_P5,  1.0 ) + "\n" +
			"P10 : "  + NumToStr( Streak_P10,  1.0 ) + "\n" +
			"P25: "  + NumToStr( Streak_P25, 1.0 ) + "\n" +
			"P50: "  + NumToStr( Streak_P50, 1.0 ) + "\n" +
			"P75: "  + NumToStr( Streak_P75, 1.0 ) + "\n" +
			"P90: "  + NumToStr( Streak_P90, 1.0 ) + "\n" +
			"P95: "  + NumToStr( Streak_P95, 1.0 ) + "\n" +
			"P99: "  + NumToStr( Streak_P99, 1.0 ) + "\n\n" +
			

			"Risk of Ruin (-30%)\n" +
			NumToStr( ProbRuin, 1.2 ) + "%";

	}
	

I get this Monte Carlo data from Amibroker:

And this data from my custom Monte Carlo analysis:

They're similar (though not identical), and I'd appreciate it if any of you with much more experience than me could tell me if the code is correct.

Thank you very much.

2 Likes
        for ( t = 0; t < NumTrade; t++ ) //Cada simulación usa TODOS los trades
        {
            idx = floor( mtRandom() * NumTrade );  //Selección aleatoria de trades (bootstrap idéntico)
            if ( idx >= NumTrade ) idx = NumTrade - 1;

This part needs improvement , there is no guarantee that you are using ALL TRADES.
Some might be duplicated

Thanks awilson,

I understand you mean I'm using bootstrap, not permutation trades. But I'm doing it consciously.

That is, I'm "getting my hand in the bag," taking a trade out and putting it back in. Some trades might be executed several times, and others not at all. I understood this to be the most "academic" way to do Monte Carlo trading.
I understand what you're proposing, and I'll analyze it.

1 Like

In AFL Report Chart, you might consider the change below.
In case the matrix does not exists you do not get a syntax error

	Title = "Monte Carlo not avalable";

	// ---------- Recuperar resultados ----------
	FinalEqMx = StaticVarGet( "MC_FinalEqRuns" );
	if( typeof(FinalEqMx) == "Matrix" )
	{
		StreakMx = StaticVarGet( "MC_StreakRuns" );
		DDMx     = StaticVarGet( "MC_DDMaxRuns" );
		DDPctMx = StaticVarGet( "MC_DDPctRuns" );
		NumSim   = StaticVarGet( "MC_NumSim" );
		ProbRuin = StaticVarGet( "MC_ProbRuin" );
		CARMx = StaticVarGet( "MC_CARRuns" );
		LowestEqMx = StaticVarGet( "MC_LowestEqRuns" );

		// ---------- Validación mínima ----------
		rowsEq     = MxGetSize( FinalEqMx, 0 );
		rowsStreak = MxGetSize( StreakMx, 0 );
		rowsDD     = MxGetSize( DDMx, 0 );
		rowsDDPct = MxGetSize( DDPctMx, 0 );
		rowsCAR = MxGetSize( CARMx, 0 );
		rowsLowest = MxGetSize( LowestEqMx, 0 );

		ok = NumSim > 0 AND rowsEq > 0 AND rowsStreak > 0 AND rowsDD > 0 AND rowsDDPct > 0 AND rowsCAR > 0 AND rowsLowest > 0;

		if( ok )
		{
			rows = MxGetSize( FinalEqMx, 0 );   // ESCALAR

			// ---------- Ordenar matrices ----------
			EqSorted     = MxSortRows( FinalEqMx, True, 0 );
			StreakSorted = MxSortRows( StreakMx, True, 0 );
			DDSorted     = MxSortRows( DDMx,     True, 0 );

1 Like

there is a mistype

"Matrix should be "matrix" 'm' shoul not be capital

1 Like