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.

