107 lignes
4,8 Kio
Python
# SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0 (see LICENSE)
| |||
"""Python twin of Cdl.mqh: 13 candlestick patterns on closed bars (the rules are in the README). This file is the exact
| |||
rule book; the MQL5 and MQL4 engines are checked against it bar by bar (tests/check_parity.py).
| |||
| |||
from reference import run, NAMES
| |||
rows = run(o, h, l, c) # rows[k] = dict(ev=[(pattern, dir), ...], mask=bit mask, ema=EMA(20) or None)
| |||
"""
| |||
NAMES = ["Bullish Engulfing", "Bearish Engulfing", "Hammer", "Hanging Man", "Inverted Hammer", "Shooting Star",
| |||
"Doji", "Morning Star", "Evening Star", "Bullish Harami", "Bearish Harami", "Piercing Line",
| |||
"Dark Cloud Cover"]
| |||
NP = len(NAMES)
| |||
ALL = (True,) * NP
| |||
# fixed direction per pattern (Doji: by the trend before it, 0 here)
| |||
DIR = [1, -1, 1, -1, 1, -1, 0, 1, -1, 1, -1, 1, -1]
| |||
EMA_N, AVG_N = 20, 10
| |||
| |||
| |||
def run(o, h, l, c, use=ALL):
| |||
n = len(c)
| |||
ema = [None] * n
| |||
s, cnt = 0.0, 0
| |||
for k in range(n): # EMA(20) of closes, SMA-seeded
| |||
cnt += 1
| |||
if cnt < EMA_N:
| |||
s += c[k]
| |||
elif cnt == EMA_N:
| |||
s += c[k]
| |||
ema[k] = s / EMA_N
| |||
else:
| |||
ema[k] = ema[k - 1] + 2.0 / (EMA_N + 1) * (c[k] - ema[k - 1])
| |||
body = [abs(c[k] - o[k]) for k in range(n)]
| |||
rng = [h[k] - l[k] for k in range(n)]
| |||
up = [h[k] - max(o[k], c[k]) for k in range(n)]
| |||
lo = [min(o[k], c[k]) - l[k] for k in range(n)]
| |||
bull = [c[k] > o[k] for k in range(n)]
| |||
bear = [c[k] < o[k] for k in range(n)]
| |||
| |||
def avg(arr, k): # SMA of the AVG_N bars before k
| |||
return sum(arr[k - AVG_N:k]) / AVG_N if k >= AVG_N else None
| |||
| |||
rows = []
| |||
for k in range(n):
| |||
ev = [] # (pattern, dir)
| |||
ab, ar = avg(body, k), avg(rng, k)
| |||
if ab is not None and k >= 3:
| |||
b, r, u, w = body[k], rng[k], up[k], lo[k]
| |||
tol = 1e-9 * abs(c[k]) # ties within 1e-9 x price count as equal (rounding noise decides nothing)
| |||
| |||
def GE(x, y):
| |||
return x >= y - tol
| |||
| |||
def LE(x, y):
| |||
return x <= y + tol
| |||
| |||
def GT(x, y):
| |||
return x > y + tol
| |||
| |||
def LT(x, y):
| |||
return x < y - tol
| |||
| |||
def tr(j): # +1 up / -1 down / 0 unknown, at bar j
| |||
if j < 0 or ema[j] is None:
| |||
return 0
| |||
return 1 if GT(c[j], ema[j]) else (-1 if LT(c[j], ema[j]) else 0)
| |||
| |||
t1, t2, t3 = tr(k - 1), tr(k - 2), tr(k - 3)
| |||
p = k - 1
| |||
# 0/1 engulfing
| |||
if bear[p] and bull[k] and LE(o[k], c[p]) and GE(c[k], o[p]) and GT(b, body[p]) and t2 < 0:
| |||
ev.append((0, 1))
| |||
if bull[p] and bear[k] and GE(o[k], c[p]) and LE(c[k], o[p]) and GT(b, body[p]) and t2 > 0:
| |||
ev.append((1, -1))
| |||
if GT(r, 0):
| |||
hammer = GE(w, 2 * b) and LE(u, 0.1 * r) and GE(w, 0.6 * r)
| |||
inv = GE(u, 2 * b) and LE(w, 0.1 * r) and GE(u, 0.6 * r)
| |||
if hammer and t1 < 0:
| |||
ev.append((2, 1))
| |||
if hammer and t1 > 0:
| |||
ev.append((3, -1))
| |||
if inv and t1 < 0:
| |||
ev.append((4, 1))
| |||
if inv and t1 > 0:
| |||
ev.append((5, -1))
| |||
if LE(b, 0.05 * r) and GE(r, 0.5 * ar) and t1 != 0:
| |||
ev.append((6, -t1))
| |||
# 7/8 stars
| |||
q = k - 2
| |||
if bear[q] and GE(body[q], ab) and LE(body[p], 0.3 * body[q]) and LE(min(o[p], c[p]), c[q]) and bull[k] \
| |||
and GT(c[k], (o[q] + c[q]) / 2) and t3 < 0:
| |||
ev.append((7, 1))
| |||
if bull[q] and GE(body[q], ab) and LE(body[p], 0.3 * body[q]) and GE(max(o[p], c[p]), c[q]) and bear[k] \
| |||
and LT(c[k], (o[q] + c[q]) / 2) and t3 > 0:
| |||
ev.append((8, -1))
| |||
# 9/10 harami
| |||
if bear[p] and GE(body[p], ab) and bull[k] and GE(o[k], c[p]) and LE(c[k], o[p]) and LT(b, body[p]) and t2 < 0:
| |||
ev.append((9, 1))
| |||
if bull[p] and GE(body[p], ab) and bear[k] and LE(o[k], c[p]) and GE(c[k], o[p]) and LT(b, body[p]) and t2 > 0:
| |||
ev.append((10, -1))
| |||
# 11/12 piercing, dark cloud
| |||
if bear[p] and GE(body[p], ab) and bull[k] and LE(o[k], c[p]) and GT(c[k], (o[p] + c[p]) / 2) and LT(c[k], o[p]) and t2 < 0:
| |||
ev.append((11, 1))
| |||
if bull[p] and GE(body[p], ab) and bear[k] and GE(o[k], c[p]) and LT(c[k], (o[p] + c[p]) / 2) and GT(c[k], o[p]) and t2 > 0:
| |||
ev.append((12, -1))
| |||
ev = [e for e in ev if use[e[0]]]
| |||
ev.sort()
| |||
rows.append(dict(ev=ev, mask=sum(1 << pt for pt, _ in ev), ema=ema[k]))
| |||
return rows
|