Các nhà đầu tư tổ chức đóng vai trò then chốt trong quá trình khám phá giá, quản trị công ty và thanh khoản thị trường. Hiểu được tổ chức giao dịch như thế nào và giao dịch nhiều đến mức nào giúp chúng ta nắm được cả động lực định giá tài sản lẫn các tác động thực của việc giám sát từ tổ chức. Công trình nền tảng của Grinblatt, Titman, và Wermers (1995) về giao dịch theo động lượng của quỹ tương hỗ, của Wermers (2000) về phân rã hiệu quả hoạt động của quỹ, và của Yan (2008) về mối quan hệ giữa tỷ lệ vòng quay và lợi nhuận tương lai đều dựa vào việc đo lường chính xác các giao dịch, dòng vốn và tỷ lệ vòng quay của tổ chức.
Tại Hoa Kỳ, nghiên cứu này được hỗ trợ bởi hệ thống báo cáo 13F hằng quý bắt buộc do Ủy ban Chứng khoán và Giao dịch Hoa Kỳ (SEC) quản lý. Mọi nhà quản lý đầu tư tổ chức có ít nhất $100 triệu tài sản đủ điều kiện phải công bố danh mục cổ phiếu nắm giữ trong vòng 45 ngày kể từ cuối mỗi quý dương lịch. Cơ sở dữ liệu 13F của Thomson-Reuters (nay là Refinitiv), có thể truy cập qua WRDS, là hạ tầng dữ liệu chuẩn mực của dòng nghiên cứu này.
Thị trường cổ phiếu Việt Nam có bối cảnh tổ chức khác biệt căn bản. Chương này điều chỉnh phương pháp cốt lõi cho phù hợp với bối cảnh Việt Nam, giải quyết năm điểm khác biệt quan trọng:
Chế độ công bố thông tin. Việt Nam không có chế độ báo cáo hằng quý bắt buộc tương đương 13F. Việc công bố sở hữu là một bức tranh chắp vá gồm các báo cáo theo sự kiện (vượt ngưỡng 5%, 10%, v.v.), báo cáo thường niên/bán niên kèm danh sách cổ đông, và việc các sở giao dịch theo dõi sở hữu nước ngoài hằng ngày.
Sự kiện doanh nghiệp. Các công ty Việt Nam phát hành cổ tức bằng cổ phiếu và cổ phiếu thưởng với tần suất cực cao so với các công ty Mỹ. Một công ty có thể phát hành 20-30% cổ phiếu thưởng trong một năm, làm thay đổi căn bản số lượng cổ phiếu. Do đó, việc điều chỉnh số cổ phiếu là then chốt và không hề đơn giản.
Giới hạn sở hữu nước ngoài (FOL). Mức trần sở hữu nước ngoài mang tính ràng buộc, thường là 49% đối với hầu hết các ngành, 30% đối với ngân hàng, và 0% đối với một số ngành hạn chế, tạo ra một ràng buộc thể chế đặc thù. Khi một cổ phiếu tiến gần FOL, hoạt động mua của nhà đầu tư nước ngoài bị hạn chế một cách cơ học, làm sai lệch việc suy luận giao dịch theo cách chuẩn.
Sở hữu nhà nước. Chính phủ Việt Nam duy trì tỷ lệ sở hữu đáng kể tại nhiều công ty niêm yết thông qua Tổng công ty Đầu tư và Kinh doanh vốn nhà nước (SCIC) và các đơn vị nhà nước khác. Điều này tạo ra một nhóm sở hữu riêng biệt không có trong dữ liệu 13F của Mỹ.
Vi cấu trúc thị trường. Biên độ giá hằng ngày (\(\pm 7\%\) trên HOSE, \(\pm 10\%\) trên HNX, \(\pm 15\%\) trên UPCOM), thanh toán T+2 và việc không có bán khống đều ảnh hưởng đến cách các giao dịch của tổ chức chuyển hóa thành kết quả thị trường.
34.1 Đo lường sở hữu và giao dịch của tổ chức
Việc đo lường sở hữu và hoạt động giao dịch của tổ chức là mối quan tâm trọng tâm của tài chính thực nghiệm kể từ khi Gompers, Ishii, và Metrick (2003) ghi nhận sự trỗi dậy của các nhà đầu tư tổ chức. Cách tiếp cận này dựa trên việc so sánh các ảnh chụp danh mục nắm giữ qua các kỳ báo cáo liên tiếp để suy ra giao dịch. Nếu nhà quản lý \(j\) nắm giữ \(h_{j,i,t}\) cổ phiếu \(i\) tại thời điểm \(t\), thì giao dịch được suy ra là:
trong đó \(\Delta h_{j,i,t} > 0\) cho biết một giao dịch mua và \(\Delta h_{j,i,t} < 0\) cho biết một giao dịch bán. Cách lấy sai phân đơn giản này đòi hỏi các danh mục nắm giữ được quan sát theo các khoảng đều đặn (ví dụ: hằng quý), số cổ phiếu được điều chỉnh theo các sự kiện doanh nghiệp giữa các ngày báo cáo, và việc gia nhập và rời khỏi bộ dữ liệu được xử lý thích hợp.
Chen, Jegadeesh, và Wermers (2000) đưa ra khái niệm độ rộng sở hữu (tức là số lượng tổ chức nắm giữ một cổ phiếu) và chỉ ra rằng thay đổi về độ rộng dự báo lợi nhuận tương lai. Sias (2004) phân rã nhu cầu của tổ chức thành thành phần hành vi bầy đàn và thành phần thông tin. Yan (2008) liên hệ tỷ lệ vòng quay của quỹ với giao dịch dựa trên thông tin và ghi nhận rằng các quỹ có tỷ lệ vòng quay cao đạt hiệu quả vượt trội, qua đó thách thức quan điểm cho rằng tỷ lệ vòng quay phản ánh giao dịch nhiễu.
Cách phân loại này mang nhiều thông tin vì các giao dịch mua khởi tạo và bán chấm dứt thể hiện những quyết định danh mục rời rạc với nội dung thông tin khác với các điều chỉnh vị thế biên (Alexander, Cici, và Gibson 2007).
34.3 Các thước đo tỷ lệ vòng quay
Có ba định nghĩa chuẩn về tỷ lệ vòng quay đã được sử dụng trong các nghiên cứu:
Tỷ lệ vòng quay của Carhart (1997). Giá trị nhỏ hơn giữa tổng giao dịch mua và tổng giao dịch bán, được chuẩn hóa theo tài sản bình quân:
trong đó \(B_{j,i,t}\) và \(S_{j,i,t}\) là giá trị bằng tiền của các giao dịch mua và bán cổ phiếu \(i\) bởi nhà quản lý \(j\) trong quý \(t\), và \(A_{j,t}\) là tổng tài sản danh mục (Carhart 1997).
Tỷ lệ vòng quay điều chỉnh theo dòng vốn. Cộng thêm giá trị tuyệt đối của dòng vốn ròng để tính đến hoạt động giao dịch do dòng vốn thúc đẩy:
Mối quan hệ giữa các thước đo này phụ thuộc vào tương quan giữa giao dịch tùy ý và giao dịch do dòng vốn gây ra (Pástor và Stambaugh 2003).
34.4 Sở hữu tổ chức tại các thị trường mới nổi
Các nghiên cứu về thị trường mới nổi đã ghi nhận một số sự thật điển hình về sở hữu tổ chức khác với các phát hiện ở thị trường phát triển. Aggarwal và c.s. (2011) ghi nhận rằng sở hữu tổ chức nước ngoài cải thiện quản trị công ty tại các thị trường mới nổi. Riêng đối với Việt Nam, Phung và Mishra (2016) xem xét mối quan hệ giữa cơ cấu sở hữu và hiệu quả hoạt động của doanh nghiệp, trong khi Vo (2015) nghiên cứu tác động của sở hữu nước ngoài đối với thanh khoản thị trường cổ phiếu.
34.5 Dòng vốn ròng và phân tích nguồn gốc hiệu quả hoạt động
Dòng vốn ròng đo lượng tiền mới chảy vào hoặc chảy ra khỏi một quỹ:
trong đó \(R_{j,t}^p\) là lợi nhuận danh mục. Sự phân rã này, do Sirri và Tufano (1998) đề xuất, tách các thay đổi trong tài sản của quỹ thành lợi nhuận đầu tư và các quyết định phân bổ vốn của nhà đầu tư. Coval và Stafford (2007) chỉ ra rằng các giao dịch do dòng vốn thúc đẩy tạo ra áp lực giá, trong đó việc bán tháo của các quỹ bị rút vốn tạo ra lợi nhuận bất thường âm đáng kể.
34.6 Áp lực giá do dòng vốn gây ra và phép tính số học khi đo lường nó
Kết quả của Coval và Stafford (2007) không chỉ dừng lại ở một sự thật về bán tháo; nó nhanh chóng trở thành một chiến lược nhận diện. Nếu một quỹ tương hỗ đối mặt với các đợt rút vốn lớn phải huy động tiền mặt, và nếu các quyết định rút vốn của chính nhà đầu tư của quỹ đó không liên quan đến các yếu tố cơ bản của từng cổ phiếu mà quỹ tình cờ nắm giữ, thì áp lực bán truyền sang các cổ phiếu đó là một nguồn biến thiên giá có khả năng trực giao với giá trị doanh nghiệp. Edmans, Goldstein, và Jiang (2012) biến logic này thành một công cụ cho biến động giá phi cơ bản và dùng nó để chỉ ra rằng giá bị suy giảm thu hút các lời chào mua thâu tóm. Khan, Kogan, và Serafeim (2012) áp dụng nó cho thời điểm phát hành cổ phiếu thứ cấp, D. Lou (2012) xây dựng thước đo giao dịch do dòng vốn gây ra (FIT) có liên quan chặt chẽ và dùng nó để giải thích khả năng dự báo lợi nhuận, còn X. Lou và Wang (2018) và Dessaint và c.s. (2019) dùng áp lực do rút vốn để đặt câu hỏi liệu các nhà quản lý có học hỏi từ giá khi đưa ra quyết định đầu tư hay không. Trong vòng một thập kỷ, thước đo này đã trở thành một trong những công cụ được sử dụng nhiều nhất trong tài chính doanh nghiệp thực nghiệm, xuất hiện trong các nghiên cứu về đầu tư, đổi mới, công bố thông tin, chi trả cổ tức, thay đổi CEO và quản trị công ty.
34.6.1 Thước đo chuẩn
Cách xây dựng quen thuộc là biến áp lực giá của quỹ tương hỗ của Edmans, Goldstein, và Jiang (2012):
trong đó \(j\) là chỉ số của các quỹ tương hỗ, \(F_{j,t}\) là tổng dòng vốn ròng của quỹ \(j\) trong quý \(t\), \(\text{TA}_{j,t-1}\) là tổng tài sản của quỹ vào cuối quý trước, \(\text{SHARES}_{i,j,t-1}\) là số cổ phiếu \(i\) mà quỹ nắm giữ vào cuối quý \(t-1\), \(\text{PRC}_{i,t-1}\) là giá cổ phiếu tại ngày đó, và \(\text{VOL}_{i,t}\) là tổng khối lượng giao dịch tính bằng tiền của cổ phiếu trong quý \(t\). Phép tổng chỉ chạy trên các quỹ có dòng vốn rút ra vượt một ngưỡng (5% tài sản trong bài báo gốc, dòng vốn ở phân vị thứ 10 trong phần lớn các nghiên cứu sau đó), và các quỹ ngành bị loại trừ để dòng vốn rút ra không thể phản ánh quan điểm về một ngành cụ thể. Tử số là giá trị bằng tiền mà quỹ sẽ bán nếu thanh lý danh mục theo tỷ lệ để đáp ứng yêu cầu rút vốn; mẫu số chuẩn hóa giao dịch giả định đó theo thanh khoản của cổ phiếu, với lập luận rằng cùng một đợt bán bắt buộc sẽ làm một cổ phiếu mỏng thanh khoản biến động mạnh hơn một cổ phiếu có thanh khoản cao.
34.6.2 Thước đo thực sự chứa đựng điều gì
Wardlaw (2020) chỉ ra rằng Phương trình 34.6 không phải là một tỷ số như vẻ bề ngoài. Viết khối lượng giao dịch bằng tiền của quý bằng khối lượng cổ phiếu nhân với giá bình quân gia quyền theo khối lượng mà cổ phiếu thực sự được giao dịch, \(\text{VOL}_{i,t} = \text{SHVOL}_{i,t} \times \bar{P}_{i,t}\). Thay vào Phương trình 34.6 và biến đổi ta được
trong đó \(\bar{r}_{i,t} \equiv \bar{P}_{i,t} / \text{PRC}_{i,t-1} - 1\) là lợi nhuận thực hiện của cổ phiếu từ cuối quý \(t-1\) đến mức giá bình quân mà cổ phiếu được chuyển nhượng trong quý \(t\). Thước đo là tích của một số hạng áp lực giao dịch thực sự và một số hạng chính là lợi nhuận đồng thời. Vì các quan sát rút vốn có \(F_{j,t} < 0\), một cổ phiếu có giá giảm trong quý sẽ nhận \(\text{MFFlow}\) âm hơn một cách cơ học bất kể các quỹ tương hỗ đã làm gì, và một cổ phiếu có giá tăng sẽ nhận giá trị ít âm hơn. Do đó, việc hồi quy lợi nhuận quý \(t\) theo \(\text{MFFlow}_{i,t}\) cho ra đúng dấu mà giả thuyết bán tháo dự báo, ngay cả trong một thế giới mà tác động giá thực sự của việc bán do dòng vốn gây ra hoàn toàn bằng không. Đây là điều mà bài báo muốn nói khi kết luận rằng cách tiếp cận chuẩn tạo ra một thước đo vô tình là hàm trực tiếp của lợi nhuận thực hiện thực tế của cổ phiếu trong quý có dòng vốn rút ra.
Hai hệ quả thực nghiệm theo sau, và hệ quả thứ hai gây tổn hại nhiều hơn. Thứ nhất, khi loại bỏ phần lợi nhuận bị gộp vào, dòng vốn rút ra chỉ gây ra một mức giảm nhỏ đồng thời trong lợi nhuận. Thứ hai, mức giảm đó không được theo sau bởi sự đảo chiều nào. Sự đảo chiều chưa bao giờ là một kiểm tra độ vững mang tính trang trí: nó là bằng chứng nhận diện. Một biến động giá do người bán có động cơ thanh khoản gây ra được cho là tạm thời và do đó sẽ tự đảo ngược, trong khi biến động giá do thông tin gây ra là vĩnh viễn. Nếu không có sự đảo chiều, kiểu hình thực nghiệm tương đương về mặt quan sát với việc các quỹ bán những cổ phiếu thực sự đang xấu đi, và giả định loại trừ mà toàn bộ chương trình nghiên cứu dựa vào mất đi bằng chứng hỗ trợ. Wardlaw (2020) báo cáo rằng nhiều kết quả đã được thiết lập trong dòng nghiên cứu này không còn đứng vững khi thước đo được làm sạch.
Cách khắc phục rất dễ phát biểu. Xây dựng thước đo trong không gian cổ phiếu: tính số cổ phiếu giả định bị bán và chuẩn hóa theo số cổ phiếu đang lưu hành tại \(t-1\) (tỷ số dòng vốn trên lượng cổ phiếu), hoặc theo khối lượng cổ phiếu giao dịch, và giữ mọi mức giá trong cách xây dựng ở ngày \(t-1\). Quy tắc chung mà ví dụ này minh họa đáng được mang theo xa hơn dòng nghiên cứu về dòng vốn: không bao giờ lập một tỷ số có tử số là một đại lượng được định giá theo giá ngày \(t-1\) và mẫu số là một đại lượng được định giá theo giá ngày \(t\), vì khi đó tỷ số theo cấu trúc chứa lợi nhuận giữa hai ngày. Chính phép tính số học này giải thích vì sao một mức giá nằm ở mẫu số của bất kỳ thước đo nào đều đáng được xem xét kỹ, tỷ số phi thanh khoản Amihud được thảo luận trong Mục 14.3.1.1 là ví dụ hiển nhiên. Sự khác biệt ở đó nằm ở mục đích chứ không phải đại số: Amihud là một thước đo mô tả có mẫu số cố ý tính bằng tiền, trong khi MFFlow là một công cụ mà toàn bộ luận điểm của nó là tính trực giao với kết quả mà nó được dùng để giải thích.
34.6.3 Vấn đề thứ hai, độc lập: thiên lệch chọn lọc
Ngay cả một thước đo được chuẩn hóa đúng cũng thừa hưởng một giả định hành vi sai trong dữ liệu. Phương trình 34.6 gán các giao dịch bằng cách giả định quỹ bị rút vốn thanh lý theo tỷ lệ, chính điều này làm cho giao dịch giả định độc lập với thông tin của nhà quản lý. Berger (2023) chỉ ra rằng các nhà quản lý không hành xử như vậy: khi đối mặt với các yêu cầu rút vốn, họ ưu tiên bán các vị thế có thanh khoản cao, hoạt động tốt và giữ lại các vị thế thua lỗ kém thanh khoản. Do đó, giả định theo tỷ lệ phân bổ sai áp lực bán hàm ý sang các công ty mà các nhà quản lý có hệ thống từ chối bán, tức là những công ty hoạt động kém, kém thanh khoản và có tăng trưởng thấp. Các mô phỏng trong bài báo đó cho thấy thiên lệch chọn lọc phát sinh làm tăng khoảng gấp đôi độ lớn của các hệ số hồi quy ước lượng và các cách khắc phục chuẩn không loại bỏ được nó; sau khi tính đến thiên lệch này, tác giả không tìm thấy hiệu ứng phản hồi nào chạy từ việc định giá sai do dòng vốn gây ra đến đầu tư, phát hành cổ phiếu hay chi trả cổ tức của doanh nghiệp. Đọc cùng nhau, Wardlaw (2020) và Berger (2023) nêu cùng một luận điểm phương pháp luận từ hai hướng: một giao dịch giả định được xây dựng từ một ảnh chụp danh mục nắm giữ và một đồng nhất thức kế toán là một mô hình về hành vi của tổ chức, không phải là một quan sát về hành vi đó, và cả việc chuẩn hóa lẫn giả định hành vi đều phải được bảo vệ.
Một lưu ý thứ ba nằm ở thượng nguồn của cả hai. Li và Zheng (2025) ghi nhận rằng chính các dòng vốn của quỹ đưa vào tử số cũng là các ước lượng. Hầu hết các nghiên cứu suy ra dòng vốn từ thay đổi trong tổng tài sản ròng vượt quá lợi nhuận của quỹ, như trong Phương trình 34.5, thay vì từ dòng vốn được báo cáo, và hai cách này khác nhau do các khoản phân phối được tái đầu tư, quy ước về thời điểm áp dụng trong kỳ và cách xử lý việc sáp nhập quỹ. Các thước đo dòng vốn khác nhau tạo ra những suy luận khác biệt đáng kể về mối quan hệ giữa dòng vốn và hiệu quả hoạt động cũng như về cách dòng vốn làm dịch chuyển giá chứng khoán, điều này khiến việc lựa chọn thước đo là điều cần được ghi chép lại thay vì thừa hưởng.
34.6.4 Điều này có ý nghĩa gì đối với nghiên cứu về Việt Nam
Thiết kế của Mỹ khó có thể áp dụng trực tiếp, vì lý do đã nêu ở đầu chương này: không có tương đương 13F, nên bảng dữ liệu danh mục nắm giữ hằng quý mà Phương trình 34.6 đòi hỏi không tồn tại đối với hầu hết các nhà đầu tư tổ chức. Ngành quỹ mở trong nước có công bố danh mục nắm giữ còn nhỏ so với vốn hóa thị trường, nên ngay cả một đợt bán giả định được xây dựng đúng cũng chỉ chiếm một phần không đáng kể trong khối lượng giao dịch của một cổ phiếu HOSE điển hình. Đó là công thức cho một biến công cụ yếu, bất kể được chuẩn hóa cẩn thận đến đâu.
Quan sát triển vọng hơn là Việt Nam có một bối cảnh trong đó giao dịch do dòng vốn gây ra không cần phải được gán một cách giả định. Các quỹ hoán đổi danh mục (ETF) bám sát các chỉ số của Việt Nam, trong nước là các quỹ DCVFMVN30, DCVFM VNDIAMOND và SSIAM VNFIN LEAD, còn ngoài nước là các ETF VanEck Vietnam và Fubon FTSE Vietnam, công bố các lệnh tạo lập và mua lại hằng ngày dựa trên các rổ cổ phiếu được công bố và theo quy tắc rõ ràng. Do đó, giao dịch mà một lệnh tạo lập hoặc mua lại buộc phải thực hiện là quan sát được và mang tính cơ học thay vì được giả định: quỹ phải giao dịch cả rổ, theo trọng số của chỉ số, theo một lịch trình đã biết. Điều này loại bỏ giả định theo tỷ lệ mà Berger (2023) phê phán, vì đối với một ETF chỉ số, giao dịch theo tỷ lệ chính là điều thực sự xảy ra, và loại bỏ vấn đề xấp xỉ theo quý, vì dòng vốn là hằng ngày. Các dòng vốn vào lớn do nhà đầu tư cá nhân Đài Loan thúc đẩy vào ETF Fubon FTSE Vietnam trong giai đoạn 2021 đến 2023 là một cú sốc nhu cầu đặc biệt rõ ràng, ở chỗ khẩu vị của nhà đầu tư cá nhân Đài Loan đối với một sản phẩm Việt Nam khó có thể được gắn với các yếu tố cơ bản của bất kỳ công ty Việt Nam riêng lẻ nào. Họ VNDIAMOND thú vị vì lý do ngược lại: các cổ phiếu thành phần của nó được chọn chính vì chúng nằm tại hoặc gần giới hạn sở hữu nước ngoài, nên nhu cầu do ETF thúc đẩy tương tác trực tiếp với ràng buộc đang có hiệu lực được phân tích trong Mục 41.1 và Mục 41.2.
Biên độ giá của Việt Nam đưa ra thêm một lý do nữa để không đặt khối lượng giao dịch ở mẫu số, và lý do này đặc thù cho thị trường này. Khối lượng giao dịch sụt giảm mạnh vào các ngày chạm biên và có thể tiến gần về không khi một cổ phiếu bị khóa ở mức giá trần hoặc sàn với hàng đợi lệnh chưa được khớp, đúng vào lúc lợi nhuận trong ngày cực đoan nhất. Do đó, một thước đo áp lực được chuẩn hóa theo khối lượng sẽ bùng nổ đúng tại các quan sát chi phối hồi quy lợi nhuận, khiến mẫu số có thêm một kênh thứ hai để đưa biến kết quả vào. Mức độ thường xuyên của hiện tượng này trên HOSE và HNX được ghi nhận trong Mục 37.2, và hiện tượng lợi nhuận bị chặn (kiểm duyệt dữ liệu) mà nó gây ra được phân tích trong Mục 37.3. Số cổ phiếu đang lưu hành, vốn không thể bị thay đổi bởi bất kỳ lượng giao dịch nào trong một ngày nhất định, là biến chuẩn hóa an toàn hơn ở đây, vì cùng lý do mà nó an toàn hơn trong dữ liệu Mỹ.
Sáu quy tắc thực tiễn sau đây dành cho bất kỳ ai xây dựng thước đo áp lực dòng vốn trên dữ liệu Việt Nam.
Xây dựng thước đo theo số cổ phiếu và chuẩn hóa theo số cổ phiếu đang lưu hành tại \(t-1\). Không chuẩn hóa theo khối lượng giao dịch bằng tiền đồng thời.
Gắn mọi mức giá trong cách xây dựng với ngày \(t-1\). Nếu một mức giá của kỳ \(t\) xuất hiện ở bất kỳ đâu trong công thức, thước đo sẽ chứa lợi nhuận của kỳ \(t\).
Báo cáo kiểm định đảo chiều như một kết quả chính, không phải một kiểm tra độ vững. Đó là bằng chứng duy nhất phân biệt áp lực phi cơ bản với thông tin, và sau Wardlaw (2020), một kết quả về áp lực dòng vốn thiếu nó thì không đáng tin.
Xác thực giao dịch được gán so với giao dịch quan sát được bất cứ khi nào có thể. Việt Nam công bố giá trị mua và bán ròng của nhà đầu tư nước ngoài hằng ngày theo mã cổ phiếu, và các rổ ETF được công bố; cả hai đều cho phép kiểm tra xem hành vi giao dịch được giả định có khớp với các giao dịch thực hiện hay không.
Chạy kiểm định placebo với dòng vốn kỳ vọng. Thay dòng vốn thực hiện bằng dòng vốn dự báo, theo tinh thần của cách xây dựng giao dịch do dòng vốn kỳ vọng gây ra trong D. Lou (2012), để kiểm tra xem kết quả được thúc đẩy bởi cú sốc dòng vốn hay bởi cơ cấu nắm giữ.
Báo cáo độ mạnh của giai đoạn một. Với mức sở hữu tổ chức còn nông như ở Việt Nam, biến công cụ yếu là kỳ vọng mặc định và phải được loại trừ một cách tường minh.
Cần lưu ý dòng nghiên cứu này đã dịch chuyển đến đâu. Cuộc tranh luận về đo lường ở trên, về bản chất, là về việc một lượng giao dịch bắt buộc nhất định làm dịch chuyển giá bao nhiêu, tức là độ dốc của đường cầu tổng hợp. Câu hỏi đó hiện được tiếp cận trực tiếp bằng cách tiếp cận hệ thống cầu của Koijen và Yogo (2019) và giả thuyết thị trường kém co giãn của Gabaix và Koijen (2021), vốn suy ra tác động giá của dòng vốn từ cấu trúc nhu cầu của nhà đầu tư thay vì từ một biến công cụ duy nhất. Greenwood và Thesmar (2011), với thước đo mức mong manh tổng hợp biến động dòng vốn của các chủ sở hữu cổ phiếu thay vì giá trị thực hiện của một quý, và Frazzini và Lamont (2008), về các hệ quả lợi nhuận của việc nhà đầu tư cá nhân đuổi theo dòng vốn, là những bổ sung hữu ích từ truyền thống trước đó. Đối với một nhà nghiên cứu mới bước vào lĩnh vực này, trình tự đáng đọc là Coval và Stafford (2007) và Edmans, Goldstein, và Jiang (2012) cho thiết kế gốc, Wardlaw (2020) và Berger (2023) cho những gì đã sai với nó, và Koijen và Yogo (2019) và Gabaix và Koijen (2021) cho khung phân tích phần lớn đã thay thế nó.
35 Hạ tầng dữ liệu
Bảng 35.1 tóm tắt các bộ dữ liệu được sử dụng trong chương này.
Bảng 35.1: Các bộ dữ liệu DataCore.vn sử dụng trong chương này
Chúng ta bắt đầu bằng việc định nghĩa một bộ đọc dữ liệu thống nhất xử lý việc tải tệp, phân tích ngày tháng và kiểm tra cơ bản:
DataCoreReader: Unified Data Access Layer
@dataclassclass DataCoreReader:""" Unified reader for DataCore.vn datasets stored locally. Supports Parquet (recommended) and CSV formats. Implements lazy loading with caching to minimize memory footprint. Parameters ---------- data_dir : str or Path Directory containing DataCore.vn data files. file_format : str File format: 'parquet' or 'csv'. Examples -------- >>> dc = DataCoreReader('/data/datacore', file_format='parquet') >>> prices = dc.prices >>> ownership = dc.ownership """ data_dir: Path file_format: str='parquet' _cache: Dict[str, pd.DataFrame] = field( default_factory=dict, repr=False ) FILE_MAP: Dict[str, str] = field(default_factory=lambda: {'prices': 'stock_prices','ownership': 'ownership_structure','major_shareholders': 'major_shareholders','corporate_actions': 'corporate_actions','company_profile': 'company_profile','financials': 'financial_statements','foreign_ownership': 'foreign_ownership','fund_holdings': 'fund_holdings', }, repr=False)def __post_init__(self):self.data_dir = Path(self.data_dir)ifnotself.data_dir.exists():raiseFileNotFoundError(f"Data directory not found: {self.data_dir}" )def _read(self, key: str) -> pd.DataFrame:"""Read and cache a dataset with automatic date parsing."""if key inself._cache:returnself._cache[key] fname =self.FILE_MAP.get(key, key) filepath =self.data_dir /f"{fname}.{self.file_format}"ifnot filepath.exists():raiseFileNotFoundError(f"Dataset not found: {filepath}\n"f"Available: "f"{list(self.data_dir.glob(f'*.{self.file_format}'))}" )ifself.file_format =='parquet': df = pd.read_parquet(filepath)else: df = pd.read_csv(filepath, parse_dates=True)# Auto-detect and parse date columns date_cols = ['date', 'ex_date', 'record_date', 'period','report_date', 'listing_date' ]for col in df.columns:if col.lower() in date_cols or'date'in col.lower():try: df[col] = pd.to_datetime(df[col])except (ValueError, TypeError):passself._cache[key] = dfprint(f" Loaded {key}: {len(df):,} rows x {len(df.columns)} cols")return df@propertydef prices(self) -> pd.DataFrame:returnself._read('prices')@propertydef ownership(self) -> pd.DataFrame:returnself._read('ownership')@propertydef major_shareholders(self) -> pd.DataFrame:returnself._read('major_shareholders')@propertydef corporate_actions(self) -> pd.DataFrame:returnself._read('corporate_actions')@propertydef company_profile(self) -> pd.DataFrame:returnself._read('company_profile')@propertydef foreign_ownership(self) -> pd.DataFrame:returnself._read('foreign_ownership')@propertydef fund_holdings(self) -> pd.DataFrame:returnself._read('fund_holdings')def clear_cache(self): n =len(self._cache)self._cache.clear()print(f" Cleared {n} cached datasets")# Initialize:# dc = DataCoreReader('/path/to/datacore_data', file_format='parquet')
36 Xử lý giá cổ phiếu và lợi nhuận
Bước đầu tiên xử lý dữ liệu cổ phiếu để thu được giá đã điều chỉnh, số cổ phiếu đang lưu hành và lợi nhuận hằng quý.
36.1 Trích xuất và điều chỉnh dữ liệu giá
Dữ liệu cổ phiếu Việt Nam đòi hỏi điều chỉnh cẩn thận cho các sự kiện doanh nghiệp diễn ra thường xuyên. Khác với Mỹ, nơi CRSP cung cấp hệ số điều chỉnh tích lũy (cfacpr, cfacshr), tại Việt Nam chúng ta phải xây dựng các hệ số điều chỉnh từ lịch sử sự kiện doanh nghiệp.
Các sự kiện doanh nghiệp tại Việt Nam
Các công ty Việt Nam thường thực hiện các sự kiện doanh nghiệp sau, mỗi sự kiện đòi hỏi điều chỉnh số cổ phiếu và/hoặc giá:
Cổ tức bằng cổ phiếu (cổ tức bằng cổ phiếu): ví dụ, cổ tức bằng cổ phiếu 20% nghĩa là 100 cổ phiếu trở thành 120 cổ phiếu
Cổ phiếu thưởng (cổ phiếu thưởng): cổ phiếu miễn phí được phân phối từ lợi nhuận giữ lại
Phát hành quyền mua (phát hành quyền mua): quyền mua cổ phiếu mới với giá ưu đãi
Chia tách/gộp cổ phiếu (chia/gộp cổ phiếu): hiếm gặp nhưng đôi khi được sử dụng
Corporate Action Adjustment Factor Construction
def build_adjustment_factors( corporate_actions: pd.DataFrame,) -> pd.DataFrame:""" Construct cumulative share adjustment factors from corporate actions. This is the Vietnamese equivalent of CRSP's cfacshr factor. For each ticker, we compute a cumulative product of adjustment ratios from corporate actions, working forward in time. The adjustment factor at date t converts historical share counts to be comparable with current (post-action) share counts: shares_adjusted_t = shares_raw_t * cfacshr_t Parameters ---------- corporate_actions : pd.DataFrame Corporate actions with columns: ticker, ex_date, action_type, ratio. The ratio field represents: - Stock dividend 20%: ratio = 1.20 - 2:1 stock split: ratio = 2.00 - Bonus shares 10%: ratio = 1.10 Returns ------- pd.DataFrame Adjustment factors: ticker, ex_date, cfacshr (cumulative). """ share_actions = corporate_actions[ corporate_actions['action_type'].isin(['stock_dividend', 'bonus_shares', 'stock_split','reverse_split', 'rights_issue' ]) ].copy()if share_actions.empty:return pd.DataFrame(columns=['ticker', 'ex_date', 'cfacshr']) share_actions = share_actions.sort_values(['ticker', 'ex_date']) share_actions['cfacshr'] = ( share_actions .groupby('ticker')['ratio'] .cumprod() )return share_actions[['ticker', 'ex_date', 'cfacshr']].reset_index( drop=True )def get_cfacshr_at_date( ticker: str, date: pd.Timestamp, adj_factors: pd.DataFrame,) ->float:""" Look up the cumulative share adjustment factor for a given ticker and date. Returns 1.0 if no corporate actions occurred. """ mask = ( (adj_factors['ticker'] == ticker) & (adj_factors['ex_date'] <= date) ) subset = adj_factors.loc[mask]if subset.empty:return1.0return subset.iloc[-1]['cfacshr']def adjust_shares_between_dates( shares: float, ticker: str, date_from: pd.Timestamp, date_to: pd.Timestamp, adj_factors: pd.DataFrame,) ->float:""" Adjust a share count observed at date_from to be comparable with shares observed at date_to, accounting for all intervening corporate actions. Example ------- >>> # Investor held 1000 shares on 2023-01-01 >>> # A 20% stock dividend occurred on 2023-03-15 >>> adjust_shares_between_dates( ... 1000, 'VNM', ... pd.Timestamp('2023-01-01'), ... pd.Timestamp('2023-06-30'), adj_factors ... ) 1200.0 """ factor_from = get_cfacshr_at_date(ticker, date_from, adj_factors) factor_to = get_cfacshr_at_date(ticker, date_to, adj_factors) relative_factor = factor_to / factor_fromreturn shares * relative_factor
36.2 Xử lý giá theo tháng và theo quý
Step 1: Price Data Processing
def process_prices( prices: pd.DataFrame, adj_factors: pd.DataFrame, begdate: str='2010-01-01', enddate: str='2024-12-31',) -> Tuple[pd.DataFrame, pd.DataFrame]:""" Process raw DataCore.vn price data into analysis-ready format. Block logic: 1. Filter to date range 2. Compute adjusted prices and shares outstanding 3. Compute quarterly compounded returns 4. Create forward quarterly returns (shifted one quarter) Parameters ---------- prices : pd.DataFrame Raw price data with: ticker, date, close, adjusted_close, volume, shares_outstanding. adj_factors : pd.DataFrame Corporate action adjustment factors. begdate, enddate : str Sample period boundaries. Returns ------- Tuple[pd.DataFrame, pd.DataFrame] (price_quarterly, qret): quarter-end observations with adjusted price, total shares, and forward quarterly return. """ price = prices[ (prices['date'] >= begdate) & (prices['date'] <= enddate) ].copy()# Month-end and quarter-end dates price['mdate'] = price['date'] + pd.offsets.MonthEnd(0) price['qdate'] = price['date'] + pd.offsets.QuarterEnd(0)# Adjusted priceif'adjusted_close'in price.columns: price['p'] = price['adjusted_close']else: price['p'] = price['close']# Total shares outstanding price['tso'] = price['shares_outstanding']# Market capitalization (millions VND) price['mcap'] = price['p'] * price['tso'] /1e6# Filter out zero shares price = price[price['tso'] >0].copy()# Compute daily returns if not presentif'ret'notin price.columns: price = price.sort_values(['ticker', 'date']) price['ret'] = price.groupby('ticker')['p'].pct_change() price['ret'] = price['ret'].fillna(0) price['logret'] = np.log(1+ price['ret'])# ---- Quarterly compounded returns ---- qret = ( price .groupby(['ticker', 'qdate'])['logret'] .sum() .reset_index() ) qret['qret'] = np.exp(qret['logret']) -1# Shift qdate back one quarter: make qret a *forward* return qret['qdate'] = qret['qdate'] + pd.offsets.QuarterEnd(-1) qret = qret.drop(columns=['logret'])# ---- Quarter-end observations ---- price_q = price[price['qdate'] == price['mdate']].copy() price_q = price_q[['qdate', 'ticker', 'p', 'tso', 'mcap']].copy()# Merge forward quarterly return price_q = price_q.merge(qret, on=['ticker', 'qdate'], how='left')# Build cfacshr lookup at each quarter-end price_q['cfacshr'] = price_q.apply(lambda row: get_cfacshr_at_date( row['ticker'], row['qdate'], adj_factors ), axis=1 )return price_q, qret
Tối ưu hóa hiệu năng
Hàm get_cfacshr_at_date sử dụng phép tra cứu theo từng dòng nên có thể chậm đối với các bộ dữ liệu lớn. Để dùng trong thực tế với hàng triệu dòng, hãy vector hóa bằng pd.merge_asof():
trong đó \(\mathcal{J}\) là tập hợp các nhà đầu tư tổ chức và \(TSO_{i,t}\) là tổng số cổ phiếu đang lưu hành. Ở Việt Nam, chúng ta tính các tỷ lệ riêng cho từng loại hình sở hữu:
def compute_io_ratios( holdings: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Compute IO ratios by type for each stock-quarter.""" agg = ( holdings .groupby(['ticker', 'rdate', 'owner_type'])['shares_adj'] .sum() .reset_index() ) io_wide = agg.pivot_table( index=['ticker', 'rdate'], columns='owner_type', values='shares_adj', fill_value=0 ).reset_index() io_wide.columns = [ c if c in ['ticker', 'rdate']elsef'shares_{c.lower().replace(" ", "_")}'for c in io_wide.columns ] io_wide = io_wide.merge( price_q[['ticker', 'qdate', 'tso']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) share_cols = [c for c in io_wide.columns if c.startswith('shares_')]for col in share_cols: ratio_name = col.replace('shares_', 'io_') io_wide[ratio_name] = io_wide[col] / io_wide['tso'] inst_cols = [ c for c in io_wide.columnsif c.startswith('shares_')and'individual'notin cand'treasury'notin c ] io_wide['io_total_inst'] = ( io_wide[inst_cols].sum(axis=1) / io_wide['tso'] )return io_wide
38.2 Mức độ tập trung sở hữu: chỉ số Herfindahl-Hirschman
trong đó \(N_{i,t}\) là số lượng cổ đông. HHI nằm trong khoảng từ \(1/N_{i,t}\) (phân bổ đều) đến 1 (một cổ đông duy nhất). Ở Việt Nam, sở hữu có xu hướng tập trung cao do các khối cổ phần lớn của nhà nước và gia đình sáng lập.
Đối với các đơn vị nhà nước hoặc các cổ đông sở hữu chéo trong doanh nghiệp, “dòng vốn ròng” không nhất thiết phản ánh các quyết định đầu tư. Những thay đổi sở hữu nhà nước thường là kết quả của chính sách của Chính phủ (cổ phần hóa, các chương trình thoái vốn). Việc diễn giải cần tính đến bối cảnh thể chế.
Bảng 40.1: Thống kê mô tả của ba thước đo tỷ lệ vòng quay theo các loại nhà đầu tư tổ chức tại Việt Nam. Vòng quay 1 theo Carhart (1997), Vòng quay 2 cộng thêm giá trị tuyệt đối của dòng vốn ròng, và Vòng quay 3 sử dụng định nghĩa đối xứng.
trong đó \(B_{i,t}\) là số nhà quản lý mua cổ phiếu \(i\) trong quý \(t\), \(S_{i,t}\) là số nhà quản lý bán, và \(p_t\) là tỷ lệ người mua kỳ vọng khi giao dịch độc lập.
Theo Alexander, Cici, và Gibson (2007), tỷ số InfoTrade đo tỷ trọng giá trị giao dịch bằng tiền đến từ các quyết định gia nhập/rút lui so với các điều chỉnh vị thế:
def compute_info_trade_ratio( trades: pd.DataFrame, price_q: pd.DataFrame) -> pd.DataFrame:"""Compute info trade ratio for each stock-quarter.""" _t = trades.merge( price_q[['ticker', 'qdate', 'p']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _t['dollar_trade'] = _t['trade'].abs() * _t['p'] /1e6 _t['is_discrete'] = _t['buysale'].isin([1, -1]) info = _t.groupby(['ticker', 'rdate']).apply(lambda g: pd.Series({'discrete_vol': g.loc[g['is_discrete'], 'dollar_trade'].sum(),'total_vol': g['dollar_trade'].sum(), }) ).reset_index() info['info_trade_ratio'] = ( info['discrete_vol'] / info['total_vol'] ).clip(0, 1)return info
44 Các ứng dụng thực nghiệm
44.1 Ứng dụng 1: Thay đổi sở hữu tổ chức và lợi nhuận tương lai
Chúng ta kiểm định xem thay đổi trong sở hữu tổ chức có dự báo lợi nhuận cổ phiếu tương lai hay không (Chen, Jegadeesh, và Wermers 2000) thông qua các hồi quy Fama-MacBeth:
Ví dụ: Giao dịch ảo từ cổ tức bằng cổ phiếu chưa điều chỉnh
Vinamilk (VNM) phát hành cổ tức bằng cổ phiếu 20% với ngày giao dịch không hưởng quyền là 15 tháng 3 năm 2023.
Quý 4 2022: Quỹ X nắm giữ 1.000.000 cổ phiếu VNM
Quý 1 2023: Quỹ X nắm giữ 1.200.000 cổ phiếu VNM
Không điều chỉnh: Suy ra giao dịch mua +200.000 cổ phiếu (BS = +2) Có điều chỉnh: Số cổ phiếu nắm giữ kỳ trước trở thành 1.200.000 cổ phiếu đã điều chỉnh, giao dịch = 0
Giao dịch ảo này làm tăng tỷ lệ vòng quay đo được và tạo ra các tín hiệu mua giả.
45.1.2 Lệch thời điểm công bố
Ngày công bố sở hữu của Việt Nam có thể không trùng với ngày cuối quý dương lịch. Quy trình của chúng ta xử lý điều này bằng cách căn chỉnh mọi công bố về ngày cuối quý gần nhất.
45.1.3 Đổi tên và sáp nhập pháp nhân
Các tổ chức Việt Nam thường xuyên đổi tên. Nếu không có mã định danh ổn định, cùng một pháp nhân có thể xuất hiện như hai cổ đông khác nhau, tạo ra các lần gia nhập/rút lui ảo. Chúng tôi khuyến nghị duy trì một bảng ánh xạ pháp nhân chủ.
Chương này phát triển một khung để tính các giao dịch, dòng vốn và tỷ lệ vòng quay của tổ chức trên thị trường cổ phiếu Việt Nam. Các đóng góp chính gồm:
Điều chỉnh sự kiện doanh nghiệp cho cổ tức bằng cổ phiếu và cổ phiếu thưởng diễn ra thường xuyên tại Việt Nam, ngăn chặn các giao dịch ảo làm nhiễu phép lấy sai phân chuẩn.
Hệ phân loại sở hữu bốn nhóm (nhà nước, tổ chức nước ngoài, tổ chức trong nước, cá nhân) nắm bắt bức tranh sở hữu độc đáo của Việt Nam.
Phân tích mức sử dụng FOL để nghiên cứu các ràng buộc sở hữu nước ngoài không có ở các thị trường phát triển.
Xử lý công bố không đều với việc tách khoảng trống đúng cách thành các giao dịch bán chấm dứt và mua khởi tạo.
Các mở rộng nâng cao gồm hành vi bầy đàn, tính bền vững của nhu cầu và phân rã hàm lượng thông tin.
Quy trình tạo ra nhiều bộ dữ liệu đầu ra (Bảng 46.1)
Bảng 46.1: Tóm tắt các bộ dữ liệu đầu ra của quy trình
Đầu ra
Đơn vị quan sát
Biến chính
Trường hợp sử dụng
holdings
Cổ đông x Mã x Quý
shares_adj, owner_type
Sở hữu theo lát cắt ngang
io_ratios
Mã x Quý
io_state, io_foreign, etc.
Quản trị công ty, thanh khoản
trades
Cổ đông x Mã x Quý
trade, buysale
Giao dịch có thông tin, hành vi bầy đàn
aggregates
Cổ đông x Quý
assets, turnover, netflows
Hiệu quả hoạt động của quỹ, dòng vốn
fol_analytics
Mã x Ngày
fol_utilization, foreign_room
Phần bù FOL, đầu tư nước ngoài
Aggarwal, Reena, Isil Erel, Miguel Ferreira, và Pedro Matos. 2011. “Does governance travel around the world? Evidence from institutional investors”. Journal of financial economics 100 (1): 154–81.
Alexander, Gordon J, Gjergji Cici, và Scott Gibson. 2007. “Does motivation matter when assessing trade performance? An analysis of mutual funds”. The Review of Financial Studies 20 (1): 125–50.
Berger, Elizabeth A. 2023. “Selection bias in mutual fund fire sales”. Journal of Financial and Quantitative Analysis 58 (3): 1039–77. https://doi.org/10.1017/S0022109022001223.
Chen, Hsiu-Lang, Narasimhan Jegadeesh, và Russ Wermers. 2000. “The value of active mutual fund management: An examination of the stockholdings and trades of fund managers”. Journal of Financial and quantitative Analysis 35 (3): 343–68.
Coval, Joshua, và Erik Stafford. 2007. “Asset fire sales (and purchases) in equity markets”. Journal of Financial Economics 86 (2): 479–512.
Dessaint, Olivier, Thierry Foucault, Laurent Frésard, và Adrien Matray. 2019. “Noisy stock prices and corporate investment”. The Review of Financial Studies 32 (7): 2625–72. https://doi.org/10.1093/rfs/hhy115.
Edmans, Alex, Itay Goldstein, và Wei Jiang. 2012. “The real effects of financial markets: The impact of prices on takeovers”. The Journal of Finance 67 (3): 933–71. https://doi.org/10.1111/j.1540-6261.2012.01738.x.
Frazzini, Andrea, và Owen A. Lamont. 2008. “Dumb money: Mutual fund flows and the cross-section of stock returns”. Journal of Financial Economics 88 (2): 299–322. https://doi.org/10.1016/j.jfineco.2007.07.001.
Gabaix, Xavier, và Ralph SJ Koijen. 2021. “In search of the origins of financial fluctuations: The inelastic markets hypothesis”. National Bureau of Economic Research.
Gompers, Paul, Joy Ishii, và Andrew Metrick. 2003. “Corporate governance and equity prices”. The quarterly journal of economics 118 (1): 107–56. https://doi.org/10.1162/00335530360535162.
Grinblatt, Mark, Sheridan Titman, và Russ Wermers. 1995. “Momentum investment strategies, portfolio performance, and herding: A study of mutual fund behavior”. American Economic Review 85 (5): 1088–1105.
Khan, Mozaffar, Leonid Kogan, và George Serafeim. 2012. “Mutual fund trading pressure: Firm-level stock price impact and timing of SEOs”. The Journal of Finance 67 (4): 1371–95. https://doi.org/10.1111/j.1540-6261.2012.01750.x.
Koijen, Ralph SJ, và Motohiro Yogo. 2019. “A demand system approach to asset pricing”. Journal of Political Economy 127 (4): 1475–515.
Lou, Dong. 2012. “A flow-based explanation for return predictability”. The Review of Financial Studies 25 (12): 3457–89. https://doi.org/10.1093/rfs/hhs103.
Lou, Xiaoxia, và Albert Y. Wang. 2018. “Flow-induced trading pressure and corporate investment”. Journal of Financial and Quantitative Analysis 53 (1): 171–201. https://doi.org/10.1017/S0022109017000953.
Pástor, L’uboš, và Robert F Stambaugh. 2003. “Liquidity risk and expected stock returns”. Journal of Political economy 111 (3): 642–85. https://doi.org/10.1086/374184.
Phung, Duc Nam, và Anil V Mishra. 2016. “Ownership structure and firm performance: Evidence from Vietnamese listed firms”. Australian Economic Papers 55 (1): 63–98.
Sias, Richard W. 2004. “Institutional herding”. The review of financial studies 17 (1): 165–206.
Sirri, Erik R, và Peter Tufano. 1998. “Costly search and mutual fund flows”. The journal of finance 53 (5): 1589–1622.
Vo, Xuan Vinh. 2015. “Foreign ownership and stock return volatility–Evidence from Vietnam”. Journal of Multinational Financial Management 30: 101–9.
Wardlaw, Malcolm. 2020. “Measuring mutual fund flow pressure as shock to stock returns”. The Journal of Finance 75 (6): 3221–43. https://doi.org/10.1111/jofi.12962.
Wermers, Russ. 2000. “Mutual fund performance: An empirical decomposition into stock-picking talent, style, transactions costs, and expenses”. The Journal of Finance 55 (4): 1655–95.
Yan, Xuemin Sterling. 2008. “Liquidity, investment style, and the relation between fund size and fund performance”. Journal of Financial and Quantitative Analysis 43 (3): 741–67.
# Giao dịch, dòng vốn và tỷ lệ vòng quay của nhà đầu tư tổ chức```{python}#| label: setup#| echo: false#| eval: trueimport warningswarnings.filterwarnings('ignore')import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.ticker as mtickerimport matplotlib.dates as mdatesimport seaborn as snsfrom datetime import datetime, timedeltafrom dateutil.relativedelta import relativedeltaimport statsmodels.api as smfrom scipy import statsfrom scipy.spatial.distance import pdist, squareformimport itertoolsfrom pathlib import Pathfrom typing import Optional, Union, Dict, List, Tuplefrom dataclasses import dataclass, field# Plotting configuration — academic style (Times New Roman)plt.rcParams.update({'figure.figsize': (10, 6),'figure.dpi': 150,'font.family': 'serif','font.serif': ['Times New Roman', 'DejaVu Serif'],'font.size': 11,'axes.titlesize': 13,'axes.labelsize': 11,'xtick.labelsize': 10,'ytick.labelsize': 10,'legend.fontsize': 10,'figure.titlesize': 14,'axes.spines.top': False,'axes.spines.right': False,'axes.grid': True,'grid.alpha': 0.3,'grid.linestyle': '--',})OWNER_COLORS = {'State': '#d62728','Foreign Institutional': '#1f77b4','Domestic Institutional': '#2ca02c','Individual': '#ff7f0e','Treasury': '#9467bd','Total Institutional': '#333333',}EXCHANGE_COLORS = {'HOSE': '#1f77b4', 'HNX': '#ff7f0e', 'UPCOM': '#2ca02c'}pd.set_option('display.max_columns', 20)pd.set_option('display.width', 120)pd.set_option('display.float_format', '{:.4f}'.format)```Các nhà đầu tư tổ chức đóng vai trò then chốt trong quá trình khám phá giá, quản trị công ty và thanh khoản thị trường. Hiểu được tổ chức giao dịch *như thế nào* và giao dịch *nhiều đến mức nào* giúp chúng ta nắm được cả động lực định giá tài sản lẫn các tác động thực của việc giám sát từ tổ chức. Công trình nền tảng của @grinblatt1995momentum về giao dịch theo động lượng của quỹ tương hỗ, của @wermers2000mutual về phân rã hiệu quả hoạt động của quỹ, và của @yan2008liquidity về mối quan hệ giữa tỷ lệ vòng quay và lợi nhuận tương lai đều dựa vào việc đo lường chính xác các giao dịch, dòng vốn và tỷ lệ vòng quay của tổ chức.Tại Hoa Kỳ, nghiên cứu này được hỗ trợ bởi hệ thống báo cáo 13F hằng quý bắt buộc do Ủy ban Chứng khoán và Giao dịch Hoa Kỳ (SEC) quản lý. Mọi nhà quản lý đầu tư tổ chức có ít nhất \$100 triệu tài sản đủ điều kiện phải công bố danh mục cổ phiếu nắm giữ trong vòng 45 ngày kể từ cuối mỗi quý dương lịch. Cơ sở dữ liệu 13F của Thomson-Reuters (nay là Refinitiv), có thể truy cập qua WRDS, là hạ tầng dữ liệu chuẩn mực của dòng nghiên cứu này.Thị trường cổ phiếu Việt Nam có bối cảnh tổ chức khác biệt căn bản. Chương này điều chỉnh phương pháp cốt lõi cho phù hợp với bối cảnh Việt Nam, giải quyết năm điểm khác biệt quan trọng:1. **Chế độ công bố thông tin.** Việt Nam không có chế độ báo cáo hằng quý bắt buộc tương đương 13F. Việc công bố sở hữu là một bức tranh chắp vá gồm các báo cáo theo sự kiện (vượt ngưỡng 5%, 10%, v.v.), báo cáo thường niên/bán niên kèm danh sách cổ đông, và việc các sở giao dịch theo dõi sở hữu nước ngoài hằng ngày.2. **Sự kiện doanh nghiệp.** Các công ty Việt Nam phát hành cổ tức bằng cổ phiếu và cổ phiếu thưởng với tần suất cực cao so với các công ty Mỹ. Một công ty có thể phát hành 20-30% cổ phiếu thưởng trong một năm, làm thay đổi căn bản số lượng cổ phiếu. Do đó, việc điều chỉnh số cổ phiếu là then chốt và không hề đơn giản.3. **Giới hạn sở hữu nước ngoài (FOL).** Mức trần sở hữu nước ngoài mang tính ràng buộc, thường là 49% đối với hầu hết các ngành, 30% đối với ngân hàng, và 0% đối với một số ngành hạn chế, tạo ra một ràng buộc thể chế đặc thù. Khi một cổ phiếu tiến gần FOL, hoạt động mua của nhà đầu tư nước ngoài bị hạn chế một cách cơ học, làm sai lệch việc suy luận giao dịch theo cách chuẩn.4. **Sở hữu nhà nước.** Chính phủ Việt Nam duy trì tỷ lệ sở hữu đáng kể tại nhiều công ty niêm yết thông qua Tổng công ty Đầu tư và Kinh doanh vốn nhà nước (SCIC) và các đơn vị nhà nước khác. Điều này tạo ra một nhóm sở hữu riêng biệt không có trong dữ liệu 13F của Mỹ.5. **Vi cấu trúc thị trường.** Biên độ giá hằng ngày ($\pm 7\%$ trên HOSE, $\pm 10\%$ trên HNX, $\pm 15\%$ trên UPCOM), thanh toán T+2 và việc không có bán khống đều ảnh hưởng đến cách các giao dịch của tổ chức chuyển hóa thành kết quả thị trường.## Đo lường sở hữu và giao dịch của tổ chứcViệc đo lường sở hữu và hoạt động giao dịch của tổ chức là mối quan tâm trọng tâm của tài chính thực nghiệm kể từ khi @gompers2003corporate ghi nhận sự trỗi dậy của các nhà đầu tư tổ chức. Cách tiếp cận này dựa trên việc so sánh các ảnh chụp danh mục nắm giữ qua các kỳ báo cáo liên tiếp để suy ra giao dịch. Nếu nhà quản lý $j$ nắm giữ $h_{j,i,t}$ cổ phiếu $i$ tại thời điểm $t$, thì giao dịch được suy ra là:$$\Delta h_{j,i,t} = h_{j,i,t} - h_{j,i,t-1}$$ {#eq-trade-simple}trong đó $\Delta h_{j,i,t} > 0$ cho biết một giao dịch mua và $\Delta h_{j,i,t} < 0$ cho biết một giao dịch bán. Cách lấy sai phân đơn giản này đòi hỏi các danh mục nắm giữ được quan sát theo các khoảng đều đặn (ví dụ: hằng quý), số cổ phiếu được điều chỉnh theo các sự kiện doanh nghiệp giữa các ngày báo cáo, và việc gia nhập và rời khỏi bộ dữ liệu được xử lý thích hợp.@chen2000value đưa ra khái niệm *độ rộng* sở hữu (tức là số lượng tổ chức nắm giữ một cổ phiếu) và chỉ ra rằng thay đổi về độ rộng dự báo lợi nhuận tương lai. @sias2004institutional phân rã nhu cầu của tổ chức thành thành phần hành vi bầy đàn và thành phần thông tin. @yan2008liquidity liên hệ tỷ lệ vòng quay của quỹ với giao dịch dựa trên thông tin và ghi nhận rằng các quỹ có tỷ lệ vòng quay cao đạt hiệu quả vượt trội, qua đó thách thức quan điểm cho rằng tỷ lệ vòng quay phản ánh giao dịch nhiễu.## Phân loại giao dịch@tbl-trade-types trình bày bốn nhóm giao dịch:| Mã | Loại | Mô tả ||:-----|:-----------------|:---------------------------------------|| $+1$ | Mua khởi tạo | Nhà quản lý thiết lập một vị thế mới || $+2$ | Mua gia tăng | Nhà quản lý tăng một vị thế hiện có || $-1$ | Bán chấm dứt | Nhà quản lý thoát hoàn toàn một vị thế || $-2$ | Bán thông thường | Nhà quản lý giảm một vị thế hiện có |: Hệ phân loại giao dịch {#tbl-trade-types}Cách phân loại này mang nhiều thông tin vì các giao dịch mua khởi tạo và bán chấm dứt thể hiện những quyết định danh mục rời rạc với nội dung thông tin khác với các điều chỉnh vị thế biên [@alexander2007does].## Các thước đo tỷ lệ vòng quayCó ba định nghĩa chuẩn về tỷ lệ vòng quay đã được sử dụng trong các nghiên cứu:**Tỷ lệ vòng quay của Carhart (1997).** Giá trị nhỏ hơn giữa tổng giao dịch mua và tổng giao dịch bán, được chuẩn hóa theo tài sản bình quân:$$\text{Turnover}^{C}_{j,t} = \frac{\min\left(\sum_i B_{j,i,t},\, \sum_i S_{j,i,t}\right)}{\frac{1}{2}\left(A_{j,t} + A_{j,t-1}\right)}$$ {#eq-turnover-carhart}trong đó $B_{j,i,t}$ và $S_{j,i,t}$ là giá trị bằng tiền của các giao dịch mua và bán cổ phiếu $i$ bởi nhà quản lý $j$ trong quý $t$, và $A_{j,t}$ là tổng tài sản danh mục [@Carhart1997].**Tỷ lệ vòng quay điều chỉnh theo dòng vốn.** Cộng thêm giá trị tuyệt đối của dòng vốn ròng để tính đến hoạt động giao dịch do dòng vốn thúc đẩy:$$\text{Turnover}^{F}_{j,t} = \frac{\min\left(\sum_i B_{j,i,t},\, \sum_i S_{j,i,t}\right) + |\text{NetFlow}_{j,t}|}{A_{j,t-1}}$$ {#eq-turnover-flow}**Tỷ lệ vòng quay đối xứng.** Sử dụng tổng giao dịch mua và bán trừ đi giá trị tuyệt đối của dòng vốn ròng:$$\text{Turnover}^{S}_{j,t} = \frac{\sum_i B_{j,i,t} + \sum_i S_{j,i,t} - |\text{NetFlow}_{j,t}|}{A_{j,t-1}}$$ {#eq-turnover-symmetric}Mối quan hệ giữa các thước đo này phụ thuộc vào tương quan giữa giao dịch tùy ý và giao dịch do dòng vốn gây ra [@pastor2003liquidity].## Sở hữu tổ chức tại các thị trường mới nổiCác nghiên cứu về thị trường mới nổi đã ghi nhận một số sự thật điển hình về sở hữu tổ chức khác với các phát hiện ở thị trường phát triển. @aggarwal2011does ghi nhận rằng sở hữu tổ chức nước ngoài cải thiện quản trị công ty tại các thị trường mới nổi. Riêng đối với Việt Nam, @phung2016ownership xem xét mối quan hệ giữa cơ cấu sở hữu và hiệu quả hoạt động của doanh nghiệp, trong khi @vo2015foreign nghiên cứu tác động của sở hữu nước ngoài đối với thanh khoản thị trường cổ phiếu.## Dòng vốn ròng và phân tích nguồn gốc hiệu quả hoạt độngDòng vốn ròng đo lượng tiền mới chảy vào hoặc chảy ra khỏi một quỹ:$$\text{NetFlow}_{j,t} = A_{j,t} - A_{j,t-1}(1 + R_{j,t}^p)$$ {#eq-netflow}trong đó $R_{j,t}^p$ là lợi nhuận danh mục. Sự phân rã này, do @sirri1998costly đề xuất, tách các thay đổi trong tài sản của quỹ thành lợi nhuận đầu tư và các quyết định phân bổ vốn của nhà đầu tư. @coval2007asset chỉ ra rằng các giao dịch do dòng vốn thúc đẩy tạo ra áp lực giá, trong đó việc bán tháo của các quỹ bị rút vốn tạo ra lợi nhuận bất thường âm đáng kể.## Áp lực giá do dòng vốn gây ra và phép tính số học khi đo lường nó {#sec-institutional-flow-pressure}Kết quả của @coval2007asset không chỉ dừng lại ở một sự thật về bán tháo; nó nhanh chóng trở thành một chiến lược nhận diện. Nếu một quỹ tương hỗ đối mặt với các đợt rút vốn lớn phải huy động tiền mặt, và nếu các quyết định rút vốn của chính nhà đầu tư của quỹ đó không liên quan đến các yếu tố cơ bản của từng cổ phiếu mà quỹ tình cờ nắm giữ, thì áp lực bán truyền sang các cổ phiếu đó là một nguồn biến thiên giá có khả năng trực giao với giá trị doanh nghiệp. @edmans2012real biến logic này thành một công cụ cho biến động giá phi cơ bản và dùng nó để chỉ ra rằng giá bị suy giảm thu hút các lời chào mua thâu tóm. @khan2012mutual áp dụng nó cho thời điểm phát hành cổ phiếu thứ cấp, @lou2012flow xây dựng thước đo giao dịch do dòng vốn gây ra (FIT) có liên quan chặt chẽ và dùng nó để giải thích khả năng dự báo lợi nhuận, còn @lou2018flow và @dessaint2019noisy dùng áp lực do rút vốn để đặt câu hỏi liệu các nhà quản lý có học hỏi từ giá khi đưa ra quyết định đầu tư hay không. Trong vòng một thập kỷ, thước đo này đã trở thành một trong những công cụ được sử dụng nhiều nhất trong tài chính doanh nghiệp thực nghiệm, xuất hiện trong các nghiên cứu về đầu tư, đổi mới, công bố thông tin, chi trả cổ tức, thay đổi CEO và quản trị công ty.### Thước đo chuẩn {#sec-institutional-mfflow}Cách xây dựng quen thuộc là biến áp lực giá của quỹ tương hỗ của @edmans2012real:$$\text{MFFlow}_{i,t} = \sum_{j} \frac{F_{j,t} \times \text{SHARES}_{i,j,t-1} \times \text{PRC}_{i,t-1}}{\text{TA}_{j,t-1} \times \text{VOL}_{i,t}}$$ {#eq-mfflow}trong đó $j$ là chỉ số của các quỹ tương hỗ, $F_{j,t}$ là tổng dòng vốn ròng của quỹ $j$ trong quý $t$, $\text{TA}_{j,t-1}$ là tổng tài sản của quỹ vào cuối quý trước, $\text{SHARES}_{i,j,t-1}$ là số cổ phiếu $i$ mà quỹ nắm giữ vào cuối quý $t-1$, $\text{PRC}_{i,t-1}$ là giá cổ phiếu tại ngày đó, và $\text{VOL}_{i,t}$ là tổng khối lượng giao dịch tính bằng *tiền* của cổ phiếu trong quý $t$. Phép tổng chỉ chạy trên các quỹ có dòng vốn rút ra vượt một ngưỡng (5% tài sản trong bài báo gốc, dòng vốn ở phân vị thứ 10 trong phần lớn các nghiên cứu sau đó), và các quỹ ngành bị loại trừ để dòng vốn rút ra không thể phản ánh quan điểm về một ngành cụ thể. Tử số là giá trị bằng tiền mà quỹ *sẽ* bán nếu thanh lý danh mục theo tỷ lệ để đáp ứng yêu cầu rút vốn; mẫu số chuẩn hóa giao dịch giả định đó theo thanh khoản của cổ phiếu, với lập luận rằng cùng một đợt bán bắt buộc sẽ làm một cổ phiếu mỏng thanh khoản biến động mạnh hơn một cổ phiếu có thanh khoản cao.### Thước đo thực sự chứa đựng điều gì {#sec-institutional-wardlaw}@wardlaw2020measuring chỉ ra rằng @eq-mfflow không phải là một tỷ số như vẻ bề ngoài. Viết khối lượng giao dịch bằng tiền của quý bằng khối lượng cổ phiếu nhân với giá bình quân gia quyền theo khối lượng mà cổ phiếu thực sự được giao dịch, $\text{VOL}_{i,t} = \text{SHVOL}_{i,t} \times \bar{P}_{i,t}$. Thay vào @eq-mfflow và biến đổi ta được$$\text{MFFlow}_{i,t} = \underbrace{\left[ \sum_{j} \frac{F_{j,t} \times \text{SHARES}_{i,j,t-1}}{\text{TA}_{j,t-1} \times \text{SHVOL}_{i,t}} \right]}_{\text{trading pressure, in share space}} \times \underbrace{\frac{\text{PRC}_{i,t-1}}{\bar{P}_{i,t}}}_{= \; 1 / (1 + \bar{r}_{i,t})}$$ {#eq-mfflow-decomposition}trong đó $\bar{r}_{i,t} \equiv \bar{P}_{i,t} / \text{PRC}_{i,t-1} - 1$ là lợi nhuận thực hiện của cổ phiếu từ cuối quý $t-1$ đến mức giá bình quân mà cổ phiếu được chuyển nhượng trong quý $t$. Thước đo là tích của một số hạng áp lực giao dịch thực sự và một số hạng *chính là* lợi nhuận đồng thời. Vì các quan sát rút vốn có $F_{j,t} < 0$, một cổ phiếu có giá giảm trong quý sẽ nhận $\text{MFFlow}$ âm hơn một cách cơ học bất kể các quỹ tương hỗ đã làm gì, và một cổ phiếu có giá tăng sẽ nhận giá trị ít âm hơn. Do đó, việc hồi quy lợi nhuận quý $t$ theo $\text{MFFlow}_{i,t}$ cho ra đúng dấu mà giả thuyết bán tháo dự báo, ngay cả trong một thế giới mà tác động giá thực sự của việc bán do dòng vốn gây ra hoàn toàn bằng không. Đây là điều mà bài báo muốn nói khi kết luận rằng cách tiếp cận chuẩn tạo ra một thước đo vô tình là hàm trực tiếp của lợi nhuận thực hiện thực tế của cổ phiếu trong quý có dòng vốn rút ra.Hai hệ quả thực nghiệm theo sau, và hệ quả thứ hai gây tổn hại nhiều hơn. Thứ nhất, khi loại bỏ phần lợi nhuận bị gộp vào, dòng vốn rút ra chỉ gây ra một mức giảm nhỏ đồng thời trong lợi nhuận. Thứ hai, mức giảm đó *không được theo sau bởi sự đảo chiều nào*. Sự đảo chiều chưa bao giờ là một kiểm tra độ vững mang tính trang trí: nó là bằng chứng nhận diện. Một biến động giá do người bán có động cơ thanh khoản gây ra được cho là tạm thời và do đó sẽ tự đảo ngược, trong khi biến động giá do thông tin gây ra là vĩnh viễn. Nếu không có sự đảo chiều, kiểu hình thực nghiệm tương đương về mặt quan sát với việc các quỹ bán những cổ phiếu thực sự đang xấu đi, và giả định loại trừ mà toàn bộ chương trình nghiên cứu dựa vào mất đi bằng chứng hỗ trợ. @wardlaw2020measuring báo cáo rằng nhiều kết quả đã được thiết lập trong dòng nghiên cứu này không còn đứng vững khi thước đo được làm sạch.Cách khắc phục rất dễ phát biểu. Xây dựng thước đo trong không gian cổ phiếu: tính *số cổ phiếu* giả định bị bán và chuẩn hóa theo số cổ phiếu đang lưu hành tại $t-1$ (tỷ số dòng vốn trên lượng cổ phiếu), hoặc theo khối lượng cổ phiếu giao dịch, và giữ mọi mức giá trong cách xây dựng ở ngày $t-1$. Quy tắc chung mà ví dụ này minh họa đáng được mang theo xa hơn dòng nghiên cứu về dòng vốn: *không bao giờ lập một tỷ số có tử số là một đại lượng được định giá theo giá ngày $t-1$ và mẫu số là một đại lượng được định giá theo giá ngày $t$, vì khi đó tỷ số theo cấu trúc chứa lợi nhuận giữa hai ngày*. Chính phép tính số học này giải thích vì sao một mức giá nằm ở mẫu số của bất kỳ thước đo nào đều đáng được xem xét kỹ, tỷ số phi thanh khoản Amihud được thảo luận trong @sec-liquidity-amihud-original là ví dụ hiển nhiên. Sự khác biệt ở đó nằm ở mục đích chứ không phải đại số: Amihud là một thước đo mô tả có mẫu số cố ý tính bằng tiền, trong khi MFFlow là một công cụ mà toàn bộ luận điểm của nó là tính trực giao với kết quả mà nó được dùng để giải thích.### Vấn đề thứ hai, độc lập: thiên lệch chọn lọc {#sec-institutional-fire-sale-selection}Ngay cả một thước đo được chuẩn hóa đúng cũng thừa hưởng một giả định hành vi sai trong dữ liệu. @eq-mfflow gán các giao dịch bằng cách giả định quỹ bị rút vốn thanh lý *theo tỷ lệ*, chính điều này làm cho giao dịch giả định độc lập với thông tin của nhà quản lý. @berger2023selection chỉ ra rằng các nhà quản lý không hành xử như vậy: khi đối mặt với các yêu cầu rút vốn, họ ưu tiên bán các vị thế có thanh khoản cao, hoạt động tốt và giữ lại các vị thế thua lỗ kém thanh khoản. Do đó, giả định theo tỷ lệ phân bổ sai áp lực bán hàm ý sang các công ty mà các nhà quản lý có hệ thống từ chối bán, tức là những công ty hoạt động kém, kém thanh khoản và có tăng trưởng thấp. Các mô phỏng trong bài báo đó cho thấy thiên lệch chọn lọc phát sinh làm tăng khoảng gấp đôi độ lớn của các hệ số hồi quy ước lượng và các cách khắc phục chuẩn không loại bỏ được nó; sau khi tính đến thiên lệch này, tác giả không tìm thấy hiệu ứng phản hồi nào chạy từ việc định giá sai do dòng vốn gây ra đến đầu tư, phát hành cổ phiếu hay chi trả cổ tức của doanh nghiệp. Đọc cùng nhau, @wardlaw2020measuring và @berger2023selection nêu cùng một luận điểm phương pháp luận từ hai hướng: một giao dịch giả định được xây dựng từ một ảnh chụp danh mục nắm giữ và một đồng nhất thức kế toán là một *mô hình* về hành vi của tổ chức, không phải là một quan sát về hành vi đó, và cả việc chuẩn hóa lẫn giả định hành vi đều phải được bảo vệ.Một lưu ý thứ ba nằm ở thượng nguồn của cả hai. @li2025measuring ghi nhận rằng chính các dòng vốn của quỹ đưa vào tử số cũng là các ước lượng. Hầu hết các nghiên cứu suy ra dòng vốn từ thay đổi trong tổng tài sản ròng vượt quá lợi nhuận của quỹ, như trong @eq-netflow, thay vì từ dòng vốn được báo cáo, và hai cách này khác nhau do các khoản phân phối được tái đầu tư, quy ước về thời điểm áp dụng trong kỳ và cách xử lý việc sáp nhập quỹ. Các thước đo dòng vốn khác nhau tạo ra những suy luận khác biệt đáng kể về mối quan hệ giữa dòng vốn và hiệu quả hoạt động cũng như về cách dòng vốn làm dịch chuyển giá chứng khoán, điều này khiến việc lựa chọn thước đo là điều cần được ghi chép lại thay vì thừa hưởng.### Điều này có ý nghĩa gì đối với nghiên cứu về Việt Nam {#sec-institutional-flow-pressure-vietnam}Thiết kế của Mỹ khó có thể áp dụng trực tiếp, vì lý do đã nêu ở đầu chương này: không có tương đương 13F, nên bảng dữ liệu danh mục nắm giữ hằng quý mà @eq-mfflow đòi hỏi không tồn tại đối với hầu hết các nhà đầu tư tổ chức. Ngành quỹ mở trong nước có công bố danh mục nắm giữ còn nhỏ so với vốn hóa thị trường, nên ngay cả một đợt bán giả định được xây dựng đúng cũng chỉ chiếm một phần không đáng kể trong khối lượng giao dịch của một cổ phiếu HOSE điển hình. Đó là công thức cho một biến công cụ yếu, bất kể được chuẩn hóa cẩn thận đến đâu.Quan sát triển vọng hơn là Việt Nam có một bối cảnh trong đó giao dịch do dòng vốn gây ra không cần phải được gán một cách giả định. Các quỹ hoán đổi danh mục (ETF) bám sát các chỉ số của Việt Nam, trong nước là các quỹ DCVFMVN30, DCVFM VNDIAMOND và SSIAM VNFIN LEAD, còn ngoài nước là các ETF VanEck Vietnam và Fubon FTSE Vietnam, công bố các lệnh tạo lập và mua lại hằng ngày dựa trên các rổ cổ phiếu được công bố và theo quy tắc rõ ràng. Do đó, giao dịch mà một lệnh tạo lập hoặc mua lại buộc phải thực hiện là *quan sát được và mang tính cơ học* thay vì được giả định: quỹ phải giao dịch cả rổ, theo trọng số của chỉ số, theo một lịch trình đã biết. Điều này loại bỏ giả định theo tỷ lệ mà @berger2023selection phê phán, vì đối với một ETF chỉ số, giao dịch theo tỷ lệ chính là điều thực sự xảy ra, và loại bỏ vấn đề xấp xỉ theo quý, vì dòng vốn là hằng ngày. Các dòng vốn vào lớn do nhà đầu tư cá nhân Đài Loan thúc đẩy vào ETF Fubon FTSE Vietnam trong giai đoạn 2021 đến 2023 là một cú sốc nhu cầu đặc biệt rõ ràng, ở chỗ khẩu vị của nhà đầu tư cá nhân Đài Loan đối với một sản phẩm Việt Nam khó có thể được gắn với các yếu tố cơ bản của bất kỳ công ty Việt Nam riêng lẻ nào. Họ VNDIAMOND thú vị vì lý do ngược lại: các cổ phiếu thành phần của nó được chọn chính vì chúng nằm tại hoặc gần giới hạn sở hữu nước ngoài, nên nhu cầu do ETF thúc đẩy tương tác trực tiếp với ràng buộc đang có hiệu lực được phân tích trong @sec-institutionalfol-util và @sec-institutionalroom-premium.Biên độ giá của Việt Nam đưa ra thêm một lý do nữa để không đặt khối lượng giao dịch ở mẫu số, và lý do này đặc thù cho thị trường này. Khối lượng giao dịch sụt giảm mạnh vào các ngày chạm biên và có thể tiến gần về không khi một cổ phiếu bị khóa ở mức giá trần hoặc sàn với hàng đợi lệnh chưa được khớp, đúng vào lúc lợi nhuận trong ngày cực đoan nhất. Do đó, một thước đo áp lực được chuẩn hóa theo khối lượng sẽ bùng nổ đúng tại các quan sát chi phối hồi quy lợi nhuận, khiến mẫu số có thêm một kênh thứ hai để đưa biến kết quả vào. Mức độ thường xuyên của hiện tượng này trên HOSE và HNX được ghi nhận trong @sec-price-limit-prevalence, và hiện tượng lợi nhuận bị chặn (kiểm duyệt dữ liệu) mà nó gây ra được phân tích trong @sec-price-limit-distortion. Số cổ phiếu đang lưu hành, vốn không thể bị thay đổi bởi bất kỳ lượng giao dịch nào trong một ngày nhất định, là biến chuẩn hóa an toàn hơn ở đây, vì cùng lý do mà nó an toàn hơn trong dữ liệu Mỹ.Sáu quy tắc thực tiễn sau đây dành cho bất kỳ ai xây dựng thước đo áp lực dòng vốn trên dữ liệu Việt Nam.1. **Xây dựng thước đo theo số cổ phiếu và chuẩn hóa theo số cổ phiếu đang lưu hành tại $t-1$.** Không chuẩn hóa theo khối lượng giao dịch bằng tiền đồng thời.2. **Gắn mọi mức giá trong cách xây dựng với ngày $t-1$.** Nếu một mức giá của kỳ $t$ xuất hiện ở bất kỳ đâu trong công thức, thước đo sẽ chứa lợi nhuận của kỳ $t$.3. **Báo cáo kiểm định đảo chiều như một kết quả chính, không phải một kiểm tra độ vững.** Đó là bằng chứng duy nhất phân biệt áp lực phi cơ bản với thông tin, và sau @wardlaw2020measuring, một kết quả về áp lực dòng vốn thiếu nó thì không đáng tin.4. **Xác thực giao dịch được gán so với giao dịch quan sát được bất cứ khi nào có thể.** Việt Nam công bố giá trị mua và bán ròng của nhà đầu tư nước ngoài hằng ngày theo mã cổ phiếu, và các rổ ETF được công bố; cả hai đều cho phép kiểm tra xem hành vi giao dịch được giả định có khớp với các giao dịch thực hiện hay không.5. **Chạy kiểm định placebo với dòng vốn kỳ vọng.** Thay dòng vốn thực hiện bằng dòng vốn dự báo, theo tinh thần của cách xây dựng giao dịch do dòng vốn kỳ vọng gây ra trong @lou2012flow, để kiểm tra xem kết quả được thúc đẩy bởi cú sốc dòng vốn hay bởi cơ cấu nắm giữ.6. **Báo cáo độ mạnh của giai đoạn một.** Với mức sở hữu tổ chức còn nông như ở Việt Nam, biến công cụ yếu là kỳ vọng mặc định và phải được loại trừ một cách tường minh.Cần lưu ý dòng nghiên cứu này đã dịch chuyển đến đâu. Cuộc tranh luận về đo lường ở trên, về bản chất, là về việc một lượng giao dịch bắt buộc nhất định làm dịch chuyển giá bao nhiêu, tức là độ dốc của đường cầu tổng hợp. Câu hỏi đó hiện được tiếp cận trực tiếp bằng cách tiếp cận hệ thống cầu của @koijen2019demand và giả thuyết thị trường kém co giãn của @gabaix2021search, vốn suy ra tác động giá của dòng vốn từ cấu trúc nhu cầu của nhà đầu tư thay vì từ một biến công cụ duy nhất. @greenwood2011stock, với thước đo mức mong manh tổng hợp biến động dòng vốn của các chủ sở hữu cổ phiếu thay vì giá trị thực hiện của một quý, và @frazzini2008dumb, về các hệ quả lợi nhuận của việc nhà đầu tư cá nhân đuổi theo dòng vốn, là những bổ sung hữu ích từ truyền thống trước đó. Đối với một nhà nghiên cứu mới bước vào lĩnh vực này, trình tự đáng đọc là @coval2007asset và @edmans2012real cho thiết kế gốc, @wardlaw2020measuring và @berger2023selection cho những gì đã sai với nó, và @koijen2019demand và @gabaix2021search cho khung phân tích phần lớn đã thay thế nó.# Hạ tầng dữ liệu {#sec-institutionaldata}@tbl-datacore-datasets tóm tắt các bộ dữ liệu được sử dụng trong chương này.| Bộ dữ liệu | Nội dung | Tần suất | Biến chính ||:-----------------|:-----------------|:-----------------|:------------------|| Giá cổ phiếu | OHLCV hằng ngày/hằng tháng | Hằng ngày |`ticker`, `date`, `close`, `adjusted_close`, `volume`, `shares_outstanding`|| Cơ cấu sở hữu | Cơ cấu cổ đông | Hằng quý/Hằng năm |`ticker`, `date`, `shareholder_name`, `shares_held`, `pct`, `type`|| Cổ đông lớn | Các cổ đông nắm giữ $\geq$ 5% | Theo sự kiện |`ticker`, `date`, `shareholder_name`, `shares`, `is_foreign`, `is_state`|| Sự kiện doanh nghiệp | Chia tách, cổ tức, cổ phiếu thưởng | Sự kiện |`ticker`, `ex_date`, `action_type`, `ratio`|| Hồ sơ công ty | Ngành, sàn giao dịch, FOL | Tĩnh/Hằng năm |`ticker`, `exchange`, `industry`, `listing_date`, `fol_limit`|| Sở hữu nước ngoài | Theo dõi nước ngoài hằng ngày | Hằng ngày |`ticker`, `date`, `foreign_shares`, `foreign_pct`, `fol_limit`|| Danh mục nắm giữ của quỹ | Ảnh chụp danh mục quỹ | Bán niên |`fund_name`, `report_date`, `ticker`, `shares_held`, `market_value`|: Các bộ dữ liệu DataCore.vn sử dụng trong chương này {#tbl-datacore-datasets}## Lớp đọc dữ liệu {#sec-institutionalreader}Chúng ta bắt đầu bằng việc định nghĩa một bộ đọc dữ liệu thống nhất xử lý việc tải tệp, phân tích ngày tháng và kiểm tra cơ bản:```{python}#| label: datacore-reader#| code-summary: "DataCoreReader: Unified Data Access Layer"@dataclassclass DataCoreReader:""" Unified reader for DataCore.vn datasets stored locally. Supports Parquet (recommended) and CSV formats. Implements lazy loading with caching to minimize memory footprint. Parameters ---------- data_dir : str or Path Directory containing DataCore.vn data files. file_format : str File format: 'parquet' or 'csv'. Examples -------- >>> dc = DataCoreReader('/data/datacore', file_format='parquet') >>> prices = dc.prices >>> ownership = dc.ownership """ data_dir: Path file_format: str='parquet' _cache: Dict[str, pd.DataFrame] = field( default_factory=dict, repr=False ) FILE_MAP: Dict[str, str] = field(default_factory=lambda: {'prices': 'stock_prices','ownership': 'ownership_structure','major_shareholders': 'major_shareholders','corporate_actions': 'corporate_actions','company_profile': 'company_profile','financials': 'financial_statements','foreign_ownership': 'foreign_ownership','fund_holdings': 'fund_holdings', }, repr=False)def __post_init__(self):self.data_dir = Path(self.data_dir)ifnotself.data_dir.exists():raiseFileNotFoundError(f"Data directory not found: {self.data_dir}" )def _read(self, key: str) -> pd.DataFrame:"""Read and cache a dataset with automatic date parsing."""if key inself._cache:returnself._cache[key] fname =self.FILE_MAP.get(key, key) filepath =self.data_dir /f"{fname}.{self.file_format}"ifnot filepath.exists():raiseFileNotFoundError(f"Dataset not found: {filepath}\n"f"Available: "f"{list(self.data_dir.glob(f'*.{self.file_format}'))}" )ifself.file_format =='parquet': df = pd.read_parquet(filepath)else: df = pd.read_csv(filepath, parse_dates=True)# Auto-detect and parse date columns date_cols = ['date', 'ex_date', 'record_date', 'period','report_date', 'listing_date' ]for col in df.columns:if col.lower() in date_cols or'date'in col.lower():try: df[col] = pd.to_datetime(df[col])except (ValueError, TypeError):passself._cache[key] = dfprint(f" Loaded {key}: {len(df):,} rows x {len(df.columns)} cols")return df@propertydef prices(self) -> pd.DataFrame:returnself._read('prices')@propertydef ownership(self) -> pd.DataFrame:returnself._read('ownership')@propertydef major_shareholders(self) -> pd.DataFrame:returnself._read('major_shareholders')@propertydef corporate_actions(self) -> pd.DataFrame:returnself._read('corporate_actions')@propertydef company_profile(self) -> pd.DataFrame:returnself._read('company_profile')@propertydef foreign_ownership(self) -> pd.DataFrame:returnself._read('foreign_ownership')@propertydef fund_holdings(self) -> pd.DataFrame:returnself._read('fund_holdings')def clear_cache(self): n =len(self._cache)self._cache.clear()print(f" Cleared {n} cached datasets")# Initialize:# dc = DataCoreReader('/path/to/datacore_data', file_format='parquet')```# Xử lý giá cổ phiếu và lợi nhuận {#sec-institutionalcrsp}Bước đầu tiên xử lý dữ liệu cổ phiếu để thu được giá đã điều chỉnh, số cổ phiếu đang lưu hành và lợi nhuận hằng quý.## Trích xuất và điều chỉnh dữ liệu giá {#sec-institutionalprice-adj}Dữ liệu cổ phiếu Việt Nam đòi hỏi điều chỉnh cẩn thận cho các sự kiện doanh nghiệp diễn ra thường xuyên. Khác với Mỹ, nơi CRSP cung cấp hệ số điều chỉnh tích lũy (`cfacpr`, `cfacshr`), tại Việt Nam chúng ta phải xây dựng các hệ số điều chỉnh từ lịch sử sự kiện doanh nghiệp.::: callout-note## Các sự kiện doanh nghiệp tại Việt NamCác công ty Việt Nam thường thực hiện các sự kiện doanh nghiệp sau, mỗi sự kiện đòi hỏi điều chỉnh số cổ phiếu và/hoặc giá:- **Cổ tức bằng cổ phiếu** (*cổ tức bằng cổ phiếu*): ví dụ, cổ tức bằng cổ phiếu 20% nghĩa là 100 cổ phiếu trở thành 120 cổ phiếu- **Cổ phiếu thưởng** (*cổ phiếu thưởng*): cổ phiếu miễn phí được phân phối từ lợi nhuận giữ lại- **Phát hành quyền mua** (*phát hành quyền mua*): quyền mua cổ phiếu mới với giá ưu đãi- **Chia tách/gộp cổ phiếu** (*chia/gộp cổ phiếu*): hiếm gặp nhưng đôi khi được sử dụng:::```{python}#| label: corporate-action-adjustment#| code-summary: "Corporate Action Adjustment Factor Construction"def build_adjustment_factors( corporate_actions: pd.DataFrame,) -> pd.DataFrame:""" Construct cumulative share adjustment factors from corporate actions. This is the Vietnamese equivalent of CRSP's cfacshr factor. For each ticker, we compute a cumulative product of adjustment ratios from corporate actions, working forward in time. The adjustment factor at date t converts historical share counts to be comparable with current (post-action) share counts: shares_adjusted_t = shares_raw_t * cfacshr_t Parameters ---------- corporate_actions : pd.DataFrame Corporate actions with columns: ticker, ex_date, action_type, ratio. The ratio field represents: - Stock dividend 20%: ratio = 1.20 - 2:1 stock split: ratio = 2.00 - Bonus shares 10%: ratio = 1.10 Returns ------- pd.DataFrame Adjustment factors: ticker, ex_date, cfacshr (cumulative). """ share_actions = corporate_actions[ corporate_actions['action_type'].isin(['stock_dividend', 'bonus_shares', 'stock_split','reverse_split', 'rights_issue' ]) ].copy()if share_actions.empty:return pd.DataFrame(columns=['ticker', 'ex_date', 'cfacshr']) share_actions = share_actions.sort_values(['ticker', 'ex_date']) share_actions['cfacshr'] = ( share_actions .groupby('ticker')['ratio'] .cumprod() )return share_actions[['ticker', 'ex_date', 'cfacshr']].reset_index( drop=True )def get_cfacshr_at_date( ticker: str, date: pd.Timestamp, adj_factors: pd.DataFrame,) ->float:""" Look up the cumulative share adjustment factor for a given ticker and date. Returns 1.0 if no corporate actions occurred. """ mask = ( (adj_factors['ticker'] == ticker) & (adj_factors['ex_date'] <= date) ) subset = adj_factors.loc[mask]if subset.empty:return1.0return subset.iloc[-1]['cfacshr']def adjust_shares_between_dates( shares: float, ticker: str, date_from: pd.Timestamp, date_to: pd.Timestamp, adj_factors: pd.DataFrame,) ->float:""" Adjust a share count observed at date_from to be comparable with shares observed at date_to, accounting for all intervening corporate actions. Example ------- >>> # Investor held 1000 shares on 2023-01-01 >>> # A 20% stock dividend occurred on 2023-03-15 >>> adjust_shares_between_dates( ... 1000, 'VNM', ... pd.Timestamp('2023-01-01'), ... pd.Timestamp('2023-06-30'), adj_factors ... ) 1200.0 """ factor_from = get_cfacshr_at_date(ticker, date_from, adj_factors) factor_to = get_cfacshr_at_date(ticker, date_to, adj_factors) relative_factor = factor_to / factor_fromreturn shares * relative_factor```## Xử lý giá theo tháng và theo quý {#sec-institutionalprice-processing}```{python}#| label: price-processing#| code-summary: "Step 1: Price Data Processing"def process_prices( prices: pd.DataFrame, adj_factors: pd.DataFrame, begdate: str='2010-01-01', enddate: str='2024-12-31',) -> Tuple[pd.DataFrame, pd.DataFrame]:""" Process raw DataCore.vn price data into analysis-ready format. Block logic: 1. Filter to date range 2. Compute adjusted prices and shares outstanding 3. Compute quarterly compounded returns 4. Create forward quarterly returns (shifted one quarter) Parameters ---------- prices : pd.DataFrame Raw price data with: ticker, date, close, adjusted_close, volume, shares_outstanding. adj_factors : pd.DataFrame Corporate action adjustment factors. begdate, enddate : str Sample period boundaries. Returns ------- Tuple[pd.DataFrame, pd.DataFrame] (price_quarterly, qret): quarter-end observations with adjusted price, total shares, and forward quarterly return. """ price = prices[ (prices['date'] >= begdate) & (prices['date'] <= enddate) ].copy()# Month-end and quarter-end dates price['mdate'] = price['date'] + pd.offsets.MonthEnd(0) price['qdate'] = price['date'] + pd.offsets.QuarterEnd(0)# Adjusted priceif'adjusted_close'in price.columns: price['p'] = price['adjusted_close']else: price['p'] = price['close']# Total shares outstanding price['tso'] = price['shares_outstanding']# Market capitalization (millions VND) price['mcap'] = price['p'] * price['tso'] /1e6# Filter out zero shares price = price[price['tso'] >0].copy()# Compute daily returns if not presentif'ret'notin price.columns: price = price.sort_values(['ticker', 'date']) price['ret'] = price.groupby('ticker')['p'].pct_change() price['ret'] = price['ret'].fillna(0) price['logret'] = np.log(1+ price['ret'])# ---- Quarterly compounded returns ---- qret = ( price .groupby(['ticker', 'qdate'])['logret'] .sum() .reset_index() ) qret['qret'] = np.exp(qret['logret']) -1# Shift qdate back one quarter: make qret a *forward* return qret['qdate'] = qret['qdate'] + pd.offsets.QuarterEnd(-1) qret = qret.drop(columns=['logret'])# ---- Quarter-end observations ---- price_q = price[price['qdate'] == price['mdate']].copy() price_q = price_q[['qdate', 'ticker', 'p', 'tso', 'mcap']].copy()# Merge forward quarterly return price_q = price_q.merge(qret, on=['ticker', 'qdate'], how='left')# Build cfacshr lookup at each quarter-end price_q['cfacshr'] = price_q.apply(lambda row: get_cfacshr_at_date( row['ticker'], row['qdate'], adj_factors ), axis=1 )return price_q, qret```::: callout-tip## Tối ưu hóa hiệu năngHàm `get_cfacshr_at_date` sử dụng phép tra cứu theo từng dòng nên có thể chậm đối với các bộ dữ liệu lớn. Để dùng trong thực tế với hàng triệu dòng, hãy vector hóa bằng `pd.merge_asof()`:``` pythonprice_q = pd.merge_asof( price_q.sort_values('qdate'), adj_factors.sort_values('ex_date'), by='ticker', left_on='qdate', right_on='ex_date', direction='backward').fillna({'cfacshr': 1.0})```:::Đầu ra là một bảng dữ liệu hằng quý gồm các quan sát cấp cổ phiếu (\@tbl-institutional-price-vars)| Biến | Mô tả ||:----------|:-------------------------------------||`ticker`| Mã cổ phiếu (ví dụ: VNM, VCB, FPT) ||`qdate`| Ngày cuối quý ||`p`| Giá đóng cửa đã điều chỉnh (VND) ||`tso`| Tổng số cổ phiếu đang lưu hành ||`mcap`| Vốn hóa thị trường (triệu VND) ||`qret`| Lợi nhuận ghép lãi của quý kế tiếp ||`cfacshr`| Hệ số điều chỉnh cổ phiếu tích lũy |: Các biến của bảng giá cuối quý {#tbl-institutional-price-vars}# Xử lý dữ liệu sở hữu {#sec-institutionalownership}## Hệ phân loại sở hữu {#sec-institutionaltaxonomy}Chúng ta định nghĩa một hệ thống phân loại cổ đông Việt Nam ánh xạ tới các nhóm có sẵn trong DataCore.vn:```{python}#| label: ownership-taxonomy#| code-summary: "Vietnamese Ownership Type Classification"class OwnershipType:""" Vietnamese ownership type classification. Vietnam's ownership structure is fundamentally different from the US: - **State** (Nha nuoc): SCIC, ministries, state-owned parents - **Foreign Institutional** (To chuc nuoc ngoai): foreign funds, ETFs, pension funds, insurance, sovereign wealth funds - **Domestic Institutional** (To chuc trong nuoc): Vietnamese securities companies, fund managers, banks, insurance - **Individual** (Ca nhan): retail investors (domestic + foreign) - **Treasury** (Co phieu quy): company repurchases """ STATE ='State' FOREIGN_INST ='Foreign Institutional' DOMESTIC_INST ='Domestic Institutional' INDIVIDUAL ='Individual' TREASURY ='Treasury' INSTITUTIONAL = [FOREIGN_INST, DOMESTIC_INST] ALL_INSTITUTIONAL = [STATE, FOREIGN_INST, DOMESTIC_INST] ALL_TYPES = [STATE, FOREIGN_INST, DOMESTIC_INST, INDIVIDUAL, TREASURY] STATE_KEYWORDS = ['scic', 'state capital', 'bo', 'ubnd', 'tong cong ty','nha nuoc', 'state', 'government', "people's committee",'ministry', 'vietnam national', 'vnpt', 'evn', 'pvn', ] FOREIGN_KEYWORDS = ['fund', 'investment', 'capital', 'asset management','securities', 'gic', 'templeton', 'dragon capital','vinacapital', 'mekong capital', 'kb securities','mirae asset', 'samsung', 'jp morgan', 'goldman','blackrock', 'vanguard', 'aberdeen', 'hsbc', ]@classmethoddef classify(cls, row: pd.Series) ->str:"""Classify based on explicit flags, then keyword fallback."""if pd.notna(row.get('is_state')) and row['is_state']:return cls.STATEif pd.notna(row.get('is_foreign')) and row['is_foreign']:if pd.notna(row.get('is_institution')) and row['is_institution']:return cls.FOREIGN_INSTreturn cls.INDIVIDUALif pd.notna(row.get('is_institution')) and row['is_institution']:return cls.DOMESTIC_INST name =str(row.get('shareholder_name', '')).lower()ifany(kw in name for kw in cls.STATE_KEYWORDS):return cls.STATEifany(kw in name for kw in cls.FOREIGN_KEYWORDS):return cls.FOREIGN_INSTreturn cls.INDIVIDUAL```## Xây dựng bảng danh mục nắm giữ {#sec-institutionalholdings-panel}Chúng ta xây dựng bảng danh mục nắm giữ (tức là phiên bản Việt Nam của việc gộp các bộ dữ liệu 13F Loại 1 và Loại 3). Các bước chính là:1. Xác định phiên bản đầu tiên có sẵn cho mỗi tổ hợp cổ đông-cổ phiếu-ngày báo cáo.2. Tính các khoảng trống báo cáo để đánh dấu các báo cáo đầu tiên và cuối cùng.3. Phân loại cổ đông.4. Điều chỉnh số cổ phiếu theo các sự kiện doanh nghiệp.```{python}#| label: holdings-panel#| code-summary: "Steps 2--4: Holdings Panel Construction"def build_holdings_panel( ownership: pd.DataFrame, adj_factors: pd.DataFrame, price_q: pd.DataFrame, company_profile: pd.DataFrame, begdate: str='2010-01-01', enddate: str='2024-12-31',) -> pd.DataFrame:""" Construct the institutional holdings panel from DataCore.vn ownership data. """ own = ownership.copy()# Align to quarter-end own['rdate'] = own['date'] + pd.offsets.QuarterEnd(0) own['fdate'] = own['date'] own = own[ (own['rdate'] >= begdate) & (own['rdate'] <= enddate) ].copy()# Keep earliest vintage per shareholder-ticker-rdate own = own.sort_values( ['shareholder_name', 'ticker', 'rdate', 'fdate'] ) fst_vint = ( own .groupby(['shareholder_name', 'ticker', 'rdate']) .first() .reset_index() )# ---- Reporting gaps for first/last flags ---- fst_vint = fst_vint.sort_values( ['shareholder_name', 'ticker', 'rdate'] ) grp = fst_vint.groupby(['shareholder_name', 'ticker']) fst_vint['lag_rdate'] = grp['rdate'].shift(1) fst_vint['qtr_gap'] = fst_vint.apply(lambda r: ( (r['rdate'].to_period('Q')- r['lag_rdate'].to_period('Q')).nif pd.notna(r['lag_rdate']) else np.nan ), axis=1 ) fst_vint['first_report'] = ( fst_vint['qtr_gap'].isna() | (fst_vint['qtr_gap'] >=2) )# Last report flag (forward gap) fst_vint = fst_vint.sort_values( ['shareholder_name', 'ticker', 'rdate'], ascending=[True, True, False] ) fst_vint['lead_rdate'] = grp['rdate'].shift(1) fst_vint['lead_gap'] = fst_vint.apply(lambda r: ( (r['lead_rdate'].to_period('Q')- r['rdate'].to_period('Q')).nif pd.notna(r['lead_rdate']) else np.nan ), axis=1 ) fst_vint['last_report'] = ( fst_vint['lead_gap'].isna() | (fst_vint['lead_gap'] >=2) ) fst_vint = fst_vint.drop( columns=['lag_rdate', 'qtr_gap', 'lead_rdate', 'lead_gap'], errors='ignore' )# ---- Classify shareholders ---- fst_vint['owner_type'] = fst_vint.apply( OwnershipType.classify, axis=1 )# ---- Adjust shares for corporate actions ---- fst_vint = fst_vint.merge( price_q[['ticker', 'qdate', 'cfacshr']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) fst_vint['shares_adj'] = ( fst_vint['shares_held'] * fst_vint['cfacshr'] ) fst_vint = fst_vint[fst_vint['shares_adj'] >0].copy() fst_vint = fst_vint.drop_duplicates( subset=['shareholder_name', 'ticker', 'rdate'] )# Merge company profileif company_profile isnotNone: fst_vint = fst_vint.merge( company_profile[['ticker', 'exchange', 'fol_limit']] .drop_duplicates(), on='ticker', how='left' ) cols = ['shareholder_name', 'ticker', 'rdate', 'fdate','shares_held', 'shares_adj', 'owner_type','first_report', 'last_report' ]if'exchange'in fst_vint.columns: cols.extend(['exchange', 'fol_limit']) holdings = fst_vint[cols].copy()print(f"Holdings panel: {len(holdings):,} observations")print(f" Shareholders: {holdings['shareholder_name'].nunique():,}")print(f" Stocks: {holdings['ticker'].nunique():,}")print(f" Quarters: {holdings['rdate'].nunique()}")return holdings```# Các thước đo sở hữu tổ chức {#sec-institutionalio-metrics}Trước khi tính các giao dịch, chúng ta thiết lập các thước đo sở hữu tổ chức chuẩn, vừa là đầu ra vừa là đầu vào cho phân tích giao dịch.## Tỷ lệ sở hữu tổ chức {#sec-institutionalio-ratio}Tỷ lệ sở hữu tổ chức (IO) của cổ phiếu $i$ tại thời điểm $t$ là:$$IO_{i,t} = \frac{\sum_{j \in \mathcal{J}} h_{j,i,t}}{TSO_{i,t}}$$ {#eq-io-ratio}trong đó $\mathcal{J}$ là tập hợp các nhà đầu tư tổ chức và $TSO_{i,t}$ là tổng số cổ phiếu đang lưu hành. Ở Việt Nam, chúng ta tính các tỷ lệ riêng cho từng loại hình sở hữu:$$IO_{i,t}^{\text{type}} = \frac{\sum_{j \in \mathcal{J}^{\text{type}}} h_{j,i,t}}{TSO_{i,t}},\quad \text{type} \in \{\text{State}, \text{Foreign}, \text{Domestic}, \text{Individual}\}$$ {#eq-io-by-type}```{python}#| label: io-ratio-computation#| code-summary: "Institutional Ownership Ratio Computation"def compute_io_ratios( holdings: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Compute IO ratios by type for each stock-quarter.""" agg = ( holdings .groupby(['ticker', 'rdate', 'owner_type'])['shares_adj'] .sum() .reset_index() ) io_wide = agg.pivot_table( index=['ticker', 'rdate'], columns='owner_type', values='shares_adj', fill_value=0 ).reset_index() io_wide.columns = [ c if c in ['ticker', 'rdate']elsef'shares_{c.lower().replace(" ", "_")}'for c in io_wide.columns ] io_wide = io_wide.merge( price_q[['ticker', 'qdate', 'tso']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) share_cols = [c for c in io_wide.columns if c.startswith('shares_')]for col in share_cols: ratio_name = col.replace('shares_', 'io_') io_wide[ratio_name] = io_wide[col] / io_wide['tso'] inst_cols = [ c for c in io_wide.columnsif c.startswith('shares_')and'individual'notin cand'treasury'notin c ] io_wide['io_total_inst'] = ( io_wide[inst_cols].sum(axis=1) / io_wide['tso'] )return io_wide```## Mức độ tập trung sở hữu: chỉ số Herfindahl-Hirschman {#sec-institutionalhhi}HHI đo mức độ tập trung sở hữu:$$HHI_{i,t} = \sum_{j=1}^{N_{i,t}} \left(\frac{h_{j,i,t}}{\sum_{k=1}^{N_{i,t}} h_{k,i,t}}\right)^2$$ {#eq-hhi}trong đó $N_{i,t}$ là số lượng cổ đông. HHI nằm trong khoảng từ $1/N_{i,t}$ (phân bổ đều) đến 1 (một cổ đông duy nhất). Ở Việt Nam, sở hữu có xu hướng tập trung cao do các khối cổ phần lớn của nhà nước và gia đình sáng lập.```{python}#| label: hhi-computation#| code-summary: "Ownership Concentration (HHI)"def compute_hhi(holdings: pd.DataFrame) -> pd.DataFrame:"""Compute HHI for each stock-quarter, overall and institutional."""def _hhi(shares: pd.Series) ->float: total = shares.sum()if total <=0:return np.nan weights = shares / totalreturn (weights **2).sum() hhi_overall = ( holdings.groupby(['ticker', 'rdate'])['shares_adj'] .apply(_hhi).reset_index() .rename(columns={'shares_adj': 'hhi_overall'}) ) inst = holdings[ holdings['owner_type'].isin(OwnershipType.ALL_INSTITUTIONAL) ] hhi_inst = ( inst.groupby(['ticker', 'rdate'])['shares_adj'] .apply(_hhi).reset_index() .rename(columns={'shares_adj': 'hhi_institutional'}) )return hhi_overall.merge(hhi_inst, on=['ticker', 'rdate'], how='left')```## Độ rộng sở hữu {#sec-institutionalbreadth}Theo @chen2000value, độ rộng sở hữu là số lượng các cổ đông tổ chức:$$\text{Breadth}_{i,t} = \#\{j : h_{j,i,t} > 0, \, j \in \mathcal{J}\}$$ {#eq-breadth}*Thay đổi* về độ rộng dự báo lợi nhuận tương lai:$$\Delta\text{Breadth}_{i,t} = \text{Breadth}_{i,t} - \text{Breadth}_{i,t-1}$$ {#eq-breadth-change}```{python}#| label: breadth-computation#| code-summary: "Ownership Breadth and Breadth Changes"def compute_breadth(holdings: pd.DataFrame) -> pd.DataFrame:"""Compute ownership breadth and changes by type.""" breadth = ( holdings[ holdings['owner_type'].isin(OwnershipType.ALL_INSTITUTIONAL) ] .groupby(['ticker', 'rdate', 'owner_type'])['shareholder_name'] .nunique() .reset_index() .rename(columns={'shareholder_name': 'n_holders'}) ) breadth_wide = breadth.pivot_table( index=['ticker', 'rdate'], columns='owner_type', values='n_holders', fill_value=0 ).reset_index() breadth_wide.columns = [ c if c in ['ticker', 'rdate']elsef'n_{c.lower().replace(" ", "_")}'for c in breadth_wide.columns ] n_cols = [c for c in breadth_wide.columns if c.startswith('n_')] breadth_wide['n_total_inst'] = breadth_wide[n_cols].sum(axis=1) breadth_wide = breadth_wide.sort_values(['ticker', 'rdate'])for col in n_cols + ['n_total_inst']: breadth_wide[f'd_{col}'] = ( breadth_wide.groupby('ticker')[col].diff() )return breadth_wide```($\text{BS} = -1$) được tạo ra cho vị thế trước đó, được gán vào quý sau báo cáo cuối cùng.Đối với các khoảng trống ở giữa (có báo cáo tại $t-2$ và $t$ nhưng không có tại $t-1$), chúng ta tách thành:- Một giao dịch bán chấm dứt tại $t-1$ với giá trị $-h_{j,i,t-2}^{\text{adj}}$;- Một giao dịch mua khởi tạo tại $t$ với giá trị $h_{j,i,t}$.## Triển khai {#sec-institutionaltrade-impl}```{python}#| label: trade-computation#| code-summary: "Step 5: Institutional Trade Computation"def compute_trades( holdings: pd.DataFrame, adj_factors: pd.DataFrame,) -> pd.DataFrame:""" Compute institutional trades from holdings panel. Uses vectorized conditional logic (NOT apply()) for performance. Algorithm: 1. Sort holdings by shareholder, ticker, quarter 2. Compute lagged holdings and reporting gaps 3. Apply modified trade logic based on first_report, gap 4. Handle terminating sales and intermediate gaps 5. Append all trade records """ t1 = holdings.sort_values( ['shareholder_name', 'ticker', 'rdate'] ).copy()# Previous holding quarter and shares grp = t1.groupby(['shareholder_name', 'ticker']) t1['phrdate'] = grp['rdate'].shift(1) t1['pshares_adj'] = grp['shares_adj'].shift(1)# Raw trade t1['trade'] = t1['shares_adj'] - t1['pshares_adj']# Quarter gap t1['qtrgap'] = t1.apply(lambda r: ( (r['rdate'].to_period('Q')- r['phrdate'].to_period('Q')).nif pd.notna(r['phrdate']) else np.nan ), axis=1 )# Boundary detection keys t1['l_key'] = ( t1['shareholder_name'] +'_'+ t1['ticker'] ).shift(1) t1['n_key'] = ( t1['shareholder_name'] +'_'+ t1['ticker'] ).shift(-1) t1['curr_key'] = t1['shareholder_name'] +'_'+ t1['ticker']# ---- Vectorized trade classification ---- is_new = (t1['curr_key'] != t1['l_key']) not_first =~t1['first_report'] consec = (t1['qtrgap'] ==1) gap = (t1['qtrgap'] !=1) & t1['qtrgap'].notna() cond1 = is_new cond1_1 = is_new & not_first cond2_1 = (~is_new) & not_first & consec cond2_2 = (~is_new) & not_first & gap# Modified trade amounts t1['modtrade'] = t1['trade'] t1.loc[cond1, 'modtrade'] = np.nan t1.loc[cond1_1, 'modtrade'] = t1.loc[cond1_1, 'shares_adj'] t1.loc[cond2_1, 'modtrade'] = t1.loc[cond2_1, 'trade'] t1.loc[cond2_2, 'modtrade'] = t1.loc[cond2_2, 'shares_adj']# Buy/sale classification t1['buysale'] = np.nan t1.loc[cond1_1, 'buysale'] =1 t1.loc[cond2_1, 'buysale'] = (2* np.sign(t1.loc[cond2_1, 'trade']) ) t1.loc[cond2_2, 'buysale'] =1.5# placeholder for split# ---- Handle intermediate gaps (buysale == 1.5) ---- t2 = t1[t1['buysale'] ==1.5].copy() t2['rdate'] = t2['phrdate'] + pd.offsets.QuarterEnd(1) t2['buysale'] =-1 t2['modtrade'] =-t2['pshares_adj'] t1.loc[t1['buysale'] ==1.5, 'buysale'] =1# ---- Terminating sales ---- is_last_combo = (t1['curr_key'] != t1['n_key']) not_last_rpt =~t1['last_report'] t3 = t1[is_last_combo & not_last_rpt].copy() t3['rdate'] = t3['rdate'] + pd.offsets.QuarterEnd(1) t3['modtrade'] =-t3['shares_adj'] t3['buysale'] =-1# ---- Combine ---- trades = pd.concat([t1, t2, t3], ignore_index=True) trades = trades[ (trades['modtrade'] !=0) & trades['modtrade'].notna() & trades['buysale'].notna() ].copy() trades = trades[['rdate', 'shareholder_name', 'ticker', 'modtrade','buysale', 'owner_type', 'first_report', 'last_report' ]].rename(columns={'modtrade': 'trade'})print(f"\nTrade computation complete:")print(f" Total records: {len(trades):,}")print(f" Initiating buys: {(trades['buysale'] ==1).sum():,}")print(f" Incremental buys: {(trades['buysale'] ==2).sum():,}")print(f" Terminating sales:{(trades['buysale'] ==-1).sum():,}")print(f" Regular sales: {(trades['buysale'] ==-2).sum():,}")return trades```### Trực quan hóa giao dịch {#sec-institutionaltrade-viz}```{python}#| label: fig-trade-distribution#| fig-cap: "Phân phối các loại giao dịch của tổ chức qua các quý. Hình thể hiện các giao dịch mua khởi tạo, mua gia tăng, bán thông thường và bán chấm dứt, cung cấp góc nhìn về các kiểu hình gia nhập/rút lui của tổ chức trên thị trường Việt Nam."#| code-fold: truedef plot_trade_distribution(trades: pd.DataFrame):"""Plot time series of trade types by quarter.""" bs_labels = {1: 'Initiating Buy', 2: 'Incremental Buy',-1: 'Terminating Sale', -2: 'Regular Sale' } trades = trades.copy() trades['trade_type'] = trades['buysale'].map(bs_labels) counts = ( trades .groupby([pd.Grouper(key='rdate', freq='QE'), 'trade_type']) .size() .unstack(fill_value=0) ) fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True) buy_cols = [c for c in counts.columns if'Buy'in c] counts[buy_cols].plot( kind='bar', stacked=True, ax=axes[0], color=['#1f77b4', '#aec7e8'], width=0.8 ) axes[0].set_title('Panel A: Institutional Purchases', fontweight='bold') axes[0].set_ylabel('Number of Trades') sale_cols = [c for c in counts.columns if'Sale'in c] counts[sale_cols].plot( kind='bar', stacked=True, ax=axes[1], color=['#d62728', '#ff9896'], width=0.8 ) axes[1].set_title('Panel B: Institutional Sales', fontweight='bold') axes[1].set_ylabel('Number of Trades')for ax in axes: ax.tick_params(axis='x', rotation=45)for i, label inenumerate(ax.get_xticklabels()):if i %4!=0: label.set_visible(False) plt.tight_layout() plt.show()# plot_trade_distribution(trades)``````{python}#| label: fig-net-trading-by-type#| fig-cap: "Giao dịch ròng của tổ chức (mua trừ bán, tỷ VND) theo loại hình sở hữu. Các nhà đầu tư tổ chức nước ngoài thể hiện hành vi ngược chu kỳ, trong khi các đơn vị nhà nước cho thấy kiểu hình ổn định hơn, phù hợp với các mục tiêu mang tính chiến lược thay vì mục tiêu danh mục."#| code-fold: truedef plot_net_trading_by_type(trades: pd.DataFrame, price_q: pd.DataFrame):"""Plot net trading volume by owner type over time.""" _t = trades.merge( price_q[['ticker', 'qdate', 'p']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _t['trade_vnd'] = _t['trade'] * _t['p'] /1e9 net = ( _t .groupby([pd.Grouper(key='rdate', freq='QE'), 'owner_type']) ['trade_vnd'].sum() .unstack(fill_value=0) ) fig, ax = plt.subplots(figsize=(12, 6))for col in net.columns: ax.plot(net.index, net[col], label=col, color=OWNER_COLORS.get(col, '#333'), linewidth=1.5) ax.axhline(y=0, color='black', linewidth=0.5) ax.set_title('Net Institutional Trading by Ownership Type', fontweight='bold') ax.set_ylabel('Net Trading (Billions VND)') ax.legend(loc='best') plt.tight_layout() plt.show()# plot_net_trading_by_type(trades, price_q)```# Tài sản danh mục, dòng vốn và lợi nhuận {#sec-institutionalassets-flows}Phần này tính tổng tài sản danh mục, tổng hợp các giao dịch mua và bán, và lợi nhuận cấp danh mục cho từng nhà đầu tư tổ chức.## Tổng tài sản và lợi nhuận danh mục {#sec-institutionalassets}Với mỗi nhà quản lý $j$ và quý $t$, tài sản danh mục là:$$A_{j,t} = \sum_{i=1}^{N_{j,t}} h_{j,i,t} \cdot P_{i,t}$$ {#eq-assets}Lợi nhuận danh mục theo giả định mua và nắm giữ là:$$R_{j,t}^{p} = \frac{\sum_{i=1}^{N_{j,t}} h_{j,i,t} \cdot P_{i,t} \cdot r_{i,t+1}}{\sum_{i=1}^{N_{j,t}} h_{j,i,t} \cdot P_{i,t}}$$ {#eq-portfolio-return}```{python}#| label: assets-computation#| code-summary: "Step 6a: Portfolio Assets and Returns"def compute_assets_and_returns( holdings: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Compute total portfolio assets and buy-and-hold returns.""" _assets = holdings[ ['shareholder_name', 'ticker', 'rdate', 'shares_adj'] ].merge( price_q[['ticker', 'qdate', 'p', 'qret']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _assets['hold_per_stock'] = _assets['shares_adj'] * _assets['p'] /1e6 _assets['next_value'] = ( _assets['shares_adj'] * _assets['p'] * _assets['qret'] ) _assets['curr_value'] = _assets['shares_adj'] * _assets['p'] assets = ( _assets .groupby(['shareholder_name', 'rdate']) .agg( assets=('hold_per_stock', 'sum'), total_next=('next_value', 'sum'), total_curr=('curr_value', 'sum'), ) .reset_index() ) assets['pret'] = assets['total_next'] / assets['total_curr'] assets = assets.drop(columns=['total_next', 'total_curr'])return assets```## Tổng hợp giao dịch mua và bán {#sec-institutionalaggregate-buysales}Tổng giao dịch mua và bán của nhà quản lý $j$ trong quý $t$:$$B_{j,t} = \sum_{i : \Delta h > 0} \Delta h_{j,i,t} \cdot P_{i,t}, \qquadS_{j,t} = \sum_{i : \Delta h < 0} |\Delta h_{j,i,t}| \cdot P_{i,t}$$ {#eq-total-buys-sales}Lãi từ giao dịch là:$$G_{j,t} = \sum_{i=1}^{N_{j,t}} \Delta h_{j,i,t} \cdot P_{i,t} \cdot r_{i,t+1}$$ {#eq-trade-gain}```{python}#| label: buys-sales-computation#| code-summary: "Step 6b: Aggregate Buys, Sales, and Trade Gains"def compute_buys_sales( trades: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Compute aggregate buys, sales, trade gains per manager-quarter.""" _flows = trades.merge( price_q[['ticker', 'qdate', 'p', 'qret']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _flows['tbuys'] = ( _flows['trade'] * (_flows['trade'] >0).astype(float)* _flows['p'] /1e6 ) _flows['tsales'] = ( (-1) * _flows['trade'] * (_flows['trade'] <0).astype(float)* _flows['p'] /1e6 ) _flows['tgain'] = ( _flows['trade'] * _flows['p'] * _flows['qret'] /1e6 ) flows = ( _flows .groupby(['shareholder_name', 'rdate']) .agg( tbuys=('tbuys', 'sum'), tsales=('tsales', 'sum'), tgain=('tgain', 'sum'), ) .reset_index() )return flows```# Dòng vốn ròng và tỷ lệ vòng quay {#sec-institutionalturnover}## Dòng vốn ròng {#sec-institutionalnetflows}Dòng vốn ròng tách các quyết định phân bổ vốn khỏi lợi nhuận đầu tư:$$\text{NetFlow}_{j,t} = A_{j,t} - A_{j,t-1}(1 + R_{j,t}^p)$$ {#eq-netflow-def}::: callout-warning## Diễn giải dòng vốn ròng tại Việt NamĐối với các đơn vị nhà nước hoặc các cổ đông sở hữu chéo trong doanh nghiệp, "dòng vốn ròng" không nhất thiết phản ánh các quyết định đầu tư. Những thay đổi sở hữu nhà nước thường là kết quả của chính sách của Chính phủ (cổ phần hóa, các chương trình thoái vốn). Việc diễn giải cần tính đến bối cảnh thể chế.:::## Ba thước đo tỷ lệ vòng quay {#sec-institutionalturnover-measures}```{python}#| label: turnover-computation#| code-summary: "Step 7: Net Flows and Turnover Ratios"def compute_aggregates( holdings: pd.DataFrame, assets: pd.DataFrame, flows: pd.DataFrame,) -> pd.DataFrame:""" Compute net flows and three turnover measures. 1. Carhart (1997): min(buys, sales) / avg(assets) 2. Flow-adjusted: [min(buys, sales) + |net flows|] / lag assets 3. Symmetric: [buys + sales - |net flows|] / lag assets """ report_flags = ( holdings .groupby(['shareholder_name', 'rdate']) .agg(first_report=('first_report', 'any'), last_report=('last_report', 'any')) .reset_index() ) agg = report_flags.merge( assets, on=['shareholder_name', 'rdate'], how='inner' ) agg = agg.merge( flows, on=['shareholder_name', 'rdate'], how='left' ) agg = agg.sort_values(['shareholder_name', 'rdate']) agg['assets_comp'] = agg['assets'] * (1+ agg['pret'].fillna(0)) grp = agg.groupby('shareholder_name') agg['lassets_comp'] = grp['assets_comp'].shift(1) agg['lassets'] = grp['assets'].shift(1)# Trade gain return agg['tgainret'] = agg['tgain'] / (agg['tbuys'] + agg['tsales'])# Net flows agg['netflows'] = agg['assets'] - agg['lassets_comp']# Turnover 1: Carhart (1997) agg['turnover1'] = ( agg[['tbuys', 'tsales']].min(axis=1) / agg[['assets', 'lassets']].mean(axis=1) )# Turnover 2: Flow-adjusted agg['turnover2'] = ( (agg[['tbuys', 'tsales']].min(axis=1)+ agg['netflows'].abs().fillna(0))/ agg['lassets'] )# Turnover 3: Symmetric agg['turnover3'] = ( (agg['tbuys'].fillna(0) + agg['tsales'].fillna(0)- agg['netflows'].abs().fillna(0))/ agg['lassets'] )# Missing for first report first_mask = agg['first_report']for col in ['netflows', 'tgainret','turnover1', 'turnover2', 'turnover3']: agg.loc[first_mask, col] = np.nan agg = agg.drop(columns=['assets_comp', 'lassets_comp', 'lassets'])print(f"\nAggregates: {len(agg):,} manager-quarters")print(f" Turnover1 mean: {agg['turnover1'].mean():.4f}")print(f" Turnover2 mean: {agg['turnover2'].mean():.4f}")print(f" Turnover3 mean: {agg['turnover3'].mean():.4f}")return agg```### Thống kê mô tả tỷ lệ vòng quay {#sec-institutionalturnover-stats}```{python}#| label: tbl-turnover-summary#| tbl-cap: "Thống kê mô tả của ba thước đo tỷ lệ vòng quay theo các loại nhà đầu tư tổ chức tại Việt Nam. Vòng quay 1 theo @Carhart1997, Vòng quay 2 cộng thêm giá trị tuyệt đối của dòng vốn ròng, và Vòng quay 3 sử dụng định nghĩa đối xứng."#| code-fold: truedef turnover_summary_table( aggregates: pd.DataFrame, holdings: pd.DataFrame,) -> pd.DataFrame:"""Publication-quality turnover summary statistics table.""" owner_map = ( holdings.groupby('shareholder_name')['owner_type'] .first().reset_index() ) agg = aggregates.merge(owner_map, on='shareholder_name', how='left') turnover_cols = ['turnover1', 'turnover2', 'turnover3'] results = []for otype in ['All'] + OwnershipType.ALL_TYPES: subset = agg if otype =='All'else agg[agg['owner_type'] == otype] row = {'Owner Type': otype, 'N': len(subset)}for col in turnover_cols: s = subset[col].dropna() row[f'{col}_mean'] = s.mean() row[f'{col}_median'] = s.median() row[f'{col}_std'] = s.std() results.append(row)return pd.DataFrame(results).round(4)# turnover_summary_table(aggregates, holdings)``````{python}#| label: fig-turnover-timeseries#| fig-cap: "Tỷ lệ vòng quay bình quân hằng quý (định nghĩa Carhart) theo loại hình sở hữu, 2010--2024. Các nhà đầu tư tổ chức nước ngoài có tỷ lệ vòng quay cao hơn các tổ chức trong nước, phù hợp với kỳ hạn đầu tư ngắn hơn và việc quản lý danh mục chủ động hơn."#| code-fold: truedef plot_turnover_timeseries( aggregates: pd.DataFrame, holdings: pd.DataFrame):"""Plot turnover time series by ownership type.""" owner_map = ( holdings.groupby('shareholder_name')['owner_type'] .first().reset_index() ) agg = aggregates.merge(owner_map, on='shareholder_name', how='left') fig, ax = plt.subplots(figsize=(12, 6))for otype in OwnershipType.ALL_INSTITUTIONAL: subset = agg[agg['owner_type'] == otype] qtr_mean = ( subset .groupby(pd.Grouper(key='rdate', freq='QE'))['turnover1'] .mean() ) ax.plot(qtr_mean.index, qtr_mean.values, label=otype, color=OWNER_COLORS.get(otype, '#333'), linewidth=1.5) ax.set_title('Quarterly Average Turnover (Carhart)', fontweight='bold') ax.set_ylabel('Turnover Ratio') ax.legend(loc='best') ax.yaxis.set_major_formatter(mticker.PercentFormatter(1.0)) plt.tight_layout() plt.show()# plot_turnover_timeseries(aggregates, holdings)```# Phân tích sở hữu nước ngoài {#sec-institutionalforeign}Giới hạn sở hữu nước ngoài của Việt Nam tạo ra những khía cạnh phân tích độc đáo không có trong các nghiên cứu về thị trường phát triển.## Mức sử dụng FOL {#sec-institutionalfol-util}$$\text{FOL\_Util}_{i,t} = \frac{FO_{i,t}}{FOL_i}$$ {#eq-fol-util}Các cổ phiếu có $\text{FOL\_Util}_{i,t} \to 1$ phải đối mặt với các hạn chế mua cơ học đối với nhà đầu tư nước ngoài.```{python}#| label: fol-analytics#| code-summary: "Foreign Ownership Limit Analytics"def compute_fol_analytics( foreign_ownership: pd.DataFrame, company_profile: pd.DataFrame,) -> pd.DataFrame:"""Compute FOL utilization and related metrics.""" fo = foreign_ownership.copy() fo = fo.merge( company_profile[['ticker', 'fol_limit']].drop_duplicates(), on='ticker', how='left' ) fo['fol_utilization'] = fo['foreign_pct'] / fo['fol_limit'] fo['foreign_room'] = fo['fol_limit'] - fo['foreign_pct'] fo['fol_binding'] = (fo['fol_utilization'] >=0.98) fo['fol_category'] = pd.cut( fo['fol_utilization'], bins=[0, 0.25, 0.50, 0.75, 0.95, 1.0, float('inf')], labels=['<25%', '25-50%', '50-75%', '75-95%','95-100%', '>100%'] )return fo```## Hồi quy phần bù room {#sec-institutionalroom-premium}Khi sở hữu nước ngoài tiến gần FOL, "room" còn lại trở nên khan hiếm. Chúng ta mô hình hóa:$$r_{i,t+1} = \alpha + \beta_1 \cdot \text{FOL\_Util}_{i,t} +\beta_2 \cdot \text{FOL\_Util}_{i,t}^2 + \gamma \cdot X_{i,t} + \varepsilon_{i,t}$$ {#eq-room-premium}Số hạng bậc hai nắm bắt sự gia tốc phi tuyến của phần bù khi sở hữu tiến gần giới hạn.```{python}#| label: room-premium-regression#| code-summary: "FOL Room Premium Regression"def estimate_room_premium( fol_analytics: pd.DataFrame, price_q: pd.DataFrame,) ->dict:"""Estimate foreign ownership room premium via panel regression.""" fol_q = ( fol_analytics .assign(qdate=lambda x: x['date'] + pd.offsets.QuarterEnd(0)) .groupby(['ticker', 'qdate']) .agg(fol_utilization=('fol_utilization', 'last'), foreign_room=('foreign_room', 'last')) .reset_index() ) panel = fol_q.merge( price_q[['ticker', 'qdate', 'mcap', 'qret']], on=['ticker', 'qdate'], how='inner' ) panel['log_mcap'] = np.log(panel['mcap'] +1) panel['fol_util_sq'] = panel['fol_utilization'] **2 panel = panel.dropna(subset=['qret', 'fol_utilization', 'log_mcap']) X = panel[['fol_utilization', 'fol_util_sq', 'log_mcap']] X = sm.add_constant(X) y = panel['qret'] model = sm.OLS(y, X).fit( cov_type='cluster', cov_kwds={'groups': panel['ticker']} )return {'model': model, 'n_obs': len(panel)}# results = estimate_room_premium(fol_analytics, price_q)``````{python}#| label: fig-fol-distribution#| fig-cap: "Phân phối mức sử dụng FOL của các cổ phiếu niêm yết Việt Nam. Phân phối hai đỉnh phản ánh hai nhóm: các cổ phiếu có sự quan tâm của nước ngoài tối thiểu (đỉnh bên trái) và các cổ phiếu tiến gần mức trần FOL (đỉnh bên phải)."#| code-fold: truedef plot_fol_utilization(fol_analytics: pd.DataFrame):"""Plot FOL utilization distribution.""" latest = ( fol_analytics.sort_values(['ticker', 'date']) .groupby('ticker').last().reset_index() ) fig, axes = plt.subplots(1, 2, figsize=(14, 5)) axes[0].hist(latest['fol_utilization'].dropna(), bins=50, color='#1f77b4', alpha=0.7, edgecolor='white') axes[0].axvline(x=0.95, color='red', linestyle='--', label='95% threshold') axes[0].set_title('Panel A: FOL Utilization Distribution', fontweight='bold') axes[0].set_xlabel('FOL Utilization Ratio') axes[0].set_ylabel('Number of Stocks') axes[0].legend()for exch in ['HOSE', 'HNX', 'UPCOM']: sub = latest[latest.get('exchange') == exch]iflen(sub) >0: axes[1].hist(sub['fol_utilization'].dropna(), bins=30, alpha=0.5, label=exch, color=EXCHANGE_COLORS.get(exch, '#333')) axes[1].set_title('Panel B: By Exchange', fontweight='bold') axes[1].set_xlabel('FOL Utilization Ratio') axes[1].legend() plt.tight_layout() plt.show()# plot_fol_utilization(fol_analytics)```# Quy trình hoàn chỉnh {#sec-institutionalpipeline}Chúng ta tích hợp tất cả các bước vào một hàm đầu-cuối duy nhất:```{python}#| label: complete-pipeline#| code-summary: "Complete End-to-End Pipeline"def run_complete_pipeline( dc: 'DataCoreReader', begdate: str='2010-01-01', enddate: str='2024-12-31',) -> Dict[str, pd.DataFrame]:""" Execute the complete institutional ownership analytics pipeline. Steps: 1. Build corporate action adjustment factors 2. Process stock prices 3. Construct holdings panel (Steps 2-4) 4. Compute IO metrics 5. Compute institutional trades (Step 5) 6. Compute portfolio assets and returns (Step 6a) 7. Compute aggregate buys, sales, trade gains (Step 6b) 8. Compute net flows and turnover (Step 7) 9. Compute foreign ownership analytics Returns dict of all output DataFrames. """print("="*60)print("INSTITUTIONAL TRADES, FLOWS, AND TURNOVER PIPELINE")print(f"Sample: {begdate} to {enddate}")print("="*60)print("\n[1/9] Building adjustment factors...") adj_factors = build_adjustment_factors(dc.corporate_actions)print("\n[2/9] Processing stock prices...") price_q, qret = process_prices( dc.prices, adj_factors, begdate, enddate )print("\n[3/9] Building holdings panel...") holdings = build_holdings_panel( dc.ownership, adj_factors, price_q, dc.company_profile, begdate, enddate )print("\n[4/9] Computing ownership metrics...") io_ratios = compute_io_ratios(holdings, price_q) hhi = compute_hhi(holdings) breadth = compute_breadth(holdings)print("\n[5/9] Computing institutional trades...") trades = compute_trades(holdings, adj_factors)print("\n[6/9] Computing portfolio assets...") assets = compute_assets_and_returns(holdings, price_q)print("\n[7/9] Computing aggregate buys and sales...") flows = compute_buys_sales(trades, price_q)print("\n[8/9] Computing net flows and turnover...") aggregates = compute_aggregates(holdings, assets, flows)print("\n[9/9] Computing foreign ownership analytics...") fol_analytics = compute_fol_analytics( dc.foreign_ownership, dc.company_profile )print("\n"+"="*60)print("PIPELINE COMPLETE")print("="*60)return {'price_q': price_q, 'holdings': holdings,'io_ratios': io_ratios, 'hhi': hhi,'breadth': breadth, 'trades': trades,'assets': assets, 'flows': flows,'aggregates': aggregates, 'fol_analytics': fol_analytics, }# dc = DataCoreReader('/path/to/datacore_data', file_format='parquet')# results = run_complete_pipeline(dc, '2010-01-01', '2024-12-31')```# Các mở rộng nâng cao {#sec-institutionalextensions}## Các thước đo hành vi bầy đàn {#sec-institutionalherding}Theo @sias2004institutional, thước đo hành vi bầy đàn Lakonishok-Shleifer-Vishny là:$$HM_{i,t} = \left|\frac{B_{i,t}}{B_{i,t} + S_{i,t}} - p_t\right|- E\left[\left|\frac{B_{i,t}}{B_{i,t} + S_{i,t}} - p_t\right|\right]$$ {#eq-herding}trong đó $B_{i,t}$ là số nhà quản lý mua cổ phiếu $i$ trong quý $t$, $S_{i,t}$ là số nhà quản lý bán, và $p_t$ là tỷ lệ người mua kỳ vọng khi giao dịch độc lập.```{python}#| label: herding-measure#| code-summary: "LSV Herding Measure"def compute_lsv_herding( trades: pd.DataFrame, min_traders: int=5,) -> pd.DataFrame:"""Compute LSV herding measure for each stock-quarter.""" tc = ( trades.groupby(['ticker', 'rdate']) .apply(lambda g: pd.Series({'n_buyers': (g['trade'] >0).sum(),'n_sellers': (g['trade'] <0).sum(),'n_traders': len(g), })) .reset_index() ) tc = tc[tc['n_traders'] >= min_traders].copy() tc['buy_prop'] = tc['n_buyers'] / tc['n_traders'] tc['p_t'] = tc.groupby('rdate')['buy_prop'].transform('mean') tc['raw_hm'] = (tc['buy_prop'] - tc['p_t']).abs()def expected_abs_deviation(row): n =int(row['n_traders']) p = row['p_t']if n ==0or p ==0or p ==1:return0from scipy.stats import binom k = np.arange(0, n +1) probs = binom.pmf(k, n, p)return np.sum(np.abs(k / n - p) * probs) tc['expected_hm'] = tc.apply(expected_abs_deviation, axis=1) tc['herding'] = tc['raw_hm'] - tc['expected_hm'] tc['buy_herding'] = np.where( tc['buy_prop'] > tc['p_t'], tc['herding'], np.nan ) tc['sell_herding'] = np.where( tc['buy_prop'] < tc['p_t'], tc['herding'], np.nan )return tc[['ticker', 'rdate', 'n_buyers', 'n_sellers','n_traders', 'herding', 'buy_herding', 'sell_herding']]```## Tính bền vững của nhu cầu {#sec-institutionalpersistence}@sias2004institutional chỉ ra rằng nhu cầu của tổ chức có tính bền vững:$$\rho_t = \text{Corr}\left(\Delta IO_{i,t},\, \Delta IO_{i,t-1}\right)$$ {#eq-persistence}```{python}#| label: demand-persistence#| code-summary: "Institutional Demand Persistence"def compute_demand_persistence(io_ratios: pd.DataFrame) -> pd.DataFrame:"""Rolling cross-sectional correlation of IO changes.""" io = io_ratios[['ticker', 'rdate', 'io_total_inst']].copy() io = io.sort_values(['ticker', 'rdate']) io['dio'] = io.groupby('ticker')['io_total_inst'].diff() io['lag_dio'] = io.groupby('ticker')['dio'].shift(1) persistence = ( io.dropna(subset=['dio', 'lag_dio']) .groupby('rdate') .apply(lambda g: g['dio'].corr(g['lag_dio'])) .reset_index() .rename(columns={0: 'persistence'}) ) persistence = persistence.sort_values('rdate') persistence['persistence_ma'] = ( persistence['persistence'].rolling(window=20, min_periods=4).mean() )return persistence``````{python}#| label: fig-persistence#| fig-cap: "Tính bền vững theo mặt cắt ngang của nhu cầu tổ chức tại Việt Nam (trung bình trượt 5 năm). Giá trị dương cho thấy các mô hình giao dịch của tổ chức giống động lượng."#| code-fold: truedef plot_demand_persistence(persistence: pd.DataFrame): fig, ax = plt.subplots(figsize=(12, 5)) ax.bar(persistence['rdate'], persistence['persistence'], width=80, alpha=0.3, color='#1f77b4', label='Quarterly') ax.plot(persistence['rdate'], persistence['persistence_ma'], color='#d62728', linewidth=2, label='Rolling Average') ax.axhline(y=0, color='black', linewidth=0.5) ax.set_title('Persistence of Institutional Demand', fontweight='bold') ax.set_ylabel('Cross-Sectional Correlation') ax.legend() plt.tight_layout() plt.show()```## Hàm lượng thông tin của các giao dịch {#sec-institutionalinfo-content}Theo @alexander2007does, tỷ số InfoTrade đo tỷ trọng giá trị giao dịch bằng tiền đến từ các quyết định gia nhập/rút lui so với các điều chỉnh vị thế:$$\text{InfoTrade}_{i,t} = \frac{\sum_{j: BS \in \{+1,-1\}} |\Delta h_{j,i,t}| \cdot P_{i,t}}{\sum_j |\Delta h_{j,i,t}| \cdot P_{i,t}}$$ {#eq-info-trade}```{python}#| label: info-trade-ratio#| code-summary: "Information Content of Trades"def compute_info_trade_ratio( trades: pd.DataFrame, price_q: pd.DataFrame) -> pd.DataFrame:"""Compute info trade ratio for each stock-quarter.""" _t = trades.merge( price_q[['ticker', 'qdate', 'p']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _t['dollar_trade'] = _t['trade'].abs() * _t['p'] /1e6 _t['is_discrete'] = _t['buysale'].isin([1, -1]) info = _t.groupby(['ticker', 'rdate']).apply(lambda g: pd.Series({'discrete_vol': g.loc[g['is_discrete'], 'dollar_trade'].sum(),'total_vol': g['dollar_trade'].sum(), }) ).reset_index() info['info_trade_ratio'] = ( info['discrete_vol'] / info['total_vol'] ).clip(0, 1)return info```# Các ứng dụng thực nghiệm {#sec-institutionalapplications}## Ứng dụng 1: Thay đổi sở hữu tổ chức và lợi nhuận tương lai {#sec-institutionalapp-returns}Chúng ta kiểm định xem thay đổi trong sở hữu tổ chức có dự báo lợi nhuận cổ phiếu tương lai hay không [@chen2000value] thông qua các hồi quy Fama-MacBeth:$$r_{i,t+1} = \alpha_t + \beta_{1,t} \cdot \Delta IO_{i,t} + \beta_{2,t} \cdot\Delta\text{Breadth}_{i,t} + \gamma_t \cdot X_{i,t} + \varepsilon_{i,t}$$ {#eq-fama-macbeth}```{python}#| label: fama-macbeth#| code-summary: "Fama-MacBeth: IO Changes and Future Returns"def fama_macbeth_io_returns( io_ratios: pd.DataFrame, breadth: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Run Fama-MacBeth regressions of future returns on IO changes.""" panel = io_ratios[['ticker', 'rdate', 'io_total_inst']].merge( breadth[['ticker', 'rdate', 'n_total_inst', 'd_n_total_inst']], on=['ticker', 'rdate'], how='inner' ).merge( price_q[['ticker', 'qdate', 'mcap', 'qret']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) panel = panel.sort_values(['ticker', 'rdate']) panel['dio'] = panel.groupby('ticker')['io_total_inst'].diff() panel['log_mcap'] = np.log(panel['mcap'] +1) panel['mom'] = panel.groupby('ticker')['qret'].shift(1) reg_vars = ['qret', 'dio', 'd_n_total_inst', 'log_mcap', 'mom'] panel = panel.dropna(subset=reg_vars) quarters =sorted(panel['rdate'].unique()) results = []for q in quarters: qdata = panel[panel['rdate'] == q]iflen(qdata) <30:continue X = sm.add_constant( qdata[['dio', 'd_n_total_inst', 'log_mcap', 'mom']] )try: model = sm.OLS(qdata['qret'], X).fit() coefs = model.params.to_dict() coefs['rdate'] = q coefs['n_obs'] =len(qdata) results.append(coefs)exceptException:continue fm = pd.DataFrame(results)# Time-series averages with Newey-West t-statisticsprint("\nFama-MacBeth Results:")print("="*50)for var in ['const', 'dio', 'd_n_total_inst', 'log_mcap', 'mom']: coefs = fm[var].dropna() mean_c = coefs.mean() nw_se = sm.OLS( coefs - mean_c, np.ones(len(coefs)) ).fit(cov_type='HAC', cov_kwds={'maxlags': 4}).bse[0] t = mean_c / nw_se if nw_se >0else np.nanprint(f" {var:20s}: coef={mean_c:8.4f}, t={t:6.2f}")return fm```## Ứng dụng 2: Tỷ lệ vòng quay và hiệu quả hoạt động {#sec-institutionalapp-turnover}@yan2008liquidity ghi nhận mối quan hệ dương giữa tỷ lệ vòng quay và hiệu quả hoạt động. Chúng ta kiểm định tại Việt Nam:$$\alpha_{j,t} = a + b \cdot \text{Turnover}_{j,t-1} + c \cdot\log(A_{j,t-1}) + d \cdot \text{Flow}_{j,t} + \varepsilon_{j,t}$$ {#eq-turnover-perf}```{python}#| label: turnover-performance#| code-summary: "Turnover-Performance Relationship"def turnover_performance_regression( aggregates: pd.DataFrame,) ->dict:"""Test turnover-performance relationship.""" agg = aggregates.sort_values(['shareholder_name', 'rdate']).copy() agg['lag_turnover1'] = ( agg.groupby('shareholder_name')['turnover1'].shift(1) ) agg['log_assets'] = np.log(agg['assets'] +1) agg['flow_ratio'] = agg['netflows'] / agg['assets'].shift(1) panel = agg.dropna( subset=['pret', 'lag_turnover1', 'log_assets', 'flow_ratio'] )for col in ['pret', 'lag_turnover1', 'flow_ratio']: lo, hi = panel[col].quantile([0.01, 0.99]) panel[col] = panel[col].clip(lo, hi) X = sm.add_constant( panel[['lag_turnover1', 'log_assets', 'flow_ratio']] ) model = sm.OLS(panel['pret'], X).fit( cov_type='cluster', cov_kwds={'groups': panel['shareholder_name']} )return {'model': model, 'n': len(panel)}```## Ứng dụng 3: Giao dịch của nước ngoài so với trong nước {#sec-institutionalapp-foreign-domestic}```{python}#| label: foreign-domestic-comparison#| code-summary: "Foreign vs. Domestic Trading Comparison"def compare_foreign_domestic( trades: pd.DataFrame, price_q: pd.DataFrame,) -> pd.DataFrame:"""Compare trading patterns between foreign and domestic institutions.""" _t = trades.merge( price_q[['ticker', 'qdate', 'p']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _t['dollar_trade'] = _t['trade'] * _t['p'] /1e6 _t['is_buy'] = _t['trade'] >0return ( _t[_t['owner_type'].isin(OwnershipType.INSTITUTIONAL)] .groupby('owner_type') .agg( n_trades=('trade', 'count'), n_buys=('is_buy', 'sum'), avg_dollar=('dollar_trade', lambda x: x.abs().mean()), net_buying=('dollar_trade', 'sum'), pct_initiating=('buysale', lambda x: (x.abs() ==1).mean()), ) .reset_index() )``````{python}#| label: fig-cumulative-net-buying#| fig-cap: "Giá trị mua ròng tích lũy của các nhà đầu tư tổ chức nước ngoài so với trong nước (tỷ VND). Các xu hướng khác biệt phản ánh các nhiệm vụ đầu tư (mandate) khác nhau: các tổ chức nước ngoài phản ứng với chu kỳ rủi ro toàn cầu, trong khi các tổ chức trong nước nhạy cảm hơn với điều kiện địa phương."#| code-fold: truedef plot_cumulative_net_buying( trades: pd.DataFrame, price_q: pd.DataFrame): _t = trades.merge( price_q[['ticker', 'qdate', 'p']], left_on=['ticker', 'rdate'], right_on=['ticker', 'qdate'], how='inner' ) _t['trade_vnd'] = _t['trade'] * _t['p'] /1e9 inst = _t[_t['owner_type'].isin(OwnershipType.INSTITUTIONAL)] net = ( inst.groupby( [pd.Grouper(key='rdate', freq='QE'), 'owner_type'] )['trade_vnd'].sum().unstack(fill_value=0) ) cum = net.cumsum() fig, ax = plt.subplots(figsize=(12, 6))for col in cum.columns: ax.plot(cum.index, cum[col], label=col, color=OWNER_COLORS.get(col, '#333'), linewidth=2) ax.axhline(y=0, color='black', linewidth=0.5) ax.set_title('Cumulative Net Institutional Buying', fontweight='bold') ax.set_ylabel('Billions VND') ax.legend(loc='best') plt.tight_layout() plt.show()```# Chất lượng dữ liệu và độ vững {#sec-institutionalrobustness}## Các sai lầm thường gặp {#sec-institutionalpitfalls}### Điều chỉnh sai sự kiện doanh nghiệp::: callout-caution## Ví dụ: Giao dịch ảo từ cổ tức bằng cổ phiếu chưa điều chỉnhVinamilk (VNM) phát hành cổ tức bằng cổ phiếu 20% với ngày giao dịch không hưởng quyền là 15 tháng 3 năm 2023.- Quý 4 2022: Quỹ X nắm giữ 1.000.000 cổ phiếu VNM- Quý 1 2023: Quỹ X nắm giữ 1.200.000 cổ phiếu VNM**Không điều chỉnh:** Suy ra giao dịch mua +200.000 cổ phiếu (BS = +2) **Có điều chỉnh:** Số cổ phiếu nắm giữ kỳ trước trở thành 1.200.000 cổ phiếu đã điều chỉnh, giao dịch = 0Giao dịch ảo này làm tăng tỷ lệ vòng quay đo được và tạo ra các tín hiệu mua giả.:::### Lệch thời điểm công bốNgày công bố sở hữu của Việt Nam có thể không trùng với ngày cuối quý dương lịch. Quy trình của chúng ta xử lý điều này bằng cách căn chỉnh mọi công bố về ngày cuối quý gần nhất.### Đổi tên và sáp nhập pháp nhânCác tổ chức Việt Nam thường xuyên đổi tên. Nếu không có mã định danh ổn định, cùng một pháp nhân có thể xuất hiện như hai cổ đông khác nhau, tạo ra các lần gia nhập/rút lui ảo. Chúng tôi khuyến nghị duy trì một bảng ánh xạ pháp nhân chủ.## Các kiểm tra xác thực {#sec-institutionalvalidation}```{python}#| label: validation#| code-summary: "Data Quality Validation Suite"def validate_pipeline_outputs( results: Dict[str, pd.DataFrame],) -> pd.DataFrame:"""Run comprehensive validation on pipeline outputs.""" checks = [] h = results['holdings'] t = results['trades'] a = results['aggregates'] checks.append({'Check': 'No negative adjusted shares','Result': 'PASS'if (h['shares_adj'] <0).sum() ==0else'FAIL','Detail': f'{(h["shares_adj"] <0).sum()} negative obs' }) checks.append({'Check': 'No duplicate holdings','Result': 'PASS'if h.duplicated( subset=['shareholder_name', 'ticker', 'rdate'] ).sum() ==0else'FAIL', }) checks.append({'Check': 'Valid buysale codes only','Result': 'PASS'if t['buysale'].isin([1, 2, -1, -2]).all()else'FAIL', }) checks.append({'Check': 'No zero trades','Result': 'PASS'if (t['trade'] ==0).sum() ==0else'FAIL', }) t1 = a['turnover1'].dropna() checks.append({'Check': 'Turnover1 in [0, 10]','Result': 'PASS'if ((t1 <0) | (t1 >10)).sum() ==0else'WARNING','Detail': f'{((t1<0)|(t1>10)).sum()} extreme values' }) first_rpt = a[a['first_report']] checks.append({'Check': 'First report -> missing netflows','Result': 'PASS'if first_rpt['netflows'].isna().all()else'FAIL', })return pd.DataFrame(checks)# validate_pipeline_outputs(results)```# Tóm tắt {#sec-institutionalsummary}Chương này phát triển một khung để tính các giao dịch, dòng vốn và tỷ lệ vòng quay của tổ chức trên thị trường cổ phiếu Việt Nam. Các đóng góp chính gồm:1. **Điều chỉnh sự kiện doanh nghiệp** cho cổ tức bằng cổ phiếu và cổ phiếu thưởng diễn ra thường xuyên tại Việt Nam, ngăn chặn các giao dịch ảo làm nhiễu phép lấy sai phân chuẩn.2. **Hệ phân loại sở hữu bốn nhóm** (nhà nước, tổ chức nước ngoài, tổ chức trong nước, cá nhân) nắm bắt bức tranh sở hữu độc đáo của Việt Nam.3. **Phân tích mức sử dụng FOL** để nghiên cứu các ràng buộc sở hữu nước ngoài không có ở các thị trường phát triển.4. **Xử lý công bố không đều** với việc tách khoảng trống đúng cách thành các giao dịch bán chấm dứt và mua khởi tạo.5. **Các mở rộng nâng cao** gồm hành vi bầy đàn, tính bền vững của nhu cầu và phân rã hàm lượng thông tin.Quy trình tạo ra nhiều bộ dữ liệu đầu ra (@tbl-institutional-outputs)| Đầu ra | Đơn vị quan sát | Biến chính | Trường hợp sử dụng ||:-----------------|:-----------------|:------------------|:-----------------||`holdings`| Cổ đông x Mã x Quý |`shares_adj`, `owner_type`| Sở hữu theo lát cắt ngang ||`io_ratios`| Mã x Quý |`io_state`, `io_foreign`, etc. | Quản trị công ty, thanh khoản ||`trades`| Cổ đông x Mã x Quý |`trade`, `buysale`| Giao dịch có thông tin, hành vi bầy đàn ||`aggregates`| Cổ đông x Quý |`assets`, `turnover`, `netflows`| Hiệu quả hoạt động của quỹ, dòng vốn ||`fol_analytics`| Mã x Ngày |`fol_utilization`, `foreign_room`| Phần bù FOL, đầu tư nước ngoài |: Tóm tắt các bộ dữ liệu đầu ra của quy trình {#tbl-institutional-outputs}<!-- # Bài tập {#sec-institutionalexercises}1. **Độ nhạy với sự kiện doanh nghiệp.** Tải dữ liệu sự kiện doanh nghiệp của VNM từ DataCore.vn và tính hệ số điều chỉnh cổ phiếu tích lũy từ 2010 đến 2024. Sẽ có bao nhiêu giao dịch ảo được tạo ra nếu không điều chỉnh? Vẽ biểu đồ hệ số này.2. **FOL ràng buộc và lợi nhuận.** Xây dựng một danh mục mua–bán khống: mua các cổ phiếu bị FOL ràng buộc ($\text{FOL\_Util} > 0.95$), bán khống các cổ phiếu không bị ràng buộc ($< 0.50$). Chênh lệch lợi nhuận hằng quý trung bình là bao nhiêu? Nó có ý nghĩa thống kê không?3. **Bất đối xứng của hành vi bầy đàn.** Tính hành vi bầy đàn LSV riêng cho cổ phiếu vốn hóa lớn (top 50 HOSE) và vốn hóa nhỏ. Hành vi bầy đàn có mạnh hơn ở các cổ phiếu vốn hóa nhỏ không? So sánh hành vi bầy đàn khi mua và khi bán.4. **Các sự kiện thoái vốn nhà nước.** Xác định các quý mà SCIC thực hiện các đợt thoái vốn lớn. Độ rộng và sở hữu nước ngoài thay đổi như thế nào sau thoái vốn?5. **Danh mục theo ngũ phân vị tỷ lệ vòng quay.** Sắp xếp các nhà đầu tư theo tỷ lệ vòng quay Carhart trễ một kỳ thành các ngũ phân vị. So sánh lợi nhuận: mối quan hệ giữa tỷ lệ vòng quay và hiệu quả hoạt động ở Việt Nam là dương (thông tin) hay âm (chi phí)?6. **Bài tập tái lập.** Tái lập @chen2000value: thay đổi độ rộng có dự báo lợi nhuận tương lai trên thị trường Việt Nam không? -->