Chart Pattern
Chart Pattern
// This source code is subject to the terms of the Mozilla Public License 2.0 at
https://mozilla.org/MPL/2.0/
// © reees
//@version=5
import reees/TA/85 as t
import reees/Draw/27 as draw
import reees/Utilities/5 as u
import reees/Pattern/1 as p
import reees/Obj_XABCD_Harmonic/10 as h
//import reees/Algebra/3 as alg
//-----------------------------------------
// inputs and vars
//-----------------------------------------
// Alert Inputs
//var a_on = input.bool(true, "Alert", inline="alert", group="Alerts")
var a_type = input.string("Both", "Alert for", options=["Potential
patterns","Complete patterns","Both"], inline="alert", group="Alerts")
// Display Inputs
var c_bline = input.color(color.new(color.green,20), "Bullish lines",
group="Display")
var c_beline = input.color(color.new(color.red,20), "Bearish lines",
group="Display")
var c_blab = input.color(color.new(color.green,75), "Bullish labels",
group="Display")
var c_belab = input.color(color.new(color.red,75), "Bearish labels",
group="Display")
var l_txt = input.color(color.new(color.white,20), "Label text", group="Display")
// Stat totals
var int[] pTot = array.new_int(0)
var int[] tTot = array.new_int(0)
var float[] t1Tot = array.new_float(0)
var float[] t2Tot = array.new_float(0)
var float[] arTot = array.new_float(0)
var float[] trTot = array.new_float(0)
//-----------------------------------------
// functions
//-----------------------------------------
nToArray(n) =>
switch n
"a1" => bullGart
"a2" => bullBat
"a3" => bullBfly
"a4" => bullCrab
"a5" => bullShark
"a6" => bullCyph
"b1" => bearGart
"b2" => bearBat
"b3" => bearBfly
"b4" => bearCrab
"b5" => bearShark
"b6" => bearCyph
typeToArray(t,tp) =>
n = (t ? "a" : "b") + str.tostring(tp)
nToArray(n)
t1(tp) =>
switch tp
6 => cyph_t1
5 => shark_t1
4 => crab_t1
3 => bfly_t1
2 => bat_t1
=> gart_t1
t2(tp) =>
switch tp
6 => cyph_t2
5 => shark_t2
4 => crab_t2
3 => bfly_t2
2 => bat_t2
=> gart_t2
// get target
targets(tp) =>
[t1(tp),t2(tp)]
harmonic_xabcd_targets(tp,xY,aY,bY,cY,dY) =>
tgt1 = t1(tp)
tgt2 = t2(tp)
[t1,t2,_] = t.harmonic_xabcd_targets(xY,aY,bY,cY,dY,tgt1,tgt2)
[t1,t2]
// Timeout period
tLimit(xX,dX) =>
int((dX - xX)*tLimitMult)
incTLimit(xX,cX) =>
avg = (cX-xX)/3
int(avg * (1 + pctAsym/100)) // time out after max possible bars based on
asymmetry parameter
// Entry has timed out
eTimeout(xX,dX) =>
bar_index - dX > int((dX - xX)*e_tLimit)
entry(p) =>
if noEntry(p)
[na,na,na]
else
t.harmonic_xabcd_entry(p.bull,p.tp,p.x.y,p.a.y,p.b.y,p.c.y,p.d.y,e_afterC,e_lvlc,e_
afterD,e_lvldPct)
alertMsg(p) =>
if na(p.d.x)
"Potential " + h.get_name(p) + " is forming."
else
h.get_name(p) + " has formed."
deleteFip() =>
for lbl in fullIpL
label.delete(lbl)
array.clear(fullIpL)
//
for ln in fullIpLn
line.delete(ln)
array.clear(fullIpLn)
//
for lf in fullIpLf
linefill.delete(lf)
array.clear(fullIpLf)
removePending(pid) =>
if array.size(pending) > 0
for i=0 to array.size(pending)-1
p = array.get(pending,i)
if p.pid == pid
if i == array.size(pending)-1
deleteFip()
array.remove(pending,i)
break
successTxt(p) =>
if p.t2Hit
" (Success - Target 1, Target 2)"
else if p.t1Hit
" (Success - Target 1)"
else if p.sHit
" (Failed)"
else if noEntry(p) and stillActive(p.x.x,p.d.x)
" (No entry)"
else if (p.eHit==false or na(p.eHit)) and stillActive(p.x.x,p.d.x) == false
" (Missed entry)"
else if na(p.eHit) and stillActive(p.x.x,p.d.x)
" (Entry pending)"
else if na(p.t1Hit) and stillActive(p.x.x,p.d.x)
" (Targets pending)"
else
" (Timed out)"
ratToStr(r) =>
na(r) ? "NA " : str.tostring(r, "0.000")
reToStr(r) =>
na(r) ? "NA " : str.tostring(r, "0.0") + "%"
// Pattern tooltip
ttTxt(p) =>
[rb,rc,rd1,rd2] = t.harmonic_xabcd_fibDispTxt(p.tp)
[_,_,e] = entry(p)
l1 = h.get_name(p) + successTxt(p) + "\n\n"
l2 = (p.invalid_d?"Incomplete":"Total") + " Score: " +
str.tostring(p.score*100,"#.###") + "\n"
l3 = " Leg retracement accuracy: " + str.tostring((1-p.score_eAvg)*100,
"#.##") + "%\n"
l42 = " PRZ level confluence: " + (p.tp==6 ? "NA" :
(str.tostring(p.score_prz*100, "#.##") + "%")) + "\n"
l43 = " Point D confluence with PRZ: " + (p.invalid_d?"NA (D
unconfirmed)":str.tostring((1-p.score_eD)*100, "#.##") + "%") +"\n"
l5 = "\n Actual % Err Theoretical\n"
l6 = "AB/XA " + str.tostring(p.r_xb, "0.000") + " " + (p.tp==5 ? "NA
" : (str.tostring(p.re_xb*100, "00.0")+"%")) + " " + rb + "\n"
l7 = "BC/AB " + str.tostring(p.r_ac, "0.000") + " " +
str.tostring(p.re_ac*100, "00.0") + "% " + rc + "\n"
l8 = "CD/BC " + ratToStr(p.r_bd) + " " + (p.tp==6 ? "NA " :
reToStr(p.re_bd*100)) + " " + rd1 + "\n"
l9 = (p.tp==6 ? "CD/XC " : "AD/XA ") + ratToStr(p.r_xd) + " " +
reToStr(p.re_xd*100) + " " + rd2 + "\n"
l91 = "\nTarget 1: " + str.tostring(p.t1,"#.#####") + "\nTarget 2: " +
str.tostring(p.t2,"#.#####")
l92 = "\nEntry: " + str.tostring(na(p.e.y)?e:p.e.y,"#.#####")
l93 = na(p.stop) ? "" : "\nStop: " + str.tostring(p.stop,"#.#####")
l1 + l2 + l3 + l42 + l43 + l5 + l6 + l7 + l8 + l9 + l92 + l93 + l91
status(p) =>
if p.t2Hit
" ✅✅"
else if p.t1Hit
" ✅"
else if p.sHit
" ❌"
else if stillActive(p.x.x,p.d.x) == false and (p.eHit == false or na(p.eHit))
" ⛔"
else if stillActive(p.x.x,p.d.x) and noEntry(p)
" ⛔"
else if stillActive(p.x.x,p.d.x)
" ⏳"
else
" 🕝"
lbTxt(p,status) =>
t.harmonic_xabcd_symbol(p.tp) + " " + str.tostring(math.round(p.score,3)*100) +
status
incLbTxt(p) =>
"Potential " + t.harmonic_xabcd_symbol(p.tp) + " (" +
str.tostring(p.score*100,"#.##") + ")"
erasePattern(p) =>
h.erase_pattern(p)
h.erase_label(p)
drawPattern(p) =>
if incOn or not na(p.d.x)
status = status(p)
lbTxt = not na(p.d.x) ? lbTxt(p,status) : incLbTxt(p)
[_,e,_] = entry(p)
tt = not na(p.d.x) ? ttTxt(p) :
draw.incTtTxt(p.tp,h.get_name(p),p.r_xb,p.re_xb,p.r_ac,p.re_ac,p.prz_bN,p.prz_bF,p.
prz_xN,p.prz_xF,p.score,e)
h.draw_pattern(p, p.bull?c_bline:c_beline)
if p.invalid_d
line.set_style(array.get(p.pLines,3),line.style_dashed)
h.draw_label(p, p.bull?c_blab:c_belab, l_txt, lbTxt, tt)
notLast(p) => // check if not same pattern as last completed pattern of same type
comp = typeToArray(p.bull,p.tp)
h.xabcd_harmonic last = array.size(comp) == 0 ? na :
array.get(comp,array.size(comp)-1)
na(last) ? true : last.x.x!=p.x.x or last.a.x!=p.a.x or last.b.x!=p.b.x
addIncompletePattern(t,h1,h2,h3,h4,h5,h6,xX,xY,aX,aY,bX,bY,cX,cY) =>
btps = array.from(h1,h2,h3,h4,h5,h6)
tps = u.boolToIntArr(btps)
lowest = lowest(bar_index-cX)
highest = highest(bar_index-cX)
int dX = na
float dY = na
// check if pattern already exists (incomplete or pending)
exists = false
iN = array.size(inc)
iP = array.size(pending)
if iN > 0
for i=0 to iN-1
p = array.get(inc,iN-1-i) // more likely to find it at top of
stack
if p.x.x==xX and p.a.x==aX and p.b.x==bX
if p.c.x == cX
exists := true
else
// if new point C, delete the old inc pattern in favor of this
one
array.remove(inc,iN-1-i)
erasePattern(p)
break
if exists==false and iP > 0
for i=0 to iP-1
p = array.get(pending,iP-1-i)
if p.x.x==xX and p.a.x==aX and p.b.x==bX and p.c.x==cX
exists := true
break
if exists == false
// add separate incomplete pattern for each potential harmonic type
for tpe in tps
tp = tpe+1
if exists == false
pat = h.init(xX,xY,aX,aY,bX,bY,cX,cY,dX,dY,params,tp)
if not na(pat)
[_,eC,_] = entry(pat)
if na(eC) or (not na(eC) and ((t and eC < lowest) or (t==false
and eC > highest)))
if notLast(pat)
array.push(inc,pat)
if incOn and (not na(eC) or not e_afterC)
drawPattern(pat)
if a_type == "Potential patterns" or a_type == "Both"
setTargets(p) =>
[t1,t2] = targets(p.tp)
h.set_target(p,1,calc_target=t1)
h.set_target(p,2,calc_target=t2)
incPid(p) =>
str.tostring(p.tp) + "_"
+ str.tostring(p.x.x) + "_"
+ str.tostring(p.a.x) + "_"
+ str.tostring(p.b.x) + "_"
+ str.tostring(p.c.x) + "_"
+ str.tostring(na)
h.init(last.x.x,last.x.y,last.a.x,last.a.y,last.b.x,last.b.y,last.c.x,last.c.y,p.d.
x,p.d.y,params,last.tp,last)
[upper,lower] =
t.harmonic_xabcd_przRange(last.prz_bN,last.prz_bF,last.prz_xN,last.prz_xF)
last.stop :=
t.harmonic_xabcd_stop(stopB,stopPct,last.bull,last.x.y,last.d.y,upper,lower,last.t1
,last.e.y)
drawPattern(last) // redraw with new D
setTargets(last) // reset targets for new D
pat := last
else // ELSE, replace last pattern
with the new pattern
removePending(last.pid)
erasePattern(array.pop(a))
addCompleted(p,force)
pat := p
else
addCompleted(p,force)
pat := p
updatePendingPatterns() =>
h.xabcd_harmonic[] new = array.new<h.xabcd_harmonic>()
if array.size(pending) > 0
for i=0 to array.size(pending)-1
ip = array.get(pending,i)
[eH,eHx,eHy] = entryHit(ip)
tLimit = tLimit(ip.x.x,ip.d.x)
expired = bar_index == (ip.d.x + tLimit + 1) or eH==false
[t1h,t2h,sH,t1x,t1y,t2x,t2y] = success(ip)
// if time has expired or there's nothing left to update, no longer
pending
if not (expired or (eH and (not na(t2h) or t1h == false or sH)))
array.push(new,ip)
// if anything to update, update completed array entry and label if
necessary
if expired
or (eH
and ((not na(t1h) and na(ip.t1Hit)) or (not na(t2h) and
na(ip.t2Hit))))
or (eH != ip.eHit) or (eH and na(ip.eHit)) or (eH==false and
na(ip.eHit))
or sH
if (eH or eH==false) and na(ip.eHit)
ip.eHit := eH
ip.e.x := eHx
ip.e.y := eHy
draw.eHitLbl(eHx,eHy,ip.d.x,ip.d.y,ip.bull)
// targets will be na if eH==false, so no need to also check eH
if t1h and na(ip.t1Hit)
ip.t1Hit := true
draw.tHitLbl(t1x,t1y,eHx,eHy,ip.bull)
else if t1h == false
ip.t1Hit := false
draw.sHitLbl(t1x,t1y,eHx,eHy,ip.bull) // only draw stop X if
no target was already hit
if sH
ip.sHit := true
//draw.sHitLbl(bar_index,ip.stop,eHx,eHy,ip.bull)
if t2h
ip.t2Hit := true
draw.tHitLbl(t2x,t2y,eHx,eHy,ip.bull)
h.draw_label(ip, ip.bull?c_blab:c_belab, l_txt,
lbTxt(ip,status(ip)), ttTxt(ip))
new
updateIncompletePatterns() =>
h.xabcd_harmonic[] new = array.new<h.xabcd_harmonic>()
if array.size(inc) > 0
for p in inc
[upper,lower] =
t.harmonic_xabcd_przRange(p.prz_bN,p.prz_bF,p.prz_xN,p.prz_xF)
tLimit = incTLimit(p.x.x,p.c.x)
[eH,eHx,eHy] = entryHit(p)
if eH and e_afterC
if notLast(p)
p.d.x := eHx
p.d.y := eHy
p.invalid_d := true
erasePattern(p)
addValidPattern(p,true)
else
erasePattern(p)
// Don't keep incomplete pattern if it's timed out or has been
invalidated
else if bar_index == (p.c.x + tLimit + 1)
or (p.bull and (high > p.c.y or low < lower))
or (p.bull==false and (low < p.c.y or high > upper))
erasePattern(p)
else
array.push(new,p)
new
drawFullInProgress() =>
if array.size(pending) > 0
last = array.get(pending,array.size(pending)-1)
if not na(last.d.x)
bb = last_bar_index - last.d.x
tLimit = tLimit(last.x.x,last.d.x)
// Only
draw if...
if bb <= tLimit //
within pattern time limit and
and noEntry(last)==false //
entry is possible and
and (eTimeout(last.x.x,last.d.x)==false or last.eHit) //
entry has not timed out and
//and (na(last.t1Hit) or (last.t1Hit and na(last.t2Hit)))
and not last.t2Hit //
targets remain to be hit and
and not last.sHit // stop
has not been hit
deleteFip() // delete previously drawn completed
pattern in progress
[highest,lowest] =
t.harmonic_xabcd_przRange(last.prz_bN,last.prz_bF,last.prz_xN,last.prz_xF)
[bcNt,_] =
harmonic_xabcd_targets(last.tp,last.x.y,last.a.y,last.b.y,last.c.y,last.prz_bN)
[bcFt,_] =
harmonic_xabcd_targets(last.tp,last.x.y,last.a.y,last.b.y,last.c.y,last.prz_bF)
[xaNt,_] =
harmonic_xabcd_targets(last.tp,last.x.y,last.a.y,last.b.y,last.c.y,last.prz_xN)
[xaFt,_] =
harmonic_xabcd_targets(last.tp,last.x.y,last.a.y,last.b.y,last.c.y,last.prz_xF)
stop = last.stop
[e,_,_] = entry(last)
entry = na(last.e.y) ? e : last.e.y
[ln,lb,lf] = draw.xabcd_inProgress(last.bull,last.tp,tLimit>500?
500:tLimit,entry,stop,last.t1,last.t2,bcNt,bcFt,xaNt,xaFt,
last.x.x,last.x.y,last.a.y,last.b.x,last.b.y,last.c.y,last.d.x,last.d.y,c_bline,c_b
eline,l_txt)
for l in ln
array.push(fullIpLn,l)
for l in lb
array.push(fullIpL,l)
for l in lf
array.push(fullIpLf,l)
else
deleteFip()
else
deleteFip()
rowValues(tp) =>
a1 = typeToArray(true,tp)
a2 = typeToArray(false,tp)
float[] ra = array.new_float(0)
t1_tot = 0
t2_tot = 0
closed = 0
if array.size(a1) > 0
for p in a1
if (not na(p.t1Hit) or p.sHit) and p.eHit
if p.t2Hit
r = (p.t2/p.e.y) - 1
array.push(ra,r)
t2_tot+=1
t1_tot+=1
closed+=1
else if p.t1Hit
r = (p.t1/p.e.y) - 1
array.push(ra,r)
t1_tot+=1
closed+=1
else if p.sHit
r = (p.stop/p.e.y) - 1
array.push(ra,r)
closed+=1
//for p in a2
if array.size(a2) > 0
for p in a2
if (not na(p.t1Hit) or p.sHit) and p.eHit
if p.t2Hit
r = (p.e.y/p.t2) - 1
array.push(ra,r)
t2_tot+=1
t1_tot+=1
closed+=1
else if p.t1Hit
r = (p.e.y/p.t1) - 1
array.push(ra,r)
t1_tot+=1
closed+=1
else if p.sHit
r = (p.e.y/p.stop) - 1
array.push(ra,r)
closed+=1
tot = array.size(a1) + array.size(a2)
array.push(pTot,tot)
array.push(tTot,closed)
if closed > 0
array.push(t1Tot,(t1_tot/closed)*100)
array.push(t2Tot,(t2_tot/closed)*100)
if not na(array.avg(ra))
array.push(arTot,array.avg(ra)*100)
if not na(array.sum(ra))
array.push(trTot,array.sum(ra)*100)
st1 = closed>0 ? str.tostring((t1_tot/closed)*100,"#.##")+"%" : "NA"
st2 = closed>0 ? str.tostring((t2_tot/closed)*100,"#.##")+"%" : "NA"
ravg = not na(array.avg(ra)) ? str.tostring(array.avg(ra)*100,"#.##")+"%" :
"NA"
rtot = not na(array.sum(ra)) ? str.tostring(array.sum(ra)*100,"#.##")+"%" :
"NA"
[str.tostring(tot),str.tostring(closed),st1,st2,ravg,rtot]
printStats() =>
if barstate.islast
nR = array.size(includeTps) + 3
r = 0
t = table.new(position.bottom_left, 7, nR, bgcolor =
color.new(color.black,30), border_width = 1)
table.cell(t, 0, 0, " ", text_color=color.white,
text_halign=text.align_center)
table.cell(t, 1, 0, "Patterns", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 2, 0, "Trades", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 3, 0, "T1 Success", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 4, 0, "T2 Success", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 5, 0, "Avg Return %", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 6, 0, "Total Return %", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
for tp in includeTps
r+=1
[tot,trd,st1,st2,ar,tr] = rowValues(tp)
table.cell(t, 0, r, " "+t.harmonic_xabcd_symbol(tp)+" ",
text_color=color.white, text_halign=text.align_center,text_size=size.small)
table.cell(t, 1, r, tot, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 2, r, trd, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 3, r, st1, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 4, r, st2, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 5, r, ar, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 6, r, tr, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
r+=1
t1Total = not na(array.avg(t1Tot)) ? (str.tostring(array.avg(t1Tot),"#.##")
+ "%") : "NA"
t2Total = not na(array.avg(t2Tot)) ? (str.tostring(array.avg(t2Tot),"#.##")
+ "%") : "NA"
arTotal = not na(array.avg(arTot)) ? (str.tostring(array.avg(arTot),"#.##")
+ "%") : "NA"
trTotal = not na(array.sum(trTot)) ? (str.tostring(array.sum(trTot),"#.##")
+ "%") : "NA"
table.cell(t, 0, r, "Total", text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 1, r, str.tostring(array.sum(pTot)), text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 2, r, str.tostring(array.sum(tTot)), text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 3, r, t1Total, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 4, r, t2Total, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 5, r, arTotal, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
table.cell(t, 6, r, trTotal, text_color=color.white,
text_halign=text.align_center,text_size=size.small)
r+=1
keyTxt = "✅ = Success | ❌ = Failure | 🕝 = Timed out* | ⛔ = No entry* | ⏳ =
In progress* \n*Not included in Success Rate/Return % statistics"
table.cell(t, 0, r, keyTxt, text_color=color.white,
text_halign=text.align_right, text_size=size.small)
table.merge_cells(t,0,r,6,r)
array.clear(pTot),array.clear(tTot),array.clear(t1Tot),array.clear(t2Tot),array.cle
ar(arTot),array.clear(trTot)
validD(p,dX,dY) =>
// If CD has valid length symmetry...
if dX <= (incTLimit(p.x.x,p.c.x) + p.c.x)
if t.pat_xabcd_testSym(p.a.x-p.x.x, p.b.x-p.a.x, p.c.x-p.b.x, p.d.x-p.c.x,
pctAsym)
highest = highest(bar_index-p.c.x,1)
lowest = lowest(bar_index-p.c.x,1)
// If no intermediate high/low invalidates the CD leg...
if ((p.bull and dY <= lowest) or (p.bull==false and dY >= highest))
and ((p.bull and p.c.y >= highest) or (p.bull==false and p.c.y <=
lowest))
// validate CD retracement for this pattern type
xa = math.abs(p.x.y - p.a.y)
bc = math.abs(p.b.y - p.c.y)
cd = math.abs(p.c.y - dY)
ad = math.abs(p.a.y - dY)
xc = math.abs(p.x.y - p.c.y)
tp = p.tp
p_types = switch tp
1 => array.from(true,false,false,false,false,false)
2 => array.from(false,true,false,false,false,false)
3 => array.from(false,false,true,false,false,false)
4 => array.from(false,false,false,true,false,false)
5 => array.from(false,false,false,false,true,false)
6 => array.from(false,false,false,false,false,true)
[t1,t2,t3,t4,t5,t6] = t.test_cd(cd,bc,xa,xc,ad,pctErr,p_types)
t1 or t2 or t3 or t4 or t5 or t6
else
false
else
false
else
false
// Check for a valid pivot point D based on the pattern confirmation length
parameter (i.e. checking bar_index[t_b])
checkForValidD() =>
x = bar_index[t_b] // bar of interest
bool isLow = true
bool isHigh = true
l = low
h = high
// Validate pivot
for i=0 to t_b-1 // check bars after potential pivot point
if l[i] < l[t_b]
isLow := false
break
if isLow // check bars before potential pivot point
for i=t_b+1 to t_b+3 // need at least 3 bars before
if l[i] < l[t_b]
isLow := false
break
for n in new
if not na(n)
addValidPattern(n)
//-----------------------------------------
// Main
//-----------------------------------------
checkForValidD()