Phân tích định lượng phương pháp giao dịch theo chỉ báo RSI

Chỉ báo RSI là gì

Chỉ báo sức mạnh tương đối RSI là chỉ báo động lượng dùng để xác định xu hướng tiếp diễn của cổ phiếu. Đây là một chỉ báo thông dụng được biểu hiện dưới dạng dao động (một đường dao động giữa 2 biên) từ 0 đến 100 mà ở đó dao động dưới 30 điểm được gọi là quá bán và dao động trên 70 điểm là quá mua. Ở vùng quá bán 30 điểm, cổ phiếu được cho là đã bị bán quá nhiều và sẽ có xu hướng đảo chiều tăng. Ở vùng 70 điểm, cổ phiếu được cho là được mua quá nhiều và sẽ có xu hướng đảo chiều giảm.

Chỉ báo RSI được phát triển bởi J. Welles Wilder và được xuất bản trong cuốn sách New Concepts in Technical Trading Systems năm 1978. Nó đã trở thành một trong những chỉ báo dao động phổ biến nhất trong phân tích kỹ thuật. RSI đã phát triển được gần 50 năm, vậy liệu RSI áp dụng vào thị trường chứng khoán Việt Nam liệu còn chính xác.

Phương pháp định lượng

Chúng tôi sẽ định lượng chỉ báo RSI qua bài viết này. Các nghiên cứu sử dụng RSI trong khung 14 ngày, dữ liệu bao gồm toàn bộ các cổ phiếu ở sàn HOSE từ 2018 đến bây giờ. Chúng tôi sẽ tìm ra các điểm mua, bán trong bộ dữ liệu trên, qua đó nghiên cứu xác suất, lợi nhuận và so sánh với thị trường chung VNINDEX

Các phương pháp giao dịch sử dụng RSI bao gồm:

Phân tích chi tiết

Chúng tôi sử dụng python trên môi trường google colab và các thư viện talib, numpy, pandas để làm thống kê. Để cài thư viện talib chúng tôi chạy các dòng lệnh sau, thư viện numpy, pandas, matplotlib đã có sẵn trên môi trường google colab


url = 'https://anaconda.org/conda-forge/libta-lib/0.4.0/download/linux-64/libta-lib-0.4.0-h166bdaf_1.tar.bz2'
!curl -L $url | tar xj -C /usr/lib/x86_64-linux-gnu/ lib --strip-components=1
url = 'https://anaconda.org/conda-forge/ta-lib/0.4.19/download/linux-64/ta-lib-0.4.19-py310hde88566_4.tar.bz2'
!curl -L $url | tar xj -C /usr/local/lib/python3.10/dist-packages/ lib/python3.10/site-packages/talib --strip-components=3

Sau đó import các thư viện cần thiết


import talib
import numpy as np
import pandas as pd
import os

Dữ liệu giá của tất cả các mã cổ phiếu ở sàn HOSE từ ngày đầu được chúng tôi thu thập và nén lại có thể được download tại đây. Chúng tôi dùng đoạn code sau để tải dữ liệu về, giải nén, và đọc dữ liệu


! wget -O data.zip https://github.com/tranphuminhbkhn/MTQuant/raw/refs/heads/main/data.zip
! unzip data.zip

data = {}
for fn in os.listdir('data'):
    symbol = fn[:-4]
    if len(symbol) != 3 and symbol != 'VNINDEX':
        continue
    data[symbol] = pd.read_csv('data/{}'.format(fn))

Sử dụng thư viện talib để tính toán chỉ báo RSI cho từng mã cổ phiếu như sau


RSI = {}
for symbol in data:
    rsi = talib.RSI(np.array(data[symbol]['Close']))
    RSI[symbol] = rsi

Sau đó chúng tôi sẽ trích xuất các giá trị cần thiết để tính toán theo mã cổ phiếu và ngày, giá trị rsi và giá close cổ phiếu trong ngày, chúng tôi giả sử sẽ mua / bán được giá ATC trong phiên mà RSI biến động < 30 hoặc > 70


DATA = {}
for symbol in data:
    DATA[symbol] = {}
    for i in range(14, len(RSI[symbol])):
        dt = data[symbol]['Date'][i]
        if dt < 180000:
            continue
        c = data[symbol]['Close'][i]
        r = RSI[symbol][i]
        DATA[symbol][dt] = [c, r]

1. Phương pháp RSI xác định vùng quá mua quá bán

Với từng cổ phiếu, và chỉ số RSI đã tính toán trước, ta sẽ cho hệ thống giả lập mua với giá mở cửa nến tiếp theo khi RSI < 30. và bán với giá mở cửa nến tiếp theo khi RSI > 70 và lưu trữ một vài tham số khác

 
if rsi < 30 and not inOrder_flag:
    bprice = price; bvniprice = vprice
    n = 0; inOrder_flag = True; order = [dt]

if rsi > 70 and inOrder_flag and n >= 2:
    sprice = price; svniprice = vprice
    order_profit.append(sprice / bprice - 1)
    inOrder_flag = False; order.append(dt); order_data[symbol].append(order)
    vni_change.append(svniprice / bvniprice - 1)
    nhold.append(n)
Kết quả thống kê phương pháp RSI quá mua / quá bán
  • Số lần vào lệnh: 2479 lần
  • Thời gian giữ trung bình mỗi lệnh: 102.62 ngày
  • Lợi nhuận trung bình mỗi lệnh : 3.7276%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 1.2228%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 59.7%

Nhìn kết quả định lượng có vẻ khá ổn. Lợi nhuận trung bình mỗi lệnh là 3.7376% trong trung bình 102.62 phiên (tương đương mức sinh lợi tầm 8% một năm) cao hơn VNINDEX trong khoảng thời gian tương ứng. Xác suất lệnh có lời hơn VNINDEX cũng đạt gần 60%. Nhưng nhìn histogram bên phải có thể hình dung khi cổ phiếu càng giảm, kéo theo RSI xuống thấp, thì hệ thống vẫn giữ, còn khi cổ phiếu tăng lên nhẹ, RSI đã vượt 70 thì hệ thống sẽ bán. Nói nôm na là hệ thống gồng lỗ chứ không gồng lãi. Tiếp theo chúng tôi sẽ phân tích nếu không phụ thuộc vào thị trường thì kết quả định lượng sẽ thế nào, và sau đó thêm mức cắt lỗ cho hệ thống

Định lượng hệ thống giao dịch RSI không phụ thuộc thị trường

Để không bị phụ thuộc vào thị trường, chúng tôi giả sử có thể giao dịch cổ phiếu ở cả 2 chiều LONG và SHORT, LONG khi RSI < 30 và SHORT khi RSI > 70.


if rsi > 70:               
    if inOrder_flag == 'LONG' and n >= 2:
        # Đóng vị thế LONG
        sprice = price # Mở vị thế tại giá đóng cửa
        order_profit.append(sprice / bprice - 1)
        order.append(dt); order_data[symbol].append(order)
        inOrder_flag = 'None'; nhold.append(n)

    if inOrder_flag == 'None':
        # Mở vị thế SHORT
        bprice = price; n = 0; inOrder_flag = 'SHORT'; order = [dt]
    
if rsi < 30:
    if inOrder_flag == 'SHORT' and n >= 2:
        # Đóng vị thế SHORT
        sprice = price # Mở vị thế tại giá đóng cửa
        order_profit.append(1 - sprice / bprice)
        order.append(dt); order_data[symbol].append(order)
        inOrder_flag = 'None'; nhold.append(n)
    
    if inOrder_flag == 'None':
        # Mở vị thế LONG
        bprice = price; n = 0; inOrder_flag = 'LONG'; order = [dt]

Kết quả thống kê phương pháp đã không còn ổn, lợi nhuận trung bình mỗi lệnh là -5.6892%, và thời gian giữ trung bình mỗi lệnh là 118.32 ngày, ở phần tiếp theo chúng tôi sẽ thêm mốc cắt lỗ vào hệ thống giao dịch RSI xác định vùng quá mua / quá bán

Thêm mốc cắt lỗ vào hệ thống

Khi phải cắt lỗ nghĩa là đồ thị giá có xu hướng đi xuống, kéo theo đồ thị RSI có thể vẫn ở dưới mức 30, nếu vẫn để theo nguyên tắc thì hệ thống sẽ tiếp tục mua lại sau khi cắt lỗ, khi đó thêm ngưỡng cắt lỗ sẽ không có giá trị. Chúng tôi thêm vào hệ thống mức cắt lỗ 10%, và khi cắt lỗ xong, RSI phải vượt lên trên mức 30 thì mới reset và có thể vào lệnh tiếp theo khi RSI xuống dưới 30


if inOrder_flag:
    n += 1
    if price < 0.9 * bprice: # Cắt lỗ
        sprice = price; svniprice = vprice
        order_profit.append(sprice / bprice - 1)
        inOrder_flag = False; order.append(dt); order_data[symbol].append(order)     
        vni_change.append(svniprice / bvniprice - 1)
        nhold.append(n)
        block_order = True # Hạn chế mua cho đến khi RSI vượt lại 30
    
if block_order and rsi > 30:
    block_order = False
Kết quả RSI quá mua / quá bán + Stoploss
  • Số lần vào lệnh: 4258 lần
  • Thời gian giữ trung bình mỗi lệnh: 40.56 ngày
  • Lợi nhuận trung bình mỗi lệnh : 0.6101%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 0.3351%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 40.16%

Không nằm ngoài dự đoán, kết quả xấu hơn rất nhiều so với khi không đề ra mốc cắt lỗ. Chúng tôi sẽ chứng minh cắt lỗ không làm tăng hiệu quả đầu tư ở phần sau. Với chúng tôi, quản trị rủi ro không bao gồm cắt lỗ. Cắt lỗ thì NĐT vẫn phải bắt đầu và đi lên lại từ mốc đó, thay vì cắt lỗ, NĐT nên nhìn về tín hiệu tương lai cổ phiếu có khả năng tăng lại hay không, không cần biết danh mục đang lãi bao nhiêu hoặc lỗ bao nhiêu. Từ các nghiên cứu tiếp theo chúng tôi sẽ không không làm thống kê đưa mốc cắt lỗ vào

2. Phương pháp RSI xác định xu hướng

Phương pháp này sẽ mua khi RSI đi từ vùng dưới 50 vượt lên trên 50, và bán khi RSI đi xuống mốc 50


if rsi > 50 and last_rsi < 50 and not inOrder_flag:
    bprice = price # Mua tại giá đóng cửa
    bvniprice = vprice
    n = 0; inOrder_flag = True; order = [dt]

if rsi < 50 and last_rsi > 50 and inOrder_flag and n >= 2:
    sprice = price # Bán tại giá đóng cửa
    svniprice = vprice
    profit = sprice / bprice - 1
    inOrder_flag = False; order.append(dt); order_data[symbol].append(order)
    order_profit.append(profit)
    vni_change.append(svniprice / bvniprice - 1)
    nhold.append(n)
Kết quả hệ thống RSI xác định xu hướng
  • Số lần vào lệnh: 38967 lần
  • Thời gian giữ trung bình mỗi lệnh: 13.02 ngày
  • Lợi nhuận trung bình mỗi lệnh : 0.6156%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 0.419%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 31.72%

Hiệu quả của phương pháp thấp hơn rất nhiều so với các phương pháp sử dụng RSI khác, kết quả chưa tính phí thuế giao dịch, và với tần suất giao dịch dày đặc như thế, khi trừ phí thuế thì lợi nhuận trung bình các lệnh giao dịch theo phương pháp này gần như về 0 và thua xa biến động của thị trường trong khoảng thời gian tương ứng

3. Phương pháp RSI Failure Swing

Phương pháp này sẽ xác định RSI đi vào vùng lớn hơn 70, hoặc bé hơn 30, sau đó sẽ chờ RSI thoát ra khỏi vùng đó, để đưa ra tín hiệu mua bán


if rsi > 30 and last_rsi < 30 and not inOrder_flag:
    bprice = price # Mua tại giá đóng cửa
    bvniprice = vprice
    n = 0; inOrder_flag = True; order = [dt]

if rsi < 70 and last_rsi > 70 and inOrder_flag and n >= 2:
    sprice = price # Bán tại giá đóng cửa
    svniprice = vprice
    profit = sprice / bprice - 1
    inOrder_flag = False; order.append(dt); order_data[symbol].append(order)
    order_profit.append(profit)
    vni_change.append(svniprice / bvniprice - 1)
    nhold.append(n)
Kết quả hệ thống RSI Failure Swing
  • Số lần vào lệnh: 2476 lần
  • Thời gian giữ trung bình mỗi lệnh: 102.98 ngày
  • Lợi nhuận trung bình mỗi lệnh : 4.7735%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 1.1942%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 58.64%

Phương pháp này cho kết quả ban đầu khá tốt lợi nhuận trung bình mỗi lệnh là 4.77% trong khoảng thời gian nắm giữ trung bình là gần 103 phiên, tương đương mức sinh lợi nhuận 10% mỗi năm. Nhưng để so sánh khi không phụ thuộc vào thị trường giống phương pháp RSI quá mua, quá bán thì kết quả thế nào. Một cách khác để so sánh hiệu quả của phương pháp mà ko bị ảnh hưởng bởi thị trường là đảo ngược điều kiện mua và bán của hệ thống, và so sánh với khi không đảo ngược. Nếu kết quả đảo ngược tốt hơn không đảo ngược thì phương pháp ko có hiệu quả và ngược lại

Kết quả hệ thống RSI Failure Swing đảo ngược điều kiện mua bán
  • Số lần vào lệnh: 2523 lần
  • Thời gian giữ trung bình mỗi lệnh: 133.18 ngày
  • Lợi nhuận trung bình mỗi lệnh : 13.8515%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 3.4509%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 43.83%

Từ hai thống kê trên ta có thể suy ra RSI Failure Swing đã không còn hiệu quả trên thị trường chứng khoán Việt Nam từ 2018 đến hiện tại

4. Phương pháp RSI phân kì dự báo đảo chiều

Phương pháp này dự đoán tín hiệu đảo chiều khi RSI phân kì với giá (RSI có xu hướng đi lên mà giá vẫn giảm thì dự đoán giá sẽ đảo chiều tăng, khi đó sẽ mua vào, và ngược lại khi giá vẫn có xu hướng tăng và RSI giảm, dự đoán giá sẽ giảm và là lúc bán ra). Có một sự nhập nhằng về RSI phân kì và phân kì ẩn Chúng ta không thể vị trí hiện tại là có thể là đỉnh hay đáy không khi chưa biết biến động sắp tới. Trong nghiên cứu này chúng tôi sẽ so sánh giá trị RSI, giá hiện tại với đỉnh RSI, đáy RSI, đỉnh giá và đáy giá trong khoảng thời gian 14 ngày trước để phát hiện điểm phân kì và đưa ra tín hiệu mua bán. Chiến lược mua bán như sau:

Với phương pháp RSI phân kỳ

  • Mua khi phân kỳ dương: RSI hiện tại lớn hơn RSI tại đáy nhưng giá hiện tại bé hơn giá tại vị trí đáy RSI
  • Bán khi phân kỳ âm: RSI hiện tại bé hơn RSI tại đỉnh nhưng giá hiện tại lớn hơn giá tại vị trí đỉnh RSI

current_price = data[i][0]; current_rsi = data[i][1]

min_rsi_pos = i - nhis + np.argmin(data[i - nhis: i, 1])
max_rsi_pos = i - nhis + np.argmax(data[i - nhis: i, 1])
price_at_min_rsi_pos = data[min_rsi_pos][0]; min_rsi = data[min_rsi_pos][1]
price_at_max_rsi_pos = data[max_rsi_pos][0]; max_rsi = data[max_rsi_pos][1]

if current_rsi > min_rsi and current_price < price_at_min_rsi_pos: # Phân kỳ tăng giá
    signal = 'BUY'
if current_rsi < max_rsi and current_price > price_at_max_rsi_pos: # Phân kỳ giảm giá
    signal = 'SELL'

Với phương pháp RSI phân kỳ ẩn

  • Mua khi phân kỳ ẩn tăng giá: Giá hiện tại lớn hơn giá tại đáy nhưng RSI hiện tại bé hơn RSI tại đáy giá
  • Bán khi phân kỳ ẩn giảm giá: Giá hiện tại bé hơn đỉnh giá nhưng RSI hiện tại lớn hơn RSI tại đỉnh giá

current_price = data[i][0]; current_rsi = data[i][1]

min_price_pos = i - nhis + np.argmin(data[i - nhis: i, 0])
max_price_pos = i - nhis + np.argmax(data[i - nhis: i, 0])
rsi_at_min_price_pos = data[min_price_pos][1]; max_price = data[max_rsi_pos][0]
rsi_at_max_price_pos = data[max_price_pos][1]; min_price = data[min_rsi_pos][0]

if current_price > min_price and current_rsi < rsi_at_min_price_pos: # Phân kỳ ẩn tăng giá
    signal = 'BUY'
if current_price < max_price and current_rsi > rsi_at_max_price_pos: # Phân kỳ ẩn giảm giá
    signal = 'SELL'
Kết quả phương pháp RSI phân kỳ
  • Số lần vào lệnh: 2995 lần
  • Thời gian giữ trung bình mỗi lệnh: 98.91 ngày
  • Lợi nhuận trung bình mỗi lệnh : 4.3007%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 1.0229%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 60.37%

Với phương pháp này tỷ lệ lệnh có lãi tương đối cao, nhưng lợi nhuận không đạt kỳ vọng. Khi đảo ngược lại điều kiện mua bán: mua khi RSI phân kỳ giảm giá, và bán khi RSI phân kỳ tăng giá, thì lợi nhuận mỗi lệnh đạt 11.8234% trong khoảng thời gian hold 101.74 ngày, chứng tỏ phương pháp này không còn hiệu quả ở thị trường chứng khoán Việt Nam

Kết quả phương pháp RSI phân kỳ ẩn
  • Số lần vào lệnh: 4517 lần
  • Thời gian giữ trung bình mỗi lệnh: 71.83 ngày
  • Lợi nhuận trung bình mỗi lệnh : 7.3885%
  • Biến động trung bình VNINDEX khi hold mỗi lệnh: 1.7545%
  • Xác suất lệnh đạt hiệu quả hơn VNINDEX: 45.49%

Phương pháp này hiệu quả hơn phương pháp RSI phân kỳ thường, khi cho tỉ lệ lợi nhuận trung bình cao hơn trong thời gian giữ ngắn hơn, nhưng để kiểm tra tính hiệu quả mà không phụ thuộc và thị trường, chúng tôi đã đảo ngược điều kiện mua bán và kết quả lợi nhuận đạt 3.3037% trên mỗi lệnh trong thời gian nắm giữ trung bình 67.51 ngày, đã kém hiệu quả một chút ít. Có thể suy ra phương pháp này còn hiệu quả trong thị trường chứng khoán Việt Nam (trong điều kiện chưa tính phí giao dịch, thuế TNCN và lãi suất nếu dùng margin)

Trong thực tế phương pháp RSI phân kỳ và phân kỳ ẩn có nhiều nhập nhằng với nhau, chúng tôi đã thống kê và thấy có nhiều điểm phương pháp phân kỳ cho tín hiệu bán nhưng phương pháp phân kỳ ẩn lại cho tín hiệu mua, và ngược lại. Các phương pháp RSI phân kỳ về mặt động lượng RSI phân kỳ thường xảy ra trong trường hợp giá đang trong một xu hướng và xu hường bắt đầu yếu dần đi, và phương pháp dự đoán sẽ có xu hướng đảo chiều

4. Kết luận

Trong các phần trên chúng tôi đã làm thống kê định lượng các phương pháp giao dịch sử dụng RSI bao gồm

Trong các phương pháp trên, chỉ có phương pháp RSI phân kỳ ẩn là còn hiệu quả, nhưng phương pháp giao dịch đó lại có chút nhập nhằng với RSI phân kì thường. Còn lại các phương pháp khác sử dụng RSI đều không còn chính xác trên thị trường chứng khoán Việt Nam

Mọi nghiên cứu phía trên chúng tôi thực hiện trên môi trường google colab, bấm vào để xem vào thực thi đoạn code