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.

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ức | Nguồn gốc | Vai 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 Dow | Mứ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.618 | Extension mức đầu tiên |
| 161.8% | Tỷ lệ vàng | Extension mục tiêu sóng 3 và sóng 5 |
| 261.8% | 1.618 bình phương | Extension 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.

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 a và b 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ần | O(2^n) | O(n) stack | Không |
| Vòng lặp | O(n) | O(1) | Có, ưu tiên số một |
| Memoization | O(n) lần đầu, O(1) sau | O(n) cache | Có, 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 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
