72s Strat: Backtesting Adaptive HMA+ pt.1
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// 2020 © io72signals / Antorio Bergasdito

//@version=4
strategy("72s Strategy: Adaptive Hull Moving Average+ pt.1", shorttitle="72s Strat: Adaptive HMA+ pt.1", overlay=true, 
     pyramiding=1, initial_capital=1000, commission_type=strategy.commission.cash_per_order, commission_value=.5, 
     slippage=3, max_bars_back=1000)

atrBase  = atr(21)
src      = close
adaptPct = 0.03141

//{ INPUTS
orderSize   = input(0.1, title="Order Size (in Lot)", step=.1, group="ORDERS")
minProfit   = input(1, step=.1, title="Base Minimum Profit before exit (ATR)", group="ORDERS") * atrBase
takeProfit  = input(3, step=.1, title="Default Target Profit (ATR)", group="ORDERS") * atrBase
useSL       = input(true, title="Use StopLoss", group="ORDERS")
SLInput     = input("Half Distance Zone", title="Base StopLoss Point", 
                 options=["One Distance Zone", "Half Distance Zone", "Last High/Low", "ATR Only"], group="ORDERS")
maxSL       = input(4.5, step=.1, title="Maximum StopLoss (ATR)", group="ORDERS") * atrBase

doubleUp    = input(false, "Activate 2nd order with xHMA+ exits", group="ORDERS")    //If enable, set pyramiding to at least: 2

minLength   = input(172, title="Minimum:", group="Adaptive HMA+ Period", inline="HMA+")
maxLength   = input(233, title="Maximum:", group="Adaptive HMA+ Period", inline="HMA+")

flat        = input(17, title="Flat Market when xHMA's slope below", group="Market Movement")
atrFast     = input(14, "Charged if ATR", group="Market Movement", inline="volality", minval=2, maxval=34)
atrSlow     = input(46, ">= ATR", group="Market Movement", inline="volality", minval=21, maxval=120)
showZone    = input(false, title="Show Adaptive HMA+ Distance Zone", group="DISTANCE ZONE")
mult        = input(2.7, title="Distance (Envelope) Multiplier", step=.1, group="DISTANCE ZONE") 

minorMin    = input(89, title="Minimum:", group="Minor Adaptive HMA+ Period", inline="mHMA+")
minorMax    = input(121, title="Maximum:", group="Minor Adaptive HMA+ Period", inline="mHMA+")

showMA      = input(true, title="Show MA?", group="OTHER")
useBg       = input(true, title="Background color to differentiate movement", group="OTHER")
rsi         = rsi(input(close,"RSI Source", inline="RSI", group="OTHER"), input(14, "RSI Length", inline="RSI", group="OTHER"))
//}...inputs


//{ ADAPTIVE HMA+
//Condition to adapt to:
charged = atr(atrFast) > atr(atrSlow)           //<--Volatility Meter. Change it to whatever match your strat/pair/tf.

//Dynamics 
var float dynamicLength = avg(minLength,maxLength)
var float minorLength   = avg(minorMin,minorMax)
dynamicLength   := iff(charged, max(minLength, dynamicLength * (1 - adaptPct)), min(maxLength, dynamicLength * (1 + adaptPct)))
minorLength     := iff(charged, max(minorMin, minorLength * (1 - adaptPct)), min(minorMax, minorLength * (1 + adaptPct)))

//Slope calculation to determine whether market is in trend, or in consolidation or choppy, or might about to change current trend
pi = atan(1) * 4
highestHigh = highest(34), lowestLow = lowest(34)
slope_range = 25 / (highestHigh - lowestLow) * lowestLow
calcslope(_ma)=>
    dt = (_ma[2] - _ma) / src * slope_range  
    c = sqrt(1 + dt * dt)
    xAngle = round(180 * acos(1 / c) / pi)
    maAngle = iff(dt > 0, -xAngle, xAngle)
    maAngle
    
//MA coloring+ to mark market dynamics 
dynColor(_ma,_col1a,_col1b,_col2a,_col2b,_col0) =>
    slope = calcslope(_ma)
    slope >= flat ? charged? _col1a : _col1b :
     slope < flat and slope > -flat ? _col0 : 
     slope <= -flat ? charged? _col2a : _col2b : _col0

//Adaptive HMA 
xhma(_src,_length) => _return = wma(2 * wma(_src, _length / 2) - wma(_src, _length), floor(sqrt(_length))) 

dynamicHMA = xhma(src,int(dynamicLength))       //<--Batman - Our main xHMA+
minorHMA = xhma(src,int(minorLength))           //<--Robin  - Faster minor xHMA+ (Optional). Can be use to assist for 
                                                //            faster entry, slower exit point, or pullbacks info too.

_slope = calcslope(dynamicHMA)
plotchar(_slope, "slope is:", "", location.top) //<--Output current slope to Data Window, for our reference when optimizing Strategy

//Envelope the main DynamicHMA with ATR band, just one way to approximate current price distance to MA. Other usages/methods may vary.
upperTL = dynamicHMA + mult * atr(40)        ,  lowerTL = dynamicHMA - mult * atr(40)           //<--Half distance zone
topTL = dynamicHMA + (mult*2) * atr(40)      ,  botTL = dynamicHMA - (mult*2) * atr(40)         //<--One distance zone
stopupperTL = dynamicHMA + (mult/2) * atr(40),  stoplowerTL = dynamicHMA - (mult/2) * atr(40)   //<--Half of the half. If need ie. tighter SL or trailing

//Plotting Distance Zone
plot(showZone?upperTL:na, color=color.green, transp=72)
plot(showZone?lowerTL:na, color=color.red,   transp=72)
plot(showZone?topTL:na, color=color.gray, transp=72)
plot(showZone?botTL:na, color=color.gray, transp=72)
sutl = plot(showZone?stopupperTL:na, color=color.white, transp=100)
sltl = plot(showZone?stoplowerTL:na, color=color.white, transp=100)
colZone = showZone? color.purple:color.new(color.white,100)
fill(sutl, sltl, color=colZone, transp=90)

plot(showMA?dynamicHMA:na, "Adaptive HMA+", dynColor(dynamicHMA, #6fbf73, #c0f5ae, #eb4d5c, #f2b1d4, color.yellow), 3)
plot(showMA?minorHMA:na,   "minor HMA+",    dynColor(minorHMA,   #6fbf73, #c0f5ae, #eb4d5c, #f2b1d4, color.yellow), 1)

//Backgroud color to differentiate market condition
notgreat = _slope < flat and _slope > -flat
bgcolor(useBg? charged? na : #afb4b9 : na)

//EMA 200. (Not being use in here. But since they've been a BFF since long, thought I'm just gonna put it here..)
emma200 = ema(close,200) //, plot(emma200)
//}...adaptive HMA+


//{ SIGNALS
//Base; When HMA+ turn to a darker color and market is out from low volatility
upSig = _slope >=  flat and charged
dnSig = _slope <= -flat and charged

//Default buy/sell, when market switchs to the above base condition, and typical price is still in tolerated distance, and RSI is on the right side.
buy  = upSig and not upSig[1] and close>dynamicHMA and low<=upperTL  and (rsi>51  and rsi<=70)
sell = dnSig and not dnSig[1] and close<dynamicHMA and high>=lowerTL and (rsi<49  and rsi>=30)

//Alternative buy/sell, when price is a bit far from MA, and RSI is still on the same side but already on oversold/overbought (relatively).
overBuy = upSig and not upSig[1] and rsi>70 and close>dynamicHMA and hl2>upperTL and minorHMA>dynamicHMA
overSell= dnSig and not dnSig[1] and rsi<30 and close<dynamicHMA and hl2<lowerTL and minorHMA<dynamicHMA

//Alternative additional buy/sell with different exits at Robin's.
secondBuy  = (buy  or overBuy)  and rsi[1]<rsi
secondSell = (sell or overSell) and rsi[1]>rsi

//Plot 'em up
atrPos = 0.2 * atr(5)
plotshape(buy?  dynamicHMA-atrPos:na,color=color.green, location=location.absolute, style=shape.labelup,   text="buy",  textcolor=color.white, size = size.small)
plotshape(sell? dynamicHMA+atrPos:na,  color=color.red, location=location.absolute, style=shape.labeldown, text="sell", textcolor=color.white, size = size.small)
plotshape(overBuy? dynamicHMA-atrPos:na,color=color.green, location=location.absolute, style=shape.labelup,   text="overBuy",  textcolor=color.white, size = size.small)
plotshape(overSell?dynamicHMA+atrPos:na,  color=color.red, location=location.absolute, style=shape.labeldown, text="overSell", textcolor=color.white, size = size.small)

plotshape(doubleUp and secondBuy? dynamicHMA-atrPos*8:na,color=color.green, location=location.absolute, style=shape.labelup,   text="+buy",  textcolor=color.white, size = size.small)
plotshape(doubleUp and secondSell?dynamicHMA+atrPos*8:na,  color=color.red, location=location.absolute, style=shape.labeldown, text="+sell", textcolor=color.white, size = size.small)

//Alert 
if buy
    alert("Buy signal at "+tostring(close), alert.freq_once_per_bar_close)
if sell
    alert("Sell signal at "+tostring(close), alert.freq_once_per_bar_close)
if overBuy
    alert("OverBuy signal at "+tostring(close), alert.freq_once_per_bar_close)
if overSell
    alert("OverSell signal at "+tostring(close), alert.freq_once_per_bar_close)
if doubleUp and secondBuy
    alert("2nd Buy signal at "+tostring(close), alert.freq_once_per_bar_close)
if doubleUp and secondSell
    alert("2nd Sell signal at "+tostring(close), alert.freq_once_per_bar_close)
//}...signals


//{ ORDERS
//(I use Lot just because it's how I usually trade, matches my flow when thinking/planning. Change the "qty" in "strategy.entry()" below to use other UOM.)
//Forex pairs are 100,000 units per 1 lot. Units per 1 lot vary on non-forex pairs, please check with your broker and change accordingly if necessary.
//(ie: In MT4 and MT5 right click a symbol and then click Specification. The Contract Size field tells how many units are in one lot.)
standard1LotUnits = 
     syminfo.type == "forex" ? 100000 : 
     syminfo.ticker == "XAUUSD" ? 100 : 
     syminfo.ticker == "XAGUSD" ? 5000 : 
     syminfo.type == "crypto" ? 1 : 1000

lotSize = standard1LotUnits * orderSize 
pip() => syminfo.mintick * (syminfo.type == "forex" ? 10 : 1)

//{ StopLoss
SLHighMax = high + maxSL, SLLowMax = low - maxSL        //<--Maximum StopLoss. If a chosen SL-point from input above happen
                                                        //   to be outside this max point, then this one will be use as SL.

lastHigh = highest(high, 48) + (5 * pip())              //<--Last High/Low. If use as SL-point, adjust the lookback length  
lastLow  = lowest(low, 48) - (5 * pip())                //   according to where your comfort lies (and position-size).

//Delivering SL from inputs
SLBuy = SLInput == "One Distance Zone"? botTL : SLInput == "Half Distance Zone"? lowerTL : 
     SLInput == "Last High/Low"? lastLow : SLInput == "ATR Only"? SLLowMax : SLLowMax
SLSell= SLInput == "One Distance Zone"? topTL : SLInput == "Half Distance Zone"? upperTL : 
     SLInput == "Last High/Low"? lastHigh: SLInput == "ATR Only"? SLHighMax: SLHighMax
//}---stopLoss

//
float entryPrice = na, float normOrder = 0, float riskOrder = 0
float buyTP = na, float sellTP = na, float buySL = na, float sellSL = na
entryPrice := nz(entryPrice[1]), normOrder := nz(normOrder[1]), riskOrder := nz(riskOrder[1])
buyTP:=nz(buyTP[1]), sellTP:=nz(sellTP[1]), buySL:=nz(buySL[1]), sellSL :=nz(sellSL[1])

//This part will also make your TP/SL change dynamically if there's a new signal while you have an open position.
//If you want to fire this TP/SL only in a new position, then add "strategy.position_size == 0" 
if (buy or sell or overBuy or overSell) //and strategy.position_size == 0
    entryPrice := close
    if buy or sell and strategy.position_size == 0
        normOrder := 1
    if overBuy or overSell and strategy.position_size == 0
        riskOrder := 1
    buyTP   := high + takeProfit 
    sellTP  := low  - takeProfit 
    if buy or overBuy
        buySL := SLLowMax<SLBuy? SLBuy:SLLowMax 
    if sell or overSell
        sellSL:= SLHighMax>SLSell? SLSell:SLHighMax 

//Trailing Minimum-Profit. --Just a simple helper to prevent premature exit rule. Or at least to have a
//change to get break-even (after commission) if market turns its back on us, before heading to stoploss area.
minBuyProfit  = strategy.position_avg_price  + minProfit 
minSellProfit = strategy.position_avg_price  - minProfit 

//{ Exit Points
//TP/SL Exits
stopLossBuy  = useSL? low <=buySL : na
stopLossSell = useSL? high>=sellSL: na
takeProfitBuy  = high>= buyTP
takeProfitSell = low <= sellTP 

//Simplified RSI Exits
RSIexitBuy  = crossunder(rsi,70) or rsi<50
RSIexitSell = crossover(rsi,30)  or rsi>50

//Exit Conditions
exitBuy  = ( close >= minBuyProfit  and (RSIexitBuy  or crossunder(close,dynamicHMA)) ) or stopLossBuy  or takeProfitBuy  
exitSell = ( close <= minSellProfit and (RSIexitSell or crossover(close,dynamicHMA) ) ) or stopLossSell or takeProfitSell
exitoverBuy  = ( close >= minBuyProfit  and (crossunder(rsi,45) or (open<stopupperTL and hl2<stopupperTL)) ) or (open<dynamicHMA and rsi<60)
     or stopLossBuy  or takeProfitBuy 
exitoverSell = ( close <= minSellProfit and (crossover(rsi,55)  or (open>stoplowerTL and hl2>stoplowerTL)) ) or (open>dynamicHMA and rsi>40)
     or stopLossSell or takeProfitSell 

//Alternative 2nd Exits at Robin's (or SL)
exitSecondBuy  = (close >= minBuyProfit and crossunder(close, minorHMA)) or stopLossBuy
exitSecondSell = (close <= minSellProfit and crossover(close, minorHMA)) or stopLossSell
    
//Plotting and Alert for Exits
inBuyExit  = strategy.position_size > 0 and ((normOrder == 1 and exitBuy)  or (riskOrder == 1 and exitoverBuy))
inSellExit = strategy.position_size < 0 and ((normOrder == 1 and exitSell) or (riskOrder == 1 and exitoverSell))
in2ndBuyExit = strategy.position_size > 0 and (doubleUp and exitSecondBuy)
in2ndSellExit=  strategy.position_size < 0 and (doubleUp and exitSecondSell)

if inBuyExit or inSellExit
    if normOrder == 1 and exitBuy 
        normOrder := 0, alert("Close Buy Position/s at "+tostring(close), alert.freq_once_per_bar_close)
    if normOrder == 1 and exitSell
        normOrder := 0, alert("Close Sell Position/s at "+tostring(close), alert.freq_once_per_bar_close)
    if riskOrder == 1 and exitoverBuy 
        riskOrder := 0, alert("Close overBuy Position/s at "+tostring(close), alert.freq_once_per_bar_close)
    if riskOrder == 1 and exitoverSell
        riskOrder := 0, alert("Close overSell Position/s at "+tostring(close), alert.freq_once_per_bar_close)

if in2ndBuyExit
    alert("Close 2nd Buy Position/s at "+tostring(close), alert.freq_once_per_bar_close)
if in2ndSellExit
    alert("Close 2nd Sell Position/s at "+tostring(close), alert.freq_once_per_bar_close)
    
plotchar(inBuyExit, color=color.green, location=location.abovebar, char="ⓧ", size=size.tiny)
plotchar(inSellExit,  color=color.red, location=location.belowbar, char="ⓧ", size=size.tiny)
plotchar(in2ndBuyExit, color=color.teal, location=location.abovebar, char="ⓧ", size=size.tiny)
plotchar(in2ndSellExit,  color=color.maroon, location=location.belowbar, char="ⓧ", size=size.tiny)
//}---exit points

//{ Actions
//ENTRIES
strategy.entry("buy",  strategy.long,  qty=lotSize, when=buy )
strategy.entry("sell", strategy.short, qty=lotSize, when=sell)
strategy.entry("overBuy",  strategy.long,  qty=lotSize, when=overBuy )
strategy.entry("overSell", strategy.short, qty=lotSize, when=overSell)

if doubleUp
    strategy.entry("2ndBuy",  strategy.long,  qty=lotSize, when=secondBuy )
    strategy.entry("2ndSell", strategy.short, qty=lotSize, when=secondSell) 

//EXITS
//We use just "strategy.close" because we already set limit conditions above for SL and TP, fires at candle close. If you want to
//separate exit data or exit at the exact SL/TP you can add such as: strategy.exit("exitbuy", from_entry="buy", stop=buySL, limit=buyTP)
//accordingly. And delete stopLossBuy, stopLossSell, takeProfitBuy, takeProfitSell definitions from above.
strategy.close("buy",  when=exitBuy , comment="close buy")
strategy.close("sell", when=exitSell, comment="close sell")
strategy.close("overBuy",  when=exitoverBuy , comment="close overBuy")
strategy.close("overSell", when=exitoverSell, comment="close overSell")

if doubleUp
    strategy.close("2ndBuy",  when=exitSecondBuy , comment="close 2nd Buy")
    strategy.close("2ndSell", when=exitSecondSell, comment="close 2nd Sell")
//}---actions

orderCol = strategy.position_size>0? color.green : strategy.position_size<0? color.red : na
// plot(strategy.position_size>0? minBuyProfit : strategy.position_size<0? minSellProfit : na, color=orderCol, style=plot.style_linebr, linewidth=1, title="Minimum Profit")
plot(strategy.position_size != 0? entryPrice:na, style=plot.style_cross, transp=50)
plot(useSL and strategy.position_size>0? buySL : useSL and strategy.position_size<0? sellSL : na, color=color.red, style=plot.style_cross, linewidth=1, title="Stop Loss", transp=50)
plot(strategy.position_size>0? buyTP : strategy.position_size<0? sellTP : na, color=color.green, style=plot.style_cross, linewidth=1, title="Take Profit", transp=50)
//}...orders


//{------------------------------------------------------------------------

//Below is just fancy stuff approximating gain in pips. You probably don't need this.
//(Also I'm not too sure this is the right way calculating pip gain in pine. If somebody knows how, kindly correct me please)
mUnit = lotSize*pip()
ppip = round((strategy.grossprofit-strategy.grossprofit[1]) / syminfo.pointvalue/mUnit,1)
lpip = round((strategy.grossloss-strategy.grossloss[1]) / syminfo.pointvalue/mUnit,1)
pipGain = round((strategy.grossprofit/syminfo.pointvalue)/mUnit)
pipLoss = round((strategy.grossloss/syminfo.pointvalue)/mUnit)
avg_ppip = round((pipGain/strategy.wintrades),1)
pctProfit = round(strategy.wintrades/strategy.closedtrades * 100, 2)
maxDD = ceil(strategy.max_drawdown/ syminfo.pointvalue/mUnit) //check if our margin can handle DD's pips*orderSize
var txt2 = ""
txt = "Total trades: "+ tostring(strategy.closedtrades)+"\n("+tostring(pctProfit)+"%) Win: "+tostring(strategy.wintrades)+" / Loss: "+tostring(strategy.losstrades)+
     "\nAverage win pips per trade: "+ tostring(avg_ppip) + " pips\n\nApproximate (*give n take)\nTotal Pip Gain: "+ tostring(pipGain) +" pips"+
     "\nTotal Pip Loss: "+tostring(pipLoss)+ " pips"+"\n\nMax Drawdown so far: "+tostring(maxDD)+" pips"
if strategy.wintrades>strategy.wintrades[1]
    txt2 := "\n\nLast closed trade: Bagged "+tostring(ppip)+" pips 👻"
if strategy.losstrades>strategy.losstrades[1]
    txt2 := "\n\nLast closed trade: Blew "+tostring(lpip)+" pips 😪"

txt += txt2
label pvtLabel   = label.new(time, lowestLow, text=txt, 
     color=color.new(color.white,100), xloc=xloc.bar_time, style=label.style_label_left, textcolor=color.gray, textalign=text.align_left)
label.set_x(pvtLabel, label.get_x(pvtLabel) + ((time-time[1]) * 21))
label.delete(pvtLabel[1])
//} 
