Phương pháp cắt lỗ có hiệu quả không?

Tổng quan về cắt lỗ

Cắt lỗ (tiếng Anh: Stop loss) là việc chủ động đóng vị thế hay đóng giao dịch tại một mức cụ thể và chấp nhận một khoản lỗ khi xu hướng cổ phiếu biến động không như tính toán ban đầu. Cắt lỗ được xem là biện pháp giúp nhà đầu tư kiểm soát được rủi ro đồng thời bảo vệ được nguồn vốn.

  • Cắt lỗ khi các vùng hỗ trợ trọng yếu bị thủng. Các vùng hỗ trợ ở đây là những mức giá có lượng cầu lớn, chẳng hạn mức giá đỉnh và đáy của quá khứ, đường xu hướng, đường trung bình cộng (MA)… Nhưng thực tế đây là những tín hiệu bán trong phương pháp giao dịch của bạn, nên đây không được xem là cắt lỗ
  • Cắt lỗ theo phần trăm: Thời điểm cần phải cắt lỗ nhất chính là khi giá cổ phiếu đã giảm theo số phần trăm nhất định so với giá mua. Con số này còn phụ thuộc vào mức độ chịu đựng rủi ro của nhà đầu tư hoặc khả năng biến động giá của cổ phiếu đó. Thông thường, người ta sẽ lấy mức 5 - 10%

Trong bài này chúng tôi sẽ nghiên cứu phương pháp cắt lỗ theo phần trăm, và dùng từ cắt lỗ để chỉ chung về phương pháp này

1. Lý thuyết toán học phía sau cắt lỗ

Khi bạn cắt lỗ tại mức giá p. Chúng tôi giả sử trong một thị trường cân bằng, mức tăng giảm mỗi ngày trung bình là 0, vì kì vọng biến đổi mỗi ngày là 0 nên ở bất kì thời điểm nào sau thời điểm bạn cắt lỗ thì vọng giá tại đó cũng bằng p. Mặt khác, sau khi bạn cắt lỗ và vào một lệnh khác, quá trình lại lặp lại, kì vọng về lợi nhuận của lệnh khác trung bình là 0, tương đương với việc bạn nắm giữ tiếp lệnh cũ. Như vậy việc cắt lỗ là không có tác dụng

Chúng tôi sẽ giả sử biến động mỗi ngày của cổ phiếu trong một thị trường cân bằng là một dãy số ngẫu nhiên phân phối chuẩn có mean = 0std = 0.02. Khi đó giá cổ phiếu được biểu diễn bởi tích tích lũy của dãy số trên. Trong đoạn code dưới chúng tôi tạo ra 100000 dãy số như thế, mỗi dãy số bao gồm 50 số thể hiện biến động hàng ngày để máy tính có thể xấp xỉ các xác suất chúng tôi cần tính toán


change = np.random.randn(100000, 50) * 0.02
profit = np.cumprod(1 + change, axis=1) - 1

Biến động giá của một vài mẫu


for i in range(50):
    print(i, np.mean(profit[:, i]))

Kết quả đoạn code trên trong trường hợp này sẽ ra 50 số xấp xỉ 0, chứng minh hai điều:

  • Nếu điểm ban đầu là điểm mua, kỳ vọng lợi nhuận ở bất kì thời điểm nào cũng là 0
  • Nếu điểm ban đầu là điểm cắt lỗ, kì vọng giá ở bất kì thời điểm nào sau điểm cắt lỗ cũng bằng giá bạn cắt lỗ

Điều đó chứng minh quan điểm nêu trên của chúng tôi. Nếu bạn không có phương pháp nào hiệu quả thì giá cổ phiếu sau khi bạn mua hoặc cắt lỗ cũng biến động như các dãy số trên và cắt lỗ sẽ không có tác dụng

Còn một quan điểm khác mà mọi người hay đưa ra để giải thích về việc cắt lỗ là: Để cổ phiếu của bạn sau khi giảm về trở lại với giá gốc, mức tăng trưởng đó sẽ phải cao hơn nhiều so với mức thua lỗ. Ví dụ, thua lỗ 10% thì chỉ cần tăng 11,11% để quay lại giá gốc, nhưng thua lỗ 50% sẽ cần tăng 100% mới quay lại giá gốc. Khi đưa ra quan điểm này thì có lẽ mọi người đã bỏ qua xác suất


print(sum(np.any(profit < -0.1, axis=1)))   # KQ: 43517
print(sum(profit[:, -1] < -0.19))           # KQ: 18962

Đoạn code trên cho thấy xác suất để gặp mốc cắt lỗ 10% trong trường hợp nắm giữ trung bình 50 ngày là 43.6%, vậy xác suất 2 lần gặp stop loss liên tiếp là 18.9%. Trong khi đó, nếu nắm giữ trung bình 50 ngày thì xác suất lỗ trên 19% (tương đương với 2 lần liên tiếp cắt lỗ) chỉ tầm 7.9%. Vậy cắt lỗ trong trường hợp này còn kéo hiệu quả? Không hẳn! hai lần cắt lỗ ở đây đã giới hạn mức lỗ 19%, trong khi 7.9% là xác suất mức lỗ lớn hơn 19%. Trên thực tế, con số ở đây là khoảng 12.6% (trung bình của những mức lỗ lớn hơn 12.6% là 19%), khi đó xác suất lỗ nhiều hơn 12.6% đúng bằng xác suất hai lần liên tiếp gặp stop loss

Tiếp theo, để tổng quan hơn, chúng tôi giả sử một phương pháp mua bán trong 50 ngày, tín hiệu mua tại đầu dãy số, bán tại cuối dãy số (sau 50 ngày) và luôn tuân thủ phương pháp đó. Mức cắt lỗ là 10%, và chỉ bán khi gặp mức cắt lỗ nếu không thì bán sau 50 ngày. Đoạn code sau đây sẽ mô phỏng quá trình đó


allprofit = [] # Biến lưu trữ lợi nhuận của từng lệnh
for i in range(100000):
    pattern = profit[i]
    zid = np.where(pattern < -0.1)[0]
    if len(zid) > 0:
        id = zid[0] # Vị trí đầu tiên trong dãy số < -0.1 và cắt lỗ tại đó
    else:
        id = -1 # Không có vị trí nào < -0.1, bán tại cuối dãy sô
    p = pattern[id]
    allprofit.append(p)
print(sum(allprofit) / len(allprofit))

Giá trị trung bình lợi nhuận của từng lệnh nếu không có cắt lỗ là 0. Ở đoạn code trên mô phỏng cắt lỗ, giá trị sum(allprofit) / len(allprofit) là lợi nhuận trung bình của lệnh trong trường hợp cắt lỗ 10% được in ra cũng xấp xĩ bằng 0 sau nhiều lần chạy càng chứng tỏ quan điểm của chúng tôi. Ở phần sau chúng tôi sẽ áp dụng thực tế và làm thống kê trên thị trường chứng khoán Việt Nam

2. Thống kê trên thị trường chứng khoán Việt Nam

Trong thị trường chứng khoán Việt Nam, có rất nhiều phương pháp giao dịch khác nhau, có những phương pháp có hiệu quả, những phương pháp không hiệu quả. Ở các phần trước chúng tôi cũng đã đưa cắt lỗ vào các phương pháp giao dịch theo chỉ báo RSI, và kêt quả là không có hiệu quả. Ở đây để đảm bảo tính trung bình, chúng tôi thống kê phương pháp mua bán ngẫu nhiên, với xác suất xảy ra tín hiệu mua và bán trong mỗi ngày là 0.02 tương đương với thời gian nắm giữ trung bình là khoảng 50 ngày. Quá trình tải, tiền xử lý dữ liệu, các biến lưu trữ và xử lý đặt tên giống các bài trước, đoạn code sau mô phỏng quá trình mua bán


if inOrder_flag:
    n += 1
    if price < 0.9 * bprice and n >= 2 and not stoploss: # Cắt lỗ
        sprice = price # Bán tại giá đóng cửa
        stoploss = sprice / bprice - 1 # Đã cắt lỗ

if random.random() < 0.02 and not inOrder_flag:
    bprice = price # Mua tại giá đóng cửa
    n = 0; inOrder_flag = True; order = [dt]; stoploss = False

if random.random() < 0.02 and inOrder_flag and n >= 2:
    sprice = price # Bán tại giá đóng cửa
    profit = sprice / bprice - 1
    if stoploss: # Nếu chưa cắt lỗ
        order_profit_with_stoploss.append(stoploss)
    else:
        order_profit_with_stoploss.append(profit)
    
    inOrder_flag = False
    order_profit.append(profit); nhold.append(n)

Trong đoạn code trên, biến stoploss được khởi tại là False mỗi khi vào lệnh khi giá giảm xuống dưới 10% thì cắt lỗ, biến stoploss sẽ được gán giá trị là mức lỗ khi cắt và được xác định là cắt lỗ rồi. Biến order_profit_with_stoploss là một dãy số định nghĩa mức lợi nhuận của từng lệnh trong trường hợp có cắt lỗ. Hình bên dưới là histogram của hai biến đó

Khi chúng tôi in ra mean của hai biến trên, kết quả như sau

  • mean(order_profit) là 0.034622, tương đương với lợi nhuận trung bình mỗi lệnh là 3.4622%
  • mean(order_profit_with_stoploss) là 0.024446, tương đương với lợi nhuận trung bình mỗi lệnh là 2.4446%

Nó so sánh quá trình bạn cắt lỗ và trong thời gian chờ tín hiệu mua lại của phương pháp đó không vào bất kì lệnh mới nào, thì việc cắt lỗ trên một thị trường uptrend (bằng chứng là lợi nhuận kì vọng dương trong thời gian nắm giữ khoảng 50 ngày) sẽ làm giảm hiệu suất lệnh của bạn

Đoạn code trên theo lý thuyết cắt lỗ là cho rằng tín hiệu này đã sai, cắt và đứng ngoài chờ đến khi tín hiệu bán ra xong mua vào lại thì mới vào lệnh. Nó có thể so sánh hiệu quả trung bình của một lệnh khi bạn hold tiếp hoặc cắt lỗ. Vậy trong trường hợp bạn thay đổi phương pháp giao dịch khác có thể vào lại ngay sau khi bán mà không quan tâm đến tín hiệu phương pháp cũ, thì sẽ thế nào, đoạn code dưới chúng tôi chạy mô phỏng điều đó


if random.random() < 0.02 and not inOrder_flag:
    bprice = price # Mua tại giá đóng cửa
    n = 1; inOrder_flag = True; order = [dt]; stoploss = False

if  (price < 0.9 * bprice or random.random() < 0.02) and inOrder_flag and n >= 2:
    sprice = price # Bán tại giá đóng cửa
    profit = sprice / bprice - 1
    inOrder_flag = False
    order_profit.append(profit); nhold.append(n)

Khi kiểm tra giá trị trung bình lợi nhuân mỗi lệnh chia cho trung bình thời gian nắm giữ trong trường hợp này, thì việc cắt lỗ mang lại lợi nhuận trung bình / ngày tốt hơn. Nhưng chỉ có thể tính như vậy trong trường hợp bạn có nguồn vốn vô tận, và mỗi lệnh đều vào một mức như nhau, còn trong thực tế khi bạn vào lệnh, sau mỗi lệnh lãi lỗ thì nav của bạn đều thay đổi. Để so sánh chính xác hơn chúng tôi tính lợi nhuận kép từ dãy số trên, và giả sử mỗi lệnh vào 1 / 500 nav


tprofit = 1
for profit in order_profit:
    tprofit *= 1 + profit / 500
print(tprofit)

Giá trị tprofit sẽ là lợi nhuận tích lũy của phương pháp giao dịch trong toàn bộ dữ liệu chúng tôi sử dụng. Giá trị này biến động nên chúng tôi chạy thuật toán phía trên 50 lần và lấy giá trị trung bình. Khi không có stop loss thì giá trị này bằng 1.5497, tương đương với mức sinh lợi 54.97%. Và khi có stop loss, giá trị này là 1.4747, tương đương mức sinh lợi 47.47%, vậy trong trường hợp này cắt lỗ vẫn không đem lại hiệu quả

3. Kết luận

Trong các phần trên chúng tôi đã chứng minh Cắt lỗ không có hiệu quả bằng mặt trực quan, toán học, và thống kê thực tế vào thị trường chứng khoán Việt Nam. Qua đó càng chứng minh quan điểm của chúng tôi: Cắt lỗ không giúp bạn đạt được lợi nhuận tốt hơn. Cách tốt nhất bạn có thể làm là không cần nhìn vào vị thế nắm giữ đang lãi hay lỗ bao nhiêu, chỉ cần phân tích, dự đoán trong tương lai liệu giá cổ phiếu có xu hướng thế nào để đưa ra quyết định mua bán. Thường thì khi áp dụng cắt lỗ vào hệ thống giao dịch, số lệnh giao dịch sẽ dày đặc hơn, những nghiên cứu, thống kê chúng tôi làm chưa tính phí và thuế giao dịch trên thị trường, và khi chịu những khoản phí thuế này nữa thì cắt lỗ hoàn toàn bất lợi trong mọi giai đoạn của thị trường. Điều duy nhất cắt lỗ đem lại là khi phương pháp giao dịch của bạn không tốt, cắt lỗ sẽ làm thời gian bạn cháy tài khoản lâu hơn một chút !?

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