Tính Fibonacci bằng Python: Tự động hóa dãy số và Backtesting Elliott

Tính Fibonacci bằng tay không sai, nhưng không đủ. Mỗi lần kéo thước thủ công là một lần cảm xúc có cơ hội can thiệp vào điểm pivot, mỗi lần vẽ lại là một lần mất đi tính nhất quán mà backtesting đòi hỏi. Trader chuyên nghiệp không dừng lại ở việc biết Fibonacci là gì mà tiến đến chỗ tự động hóa toàn bộ quá trình đo đạc sóng bằng Python. Bài viết này trình bày cách xây dựng một pipeline hoàn chỉnh: từ dãy fibonacci python cơ bản, qua công cụ tính fibonacci retracement và extension, đến tích hợp trực tiếp vào hệ thống backtesting với dữ liệu OHLCV thực. Đây là cách tiếp cận mà Đông Tà Elliott và cộng đồng Quant tại FinzAcademy đang áp dụng để loại bỏ yếu tố cảm xúc khỏi phân tích sóng Elliott.

Tại sao trader tính Fibonacci bằng Python chính xác hơn là vẽ tay thủ công?

Tính Fibonacci bằng mắt thường là thói quen phổ biến, nhưng đây cũng là nguồn gốc của hầu hết sai sót trong phân tích sóng Elliott. Khi hai trader cùng nhìn vào một biểu đồ nhưng kéo thước Fibonacci từ hai điểm pivot khác nhau, họ sẽ nhận được hai tập mức giá hoàn toàn không trùng nhau. Kết quả là quyết định giao dịch phân kỳ ngay từ bước đầu tiên.

Tính Fibonacci bằng Python chính xác hơn là vẽ tay thủ công
Tính Fibonacci bằng Python chính xác hơn là vẽ tay thủ công

Sai số pivot là lỗi có hệ thống, không phải lỗi ngẫu nhiên. Trong một phiên biến động mạnh, sự chênh lệch vài pip trong việc chọn đỉnh hay đáy sẽ đẩy mức 61.8% lệch ra khỏi vùng hỗ trợ thực sự. Trader vào lệnh sai vùng, đặt stop loss sai vị trí, và khi giá phản ứng đúng theo Fibonacci thực tế thì lệnh đã bị cắt lỗ từ trước.

Cảm xúc làm vấn đề trở nên tệ hơn. Khi thị trường đang trong xu hướng tăng mạnh, trader có xu hướng vô thức chọn điểm đáy thấp hơn để kéo dài range Fibonacci, từ đó hạ thấp mức 61.8% xuống sâu hơn thực tế nhằm tự thuyết phục rằng mức vào lệnh của mình đang an toàn. Đây là thiên lệch xác nhận (confirmation bias) được thể hiện trực tiếp qua cách đặt thước.

Backtesting quy mô lớn là điều không thể thực hiện được nếu dữ liệu Fibonacci không được chuẩn hóa. Muốn kiểm tra xem mức 38.2% hay 61.8% có hiệu quả hơn trong điều kiện nào, trader cần chạy hàng trăm tình huống trên dữ liệu lịch sử. Không một con người nào có thể vẽ tay đủ nhanh và đủ nhất quán cho quá trình đó. Python giải quyết cả ba vấn đề trên: chuẩn hóa pivot, loại bỏ cảm xúc, và tự động hóa toàn bộ pipeline tính toán.

Dãy Fibonacci là gì và các mức nào thực sự quan trọng trong trading?

Dãy Fibonacci là chuỗi số trong đó mỗi phần tử bằng tổng của hai phần tử đứng trước: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144… Khi chia một số trong dãy cho số đứng liền trước, tỷ lệ hội tụ về 1.618, được gọi là tỷ lệ vàng (golden ratio). Nghịch đảo của 1.618 xấp xỉ bằng 0.618, đây chính là nền tảng toán học của toàn bộ hệ thống mức Fibonacci trong trading.

Tỷ lệ vàng 0.618 không phải là con số tùy tiện. Khi một phần tử trong dãy được chia cho phần tử đứng hai bậc sau, kết quả hội tụ về 0.382. Khi chia cho phần tử đứng ba bậc sau, kết quả hội tụ về 0.236. Đây là cách toán học sinh ra chính xác các mức retracement mà trader sử dụng hằng ngày.

Không phải mức Fibonacci nào cũng có tầm quan trọng như nhau. Từ góc nhìn thực chiến, nhóm mức 38.2% đến 61.8% tạo thành vùng retracement cốt lõi, nơi xác suất giá phản ứng là cao nhất trong điều kiện trend còn nguyên vẹn. Mức 50% không có nguồn gốc trực tiếp từ dãy Fibonacci mà xuất phát từ lý thuyết Dow, nhưng vẫn được cộng đồng trader sử dụng rộng rãi đến mức trở thành một mức kháng cự hỗ trợ tự thực hiện (self-fulfilling).

MứcNguồn gốcVai trò trong trading
23.6%F(n)/F(n+3)Retracement nông, trend mạnh
38.2%F(n)/F(n+2)Retracement chuẩn, vùng vào lệnh sớm
50.0%Lý thuyết DowMức tâm lý, không thuần Fibonacci
61.8%F(n)/F(n+1)Golden retracement, xác suất phản ứng cao nhất
78.6%Căn bậc hai của 61.8%Retracement sâu, gần ngưỡng invalidation
100.0%Toàn bộ rangeĐiểm bắt đầu sóng, ngưỡng invalidation
127.2%Căn bậc hai của 1.618Extension mức đầu tiên
161.8%Tỷ lệ vàngExtension mục tiêu sóng 3 và sóng 5
261.8%1.618 bình phươngExtension mở rộng khi sóng 3 kéo dài

Mức 78.6% là điểm mà nhiều trader mắc sai lầm phổ biến nhất. Khi giá chạm 78.6%, tâm lý hoảng loạn thường khiến trader đóng lệnh vì nghĩ trend đã đảo chiều. Thực tế đây vẫn là vùng hợp lệ của sóng điều chỉnh nếu giá chưa phá vỡ điểm 0% của sóng 1.

Dãy Fibonacci Python: 3 cách viết code từ đệ quy đến tối ưu

Viết dãy fibonacci python là bài tập xuất hiện ở mọi khóa lập trình cơ bản, nhưng trader cần hiểu rõ sự khác biệt về hiệu suất giữa các cách tiếp cận trước khi tích hợp vào hệ thống thực. Mỗi phương pháp có đặc tính tốc độ và bộ nhớ khác nhau, và điều đó ảnh hưởng trực tiếp đến khả năng mở rộng khi backtesting trên hàng nghìn cây nến.

Dãy Fibonacci Python
Dãy Fibonacci Python

Cách 1: Đệ quy thuần (Recursion)

Đây là cách trực quan nhất nhưng có độ phức tạp thời gian O(2^n), nghĩa là tốc độ giảm theo cấp số nhân khi n tăng lên.

python

def fibonacci_recursive(n):
    if n <= 1:
        return n
    return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)

# Ví dụ: in 10 phần tử đầu
for i in range(10):
    print(fibonacci_recursive(i), end=" ")
# Kết quả: 0 1 1 2 3 5 8 13 21 34

Đệ quy tính lại giá trị giống nhau nhiều lần. Khi gọi fibonacci_recursive(10), hàm sẽ tính fibonacci_recursive(8) đến hai lần, fibonacci_recursive(7) đến ba lần, và cứ thế tăng lên theo cấp số nhân. Với n = 40, thời gian chạy đã tính bằng giây. Đây là lý do tuyệt đối không dùng đệ quy thuần trong môi trường trading.

Cách 2: Vòng lặp (Iterative)

Vòng lặp có độ phức tạp O(n) về thời gian và O(1) về bộ nhớ nếu chỉ cần hai giá trị liên tiếp. Đây là lựa chọn mặc định cho mọi ứng dụng thực chiến.

python

def fibonacci_iterative(n):
    if n <= 1:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

def fibonacci_sequence(count):
    sequence = []
    a, b = 0, 1
    for _ in range(count):
        sequence.append(a)
        a, b = b, a + b
    return sequence

# Sinh dãy 15 phần tử
print(fibonacci_sequence(15))
# Kết quả: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

Vòng lặp chỉ lưu hai biến ab tại mỗi bước. Bộ nhớ sử dụng không đổi dù n có lớn đến đâu. Trong pipeline backtesting chạy liên tục, đặc tính này giúp hệ thống ổn định và không gây memory leak.

Cách 3: Dynamic Programming với Memoization

Memoization lưu kết quả đã tính vào bộ nhớ đệm để tránh tính lại. Với Python, decorator @lru_cache từ thư viện functools làm điều này tự động.

python

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci_memo(n):
    if n <= 1:
        return n
    return fibonacci_memo(n - 1) + fibonacci_memo(n - 2)

# Lần đầu tính chậm hơn, các lần sau gần như tức thì
print([fibonacci_memo(i) for i in range(15)])

Memoization hữu ích khi cần gọi hàm Fibonacci nhiều lần với các giá trị n lặp đi lặp lại trong một session, ví dụ như khi tính song song nhiều chuỗi sóng Elliott trên cùng một dataset. Tuy nhiên, bộ nhớ đệm chiếm RAM và không tự giải phóng nếu không gọi .cache_clear().

So sánh hiệu suất

Phương phápĐộ phức tạp thời gianĐộ phức tạp bộ nhớPhù hợp cho trading
Đệ quy thuầnO(2^n)O(n) stackKhông
Vòng lặpO(n)O(1)Có, ưu tiên số một
MemoizationO(n) lần đầu, O(1) sauO(n) cacheCó, khi cần tái sử dụng

Đối với pipeline tính Fibonacci trong trading, vòng lặp iterative là lựa chọn tối ưu vì kết hợp tốc độ nhanh, bộ nhớ tối thiểu và không có rủi ro stack overflow khi n lớn.

Dãy Fibonacci Pascal: Mối liên hệ toán học giúp tính nhanh tỷ lệ retracement

Tam giác Pascal và dãy Fibonacci có mối liên hệ ít được nhắc đến nhưng rất hữu ích khi tối ưu hóa code tính toán trong môi trường quantitative trading. Hiểu được mối liên hệ này giúp trader lập trình viết các hàm tính mức Fibonacci một cách linh hoạt hơn, đặc biệt khi làm việc với các bài toán tổ hợp trong phân tích sóng mở rộng.

Tam giác Pascal
Tam giác Pascal

Tam giác Pascal là mảng số trong đó mỗi phần tử bằng tổng của hai phần tử ngay trên nó:

        1
       1 1
      1 2 1
     1 3 3 1
    1 4 6 4 1
   1 5 10 10 5 1
  1 6 15 20 15 6 1

Khi cộng theo đường chéo nghiêng (shallow diagonal), kết quả chính xác là dãy Fibonacci: 1, 1, 2, 3, 5, 8, 13… Công thức toán học biểu diễn mối liên hệ này là F(n) = Σ C(n-1-k, k) với k chạy từ 0 đến floor((n-1)/2), trong đó C(n,k) là hệ số tổ hợp.

python

from math import comb

def fibonacci_pascal(n):
    # Tính F(n) thông qua tổng hệ số tổ hợp theo đường chéo Pascal
    result = 0
    k = 0
    while (n - 1 - k) >= k:
        result += comb(n - 1 - k, k)
        k += 1
    return result

# Kiểm tra
for i in range(1, 11):
    print(f"F({i}) = {fibonacci_pascal(i)}")
# Kết quả: F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5, F(6)=8...

Ứng dụng thực tiễn của cách tiếp cận Pascal nằm ở việc tính trực tiếp một phần tử F(n) bất kỳ mà không cần sinh toàn bộ dãy từ đầu. Trong trường hợp cần tính nhanh tỷ lệ F(n)/F(n+1) để xác nhận mức 61.8% trong một đoạn sóng cụ thể, hàm dựa trên comb() từ thư viện math của Python cho phép tính toán chính xác với số nguyên lớn, tránh lỗi làm tròn floating-point có thể xảy ra khi lặp nhiều vòng.

Checklist ứng dụng của cấu trúc Pascal trong môi trường Quant:

  • Tính F(n) đơn lẻ không cần sinh toàn bộ dãy
  • Kiểm tra tính hợp lệ của tỷ lệ F(n)/F(n+1) với độ chính xác số nguyên
  • Xây dựng bảng tra cứu Fibonacci tĩnh để tăng tốc pipeline backtesting
  • Tích hợp vào các mô hình xác suất khi đếm số lượng tổ hợp sóng hợp lệ

Công cụ tính Fibonacci tự động bằng Python: Từ dãy số đến mức giá retracement và extension

Mục tiêu của phần này là xây dựng một công cụ tính Fibonacci hoàn chỉnh, nhận vào swing high và swing low, trả về toàn bộ mức giá retracement và extension sẵn sàng đưa vào hệ thống. Đây là bước chuyển đổi từ dãy số toán học thuần túy sang một công cụ phân tích giá thực chiến.

Input và công thức tính

Công thức retracement trong uptrend:

Mức giá = High - (High - Low) × tỷ lệ_Fibonacci

Công thức retracement trong downtrend:

Mức giá = Low + (High - Low) × tỷ lệ_Fibonacci

Công thức extension trong uptrend (sau khi xác định điểm C tức là điểm kết thúc retracement):

Mức giá = High + (High - Low) × tỷ lệ_extension

Hàm fib_levels() hoàn chỉnh

python

def fib_levels(high, low, trend="up", point_c=None):
    """
    Tính tất cả mức Fibonacci retracement và extension.

    Parameters:
        high (float): Giá cao nhất của sóng (swing high)
        low (float): Giá thấp nhất của sóng (swing low)
        trend (str): "up" hoặc "down"
        point_c (float): Điểm kết thúc retracement để tính extension (tùy chọn)

    Returns:
        dict: Tập hợp mức giá retracement và extension
    """
    diff = high - low

    retracement_ratios = {
        "0.0%":   0.000,
        "23.6%":  0.236,
        "38.2%":  0.382,
        "50.0%":  0.500,
        "61.8%":  0.618,
        "78.6%":  0.786,
        "100.0%": 1.000,
    }

    extension_ratios = {
        "127.2%": 1.272,
        "138.2%": 1.382,
        "161.8%": 1.618,
        "200.0%": 2.000,
        "261.8%": 2.618,
    }

    result = {"retracement": {}, "extension": {}}

    # Tính retracement
    for label, ratio in retracement_ratios.items():
        if trend == "up":
            result["retracement"][label] = round(high - diff * ratio, 5)
        else:
            result["retracement"][label] = round(low + diff * ratio, 5)

    # Tính extension nếu có point_c
    if point_c is not None:
        for label, ratio in extension_ratios.items():
            if trend == "up":
                result["extension"][label] = round(high + diff * ratio, 5)
            else:
                result["extension"][label] = round(low - diff * ratio, 5)

    return result


# Ví dụ thực tế: EURUSD uptrend
levels = fib_levels(high=1.10550, low=1.06650, trend="up")

print("=== RETRACEMENT LEVELS ===")
for level, price in levels["retracement"].items():
    print(f"  {level:>8} → {price:.5f}")

Kết quả ví dụ:

=== RETRACEMENT LEVELS ===
     0.0% → 1.10550
    23.6% → 1.09630
    38.2% → 1.09060
    50.0% → 1.08600
    61.8% → 1.08140
    78.6% → 1.07484
   100.0% → 1.06650

Trực quan hóa mức Fibonacci với matplotlib

python

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

def plot_fibonacci(high, low, levels, symbol="Asset", trend="up"):
    fig, ax = plt.subplots(figsize=(10, 8))

    colors = {
        "0.0%": "#2c3e50",
        "23.6%": "#27ae60",
        "38.2%": "#2980b9",
        "50.0%": "#8e44ad",
        "61.8%": "#e74c3c",
        "78.6%": "#e67e22",
        "100.0%": "#2c3e50",
    }

    retracement = levels["retracement"]
    prices = list(retracement.values())
    price_min = min(prices) * 0.9995
    price_max = max(prices) * 1.0005

    for label, price in retracement.items():
        color = colors.get(label, "#95a5a6")
        ax.axhline(y=price, color=color, linewidth=1.5, linestyle="--", alpha=0.8)
        ax.text(1.02, price, f"{label}  {price:.5f}",
                transform=ax.get_yaxis_transform(),
                va="center", fontsize=9, color=color)

    ax.set_ylim(price_min, price_max)
    ax.set_title(f"Fibonacci Retracement — {symbol}", fontsize=13, fontweight="bold")
    ax.set_ylabel("Mức giá")
    ax.set_xlabel("Vùng phân tích")
    ax.set_xlim(0, 1)
    plt.tight_layout()
    plt.show()

Hàm fib_levels() là đơn vị tính toán cốt lõi của toàn bộ hệ thống. Mọi module phía sau, từ swing detection cho đến signal generation, đều gọi hàm này như một thành phần tiêu chuẩn.

Tích hợp công cụ tính Fibonacci vào pipeline Backtesting với dữ liệu OHLCV

Giai đoạn này biến công cụ tính Fibonacci thành một hệ thống backtesting thực sự. Thay vì tính toán thủ công từng sóng, pipeline sẽ tự động phát hiện swing, tính Fibonacci, và sinh tín hiệu giao dịch trên toàn bộ lịch sử giá.

Bước 1: Lấy dữ liệu OHLCV với yfinance

python

import yfinance as yf
import pandas as pd
import numpy as np
from scipy.signal import argrelextrema

# Tải dữ liệu
ticker = "EURUSD=X"
df = yf.download(ticker, start="2023-01-01", end="2024-12-31", interval="1d")
df = df[["Open", "High", "Low", "Close", "Volume"]].dropna()
df.index = pd.to_datetime(df.index)

print(f"Tổng số phiên: {len(df)}")
print(df.tail())

Bước 2: Tự động phát hiện Swing High và Swing Low

python

def detect_swings(df, order=5):
    """
    Phát hiện swing high và swing low bằng argrelextrema.

    Parameters:
        df (DataFrame): Dữ liệu OHLCV
        order (int): Số phiên mỗi bên để xác nhận swing (mặc định 5)

    Returns:
        DataFrame với cột 'swing_high' và 'swing_low'
    """
    df = df.copy()

    # Phát hiện swing high từ cột High
    swing_high_idx = argrelextrema(
        df["High"].values,
        comparator=np.greater,
        order=order
    )[0]

    # Phát hiện swing low từ cột Low
    swing_low_idx = argrelextrema(
        df["Low"].values,
        comparator=np.less,
        order=order
    )[0]

    df["swing_high"] = np.nan
    df["swing_low"] = np.nan

    df.iloc[swing_high_idx, df.columns.get_loc("swing_high")] = df["High"].iloc[swing_high_idx]
    df.iloc[swing_low_idx, df.columns.get_loc("swing_low")] = df["Low"].iloc[swing_low_idx]

    return df

df = detect_swings(df, order=5)
swing_highs = df["swing_high"].dropna()
swing_lows = df["swing_low"].dropna()

print(f"Số swing high phát hiện: {len(swing_highs)}")
print(f"Số swing low phát hiện: {len(swing_lows)}")

Tham số order=5 nghĩa là một điểm được coi là swing high khi cao hơn 5 phiên về mỗi phía. Giá trị này cần điều chỉnh tùy theo khung thời gian, với dữ liệu ngày thì order=5 tương đương một tuần giao dịch.

Bước 3: Áp dụng fib_levels() tự động trên toàn bộ lịch sử

python

def generate_fib_signals(df, lookback=20):
    """
    Tự động tạo mức Fibonacci cho từng cặp swing high/low gần nhất.

    Parameters:
        df (DataFrame): Dữ liệu đã có swing_high và swing_low
        lookback (int): Số phiên nhìn lại để tìm swing gần nhất

    Returns:
        List các dict chứa mức Fibonacci và thông tin sóng
    """
    signals = []

    for i in range(lookback, len(df)):
        window = df.iloc[i - lookback:i]

        recent_highs = window["swing_high"].dropna()
        recent_lows = window["swing_low"].dropna()

        if len(recent_highs) == 0 or len(recent_lows) == 0:
            continue

        latest_high_idx = recent_highs.index[-1]
        latest_low_idx = recent_lows.index[-1]

        high_val = recent_highs.iloc[-1]
        low_val = recent_lows.iloc[-1]

        # Xác định trend dựa trên thứ tự xuất hiện của swing
        trend = "up" if latest_low_idx < latest_high_idx else "down"

        levels = fib_levels(high=high_val, low=low_val, trend=trend)
        current_price = df["Close"].iloc[i]

        retracement = levels["retracement"]
        level_382 = retracement["38.2%"]
        level_618 = retracement["61.8%"]

        # Tín hiệu: Giá đang nằm trong vùng golden zone
        in_golden_zone = level_618 <= current_price <= level_382 if trend == "up" \
            else level_382 <= current_price <= level_618

        if in_golden_zone:
            signals.append({
                "date": df.index[i],
                "trend": trend,
                "swing_high": high_val,
                "swing_low": low_val,
                "current_price": current_price,
                "level_38.2": level_382,
                "level_61.8": level_618,
                "signal": "LONG" if trend == "up" else "SHORT",
            })

    return pd.DataFrame(signals)

signal_df = generate_fib_signals(df, lookback=20)
print(f"Tổng số tín hiệu: {len(signal_df)}")
print(signal_df.head(10))

Bước 4: Xây dựng logic xác nhận nến trước khi vào lệnh

python

def add_candle_confirmation(df, signal_df):
    """
    Lọc tín hiệu chỉ giữ lại những phiên nến có xác nhận đảo chiều.
    Ví dụ: nến bullish engulfing, hammer, hoặc close > open với biên độ đủ lớn.
    """
    confirmed = []

    for _, row in signal_df.iterrows():
        idx = df.index.get_loc(row["date"])
        if idx == 0:
            continue

        candle = df.iloc[idx]
        prev_candle = df.iloc[idx - 1]

        body = abs(candle["Close"] - candle["Open"])
        prev_body = abs(prev_candle["Close"] - prev_candle["Open"])

        # Xác nhận bullish engulfing cho LONG
        if row["signal"] == "LONG":
            bullish_engulf = (
                candle["Close"] > candle["Open"] and
                candle["Open"] < prev_candle["Close"] and
                candle["Close"] > prev_candle["Open"] and
                body > prev_body * 0.8
            )
            if bullish_engulf:
                confirmed.append(row)

        # Xác nhận bearish engulfing cho SHORT
        elif row["signal"] == "SHORT":
            bearish_engulf = (
                candle["Close"] < candle["Open"] and
                candle["Open"] > prev_candle["Close"] and
                candle["Close"] < prev_candle["Open"] and
                body > prev_body * 0.8
            )
            if bearish_engulf:
                confirmed.append(row)

    return pd.DataFrame(confirmed)

confirmed_signals = add_candle_confirmation(df, signal_df)
print(f"Tín hiệu sau khi lọc xác nhận nến: {len(confirmed_signals)}")

Pipeline hoàn chỉnh từ dữ liệu thô đến tín hiệu đã được xác nhận có thể chạy lại trên bất kỳ ticker nào, bất kỳ khung thời gian nào, mà không cần một thao tác vẽ tay nào. Đây chính là lợi thế cốt lõi của tự động hóa so với phương pháp thủ công.

Kiểm tra xác nhận sóng Elliott bằng Fibonacci trong Python: Quy tắc bắt buộc

Fibonacci và sóng Elliott không thể tách rời nhau trong phân tích kỹ thuật định lượng. Ba quy tắc cứng của lý thuyết Elliott có thể và cần được mã hóa thành điều kiện Python để loại bỏ hầu hết tín hiệu giả trước khi đưa vào backtesting. Đây là tầng lọc nâng cao mà phần lớn các hệ thống tự động bỏ qua.

Quy tắc 1: Sóng 2 không được retracement quá 100% sóng 1

Sóng 2 được phép retracement từ 38.2% đến 61.8% của sóng 1 theo hướng dẫn Fibonacci. Nếu sóng 2 chạm hoặc vượt qua điểm bắt đầu của sóng 1, tức retracement vượt 100%, toàn bộ wave count bị invalidate và cần đếm lại từ đầu.

python

def validate_wave2(wave1_start, wave1_end, wave2_end):
    """
    Kiểm tra sóng 2 không retracement quá 100% sóng 1.

    Parameters:
        wave1_start (float): Điểm bắt đầu sóng 1 (giá thấp nếu uptrend)
        wave1_end (float): Điểm kết thúc sóng 1 (giá cao nếu uptrend)
        wave2_end (float): Điểm kết thúc sóng 2

    Returns:
        tuple: (is_valid, retracement_ratio)
    """
    wave1_length = abs(wave1_end - wave1_start)
    wave2_retracement = abs(wave2_end - wave1_end)
    ratio = wave2_retracement / wave1_length if wave1_length > 0 else 0

    # Sóng 2 hợp lệ nếu retracement < 100% và không phá điểm bắt đầu sóng 1
    is_valid = wave2_end > wave1_start  # Dành cho uptrend

    return is_valid, round(ratio, 4)

# Ví dụ
valid, ratio = validate_wave2(wave1_start=100.0, wave1_end=120.0, wave2_end=112.0)
print(f"Sóng 2 hợp lệ: {valid} | Retracement: {ratio*100:.1f}%")
# Kết quả: Sóng 2 hợp lệ: True | Retracement: 40.0%

Quy tắc 2: Sóng 3 phải là sóng dài nhất trong số sóng 1, 3, 5

Sóng 3 thường có biên độ khoảng 1.618 lần sóng 1. Trong mọi trường hợp, sóng 3 không được ngắn hơn cả sóng 1 lẫn sóng 5 cùng lúc.

python

def validate_wave3(wave1_start, wave1_end, wave3_start, wave3_end,
                   wave5_start=None, wave5_end=None):
    """
    Kiểm tra sóng 3 không phải là sóng ngắn nhất.
    """
    wave1_length = abs(wave1_end - wave1_start)
    wave3_length = abs(wave3_end - wave3_start)

    # Kiểm tra sóng 3 dài hơn sóng 1 ít nhất
    longer_than_wave1 = wave3_length > wave1_length

    fib_extension_ratio = wave3_length / wave1_length if wave1_length > 0 else 0

    result = {
        "wave1_length": round(wave1_length, 5),
        "wave3_length": round(wave3_length, 5),
        "ratio_vs_wave1": round(fib_extension_ratio, 4),
        "longer_than_wave1": longer_than_wave1,
        "is_valid": longer_than_wave1,
    }

    # Nếu có dữ liệu sóng 5, kiểm tra thêm
    if wave5_start is not None and wave5_end is not None:
        wave5_length = abs(wave5_end - wave5_start)
        # Sóng 3 không được ngắn nhất: phải dài hơn ít nhất một trong hai (sóng 1 hoặc sóng 5)
        not_shortest = wave3_length >= min(wave1_length, wave5_length)
        result["wave5_length"] = round(wave5_length, 5)
        result["is_valid"] = not_shortest

    return result

# Ví dụ
info = validate_wave3(
    wave1_start=100, wave1_end=120,
    wave3_start=112, wave3_end=148
)
print(f"Sóng 3 hợp lệ: {info['is_valid']}")
print(f"Tỷ lệ sóng 3 / sóng 1: {info['ratio_vs_wave1']} ({info['ratio_vs_wave1']*100:.1f}%)")
# Kết quả: Sóng 3 hợp lệ: True | Tỷ lệ: 1.8 (180.0%)

Quy tắc 3: Sóng 4 không được chồng lên vùng giá sóng 1

Điểm thấp nhất của sóng 4 trong uptrend không được thấp hơn điểm cao nhất của sóng 1.

python

def validate_wave4(wave1_end, wave4_end, trend="up"):
    """
    Kiểm tra sóng 4 không chồng lên vùng giá sóng 1.

    Parameters:
        wave1_end (float): Đỉnh sóng 1 (uptrend) hoặc đáy sóng 1 (downtrend)
        wave4_end (float): Đáy sóng 4 (uptrend) hoặc đỉnh sóng 4 (downtrend)
        trend (str): "up" hoặc "down"

    Returns:
        bool: True nếu hợp lệ
    """
    if trend == "up":
        # Đáy sóng 4 phải cao hơn đỉnh sóng 1
        is_valid = wave4_end > wave1_end
    else:
        # Đỉnh sóng 4 phải thấp hơn đáy sóng 1
        is_valid = wave4_end < wave1_end

    return is_valid

# Ví dụ
valid = validate_wave4(wave1_end=120.0, wave4_end=133.5, trend="up")
print(f"Sóng 4 hợp lệ: {valid}")
# Kết quả: Sóng 4 hợp lệ: True

Hàm kiểm tra tổng hợp 5 sóng

python

def validate_impulse_wave(p0, p1, p2, p3, p4, p5):
    """
    Kiểm tra toàn bộ 3 quy tắc cứng cho một chuỗi sóng xung lực 5 sóng.

    Parameters:
        p0..p5 (float): Giá tại các điểm pivot 0 đến 5
        (p0: đầu sóng 1, p1: cuối sóng 1, p2: cuối sóng 2, ...)
    """
    rule1_valid, w2_ratio = validate_wave2(
        wave1_start=p0, wave1_end=p1, wave2_end=p2
    )
    rule2_info = validate_wave3(
        wave1_start=p0, wave1_end=p1,
        wave3_start=p2, wave3_end=p3,
        wave5_start=p4, wave5_end=p5
    )
    rule3_valid = validate_wave4(wave1_end=p1, wave4_end=p4, trend="up")

    all_valid = rule1_valid and rule2_info["is_valid"] and rule3_valid

    print("=" * 40)
    print(f"Quy tắc 1 (Sóng 2 < 100% Sóng 1): {'✓ HỢP LỆ' if rule1_valid else '✗ VI PHẠM'}")
    print(f"  → Sóng 2 retracement: {w2_ratio*100:.1f}%")
    print(f"Quy tắc 2 (Sóng 3 không ngắn nhất): {'✓ HỢP LỆ' if rule2_info['is_valid'] else '✗ VI PHẠM'}")
    print(f"  → Tỷ lệ Sóng 3 / Sóng 1: {rule2_info['ratio_vs_wave1']:.3f}x")
    print(f"Quy tắc 3 (Sóng 4 không chồng Sóng 1): {'✓ HỢP LỆ' if rule3_valid else '✗ VI PHẠM'}")
    print("=" * 40)
    print(f"KẾT LUẬN: {'✓ SÓNG HỢP LỆ' if all_valid else '✗ SÓNG KHÔNG HỢP LỆ'}")

    return all_valid

# Ví dụ thực chiến
validate_impulse_wave(
    p0=100.0,   # Đầu sóng 1
    p1=120.0,   # Đỉnh sóng 1
    p2=112.0,   # Đáy sóng 2 (retracement 40%)
    p3=148.0,   # Đỉnh sóng 3 (extension 180%)
    p4=133.5,   # Đáy sóng 4 (retracement 43.2% của sóng 3)
    p5=160.0    # Đỉnh sóng 5
)

Những sai lầm phổ biến khi tự động hóa tính Fibonacci và cách kiểm tra output

Tự động hóa tính Fibonacci giải quyết được vấn đề cảm xúc và tốc độ, nhưng tạo ra một nhóm lỗi mới mang tính hệ thống. Khi cùng một sai sót được áp dụng trên hàng nghìn dữ liệu, hậu quả nghiêm trọng hơn nhiều so với sai lầm đơn lẻ trong vẽ tay thủ công. Nhận biết và kiểm tra các lỗi này là phần không thể thiếu trong quy trình phát triển hệ thống.

Lỗi 1: Chọn sai pivot do tham số order không phù hợp

Hàm argrelextrema với tham số order quá nhỏ sẽ phát hiện quá nhiều swing không có ý nghĩa, trong khi order quá lớn sẽ bỏ qua các sóng thực sự quan trọng. Không có giá trị order chuẩn chung cho mọi loại tài sản và mọi khung thời gian.

python

def audit_swings(df, orders=[3, 5, 8, 13]):
    """Kiểm tra số lượng swing phát hiện với các giá trị order khác nhau."""
    print(f"{'Order':>8} | {'Swing High':>12} | {'Swing Low':>10}")
    print("-" * 38)
    for order in orders:
        temp = detect_swings(df, order=order)
        n_high = temp["swing_high"].dropna().__len__()
        n_low = temp["swing_low"].dropna().__len__()
        print(f"{order:>8} | {n_high:>12} | {n_low:>10}")

Trader cần chạy hàm kiểm tra này trước khi tích hợp vào pipeline. Nếu số swing high gấp đôi số swing low, đó là dấu hiệu tham số order đang quá nhỏ và đang bắt nhiễu.

Lỗi 2: Dữ liệu thiếu phiên hoặc có giá trị NaN

Dữ liệu từ yfinance hoặc các nguồn khác đôi khi có phiên bị thiếu do ngày lễ hoặc lỗi API. Một chuỗi giá bị thiếu phiên giữa chừng sẽ khiến argrelextrema trả về index lệch và mức Fibonacci tính ra bị sai hoàn toàn.

python

def check_data_quality(df):
    """Kiểm tra chất lượng dữ liệu trước khi đưa vào pipeline."""
    issues = []

    nan_count = df[["Open", "High", "Low", "Close"]].isna().sum().sum()
    if nan_count > 0:
        issues.append(f"Có {nan_count} giá trị NaN trong OHLC")

    # Kiểm tra High >= Low
    invalid_candles = (df["High"] < df["Low"]).sum()
    if invalid_candles > 0:
        issues.append(f"Có {invalid_candles} nến có High < Low (dữ liệu lỗi)")

    # Kiểm tra khoảng cách ngày bất thường
    if isinstance(df.index, pd.DatetimeIndex):
        gaps = df.index.to_series().diff().dt.days
        large_gaps = gaps[gaps > 7].count()
        if large_gaps > 0:
            issues.append(f"Có {large_gaps} khoảng cách > 7 ngày (có thể thiếu dữ liệu)")

    if not issues:
        print("✓ Dữ liệu sạch, sẵn sàng đưa vào pipeline")
    else:
        for issue in issues:
            print(f"✗ {issue}")

    return len(issues) == 0

Lỗi 3: Không điều chỉnh múi giờ khi backtesting Forex

Dữ liệu Forex thường được lưu theo giờ UTC. Khi nến ngày kết thúc lúc 22:00 UTC thay vì 00:00, điểm đóng cửa của phiên New York và điểm mở cửa của phiên châu Á sẽ nằm sai nến. Tính Fibonacci từ High/Low của nến sai phiên là một trong những lỗi thầm lặng khó phát hiện nhất.

python

import pytz

def normalize_timezone(df, source_tz="UTC", target_tz="Asia/Ho_Chi_Minh"):
    """Chuẩn hóa múi giờ dữ liệu Forex về múi giờ mục tiêu."""
    if df.index.tzinfo is None:
        df.index = df.index.tz_localize(source_tz)
    df.index = df.index.tz_convert(target_tz)
    return df

Checklist kiểm tra output trước khi đưa tín hiệu vào hệ thống thực

  • Số lượng swing phát hiện có hợp lý so với số nến không (tỷ lệ tham khảo: 1 swing / 8 đến 15 nến)
  • Mức 61.8% và 38.2% có nằm trong range High/Low không (nếu ngoài range thì công thức sai)
  • Không có giá trị NaN hoặc Inf trong output của fib_levels()
  • Tín hiệu LONG chỉ xuất hiện khi trend xác định là “up” và ngược lại
  • Múi giờ của timestamp trong signal_df khớp với múi giờ của dữ liệu nguồn
  • Ba quy tắc Elliott đã được kiểm tra và tất cả trả về True trước khi lưu tín hiệu
  • Backtesting đã chạy trên dữ liệu out-of-sample (dữ liệu ngoài khoảng huấn luyện) để tránh overfitting

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *