Chuyển đến nội dung chính

Hướng dẫn Giao dịch Thuật toán bằng R

Trong hướng dẫn R này, bạn sẽ thực hiện web scraping, gọi một API tài chính và dùng một htmlwidget để tạo biểu đồ chuỗi thời gian tương tác nhằm triển khai một chiến lược giao dịch thuật toán đơn giản
Đã cập nhật 5 thg 10, 2026  · 15 phút đọc

Khám phá cùng AI

ChatGPTClaudePerplexity

Trong bài viết này, tôi sẽ hướng dẫn cách dùng R để thu thập các cổ phiếu được niêm yết trên loyal3, lấy dữ liệu lịch sử từ Yahoo và sau đó thực hiện một chiến lược giao dịch thuật toán đơn giản. Trong quá trình đó, bạn sẽ học một chút về web scraping, một hàm gọi tới API tài chính và một htmlwidget để tạo biểu đồ chuỗi thời gian tương tác.

Trong phạm vi bài viết này, một thuật toán giao dịch (trading algo) được định nghĩa là một tập hợp quy tắc kích hoạt sự kiện mua hoặc bán thay vì một mô hình dự báo hoặc dự báo chuỗi thời gian. Đây là loại thuật toán giao dịch đơn giản nhất, nhưng nếu bạn muốn tìm hiểu sâu hơn về tài chính với R, tôi khuyến khích bạn học khóa học mô hình hóa chiến lược giao dịch định lượng trong R của DataCamp.

Bối cảnh

Năm 2015, tôi bắt đầu đầu tư một chút trên loyal3. Dịch vụ của họ khá khác biệt và là nơi tuyệt vời để khởi đầu hành trình đầu tư. Thay vì thu phí giao dịch từ nhà đầu tư, loyal3 thu phí từ các công ty để được niêm yết trên nền tảng của họ. Giả định là những người yêu thích dịch vụ của một công ty cũng sẽ mua cổ phiếu và từ đó trở thành những người ủng hộ thương hiệu mạnh mẽ. Điểm hấp dẫn hơn ở nền tảng này là bạn có thể mua cổ phiếu lẻ (fractional shares). Vì vậy, bạn có thể sở hữu cổ phiếu amazon trị giá $800 chỉ với $10 và mua thêm các phần $10 mỗi khi bạn có chút tiền dư vào cuối tháng. Dĩ nhiên có chi phí ma sát vì bạn phải giao dịch theo khung thời gian nhất định và toàn bộ danh mục của bạn bị giới hạn ở khoảng 70 cổ phiếu, nhưng loyal3 là một cách thú vị và chi phí thấp để khám phá giao dịch cổ phiếu. Bạn có thể “vào cuộc” chỉ với $10!

Để rõ ràng, tôi cũng có các tài khoản hưu trí và đầu tư thông thường, nhưng tôi thích giao diện gọn gàng trên ứng dụng của loyal3 và việc không có phí. Tôi thường kiểm tra danh mục loyal3 “giải trí” của mình thường xuyên hơn các quỹ tương hỗ, đơn giản vì dễ dàng và thú vị khi xem hiệu suất những cổ phiếu do chính tôi chọn.

algorithmic trading R
Các cổ phiếu có sẵn tại www.loyal3.com

Thiết lập Môi trường làm việc

Bắt đầu bằng cách nạp các thư viện vào môi trường của bạn. Dạo gần đây tôi gần như luôn dùng rvest cho web scraping. Có các gói khác hoạt động được như RSelenium, nhưng tôi thích sự đơn giản khi thực thi của rvest.

Gói thứ hai, pbapply, là tùy chọn vì nó chỉ thêm thanh tiến trình cho các hàm apply. Vì bạn có thể sẽ scrape hàng trăm trang web, một thanh tiến trình sẽ hữu ích để ước lượng thời gian.

Tiếp theo, TTR là một gói mà tôi mới bắt đầu khám phá. Thư viện này dùng để xây dựng các “Quy tắc Giao dịch Kỹ thuật”. Mặc dù trong bài viết này bạn sẽ học một thuật toán giao dịch đơn giản, gói TTR có thể thực hiện các phép tính tinh vi hơn và đáng để học.

Thư viện dygraphs là một wrapper cho thư viện vẽ biểu đồ JavaScript mã nguồn mở, hiệu năng cao. Nó là một trong những htmlwidget giúp biểu đồ R trở nên động và là một phần của tệp html thay vì hình ảnh tĩnh. Cuối cùng, gói lubridate dùng để thao tác ngày tháng dễ dàng.

library(rvest)
library(pbapply)
library(TTR)
library(dygraphs)
library(lubridate)

Thu thập Dữ liệu

Tất cả cổ phiếu của loyal3 được liệt kê trên một trang duy nhất. Trước khi tra cứu giá cổ phiếu theo ngày để xây dựng thuật toán giao dịch, bạn cần thu thập tất cả mã cổ phiếu (ticker) có sẵn. Việc đầu tiên là khai báo stock.list là một chuỗi URL. Tiếp theo dùng read_html() để phiên R của bạn tạo một phiên Internet và thu thập toàn bộ thông tin html trên trang dưới dạng tập node XML. CSS của trang có một ID gọi là “.company-name”. Dùng giá trị này làm tham số khi gọi html_nodes() để chỉ chọn dữ liệu XML gắn với node đó. Cuối cùng, dùng html_text() để lấy ra các giá trị văn bản thực của tên công ty.

stock.list<-'https://www.loyal3.com/stocks'
stocks<-read_html(stock.list)
stocks.names<-html_nodes(stocks,'.company-name')
stocks.names<-html_text(stocks.names)

Để xem các cổ phiếu đang có trên loyal3, bạn có thể in đối tượng stocks.names ra console. Lệnh này trả về tên công ty dưới dạng một vector văn bản.

stocks.names 

Để nghiên cứu giá cổ phiếu, trước tiên bạn cần lấy mã ticker. Khi ở trên trang loyal3, bạn có thể nhấp vào ô của công ty để mở trang có mã ticker và thông tin công ty khác.

Dùng html_nodes() trên stocks, bạn sẽ lấy tất cả node được đánh dấu bằng “a.” Trong HTML, thẻ <a> định nghĩa một siêu liên kết dùng để dẫn từ trang này sang trang khác. Bên trong thẻ liên kết, “href” là địa chỉ URL chính xác. Vì vậy html_attr() sẽ trích xuất URL của TẤT CẢ liên kết trên trang nếu bạn truyền vào “href”.

Sau khi kiểm tra thủ công, tôi thấy các liên kết từ thứ 54 đến 123 trên trang là các trang công ty cần để scrape thông tin ticker. Dòng cuối dùng paste0() để nối chuỗi URL gốc ’http://www.loyal3.com` với trang công ty cụ thể, như “/WALMART”. Ví dụ, http://www.loyal3.com/WALMART:

loyal.links<-html_nodes(stocks, "a")
loyal.links<-html_attr(loyal.links, "href")
stock.links<-paste0('http://www.loyal3.com',loyal.links[54:123])

Trên mỗi trang công ty có phần mô tả, giá đóng cửa gần nhất và mã ticker. Tất cả trang công ty đều có cấu trúc giống nhau nên có thể dùng hàm tùy chỉnh get.ticker() để lấy mã ticker.

Bên trong trang web của công ty có một bảng gọi là “ticker-price”. Hàm sẽ điều hướng đến trang công ty, xác định bảng phù hợp, trích xuất văn bản bằng html_text(). Cuối cùng, dùng sub() cùng biểu thức chính quy ^([[:alpha:]]*).* và \\1 để giữ lại các ký tự chữ cái. Kết quả là mọi ký tự đặc biệt như $, và các ký tự số như giá đóng cửa, sẽ bị loại bỏ. Khi hàm đọc từng trong số 70 trang, nó sẽ chỉ thu thập mã ticker.

 get.ticker<-function(url){
  x<-read_html(url)
  x<-html_node(x,'.ticker-price')
  x<-html_text(x)
  x<-sub("^([[:alpha:]]*).*", "\\1", x)
  return(x)
  } 
loyal3 stock page for Alibaba
trang cổ phiếu loyal3 của Alibaba, nơi bạn thấy bảng chứa mã ticker của cổ phiếu, BABA, nằm bên dưới đoạn chữ in đậm

Với hàm tùy chỉnh của bạn, hãy dùng pblapply() để áp dụng hàm cho từng phần tử của stock.links tương ứng với từng trang công ty. Đối tượng kết quả, stock.tickers, là một danh sách các mã cổ phiếu riêng lẻ, mỗi phần tử tương ứng một công ty.

stock.tickers<-pblapply(stock.links,get.ticker)

Một cách để chuyển một danh sách phần tử thành một đối tượng phẳng là dùng do.call(). Ở đây, bạn áp dụng rbind để ghép hàng từng phần tử của danh sách thành một vector duy nhất. Cuối cùng, bạn tạo một data frame với thông tin mã và tên công ty.

stock.ticks<-do.call(rbind,stock.tickers)
stock.ticks<-data.frame(symbol=stock.ticks,name=stocks.names)

Để phân tích nhất quán, bạn có thể muốn giới hạn lượng dữ liệu lịch sử thu thập cho mỗi cổ phiếu. Hàm Sys.Data() sẽ lưu một đối tượng ngày theo thứ tự năm, tháng rồi ngày. Dùng số nguyên cho số năm là một cách để trừ ra một khoảng thời gian cụ thể từ đối tượng start.date.

start.date<-Sys.Date()
end.date<-Sys.Date()-years(3)

Để lấy dữ liệu Yahoo Finance, đối tượng ngày cần được đổi thành chuỗi ký tự đơn giản không có dấu gạch ngang. Dùng hàm thay thế toàn cục gsub() trên cả start.date và end.date sẽ đổi lớp và đồng thời loại bỏ dấu gạch. Bên trong gsub(), truyền vào mẫu ký tự cần tìm, rồi đến chuỗi thay thế. Trong trường hợp này, mẫu thay thế là chuỗi rỗng giữa hai dấu ngoặc kép. Tham số cuối cùng là đối tượng mà gsub() sẽ áp dụng.

start.date<-gsub('-','', start.date)
end.date<-gsub('-','', end.date)

Hàm TTR() getYahooData() nhận vào một mã cổ phiếu, cùng ngày bắt đầu và kết thúc. Hàm trả về một data frame có thông tin chuỗi thời gian. Mỗi hàng là một ngày và các cột chứa thông tin như giá “Open”, “High”, “Low” và “Closing” cho một cổ phiếu. Vì bạn đang tra cứu nhiều công ty, bạn có thể dùng lapply() hoặc pblapply(). Truyền vào vector mã công ty, sau đó là hàm getYahooData(), rồi thông tin ngày. Các đối tượng date sẽ được tái sử dụng mỗi lần getYahooData() được áp dụng cho một mã cổ phiếu.

 stocks.ts<-pblapply(stock.ticks$symbol,getYahooData,end.date, start.date)

Để việc lựa chọn trong danh sách trả về stocks.ts dễ điều hướng hơn, bạn có thể gán tên cho các phần tử danh sách. Dùng names với đối tượng stocks.ts, khai báo tên chính là vector $symbol gốc.

names(stocks.ts)<-stock.ticks$symbol

Khi làm việc với danh sách lớn, tôi thích kiểm tra đối tượng kết quả để chắc chắn đầu ra như mong đợi. Giờ các phần tử đã có tên, bạn có thể tham chiếu trực tiếp. Trong ví dụ này, bạn đang xem 6 hàng đầu tiên của AMC Entertainment Holdings (AMC). Dùng head() trên danh sách trong khi tham chiếu $AMC sẽ trả về một phần chuỗi thời gian của cổ phiếu này:

head(stocks.ts$AMC)

Khám phá Dữ liệu Cổ phiếu

Khi nghe bình luận tin tức tài chính, người ta thường nhắc đến biểu đồ. Bất chấp giao dịch tần suất cao và quản lý chủ động do người khác thực hiện, nhiều nhà đầu tư nhỏ vẫn tham chiếu biểu đồ để tìm hiểu. Đối tượng chuỗi thời gian có thể được hiển thị nhanh bằng plot. Truyền vào danh sách tham chiếu đến phần tử theo tên như $AMC và cột bạn muốn hiển thị, ở đây là $Close.

plot(stocks.ts$AMZN$Close)
stocks plot

Biểu đồ trên là tĩnh và không quá thú vị.

Hãy dùng một thư viện JavaScript để tạo biểu đồ có thể khám phá. Trong đoạn mã này, bạn có thể thấy “%>%” hay toán tử pipe. Toán tử pipe là cách hay để viết mã ngắn gọn. Nó chuyển tiếp một đối tượng tới hàm tiếp theo mà không buộc bạn phải viết lại tên đối tượng như trước đó trong bài.

Trong ví dụ này, bạn tạo một dygraph tham chiếu tới cổ phiếu Twitter, $TWTR, rồi cột muốn vẽ, $Close. Bên trong dygraph, main thêm tiêu đề được chỉ định trong dấu ngoặc kép. Dùng “%>%” toàn bộ đối tượng này được chuyển tiếp đến hàm tiếp theo dyRangeSelector(). Bạn có thể chỉ định phạm vi ngày mặc định bằng c() với chuỗi ngày bắt đầu và kết thúc. Đối tượng HTML kết quả là một chuỗi thời gian động cho cổ phiếu của Twitter với thanh trượt ngày ở phía dưới.

Hãy nhớ, để đổi cổ phiếu hiển thị, hãy thay mã ticker trong danh sách stocks.ts rồi tiêu đề biểu đồ.

dygraph(stocks.ts$TWTR$Close, main = "TWTR Stock Price") %>%
  dyRangeSelector(dateWindow = c("2013-12-18", "2016-12-30"))

basic dygraph for Twitter’s stock
Đây là một dygraph cơ bản cho cổ phiếu của Twitter

Một Chiến lược Giao dịch Đơn giản: Theo xu hướng

Các nhà giao dịch tần suất cao và quỹ phòng hộ dùng các mô hình tinh vi và cách tiếp cận dựa trên quy tắc để thực thi giao dịch. Nếu bạn muốn tìm hiểu thêm, tôi gợi ý truy cập www.quantopian.com cho các cách tiếp cận nâng cao. Với các cách đơn giản hơn, hãy bắt đầu từ trang này tại www.Investopedia.com.

Trong đoạn mã dưới đây, bạn sẽ trực quan hóa một chiến lược giao dịch động lượng đơn giản. Về cơ bản, bạn sẽ tính đường trung bình động 200 ngày và 50 ngày cho giá cổ phiếu. Vào bất kỳ ngày nào mà trung bình 50 ngày cao hơn trung bình 200 ngày, bạn sẽ mua hoặc giữ vị thế. Vào những ngày trung bình 200 ngày lớn hơn trung bình 50 ngày, bạn sẽ bán cổ phần. Chiến lược này gọi là chiến lược theo xu hướng. Tính dương hoặc âm giữa hai đường trung bình theo thời gian thể hiện động lượng của cổ phiếu.

Gói TTR cung cấp SMA() để tính trung bình động đơn giản. Trong đoạn mã này, bạn đang xem 6 giá trị đầu tiên cho trung bình động 200 và 50 ngày của Twitter. SMA() hoạt động bằng cách truyền vào dữ liệu chuỗi thời gian cho một cổ phiếu và một cột cụ thể như Close. Đây là một vector đơn của giá đóng cửa cho cổ phiếu TWTR. Tham số thứ hai là một số nguyên đại diện cho số quan sát của trung bình động. Nếu không dùng head() thì hàm SMA() sẽ trả về tất cả giá trị.

head(SMA(stocks.ts$TWTR$Close, 200))
head(SMA(stocks.ts$TWTR$Close, 50))

Giờ bạn đã xem xét chi tiết hàm trung bình động, bạn cần áp dụng cho cả 70 cổ phiếu. stocks.ts là một danh sách gồm 70 data frame chứa dữ liệu từng cổ phiếu. Cột thứ tư của mỗi data frame chứa giá đóng cửa mà chúng ta muốn dùng để tính trung bình động.

Hàm tùy chỉnh mov.avgs() nhận một data frame cổ phiếu để tính các đường trung bình động. Dòng đầu chọn giá đóng cửa vì nó đánh chỉ số [,4] để tạo stock.close. Tiếp theo, hàm dùng ifelse để kiểm tra số hàng trong data frame. Cụ thể, nếu nrow trong data frame nhỏ hơn (2*260), thì hàm sẽ tạo một data frame các trung bình động với “NA”.

Tôi chọn con số này vì có khoảng 250 ngày giao dịch mỗi năm nên phép kiểm tra này đảm bảo chuỗi thời gian dài khoảng 2 năm hoặc hơn. Loyal3 đôi khi có thể tiếp cận các IPO và nếu cổ phiếu mới lên sàn thì sẽ không đủ dữ liệu cho trung bình động 200 ngày. Tuy nhiên, nếu giá trị nrow lớn hơn 2*260 thì hàm sẽ tạo một data frame với dữ liệu gốc cùng với hai cột mới là trung bình động 200 và 50 ngày. Dùng colnames, tôi khai báo tên cột. Phần cuối của hàm dùng complete.cases để kiểm tra các giá trị ở cột trung bình động 200 ngày. Bất kỳ hàng nào không có giá trị sẽ bị loại trong kết quả cuối.

mov.avgs<-function(stock.df){
  stock.close<-stock.df[,4]
  ifelse((nrow(stock.df)<(2*260)),
         x<-data.frame(stock.df, 'NA', 'NA'),
         x<-data.frame(stock.df, SMA(stock.close, 200), SMA(stock.close, 50)))
  colnames(x)<-c(names(stock.df), 'sma_200','sma_50')
  x<-x[complete.cases(x$sma_200),]
  return(x)
  }

Với hàm mov.avgs() này, bạn có thể dùng pblapply() để thêm tính toán trung bình động cho từng trong số 70 data frame.

stocks.ts<-pblapply(stocks.ts, mov.avgs)

Dùng đoạn mã dưới để trực quan hóa các đường trung bình động của một cổ phiếu bằng dygraph. Một lần nữa, mã này dùng toán tử “%>%” để chuyển tiếp đối tượng. Hàm dygraph() nhận data frame stocks.ts$FOX. Cụ thể, data frame được đánh chỉ số theo tên cột bằng c('sma_200','sma_50'). Đối tượng này được truyền sang dySeries() ở 2 dòng tiếp theo. Bạn có thể tham chiếu cột theo tên nên mỗi dySeries() sẽ vẽ một đường cho giá trị “sma_50” và “sma_200” ở dòng 2 và 3. Đối tượng lại được chuyển tiếp đến dyRangeSelector() để điều chỉnh chiều cao bộ chọn. Cuối cùng, tôi thêm phần đổ bóng để xác định các giai đoạn mà bạn nên mua/giữ cổ phiếu và giai đoạn mà bạn nên bán hoặc đứng ngoài tùy vị thế.

dygraph(stocks.ts$FOX[,c('sma_200','sma_50')],main = 'FOX Moving Averages') %>%
  dySeries('sma_50', label = 'sma 50') %>%
  dySeries('sma_200', label = 'sma 200') %>%
  dyRangeSelector(height = 30) %>%
  dyShading(from = '2016-4-28', to = '2016-7-27', color = '#CCEBD6') %>%
  dyShading(from = '2016-7-28', to = '2016-12-30', color = '#FFE6E6')

Đây là kết quả cuối cùng dưới dạng chuỗi thời gian tương tác.

fox moving averages
Các đường trung bình động của FOX với vùng đổ bóng cho mua/giữ so với bán

Kết luận

Là một nhà giao dịch thuật toán mới vào nghề, bạn không cần vẽ biểu đồ cho cả 70 cổ phiếu. Thay vào đó, bạn nên chạy mã mỗi ngày và thêm cách xác định bằng lập trình những cổ phiếu phù hợp phương pháp dựa trên quy tắc: “mua nếu trung bình động 50 ngày cao hơn trung bình động 200 ngày”. Khi xem biểu đồ ở trên, phần màu xanh lá là thời điểm bạn sẽ mua cổ phiếu FOX. Phần màu đỏ thể hiện thời điểm nên bán cổ phần của bạn và không quay lại.

Vì biểu đồ là tương tác, bạn có thể dùng thanh trượt để thay đổi kích thước hiển thị. Dựa trên cách tiếp cận giao dịch thuật toán đơn giản này, bây giờ có thể là thời điểm tốt để mua FOX! Ngày 30 tháng 12 năm 2016 là ngày giao dịch mà trung bình động 50 ngày tăng cao hơn trung bình động 200 ngày $0,01!

fox equity
Phần phóng to của cổ phiếu FOX

Dĩ nhiên, hãy nhớ rằng mọi khoản đầu tư đều có thể mất giá. Để tìm hiểu thêm về tài chính và giao dịch thuật toán, hãy xem các khóa học của DataCamp tại đây.

Hãy xem hướng dẫn Kỹ năng Lập trình Cơ bản trong R của chúng tôi.

Chủ đề
R
Khoa học Dữ liệu

Tìm hiểu thêm về R

Khóa học

Nhập môn Ngôn ngữ lập trình R

4 giờ
3.1M
Nắm vững kiến thức cơ bản về phân tích dữ liệu trong R, gồm vectors, lists và data frames; luyện tập R với các data set thực tế.
Xem chi tiếtRight Arrow
Bắt Đầu Khóa Học
Xem thêmRight Arrow
Liên quan

blog

Claude Opus 4.6: Tính năng, Điểm chuẩn, Bài kiểm tra thực hành và hơn thế nữa

Mô hình mới nhất của Anthropic dẫn đầu ở mã hóa tác tử và lập luận phức tạp. Thêm vào đó, nó có cửa sổ ngữ cảnh 1M.
Matt Crabtree's photo

Matt Crabtree

10 phút

Xem ThêmXem Thêm