Period Dollar Cost Average Backtester
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © ThiagoSchmitz

//@version=5
indicator("Period Dollar Cost Average Backtester", "PDCA BAcktester", precision=8)
float amount = input.float(10.0, "Amount to buy")
float fee = input.float(0.1, "Broker Fee %")
int frequency = input.int(1, "Frequency", inline="frequency")
string unit = input.string("hours", "", options=["minutes", "hours", "days"], inline="frequency")
int startDate = input.time(timestamp("Jan 01 2018"), "Starting Date")
int endDate = input.time(timestamp("Jan 01 2022"), "Ending Date")
float cellWidth = input.float(14.0, "Info Cell Width", group="panel", inline="cell")
float cellHeight = input.float(10.0, "Info Cell Height", group="panel", inline="cell")

var float coins = 0.0
var float invested = 0.0
var float firstEntry = 0.0
var float totalFee = 0.0
var bool configured = false
var float grossProfit = 0.0
var float grossLoss = 0.0
var int totalTrades = 0
var float drawdown = 0.0
var float maxRelativeDrawdown = 0.0
var int maxConsecutiveWins = 0
var int maxConsecutiveLosses = 0
var int consecutiveWins = 0
var int consecutiveLosses = 0
var int winningTrades = 0
var int losingTrades = 0
var table info = table.new(position.middle_right, 10,10, #212121, #313131, 1, #313131, 1)
bool window = time >= startDate and time < endDate

if not configured
    table.cell(info, 0, 0, "Total Invested: $0.00",                 cellWidth, cellHeight, color.green, text.align_left)
    table.cell(info, 1, 0, "Total Net Profit: $0.00",               cellWidth, cellHeight, color.fuchsia, text.align_left)
    
    table.cell(info, 0, 1, "Holding Profit: $0.00",                 cellWidth, cellHeight, color.yellow, text.align_left)
    table.cell(info, 1, 1, "Entry Price: $0.00",                    cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 2, "Gross Profit: $0.00",                   cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 2, "Gross Loss: $0.00",                     cellWidth, cellHeight, #BDBDBD, text.align_left)    
    
    table.cell(info, 0, 3, "Profit Factor: 0",                      cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 3, "Profit/Trade: $0.00",                   cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 4, "Recovery Factor: 0.00",                 cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 4, "Total Asset: 0.00",                     cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 5, "Absolute Drawdown: $0.00 (0.00%)",      cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 5, "Relative Drawdown: $0.00 (0.00%)",      cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 6, "Total Trades: 0",                       cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 6, "Total Fee: $0.00 (0.00%)",              cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 7, "Total Losing Trades: 0",                cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 7, "Total Winning Trades: 0",               cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    table.cell(info, 0, 8, "Max Consecutive Wins: 0",               cellWidth, cellHeight, #BDBDBD, text.align_left)
    table.cell(info, 1, 8, "Max Consecutive Losses: 0",             cellWidth, cellHeight, #BDBDBD, text.align_left)
    
    configured := true

trigger = switch unit
    "days" => dayofmonth(time) % frequency == 0 and hour(time) == 0 and minute(time) == 0
    "hours" => hour(time) % frequency == 0 and minute(time) == 0
    "minutes" => minute(time) % frequency == 0

float holdingProfit = firstEntry > 0 ? (invested / firstEntry) * close : 0.0 // quote
float convertedProfit = coins * close
float change = math.abs(ta.change(convertedProfit))
float netProfit = grossProfit / grossLoss

if window
    float relativeDrawdown = 0.0
    if trigger
        float mult = 1.0
        totalTrades := totalTrades + 1
        if timeframe.isdaily and unit == "hours"
            mult := 24.0 / frequency
        else if timeframe.isdaily and unit == "minutes"
            mult := 1440 / frequency
        else if timeframe.isintraday and unit == "minutes"
            mult := timeframe.multiplier / frequency
        else if timeframe.isintraday and unit == "hours" and timeframe.multiplier > 60
            mult := (timeframe.multiplier / 60) / frequency
        invested := invested + (amount * mult)
        float tradeFee = (amount * mult) * fee / 100
        coins := coins + ((amount * mult) / close) - (tradeFee / close)
        totalFee := totalFee + tradeFee
        if firstEntry == 0.0
            firstEntry := close
        
    if convertedProfit > convertedProfit[1]
        grossProfit := grossProfit + change
        winningTrades := winningTrades + 1
    else if convertedProfit < convertedProfit[1]
        grossLoss := grossLoss + change
        losingTrades := losingTrades + 1
        
    if grossLoss - grossProfit > drawdown
        drawdown := grossLoss - grossProfit
    
    if convertedProfit > nz(convertedProfit[1])
        consecutiveWins := consecutiveWins + 1
        maxConsecutiveWins := maxConsecutiveWins < consecutiveWins ? consecutiveWins : maxConsecutiveWins
        consecutiveLosses := 0
        relativeDrawdown := 0
    else if convertedProfit < nz(convertedProfit[1])
        consecutiveLosses := consecutiveLosses + 1
        maxConsecutiveLosses := maxConsecutiveLosses < consecutiveLosses ? consecutiveLosses : maxConsecutiveLosses
        relativeDrawdown := relativeDrawdown + change
        maxRelativeDrawdown := maxRelativeDrawdown < relativeDrawdown ? relativeDrawdown : maxRelativeDrawdown
        consecutiveWins := 0
    else 
        consecutiveLosses := 0
        consecutiveWins := 0
        relativeDrawdown := 0
    table.cell_set_text(info, 0, 0, "Total Invested: $" + str.tostring(math.round(invested, 2)))
    table.cell_set_text(info, 1, 0, "Total Net Profit: $" + str.tostring(math.round(grossProfit - grossLoss, 2)) + " (" + str.tostring(math.round((grossProfit - grossLoss) * 100 / invested, 2)) + "%)")
    
    if firstEntry > 0
        table.cell_set_text(info, 0, 1, "Holding Profit: $" + str.tostring(math.round(holdingProfit, 2)) + " (" + str.tostring(math.round(holdingProfit * 100 / invested, 2)) + "%)")
        table.cell_set_text(info, 1, 1, "Entry Price: $" + str.tostring(math.round(firstEntry, 2)))
        
    table.cell_set_text(info, 0, 2, "Gross Profit: $" + str.tostring(math.round(grossProfit, 2)))
    table.cell_set_text(info, 1, 2, "Gross Loss: $" + str.tostring(math.round(grossLoss, 2)))    
    
    table.cell_set_text(info, 0, 3, "Profit Factor: " + str.tostring(math.round(netProfit, 2)))
    table.cell_set_text(info, 1, 3, "Profit/Trade: $" + str.tostring(math.round((grossProfit - grossLoss) / totalTrades, 2)))
    
    table.cell_set_text(info, 0, 4, "Recovery Factor: " + str.tostring(math.round(netProfit / maxRelativeDrawdown, 8)))
    table.cell_set_text(info, 1, 4, "Total Asset Bought: " + str.tostring(math.round(coins, 8)))
    
    table.cell_set_text(info, 0, 5, "Absolute Drawdown: $" + str.tostring(math.round(drawdown, 2)) + " (" + str.tostring(math.round(drawdown * 100 / invested, 3)) + "%)") 
    table.cell_set_text(info, 1, 5, "Relative Drawdown: $" + str.tostring(math.round(maxRelativeDrawdown, 2)) + " (" + str.tostring(math.round(maxRelativeDrawdown * 100 / invested, 3)) + "%)")
    
    table.cell_set_text(info, 0, 6, "Total Trades: " + str.tostring(totalTrades))
    table.cell_set_text(info, 1, 6, "Total Fee: $" + str.tostring(math.round(totalFee, 2)) + " (" + str.tostring(math.round(totalFee * 100 / invested, 3)) + "%)")
    
    table.cell_set_text(info, 0, 7, "Total Winning Trades: " + str.tostring(winningTrades))
    table.cell_set_text(info, 1, 7, "Total Losing Trades: " + str.tostring(losingTrades))
    
    table.cell_set_text(info, 0, 8, "Max Consecutive Wins: " + str.tostring(maxConsecutiveWins))
    table.cell_set_text(info, 1, 8, "Max Consecutive Losses: " + str.tostring(maxConsecutiveLosses))
    
plot(window and firstEntry > 0 ? holdingProfit : na, "Holding Profit", color.yellow)    
plot(window ? convertedProfit : na, "Converted Profit", color.fuchsia)    
plot(window ? invested : na, "Invested Amount", color.lime, 2)







