Phát hiện mẫu nến Nhật bằng Python cho phân tích kỹ thuật
2024-03-17 — QuantTrade
Giới thiệu
Biểu đồ nến Nhật là một trong những công cụ phân tích kỹ thuật lâu đời và hiệu quả nhất được sử dụng bởi các nhà giao dịch. Được phát triển từ thế kỷ 18 bởi Munehisa Homma, một thương nhân gạo Nhật Bản, các mẫu nến Nhật đã trở thành trung tâm của việc phân tích kỹ thuật hiện đại. Mỗi nến đại diện cho bốn mức giá quan trọng trong một khoảng thời gian: giá mở cửa, giá cao nhất, giá thấp nhất và giá đóng cửa.
Việc phát hiện tự động các mẫu nến Nhật bằng Python có thể giúp các nhà giao dịch xác định các điểm vào và ra tiềm năng, cũng như các điểm đảo chiều và xu hướng thị trường. Bài viết này sẽ hướng dẫn bạn cách sử dụng Python để phát hiện và phân tích các mẫu nến Nhật phổ biến trong dữ liệu tài chính.
Hiểu về cấu trúc nến Nhật

Trước khi bắt đầu phát hiện mẫu, chúng ta cần hiểu rõ cấu trúc cơ bản của một nến Nhật:
Thân nến (Body): Hình chữ nhật đại diện cho sự chênh lệch giữa giá mở cửa và giá đóng cửa
- Nến tăng (Bullish): Giá đóng cửa cao hơn giá mở cửa, thường được tô màu xanh lá hoặc trắng
- Nến giảm (Bearish): Giá đóng cửa thấp hơn giá mở cửa, thường được tô màu đỏ hoặc đen
Bóng nến (Shadow/Wick): Đường thẳng phía trên và phía dưới thân nến
- Bóng trên (Upper Shadow): Thể hiện giá cao nhất trong khoảng thời gian
- Bóng dưới (Lower Shadow): Thể hiện giá thấp nhất trong khoảng thời gian
Mỗi nến kể một câu chuyện về cuộc chiến giữa người mua và người bán trong khoảng thời gian đó.
Cài đặt các thư viện cần thiết
Để phát hiện mẫu nến Nhật bằng Python, chúng ta sẽ sử dụng các thư viện sau:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import mplfinance as mpf
import yfinance as yf
from datetime import datetime, timedelta
- pandas: Xử lý dữ liệu dạng bảng
- numpy: Tính toán số học
- matplotlib: Vẽ biểu đồ cơ bản
- mplfinance: Vẽ biểu đồ tài chính
- yfinance: Tải dữ liệu từ Yahoo Finance
Tải dữ liệu cổ phiếu
Đầu tiên, chúng ta cần lấy dữ liệu OHLC (Open, High, Low, Close) để phân tích:
def get_stock_data(ticker, period="1y"):
"""
Tải dữ liệu cổ phiếu từ Yahoo Finance
Parameters:
----------
ticker : str
Mã cổ phiếu
period : str
Khoảng thời gian (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)
Returns:
-------
DataFrame
Dữ liệu OHLC của cổ phiếu
"""
data = yf.download(ticker, period=period)
return data
Ví dụ sử dụng:
# Tải dữ liệu VNIndex trong 1 năm gần nhất
df = get_stock_data("^VNINDEX", period="1y")
print(df.head())
Phát hiện mẫu nến Nhật đơn
1. Mẫu nến Doji

Nến Doji hình thành khi giá mở cửa và giá đóng cửa gần như bằng nhau, tạo ra một thân nến rất nhỏ. Đây là dấu hiệu cho thấy sự do dự của thị trường và thường báo hiệu điểm đảo chiều tiềm năng.
def detect_doji(df, doji_threshold=0.1):
"""
Phát hiện mẫu nến Doji
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
doji_threshold : float
Ngưỡng để xác định Doji (% so với biên độ giá)
Returns:
-------
Series
True nếu là Doji, False nếu không phải
"""
df = df.copy()
# Tính toán chiều dài thân nến
df['body'] = abs(df['Close'] - df['Open'])
# Tính toán biên độ giá (high - low)
df['range'] = df['High'] - df['Low']
# Xác định Doji khi thân nến rất nhỏ
doji = df['body'] <= (df['range'] * doji_threshold)
return doji
2. Mẫu nến Hammer và Hanging Man
Nến Hammer (Búa) và Hanging Man (Người treo cổ) có hình dạng giống nhau với thân nhỏ và bóng dưới dài. Tuy nhiên, chúng xuất hiện ở các vị trí khác nhau và có ý nghĩa ngược nhau:
- Hammer: Xuất hiện sau xu hướng giảm và báo hiệu đảo chiều tăng
- Hanging Man: Xuất hiện sau xu hướng tăng và báo hiệu đảo chiều giảm
def detect_hammer(df, body_pct=0.25, shadow_pct=2.0):
"""
Phát hiện mẫu nến Hammer (Búa) hoặc Hanging Man (Người treo cổ)
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
body_pct : float
Tỷ lệ thân nến tối đa so với tổng chiều dài nến
shadow_pct : float
Tỷ lệ bóng dưới tối thiểu so với thân nến
Returns:
-------
Series
1 nếu là Hammer, -1 nếu là Hanging Man, 0 nếu không phải
"""
df = df.copy()
# Xác định chiều hướng nến (tăng/giảm)
df['bullish'] = df['Close'] >= df['Open']
# Tính chiều dài thân nến
df['body'] = abs(df['Close'] - df['Open'])
# Tính bóng trên và bóng dưới
df['upper_shadow'] = df.apply(
lambda x: x['High'] - x['Close'] if x['bullish'] else x['High'] - x['Open'],
axis=1
)
df['lower_shadow'] = df.apply(
lambda x: x['Open'] - x['Low'] if x['bullish'] else x['Close'] - x['Low'],
axis=1
)
# Tính tổng chiều dài nến
df['total_length'] = df['High'] - df['Low']
# Điều kiện cho Hammer/Hanging Man:
# 1. Thân nhỏ (dưới body_pct của tổng chiều dài)
small_body = df['body'] <= (df['total_length'] * body_pct)
# 2. Bóng dưới dài (ít nhất shadow_pct lần thân nến)
long_lower_shadow = df['lower_shadow'] >= (df['body'] * shadow_pct)
# 3. Bóng trên rất nhỏ hoặc không có
small_upper_shadow = df['upper_shadow'] <= (df['body'] * 0.5)
# Xác định Hammer và Hanging Man
pattern = np.zeros(len(df))
# Lấy trung bình động 5 ngày để xác định xu hướng
df['trend'] = df['Close'].rolling(window=5).mean().diff().fillna(0)
# Hammer xuất hiện trong xu hướng giảm
hammer_idx = (small_body & long_lower_shadow & small_upper_shadow & (df['trend'] < 0))
pattern[hammer_idx] = 1
# Hanging Man xuất hiện trong xu hướng tăng
hanging_man_idx = (small_body & long_lower_shadow & small_upper_shadow & (df['trend'] > 0))
pattern[hanging_man_idx] = -1
return pd.Series(pattern, index=df.index)
3. Mẫu nến Shooting Star và Inverted Hammer
Tương tự Hammer và Hanging Man, Shooting Star (Sao Băng) và Inverted Hammer (Búa Ngược) có hình dạng giống nhau với thân nhỏ và bóng trên dài, nhưng xuất hiện ở các vị trí khác nhau:
- Inverted Hammer: Xuất hiện sau xu hướng giảm và báo hiệu đảo chiều tăng
- Shooting Star: Xuất hiện sau xu hướng tăng và báo hiệu đảo chiều giảm
def detect_shooting_star(df, body_pct=0.25, shadow_pct=2.0):
"""
Phát hiện mẫu nến Shooting Star (Sao Băng) hoặc Inverted Hammer (Búa Ngược)
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
body_pct : float
Tỷ lệ thân nến tối đa so với tổng chiều dài nến
shadow_pct : float
Tỷ lệ bóng trên tối thiểu so với thân nến
Returns:
-------
Series
1 nếu là Inverted Hammer, -1 nếu là Shooting Star, 0 nếu không phải
"""
df = df.copy()
# Xác định chiều hướng nến (tăng/giảm)
df['bullish'] = df['Close'] >= df['Open']
# Tính chiều dài thân nến
df['body'] = abs(df['Close'] - df['Open'])
# Tính bóng trên và bóng dưới
df['upper_shadow'] = df.apply(
lambda x: x['High'] - x['Close'] if x['bullish'] else x['High'] - x['Open'],
axis=1
)
df['lower_shadow'] = df.apply(
lambda x: x['Open'] - x['Low'] if x['bullish'] else x['Close'] - x['Low'],
axis=1
)
# Tính tổng chiều dài nến
df['total_length'] = df['High'] - df['Low']
# Điều kiện cho Shooting Star/Inverted Hammer:
# 1. Thân nhỏ (dưới body_pct của tổng chiều dài)
small_body = df['body'] <= (df['total_length'] * body_pct)
# 2. Bóng trên dài (ít nhất shadow_pct lần thân nến)
long_upper_shadow = df['upper_shadow'] >= (df['body'] * shadow_pct)
# 3. Bóng dưới rất nhỏ hoặc không có
small_lower_shadow = df['lower_shadow'] <= (df['body'] * 0.5)
# Xác định Inverted Hammer và Shooting Star
pattern = np.zeros(len(df))
# Lấy trung bình động 5 ngày để xác định xu hướng
df['trend'] = df['Close'].rolling(window=5).mean().diff().fillna(0)
# Inverted Hammer xuất hiện trong xu hướng giảm
inverted_hammer_idx = (small_body & long_upper_shadow & small_lower_shadow & (df['trend'] < 0))
pattern[inverted_hammer_idx] = 1
# Shooting Star xuất hiện trong xu hướng tăng
shooting_star_idx = (small_body & long_upper_shadow & small_lower_shadow & (df['trend'] > 0))
pattern[shooting_star_idx] = -1
return pd.Series(pattern, index=df.index)
4. Mẫu nến Engulfing
Mẫu nến Engulfing (Nuốt chửng) là mẫu hai nến, trong đó nến thứ hai hoàn toàn "nuốt chửng" thân nến đầu tiên:
- Bullish Engulfing: Nến tăng (xanh) thứ hai nuốt chửng nến giảm (đỏ) đầu tiên
- Bearish Engulfing: Nến giảm (đỏ) thứ hai nuốt chửng nến tăng (xanh) đầu tiên
def detect_engulfing(df):
"""
Phát hiện mẫu nến Engulfing (Nuốt chửng)
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
Returns:
-------
Series
1 nếu là Bullish Engulfing, -1 nếu là Bearish Engulfing, 0 nếu không phải
"""
df = df.copy()
# Xác định chiều hướng nến
df['bullish'] = df['Close'] > df['Open']
df['bearish'] = df['Close'] < df['Open']
# Shift để so sánh với nến trước đó
df['prev_bullish'] = df['bullish'].shift(1)
df['prev_bearish'] = df['bearish'].shift(1)
df['prev_open'] = df['Open'].shift(1)
df['prev_close'] = df['Close'].shift(1)
pattern = np.zeros(len(df))
# Bullish Engulfing: nến hiện tại tăng, nến trước giảm, và thân nến hiện tại nuốt chửng nến trước
bullish_engulfing = (
df['bullish'] &
df['prev_bearish'] &
(df['Open'] <= df['prev_close']) &
(df['Close'] >= df['prev_open'])
)
# Bearish Engulfing: nến hiện tại giảm, nến trước tăng, và thân nến hiện tại nuốt chửng nến trước
bearish_engulfing = (
df['bearish'] &
df['prev_bullish'] &
(df['Open'] >= df['prev_close']) &
(df['Close'] <= df['prev_open'])
)
pattern[bullish_engulfing] = 1
pattern[bearish_engulfing] = -1
return pd.Series(pattern, index=df.index)
Phát hiện mẫu nến Nhật phức tạp
1. Mẫu nến Morning Star và Evening Star
Morning Star (Sao Mai) và Evening Star (Sao Hôm) là các mẫu ba nến báo hiệu đảo chiều:
- Morning Star: Mẫu đảo chiều tăng, gồm nến giảm dài, nến thân nhỏ, và nến tăng dài
- Evening Star: Mẫu đảo chiều giảm, gồm nến tăng dài, nến thân nhỏ, và nến giảm dài
def detect_morning_evening_star(df, body_pct=0.3, middle_body_pct=0.15):
"""
Phát hiện mẫu nến Morning Star (Sao Mai) và Evening Star (Sao Hôm)
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
body_pct : float
Tỷ lệ tối thiểu của thân nến đầu và cuối so với biên độ (range)
middle_body_pct : float
Tỷ lệ tối đa của thân nến giữa so với biên độ (range)
Returns:
-------
Series
1 nếu là Morning Star, -1 nếu là Evening Star, 0 nếu không phải
"""
df = df.copy()
# Tính chiều dài thân nến
df['body'] = abs(df['Close'] - df['Open'])
# Tính biên độ giá (high - low)
df['range'] = df['High'] - df['Low']
# Tính tỷ lệ thân nến so với biên độ
df['body_ratio'] = df['body'] / df['range']
# Xác định nến tăng/giảm
df['bullish'] = df['Close'] > df['Open']
df['bearish'] = df['Close'] < df['Open']
# Shift để so sánh với các nến trước đó
df['prev_body_ratio'] = df['body_ratio'].shift(1)
df['prev_bullish'] = df['bullish'].shift(1)
df['prev_bearish'] = df['bearish'].shift(1)
df['prev2_body_ratio'] = df['body_ratio'].shift(2)
df['prev2_bullish'] = df['bullish'].shift(2)
df['prev2_bearish'] = df['bearish'].shift(2)
pattern = np.zeros(len(df))
# Morning Star
morning_star = (
# Nến đầu tiên: Giảm với thân lớn
df['prev2_bearish'] &
(df['prev2_body_ratio'] >= body_pct) &
# Nến giữa: Thân nhỏ (gap xuống so với nến đầu)
(df['prev_body_ratio'] <= middle_body_pct) &
# Nến cuối: Tăng với thân lớn (xâm chiếm vào thân nến đầu tiên)
df['bullish'] &
(df['body_ratio'] >= body_pct) &
(df['Close'] > (df['Open'].shift(2) + df['Close'].shift(2)) / 2)
)
# Evening Star
evening_star = (
# Nến đầu tiên: Tăng với thân lớn
df['prev2_bullish'] &
(df['prev2_body_ratio'] >= body_pct) &
# Nến giữa: Thân nhỏ (gap lên so với nến đầu)
(df['prev_body_ratio'] <= middle_body_pct) &
# Nến cuối: Giảm với thân lớn (xâm chiếm vào thân nến đầu tiên)
df['bearish'] &
(df['body_ratio'] >= body_pct) &
(df['Close'] < (df['Open'].shift(2) + df['Close'].shift(2)) / 2)
)
pattern[morning_star] = 1
pattern[evening_star] = -1
return pd.Series(pattern, index=df.index)
2. Mẫu nến Three White Soldiers và Three Black Crows
Ba Binh Sĩ Trắng (Three White Soldiers) và Ba Con Quạ Đen (Three Black Crows) là các mẫu ba nến liên tiếp cùng chiều, báo hiệu xu hướng mạnh mẽ:
- Three White Soldiers: Ba nến tăng liên tiếp, mỗi nến mở cửa trong thân nến trước và đóng cửa cao hơn
- Three Black Crows: Ba nến giảm liên tiếp, mỗi nến mở cửa trong thân nến trước và đóng cửa thấp hơn
def detect_three_soldiers_crows(df, body_pct=0.5):
"""
Phát hiện mẫu nến Three White Soldiers (Ba Binh Sĩ Trắng) và Three Black Crows (Ba Con Quạ Đen)
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
body_pct : float
Tỷ lệ tối thiểu của thân nến so với biên độ
Returns:
-------
Series
1 nếu là Three White Soldiers, -1 nếu là Three Black Crows, 0 nếu không phải
"""
df = df.copy()
# Tính chiều dài thân nến
df['body'] = abs(df['Close'] - df['Open'])
# Tính biên độ giá (high - low)
df['range'] = df['High'] - df['Low']
# Tính tỷ lệ thân nến so với biên độ
df['body_ratio'] = df['body'] / df['range']
# Xác định nến tăng/giảm
df['bullish'] = df['Close'] > df['Open']
df['bearish'] = df['Close'] < df['Open']
# Shift để so sánh với các nến trước đó
for i in range(1, 3):
df[f'prev{i}_open'] = df['Open'].shift(i)
df[f'prev{i}_close'] = df['Close'].shift(i)
df[f'prev{i}_bullish'] = df['bullish'].shift(i)
df[f'prev{i}_bearish'] = df['bearish'].shift(i)
df[f'prev{i}_body_ratio'] = df['body_ratio'].shift(i)
pattern = np.zeros(len(df))
# Three White Soldiers
three_white_soldiers = (
# Ba nến tăng liên tiếp
df['bullish'] & df['prev1_bullish'] & df['prev2_bullish'] &
# Thân nến đủ lớn
(df['body_ratio'] >= body_pct) &
(df['prev1_body_ratio'] >= body_pct) &
(df['prev2_body_ratio'] >= body_pct) &
# Mỗi nến mở cửa trong thân nến trước và đóng cửa cao hơn
(df['Open'] > df['prev1_open']) &
(df['Open'] < df['prev1_close']) &
(df['Close'] > df['prev1_close']) &
(df['prev1_open'] > df['prev2_open']) &
(df['prev1_open'] < df['prev2_close']) &
(df['prev1_close'] > df['prev2_close'])
)
# Three Black Crows
three_black_crows = (
# Ba nến giảm liên tiếp
df['bearish'] & df['prev1_bearish'] & df['prev2_bearish'] &
# Thân nến đủ lớn
(df['body_ratio'] >= body_pct) &
(df['prev1_body_ratio'] >= body_pct) &
(df['prev2_body_ratio'] >= body_pct) &
# Mỗi nến mở cửa trong thân nến trước và đóng cửa thấp hơn
(df['Open'] < df['prev1_open']) &
(df['Open'] > df['prev1_close']) &
(df['Close'] < df['prev1_close']) &
(df['prev1_open'] < df['prev2_open']) &
(df['prev1_open'] > df['prev2_close']) &
(df['prev1_close'] < df['prev2_close'])
)
pattern[three_white_soldiers] = 1
pattern[three_black_crows] = -1
return pd.Series(pattern, index=df.index)
Tổng hợp tất cả các mẫu nến
Chúng ta có thể tạo một hàm để phát hiện tất cả các mẫu nến đã định nghĩa:
def detect_all_patterns(df):
"""
Phát hiện tất cả các mẫu nến đã định nghĩa
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
Returns:
-------
DataFrame
DataFrame với các cột là các mẫu nến đã phát hiện
"""
patterns = pd.DataFrame(index=df.index)
# Phát hiện các mẫu nến đơn
patterns['doji'] = detect_doji(df)
patterns['hammer_hanging_man'] = detect_hammer(df)
patterns['shooting_star_inverted_hammer'] = detect_shooting_star(df)
# Phát hiện các mẫu nến kép
patterns['engulfing'] = detect_engulfing(df)
# Phát hiện các mẫu nến phức tạp
patterns['morning_evening_star'] = detect_morning_evening_star(df)
patterns['three_soldiers_crows'] = detect_three_soldiers_crows(df)
return patterns
Vẽ biểu đồ với các mẫu nến đã phát hiện
Để trực quan hóa các mẫu nến đã phát hiện, chúng ta có thể sử dụng mplfinance:
def plot_candlestick_patterns(df, patterns, title='Phát hiện mẫu nến', num_bars=120):
"""
Vẽ biểu đồ nến với các mẫu nến đã phát hiện
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
patterns : DataFrame
DataFrame với các cột là các mẫu nến đã phát hiện
title : str
Tiêu đề biểu đồ
num_bars : int
Số nến hiển thị
"""
df = df.copy().iloc[-num_bars:]
# Chuẩn bị dữ liệu để đánh dấu
markers = []
# Đánh dấu Doji
if 'doji' in patterns.columns:
doji_dates = df.index[patterns['doji'].iloc[-num_bars:]]
if len(doji_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['High'][doji_dates] * 1.02, index=doji_dates),
type='scatter', marker='^', markersize=50, color='blue'))
# Đánh dấu Hammer (tín hiệu tăng)
if 'hammer_hanging_man' in patterns.columns:
hammer_dates = df.index[patterns['hammer_hanging_man'].iloc[-num_bars:] == 1]
if len(hammer_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['Low'][hammer_dates] * 0.98, index=hammer_dates),
type='scatter', marker='^', markersize=70, color='g'))
# Đánh dấu Hanging Man (tín hiệu giảm)
if 'hammer_hanging_man' in patterns.columns:
hanging_man_dates = df.index[patterns['hammer_hanging_man'].iloc[-num_bars:] == -1]
if len(hanging_man_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['High'][hanging_man_dates] * 1.02, index=hanging_man_dates),
type='scatter', marker='v', markersize=70, color='r'))
# Đánh dấu Bullish Engulfing
if 'engulfing' in patterns.columns:
bullish_engulfing_dates = df.index[patterns['engulfing'].iloc[-num_bars:] == 1]
if len(bullish_engulfing_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['Low'][bullish_engulfing_dates] * 0.97, index=bullish_engulfing_dates),
type='scatter', marker='^', markersize=120, color='g'))
# Đánh dấu Bearish Engulfing
if 'engulfing' in patterns.columns:
bearish_engulfing_dates = df.index[patterns['engulfing'].iloc[-num_bars:] == -1]
if len(bearish_engulfing_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['High'][bearish_engulfing_dates] * 1.03, index=bearish_engulfing_dates),
type='scatter', marker='v', markersize=120, color='r'))
# Đánh dấu Morning Star
if 'morning_evening_star' in patterns.columns:
morning_star_dates = df.index[patterns['morning_evening_star'].iloc[-num_bars:] == 1]
if len(morning_star_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['Low'][morning_star_dates] * 0.96, index=morning_star_dates),
type='scatter', marker='*', markersize=200, color='g'))
# Đánh dấu Evening Star
if 'morning_evening_star' in patterns.columns:
evening_star_dates = df.index[patterns['morning_evening_star'].iloc[-num_bars:] == -1]
if len(evening_star_dates) > 0:
markers.append(mpf.make_addplot(pd.Series(df['High'][evening_star_dates] * 1.04, index=evening_star_dates),
type='scatter', marker='*', markersize=200, color='r'))
# Cài đặt style cho biểu đồ
mc = mpf.make_marketcolors(up='g', down='r', inherit=True)
s = mpf.make_mpf_style(base_mpf_style='yahoo', marketcolors=mc)
# Vẽ biểu đồ
mpf.plot(df, type='candle', style=s, title=title, figsize=(12, 8),
volume=True, addplot=markers if markers else None)
Đánh giá hiệu quả của mẫu nến
Để đánh giá hiệu quả của mỗi mẫu nến trong việc dự báo biến động giá trong tương lai, chúng ta cần phân tích xem liệu sau khi xuất hiện mẫu nến, giá có diễn biến theo hướng dự báo hay không:
def evaluate_pattern_performance(df, patterns, forward_periods=[1, 3, 5, 10]):
"""
Đánh giá hiệu quả của các mẫu nến
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
patterns : DataFrame
DataFrame với các cột là các mẫu nến đã phát hiện
forward_periods : list
Danh sách các khoảng thời gian tương lai để đánh giá
Returns:
-------
DataFrame
DataFrame với tỷ lệ thành công của mỗi mẫu nến
"""
results = {}
for pattern in patterns.columns:
pattern_results = {}
# Đánh giá tín hiệu tăng (1)
bullish_signals = patterns[pattern] == 1
if bullish_signals.any():
bullish_success_rates = {}
for period in forward_periods:
# Tính % thay đổi giá trong khoảng thời gian tương lai
future_returns = df['Close'].pct_change(period).shift(-period)
# Tín hiệu thành công nếu giá tăng
success = (future_returns[bullish_signals] > 0).mean()
bullish_success_rates[f"{period}_days"] = success
pattern_results["bullish"] = bullish_success_rates
# Đánh giá tín hiệu giảm (-1)
bearish_signals = patterns[pattern] == -1
if bearish_signals.any():
bearish_success_rates = {}
for period in forward_periods:
# Tính % thay đổi giá trong khoảng thời gian tương lai
future_returns = df['Close'].pct_change(period).shift(-period)
# Tín hiệu thành công nếu giá giảm
success = (future_returns[bearish_signals] < 0).mean()
bearish_success_rates[f"{period}_days"] = success
pattern_results["bearish"] = bearish_success_rates
results[pattern] = pattern_results
return results
Ứng dụng thực tế: Chiến lược giao dịch dựa trên mẫu nến
Từ các mẫu nến đã phát hiện, chúng ta có thể xây dựng một chiến lược giao dịch đơn giản:
def backtest_candlestick_strategy(df, patterns, initial_capital=100000):
"""
Backtest chiến lược giao dịch dựa trên mẫu nến
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
patterns : DataFrame
DataFrame với các cột là các mẫu nến đã phát hiện
initial_capital : float
Vốn ban đầu
Returns:
-------
DataFrame
DataFrame với kết quả backtest
"""
results = df[['Close']].copy()
# Khởi tạo tín hiệu
results['signal'] = 0
# Tạo tín hiệu từ các mẫu nến
for pattern in patterns.columns:
# Mẫu nến tăng
results.loc[patterns[pattern] == 1, 'signal'] = 1
# Mẫu nến giảm
results.loc[patterns[pattern] == -1, 'signal'] = -1
# Loại bỏ tín hiệu mâu thuẫn (giữ tín hiệu đầu tiên)
for i in range(1, len(results)):
if results['signal'].iloc[i] != 0 and results['signal'].iloc[i-1] == results['signal'].iloc[i]:
results['signal'].iloc[i] = 0
# Tính toán vị thế
results['position'] = results['signal'].cumsum()
# Giới hạn vị thế từ -1 đến 1 (short hoặc long)
results['position'] = results['position'].clip(-1, 1)
# Tính lợi nhuận hàng ngày
results['returns'] = results['Close'].pct_change()
results['strategy_returns'] = results['position'].shift(1) * results['returns']
# Tính vốn
results['capital'] = initial_capital * (1 + results['strategy_returns']).cumprod()
# Tính drawdown
results['peak'] = results['capital'].cummax()
results['drawdown'] = (results['capital'] - results['peak']) / results['peak']
return results
Ví dụ sử dụng
Tích hợp tất cả các phần đã phát triển ở trên:
if __name__ == "__main__":
# Tải dữ liệu
df = get_stock_data("AAPL", period="1y")
# Phát hiện các mẫu nến
patterns = detect_all_patterns(df)
# Vẽ biểu đồ với các mẫu nến đã phát hiện
plot_candlestick_patterns(df, patterns, title='Mẫu nến Nhật trên cổ phiếu AAPL')
# Đánh giá hiệu quả của các mẫu nến
performance = evaluate_pattern_performance(df, patterns)
print("Hiệu quả của các mẫu nến:")
print(pd.DataFrame(performance))
# Backtest chiến lược
results = backtest_candlestick_strategy(df, patterns)
# Vẽ biểu đồ kết quả
plt.figure(figsize=(12, 6))
plt.plot(results['capital'])
plt.title('Kết quả Backtest Chiến lược Mẫu nến Nhật')
plt.xlabel('Ngày')
plt.ylabel('Vốn')
plt.grid(True)
plt.show()
# In thống kê hiệu suất
total_return = (results['capital'].iloc[-1] / results['capital'].iloc[0] - 1) * 100
annualized_return = (1 + total_return/100) ** (252 / len(results)) - 1
max_drawdown = results['drawdown'].min() * 100
sharpe_ratio = results['strategy_returns'].mean() / results['strategy_returns'].std() * (252 ** 0.5)
print(f"Tổng lợi nhuận: {total_return:.2f}%")
print(f"Lợi nhuận hàng năm: {annualized_return*100:.2f}%")
print(f"Drawdown tối đa: {max_drawdown:.2f}%")
print(f"Tỷ số Sharpe: {sharpe_ratio:.2f}")
Cải tiến và nâng cao
1. Kết hợp với các chỉ báo kỹ thuật khác
Mẫu nến Nhật thường hiệu quả hơn khi được kết hợp với các chỉ báo kỹ thuật khác như RSI, MACD, hay Bollinger Bands:
def add_technical_indicators(df):
"""
Thêm các chỉ báo kỹ thuật phổ biến vào dữ liệu
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
Returns:
-------
DataFrame
DataFrame với các chỉ báo kỹ thuật đã thêm
"""
df = df.copy()
# Thêm SMA
df['SMA20'] = df['Close'].rolling(window=20).mean()
df['SMA50'] = df['Close'].rolling(window=50).mean()
# Thêm RSI
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
# Thêm MACD
ema12 = df['Close'].ewm(span=12, adjust=False).mean()
ema26 = df['Close'].ewm(span=26, adjust=False).mean()
df['MACD'] = ema12 - ema26
df['Signal_Line'] = df['MACD'].ewm(span=9, adjust=False).mean()
# Thêm Bollinger Bands
df['BB_Middle'] = df['Close'].rolling(window=20).mean()
df['BB_Std'] = df['Close'].rolling(window=20).std()
df['BB_Upper'] = df['BB_Middle'] + 2 * df['BB_Std']
df['BB_Lower'] = df['BB_Middle'] - 2 * df['BB_Std']
return df
2. Lọc mẫu nến bằng khối lượng giao dịch
Khối lượng giao dịch là một yếu tố quan trọng để xác nhận tín hiệu từ mẫu nến:
def filter_patterns_by_volume(df, patterns, volume_threshold=1.5):
"""
Lọc mẫu nến dựa trên khối lượng giao dịch
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
patterns : DataFrame
DataFrame với các mẫu nến đã phát hiện
volume_threshold : float
Ngưỡng khối lượng so với trung bình 10 ngày
Returns:
-------
DataFrame
DataFrame với các mẫu nến đã lọc
"""
# Tính khối lượng trung bình 10 ngày
df['avg_volume'] = df['Volume'].rolling(window=10).mean()
# Tạo một bản sao để không ảnh hưởng đến patterns gốc
filtered_patterns = patterns.copy()
# Lọc theo khối lượng
for col in filtered_patterns.columns:
# Chỉ giữ lại các mẫu nến khi khối lượng giao dịch cao hơn ngưỡng
is_pattern = filtered_patterns[col] != 0
is_high_volume = df['Volume'] > (df['avg_volume'] * volume_threshold)
# Reset các mẫu không thỏa điều kiện về khối lượng
filtered_patterns.loc[is_pattern & ~is_high_volume, col] = 0
return filtered_patterns
3. Xếp hạng độ tin cậy của mẫu nến
Không phải tất cả các mẫu nến đều có độ tin cậy như nhau. Chúng ta có thể xếp hạng dựa trên tỷ lệ thành công trong quá khứ:
def rank_patterns_by_reliability(df, patterns, lookback_period=120):
"""
Xếp hạng mẫu nến dựa trên độ tin cậy trong quá khứ
Parameters:
----------
df : DataFrame
Dữ liệu OHLC
patterns : DataFrame
DataFrame với các mẫu nến đã phát hiện
lookback_period : int
Số ngày để đánh giá độ tin cậy trong quá khứ
Returns:
-------
dict
Dictionary với xếp hạng độ tin cậy của mỗi mẫu nến
"""
reliability = {}
for pattern in patterns.columns:
bullish_signals = patterns[pattern] == 1
bearish_signals = patterns[pattern] == -1
# Tính tỷ lệ thành công trong khoảng lookback_period ngày gần nhất
if bullish_signals.sum() > 0:
# Tính % thay đổi giá trong 5 ngày tới
future_returns = df['Close'].pct_change(5).shift(-5)
# Đếm số tín hiệu thành công (giá tăng sau tín hiệu tăng)
success_rate_bullish = (future_returns[bullish_signals] > 0).mean()
reliability[f"{pattern}_bullish"] = success_rate_bullish
if bearish_signals.sum() > 0:
# Tính % thay đổi giá trong 5 ngày tới
future_returns = df['Close'].pct_change(5).shift(-5)
# Đếm số tín hiệu thành công (giá giảm sau tín hiệu giảm)
success_rate_bearish = (future_returns[bearish_signals] < 0).mean()
reliability[f"{pattern}_bearish"] = success_rate_bearish
# Sắp xếp theo độ tin cậy giảm dần
sorted_reliability = {k: v for k, v in sorted(reliability.items(), key=lambda item: item[1], reverse=True)}
return sorted_reliability
Kết luận
Phát hiện mẫu nến Nhật bằng Python là một công cụ mạnh mẽ trong phân tích kỹ thuật, giúp xác định các điểm vào và ra tiềm năng trên thị trường. Tuy nhiên, cũng cần lưu ý rằng:
- Không có mẫu nến nào có tỷ lệ thành công 100%, vì vậy nên kết hợp với nhiều công cụ phân tích khác
- Bối cảnh thị trường và khối lượng giao dịch đóng vai trò quan trọng trong việc xác nhận tín hiệu
- Backtesting là cần thiết để đánh giá hiệu quả của chiến lược trong các điều kiện thị trường khác nhau
Với sự phát triển của trí tuệ nhân tạo và học máy, các kỹ thuật phát hiện mẫu nến ngày càng trở nên tinh vi hơn, cho phép nhà đầu tư có thêm nhiều công cụ để đưa ra quyết định giao dịch thông minh hơn.
Tài liệu tham khảo
- Steve Nison, "Japanese Candlestick Charting Techniques"
- Thomas Bulkowski, "Encyclopedia of Candlestick Charts"
- John Murphy, "Technical Analysis of the Financial Markets"
- Greg Morris, "Candlestick Charting Explained"
- mplfinance documentation: https://github.com/matplotlib/mplfinance
- Yahoo Finance API: https://github.com/ranaroussi/yfinance