Reading Backtest Results
When a backtest finishes, the backtest page shows the profit curve, statistics, status information, logs and account information.
Profit curve
The profit curve consists of the values the strategy records with LogProfit() (LogProfit). If the strategy never calls LogProfit(), there is no profit curve and the statistics below, which depend on that series, cannot be computed; only the final assets in the account information remain. The profit and max_drawdown returned by the MCP tool get_backtest also come from LogProfit().
Statistics
The statistics are computed from the profit series profits (each element [timestamp, profit]) and the initial assets totalAssets with the algorithm below:
| Metric | Meaning |
|---|---|
| Return (totalReturns) | Last profit value ÷ initial assets. |
| Annualized return (annualizedReturns) | Return × one year (yearDays days) ÷ backtest duration, scaled linearly. |
| Max drawdown (maxDrawdown) | The largest fractional drop of assets (initial assets + profit) from their previous peak. maxDrawdownStartTime is the time of that peak, maxDrawdownTime the time of the deepest point. |
| Win rate (winningRate) | Share of points in the profit series that are higher than the previous point (the first point is compared with 0). It counts profit records, not individual trades. |
| Volatility (volatility) | The backtest period is cut into days; each day's profit ÷ initial assets is multiplied by yearDays to annualize it (days without profit records count as 0); volatility is the population standard deviation of these values. |
| Sharpe ratio (sharpeRatio) | (Annualized return − risk-free rate 3%) ÷ volatility; 0 when volatility is 0. |
yearDays is the number of days per year used for annualization, passed in by the backtest page. Note that daily returns are annualized by multiplying by yearDays rather than the usual √yearDays, so this Sharpe ratio should not be compared directly with values from other platforms.
Algorithm source:
javascript
function returnAnalyze(totalAssets, profits, ts, te, period, yearDays) {
// force by days
period = 86400000
if (profits.length == 0) {
return null
}
var freeProfit = 0.03 // 0.04
var yearRange = yearDays * 86400000
var totalReturns = profits[profits.length - 1][1] / totalAssets
var annualizedReturns = (totalReturns * yearRange) / (te - ts)
// MaxDrawDown
var maxDrawdown = 0
var maxAssets = totalAssets
var maxAssetsTime = 0
var maxDrawdownTime = 0
var maxDrawdownStartTime = 0
var winningRate = 0
var winningResult = 0
for (var i = 0; i < profits.length; i++) {
if (i == 0) {
if (profits[i][1] > 0) {
winningResult++
}
} else {
if (profits[i][1] > profits[i - 1][1]) {
winningResult++
}
}
if ((profits[i][1] + totalAssets) > maxAssets) {
maxAssets = profits[i][1] + totalAssets
maxAssetsTime = profits[i][0]
}
if (maxAssets > 0) {
var drawDown = 1 - (profits[i][1] + totalAssets) / maxAssets
if (drawDown > maxDrawdown) {
maxDrawdown = drawDown
maxDrawdownTime = profits[i][0]
maxDrawdownStartTime = maxAssetsTime
}
}
}
if (profits.length > 0) {
winningRate = winningResult / profits.length
}
// trim profits
var i = 0
var datas = []
var sum = 0
var preProfit = 0
var perRatio = 0
var rangeEnd = te
if ((te - ts) % period > 0) {
rangeEnd = (parseInt(te / period) + 1) * period
}
for (var n = ts; n < rangeEnd; n += period) {
var dayProfit = 0.0
var cut = n + period
while (i < profits.length && profits[i][0] < cut) {
dayProfit += (profits[i][1] - preProfit)
preProfit = profits[i][1]
i++
}
perRatio = ((dayProfit / totalAssets) * yearRange) / period
sum += perRatio
datas.push(perRatio)
}
var sharpeRatio = 0
var volatility = 0
if (datas.length > 0) {
var avg = sum / datas.length;
var std = 0;
for (i = 0; i < datas.length; i++) {
std += Math.pow(datas[i] - avg, 2);
}
volatility = Math.sqrt(std / datas.length);
if (volatility !== 0) {
sharpeRatio = (annualizedReturns - freeProfit) / volatility
}
}
return {
totalAssets: totalAssets,
yearDays: yearDays,
totalReturns: totalReturns,
annualizedReturns: annualizedReturns,
sharpeRatio: sharpeRatio,
volatility: volatility,
maxDrawdown: maxDrawdown,
maxDrawdownTime: maxDrawdownTime,
maxAssetsTime: maxAssetsTime,
maxDrawdownStartTime: maxDrawdownStartTime,
winningRate: winningRate
}
}
Downloading data
- Status bar data: after the backtest finishes, click "Download Table" at the top right of the "Status" panel to download the final status bar data as a CSV file.
- Log data: click "Download Table" at the top right of the "Logs" panel to download the backtest logs as a CSV file.