Hi guys,
I am trying to develop a system that use the following formula for Position sizing based on Avg.Profit % and Win rate %:
Position size = Current Equity * X / (7%)
with X = (2%  Avg. Profit * Win rate)/(1  Win rate)
Also, I have the following code to open only 1 position during backtest everyday:
limit_daily_entries = 1;
SetCustomBacktestProc( "" );
if( Status( "action" ) == actionPortfolio )
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing (always required)
for( i = 0; i < BarCount; i++ ) // Loop through all bars
{
count = 0;
for( sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
{
if( sig.IsEntry() )
{
if( count < limit_daily_entries )
count ++;
else
sig.Price = 1 ; // ignore entry signal
}
}
bo.ProcessTradeSignals( i ); // Process trades at bar (always required)
}
bo.PostProcess(); // Do postprocessing (always required)
}
I am still new to custom backtesting, and I have been trying to read a lot of document on this.
The problem is to be able to calculate Win rate % and Avg Profit % of the system up to every trade, while also being able to combine that CBT procedure with the 1 single trade/day code above.
Thank you so much!
You can loop through the closed trade list on each bar to calculate the metrics up to this point in the bar loop and then use the results to adjust entries in the signal loop (along with your other processing) before finally processing the signals.
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject();
bo.PreProcess();
DetailedLog = GetOption("PortfolioReportMode") == 1;
dt = DateTime();
for (i = 0; i < BarCount; i++)
{
if (DetailedLog) bo.RawTextOutput("Bar Date: " + DateTimeToStr( dt[i] ));
// Loop through trades closed up to this point
TotalTradePnL = 0;
TotalWins = 0;
TradeCount = 0;
for (trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
{
TradeCount++;
TradePnL = trade.GetPercentProfit();
TotalTradePnL += TradePnL;
if (TradePnL > 0)
TotalWins++;
}
// if there are closed trades, then do the calculations and use the results
if (TradeCount > 0)
{
WinRateToDate = (TotalWins / TradeCount) * 100;
AvgPnLToDate = TotalTradePnL / TradeCount;
if (DetailedLog) bo.RawTextOutput("\tWinRateToDate: " + WinRateToDate);
if (DetailedLog) bo.RawTextOutput("\tAvgPnLToDate: " + AvgPnLToDate);
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) )
{
if (sig.IsEntry())
{
// Set Pos Size based on values
}
}
}
bo.ProcessTradeSignals( i );
}
bo.PostProcess();
}
Values are output to the Detailed Log.
That should be enough to get you started.
2 Likes
That's absolutely fantastic sir! I am gonna work on this and get back my full code as soon as I finish it. Briliant code.
There are different ways to calculate the Kelly formula, while i am not sure that this is the right way to calculate it , here is a template you can use to further play around with the idea.
SetCustomBacktestProc("");
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing
for (i = 0; i < BarCount; i++) // Loop through all bars
{
winning_trades = 0;
losing_trades = 0;
closed_trades = 0;
luck = 0;
payoff = 0;
sumPayoff = 0;
kelly = 0;
payoffWIN = 0;
payoffLose = 0;
dt = DateTime();
sumpayoffWIN = 0;
sumpayoffLose = 0;
k2 = 0;
for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade() )
{
closed_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue;
sumpayoffWIN += payoffWIN;
}
if(trade.GetPercentProfit < 0)
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
Complete_Flip = winning_trades >= 1 AND losing_trades >= 1;
sumPayoff = sumpayoffWIN /abs(sumpayoffLose);
luck = winning_trades / closed_trades;
kelly = IIf ( Complete_Flip >=1 ,kelly = luck  (1  luck) / sumPayoff,kelly ==0);
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsEntry() && sig.IsLong() )
bo.EnterTrade( i, sig.Symbol, sig.IsLong(), sig.Price, IIf(Complete_Flip AND kelly > 0,(kelly * 100),0.05*bo.Equity ));
if( sig.IsExit() && sig.IsLong() )
bo.ExitTrade( i, sig.Symbol, sig.Price );
}
bo.HandleStops( i ); // Handle programmed stops at this bar
bo.UpdateStats( i, 2 ); // Update stats at bar's end
} // End of for loop over bars
bo.PostProcess(); // Do postprocessing
}
The position size is adjusted after a complete flip of the "coin". IE one losing trade and one winning trade. After that you can use the formula to see how much of a percentage of your equity to invest.
Before the complete flip you can enter any value of your equity for the first few trades.
Again, not sure this is the proper way to calculate it, but that part you can research yourself.
Hope it helps.
1 Like
@constantinpantea and @MacAllan,
You guys' codes have been very helpful for me to figure this out. So I think the following code I write seems to generate the correct outcome that I need. Here it is. Before that, I should explain a few things.
This CBT code aim to generate position size for each trade based on current win rate and avg Win % and some additional system parameters that I aim to optimize (a.k.a system_losers and system_return). For winning trades, my position size should increase, for losing trade, my position size should decrease according to the formula I mentioned in the first post.
I also would like to open only 1 new trade everyday (limit daily entries)
The minimum position size should be 5% equity and maximum 25% equity for each trade. Hence, the following code were built based on everyone's suggestion:
SetCustomBacktestProc("");
limit_daily_entries = 1;
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing
isDetailedReport = GetOption("PortfolioReportMode") == 1;
dt = DateTime();
for (i = 0; i < BarCount; i++) // Loop through all bars
{
if (isDetailedReport) bo.RawTextOutput(NumToStr(dt[i], formatDateTime)+"\t");
winning_trades = 0; //count win trades
losing_trades = 0; //count lose trades
closed_trades = 0; //Total number of trades that are closed
luck = 0; //Win rate in decimal
payoff = 0; //gain/loss in decimal
sumPayoff = 0; //Sum of all return in decimal
payoffWIN = 0; //Gain in decimal
payoffLose = 0; //Lose in decimal
dt = DateTime();
sumpayoffWIN = 0; //Sum of gain in decimal
sumpayoffLose = 0; //Sum of loss in decimal
system_return = 1; //optimizable
system_losers = 5; //optimizable
for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade() )
{
closed_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue; //i.e: profit $500k / $1M entry = 0.5
sumpayoffWIN += payoffWIN; //Add up all the profit in decimal
}
if(trade.GetPercentProfit < 0) //Not necessary in this formula.
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
Complete_Flip = winning_trades >0 AND losing_trades >0;
luck = winning_trades / closed_trades; //decimal win rate
AvgWinPnL = sumpayoffWin / winning_trades; //decimal profit/trade
X = (system_return  AvgWinPnL * 100 * luck)/(1  luck);
Link = X / system_losers; //Normally a positive fraction number (2 negative numbers divide)
Linkvermore = IIf(Link>1,1,IIf(Link<0.1,0.1,Link)); //Limit Linker in the 0.1 to 1 range
start_size = 25;
min_size = 5;
all_losers = losing_trades > 0 AND winning_trades == 0;
Linker0 = IIf(Complete_Flip , 25*Linkvermore, start_size);
Linker = IIf(all_losers, Min_size, Linker0);
//bo.AddCustomMetric("Flip",Complete_Flip);
bo.AddCustomMetric("Link",Linker);
count = 0;
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsEntry() && sig.IsLong() )
{
if ( count < limit_daily_entries )
{ count ++;
bo.EnterTrade( i, sig.Symbol, sig.IsLong(), sig.Price, Linker);
}
else
sig.Price = 1; //ignore entry signal
}
if( sig.IsExit() && sig.IsLong() )
bo.ExitTrade( i, sig.Symbol, sig.Price );
}
//bo.ProcessTradeSignals( i ); // Process trades at bar (always required)
bo.HandleStops( i ); // Handle programmed stops at this bar
bo.UpdateStats( i, 2 ); // Update stats at bar's end
} // End of for loop over bars
bo.PostProcess(); // Do postprocessing
}
I think it works wells as intended. However, some problems arise. Here are the problems:

My detail log changed. I no longer can see which position is currently open. At least I can see the date, thanks to @mradtke solution in this post Different Detailed Log after implement CBT
However, I would also like to see the Open positions like the normal detail log. Do you guys know how I could add this line to the detail log?

My system also implement scaleout. I am not sure how to add that function to my CBT code above.
Thanks a lot to everyone here, I have learned a lot about Amibroker code since I join this forum!
2 Likes
You're using LowLevel CBT, when there's no need to. MidLevel will do just fine and make life easier for you, including solving issue number 1. To make the change, that means not using bo.EnterTrade
and bo.ExitTrade
plus bringing bo.ProcessTradeSignals( i );
back in.
Scale outs can be done by looping through the open trade list on each bar and checking for the criteria that you want to use to scale out. The only decision then, is where in the process that should occur  before any other processing, or after the signals have been processed for the current bar. Scale out example:
// Scale outs
for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
{
if (trade.BarsInTrade == 10) // If trade has been open for 10 bars  add your own criteria here.
{
bo.ScaleTrade(i, trade.Symbol, False, trade.GetPrice(i, "C"), trade.GetPositionValue() / 2); // Scale out half of position.
}
}
Here's the full updated midlevel code with the scale outs added after signal processing.
SetCustomBacktestProc("");
limit_daily_entries = 1;
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing
isDetailedReport = GetOption("PortfolioReportMode") == 1;
dt = DateTime();
for (i = 0; i < BarCount; i++) // Loop through all bars
{
if (isDetailedReport) bo.RawTextOutput(NumToStr(dt[i], formatDateTime)+"\t");
winning_trades = 0; //count win trades
losing_trades = 0; //count lose trades
closed_trades = 0; //Total number of trades that are closed
luck = 0; //Win rate in decimal
payoff = 0; //gain/loss in decimal
sumPayoff = 0; //Sum of all return in decimal
payoffWIN = 0; //Gain in decimal
payoffLose = 0; //Lose in decimal
dt = DateTime();
sumpayoffWIN = 0; //Sum of gain in decimal
sumpayoffLose = 0; //Sum of loss in decimal
system_return = 1; //optimizable
system_losers = 5; //optimizable
for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade() )
{
closed_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue; //i.e: profit $500k / $1M entry = 0.5
sumpayoffWIN += payoffWIN; //Add up all the profit in decimal
}
if(trade.GetPercentProfit < 0) //Not necessary in this formula.
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
Complete_Flip = winning_trades >0 AND losing_trades >0;
luck = winning_trades / closed_trades; //decimal win rate
AvgWinPnL = sumpayoffWin / winning_trades; //decimal profit/trade
X = (system_return  AvgWinPnL * 100 * luck)/(1  luck);
Link = X / system_losers; //Normally a positive fraction number (2 negative numbers divide)
Linkvermore = IIf(Link>1,1,IIf(Link<0.1,0.1,Link)); //Limit Linker in the 0.1 to 1 range
start_size = 25;
min_size = 5;
all_losers = losing_trades > 0 AND winning_trades == 0;
Linker0 = IIf(Complete_Flip , 25*Linkvermore, start_size);
Linker = IIf(all_losers, Min_size, Linker0);
//bo.AddCustomMetric("Flip",Complete_Flip);
bo.AddCustomMetric("Link",Linker);
count = 0;
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsEntry() && sig.IsLong() )
{
if ( count < limit_daily_entries )
{
count ++;
}
else
sig.Price = 1; //ignore entry signal
}
}
bo.ProcessTradeSignals( i ); // Process trades at bar (always required)
// Scale outs
for (trade = bo.GetFirstOpenPos(); trade; trade = bo.GetNextOpenPos())
{
if (trade.BarsInTrade == 10) // If trade has been open for 10 bars  add your own criteria here.
{
bo.ScaleTrade(i, trade.Symbol, False, trade.GetPrice(i, "C"), trade.GetPositionValue() / 2); // Scale out half of position.
}
}
} // End of for loop over bars
bo.PostProcess(); // Do postprocessing
}
1 Like
@MacAllan,
Great idea! Thanks to your code, I nailed it exactly the way I want.
I have to clarify though, I did not need to write a loop for scaleout. A much simple solution can be adopt in the main code. Here is my main code for scaleout:
DoScaleOut = RSI(14)>75; //A dummy example
Buy = Buy + sigScaleOut * DoScaleout;
SetPositionSize( 20, spsPercentOfPosition * ( Buy == sigScaleOut * DoScaleout ) ); //Scale_out 20% of position
Here is the final code, using only midlevel CBT:
SetCustomBacktestProc("");
limit_daily_entries = 1;
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing
for (i = 0; i < BarCount; i++) // Loop through all bars
{
winning_trades = 0; //count win trades
losing_trades = 0; //count lose trades
closed_trades = 0; //Total number of trades that are closed
luck = 0; //Win rate in decimal
payoff = 0; //gain/loss in decimal
sumPayoff = 0; //Sum of all return in decimal
payoffWIN = 0; //Gain in decimal
payoffLose = 0; //Lose in decimal
sumpayoffWIN = 0; //Sum of gain in decimal
sumpayoffLose = 0; //Sum of loss in decimal
system_return = 1; //optimizable
system_losers = 5; //optimizable
for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade() )
{
closed_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue; //i.e: profit $500k / $1M entry = 0.5
sumpayoffWIN += payoffWIN; //Add up all the profit in decimal
}
if(trade.GetPercentProfit < 0) //Not necessary in this formula.
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
Complete_Flip = winning_trades >0 AND losing_trades >0;
luck = winning_trades / closed_trades; //decimal win rate
AvgWinPnL = sumpayoffWin / winning_trades; //decimal profit/trade
X = (system_return  AvgWinPnL * 100 * luck)/(1  luck);
Link = X / system_losers; //Normally a positive fraction number (2 negative numbers divide)
Linkvermore = IIf(Link>1,1,IIf(Link<0.1,0.1,Link)); //Limit Linker in the 0.1 to 1 range
start_size = 25;
Max_size = 25;
Min_size = 5;
all_losers = losing_trades > 0 AND winning_trades == 0;
Linker0 = IIf(Complete_Flip , Max_size * Linkvermore, start_size);
Linker = IIf(all_losers, Min_size, Linker0);
//bo.AddCustomMetric("Flip",Complete_Flip);
bo.AddCustomMetric("Link",Linker);
count = 0;
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsEntry() && sig.IsLong() )
{
if ( count < limit_daily_entries )
{
count ++;
sig.PosSize = Linker;
}
else
sig.Price = 1; //ignore entry signal
}
}
bo.ProcessTradeSignals( i ); // Process trades at bar (always required)
} // End of for loop over bars
bo.PostProcess(); // Do postprocessing
}
1 Like
Okay, so I thought the code above solve everything. However, one big problem arise when I try to use the CBT for backtest on 1 ticker 100% of the account.
The problem is that for trades that have Sell signal on the same day of another ticker's buy signal, the backtest system will not count the trade that has the Sell signal as a closed trade.
As the result, my system of calculating the win rate did not include that ticker which has Sell Signal today. Hence, the position size was calculated wrong because it didn't account for the position that close today.
Can someone please help me with this while it seems like minor problem, but actually a big problem for me?
My CBT code is here:
SetCustomBacktestProc("");
limit_daily_entries = 1;
if (Status("action") == actionPortfolio)
{
bo = GetBacktesterObject(); // Get backtester object
bo.PreProcess(); // Do preprocessing
for (i = 0; i < BarCount; i++) // Loop through all bars
{
winning_trades = 0; //count win trades
losing_trades = 0; //count lose trades
total_trades = 0; //Total number of trades that are closed
luck = 0; //Win rate in decimal
payoff = 0; //gain/loss in decimal
sumPayoff = 0; //Sum of all return in decimal
payoffWIN = 0; //Gain in decimal
payoffLose = 0; //Lose in decimal
sumpayoffWIN = 0; //Sum of gain in decimal
sumpayoffLose = 0; //Sum of loss in decimal
system_return = 1; //optimizable
system_losers = 5; //optimizable
for(trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade())
{
total_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue; //i.e: profit $500k / $1M entry = 0.5
sumpayoffWIN += payoffWIN; //Add up all the profit in decimal
}
if(trade.GetPercentProfit < 0)
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
Complete_Flip = winning_trades >0 AND losing_trades >0;
luck = winning_trades / total_trades; //decimal win rate
AvgWinPnL = sumpayoffWin / winning_trades; //decimal profit/trade
start_size = 100;
Max_size = 100;
Min_portion = 0.1;//Optimizable from 0.1 to 1
Min_size = 5;
X = (system_return  AvgWinPnL * 100 * luck)/(1  luck);
Link = X / system_losers; //Normally a positive fraction number (2 negative numbers divide)
Linkvermore = IIf(Link>1,1,IIf(Link<Min_portion, Min_portion, Link)); //Limit Linker in the 0.1 to 1 range
all_losers = losing_trades > 0 AND winning_trades == 0;
Linker0 = IIf(Complete_Flip , Max_size * Linkvermore, start_size);
Linker = IIf(all_losers, Min_size, Linker0);
count = 0;
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsEntry() && sig.IsLong() )
{
if ( count < limit_daily_entries )
{
count ++;
sig.PosSize = Linker;
}
else
sig.Price = 1; //ignore entry signal
}
}
bo.ProcessTradeSignals( i ); // Process trades at bar (always required)
} // End of for loop over bars
bo.PostProcess(); // Do postprocessing
}
As you can see in the trade report picture, PLC has sell Signal on the same day with HUT having Buy signal. In that case, my win rate should be 50% and my position size for HUT should be big, and not 5% like that which is the minimal rate I set.
I already use the following line of code as an attempt to solve the problem above
SetOption("SettlementDelay", 1 );
However, I still see my position size for HUT is calculated wrong
Your issue here is that the trades closed on the current bar won't appear in the trade
list your looping through until you've triggered bo.ProcessTradeSignals( i );
. However, once the signals are processed with that command, your entry signals will also be turned into trades, meaning you lose the ability to adjust or exclude them ahead of time.
The easiest way around this is to insert a second signal loop between the trade loop you have and your current signal loop. The reason for this is, the exit signals for trades exiting on the current bar will also appear in the signal list and return true for sig.IsExit()
. So you loop through the closed trade list, then loop through the signal list once, checking for exits and updating your stats from the trade loop, before looping through the signals once again to adjust your entries, and then finally trigger bo.ProcessTradeSignals( i );
where you have it now.
Hope that helps.
1 Like
Hi @MacAllan,
Thanks again for your very kind and helpful comment. I see your point here adding another loop to process exit signal with sig.IsExit()
So my understanding is that I will write another exit loop like this right under my loop through the close trade list:
for (sig = bo.GetFirstSignal( i ); sig; sig = bo.GetNextSignal( i ) ) // Loop through all signals at this bar
{
if( sig.IsExit() )
{ //some condition
}
} //Close the loop with exit signal
And then all the formula and loop for 1 entry go under this line.
I wonder then how do I modify this loop to add to the number winning trades or losing trades? Do I have to call the loop for close trades again inside this additional signal loop?
Many thanks!
You can use bo.FindOpenPos()
to get the trade object for the open position that's about to exit. Then you can replicate the lines from your earlier trade loop to update your stats (you should also consider putting these lines in a function or procedure and passing in the trade object, to avoid duplication  see DRY).
if (sig.IsExit())
{
trade = bo.FindOpenPos(sig.Symbol); // Get the trade object for the signal's symbol
// lines replicated from trade loop:
total_trades++;
payoff = trade.getprofit() / trade.GetEntryValue;
sumPayoff +=payoff;
if(trade.GetPercentProfit > 0)
{
winning_trades++ ;
payoffWIN = trade.getprofit / trade.GetEntryValue; //i.e: profit $500k / $1M entry = 0.5
sumpayoffWIN += payoffWIN; //Add up all the profit in decimal
}
if(trade.GetPercentProfit < 0)
{
losing_trades++;
payoffLose = trade.getprofit / trade.GetEntryValue;
sumpayoffLose +=payoffLose;
}
}
1 Like