Indicator · Pine Script v6 · On the chart

Harmonic Pattern Recognition

by riza10l

An overlay indicator that scans a configurable lookback window for Bat, Butterfly, Crab and Shark harmonic patterns using Fibonacci ratio checks with an adjustable tolerance.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code1,091
Settings21inputs
LicenceMIT
Last update4 months ago17 Jun 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,091 lines of Pine Script v6 from src/harmonic_patterns.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

harmonic_patterns.pine
//@version=6
indicator("Harmonic Pattern Recognition", overlay=true)

// =====================================================
// Introduction
// =====================================================
// Harmonic Pattern Recognition Indicator
//
// This indicator identifies and visualizes harmonic price patterns in financial markets.
// It detects four common harmonic formations:
// - Bat Pattern
// - Butterfly Pattern
// - Crab Pattern
// - Shark Pattern
//
// Each pattern is validated against standard Fibonacci ratio requirements
// and provides potential entry, stop loss, and target levels based on
// harmonic trading principles.
//
// Version: 0.0.1
// Release Date: 2025-04-30

// =====================================================
// Constants and Default Values
// =====================================================
// The following section defines all constants used throughout the indicator.
// This includes version information, default values, Fibonacci levels,
// pattern types, and threshold values for various calculations.

// Version info
var string VERSION = "0.0.1"
var string VERSION_DATE = "2025-04-30"

// General default values used for initialization and fallback scenarios
var float DEFAULT_TOLERANCE = 0.05
var float DEFAULT_VALUE_ON_ERROR = 0.0
var string DEFAULT_STRING_ON_ERROR = "N/A"
var int DEFAULT_LOOKBACK = 100
var int DEFAULT_MAX_PATTERNS = 10
var int MIN_BARS_FOR_PATTERN = 5

// Error handling constants
var int MAX_RETRY_ATTEMPTS = 3
var int MAX_ERRORS_TO_TRACK = 50

// Standard Fibonacci levels used in harmonic pattern validation
var float FIB_LEVEL_0 = 0.0
var float FIB_LEVEL_236 = 0.236
var float FIB_LEVEL_382 = 0.382
var float FIB_LEVEL_5 = 0.5
var float FIB_LEVEL_618 = 0.618
var float FIB_LEVEL_786 = 0.786
var float FIB_LEVEL_886 = 0.886
var float FIB_LEVEL_1 = 1.0
var float FIB_LEVEL_1272 = 1.272
var float FIB_LEVEL_1414 = 1.414
var float FIB_LEVEL_1618 = 1.618
var float FIB_LEVEL_2 = 2.0
var float FIB_LEVEL_2236 = 2.236
var float FIB_LEVEL_2618 = 2.618
var float FIB_LEVEL_3618 = 3.618
var float FIB_LEVEL_4236 = 4.236

// Pattern type identifiers
var string PATTERN_TYPE_BAT = "Bat"
var string PATTERN_TYPE_BUTTERFLY = "Butterfly"
var string PATTERN_TYPE_CRAB = "Crab"
var string PATTERN_TYPE_SHARK = "Shark"

// Market type classifications for potential future use in optimizing pattern detection
var string MARKET_TYPE_STOCK = "Stock"
var string MARKET_TYPE_FOREX = "Forex" 
var string MARKET_TYPE_CRYPTO = "Crypto"
var string MARKET_TYPE_COMMODITY = "Commodity"
var string MARKET_TYPE_UNKNOWN = "Unknown"

// Pattern direction constants
var int PATTERN_DIRECTION_BULLISH = 1
var int PATTERN_DIRECTION_BEARISH = -1

// Quality threshold definitions for pattern evaluation
var float QUALITY_HIGH = 0.8
var float QUALITY_MEDIUM = 0.5
var float QUALITY_LOW = 0.3

// Pattern age and minimum bar count constraints
var int MAX_PATTERN_AGE = 50
var int MIN_PATTERN_BARS = 10

// Standard colors used for visualization
var color BULLISH_COLOR = color.green
var color BEARISH_COLOR = color.red
var color NEUTRAL_COLOR = color.blue
var color WARNING_COLOR = color.yellow
var color ERROR_COLOR = color.red
var color INFO_COLOR = color.teal

// =====================================================
// Data Type Definitions
// =====================================================
// This section defines custom data types used throughout the indicator.
// These structured types enhance code organization and maintainability.

// Pattern options structure controls which harmonic patterns are detected
type pattern_options
    bool enable_bat
    bool enable_butterfly
    bool enable_crab
    bool enable_shark

// Creates default pattern options
// @returns A pattern_options object with all patterns enabled
create_default_pattern_options() =>
    pattern_options.new(
        true,
        true,
        true,
        true
    )

// Display options structure controls visualization parameters
type display_options
    bool show_labels
    bool show_predictions
    bool show_prices
    bool show_quality

// Creates default display options
// @returns A display_options object with standard visual elements enabled
create_default_display_options() =>
    display_options.new(
        true,
        true,
        true,
        true
    )

// Alert options structure controls alert behavior
type alert_options
    bool alert_on_new_pattern
    bool alert_on_completion
    bool alert_near_d
    float quality_threshold

// Creates default alert options
// @returns An alert_options object with standard alerts enabled
create_default_alert_options() =>
    alert_options.new(
        true,
        true,
        true,
        0.65
    )

// Style options structure controls visual appearance
type style_options
    color bullish_color
    color bearish_color
    color label_color
    int line_width

// Creates default style options
// @returns A style_options object with standard visual styling
create_default_style_options() =>
    style_options.new(
        color.new(color.green, 20),
        color.new(color.red, 20),
        color.white,
        2
    )

// Scan options structure controls pattern detection parameters
type scan_options
    int lookback_periods
    float pivot_sensitivity
    float tolerance

// Creates default scan options
// @returns A scan_options object with standard detection parameters
create_default_scan_options() =>
    scan_options.new(
        100,
        0.5,
        0.05
    )

// Signal type constants for trade recommendations
var string SIGNAL_NONE = "Tidak Ada Sinyal"
var string SIGNAL_BUY = "Beli"
var string SIGNAL_SELL = "Jual"
var string SIGNAL_EXIT = "Keluar"

// Trade signal structure encapsulates trading recommendations
type trade_signal
    string type
    string pattern_name
    float entry_price
    float stop_loss
    float target1
    float target2
    int bar_index
    float quality

// =====================================================
// UI Settings
// =====================================================
// This section defines the user interface controls for configuring the indicator.
// The settings are organized into logical groups for general configuration,
// pattern selection, alerts, visual appearance, and error handling.

// Input parameter tab definitions
var string tab_general = "Pengaturan Umum"
var string tab_patterns = "Pilihan Pola"
var string tab_alerts = "Pengaturan Alert"
var string tab_visuals = "Pengaturan Visual"
var string tab_error = "Penanganan Error"

// General settings control core indicator behavior
mode = input.string("Deteksi Otomatis", "Mode Deteksi", options=["Deteksi Otomatis", "Gambar Manual"], group=tab_general)
lookback = input.int(100, "Periode Lookback", minval=20, maxval=500, group=tab_general)
tolerance = input.float(0.05, "Toleransi Pola", minval=0.01, step=0.01, group=tab_general, tooltip="Persentase toleransi yang diperbolehkan untuk level Fibonacci")

// Pattern selection settings control which harmonic patterns are detected
enable_bat = input.bool(true, "Pola Bat (Kelelawar)", group=tab_patterns)
enable_butterfly = input.bool(true, "Pola Butterfly (Kupu-kupu)", group=tab_patterns)
enable_crab = input.bool(true, "Pola Crab (Kepiting)", group=tab_patterns)
enable_shark = input.bool(true, "Pola Shark (Hiu)", group=tab_patterns)

// Alert settings control notification behavior
alert_completion = input.bool(true, "Alert saat pola hampir selesai", group=tab_alerts)
alert_trade_points = input.bool(true, "Alert pada titik trading", group=tab_alerts)
min_quality_for_alert = input.float(0.6, "Minimum Akurasi Alert", minval=0.1, maxval=1.0, step=0.1, group=tab_alerts, tooltip="Hanya picu alert untuk pola dengan akurasi di atas nilai ini")

// Visual settings control appearance of patterns on the chart
show_labels = input.bool(true, "Tampilkan Label", group=tab_visuals)
show_prices = input.bool(true, "Tampilkan Level Harga", group=tab_visuals)
show_completion = input.bool(true, "Tampilkan Penyelesaian Pola", group=tab_visuals)
show_quality = input.bool(true, "Tampilkan Kualitas/Akurasi", group=tab_visuals)
show_predictions = input.bool(true, "Tampilkan Proyeksi (Potential D)", group=tab_visuals, tooltip="Tampilkan prediksi proyeksi titik D pada chart")
quality_color_coding = input.bool(true, "Warna berdasarkan akurasi", group=tab_visuals, tooltip="Ubah warna pola berdasarkan kualitas/akurasi pola")
pattern_color = input.color(color.blue, "Warna Garis Pola", group=tab_visuals)

// Error handling settings control how errors are managed
enable_error_recovery = input.bool(true, "Aktifkan Pemulihan Error", group=tab_error)
max_retry_attempts = input.int(3, "Jumlah Maksimal Percobaan Ulang", minval=1, maxval=10, group=tab_error)
error_log_level = input.string("Peringatan", "Tingkat Log Error", options=["Nonaktif", "Info", "Peringatan", "Error"], group=tab_error)
show_error_stats = input.bool(false, "Tampilkan Statistik Error", group=tab_error)

// =====================================================
// Error Handling Tools
// =====================================================
// This section provides functions for robust error handling throughout the indicator.
// These functions help prevent calculation errors, handle edge cases, and provide
// fallback mechanisms to ensure the indicator continues functioning even when
// unexpected conditions are encountered.

// Error statistics variables
var int total_errors = 0
var int recovered_errors = 0
var string last_error_message = ""
var int error_log_count = 0

// Retrieves a value safely, returning a default if the value is NA
// @param value The value to check
// @param default_value The value to return if the input is NA
// @returns The original value or default if NA
safe_value(value, default_value) =>
    na(value) ? default_value : value

// Safely performs division, avoiding division by zero errors
// @param numerator The dividend
// @param denominator The divisor
// @param default_value The value to return in case of division by zero
// @returns The division result or default_value if denominator is zero or NA
safe_divide(numerator, denominator, default_value) =>
    denominator == 0 or na(denominator) ? default_value : numerator / denominator

// Checks if a value is within a specified range
// @param value The value to check
// @param min_value The lower bound (inclusive)
// @param max_value The upper bound (inclusive)
// @returns Boolean indicating if value is within range
in_range(value, min_value, max_value) =>
    if na(value)
        false
    else
        value >= min_value and value <= max_value

// Attempts to execute an operation multiple times until success
// @param expression The operation to execute
// @param max_attempts Maximum number of retry attempts
// @returns The result of the operation or NA if all attempts fail
retry_operation(float expression, int max_attempts) =>
    float result = expression
    int attempt = 1
    
    while (na(result) and attempt < max_attempts)
        result := expression
        attempt += 1
    
    result

// Logs errors based on configured severity level
// @param message Error message to log
// @param level Error severity level ("Info", "Warning", or "Error")
// @returns Boolean indicating if the error should be logged
log_error(string message, string level) =>
    bool should_log = false
    
    if error_log_level == "Disabled"
        should_log := false
    else if error_log_level == "Info" and (level == "Info" or level == "Warning" or level == "Error")
        should_log := true
    else if error_log_level == "Warning" and (level == "Warning" or level == "Error")
        should_log := true
    else if error_log_level == "Error" and level == "Error"
        should_log := true
    
    should_log

// Displays an error message on the chart
// @param message Error message to display
// @param price_pos Vertical position for the error label
// @returns Boolean indicating if the error was displayed
display_error(string message, float price_pos) =>
    if na(message) or message == ""
        false
    else
        label.new(bar_index, price_pos, message, 
            style=label.style_label_down, 
            color=color.red, 
            textcolor=color.white, 
            size=size.small)
        true

// Attempts to automatically recover from calculation errors
// @param condition The condition to evaluate
// @param operation_name Name of the operation (for error reporting)
// @returns Boolean indicating success or failure
auto_recover(float condition, string operation_name) =>
    if enable_error_recovery
        float success = retry_operation(condition, max_retry_attempts)
        bool is_failed = na(success) or success == 0.0
        if is_failed
            bool logged = log_error(operation_name + " failed after " + str.tostring(max_retry_attempts) + " retries", "Error")
            if logged
                display_error(operation_name + " Error", high)
        success
    else
        float success = condition
        bool is_failed = na(success) or success == 0.0
        if is_failed
            bool logged = log_error(operation_name + " failed", "Error")
            if logged
                display_error(operation_name + " Error", high)
        success

// =====================================================
// Helper Functions
// =====================================================
// This section contains utility functions that support pattern detection and analysis.
// These functions handle common calculations like retracements, ratio validation,
// and price formatting.

// Calculates the retracement level between two price points
// @param start_price Starting price point
// @param end_price Ending price point
// @param current_price Current price to evaluate
// @returns Retracement value as a decimal (0.0-1.0) or DEFAULT_VALUE_ON_ERROR on failure
safe_retracement(float start_price, float end_price, float current_price) =>
    if na(start_price) or na(end_price) or na(current_price) or start_price == end_price
        DEFAULT_VALUE_ON_ERROR
    else
        price_range = end_price - start_price
        retracement = safe_divide(current_price - end_price, price_range, DEFAULT_VALUE_ON_ERROR)
        retracement

// Validates if a ratio is within tolerance range of an expected Fibonacci level
// @param actual_ratio The calculated ratio to validate
// @param expected_ratio The expected Fibonacci ratio
// @param tolerance The acceptable deviation from the expected ratio
// @returns Boolean indicating if the ratio is valid
validate_harmonic_ratio(float actual_ratio, float expected_ratio, float tolerance = 0.05) =>
    if na(actual_ratio) or na(expected_ratio)
        false
    else
        min_valid = expected_ratio - tolerance
        max_valid = expected_ratio + tolerance
        actual_ratio >= min_valid and actual_ratio <= max_valid

// Formats price value with appropriate precision based on magnitude
// @param price The price value to format
// @returns Formatted price string with adaptive precision
format_price(float price) =>
    if na(price)
        "N/A"
    else if price < 0.1
        // Higher precision for small values
        str.tostring(price, "#.#####")
    else if price < 10
        // Medium precision
        str.tostring(price, "#.###")
    else
        // Standard precision
        str.tostring(price, "#.##")

// Calculates ratio between two price movements
// @param move1 First price movement
// @param move2 Second price movement
// @returns Ratio of move2 to move1 or NA if invalid
calculate_ratio(float move1, float move2) =>
    if na(move1) or na(move2) or move1 == 0
        float(na)
    else
        move2 / move1

// =====================================================
// Harmonic Pattern Validation
// =====================================================

// Validate Bat pattern
// @param x_price X point price
// @param a_price A point price
// @param b_price B point price
// @param c_price C point price
// @param tolerance Tolerance for ratio validation
// @returns Array containing [is_valid, d_price]
validate_bat_pattern(float x_price, float a_price, float b_price, float c_price, float tolerance = 0.05) =>
    is_valid = false
    d_price = 0.0
    
    // Calculate key moves
    xa = a_price - x_price
    ab = b_price - a_price
    bc = c_price - b_price
    
    // Check key ratios for bat pattern
    // Bat pattern key ratios:
    // - AB = 0.382-0.5 of XA
    // - BC = 0.382-0.886 of AB
    // - CD = 1.618-2.618 of BC
    // - D = 0.886 retracement of XA
    
    ab_xa_ratio = calculate_ratio(xa, ab)
    bc_ab_ratio = calculate_ratio(ab, bc)
    
    // Validate AB/XA ratio (should be between 0.382-0.5)
    ab_xa_valid = validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_382, tolerance) or 
                  validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_5, tolerance) or
                  (ab_xa_ratio > FIB_LEVEL_382 - tolerance and ab_xa_ratio < FIB_LEVEL_5 + tolerance)
    
    // Validate BC/AB ratio (should be between 0.382-0.886)
    bc_ab_valid = validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_382, tolerance) or
                  validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_786, tolerance) or
                  (bc_ab_ratio > FIB_LEVEL_382 - tolerance and bc_ab_ratio < FIB_LEVEL_786 + tolerance)
    
    // Calculate D point (0.886 retracement of XA)
    d_price := a_price - xa * FIB_LEVEL_886
    
    // Check overall pattern validity
    is_valid := ab_xa_valid and bc_ab_valid
    
    [is_valid, d_price]

// Validate Butterfly pattern
// @param x_price X point price
// @param a_price A point price
// @param b_price B point price
// @param c_price C point price
// @param tolerance Tolerance for ratio validation
// @returns Array containing [is_valid, d_price]
validate_butterfly_pattern(float x_price, float a_price, float b_price, float c_price, float tolerance = 0.05) =>
    is_valid = false
    d_price = 0.0
    
    // Calculate key moves
    xa = a_price - x_price
    ab = b_price - a_price
    bc = c_price - b_price
    
    // Butterfly pattern key ratios:
    // - AB = 0.786 of XA
    // - BC = 0.382-0.886 of AB
    // - CD = 1.618-2.618 of BC
    // - D = 1.27-1.618 extension of XA
    
    ab_xa_ratio = calculate_ratio(xa, ab)
    bc_ab_ratio = calculate_ratio(ab, bc)
    
    // Validate AB/XA ratio (should be around 0.786)
    ab_xa_valid = validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_786, tolerance)
    
    // Validate BC/AB ratio (should be between 0.382-0.886)
    bc_ab_valid = validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_382, tolerance) or
                  validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_886, tolerance) or
                  (bc_ab_ratio > FIB_LEVEL_382 - tolerance and bc_ab_ratio < FIB_LEVEL_886 + tolerance)
    
    // Calculate D point (1.27-1.618 extension of XA)
    // Using average of the range for simplicity
    d_price := a_price - xa * FIB_LEVEL_1272
    
    // Check overall pattern validity
    is_valid := ab_xa_valid and bc_ab_valid
    
    [is_valid, d_price]

// Validate Crab pattern
// @param x_price X point price
// @param a_price A point price
// @param b_price B point price
// @param c_price C point price
// @param tolerance Tolerance for ratio validation
// @returns Array containing [is_valid, d_price]
validate_crab_pattern(float x_price, float a_price, float b_price, float c_price, float tolerance = 0.05) =>
    is_valid = false
    d_price = 0.0
    
    // Calculate key moves
    xa = a_price - x_price
    ab = b_price - a_price
    bc = c_price - b_price
    
    // Crab pattern key ratios:
    // - AB = 0.382-0.618 of XA
    // - BC = 0.382-0.886 of AB
    // - CD = 2.618-3.618 of BC
    // - D = 1.618 extension of XA
    
    ab_xa_ratio = calculate_ratio(xa, ab)
    bc_ab_ratio = calculate_ratio(ab, bc)
    
    // Validate AB/XA ratio (should be between 0.382-0.618)
    ab_xa_valid = validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_382, tolerance) or
                  validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_618, tolerance) or
                  (ab_xa_ratio > FIB_LEVEL_382 - tolerance and ab_xa_ratio < FIB_LEVEL_618 + tolerance)
    
    // Validate BC/AB ratio (should be between 0.382-0.886)
    bc_ab_valid = validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_382, tolerance) or
                  validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_886, tolerance) or
                  (bc_ab_ratio > FIB_LEVEL_382 - tolerance and bc_ab_ratio < FIB_LEVEL_886 + tolerance)
    
    // Calculate D point (1.618 extension of XA)
    d_price := a_price - xa * FIB_LEVEL_1618
    
    // Check overall pattern validity
    is_valid := ab_xa_valid and bc_ab_valid
    
    [is_valid, d_price]

// Validate Shark pattern
// @param x_price X point price
// @param a_price A point price
// @param b_price B point price
// @param c_price C point price
// @param tolerance Tolerance for ratio validation
// @returns Array containing [is_valid, d_price]
validate_shark_pattern(float x_price, float a_price, float b_price, float c_price, float tolerance = 0.05) =>
    is_valid = false
    d_price = 0.0
    
    // Calculate key moves
    xa = a_price - x_price
    ab = b_price - a_price
    bc = c_price - b_price
    
    // Shark pattern key ratios:
    // - AB = 0.5 of XA
    // - BC = 1.13-1.618 of AB
    // - CD = 1.618-2.24 of BC
    // - D = 0.886-1.13 of XA
    
    ab_xa_ratio = calculate_ratio(xa, ab)
    bc_ab_ratio = calculate_ratio(ab, bc)
    
    // Validate AB/XA ratio (should be around 0.5)
    ab_xa_valid = validate_harmonic_ratio(ab_xa_ratio, FIB_LEVEL_5, tolerance)
    
    // Validate BC/AB ratio (should be between 1.13-1.618)
    bc_ab_valid = validate_harmonic_ratio(bc_ab_ratio, 1.13, tolerance) or
                  validate_harmonic_ratio(bc_ab_ratio, FIB_LEVEL_1618, tolerance) or
                  (bc_ab_ratio > 1.13 - tolerance and bc_ab_ratio < FIB_LEVEL_1618 + tolerance)
    
    // Calculate D point (0.886-1.13 of XA)
    // Using average of the range for simplicity
    d_price := a_price - xa * FIB_LEVEL_886
    
    // Check overall pattern validity
    is_valid := ab_xa_valid and bc_ab_valid
    
    [is_valid, d_price]


// Calculates completion percentage of price move
// @param current_price Current price
// @param target_price Target price level
// @param start_price Starting price level
// @returns Completion percentage as decimal (0.0-1.0) or NA if invalid
get_completion_percentage(float current_price, float target_price, float start_price) =>
    if na(current_price) or na(target_price) or na(start_price) or start_price == target_price
        float(na)
    else
        total_move = math.abs(target_price - start_price)
        current_move = math.abs(current_price - start_price)
        completion = current_move / total_move
        // Cap at 100%
        math.min(completion, 1.0)

// Calculates overall pattern quality based on component metrics
// @param ratio_quality Quality of Fibonacci ratios (0.0-1.0)
// @param symmetry_quality Quality of pattern symmetry (0.0-1.0)
// @param time_quality Quality of time-based aspects (0.0-1.0)
// @returns Weighted quality score (0.0-1.0) or NA if invalid
calculate_pattern_quality(float ratio_quality, float symmetry_quality, float time_quality) =>
    if na(ratio_quality) or na(symmetry_quality) or na(time_quality)
        float(na)
    else
        // Weighted average (ratios most important, time least important)
        weighted_quality = ratio_quality * 0.6 + symmetry_quality * 0.3 + time_quality * 0.1
        // Constrain to 0.0-1.0 range
        math.max(0.0, math.min(weighted_quality, 1.0))

// =====================================================
// Signal Generation Functions
// =====================================================
// This section contains functions for generating and managing trading signals
// based on detected harmonic patterns. These functions create, evaluate, and
// format trading recommendations.

// Creates an empty signal structure with default values
// @returns Initialized empty trade_signal structure
create_empty_signal() =>
    trade_signal.new(
        SIGNAL_NONE,
        "",
        na,
        na,
        na,
        na,
        0,
        0.0
    )

// Generates a trading signal based on pattern parameters
// @param pattern_name Name of the harmonic pattern
// @param direction Direction of the pattern (1=bullish, -1=bearish)
// @param d_price Price level of the D point (entry price)
// @param a_price Price level of the A point (used for target calculation)
// @param quality Pattern quality score (0.0-1.0)
// @param risk_percent Risk percentage for stop loss calculation
// @param signal_bar_index Bar index for the signal
// @returns Populated trade_signal structure
generate_trade_signal(string pattern_name, int direction, float d_price, 
                      float a_price, float quality, float risk_percent = 0.01,
                      int signal_bar_index = na) =>
    int sig_bar = na(signal_bar_index) ? bar_index : signal_bar_index
    // Signal type
    signal_type = direction == 1 ? SIGNAL_BUY : SIGNAL_SELL
    
    // Calculate stop loss price (D point moved in reverse direction by risk_percent)
    stop_loss = direction == 1 ? d_price * (1 - risk_percent) : d_price * (1 + risk_percent)
    
    // Calculate target prices
    // Target 1 is usually 0.382 retracement from D to A
    // Target 2 is usually 0.618 retracement from D to A
    d_to_a = math.abs(a_price - d_price)
    target1 = direction == 1 ? d_price + d_to_a * 0.382 : d_price - d_to_a * 0.382
    
    target2 = direction == 1 ? d_price + d_to_a * 0.618 : d_price - d_to_a * 0.618
    
    trade_signal.new(
        signal_type,
        pattern_name,
        d_price,
        stop_loss,
        target1,
        target2,
        sig_bar,
        quality
    )

// Checks if a signal is valid (not empty)
// @param signal Signal object to evaluate
// @returns Boolean indicating if signal contains valid trading recommendation
is_valid_signal(trade_signal signal) =>
    signal.type != SIGNAL_NONE

// Calculates risk-reward ratio for a signal
// @param signal Signal object to evaluate
// @param use_target2 Whether to use target 2 (otherwise target 1 is used)
// @returns Risk-reward ratio as a decimal or 0.0 if invalid
calculate_risk_reward(trade_signal signal, bool use_target2 = false) =>
    if not is_valid_signal(signal)
        0.0
    else
        risk = math.abs(signal.entry_price - signal.stop_loss)
        
        target = use_target2 ? signal.target2 : signal.target1
        reward = math.abs(signal.entry_price - target)
        
        risk == 0 ? 0.0 : reward / risk

// Generates descriptive text for a trading signal
// @param signal Signal object to describe
// @returns Formatted text description of the signal
get_signal_text(trade_signal signal) =>
    if not is_valid_signal(signal)
        "Tidak Ada Sinyal Trading"
    else
        rr1 = str.tostring(calculate_risk_reward(signal, false), "#.##")
        rr2 = str.tostring(calculate_risk_reward(signal, true), "#.##")
        quality_text = str.tostring(signal.quality * 100, "#.00") + "%"
        
        string txt = signal.type + " " + signal.pattern_name
        txt := txt + "\nEntry: " + str.tostring(signal.entry_price, "#.####")
        txt := txt + "\nStop Loss: " + str.tostring(signal.stop_loss, "#.####")
        txt := txt + "\nTarget 1: " + str.tostring(signal.target1, "#.####") + " (RR: " + rr1 + ")"
        txt := txt + "\nTarget 2: " + str.tostring(signal.target2, "#.####") + " (RR: " + rr2 + ")"
        txt := txt + "\nAkurasi: " + quality_text
        txt

// Creates a visual label for a trading signal
// @param signal Signal object to visualize
// @param label_bar_index Bar index for label placement
draw_signal_label(trade_signal signal, int label_bar_index = na) =>
    int bar_idx = na(label_bar_index) ? bar_index : label_bar_index
    if is_valid_signal(signal)
        label_color = signal.type == SIGNAL_BUY ? color.green : color.red
        
        label.new(
            bar_idx, signal.entry_price,
            get_signal_text(signal),
            color = color.new(color.black, 80),
            textcolor = label_color,
            style = label.style_label_down,
            size = size.normal
        )


// =====================================================
// Harmonic Pattern Detection Logic
// =====================================================
// This section contains the core logic for detecting pivot points and
// identifying potential harmonic patterns in the price series.

// Detects local pivot points (highs and lows) in a price series
//
// This function identifies potential reversals by comparing a central point
// with surrounding bars. It implements safety checks to handle boundary conditions
// and missing values.
//
// @param src Source price series
// @param length Number of bars to analyze on each side
// @returns Array containing [pivot_high, pivot_low] values
find_pivots_safe(float src, int length) =>
    float pivot_high = 0.0
    float pivot_low = 0.0
    
    // Use safe checks to handle boundary conditions
    if not na(src) and length > 0 and bar_index >= length
        // Look for highs
        bool is_pivot_high = true
        for i = 1 to length
            if not in_range(i, 1, bar_index) or na(src[i])
                is_pivot_high := false
                break
            if src[0] <= src[i]
                is_pivot_high := false
        
        // Look for lows
        bool is_pivot_low = true
        for i = 1 to length
            if not in_range(i, 1, bar_index) or na(src[i])
                is_pivot_low := false
                break
            if src[0] >= src[i]
                is_pivot_low := false
        
        // Set results
        pivot_high := is_pivot_high ? src[0] : 0.0
        pivot_low := is_pivot_low ? src[0] : 0.0
    
    [pivot_high, pivot_low]

// Initialize pivot point arrays
pivot_length = math.max(5, math.floor(lookback / 10))
// X coordinates (bar indices)
var pivot_points_x = array.new_int(0)
// Y coordinates (prices)
var pivot_points_y = array.new_float(0)
// Types (1=high, -1=low)
var pivot_types = array.new_int(0)

// Check for new pivots
[ph, pl] = find_pivots_safe(close, pivot_length)

// Add newly discovered pivot points
if ph > 0
    array.push(pivot_points_x, bar_index)
    array.push(pivot_points_y, ph)
    array.push(pivot_types, 1)

if pl > 0
    array.push(pivot_points_x, bar_index)
    array.push(pivot_points_y, pl)
    array.push(pivot_types, -1)

// Remove too old pivot points
if array.size(pivot_points_x) > 0
    int oldest_index = array.get(pivot_points_x, 0)
    if bar_index - oldest_index > lookback
        array.shift(pivot_points_x)
        array.shift(pivot_points_y)
        array.shift(pivot_types)

// =====================================================
// Drawing Functions
// =====================================================

// Draws a harmonic pattern on the chart with proper styling and labels
// @param x1 X point bar index
// @param y1 X point price
// @param x2 A point bar index
// @param y2 A point price
// @param x3 B point bar index
// @param y3 B point price
// @param x4 C point bar index
// @param y4 C point price
// @param pattern_name Name of the harmonic pattern
// @param direction Direction of the pattern (1=bullish, -1=bearish)
// @param quality Pattern quality score (0.0-1.0)
draw_pattern(int x1, float y1, int x2, float y2, int x3, float y3, int x4, float y4, string pattern_name, int direction, float quality) =>
    string pattern_text = pattern_name + " (" + (direction == 1 ? "Bullish" : "Bearish") + ")"
    
    // Quality-based color
    color pattern_color_with_quality = pattern_color
    if quality_color_coding
        color base_color = direction == 1 ? color.green : color.red
        int transparency = math.round((1 - quality) * 70)
        pattern_color_with_quality := color.new(base_color, transparency)
    
    // Draw XA line
    line.new(x1, y1, x2, y2, 
        color=pattern_color_with_quality, 
        width=1,
        style=line.style_dashed)
    
    // Draw AB line
    line.new(x2, y2, x3, y3, 
        color=pattern_color_with_quality, 
        width=1,
        style=line.style_dashed)
    
    // Draw BC line
    line.new(x3, y3, x4, y4, 
        color=pattern_color_with_quality, 
        width=1,
        style=line.style_dashed)
    
    // Add labels (if enabled)
    if show_labels
        string quality_text = show_quality ? "\nAkurasi: " + str.tostring(quality * 100, "#.##") + "%" : ""
        label.new(
            x1, y1, 
            "X\n" + pattern_text + quality_text, 
            style=label.style_label_down,
            color=color.new(color.black, 80),
            textcolor=pattern_color_with_quality)
        
        label.new(x2, y2, "A", style=label.style_label_down, color=color.new(color.black, 80), textcolor=pattern_color_with_quality)
        label.new(x3, y3, "B", style=label.style_label_down, color=color.new(color.black, 80), textcolor=pattern_color_with_quality)
        label.new(x4, y4, "C", style=label.style_label_down, color=color.new(color.black, 80), textcolor=pattern_color_with_quality)

// Draws projection for potential D point with appropriate styling
// @param x1 X point bar index
// @param y1 X point price
// @param x4 C point bar index
// @param y4 C point price
// @param d_price Projected D point price
// @param direction Direction of the pattern (1=bullish, -1=bearish)
// @param pattern_name Name of the harmonic pattern
// @param quality Pattern quality score (0.0-1.0)
draw_potential_d(int x1, float y1, int x4, float y4, float d_price, int direction, string pattern_name, float quality) =>
    if show_predictions
        int d_x = bar_index + 5
        string d_text = "Potensi D\n" + pattern_name + "\nEntry: " + str.tostring(d_price, "#.####")
        
        // Draw CD line
        line.new(x4, y4, d_x, d_price, 
            color=color.new(pattern_color, 50), 
            width=1,
            style=line.style_dotted)
        
        // Add D point label
        label.new(
            d_x, d_price, 
            d_text, 
            style=label.style_label_down,
            color=color.new(color.black, 80),
            textcolor=direction == 1 ? color.green : color.red)

// Processes pivot points to identify harmonic patterns and generate trading signals
//
// This function analyzes recent pivot points to detect valid harmonic patterns,
// then generates appropriate trading signals based on the highest quality pattern found.
//
// @returns A trade_signal object containing the recommended action if a valid pattern is found
process_pattern_trading_signals() =>
    trade_signal signal = create_empty_signal()
    
    // Check if there are enough pivot points to detect patterns
    if array.size(pivot_points_x) >= 4
        int size = array.size(pivot_points_x)
        
        // Get the most recent 4 pivot points
        int x_index = array.get(pivot_points_x, size - 4)
        int a_index = array.get(pivot_points_x, size - 3)
        int b_index = array.get(pivot_points_x, size - 2)
        int c_index = array.get(pivot_points_x, size - 1)
        
        float x_price = array.get(pivot_points_y, size - 4)
        float a_price = array.get(pivot_points_y, size - 3)
        float b_price = array.get(pivot_points_y, size - 2)
        float c_price = array.get(pivot_points_y, size - 1)
        
        // Determine pattern direction
        int direction = a_price > x_price ? -1 : 1
        
        // Check various harmonic patterns and prioritize highest quality signal
        trade_signal best_signal = create_empty_signal()
        float best_quality = 0.0
        string best_pattern_name = ""
        float best_d_price = 0.0
        
        // Bat pattern
        if enable_bat
            [is_valid, d_price] = validate_bat_pattern(x_price, a_price, b_price, c_price, tolerance)
            if is_valid
                // Simplified quality calculation
                float quality = 0.8
                if quality >= min_quality_for_alert and quality > best_quality
                    best_quality := quality
                    best_d_price := d_price
                    best_pattern_name := PATTERN_TYPE_BAT
                    best_signal := generate_trade_signal(
                        PATTERN_TYPE_BAT, 
                        direction, 
                        d_price, 
                        a_price, 
                        quality,
                        0.01,
                        c_index)
        
        // Butterfly pattern
        if enable_butterfly
            [is_valid, d_price] = validate_butterfly_pattern(x_price, a_price, b_price, c_price, tolerance)
            if is_valid
                // Simplified quality calculation
                float quality = 0.75
                if quality >= min_quality_for_alert and quality > best_quality
                    best_quality := quality
                    best_d_price := d_price
                    best_pattern_name := PATTERN_TYPE_BUTTERFLY
                    best_signal := generate_trade_signal(
                        PATTERN_TYPE_BUTTERFLY, 
                        direction, 
                        d_price, 
                        a_price, 
                        quality,
                        0.01,
                        c_index)
        
        // Crab pattern
        if enable_crab
            [is_valid, d_price] = validate_crab_pattern(x_price, a_price, b_price, c_price, tolerance)
            if is_valid
                // Simplified quality calculation
                float quality = 0.85
                if quality >= min_quality_for_alert and quality > best_quality
                    best_quality := quality
                    best_d_price := d_price
                    best_pattern_name := PATTERN_TYPE_CRAB
                    best_signal := generate_trade_signal(
                        PATTERN_TYPE_CRAB, 
                        direction, 
                        d_price, 
                        a_price, 
                        quality,
                        0.01,
                        c_index)
        
        // Shark pattern
        if enable_shark
            [is_valid, d_price] = validate_shark_pattern(x_price, a_price, b_price, c_price, tolerance)
            if is_valid
                // Simplified quality calculation
                float quality = 0.7
                if quality >= min_quality_for_alert and quality > best_quality
                    best_quality := quality
                    best_d_price := d_price
                    best_pattern_name := PATTERN_TYPE_SHARK
                    best_signal := generate_trade_signal(
                        PATTERN_TYPE_SHARK, 
                        direction, 
                        d_price, 
                        a_price, 
                        quality,
                        0.01,
                        c_index)
        
        // Use highest quality signal
        if best_quality > 0
            signal := best_signal
    
    signal

// =====================================================
// Main Calculation Logic
// =====================================================

// Main harmonic pattern detection function
//
// This function coordinates the overall pattern recognition process,
// from finding pivot points to validating patterns.
detect_harmonic_patterns() =>
    // Detection logic for harmonic patterns
    // Current implementation uses a modular approach with specialized functions
    true

// Process patterns to generate signals
trade_signal signal = process_pattern_trading_signals()

// Display the signal if valid and it's a new pattern (not already drawn)
var int last_signal_c_index = -1
if is_valid_signal(signal) and signal.bar_index != last_signal_c_index
    // Draw the signal text label on the chart
    draw_signal_label(signal)
    
    // Draw the pattern lines (XA, AB, BC, CD) and projected potential D point
    int size = array.size(pivot_points_x)
    if size >= 4
        int x_index = array.get(pivot_points_x, size - 4)
        int a_index = array.get(pivot_points_x, size - 3)
        int b_index = array.get(pivot_points_x, size - 2)
        int c_index = array.get(pivot_points_x, size - 1)
        
        float x_price = array.get(pivot_points_y, size - 4)
        float a_price = array.get(pivot_points_y, size - 3)
        float b_price = array.get(pivot_points_y, size - 2)
        float c_price = array.get(pivot_points_y, size - 1)
        
        int direction = a_price > x_price ? -1 : 1
        
        draw_pattern(x_index, x_price, a_index, a_price, b_index, b_price, c_index, c_price, signal.pattern_name, direction, signal.quality)
        draw_potential_d(x_index, x_price, c_index, c_price, signal.entry_price, direction, signal.pattern_name, signal.quality)
        
    last_signal_c_index := signal.bar_index

// =====================================================
// Alert Logic
// =====================================================
// This section handles alert generation based on detected patterns and signals.
// Alerts are triggered according to user-defined criteria and thresholds.

// Generate alerts for valid trading signals
var int last_alert_c_index = -1
if alert_trade_points and is_valid_signal(signal) and signal.quality >= min_quality_for_alert and signal.bar_index != last_alert_c_index
    string alert_message = get_signal_text(signal)
    alert(alert_message, alert.freq_once_per_bar)
    last_alert_c_index := signal.bar_index

// =====================================================
// Chart Information Display
// =====================================================
// This section manages the display of supplementary information on the chart,
// including error statistics and debugging information when enabled.

// Display error statistics when enabled
if show_error_stats and barstate.islast
    string stats_text = "Error: " + str.tostring(total_errors) + 
                 " | Berhasil Dipulihkan: " + str.tostring(recovered_errors)
    
    if last_error_message != ""
        stats_text := stats_text + "\nError Terakhir: " + last_error_message
    
    label.new(
        bar_index, low, 
        stats_text, 
        style=label.style_label_up,
        color=color.new(color.gray, 80),
        textcolor=color.white,
        size=size.small)

Overview

Harmonic Pattern Recognition draws pattern lines, labels, price levels, a projected completion point and a quality score on the price chart, and can colour patterns by that score. Alerts can fire when a pattern nears completion or reaches trade points above a minimum quality. Labels and settings are partly in Indonesian.

Details

Published by

riza10l on GitHub

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Support & resistance

Markets

Crypto

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Mode DeteksistringDeteksi Otomatismode
Periode Lookbackint100lookback
Toleransi Polafloat0.05tolerance
Pola Bat (Kelelawar)booltrueenable_bat
Pola Butterfly (Kupu-kupu)booltrueenable_butterfly
Pola Crab (Kepiting)booltrueenable_crab
Pola Shark (Hiu)booltrueenable_shark
Alert saat pola hampir selesaibooltruealert_completion
Alert pada titik tradingbooltruealert_trade_points
Minimum Akurasi Alertfloat0.6min_quality_for_alert
Tampilkan Labelbooltrueshow_labels
Tampilkan Level Hargabooltrueshow_prices
Show all 21 settings
SettingTypeDefaultVariable
Tampilkan Penyelesaian Polabooltrueshow_completion
Tampilkan Kualitas/Akurasibooltrueshow_quality
Tampilkan Proyeksi (Potential D)booltrueshow_predictions
Warna berdasarkan akurasibooltruequality_color_coding
Warna Garis Polacolorcolor.bluepattern_color
Aktifkan Pemulihan Errorbooltrueenable_error_recovery
Jumlah Maksimal Percobaan Ulangint3max_retry_attempts
Tingkat Log ErrorstringPeringatanerror_log_level
Tampilkan Statistik Errorboolfalseshow_error_stats
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

948 stars

Moving Average Channel plots separate simple moving averages for upper and lower bands on the price chart. It fills the space between bands with semi-transparent color and colors bars green above the upper band, red below the lower band, or gray in between. Us

Indicator v4 Moving averagesSupport & resistance
Updated 11 days ago · GPL-3.0 GitHub repository of Moving Average Channel

ICT Equal Highs and Lows

by ArunKBhaskar
575 stars

This overlay indicator finds pivot highs and lows on the price chart and compares the last two highs and the last two lows. When the percentage difference between a pair is below the Equal HL threshold, it treats them as equal levels and draws a box. Left and

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows
330 stars

This indicator monitors multiple cryptocurrency symbols for trading signals. It calculates and plots entry prices, take profit, and stop loss levels for each symbol including BTCUSDT, ETHUSDT, BNBUSDT and others. Price crossovers trigger buy, sell or close ale

Indicator v5 Support & resistanceScreeners & dashboards
Updated 2 months ago · None stated GitHub repository of Multi Alert Long/Short [Alorse]

This overlay indicator draws a fixed-range volume profile on the price chart, splitting traded activity into up and down volume at each price level. It also plots a sentiment profile, a developing point of control, and value-area high and low around a configur

Indicator v5 VolumeSupport & resistance
Updated 2 months ago · MIT GitHub repository of Volume Profile and Indicator by DGT

BZ-tracker

by Allen1212
303 stars

The BZ-tracker indicator marks ARK Invest buy and sell transactions on stock charts with labels. It plots green labels for purchases and red labels for sales, each annotated with a multiplier such as X1 or X2 to show the number of ARK ETFs trading the stock on

Indicator v4 Support & resistanceScreeners & dashboards
Updated 2 years ago · None stated GitHub repository of BZ-tracker

Function - Linear Regression

by ricardosantos79
261 stars

This indicator fits a linear regression through stored pivot highs and lows and draws the resulting channel on the price chart. A function returns predicted values, maximum and minimum deviations, and the average absolute deviation. Confirmed two-bar pivots up

Indicator v5 TrendSupport & resistance
Updated 4 years ago · None stated GitHub repository of Function - Linear Regression

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers