Harmonic Pattern Engine
import pandas as pd, numpy as np
from scipy.signal import argrelextrema
HARMONIC_RATIOS = {
"gartley": {"XB": (0.618, 0.618), "AC": (0.382, 0.886), "BD": (1.272, 1.618), "XD": (0.786, 0.786)},
"butterfly": {"XB": (0.786, 0.786), "AC": (0.382, 0.886), "BD": (1.618, 2.618), "XD": (1.272, 1.618)},
"bat": {"XB": (0.382, 0.500), "AC": (0.382, 0.886), "BD": (1.618, 2.618), "XD": (0.886, 0.886)},
"crab": {"XB": (0.382, 0.618), "AC": (0.382, 0.886), "BD": (2.240, 3.618), "XD": (1.618, 1.618)},
"cypher": {"XB": (0.382, 0.618), "AC": (1.130, 1.414), "BD": (1.272, 2.000), "XD": (0.786, 0.786)},
}
class HarmonicEngine:
@staticmethod
def detect_xabcd(df: pd.DataFrame, tolerance: float = 0.05) -> list[dict]:
"""Detect XABCD harmonic patterns from swing points."""
highs = argrelextrema(df["high"].values, np.greater, order=5)[0]
lows = argrelextrema(df["low"].values, np.less, order=5)[0]
swings = []
for i in highs: swings.append({"idx": i, "price": df["high"].iloc[i], "type": "H"})
for i in lows: swings.append({"idx": i, "price": df["low"].iloc[i], "type": "L"})
swings.sort(key=lambda s: s["idx"])
patterns = []
for i in range(len(swings) - 4):
X, A, B, C, D = [swings[j]["price"] for j in range(i, i + 5)]
XA = abs(A - X)
if XA == 0: continue
AB = abs(B - A)
BC = abs(C - B)
CD = abs(D - C)
XB_ratio = AB / XA
AC_ratio = BC / AB if AB > 0 else 0
BD_ratio = CD / BC if BC > 0 else 0
XD_ratio = abs(D - X) / XA
for name, ratios in HARMONIC_RATIOS.items():
xb_min, xb_max = ratios["XB"][0] - tolerance, ratios["XB"][1] + tolerance
xd_min, xd_max = ratios["XD"][0] - tolerance, ratios["XD"][1] + tolerance
if xb_min <= XB_ratio <= xb_max and xd_min <= XD_ratio <= xd_max:
bullish = D < X if swings[i]["type"] == "L" else D > X
patterns.append({
"pattern": name,
"bullish": bullish,
"X": round(X, 5), "A": round(A, 5), "B": round(B, 5), "C": round(C, 5), "D": round(D, 5),
"XB": round(XB_ratio, 3), "XD": round(XD_ratio, 3),
"prz": round(D, 5),
"signal": f"{'BUY' if bullish else 'SELL'} at PRZ {round(D, 5)}",
"stop": round(X, 5),
"tp1": round(D + (A - D) * 0.382, 5) if bullish else round(D - (D - A) * 0.382, 5),
"tp2": round(D + (A - D) * 0.618, 5) if bullish else round(D - (D - A) * 0.618, 5),
})
return patterns
1---2name: harmonic-pattern-engine3description: Harmonic pattern detection — Gartley, Butterfly, Bat, Crab, Cypher, Shark with Fibonacci ratio validation. Use for "harmonic pattern", "Gartley", "Butterfly pattern", "Bat pattern", "Crab pattern", "Cypher", "XABCD", "harmonic trading", "Scott Carney", or any harmonic analysis. Works with fibonacci-strategy-engine and chart-pattern-scanner.4---56# Harmonic Pattern Engine78```python9import pandas as pd, numpy as np10from scipy.signal import argrelextrema1112HARMONIC_RATIOS = {13 "gartley": {"XB": (0.618, 0.618), "AC": (0.382, 0.886), "BD": (1.272, 1.618), "XD": (0.786, 0.786)},14 "butterfly": {"XB": (0.786, 0.786), "AC": (0.382, 0.886), "BD": (1.618, 2.618), "XD": (1.272, 1.618)},15 "bat": {"XB": (0.382, 0.500), "AC": (0.382, 0.886), "BD": (1.618, 2.618), "XD": (0.886, 0.886)},16 "crab": {"XB": (0.382, 0.618), "AC": (0.382, 0.886), "BD": (2.240, 3.618), "XD": (1.618, 1.618)},17 "cypher": {"XB": (0.382, 0.618), "AC": (1.130, 1.414), "BD": (1.272, 2.000), "XD": (0.786, 0.786)},18}1920class HarmonicEngine:2122 @staticmethod23 def detect_xabcd(df: pd.DataFrame, tolerance: float = 0.05) -> list[dict]:24 """Detect XABCD harmonic patterns from swing points."""25 highs = argrelextrema(df["high"].values, np.greater, order=5)[0]26 lows = argrelextrema(df["low"].values, np.less, order=5)[0]27 swings = []28 for i in highs: swings.append({"idx": i, "price": df["high"].iloc[i], "type": "H"})29 for i in lows: swings.append({"idx": i, "price": df["low"].iloc[i], "type": "L"})30 swings.sort(key=lambda s: s["idx"])3132 patterns = []33 for i in range(len(swings) - 4):34 X, A, B, C, D = [swings[j]["price"] for j in range(i, i + 5)]35 XA = abs(A - X)36 if XA == 0: continue37 AB = abs(B - A)38 BC = abs(C - B)39 CD = abs(D - C)40 XB_ratio = AB / XA41 AC_ratio = BC / AB if AB > 0 else 042 BD_ratio = CD / BC if BC > 0 else 043 XD_ratio = abs(D - X) / XA4445 for name, ratios in HARMONIC_RATIOS.items():46 xb_min, xb_max = ratios["XB"][0] - tolerance, ratios["XB"][1] + tolerance47 xd_min, xd_max = ratios["XD"][0] - tolerance, ratios["XD"][1] + tolerance48 if xb_min <= XB_ratio <= xb_max and xd_min <= XD_ratio <= xd_max:49 bullish = D < X if swings[i]["type"] == "L" else D > X50 patterns.append({51 "pattern": name,52 "bullish": bullish,53 "X": round(X, 5), "A": round(A, 5), "B": round(B, 5), "C": round(C, 5), "D": round(D, 5),54 "XB": round(XB_ratio, 3), "XD": round(XD_ratio, 3),55 "prz": round(D, 5),56 "signal": f"{'BUY' if bullish else 'SELL'} at PRZ {round(D, 5)}",57 "stop": round(X, 5),58 "tp1": round(D + (A - D) * 0.382, 5) if bullish else round(D - (D - A) * 0.382, 5),59 "tp2": round(D + (A - D) * 0.618, 5) if bullish else round(D - (D - A) * 0.618, 5),60 })61 return patterns62```