eStrategy
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © HeWhoMustNotBeNamed

//@version=5

// @description Library contains methods which can help build custom strategy for continuous investment plans and also compare it with systematic buy and hold.
library("eStrategy")

// @function Calculates max equity, current drawdown from equity ATH and max drawdown
// @param equity Present equity value
// @returns equityAth - Equity all time high
//          drawdown - Present drawdown percentage from equity ATH
//          maxDrawdown - Max drawdown percentage
export getEquityAthAndDrawdown(float equity)=>
    var equityAth = 0.0
    var maxDrawdown = 0.0
    equityAth := math.max(equityAth, equity)
    drawdown = (equityAth - equity)*100/equityAth
    maxDrawdown := math.max(maxDrawdown, nz(drawdown,0))
    [equityAth, drawdown, maxDrawdown]

// @function Calculates average cash holding percent for strategy
// @param cash Present cash holding
// @param equity Present equity value
// @returns currentCashPercent - Cash Percent of Equity
//          avgCashPercent - Average cash percent of equity
export getAverageCashPercent(float cash, float equity)=>
    var counter = 0
    var cashPercent = 0.0
    currentCashPercent = 0.0
    if(equity > 0)
        counter += 1
        currentCashPercent := 100*cash/equity
        cashPercent += currentCashPercent
    avgCashPercent = cashPercent/counter
    [currentCashPercent, avgCashPercent]

// @function Depicts systematic buy and hold over period of time
// @param startYear Year on which SIP is started
// @param initialDeposit Initial one time investment at the start
// @param depositFrequency Frequency of recurring deposit - can be monthly or weekly
// @param recurringDeposit Recurring deposit amount
// @param buyPrice Indicatinve buy price. Use high to be conservative. low, close, open, hl2, hlc3, ohlc4, hlcc4 are other options.
// @returns totalInvestment - initial + recurring deposits
//          totalQty - Quantity of units held for given instrument
//          totalEquity - Present equity
export sip(simple int startYear = 2010, simple float initialDeposit = 10000,
                 simple string depositFrequency ="monthly", simple float recurringDeposit=1000,
                 float buyPrice = high, float dividends = 0) =>
    var totalQty = 0.0
    var totalInvestment = 0.0
    var cash = 0.0
    term = depositFrequency == "monthly"? month : weekofyear
    var started = false
    changeOfTerm = ta.change(term)!=0
    recDep = changeOfTerm? recurringDeposit : 0
    if(year >= startYear)
        cash += dividends
        if(changeOfTerm)
            bnhAmount = (started ? recDep : initialDeposit) + cash
            cash := 0
            started := true
            bnhQty = bnhAmount/buyPrice
            totalInvestment += bnhAmount
            totalQty += bnhQty
    totalEquity = close*totalQty
    [totalInvestment, totalQty, totalEquity]


// @function Allows users to define custom strategy and enhance systematic buy and hold by adding take profit and reloads
// @param startYear Year on which Strategy is started
// @param initialDeposit Initial one time investment at the start
// @param depositFrequency Frequency of recurring deposit - can be monthly or weekly
// @param recurringDeposit Recurring deposit amount
// @param buyPrice Indicatinve buy price. Use high to be conservative. low, close, open, hl2, hlc3, ohlc4, hlcc4 are other options.
// @param sellPrice Indicatinve sell price. Use low to be conservative. high, close, open, hl2, hlc3, ohlc4, hlcc4 are other options.
// @param initialInvestmentPercent percent of amount to invest from the initial depost. Keep rest of them as cash 
// @param recurringInvestmentPercent percent of amount to invest from recurring deposit. Keep rest of them as cash
// @param signal can be 1, -1 or 0. 1 means buy/reload. -1 means take profit and 0 means neither. 
// @param tradePercent percent of amount to trade when signal is not 0. If taking profit, it will sell the percent from existing position. If reloading, it will buy with percent from cash reserve
// @returns totalInvestment - initial + recurring deposits
//          totalQty - Quantity of units held for given instrument
//          totalCash = Amount of cash held
//          totalEquity - Overall equity = totalQty*close + totalCash
export customStrategy(simple int startYear = 2010, simple float initialDeposit = 10000,
                         simple string depositFrequency ="monthly", simple float recurringDeposit=1000,
                         float buyPrice = high, float sellPrice = low,
                         simple float initialInvestmentPercent=50, simple float recurringInvestmentPercent=50, int signal=0, float tradePercent=50,
                         float dividends = 0) =>
    var totalCash = 0.0
    var totalQty = 0.0
    var totalInvestment = 0.0
    var cumulativePercentCash = 0.0
    term = depositFrequency == "monthly"? month : weekofyear
    var counter = 0
    changeOfTerm = ta.change(term)!=0
    recDep = changeOfTerm? recurringDeposit : 0
    if(year >= startYear)
        totalCash += dividends
        if(changeOfTerm)
            investAmount = (counter == 0 ? initialDeposit : recDep)
            buyAmount = signal == 0 ? (counter == 0 ? initialDeposit*initialInvestmentPercent/100 : recDep*recurringInvestmentPercent/100) : 0
            cashAmount = investAmount - buyAmount
            buyQty = buyAmount/buyPrice
            totalInvestment += investAmount
            totalQty += buyQty
            totalCash += cashAmount

        if(counter!=0 and signal != 0)
            if(signal == 1)
                buyAmount = totalCash*tradePercent/100
                buyQty = buyAmount/buyPrice
                totalQty += buyQty
                totalCash -= buyAmount
            if(signal == -1)
                sellQty = totalQty*tradePercent/100
                sellAmount = sellQty*sellPrice
                totalQty -= sellQty
                totalCash += sellAmount
        counter += 1
    totalEquity = totalCash + close*totalQty
    [totalInvestment, totalQty, totalCash, totalEquity]

    
// Coding example using sip
startYear = input.int(2010, maxval=2999, minval=0, title='Start Year', group='Inital Investment', tooltip='Year on which investment is started', confirm=true)
initialDeposit = input.float(10000, "Deposit", minval=0, step=5000, group="Inital Investment", tooltip='Initial one time deposit to start with', confirm=true)
recurringDepositFrequency = input.string("monthly", "Frequency", group="Recurring Investment",  options=["monthly", "weekly"], tooltip="Periodic investment frequency", confirm=true)
recurringDeposit = input.float(1000, "Deposit", step=1000, minval=0, group="Recurring Investment", tooltip="Periodic investment", confirm=true)
buyPrice = input.source(high, "Buy Price", group="Buy and Hold")


strategyInvestPercent = input.float(80, "Regular investiment Percent", group="Strategy", step=10)
dipPercent = input.float(5, "Dip Percent", group="Strategy", step=5)
dipQty = input.float(30, "Dip Qty Percent", group="Strategy", step=10)
dipQtyIncrements = input.float(10, "Dip Qty Multiplier", group="Strategy", step=5)
buyPriceStrategy = input.source(high, "Buy Price", group="Strategy")
sellPriceStrategy = input.source(low, "Sell Price", group="Strategy")

dividends = nz(request.dividends(syminfo.tickerid, dividends.gross, barmerge.gaps_on, ignore_invalid_symbol=true))

[totalInvestment, buyAndHoldQty, bnhEquity] = sip(startYear, initialDeposit, recurringDepositFrequency, recurringDeposit, buyPrice, dividends)

[bEquityAth, bDrawdown, bMaxDrawdown] = getEquityAthAndDrawdown(bnhEquity)
var ath = high
ath := math.max(ath, high)
distanceFromAth = (ath-close)*100/ath

distanceNumber = distanceFromAth < dipPercent? 0 : 
                     distanceFromAth < dipPercent*2? 1 : 
                     distanceFromAth < dipPercent*3? 2 : 
                     distanceFromAth < dipPercent*4? 3 : 
                     distanceFromAth < dipPercent*5? 4 : 
                     distanceFromAth < dipPercent*6? 5 : 6

signal = ta.change(distanceNumber) > 0? 1: 0
qtyPercent = signal == 6? 100 : signal == 1? dipQty+dipQtyIncrements*(signal-1) : 0
[_totalInvestment, totalQty, totalCash, totalEquity] = customStrategy(startYear, initialDeposit, recurringDepositFrequency, recurringDeposit, buyPriceStrategy, sellPriceStrategy,
                         strategyInvestPercent, strategyInvestPercent, signal, qtyPercent, dividends)
[sEquityAth, sDrawdown, sMaxDrawdown] = getEquityAthAndDrawdown(totalEquity)
[currentCashPercent, avgCashPercent] = getAverageCashPercent(totalCash, totalEquity)

plot(totalEquity, "Strategy", color=color.new(color.yellow, 80), style=plot.style_area)
plot(bnhEquity, "Buy and Hold", color=color.new(color.green, 80), style=plot.style_area)
plot(totalInvestment, "Investment", color=color.new(color.purple, 80), style=plot.style_area)

var stats = table.new(position=position.top_right, columns=3, rows=7, border_width=1)
table.cell(stats, 1, 0, "Buy and Hold", bgcolor=color.teal, text_color=color.white)
table.cell(stats, 2, 0, "Strategy", bgcolor=color.teal, text_color=color.white)

table.cell(stats, 0, 1, "Equity", bgcolor=color.maroon, text_color=color.white)
table.cell(stats, 0, 2, "Max Equity", bgcolor=color.maroon, text_color=color.white)
table.cell(stats, 0, 3, "Drawdown", bgcolor=color.maroon, text_color=color.white)
table.cell(stats, 0, 4, "Max Drawdown", bgcolor=color.maroon, text_color=color.white)
table.cell(stats, 0, 5, "ROI", bgcolor=color.maroon, text_color=color.white)
table.cell(stats, 0, 6, "Average Cash Percent", bgcolor=color.maroon, text_color=color.white)

roiBnh = bnhEquity*100/totalInvestment
table.cell(stats, 1, 1, str.tostring(bnhEquity), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 1, 2, str.tostring(bEquityAth), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 1, 3, str.tostring(bDrawdown), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 1, 4, str.tostring(bMaxDrawdown), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 1, 5, str.tostring(roiBnh), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 1, 6, str.tostring(0), bgcolor=color.aqua, text_color=color.black)
roiStrategy = totalEquity*100/totalInvestment
table.cell(stats, 2, 1, str.tostring(totalEquity), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 2, 2, str.tostring(sEquityAth), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 2, 3, str.tostring(sDrawdown), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 2, 4, str.tostring(sMaxDrawdown), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 2, 5, str.tostring(roiStrategy), bgcolor=color.aqua, text_color=color.black)
table.cell(stats, 2, 6, str.tostring(avgCashPercent), bgcolor=color.aqua, text_color=color.black)

