//This breaks down the source code behind my Monthly Performance Table script.
// This monthly performance table code can be copy & pasted into any strategy script to add a table that breaks down key monthly and yearly performance metrics for strategy backtests:
//Note: the code below includes a sample strategy script taken from the Simple Pullback Strategy lesson, but the strategy code can be replaced with any other strategy and the table should still work :)
//CHANGES TO CODE
// After releasing this script publicly, I've had some traders reach out to offer improvements to the code. So the source code you see below is a little different from the video, but the concept remains the same.
// Huge thanks to Marc in particular (https://www.tradingview.com/u/Mrcrbw/) for providing major improvements and fixes to the code that made this script much better!
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Version 2 (Last Updated 29th June, 2023)
// Huge thanks to Marc (https://www.tradingview.com/u/Mrcrbw/) for providing
// major improvements and fixes to the code that made this script much better!
// @version=5
//////////////////////////////////////////////////////////////////////////
// EXAMPLE STRATEGY (REPLACE THIS CODE WITH YOUR OWN) //
// Replace lines 15 -> 69 with your own strategy script code! //
// This example system is my Simple Pullback Strategy //
// Check my TradingView profile for more info about this script //
//////////////////////////////////////////////////////////////////////////
strategy("Monthly Performance Table",
overlay=true,
initial_capital=100000,
default_qty_type=strategy.percent_of_equity,
default_qty_value=100,
commission_type=strategy.commission.cash_per_contract,
commission_value=0.005)
// Get user input
i_ma1 = input.int(title="MA 1 Length", defval=200, step=10, group="Strategy Parameters", tooltip="Long-term MA")
i_ma2 = input.int(title="MA 2 Length", defval=10, step=10, group="Strategy Parameters", tooltip="Short-term MA")
i_stopPercent = input.float(title="Stop Loss Percent", defval=0.10, step=0.1, group="Strategy Parameters", tooltip="Failsafe Stop Loss Percent Decline")
i_lowerClose = input.bool(title="Exit On Lower Close", defval=false, group="Strategy Parameters", tooltip="Wait for a lower-close before exiting above MA2")
i_startTime = input.time(title="Start Filter", defval=timestamp("01 Jan 1995 13:30 +0000"), group="Time Filter", tooltip="Start date & time to begin searching for setups")
i_endTime = input.time(title="End Filter", defval=timestamp("1 Jan 2099 19:30 +0000"), group="Time Filter", tooltip="End date & time to stop searching for setups")
// Get indicator values
ma1 = ta.sma(close, i_ma1)
ma2 = ta.sma(close, i_ma2)
// Check filter(s)
f_dateFilter = time >= i_startTime and time <= i_endTime
// Check buy/sell conditions
var float buyPrice = 0
buyCondition = close > ma1 and close < ma2 and strategy.position_size == 0 and f_dateFilter
sellCondition = close > ma2 and strategy.position_size > 0 and (not i_lowerClose or close < low[1])
stopDistance = strategy.position_size > 0 ? ((buyPrice - close) / close) : na
stopPrice = strategy.position_size > 0 ? buyPrice - (buyPrice * i_stopPercent) : na
stopCondition = strategy.position_size > 0 and stopDistance > i_stopPercent
// Enter positions
if buyCondition
strategy.entry(id="Long", direction=strategy.long, comment="Equity = $" + str.tostring(strategy.equity, "#.##"))
if buyCondition[1]
buyPrice := strategy.position_avg_price
// Exit positions
if sellCondition or stopCondition
strategy.close(id="Long", comment="Exit" + (stopCondition ? "SL=true" : ""))
buyPrice := na
// Draw pretty colors
plot(buyPrice, color=color.lime, style=plot.style_linebr)
plot(stopPrice, color=color.red, style=plot.style_linebr, offset=-1)
plot(ma1, color=color.blue)
plot(ma2, color=color.orange)
////////////////////////////////////////////////////////////
// END EXAMPLE STRATEGY (REPLACE THIS CODE WITH YOUR OWN) //
////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MONTHLY TABLE based on the script written by QuantNomad - Copy & Paste code from here down into your strategy script /{
// Original Script: https://tradingview.com/script/kzp8e4X3-Monthly-Returns-in-PineScript-Strategies/ //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var string GROUP_PERFORMANCE_TABLE = "Monthly Performance Table"
mptable_on = input.bool(title="Turn On |", defval=true, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Toggles")
mptable_debug = input.bool(title="Debug Mode |", defval=false, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Toggles")
mptable_precision = input.int(title="Decimal Precision", defval=2, minval=1, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Toggles", tooltip="Decimal precision of each cell")
mptable_titleColor = input.color(title="Title Cell Color", defval=#cccccc, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_titleTextColor = input.color(title="Title Text Color", defval=#363a45, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_textColor = input.color(title="Cell Text Color", defval=color.white, display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_ProfitColor = input.color(title="Year Profit Color", defval=color.new(color.green, 50), display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_LossColor = input.color(title="Year Loss Color", defval=color.new(color.red, 50), display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_BreakEvenColor = input.color(title="Year B/E Color", defval=color.new(color.white, 50), display=display.none, group=GROUP_PERFORMANCE_TABLE, inline="MPT_Colors")
mptable_pageNumber = input.int(title="Page Number", defval=1, minval=1, step=1, maxval=10, display=display.none, group=GROUP_PERFORMANCE_TABLE, tooltip="Which page of results to display") - 1 // -1 is for corection for arrays. Aaray index start with 0
mptable_pageSize = input.int(title="Page Size (Rows)", defval=20, minval=1, display=display.none, group=GROUP_PERFORMANCE_TABLE, tooltip="How many rows to display") - 1 // 9 is used to show 10 rows of data due to array start from 0
mptable_tableTextSize = input.string(title="Text Size", defval="Small", options=["Auto", "Huge", "Large", "Normal", "Small", "Tiny"], display=display.none, group=GROUP_PERFORMANCE_TABLE)
// Custom function for getting table text sized based on user input
table_text_size(_size) =>
switch _size
"Auto" => size.auto
"Huge" => size.huge
"Large" => size.large
"Normal" => size.normal
"Small" => size.small
=> size.tiny
tableTextSize = table_text_size(mptable_tableTextSize)
// Custom function for getting decimal precision based on given number
// (eg. if number is > 0 but < 0.05 or < 0 and > -0.05, set precision to 3 to avoid rounding to 0 which is misleading)
GetRoundingPrecision(float num) =>
if (num > 0 and num < 0.05) or (num < 0 and num > -0.05)
3
else
mptable_precision
// Define an open trade's cost (used to calculate commission cost)
type TradeCost
int entryTime
float entryPrice
float cost
// Define a monthly/yearly return type
type StrategyReturn
float profit
float drawdown
float peak
int timestamp
// Store accumulated P&L values
var float accumulatedMonthlyPL = 0
var float accumulatedYearlyPL = 0
var float bestAccumulatedMonthlyPL = 0
var float bestAccumulatedYearlyPL = 0
// Store drawdown values
var float equityPeak = strategy.initial_capital
var float yearlyEquityHigh = 0
var float currentYearlyDrawdown = 0
var float yearlyMaxDrawdown = 0
var float worstDrawdown = 0
var float monthlyEquityHigh = 0
var float currentMonthlyDrawdown = 0
var float monthlyMaxDrawdown = 0
var int currentDrawdownBars = 0
var int maxDrawdownBars = 0
// Store stat arrays
var totalDrawdownBars = array.new<int>(0)
var totalDrawdowns = array.new<float>(0)
// Store long & short trade count
var int totalBreakEvenTrades = 0
var int totalLongTrades = 0
var int totalLongTradeWins = 0
var int totalShortTrades = 0
var int totalShortTradeWins = 0
// Store open trade commission costs in array
var costOfOpenTrades = array.new<TradeCost>(0)
// Detect opened trade and save cost of trade (I tried many methods to get my numbers to match the Cumulative Profit list in the Strategy Tester, no idea why, but none of them worked without doing this)
if strategy.opentrades != strategy.opentrades[1] and strategy.closedtrades == strategy.closedtrades[1]
costOfTrade = strategy.grossloss - strategy.grossloss[1]
costOfOpenTrades.push(TradeCost.new(strategy.opentrades.entry_time(strategy.opentrades - 1), strategy.opentrades.entry_price(strategy.opentrades - 1), costOfTrade))
// Detect a closed trade
// TV Documentation: Trade List's Cumulative Profit % Formula = TradeProfit / (InitialCapital + Cumulative Profit of the previous trades) * 100%
if strategy.closedtrades != strategy.closedtrades[1]
// Retrieve trade cost for the closed trade
float tradeCost = 0
int removeIdx = -1
if costOfOpenTrades.size() > 0
for i = 0 to costOfOpenTrades.size() - 1
TradeCost tc = costOfOpenTrades.get(i)
if tc.entryTime == strategy.closedtrades.entry_time(strategy.closedtrades - 1) and tc.entryPrice == strategy.closedtrades.entry_price(strategy.closedtrades - 1)
tradeCost := tc.cost
removeIdx := i
break
// Remove cost
if removeIdx != -1
costOfOpenTrades.remove(removeIdx)
// Calculate equity before trade closed (strategy.equity will not do, because it changes bar-by-bar based on open P&L not realized P&L)
float preEquity = strategy.initial_capital + strategy.netprofit[1]
// Calculate P&L + cost of this trade
float profitLoss = 0
if strategy.losstrades > strategy.losstrades[1]
profitLoss := (strategy.grossloss - (strategy.grossloss[1] - tradeCost)) * -1
else
profitLoss := strategy.grossprofit - strategy.grossprofit[1]
// Check if this was a long or short trade and if it won or lost
if strategy.position_size[1] > 0
totalLongTrades := totalLongTrades + 1
if profitLoss > 0
totalLongTradeWins := totalLongTradeWins + 1
else if strategy.position_size[1] < 0
totalShortTrades := totalShortTrades + 1
if profitLoss > 0
totalShortTradeWins := totalShortTradeWins + 1
// Check if the trade broke even
if profitLoss == 0
totalBreakEvenTrades := totalBreakEvenTrades + 1
// Calculate cumulative profit % for this trade
float cumulativeProfitPercent = (profitLoss / preEquity) * 100
// Store highest peak value of equity (we can now use strategy.equity since equity has updated to realized P&L on this bar)
if strategy.equity > equityPeak
equityPeak := strategy.equity
// Calculate total system drawdown %
float equityDD = ((strategy.equity - equityPeak) / equityPeak) * 100
if equityDD < worstDrawdown
worstDrawdown := equityDD
// Store accumulated monthly + yearly P&L
accumulatedMonthlyPL := cumulativeProfitPercent + accumulatedMonthlyPL[1]
accumulatedYearlyPL := accumulatedYearlyPL + cumulativeProfitPercent
// Save max favourable excursion for this month (ie. peak return as %)
if accumulatedMonthlyPL > bestAccumulatedMonthlyPL
bestAccumulatedMonthlyPL := accumulatedMonthlyPL
// Save max favourable excursion for this year (ie. peak return as %)
if accumulatedYearlyPL > bestAccumulatedYearlyPL
bestAccumulatedYearlyPL := accumulatedYearlyPL
// Track max equity high over current year for max yearly drawdown calculation
if accumulatedYearlyPL > yearlyEquityHigh
yearlyEquityHigh := accumulatedYearlyPL
// Check if our yearly realized equity high minus current realized equity exceeds our stored max drawdown for the year, update if necessary, and save worst drawdown
if accumulatedYearlyPL - yearlyEquityHigh < 0
currentYearlyDrawdown := accumulatedYearlyPL - yearlyEquityHigh
if currentYearlyDrawdown < yearlyMaxDrawdown
yearlyMaxDrawdown := currentYearlyDrawdown
currentDrawdownBars := currentDrawdownBars + 1
// Track max equity high over current month for max monthly drawdown calculation
if accumulatedMonthlyPL > monthlyEquityHigh
monthlyEquityHigh := accumulatedMonthlyPL
// Check if our monthly realized equity high minus current realized equity exceeds our stored max drawdown for the month, update if necessary, and save worst drawdown
if accumulatedMonthlyPL - monthlyEquityHigh < 0
currentMonthlyDrawdown := accumulatedMonthlyPL - monthlyEquityHigh
if currentMonthlyDrawdown < monthlyMaxDrawdown
monthlyMaxDrawdown := currentMonthlyDrawdown
// Debug label
if mptable_debug
string debugTip = "Equity = $" + str.tostring(strategy.equity, "#.##") +
"\nP&L=" + str.tostring(cumulativeProfitPercent) + "%" +
"\nAccumMonthlyP&L=" + str.tostring(math.round(accumulatedMonthlyPL, GetRoundingPrecision(accumulatedMonthlyPL))) + "%" +
"\nAccumYearlyP&L=" + str.tostring(math.round(accumulatedYearlyPL, GetRoundingPrecision(accumulatedYearlyPL))) + "%" +
"\nMonthlyMaxDD=" + str.tostring(math.round(monthlyMaxDrawdown, GetRoundingPrecision(monthlyMaxDrawdown))) + "%" +
"\nYearlyMaxDD=" + str.tostring(math.round(yearlyMaxDrawdown, GetRoundingPrecision(yearlyMaxDrawdown))) + "%" +
"\nTotalMaxDD=" + str.tostring(math.round(worstDrawdown, GetRoundingPrecision(worstDrawdown))) + "%" +
"\nCurrentDDBars=" + str.tostring(currentDrawdownBars) +
"\nMaxDDBars=" + str.tostring(maxDrawdownBars) +
"\nTotalBreakEven=" + str.tostring(totalBreakEvenTrades) +
"\nTotalLongs=" + str.tostring(totalLongTrades) +
"\nTotalLongWins=" + str.tostring(totalLongTradeWins) +
"\nTotalShorts=" + str.tostring(totalShortTrades) +
"\nTotalShortWins=" + str.tostring(totalShortTradeWins)
label.new(bar_index, high + (high * 0.01), "P&L " + str.tostring(math.round(cumulativeProfitPercent, GetRoundingPrecision(cumulativeProfitPercent))) + "%", tooltip=debugTip, textcolor=color.white)
// Calculate drawdown since last equity high (NOT max drawdown, just the current max DD since we were out of DD)
float t_equityDD = ((strategy.equity - equityPeak) / equityPeak) * 100
var float currentMaxDrawdownSinceLast = 0
// Update Max Drawdown bar count and current DD if equity is under water, check isconfirmed to prevent double-counting bars with recalc_on_order_fills on
if strategy.equity < equityPeak and barstate.isconfirmed
currentDrawdownBars := currentDrawdownBars + 1
if currentDrawdownBars > maxDrawdownBars
maxDrawdownBars := currentDrawdownBars
if t_equityDD < currentMaxDrawdownSinceLast
currentMaxDrawdownSinceLast := t_equityDD
else
if currentDrawdownBars > 0
totalDrawdownBars.push(currentDrawdownBars)
totalDrawdowns.push(currentMaxDrawdownSinceLast)
currentDrawdownBars := 0
currentMaxDrawdownSinceLast := 0
// Prepare arrays to store Yearly and Monthly P&Ls
var monthlyReturns = array.new<StrategyReturn>(0)
var yearlyReturns = array.new<StrategyReturn>(0)
var bool firstEntryTime = false
// Retrieve entry time of initial entry in open position
if not firstEntryTime and strategy.opentrades.entry_time(0)
firstEntryTime := true
// Detect new month and year
new_month = month(time) != month(time[1])
new_year = year(time) != year(time[1])
// Detect a new month and store its return profile
if not barstate.isfirst and new_month and firstEntryTime or barstate.islastconfirmedhistory
StrategyReturn mr = StrategyReturn.new(accumulatedMonthlyPL, monthlyMaxDrawdown, bestAccumulatedMonthlyPL, time[1]) // time)
monthlyReturns.push(mr)
accumulatedMonthlyPL := 0
monthlyMaxDrawdown := 0
monthlyEquityHigh := 0
currentMonthlyDrawdown := 0
bestAccumulatedMonthlyPL := 0
// Detect a new year and reset tracking variables
if not barstate.isfirst and new_year and firstEntryTime or barstate.islastconfirmedhistory
StrategyReturn yr = StrategyReturn.new(accumulatedYearlyPL, yearlyMaxDrawdown, bestAccumulatedYearlyPL, time[1])
yearlyReturns.push(yr)
accumulatedYearlyPL := 0
yearlyMaxDrawdown := 0
yearlyEquityHigh := 0
currentYearlyDrawdown := 0
bestAccumulatedYearlyPL := 0
// DEBUG code
bgcolor(mptable_debug and new_month ? color.lime : na, title="New Month", display=display.none)
bgcolor(mptable_debug and new_year ? color.red : na, title="New Year", display=display.none)
// END DEBUG CODE
// Define Monthly P&L Table
var table performance_table = table(na)
//Adjust mptable_pageSize if the years are less than the mptable_pageSize
if yearlyReturns.size() < mptable_pageSize
mptable_pageSize := yearlyReturns.size()
// Caluclate the start and end of page to display
startIndex = math.max(math.min(yearlyReturns.size() - 1, yearlyReturns.size() - 1 - (mptable_pageSize + 1) * mptable_pageNumber), mptable_pageSize - 1)
endIndex = math.max(startIndex - mptable_pageSize, 0)
mptable_pageSize := endIndex <= mptable_pageSize ? endIndex : mptable_pageSize
// If this is the last bar on our chart, display the performance table
var int EXTRA_STAT_ROWS = 5 // This ensures table includes enough rows for CAGR etc
if mptable_on and monthlyReturns.size() > 0 and barstate.islastconfirmedhistory
// Create table (100 rows = 100 years of data, should be plenty for all markets!)
performance_table := table.new(position.bottom_right, columns=17, rows=yearlyReturns.size() + EXTRA_STAT_ROWS, border_width=1)
// Set column headers
performance_table.cell(0, 0, "Year", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(1, 0, "Jan", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(2, 0, "Feb", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(3, 0, "Mar", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(4, 0, "Apr", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(5, 0, "May", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(6, 0, "Jun", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(7, 0, "Jul", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(8, 0, "Aug", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(9, 0, "Sep", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(10, 0, "Oct", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(11, 0, "Nov", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(12, 0, "Dec", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(13, 0, "TOTAL", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
performance_table.cell(14, 0, "MaxDD", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
// Set yearly values
for year_index = startIndex to yearlyReturns.size() == 0 ? na : endIndex
// Get yearly return for this loop, set year number in first column, determine color of cell
StrategyReturn yearlyReturn = yearlyReturns.get(year_index)
// Set year title and determine color
performance_table.cell(0, year_index + 1, str.tostring(year(yearlyReturn.timestamp)), bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_size=tableTextSize)
color y_color = yearlyReturn.profit > 0 ? mptable_ProfitColor : yearlyReturn.profit == 0 ? mptable_BreakEvenColor : mptable_LossColor
// Populate yearly cell values
string yearlyTip = "MaxDD: " + str.tostring(math.round(yearlyReturn.drawdown, GetRoundingPrecision(yearlyReturn.drawdown))) + "%" +
"\nMFE: " + str.tostring(math.round(yearlyReturn.peak, GetRoundingPrecision(yearlyReturn.peak))) + "%"
performance_table.cell(13, year_index + 1, (yearlyReturn.profit > 0 ? "+" : "") + str.tostring(math.round(yearlyReturn.profit, GetRoundingPrecision(yearlyReturn.profit))) + "%", bgcolor=y_color, text_color=color.white, text_size=tableTextSize, tooltip=yearlyTip)
performance_table.cell(14, year_index + 1, str.tostring(math.round(yearlyReturn.drawdown, GetRoundingPrecision(yearlyReturn.drawdown))) + "%", bgcolor=mptable_BreakEvenColor, text_color=color.white, text_size=tableTextSize)
// Set monthly values
for month_index = 0 to monthlyReturns.size() - 1
// Get monthly return for this loop, get current year for this loop, then calculate the corresponding table column and row
StrategyReturn monthlyReturn = monthlyReturns.get(month_index)
int yearOfMonth = year(monthlyReturn.timestamp)
int monthCol = month(monthlyReturn.timestamp)
// populate monthly profit only if the years of the yearly return match with the monthly return.
if yearOfMonth == year(yearlyReturn.timestamp)
// Determine color for monthly P&L
color m_color = monthlyReturn.profit > 0 ? color.new(mptable_ProfitColor, color.t(mptable_ProfitColor) + 20) : monthlyReturn.profit == 0 ? color.new(mptable_BreakEvenColor, color.t(mptable_BreakEvenColor) + 20) : color.new(mptable_LossColor, color.t(mptable_LossColor) + 20)
// Set monthly P&L cell
string monthlyTip = "MaxDD: " + str.tostring(math.round(monthlyReturn.drawdown, GetRoundingPrecision(monthlyReturn.drawdown))) + "%" +
"\nMFE: " + str.tostring(math.round(monthlyReturn.peak, GetRoundingPrecision(monthlyReturn.peak))) + "%"
performance_table.cell(monthCol, year_index + 1, str.tostring(math.round(monthlyReturn.profit, GetRoundingPrecision(monthlyReturn.profit))), bgcolor=m_color, text_color=color.white, text_size=tableTextSize, tooltip=monthlyTip)
float percentReturn = (strategy.netprofit / strategy.initial_capital) * 100
float cagr = (math.pow((strategy.netprofit + strategy.initial_capital) / strategy.initial_capital, 1 / yearlyReturns.size()) - 1) * 100
float mar = cagr / math.abs(worstDrawdown)
lastMonthRowIndex = startIndex < 5 ? 5 : startIndex
// Populate table data
float totalWinRate = (strategy.wintrades / strategy.closedtrades) * 100
float longWinRate = nz((totalLongTradeWins / totalLongTrades) * 100)
float shortWinRate = nz((totalShortTradeWins / totalShortTrades) * 100)
string returnTip = "Based on a total of " + str.tostring(strategy.closedtrades) + " trades" +
"\nWin Rate = " + str.tostring(math.round(totalWinRate, GetRoundingPrecision(totalWinRate))) + "%" +
"\nLong Trades = " + str.tostring(totalLongTrades) + " (Win " + str.tostring(math.round(longWinRate, GetRoundingPrecision(longWinRate))) + "%)" +
"\nShort Trades = " + str.tostring(totalShortTrades) + " (Win " + str.tostring(math.round(shortWinRate, GetRoundingPrecision(shortWinRate))) + "%)"
performance_table.cell(15, lastMonthRowIndex, "Return: " + (percentReturn > 0 ? "+" : "") + str.tostring(math.round(percentReturn, GetRoundingPrecision(percentReturn))) + "%", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip=returnTip)
performance_table.cell(15, lastMonthRowIndex - 1, "MAR: " + str.tostring(mar, "#.##"), bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip="Measure of return adjusted for risk: CAGR divided by Max Drawdown. Indicates how comfortable the system might be to trade. Higher than 0.5 is ideal, 1.0 and above is very good, and anything 3.0 or above should be considered suspicious.")
performance_table.cell(15, lastMonthRowIndex - 2, "DD Bars: " + str.tostring(maxDrawdownBars), bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip="Average Drawdown Bars: " + str.tostring(totalDrawdownBars.avg(), "#.#") + "\n\nThis is how many bars it took to recover the longest drawdown (note: this is different to the MAX drawdown, and represents time drawdown)")
performance_table.cell(15, lastMonthRowIndex - 3, "MaxDD: " + str.tostring(math.round(worstDrawdown, GetRoundingPrecision(worstDrawdown))) + "%", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip="Average Drawdown: " + str.tostring(totalDrawdowns.avg(), "#.##") + "%\n\nThis number is different to the Strategy Tester because this number is based on closed trade equity while the Tester's MaxDD is based on open equity.")
performance_table.cell(15, lastMonthRowIndex - 4, "CAGR: " + (cagr > 0 ? "+" : "") + str.tostring(math.round(cagr, GetRoundingPrecision(cagr))) + "%", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip="Compounded annual growth rate")
performance_table.cell(15, lastMonthRowIndex - 5, "REALIZED P&L", bgcolor=mptable_titleColor, text_color=mptable_titleTextColor, text_font_family=font.family_monospace, text_size=tableTextSize, tooltip="These numbers are based on Realized equity (closed trades)")
// } END MONTHLY TABLE
/////////// END OF PERFORMANCE TABLE CODE. ///////////
Overview
Monthly Performance Table buys when price is above a long-term moving average but below a short-term moving average, and exits when price closes above the short-term moving average or hits a percentage stop loss. The script calculates monthly and yearly percentage returns and displays them in a configurable dashboard table. Inputs allow adjusting moving average lengths, date filters, stop percentages, and table formatting like colors, pagination, and text sizes.
Customizable monthly and yearly performance table display
Built-in dual simple moving average pullback strategy
Configurable date range filtering for backtesting
Table display options for pagination, colors, and text size
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
MA 1 Length
int
200
i_ma1
MA 2 Length
int
10
i_ma2
Stop Loss Percent
float
0.10
i_stopPercent
Exit On Lower Close
bool
false
i_lowerClose
Start Filter
time
timestamp("01 Jan 1995 13:30 +0000")
i_startTime
End Filter
time
timestamp("1 Jan 2099 19:30 +0000")
i_endTime
Turn On |
bool
true
mptable_on
Debug Mode |
bool
false
mptable_debug
Decimal Precision
int
2
mptable_precision
Title Cell Color
color
#cccccc
mptable_titleColor
Title Text Color
color
#363a45
mptable_titleTextColor
Cell Text Color
color
color.white
mptable_textColor
Show all 18 settings
Setting
Type
Default
Variable
Year Profit Color
color
color.new(color.green, 50)
mptable_ProfitColor
Year Loss Color
color
color.new(color.red, 50)
mptable_LossColor
Year B/E Color
color
color.new(color.white, 50)
mptable_BreakEvenColor
Page Number
int
1
mptable_pageNumber
Page Size (Rows)
int
20
mptable_pageSize
Text Size
string
Small
mptable_tableTextSize
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
This mean-reversion strategy plots Bollinger Bands and a long-term moving average directly on the price chart. It enters trades when price candles penetrate the outer Bollinger Bands beyond a user-defined body percentage, anticipating a price reversal. Entries
Inspired by Linda Raschke's strategy for T-note futures, this script identifies extended trends where price remains on one side of a 5-period SMA for more than eight consecutive bars. It enters long on the first pullback below the SMA following an eight-bar bu
This strategy generates buy and sell signals based on Bollinger Band crossovers combined with a simple moving average trend filter. It enters a long position when price crosses above the lower band while trading above the SMA filter line, and enters a short po
This strategy identifies long entry opportunities based on short-term price pullbacks below a major trend filter. It enters long when three consecutive lower closes occur below the 200-period simple moving average, followed by the current bar closing back abov
This strategy evaluates market trend direction using a Noro TrendRibbon and creates a price corridor based on recent moving-average extremes, plotting directly on the price chart. It executes long entries when price dips below the corridor during an upward tre
This strategy overlays volume-weighted average price deviation bands and an exponential moving average trend filter directly on the price chart. It enters long positions during an uptrend when price recovers above the lower band, and enters short positions in