Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 7

Replica of TradingView's Backtesting Engine with Arrays

Here is a perfectly replicated TradingView backtesting engine condensed into a single library function
calculated with arrays. It includes TradingView's calculations for Net profit, Total Trades, Percent of
Trades Profitable, Profit Factor, Max Drawdown (absolute and percent), and Average Trade (absolute and
percent). Here's how TradingView defines each aspect of its backtesting system:
Net Profit: The overall profit or loss achieved.
Total Trades: The total number of closed trades, winning and losing.
Percent Profitable: The percentage of winning trades, the number of winning trades divided by the total
number of closed trades.
Profit Factor: The amount of money the strategy made for every unit of money it lost, gross profits
divided by gross losses.
Max Drawdown: The greatest loss drawdown, i.e., the greatest possible loss the strategy had compared
to its highest profits.
Average Trade: The sum of money gained or lost by the average trade, Net Profit divided by the overall
number of closed trades.

Here's how each variable is defined in the library function:


_backtest(bool _enter, bool _exit, float _startQty, float _tradeQty)

 bool _enter: When the strategy should enter a trade (entry condition)

 bool _exit: When the strategy should exit a trade (exit condition)

 float _startQty: The starting capital in the account (for BTCUSD, it is the amount of USD the
account starts with)

 float _tradeQty: The amount of capital traded (if set to 1000 on BTCUSD, it will trade 1000 USD
on each trade)

Currently, this library only works with long strategies, and I've included a commented out section under
DEMO STRATEGY where you can replicate my results with TradingView's backtesting engine. There's tons
I could do with this beyond what is shown, but this was a project I worked on back in June of 2022
before getting burned out. Feel free to comment with any suggestions or bugs, and I'll try to add or fix
them all soon. Here's my list of thing to add to the library currently (may not all be added):

 Add commission calculations.

 Add support for shorting

 Add a graph that resembles TradingView's overview graph.

 Clean and optimize code.


 Clean up in a way that makes it easy to add other TradingView calculations (such as Sharpe and
Sortino ratio).

 Separate all variables, so they become accessible outside of calculations (such as gross profit,
gross loss, number of winning trades, number of losing trades, etc.).

//@version=5

library("OzzyBacktestEngine")

// quality of life user function that help when working with array data usage

_indexArray(_array, _index) =>

array.size(_array) > _index ? _index : na

_clearArray(_array, _oversize) =>

if array.size(_array) >= _oversize

array.pop(_array)

// _backtest() function

export _backtest(bool _enter, bool _exit, float _startQty, float _tradeQty) =>

var profit_array = array.new_float()

var percentProfit_array = array.new_float()

var entryPrice_array = array.new_float()

var entryQty_array = array.new_float()

var exitPrice_array = array.new_float()

var tradeReturn_array = array.new_float()

var trade = _tradeQty / close

var tradeAmt =0

var tradeProfitable = 0
var grossProfit = 0.0

var grossLoss = 0.0

var netProfit = 0.0

var netPercentProfit = 0.0

var percentProfitable = 0.0

var profitFactor = 0.0

var entryPrice = 0.0

var entryQty = 0.0

var exitPrice = 0.0

var tradeOpen =0

var maxDrawdown = 0.0

var maxPercentDrawdown= 0.0

var avgTrade = 0.0

var avgPercentTrade = 0.0

if barstate.isconfirmed

if barstate.isfirst

array.unshift(profit_array, 0)

if _enter[1] and (tradeOpen == 0)

tradeOpen := 1

array.unshift(entryPrice_array, open)

entryPrice := array.get(entryPrice_array, _indexArray(entryPrice_array, 0))

array.unshift(entryQty_array, _tradeQty/entryPrice)

entryQty := array.get(entryQty_array, _indexArray(entryQty_array, 0))

netProfit := netProfit

_clearArray(entryPrice_array, 3)

_clearArray(entryQty_array, 3)
if _exit[1] and (tradeOpen == 1)

tradeOpen := 0

tradeAmt += 1

array.unshift(exitPrice_array, open)

exitPrice := array.get(exitPrice_array, _indexArray(exitPrice_array, 0))

_clearArray(exitPrice_array, 3)

tradeReturn = ((entryQty * exitPrice) - (entryPrice * entryQty))

netProfit += tradeReturn

netPercentProfit := (((_startQty + netProfit) - _startQty) / _startQty) * 100

array.unshift(tradeReturn_array, tradeReturn)

array.unshift(profit_array, netProfit)

array.unshift(percentProfit_array, netPercentProfit)

curr_net = array.get(profit_array, _indexArray(profit_array, 0))

last_net = array.get(profit_array, _indexArray(profit_array, 1))

if curr_net > last_net

tradeProfitable += 1

grossProfit += math.abs(tradeReturn)

if curr_net <= last_net

grossLoss += math.abs(tradeReturn)

percentProfitable := (tradeProfitable / tradeAmt) * 100

profitFactor := grossProfit / grossLoss

// Max Drawdown

indexMaxProfit = array.indexof(profit_array, array.max(profit_array, 0))

var maxProfit = 0.0


maxProfit := array.size(profit_array) > 2 ? array.get(profit_array, indexMaxProfit > 0 ?
indexMaxProfit : na) : array.get(profit_array, _indexArray(profit_array, 0))

drawdown = maxProfit - curr_net

var drawdown_array = array.new_float()

array.unshift(drawdown_array, drawdown)

indexMaxDrawdown = array.indexof(drawdown_array, array.max(drawdown_array, 0))

maxDrawdown := array.get(drawdown_array, indexMaxDrawdown >= 0 ? indexMaxDrawdown :


na)

maxPercentDrawdown := (maxDrawdown / (_startQty + array.get(profit_array,


indexMaxDrawdown))) * 100

// "THE PERCENTAGE AND ABBSOLUTE VALUES OF A DRAWDOWN ARE TWO DIFFERENT METRICS.
THEY ARE TRICKED INDEPENDENTLY."

// This takes the percent of the current Max Drawdown, not the max percent drawdown

// Average Trade

avgTrade := array.avg(tradeReturn_array)

avgPercentTrade := (avgTrade / _startQty) * 100

[netProfit, netPercentProfit, tradeAmt, percentProfitable, profitFactor, maxDrawdown,


maxPercentDrawdown, avgTrade, avgPercentTrade]

////////////////////////////////////////////

//// DEMO STRATEGY (PLOTTED ON CHART) ////

////////////////////////////////////////////

// //@version=5
// strategy("DEMO STRATEGY", overlay=true, default_qty_type = strategy.cash, default_qty_value =
1000, initial_capital = 10000)

// sma_fast = ta.sma(close, 9)

// sma_slow = ta.sma(close, 26)

// enterLong = ta.crossover(sma_fast, sma_slow)

// exitLong = ta.crossunder(sma_fast, sma_slow)

// if enterLong

// strategy.entry("Long", strategy.long)

// if exitLong

// strategy.close("Long")

sma_fast = ta.sma(close, 9)

sma_slow = ta.sma(close, 26)

enterLong = ta.crossover(sma_fast, sma_slow)

exitLong = ta.crossunder(sma_fast, sma_slow)

plot(sma_fast, color=color.lime)

plot(sma_slow, color=color.blue, linewidth=2)

// example of _backtest() function being used

[netProfit, netPercentProfit, tradeAmt, percentProfitable, profitFactor, maxDrawdown,


maxPercentDrawdown, avgTrade, avgPercentTrade] = _backtest(enterLong, exitLong, 10000, 1000)
// table showing data

backtest_table = table.new(position.top_right, 7, 2, bgcolor=color.new(#CFCFCF, 80), border_color=na,


border_width=1)

_addCell(_table) =>

table.cell(_table, 1, 0, text="Net Profit", text_color=color.white)

table.cell(_table, 2, 0, text="Total Trades", text_color=color.white)

table.cell(_table, 3, 0, text="Percent of Trades \nProfitable", text_color=color.white)

table.cell(_table, 4, 0, text="Profit Factor", text_color=color.white)

table.cell(_table, 5, 0, text="Max Drawdown", text_color=color.white)

table.cell(_table, 6, 0, text="Average Trade", text_color=color.white)

table.cell(_table, 1, 1, text="$" + str.tostring(math.round(netProfit, 2)) + "\n" +


str.tostring(math.round(netPercentProfit, 2)) + "%", text_color=color.white)

table.cell(_table, 2, 1, text=str.tostring(math.round(tradeAmt)), text_color=color.white)

table.cell(_table, 3, 1, text=str.tostring(math.round(percentProfitable, 2)) + "%",


text_color=color.white)

table.cell(_table, 4, 1, text=str.tostring(math.round(profitFactor, 3)), text_color=color.white)

table.cell(_table, 5, 1, text=str.tostring(math.round(maxDrawdown, 2)) + "\n" +


str.tostring(math.round(maxPercentDrawdown, 2)) + "%", text_color=color.white)

table.cell(_table, 6, 1, text=str.tostring(math.round(avgTrade, 2)) + "\n" +


str.tostring(math.round(avgPercentTrade, 2)) + "%", text_color=color.white)

_addCell(backtest_table)

You might also like