This is a quantitative trading strategy based on monthly returns display. It uses Pine Script to calculate and present monthly and yearly returns of the strategy, as well as benchmark returns, in a table on the chart for analysis.
The core logic of this strategy is to track and calculate monthly and yearly performance, and display it in a table format. The key steps are:
Calculate monthly and yearly return based on change in equity.
Calculate benchmark monthly and yearly returns based on price change.
Store the monthly and yearly returns in arrays.
When bar is confirmed, populate a table using the stored return arrays to present monthly perf.
Display benchmark in second table row. Display alpha in third row.
By doing so, this script can clearly present the monthly returns in an organized table, along with benchmark comparison. This helps analyze performance.
The main advantages of this monthly return strategy are:
Intuitive display of monthly returns. The table format makes performance easy to analyze.
Clear benchmark comparison. Displaying a separate benchmark row allows strategy vs market performance analysis.
Alpha calculation. The alpha row shows if strategy is outperforming/underperforming the benchmark.
Customizable parameters for flexibility. User can set colors, date range, benchmark symbol etc as needed.
Some risks to note with this strategy are:
No trading logic. This merely displays returns, does not include actual trades.
Historical performance may not continue. As with any backtest, past returns do not guarantee future performance.
Potential errors in return calculation. Bugs could lead to incorrect monthly return figures.
Overall this script mainly serves as a performance visualization tool. The risks can be mitigated by ensuring accuracy of return calculations and not relying solely on backtests.
Some ways this monthly return strategy could be improved are:
Add actual trading strategy whose performance is displayed. Combine with a quant strategy.
Add further benchmark customization parameters like benchmark symbol, timeframe etc.
Enhance table formatting for better visuals - colors, cells, formatting etc.
Add other return metrics - CAGR, Sharpe ratio etc for more analysis.
This is a strategy focused specifically on displaying monthly returns of system and benchmark in table format for easier analysis. Its advantages are intuitive visualization and comparison of strategy vs benchmark. Risks are lack of trading logic and reliance on backtest. It can be enhanced by combining with quant strategy, adding further customization options and more metrics.
/*backtest start: 2023-12-01 00:00:00 end: 2023-12-31 23:59:59 period: 2h basePeriod: 15m exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}] */ //@version=5 strategy('Monthly Returns with Benchmark', overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=25, commission_type=strategy.commission.percent, commission_value=0.1) //////////// // Inputs // // Pivot points inputs leftBars = input(2, group = "Pivot Points") rightBars = input(1, group = "Pivot Points") // Styling inputs prec = input(2, title='Return Precision', group = "Monthly Table") from_date = input(timestamp("01 Jan 2000 00:00 +0000"), "From Date", group = "Monthly Table") prof_color = input.color(color.green, title = "Gradient Colors", group = "Monthly Table", inline = "colors") loss_color = input.color(color.red, title = "", group = "Monthly Table", inline = "colors") // Benchmark inputs use_cur = input.bool(true, title = "Use current Symbol for Benchmark", group = "Benchmark") symb_bench = input('BTC_USDT:swap', title = "Benchmark", group = "Benchmark") disp_bench = input.bool(true, title = "Display Benchmark?", group = "Benchmark") disp_alpha = input.bool(true, title = "Display Alpha?", group = "Benchmark") // Pivot Points Strategy swh = ta.pivothigh(leftBars, rightBars) swl = ta.pivotlow(leftBars, rightBars) hprice = 0.0 hprice := not na(swh) ? swh : hprice[1] lprice = 0.0 lprice := not na(swl) ? swl : lprice[1] le = false le := not na(swh) ? true : le[1] and high > hprice ? false : le[1] se = false se := not na(swl) ? true : se[1] and low < lprice ? false : se[1] if le strategy.entry('PivRevLE', strategy.long, comment='PivRevLE', stop=hprice + syminfo.mintick) if se strategy.entry('PivRevSE', strategy.short, comment='PivRevSE', stop=lprice - syminfo.mintick) plot(hprice, color=color.new(color.green, 0), linewidth=2) plot(lprice, color=color.new(color.red, 0), linewidth=2) /////////////////// // MONTHLY TABLE // new_month = month(time) != month(time[1]) new_year = year(time) != year(time[1]) eq = strategy.equity bench_eq = close // benchmark eq bench_eq_htf = request.security(symb_bench, timeframe.period, close) if (not use_cur) bench_eq := bench_eq_htf bar_pnl = eq / eq[1] - 1 bench_pnl = bench_eq / bench_eq[1] - 1 cur_month_pnl = 0.0 cur_year_pnl = 0.0 // Current Monthly P&L cur_month_pnl := bar_index == 0 ? 0 : time >= from_date and (time[1] < from_date or new_month) ? bar_pnl : (1 + cur_month_pnl[1]) * (1 + bar_pnl) - 1 // Current Yearly P&L cur_year_pnl := bar_index == 0 ? 0 : time >= from_date and (time[1] < from_date or new_year) ? bar_pnl : (1 + cur_year_pnl[1]) * (1 + bar_pnl) - 1 bench_cur_month_pnl = 0.0 bench_cur_year_pnl = 0.0 // Current Monthly P&L - Bench bench_cur_month_pnl := bar_index == 0 or (time[1] < from_date and time >= from_date) ? 0 : time >= from_date and new_month ? bench_pnl : (1 + bench_cur_month_pnl[1]) * (1 + bench_pnl) - 1 // Current Yearly P&L - Bench bench_cur_year_pnl := bar_index == 0 ? 0 : time >= from_date and (time[1] < from_date or new_year) ? bench_pnl : (1 + bench_cur_year_pnl[1]) * (1 + bench_pnl) - 1 var month_time = array.new_int(0) var year_time = array.new_int(0) var month_pnl = array.new_float(0) var year_pnl = array.new_float(0) var bench_month_pnl = array.new_float(0) var bench_year_pnl = array.new_float(0) // Filling monthly / yearly pnl arrays if array.size(month_time) > 0 if month(time) == month(array.get(month_time, array.size(month_time) - 1)) array.pop(month_pnl) array.pop(bench_month_pnl) array.pop(month_time) if array.size(year_time) > 0 if year(time) == year(array.get(year_time, array.size(year_time) - 1)) array.pop(year_pnl) array.pop(bench_year_pnl) array.pop(year_time) if (time >= from_date) array.push(month_time, time) array.push(year_time, time) array.push(month_pnl, cur_month_pnl) array.push(year_pnl, cur_year_pnl) array.push(bench_year_pnl, bench_cur_year_pnl) array.push(bench_month_pnl, bench_cur_month_pnl) // Monthly P&L Table var monthly_table = table(na) if array.size(year_pnl) > 0 and barstate.islastconfirmedhistory monthly_table := table.new(position.bottom_right, columns=15, rows=array.size(year_pnl) * 3 + 5, border_width=1) // Fill monthly performance table.cell(monthly_table, 0, 0, 'Perf', bgcolor = #999999) table.cell(monthly_table, 1, 0, 'Jan', bgcolor = #999999) table.cell(monthly_table, 2, 0, 'Feb', bgcolor = #999999) table.cell(monthly_table, 3, 0, 'Mar', bgcolor = #999999) table.cell(monthly_table, 4, 0, 'Apr', bgcolor = #999999) table.cell(monthly_table, 5, 0, 'May', bgcolor = #999999) table.cell(monthly_table, 6, 0, 'Jun', bgcolor = #999999) table.cell(monthly_table, 7, 0, 'Jul', bgcolor = #999999) table.cell(monthly_table, 8, 0, 'Aug', bgcolor = #999999) table.cell(monthly_table, 9, 0, 'Sep', bgcolor = #999999) table.cell(monthly_table, 10, 0, 'Oct', bgcolor = #999999) table.cell(monthly_table, 11, 0, 'Nov', bgcolor = #999999) table.cell(monthly_table, 12, 0, 'Dec', bgcolor = #999999) table.cell(monthly_table, 13, 0, ' ', bgcolor = #999999) table.cell(monthly_table, 14, 0, 'Year', bgcolor = #999999) max_abs_y = math.max(math.abs(array.max(year_pnl)), math.abs(array.min(year_pnl))) max_abs_m = math.max(math.abs(array.max(month_pnl)), math.abs(array.min(month_pnl))) for yi = 0 to array.size(year_pnl) - 1 by 1 table.cell(monthly_table, 0, yi + 1, str.tostring(year(array.get(year_time, yi))), bgcolor=#cccccc) table.cell(monthly_table, 13, yi + 1, ' ', bgcolor=#999999) y_color = color.from_gradient(array.get(year_pnl, yi), -max_abs_y, max_abs_y, loss_color, prof_color) table.cell(monthly_table, 14, yi + 1, str.tostring(math.round(array.get(year_pnl, yi) * 100, prec)), bgcolor=y_color) for mi = 0 to array.size(month_time) - 1 by 1 m_row = year(array.get(month_time, mi)) - year(array.get(year_time, 0)) + 1 m_col = month(array.get(month_time, mi)) m_color = color.from_gradient(array.get(month_pnl, mi), -max_abs_m, max_abs_m, loss_color, prof_color) table.cell(monthly_table, m_col, m_row, str.tostring(math.round(array.get(month_pnl, mi) * 100, prec)), bgcolor=m_color) // Fill benchmark performance next_row = array.size(year_pnl) + 1 if (disp_bench) table.cell(monthly_table, 0, next_row, 'Bench', bgcolor=#999999) table.cell(monthly_table, 1, next_row, 'Jan', bgcolor=#999999) table.cell(monthly_table, 2, next_row, 'Feb', bgcolor=#999999) table.cell(monthly_table, 3, next_row, 'Mar', bgcolor=#999999) table.cell(monthly_table, 4, next_row, 'Apr', bgcolor=#999999) table.cell(monthly_table, 5, next_row, 'May', bgcolor=#999999) table.cell(monthly_table, 6, next_row, 'Jun', bgcolor=#999999) table.cell(monthly_table, 7, next_row, 'Jul', bgcolor=#999999) table.cell(monthly_table, 8, next_row, 'Aug', bgcolor=#999999) table.cell(monthly_table, 9, next_row, 'Sep', bgcolor=#999999) table.cell(monthly_table, 10, next_row, 'Oct', bgcolor=#999999) table.cell(monthly_table, 11, next_row, 'Nov', bgcolor=#999999) table.cell(monthly_table, 12, next_row, 'Dec', bgcolor=#999999) table.cell(monthly_table, 13, next_row, ' ', bgcolor = #999999) table.cell(monthly_table, 14, next_row, 'Year', bgcolor=#999999) max_bench_abs_y = math.max(math.abs(array.max(bench_year_pnl)), math.abs(array.min(bench_year_pnl))) max_bench_abs_m = math.max(math.abs(array.max(bench_month_pnl)), math.abs(array.min(bench_month_pnl))) for yi = 0 to array.size(year_time) - 1 by 1 table.cell(monthly_table, 0, yi + 1 + next_row + 1, str.tostring(year(array.get(year_time, yi))), bgcolor=#cccccc) table.cell(monthly_table, 13, yi + 1 + next_row + 1, ' ', bgcolor=#999999) y_color = color.from_gradient(array.get(bench_year_pnl, yi), -max_bench_abs_y, max_bench_abs_y, loss_color, prof_color) table.cell(monthly_table, 14, yi + 1 + next_row + 1, str.tostring(math.round(array.get(bench_year_pnl, yi) * 100, prec)), bgcolor=y_color) for mi = 0 to array.size(month_time) - 1 by 1 m_row = year(array.get(month_time, mi)) - year(array.get(year_time, 0)) + 1 m_col = month(array.get(month_time, mi)) m_color = color.from_gradient(array.get(bench_month_pnl, mi), -max_bench_abs_m, max_bench_abs_m, loss_color, prof_color) table.cell(monthly_table, m_col, m_row + next_row + 1, str.tostring(math.round(array.get(bench_month_pnl, mi) * 100, prec)), bgcolor=m_color) // Fill Alpha if (disp_alpha) next_row := array.size(year_pnl) * 2 + 3 table.cell(monthly_table, 0, next_row, 'Alpha', bgcolor=#999999) table.cell(monthly_table, 1, next_row, 'Jan', bgcolor=#999999) table.cell(monthly_table, 2, next_row, 'Feb', bgcolor=#999999) table.cell(monthly_table, 3, next_row, 'Mar', bgcolor=#999999) table.cell(monthly_table, 4, next_row, 'Apr', bgcolor=#999999) table.cell(monthly_table, 5, next_row, 'May', bgcolor=#999999) table.cell(monthly_table, 6, next_row, 'Jun', bgcolor=#999999) table.cell(monthly_table, 7, next_row, 'Jul', bgcolor=#999999) table.cell(monthly_table, 8, next_row, 'Aug', bgcolor=#999999) table.cell(monthly_table, 9, next_row, 'Sep', bgcolor=#999999) table.cell(monthly_table, 10, next_row, 'Oct', bgcolor=#999999) table.cell(monthly_table, 11, next_row, 'Nov', bgcolor=#999999) table.cell(monthly_table, 12, next_row, 'Dec', bgcolor=#999999) table.cell(monthly_table, 13, next_row, '', bgcolor=#999999) table.cell(monthly_table, 14, next_row, 'Year', bgcolor=#999999) max_alpha_abs_y = 0.0 for yi = 0 to array.size(year_time) - 1 by 1 if (math.abs(array.get(year_pnl, yi) - array.get(bench_year_pnl, yi)) > max_alpha_abs_y) max_alpha_abs_y := math.abs(array.get(year_pnl, yi) - array.get(bench_year_pnl, yi)) max_alpha_abs_m = 0.0 for mi = 0 to array.size(month_pnl) - 1 by 1 if (math.abs(array.get(month_pnl, mi) - array.get(bench_month_pnl, mi)) > max_alpha_abs_m) max_alpha_abs_m := math.abs(array.get(month_pnl, mi) - array.get(bench_month_pnl, mi)) for yi = 0 to array.size(year_time) - 1 by 1 table.cell(monthly_table, 0, yi + 1 + next_row + 1, str.tostring(year(array.get(year_time, yi))), bgcolor=#cccccc) table.cell(monthly_table, 13, yi + 1 + next_row + 1, ' ', bgcolor=#999999) y_color = color.from_gradient(array.get(year_pnl, yi) - array.get(bench_year_pnl, yi), -max_alpha_abs_y, max_alpha_abs_y, loss_color, prof_color) table.cell(monthly_table, 14, yi + 1 + next_row + 1, str.tostring(math.round((array.get(year_pnl, yi) - array.get(bench_year_pnl, yi)) * 100, prec)), bgcolor=y_color) for mi = 0 to array.size(month_time) - 1 by 1 m_row = year(array.get(month_time, mi)) - year(array.get(year_time, 0)) + 1 m_col = month(array.get(month_time, mi)) m_color = color.from_gradient(array.get(month_pnl, mi) - array.get(bench_month_pnl, mi), -max_alpha_abs_m, max_alpha_abs_m, loss_color, prof_color) table.cell(monthly_table, m_col, m_row + next_row + 1, str.tostring(math.round((array.get(month_pnl, mi) - array.get(bench_month_pnl, mi)) * 100, prec)), bgcolor=m_color)