Thị trường tài chính là những mạng lưới. Lợi nhuận của mỗi cổ phiếu đều chịu ảnh hưởng từ các mối liên kết: các doanh nghiệp có chung chuỗi cung ứng, thành viên hội đồng quản trị, kiểm toán viên, nhà đầu tư, bên cho vay và cơ quan quản lý. Các cú sốc lan truyền qua những liên kết này (chẳng hạn, một vụ phá sản gây hiệu ứng lan tỏa trong mạng lưới nhà cung cấp, một cú siết thanh khoản của ngân hàng trung ương lan ra qua các khoản dư nợ liên ngân hàng, còn thông tin khuếch tán qua các thành viên hội đồng quản trị kiêm nhiệm và qua việc các tổ chức cùng nắm giữ cổ phiếu). Tuy nhiên, mô thức thực nghiệm chiếm ưu thế trong tài chính lại coi các doanh nghiệp là những quan sát độc lập được lập chỉ số theo \((i, t)\), chỉ liên hệ với nhau thông qua mức độ phơi nhiễm chung với các nhân tố chung. Chương này giới thiệu các công cụ để mô hình hóa, đo lường và khai thác cấu trúc mạng lưới mà các hồi quy dữ liệu bảng (panel) tiêu chuẩn bỏ qua.
Hệ thống tài chính Việt Nam có mật độ mạng lưới đặc biệt cao. Sở hữu nhà nước tạo ra một mạng lưới các doanh nghiệp đan xen: cùng một bộ chủ quản có thể giám sát cả bên đi vay, bên cho vay và bên bảo hiểm. Các tập đoàn kim tự tháp (như Vingroup, Masan và FPT) liên kết hàng chục thực thể niêm yết và chưa niêm yết thông qua các chuỗi sở hữu vốn cổ phần. Hệ thống ngân hàng có quy mô nhỏ và tập trung, với một số ít ngân hàng thương mại nhà nước nắm giữ phần lớn tài sản, từ đó hình thành các khoản dư nợ liên ngân hàng dày đặc. Việc thành viên hội đồng quản trị kiêm nhiệm (chẳng hạn, những thành viên đồng thời tham gia nhiều hội đồng quản trị) rất phổ biến và thường đi theo các quan hệ sở hữu. Bản thân thị trường cổ phiếu cũng thể hiện các mẫu hình đồng biến động lợi nhuận mà khi được biểu diễn dưới dạng mạng lưới tương quan, sẽ bộc lộ các cụm theo ngành và theo hình thức sở hữu mà phân tích nhân tố tiêu chuẩn không nhìn thấy.
Chương này trình bày toàn bộ các phương pháp mạng lưới được sử dụng trong kinh tế học tài chính, từ lý thuyết đồ thị cổ điển đến các mạng nơ-ron đồ thị (GNN) hiện đại. Chúng ta tổ chức nội dung thành tám phần. Thứ nhất, các nền tảng của lý thuyết đồ thị và cách biểu diễn dữ liệu tài chính dưới dạng mạng lưới. Thứ hai, các mạng lưới sở hữu và kiểm soát, là cấu trúc mạng lưới đặc trưng nhất của thị trường Việt Nam. Thứ ba, các mạng lưới thành viên hội đồng quản trị kiêm nhiệm và quản trị công ty. Thứ tư, các mạng lưới chuỗi cung ứng và thương mại. Thứ năm, các mạng lưới tương quan và đồng biến động phục vụ xây dựng danh mục đầu tư và giám sát rủi ro hệ thống. Thứ sáu, các mạng lưới liên ngân hàng và lây lan. Thứ bảy, các mạng lưới xã hội giữa các địa phương, được đo bằng Chỉ số Kết nối Xã hội (Social Connectedness Index), và vai trò của chúng như kênh truyền dẫn thông tin tài chính (L. Allen, Peng, và Shan 2026). Thứ tám, các mạng nơ-ron đồ thị và học máy dựa trên đồ thị cho định giá tài sản, rủi ro tín dụng và phát hiện bất thường.
Hiện mã
import pandas as pdimport numpy as npfrom pathlib import Pathimport warningswarnings.filterwarnings("ignore")# Graph librariesimport networkx as nxfrom scipy import sparsefrom scipy.spatial.distance import squareform# Statistical and econometricfrom scipy import statsimport statsmodels.api as smfrom linearmodels.panel import PanelOLS# Visualizationimport plotnine as p9from mizani.formatters import percent_format, comma_formatimport matplotlib.pyplot as pltimport matplotlib.patches as mpatches# Deep learning (for GNNs later)import torchimport torch.nn as nnimport torch.nn.functional as F
47.1.1 Biểu diễn dữ liệu tài chính dưới dạng đồ thị
Một đồ thị \(G = (V, E)\) gồm một tập đỉnh (nút) \(V\) và một tập cạnh (liên kết) \(E \subseteq V \times V\). Trong các mạng lưới tài chính, các nút đại diện cho các tác nhân kinh tế, như doanh nghiệp, ngân hàng, nhà đầu tư, thành viên hội đồng quản trị, còn các cạnh đại diện cho các mối quan hệ, bao gồm tỷ lệ sở hữu, cho vay, ghế trong hội đồng quản trị, hợp đồng cung ứng và đồng biến động lợi nhuận.
Các đồ thị tài chính có nhiều dạng khác nhau:
Có hướng so với vô hướng. Quan hệ sở hữu là có hướng: doanh nghiệp \(A\) sở hữu một tỷ lệ cổ phần trong doanh nghiệp \(B\), nhưng không nhất thiết có chiều ngược lại. Quan hệ kiêm nhiệm hội đồng quản trị là vô hướng: nếu thành viên \(d\) ngồi trong hội đồng quản trị của cả doanh nghiệp \(A\) và doanh nghiệp \(B\), thì mối liên kết là đối xứng. Chuỗi cung ứng là có hướng: \(A\) cung cấp cho \(B\).
Có trọng số so với không trọng số. Các mạng lưới sở hữu tự nhiên có trọng số theo tỷ lệ sở hữu. Các mạng lưới tương quan có trọng số theo hệ số tương quan từng cặp. Quan hệ kiêm nhiệm hội đồng quản trị có thể được gán trọng số theo số thành viên dùng chung.
Tĩnh so với động. Hầu hết các mạng lưới tài chính tiến triển theo thời gian khi quan hệ sở hữu thay đổi, các thành viên hội đồng quản trị luân chuyển và các tương quan dịch chuyển. Một đồ thị theo thời gian \(G_t = (V_t, E_t)\) nắm bắt sự tiến triển này.
Hai phía so với một phía. Dữ liệu thô của nhiều mạng lưới tài chính là đồ thị hai phía (bipartite): thành viên hội đồng quản trị \(\times\) doanh nghiệp, nhà đầu tư \(\times\) cổ phiếu, ngân hàng \(\times\) bên đi vay. Phép chiếu một phía (one-mode projection) chuyển đồ thị này thành đồ thị một phía: hai doanh nghiệp được nối với nhau nếu có chung một thành viên hội đồng quản trị, hai cổ phiếu được nối với nhau nếu có chung một nhà đầu tư tổ chức, v.v.
47.1.2 Các thước đo chính của đồ thị
Với một đồ thị \(G\) có \(n = |V|\) nút và \(m = |E|\) cạnh, chúng ta định nghĩa các thước đo sau, mỗi thước đo có một cách diễn giải tài chính riêng.
Độ trung tâm bậc (degree centrality). Bậc \(k_i\) của nút \(i\) là số cạnh liên thuộc với \(i\). Trong đồ thị có hướng, chúng ta phân biệt bậc vào \(k_i^{\text{in}}\) (các cạnh hướng tới \(i\)) và bậc ra \(k_i^{\text{out}}\) (các cạnh xuất phát từ \(i\)). Độ trung tâm bậc chuẩn hóa là:
\[
C_D(i) = \frac{k_i}{n - 1}
\tag{47.1}\]
Trong một mạng lưới sở hữu, bậc ra cao có nghĩa là doanh nghiệp sở hữu cổ phần trong nhiều doanh nghiệp khác (tập đoàn đa ngành); bậc vào cao có nghĩa là nhiều thực thể sở hữu cổ phần trong doanh nghiệp đó (sở hữu phân tán).
Độ trung tâm trung gian (betweenness centrality). Tỷ lệ các đường đi ngắn nhất giữa mọi cặp nút đi qua nút \(i\):
\[
C_B(i) = \sum_{s \neq i \neq t} \frac{\sigma_{st}(i)}{\sigma_{st}}
\tag{47.2}\]
trong đó \(\sigma_{st}\) là tổng số đường đi ngắn nhất từ \(s\) đến \(t\) và \(\sigma_{st}(i)\) là số đường đi đi qua \(i\). Độ trung gian cao xác định các nút “cầu nối” (tức là các doanh nghiệp hoặc ngân hàng nối những bộ phận vốn tách rời của hệ thống tài chính). Sự sụp đổ của một ngân hàng có độ trung gian cao có thể làm phân mảnh mạng lưới liên ngân hàng.
Độ trung tâm vector riêng (eigenvector centrality). Một nút là trung tâm nếu nó được nối với các nút trung tâm khác. Độ trung tâm vector riêng là nghiệm của:
\[
\lambda \mathbf{c} = A \mathbf{c}
\tag{47.3}\]
trong đó \(A\) là ma trận kề và \(\lambda\) là trị riêng lớn nhất. PageRank của Google là một biến thể được chính quy hóa, thiết kế cho đồ thị có hướng. Trong các mạng lưới tài chính, độ trung tâm vector riêng xác định các tổ chức quan trọng về mặt hệ thống (tức là những tổ chức được nối với các tổ chức quan trọng khác).
Hệ số phân cụm (clustering coefficient). Tỷ lệ các nút lân cận của một nút mà bản thân chúng cũng là lân cận của nhau:
trong đó \(T_i\) là số tam giác chứa nút \(i\). Mức phân cụm cao cho thấy các nhóm gắn kết chặt chẽ (chẳng hạn, các tập đoàn, các vòng cho vay, các danh mục cùng đầu tư).
Cấu trúc cộng đồng. Nhiều mạng lưới tài chính có cấu trúc mô-đun: các nhóm nút kết nối dày đặc với nhau nhưng liên kết thưa giữa các nhóm. Các thuật toán phát hiện cộng đồng (Louvain, lan truyền nhãn, phân cụm phổ) xác định các mô-đun này, mà trong các mạng lưới tài chính thường tương ứng với các tập đoàn, các ngành hoặc các cụm cho vay.
Hiện mã
def compute_network_metrics(G):""" Compute standard network metrics for a graph. Parameters ---------- G : nx.Graph or nx.DiGraph Input graph. Returns ------- dict : Node-level and graph-level metrics. """ is_directed = G.is_directed()# Node-level metricsif is_directed: in_degree =dict(G.in_degree()) out_degree =dict(G.out_degree()) degree = {n: in_degree[n] + out_degree[n] for n in G.nodes()}else: degree =dict(G.degree()) betweenness = nx.betweenness_centrality(G, weight="weight") eigenvector = nx.eigenvector_centrality_numpy( G, weight="weight" ) iflen(G) >0else {} clustering = nx.clustering(G, weight="weight") ifnot is_directed else {}# PageRank (works for both directed and undirected) pagerank = nx.pagerank(G, weight="weight")# Graph-level metrics n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() density = nx.density(G)# Connected componentsif is_directed: n_weak_components = nx.number_weakly_connected_components(G) n_strong_components = nx.number_strongly_connected_components(G)else: n_components = nx.number_connected_components(G)# Degree distribution statistics degrees =list(degree.values()) avg_degree = np.mean(degrees) if degrees else0 max_degree =max(degrees) if degrees else0# Assortativity assortativity = nx.degree_assortativity_coefficient(G)# Community detection (Louvain)ifnot is_directed:try: communities = nx.community.louvain_communities(G) modularity = nx.community.modularity(G, communities) n_communities =len(communities)exceptException: modularity, n_communities = np.nan, 0else: modularity, n_communities = np.nan, 0 node_metrics = pd.DataFrame({"degree": degree,"betweenness": betweenness,"eigenvector": eigenvector,"pagerank": pagerank,"clustering": clustering if clustering else {n: np.nan for n in G.nodes()} }) graph_metrics = {"n_nodes": n_nodes,"n_edges": n_edges,"density": density,"avg_degree": avg_degree,"max_degree": max_degree,"assortativity": assortativity,"modularity": modularity,"n_communities": n_communities }return node_metrics, graph_metrics
47.1.3 Ma trận kề và các tính chất phổ
Ma trận kề \(A \in \mathbb{R}^{n \times n}\) mã hóa cấu trúc đồ thị: \(A_{ij} = w_{ij}\) nếu có một cạnh từ \(i\) đến \(j\) với trọng số \(w_{ij}\), và \(A_{ij} = 0\) trong trường hợp ngược lại. Với đồ thị vô hướng, \(A\) là ma trận đối xứng.
Ma trận Laplace của đồ thị \(L = D - A\) (trong đó \(D\) là ma trận bậc đường chéo) có các trị riêng \(0 = \lambda_1 \leq \lambda_2 \leq \ldots \leq \lambda_n\) với những cách diễn giải cấu trúc quan trọng:
Bội của \(\lambda = 0\) bằng số thành phần liên thông.
Trị riêng thứ hai \(\lambda_2\) (độ liên thông đại số hay giá trị Fiedler) đo mức độ liên kết tốt của đồ thị. \(\lambda_2\) thấp hàm ý đồ thị có một nút thắt cổ chai, tức là một điểm yếu mà việc cắt một vài cạnh sẽ làm đứt rời những phần lớn của đồ thị.
Các vector riêng của \(L\) cung cấp phép nhúng phổ (spectral embedding) của đồ thị, là nền tảng cho phân cụm phổ và các mạng tích chập đồ thị.
Ma trận Laplace chuẩn hóa \(\tilde{L} = D^{-1/2} L D^{-1/2}\) được dùng trong GCN vì nó ổn định việc truyền thông điệp giữa các nút có bậc khác nhau.
Hiện mã
def spectral_graph_analysis(A, n_components=10):""" Compute spectral properties of a financial network. Parameters ---------- A : np.ndarray or sparse matrix Adjacency matrix. n_components : int Number of eigenvalues/vectors to compute. Returns ------- dict : Eigenvalues, algebraic connectivity, spectral gap. """ n = A.shape[0]if sparse.issparse(A): A_dense = A.toarray()else: A_dense = A# Degree matrix D = np.diag(A_dense.sum(axis=1))# Graph Laplacian L = D - A_dense# Normalized Laplacian D_inv_sqrt = np.diag(1.0/ np.sqrt(np.diag(D) +1e-10)) L_norm = D_inv_sqrt @ L @ D_inv_sqrt# Eigendecomposition (smallest eigenvalues) eigenvalues, eigenvectors = np.linalg.eigh(L_norm)# Sort by eigenvalue idx = np.argsort(eigenvalues) eigenvalues = eigenvalues[idx] eigenvectors = eigenvectors[:, idx]# Algebraic connectivity (Fiedler value) fiedler_value = eigenvalues[1] if n >1else0 fiedler_vector = eigenvectors[:, 1] if n >1else np.zeros(n)# Spectral gap spectral_gap = eigenvalues[1] - eigenvalues[0] if n >1else0return {"eigenvalues": eigenvalues[:n_components],"fiedler_value": fiedler_value,"fiedler_vector": fiedler_vector,"spectral_gap": spectral_gap,"spectral_embedding": eigenvectors[:, 1:n_components +1] }
47.2 Mạng lưới sở hữu và kiểm soát
47.2.1 Cấu trúc sở hữu ở Việt Nam
Có thể nói các mạng lưới sở hữu là cấu trúc đồ thị có hệ quả kinh tế lớn nhất trong thị trường Việt Nam. Bức tranh doanh nghiệp Việt Nam được đặc trưng bởi ba nét riêng tạo ra các cấu trúc sở hữu phức tạp:
State ownership pyramids. The government holds equity in hundreds of firms through a hierarchy of holding entities: the State Capital Investment Corporation (SCIC), line ministries, provincial People’s Committees, and state-owned economic groups (tập đoàn kinh tế nhà nước). These chains create multi-layered pyramids where the state’s ultimate control rights may substantially exceed its cash flow rights.
Private business groups. Vietnamese conglomerates (Vingroup, Masan, FPT, Hoà Phát, Thaco) create complex webs of cross-ownership, subsidiary relationships, and associate stakes. These structures serve multiple purposes: internal capital markets, tax optimization, regulatory arbitrage, and control enhancement.
Sở hữu vòng tròn và sở hữu chéo. Các quy định của Việt Nam không ngăn chặn hiệu quả tình trạng sở hữu vòng tròn (doanh nghiệp \(A\) sở hữu doanh nghiệp \(B\), doanh nghiệp này sở hữu doanh nghiệp \(C\), và doanh nghiệp sau cùng này lại sở hữu doanh nghiệp \(A\)), tạo ra các vòng lặp trong đồ thị sở hữu làm khuếch đại quyền kiểm soát vượt quá tỷ lệ sở hữu trực tiếp và làm phình vốn chủ sở hữu kế toán (Bebchuk 1999).
Sở hữu trực tiếp đánh giá thấp quyền kiểm soát thực sự được thực thi qua các chuỗi kim tự tháp. Tỷ lệ sở hữu cuối cùng của thực thể \(A\) trong doanh nghiệp \(Z\) qua chuỗi \(A \to B \to C \to Z\) là tích của các tỷ lệ sở hữu trung gian:
trong đó \(w_{ij}\) là tỷ lệ sở hữu trực tiếp của \(i\) trong \(j\). Khi tồn tại nhiều đường đi từ \(A\) đến \(Z\), tổng sở hữu cuối cùng là tổng trên các đường đi. Tuy nhiên, quyền kiểm soát được quyết định bởi mắt xích yếu nhất trong chuỗi (tỷ lệ sở hữu nhỏ nhất dọc theo đường đi), phản ánh nguyên tắc rằng kiểm soát đòi hỏi đa số ở mỗi tầng.
Hiện mã
def compute_ultimate_ownership(G, source, target, max_depth=10):""" Compute ultimate ownership of source in target through all paths. Parameters ---------- G : nx.DiGraph Ownership graph with edge weights as ownership fractions. source : str Ultimate owner node. target : str Target firm node. max_depth : int Maximum chain length to consider. Returns ------- dict : Total ownership, control rights, number of paths. """if source notin G or target notin G:return {"ownership": 0, "control": 0, "n_paths": 0} total_ownership =0 max_control =0 n_paths =0# Find all simple paths from source to targettry:for path in nx.all_simple_paths(G, source, target, cutoff=max_depth):# Cash flow rights: product of stakes ownership =1.0 min_stake =1.0for i inrange(len(path) -1): edge_data = G[path[i]][path[i +1]] stake = edge_data.get("weight", 0) ownership *= stake min_stake =min(min_stake, stake) total_ownership += ownership max_control =max(max_control, min_stake) n_paths +=1except nx.NetworkXNoPath:passreturn {"ownership": total_ownership,"control": max_control,"n_paths": n_paths }def compute_control_wedge(G, firms_list, max_depth=5):""" Compute the wedge between control and cash flow rights for the largest shareholder of each firm. The wedge = control rights - cash flow rights. Positive wedge → potential for tunneling. """ wedge_data = []for firm in firms_list:if firm notin G:continue# Find all direct owners predecessors =list(G.predecessors(firm))ifnot predecessors:continue# Find largest direct owner stakes = {p: G[p][firm]["weight"] for p in predecessors} largest_owner =max(stakes, key=stakes.get) direct_stake = stakes[largest_owner]# Compute ultimate ownership through all paths ultimate = compute_ultimate_ownership( G, largest_owner, firm, max_depth ) wedge_data.append({"ticker": firm,"largest_owner": largest_owner,"direct_stake": direct_stake,"ultimate_ownership": ultimate["ownership"],"control_rights": ultimate["control"],"n_ownership_paths": ultimate["n_paths"],"wedge": ultimate["control"] - ultimate["ownership"] })return pd.DataFrame(wedge_data)# Compute for all listed firmslisted_firms = [n for n, d in G_own.nodes(data=True)if d.get("node_type") =="firm"]wedge_df = compute_control_wedge(G_own, listed_firms)
Bảng 47.2: Khoảng cách giữa quyền kiểm soát và quyền dòng tiền: Thống kê tóm tắt
47.2.3 Độ trung tâm sở hữu và giá trị doanh nghiệp
Chúng ta kiểm định xem vị trí của một doanh nghiệp trong mạng lưới sở hữu có dự báo được định giá thị trường của doanh nghiệp đó hay không, sau khi kiểm soát các yếu tố quyết định tiêu chuẩn:
Các tập đoàn (tức là tập hợp các doanh nghiệp độc lập về mặt pháp lý nhưng liên kết với nhau bằng quan hệ sở hữu chung) là một đặc trưng nổi bật của cấu trúc doanh nghiệp Việt Nam. Chúng ta phát hiện chúng bằng thuật toán, sử dụng phương pháp phát hiện cộng đồng trên đồ thị sở hữu.
Hiện mã
def detect_business_groups(G, min_group_size=3, ownership_threshold=0.10):""" Detect business groups from ownership network. A business group is a connected component in the undirected projection of the ownership graph, filtered for economically meaningful ownership stakes. Parameters ---------- G : nx.DiGraph Directed ownership graph. min_group_size : int Minimum firms in a group. ownership_threshold : float Minimum ownership stake to count as a link. Returns ------- DataFrame : Group assignments with apex firm. """# Filter edges by threshold G_filtered = nx.DiGraph()for u, v, d in G.edges(data=True):if d.get("weight", 0) >= ownership_threshold: G_filtered.add_edge(u, v, **d)# Convert to undirected for component detection G_undirected = G_filtered.to_undirected()# Find connected components components =list(nx.connected_components(G_undirected))# Filter by size groups = [c for c in components iflen(c) >= min_group_size]# For each group, identify the apex (top of pyramid) group_data = []for group_id, members inenumerate(groups): subgraph = G_filtered.subgraph(members)# Apex: node with highest out-degree and lowest in-degree# (controls others but is not controlled) scores = {}for node in members: in_deg = subgraph.in_degree(node) out_deg = subgraph.out_degree(node) scores[node] = out_deg - in_deg apex =max(scores, key=scores.get) if scores elselist(members)[0]for member in members: node_data = G.nodes.get(member, {}) group_data.append({"ticker": member,"group_id": group_id,"group_size": len(members),"apex": apex,"is_apex": member == apex,"node_type": node_data.get("node_type", "unknown"),"industry": node_data.get("industry", "Unknown"),"market_cap": node_data.get("market_cap", 0) })return pd.DataFrame(group_data)groups_df = detect_business_groups(G_own)print(f"Business groups detected: {groups_df['group_id'].nunique()}")print(f"Firms in groups: {len(groups_df[groups_df['node_type'] =='firm'])}")
Bảng 47.3: Các tập đoàn lớn nhất của Việt Nam theo phân tích mạng lưới sở hữu
47.3 Mạng lưới thành viên hội đồng quản trị kiêm nhiệm
47.3.1 Xây dựng mạng lưới
Một quan hệ kiêm nhiệm hội đồng quản trị tồn tại khi một thành viên đồng thời giữ ghế trong hội đồng quản trị của hai hay nhiều doanh nghiệp. Mạng lưới kiêm nhiệm hội đồng quản trị là phép chiếu một phía của đồ thị hai phía thành viên\(\times\)doanh nghiệp: hai doanh nghiệp được nối với nhau nếu có chung ít nhất một thành viên, với trọng số là số thành viên dùng chung.
Quan hệ kiêm nhiệm là một kênh thông tin. Bizjak, Lemmon, và Naveen (2008) chỉ ra rằng các thông lệ trả thưởng lan truyền qua mạng lưới hội đồng quản trị. Cai và c.s. (2014) chứng minh rằng các doanh nghiệp được nối với nhau qua quan hệ kiêm nhiệm có chính sách đầu tư tương quan với nhau. Trong thị trường Việt Nam, quan hệ kiêm nhiệm thường đi theo các quan hệ sở hữu (chẳng hạn, công ty mẹ bổ nhiệm thành viên vào hội đồng quản trị của công ty con), tạo ra một kênh quản trị củng cố kênh sở hữu.
Hiện mã
# Load board membership databoard_data = dc.get_board_memberships( date="2024-06-30")print(f"Director-firm pairs: {len(board_data)}")print(f"Unique directors: {board_data['director_id'].nunique()}")print(f"Unique firms: {board_data['ticker'].nunique()}")# Build bipartite graphB = nx.Graph()for _, row in board_data.iterrows(): B.add_node(row["director_id"], bipartite=0) B.add_node(row["ticker"], bipartite=1) B.add_edge( row["director_id"], row["ticker"], role=row.get("role", "director"), is_independent=row.get("is_independent", False) )# Project to firm-firm interlock networkfirm_nodes = [n for n, d in B.nodes(data=True) if d.get("bipartite") ==1]G_interlock = nx.bipartite.weighted_projected_graph(B, firm_nodes)# Edge weight = number of shared directorsinterlock_metrics, interlock_graph = compute_network_metrics(G_interlock)print(f"\nBoard Interlock Network:")print(f" Firms: {G_interlock.number_of_nodes()}")print(f" Interlocking pairs: {G_interlock.number_of_edges()}")print(f" Density: {nx.density(G_interlock):.4f}")
Hiện mã
# Do board interlocks follow ownership lines?# Compare interlock edges with ownership edgesinterlock_edges =set(G_interlock.edges())ownership_edges_undirected =set()for u, v in G_own.edges():if u in firm_nodes and v in firm_nodes: ownership_edges_undirected.add((min(u, v), max(u, v)))interlock_edges_normalized = { (min(u, v), max(u, v)) for u, v in interlock_edges}overlap = interlock_edges_normalized & ownership_edges_undirectedn_interlock =len(interlock_edges_normalized)n_ownership =len(ownership_edges_undirected)n_overlap =len(overlap)print(f"Interlock edges: {n_interlock}")print(f"Ownership edges (firm-firm): {n_ownership}")print(f"Overlap: {n_overlap}")if n_interlock >0:print(f"Fraction of interlocks with ownership link: "f"{n_overlap / n_interlock:.3f}")
47.3.2 Kiêm nhiệm và đồng biến động lợi nhuận
Nếu quan hệ kiêm nhiệm đóng vai trò kênh thông tin, các doanh nghiệp được nối với nhau qua quan hệ kiêm nhiệm sẽ thể hiện mức đồng biến động lợi nhuận vượt mức, vượt quá phần được giải thích bởi đặc điểm ngành hoặc quy mô chung. Chúng ta kiểm định điều này bằng phương pháp của Cohen và Frazzini (2008):
Các quan hệ chuỗi cung ứng (liên kết khách hàng–nhà cung cấp) thể hiện những mối liên hệ kinh tế thực mà qua đó các cú sốc lan truyền. Acemoglu, Ozdaglar, và Tahbaz-Salehi (2015) chứng minh về mặt lý thuyết rằng khi tồn tại các liên kết chuỗi cung ứng, các cú sốc đặc thù của từng doanh nghiệp có thể tạo ra biến động tổng thể thay vì triệt tiêu lẫn nhau. Cohen và Frazzini (2008) và Menzly và Ozbas (2010) cho thấy các liên kết chuỗi cung ứng dự báo được chênh lệch lợi nhuận chéo (cross-sectional): khi cổ phiếu của một khách hàng lớn giảm giá, cổ phiếu của các nhà cung cấp giảm theo với độ trễ.
Chiến lược “động lượng khách hàng” của Cohen và Frazzini (2008) khai thác sự lan truyền chậm của thông tin qua các chuỗi cung ứng. Khi cổ phiếu của một doanh nghiệp khách hàng tăng giá, cổ phiếu của các nhà cung cấp có xu hướng tăng theo trong những tháng tiếp theo:
trong đó \(\text{CustomerReturn}_{i,t} = \sum_{j \in \text{Customers}(i)} w_{ij} \cdot r_{j,t}\) là lợi nhuận bình quân gia quyền của các khách hàng của doanh nghiệp \(i\).
Hiện mã
# Compute customer-weighted returns for each supplierdef compute_customer_returns(supply_graph, monthly_returns):""" Compute customer-weighted returns for each supplier firm. """# For each supplier, compute weighted average customer return results = []for date in monthly_returns["date"].unique(): date_returns = monthly_returns[monthly_returns["date"] == date] ret_dict =dict(zip(date_returns["ticker"], date_returns["ret"]))for supplier in supply_graph.nodes(): customers =list(supply_graph.successors(supplier))ifnot customers:continue customer_rets = [] customer_weights = []for cust in customers:if cust in ret_dict: edge_data = supply_graph[supplier][cust] weight = edge_data.get("pct_of_supplier_revenue", 1) customer_rets.append(ret_dict[cust]) customer_weights.append(weight)if customer_rets: weights = np.array(customer_weights)if weights.sum() >0: weights = weights / weights.sum()else: weights = np.ones(len(weights)) /len(weights) weighted_ret = np.average(customer_rets, weights=weights) results.append({"date": date,"ticker": supplier,"customer_ret": weighted_ret,"n_customers": len(customer_rets) })return pd.DataFrame(results)customer_rets = compute_customer_returns(G_supply, monthly_returns)# Merge with own returns and lagmomentum_data = monthly_returns.merge( customer_rets, on=["ticker", "date"], how="inner")momentum_data = momentum_data.sort_values(["ticker", "date"])momentum_data["own_ret_lag"] = momentum_data.groupby("ticker")["ret"].shift(1)momentum_data["customer_ret_lag"] = momentum_data.groupby("ticker")["customer_ret"].shift(1)momentum_data["ret_lead"] = momentum_data.groupby("ticker")["ret"].shift(-1)# Regressionmom_clean = momentum_data.dropna( subset=["ret_lead", "customer_ret_lag", "own_ret_lag"])model_mom = sm.OLS( mom_clean["ret_lead"], sm.add_constant(mom_clean[["customer_ret_lag", "own_ret_lag"]])).fit(cov_type="cluster", cov_kwds={"groups": mom_clean["ticker"]})
Bảng 47.4: Động lượng khách hàng: Lợi nhuận của nhà cung cấp được dự báo bởi lợi nhuận của khách hàng
47.4.3 Lan truyền trong mạng lưới: Khuếch tán cú sốc qua các chuỗi cung ứng
Mô hình của Acemoglu, Ozdaglar, và Tahbaz-Salehi (2015) dự báo rằng tác động tổng thể của một cú sốc đặc thù phụ thuộc vào cấu trúc của mạng lưới. Trong mạng hình sao (một trung tâm duy nhất), các cú sốc tại trung tâm tạo ra biến động tổng thể. Trong mạng đối xứng, các cú sốc triệt tiêu lẫn nhau. Chúng ta triển khai một phương pháp dựa trên mô phỏng để đo lường sự lan truyền cú sốc trong chuỗi cung ứng của Việt Nam.
Hiện mã
def simulate_shock_propagation(G, shocked_node, shock_size=-0.10, decay=0.5, max_steps=5):""" Simulate the propagation of an idiosyncratic shock through a supply chain network. Parameters ---------- G : nx.DiGraph Supply chain graph (supplier → customer). shocked_node : str Node receiving the initial shock. shock_size : float Initial shock magnitude (e.g., -0.10 = -10% revenue shock). decay : float Fraction of shock transmitted per link (0 < decay < 1). max_steps : int Maximum propagation steps. Returns ------- dict : {node: cumulative shock received}. """ shocks = {shocked_node: shock_size} frontier = {shocked_node}for step inrange(max_steps): new_frontier =set()for node in frontier:# Propagate to customers (downstream)for customer in G.successors(node): edge_weight = G[node][customer].get("pct_of_customer_cogs", 0.1 ) transmitted = shocks[node] * decay * edge_weightifabs(transmitted) >0.001: shocks[customer] = shocks.get(customer, 0) + transmitted new_frontier.add(customer)# Propagate to suppliers (upstream)for supplier in G.predecessors(node): edge_weight = G[supplier][node].get("pct_of_supplier_revenue", 0.1 ) transmitted = shocks[node] * decay * edge_weightifabs(transmitted) >0.001: shocks[supplier] = shocks.get(supplier, 0) + transmitted new_frontier.add(supplier) frontier = new_frontierifnot frontier:breakreturn shocks# Identify systemically important supply chain nodes# (Those whose shock has largest aggregate impact)systemic_importance = {}listed_in_supply = [n for n in G_supply.nodes()if n inset(firms["ticker"])]for firm in listed_in_supply[:100]: # Sample for computational feasibility shocks = simulate_shock_propagation(G_supply, firm, -0.10) aggregate_impact =sum(abs(v) for k, v in shocks.items() if k != firm) systemic_importance[firm] = aggregate_impactsystemic_df = pd.DataFrame(list(systemic_importance.items()), columns=["ticker", "systemic_impact"]).sort_values("systemic_impact", ascending=False)
Bảng 47.5: Các doanh nghiệp quan trọng nhất về mặt hệ thống trong mạng lưới chuỗi cung ứng
Mạng lưới tương quan nối các tài sản có lợi nhuận đồng biến động. Có lẽ đây là mạng lưới tài chính được sử dụng rộng rãi nhất vì nó chỉ cần dữ liệu lợi nhuận—không cần thông tin sở hữu hay chuỗi cung ứng độc quyền. Cách xây dựng tiêu chuẩn gồm ba bước:
Tính các tương quan từng cặp. Với \(n\) tài sản trong \(T\) kỳ, ma trận tương quan mẫu \(\hat{\rho} \in \mathbb{R}^{n \times n}\) có \(n(n-1)/2\) phần tử riêng biệt.
Đặt ngưỡng hoặc biến đổi. Chuyển ma trận tương quan dày thành một đồ thị thưa. Các cách tiếp cận phổ biến: ngưỡng cứng (\(\rho_{ij} > \bar{\rho}\)), cây khung nhỏ nhất (Minimum Spanning Tree, MST), hoặc đồ thị lọc cực đại phẳng (Planar Maximally Filtered Graph, PMFG).
Phân tích đồ thị. Phát hiện cộng đồng cho thấy các cụm theo ngành; độ trung tâm xác định các cổ phiếu dẫn dắt thị trường; sự tiến triển theo thời gian theo dõi các thay đổi chế độ.
Diebold và Yılmaz (2014) đề xuất đo lường mức độ kết nối tài chính bằng phân rã phương sai từ mô hình tự hồi quy vector (VAR). Tỷ lệ phương sai sai số dự báo trước \(H\) bước của biến \(i\) do các cú sốc từ biến \(j\) gây ra xác định mức kết nối có hướng theo từng cặp:
trong đó \(\tilde{\theta}_{ij}^g(H)\) là phân rã phương sai sai số dự báo tổng quát. Chỉ số kết nối tổng (total connectedness index, TCI) tổng hợp trên mọi cặp:
TCI cao cho thấy một hệ thống kết nối chặt chẽ, nơi các cú sốc lan truyền rộng; TCI thấp cho thấy mức độ cô lập tương đối.
Hiện mã
def diebold_yilmaz_connectedness(returns_df, var_order=2, forecast_horizon=10):""" Compute the Diebold-Yilmaz (2014) connectedness table. Parameters ---------- returns_df : DataFrame Returns with columns as assets, index as dates. var_order : int VAR lag order. forecast_horizon : int Forecast horizon for variance decomposition. Returns ------- dict : Connectedness matrix, TCI, TO/FROM measures. """from statsmodels.tsa.api import VAR# Fit VAR model = VAR(returns_df.dropna()) results = model.fit(var_order)# Generalized Forecast Error Variance Decomposition fevd = results.fevd(forecast_horizon)# Extract decomposition matrix at horizon H n = returns_df.shape[1] C = np.zeros((n, n))for i inrange(n): decomp = fevd.decomp[i] # H x n array C[i, :] = decomp[-1, :] # Take horizon H values# Normalize rows to sum to 100 row_sums = C.sum(axis=1, keepdims=True) C_norm = C / row_sums *100# Connectedness measures# FROM: how much of i's variance comes from others FROM = C_norm.sum(axis=1) - np.diag(C_norm)# TO: how much of others' variance comes from i TO = C_norm.sum(axis=0) - np.diag(C_norm)# NET = TO - FROM NET = TO - FROM# Total Connectedness Index TCI = FROM.sum() / n names = returns_df.columns.tolist() connectedness_df = pd.DataFrame(C_norm, index=names, columns=names) connectedness_df["FROM_others"] = FROM connectedness_df.loc["TO_others"] =list(TO) + [TCI]return {"connectedness_matrix": connectedness_df,"TCI": TCI,"FROM": pd.Series(FROM, index=names),"TO": pd.Series(TO, index=names),"NET": pd.Series(NET, index=names) }# Apply to top Vietnamese stocks by liquiditytop_stocks = ( daily_returns.groupby("ticker")["volume"] .mean() .nlargest(20) .index)top_returns = daily_wide[ [c for c in top_stocks if c in daily_wide.columns]].dropna()dy_results = diebold_yilmaz_connectedness(top_returns)print(f"Total Connectedness Index: {dy_results['TCI']:.2f}%")
Bảng 47.6: Kết nối ròng Diebold–Yilmaz: Bên truyền dẫn ròng (+) và bên tiếp nhận ròng (−)
# Rolling TCI to track systemic risk over timedef rolling_tci(returns_wide, window=252, step=21, var_order=1, forecast_horizon=10, min_stocks=10):"""Compute rolling Total Connectedness Index.""" dates = returns_wide.index tci_series = []for i inrange(window, len(dates), step): window_data = returns_wide.iloc[i - window:i]# Keep stocks with sufficient data valid_cols = window_data.dropna(axis=1, thresh=int(window *0.9))if valid_cols.shape[1] < min_stocks:continue# Use top stocks by variance (most informative) top_cols = valid_cols.var().nlargest(min_stocks).index subset = valid_cols[top_cols].dropna()iflen(subset) < window *0.8:continuetry: result = diebold_yilmaz_connectedness( subset, var_order, forecast_horizon ) tci_series.append({"date": dates[i],"TCI": result["TCI"],"n_stocks": len(top_cols) })exceptException:continuereturn pd.DataFrame(tci_series)# tci_df = rolling_tci(daily_wide, window=252, step=21)
47.6 Mạng lưới liên ngân hàng và lây lan tài chính
47.6.1 Thị trường liên ngân hàng như một mạng lưới
Thị trường liên ngân hàng, nơi các ngân hàng cho nhau vay dự trữ, là mạng lưới tài chính điển hình. F. Allen và Gale (2000) chứng minh rằng cấu trúc các liên kết liên ngân hàng quyết định việc sự sụp đổ của một ngân hàng sẽ lan truyền thành khủng hoảng hệ thống hay được mạng lưới hấp thụ. Hai cấu trúc cực đoan minh họa điều này:
Mạng đầy đủ (mọi ngân hàng nối với mọi ngân hàng). Mức dư nợ của mỗi ngân hàng với bất kỳ một đối tác nào đều nhỏ. Sự sụp đổ của một ngân hàng gây ra tổn thất nhỏ cho nhiều ngân hàng, mà từng ngân hàng có thể tự hấp thụ. Mạng lưới có khả năng chống chịu.
Mạng vòng (mỗi ngân hàng nối với một ngân hàng lân cận). Mức dư nợ của mỗi ngân hàng tập trung vào một đối tác duy nhất. Sự sụp đổ của một ngân hàng có thể kéo đổ ngân hàng lân cận, kích hoạt một chuỗi vỡ nợ dây chuyền. Mạng lưới dễ đổ vỡ.
Hệ thống ngân hàng Việt Nam, với sự tập trung của các ngân hàng nhà nước và việc cơ quan quản lý nhấn mạnh cho vay liên ngân hàng để quản lý thanh khoản, nằm giữa hai thái cực này.
Chúng ta triển khai cơ chế bù trừ của Eisenberg và Noe (2001) để mô phỏng các vụ vỡ nợ dây chuyền trong mạng lưới liên ngân hàng. Khi một ngân hàng sụp đổ, ngân hàng đó không thể thực hiện các nghĩa vụ liên ngân hàng, gây tổn thất cho các chủ nợ của nó, và đến lượt các chủ nợ này có thể sụp đổ:
trong đó \(p_i^*\) là khoản thanh toán thực tế của ngân hàng \(i\), \(\bar{p}_i\) là tổng nghĩa vụ của ngân hàng đó, \(e_i\) là tài sản bên ngoài trừ nợ phải trả bên ngoài của nó, và \(L_{ji}/\sum_k L_{jk}\) là phần nghĩa vụ của ngân hàng \(j\) phải trả cho ngân hàng \(i\).
Hiện mã
def simulate_cascading_defaults(G, initial_failure, equity_buffer=0.08, max_rounds=20):""" Simulate cascading defaults in an interbank network. Parameters ---------- G : nx.DiGraph Interbank exposure graph (lender → borrower, weight = exposure). initial_failure : str Bank that fails initially. equity_buffer : float Fraction of equity that absorbs losses before default. max_rounds : int Maximum cascade rounds. Returns ------- dict : Defaulted banks, round-by-round losses, systemic loss. """ defaulted = {initial_failure} cascade_history = [{"round": 0, "defaults": [initial_failure],"total_defaults": 1}]for round_num inrange(1, max_rounds +1): new_defaults =set()for bank in G.nodes():if bank in defaulted:continue# Compute losses from defaulted counterparties total_loss =0for defaulted_bank in defaulted:if G.has_edge(bank, defaulted_bank):# bank lent to defaulted_bank → loss exposure = G[bank][defaulted_bank]["weight"]# Assume LGD = 60% loss = exposure *0.6 total_loss += loss# Check if losses exceed equity buffer bank_data = G.nodes.get(bank, {}) equity = bank_data.get("equity", float("inf"))if total_loss > equity * equity_buffer: new_defaults.add(bank)ifnot new_defaults:break defaulted |= new_defaults cascade_history.append({"round": round_num,"defaults": list(new_defaults),"total_defaults": len(defaulted) })# Compute systemic loss total_system_equity =sum( G.nodes[n].get("equity", 0) for n in G.nodes() ) defaulted_equity =sum( G.nodes[n].get("equity", 0) for n in defaulted ) systemic_loss_pct = ( defaulted_equity / total_system_equityif total_system_equity >0else0 )return {"defaulted_banks": list(defaulted),"n_defaults": len(defaulted),"cascade_rounds": len(cascade_history) -1,"cascade_history": cascade_history,"systemic_loss_pct": systemic_loss_pct }# Simulate cascade for each bankcascade_results = []for bank in G_bank.nodes(): result = simulate_cascading_defaults(G_bank, bank) cascade_results.append({"initial_failure": bank,"n_cascading_defaults": result["n_defaults"],"systemic_loss_pct": result["systemic_loss_pct"],"cascade_rounds": result["cascade_rounds"] })cascade_df = pd.DataFrame(cascade_results).sort_values("systemic_loss_pct", ascending=False)
Bảng 47.7: Các ngân hàng quan trọng về mặt hệ thống: Phân tích vỡ nợ dây chuyền
Hiện mã
cascade_df.head(15).round(4)
47.7 Kết nối xã hội giữa các địa phương
Mọi mạng lưới trong chương này cho đến giờ đều nối các doanh nghiệp hoặc tổ chức tài chính. Một loại mạng lưới khác nối con người, và qua con người nối các địa phương. Điều này quan trọng với tài chính vì các hộ gia đình tìm hiểu về sản phẩm tài chính từ những người họ quen biết, và vì bên cho vay và nhà đầu tư cũng một phần tìm hiểu về các bên đi vay và doanh nghiệp ở xa thông qua chính những mối quan hệ đó. Cho đến gần đây, mạng lưới này chưa thể được đo lường trên quy mô lớn. Chỉ số Kết nối Xã hội (Social Connectedness Index, SCI) của Bailey, Cao, Kuchler, Stroebel, và Wong (2018) đã thay đổi điều đó. Được xây dựng từ toàn bộ các liên kết bạn bè giữa người dùng Facebook, chỉ số này đo lường, với mỗi cặp vùng \(i\) và \(j\), xác suất tương đối để một người dùng ở \(i\) và một người dùng ở \(j\) là bạn bè:
Mức độ kết nối giảm mạnh theo khoảng cách địa lý, nhưng cũng phản ánh lịch sử di cư, các tuyến giao thông, và các ranh giới chính trị và văn hóa (Bailey, Cao, Kuchler, Stroebel, và Wong 2018). Với một vùng cho trước, SCI xác định một tập trọng số trên tất cả các vùng còn lại. Hữu ích nhất là các tỷ trọng bạn bè, tức tỷ lệ các liên kết bạn bè của vùng \(i\) dẫn đến vùng \(j\),
trong đó \(\mathcal{N}(i)\) là tập các vùng mà trên đó mức phơi nhiễm được tính: tất cả các vùng, hoặc chỉ những vùng ở xa \(i\) về mặt địa lý. Hai khuôn mẫu xây dựng trên Phương trình 47.14 làm nền cho tài liệu tài chính xã hội được Kuchler và Stroebel (2021) tổng quan. Trong Bailey, Cao, Kuchler, và Stroebel (2018), \(X_j\) là tăng trưởng giá nhà ở các vùng của bạn bè: những cá nhân có bạn bè trải qua mức tăng giá nhà lớn hơn kỳ vọng mức tăng trong tương lai lớn hơn và mua nhà lớn hơn với giá cao hơn. Trong Kuchler và c.s. (2022), \(X_j\) là vốn tổ chức đặt tại vùng \(j\): các doanh nghiệp có trụ sở ở những vùng có mức gần gũi xã hội với vốn cao thì có sở hữu tổ chức, định giá và thanh khoản cao hơn, với các tác động tập trung ở các doanh nghiệp nhỏ không được các nhà phân tích theo dõi. Các nghiên cứu trước đó đã xác lập phiên bản ở cấp độ cá nhân của ý tưởng này, từ việc tham gia thị trường cổ phiếu lan truyền qua tương tác xã hội (Hong, Kubik, và Stein 2004) đến việc những người cho vay trên một nền tảng cho vay ngang hàng coi mạng lưới bạn bè của bên đi vay là tín hiệu về chất lượng tín dụng (Lin, Prabhala, và Viswanathan 2013).
47.7.1 Mạng lưới xã hội như kênh truyền dẫn tín dụng
L. Allen, Peng, và Shan (2026) đưa bộ công cụ này vào tín dụng tiêu dùng và, một cách hiếm có, vào cả hai phía của thị trường cùng lúc. Bối cảnh của họ là LendingClub, nền tảng cho vay trực tuyến lớn nhất Hoa Kỳ, có dữ liệu công khai bao gồm cả các đơn bị từ chối lẫn được chấp thuận, nhờ đó có thể nghiên cứu các quyết định cấp vốn chứ không chỉ các khoản vay đã được cấp. Phiên bản đầu của bài báo sử dụng các đơn vay từ năm 2010 đến 2017, xác định vị trí bên đi vay theo mã ZIP ba chữ số, và ánh xạ các khu vực đó sang SCI cấp hạt (county).
Cầu. Số đơn vay trực tuyến ở một khu vực tăng lên sau khi hoạt động vay trực tuyến tăng ở những khu vực ở xa về địa lý nhưng có kết nối xã hội với khu vực đó. Giới hạn địa lý chính là yếu tố nhận diện. Bạn bè ở gần cùng chia sẻ điều kiện kinh tế địa phương, nên tương quan giữa hoạt động vay của một khu vực và của bạn bè nó có thể phản ánh một cú sốc chung chứ không phải quá trình học hỏi. Việc giới hạn \(\mathcal{N}(i)\) trong Phương trình 47.14 vào các vùng ở xa (ngoài bang, trong bản thảo đầu, theo thiết kế của Bailey, Cao, Kuchler, và Stroebel (2018)) loại bỏ thành phần chung đó. Phần còn lại phù hợp với việc nhận thức về một sản phẩm tài chính mới lan truyền qua các mối quan hệ xã hội: mọi người nghe về một nền tảng từ những người bạn đã dùng nó.
Cung. Các đơn vay từ những khu vực có mức gần gũi xã hội cao với các vùng dồi dào tiền gửi, một thước đo theo tinh thần của Kuchler và c.s. (2022) với tiền gửi thay cho vốn tổ chức, có khả năng được cấp vốn cao hơn. Riêng kết quả này có thể phản ánh một trong hai kênh mà bài báo đặt đối chọi nhau. Các mối quan hệ xã hội có thể truyền thông tin khó có được về điều kiện tín dụng của một cộng đồng đến những người có khả năng cấp vốn cho cộng đồng đó. Hoặc chúng có thể truyền sự thiên vị theo quan hệ thân quen (affinity): sự quen thuộc, và việc lặp lại các ý kiến qua một mạng lưới tạo ra thiên lệch thuyết phục, khiến bên cấp vốn ưu ái các bên đi vay có kết nối bất kể chất lượng. Cả hai kênh đều dự báo rằng các bên đi vay có kết nối nhận được nhiều tín dụng hơn. Chúng bất đồng về hiệu quả của khoản vay cận biên. Sự thiên vị theo quan hệ thân quen hạ thấp tiêu chuẩn, nên các khoản vay bổ sung sẽ vỡ nợ thường xuyên hơn; thông tin làm sắc nét việc sàng lọc, nên chúng sẽ vỡ nợ ít hơn. Kết quả sau thực tế (ex post) tốt hơn, điều này chỉ ra kênh thông tin. Lin, Prabhala, và Viswanathan (2013) đi đến kết luận tương tự từ các liên kết bạn bè của từng bên đi vay trên Prosper, và khi Duarte, Siegel, và Young (2012) áp dụng cùng logic cho ngoại hình của bên đi vay, họ thấy rằng những bên đi vay trông đáng tin mà người cho vay ưu ái thực sự vỡ nợ ít hơn.
Nhận diện. Mức độ kết nối xã hội không được phân bổ ngẫu nhiên. Các khu vực mà cư dân có mạng lưới phân tán về địa lý thường giàu hơn, học vấn cao hơn và di động hơn (Bailey, Cao, Kuchler, Stroebel, và Wong 2018), và mỗi đặc điểm đó đều ảnh hưởng trực tiếp đến kết quả tín dụng. Do đó, phiên bản đã công bố của bài báo thiết lập quan hệ nhân quả bằng các biến công cụ shift-share. Các tỷ trọng là các trọng số mạng lưới xã hội \(w_{ij}\), còn các cú dịch chuyển là những sự kiện ở các vùng có kết nối làm thay đổi cầu hoặc cung tín dụng ở đó mà không phản ánh điều kiện ở khu vực của chính bên đi vay: thiên tai ở phía cầu và hành vi sai phạm của cố vấn tài chính ở phía cung. Hành vi sai phạm của cố vấn được đo lường đặc biệt tốt ở Hoa Kỳ vì hồ sơ kỷ luật của nhà môi giới là công khai, đây là dữ liệu mà Egan, Matvos, và Seru (2019) khai thác.
Phân phối. Cả tác động lên cầu lẫn lên cung đều mạnh nhất ở các cộng đồng thiệt thòi, và không đi kèm tỷ lệ nợ quá hạn cao hơn. Các mối quan hệ xã hội với những vùng thịnh vượng hơn dường như thay thế một phần cho hạ tầng thông tin địa phương (chi nhánh ngân hàng, lịch sử tín dụng dài, bên cho vay dựa trên quan hệ) mà các khu vực thiệt thòi thiếu.
Bài báo đáng được đọc kỹ bởi bất kỳ ai xây dựng các thước đo mạng lưới, vì bốn lý do. Nó coi một mạng lưới giữa các địa phương là một đối tượng quan sát được, đo lường được ngang hàng với các liên kết sở hữu hay chuỗi cung ứng. Nó nghiên cứu cả hai phía của một thị trường thông qua cùng một mạng lưới, điều mà ít bài báo về mạng lưới làm được. Nó sử dụng kết quả sau thực tế làm phép kiểm định phân tách thông tin với thiên lệch, một phép kiểm định mà mọi khẳng định rằng mạng lưới “cải thiện” phân bổ đều nên được yêu cầu vượt qua. Và nó nêu rõ rằng biến phơi nhiễm xã hội là một cấu trúc shift-share, điều quyết định những gì có thể dùng để nhận diện nó.
47.7.2 Logic shift-share và suy luận
Phương trình 47.14 là tổng có trọng số các cú dịch chuyển theo vùng, cùng cấu trúc với các biến công cụ Bartik của kinh tế học lao động, và tài liệu kinh tế lượng về các thiết kế này nêu rất chính xác khi nào chúng nhận diện được một tác động nhân quả. Việc nhận diện có thể đến từ các tỷ trọng ngoại sinh (Goldsmith-Pinkham, Sorkin, và Swift 2020). Ở đây điều đó có nghĩa là cấu trúc các mối quan hệ xã hội của một khu vực không liên quan đến kết quả tín dụng của khu vực đó ngoại trừ thông qua các cú sốc mà các mối quan hệ truyền đi, một giả định mạnh khi các cấu trúc đó phản ánh di cư, dân tộc và cấu trúc ngành. Ngoài ra, việc nhận diện có thể đến từ các cú dịch chuyển được phân bổ giữa các vùng gần như ngẫu nhiên, có điều kiện theo các tỷ trọng (Borusyak, Hull, và Jaravel 2022). Thiên tai là những cú dịch chuyển hấp dẫn chính vì con đường thứ hai là hợp lý đối với chúng. Dù theo con đường nào, suy luận cũng đòi hỏi sự cẩn trọng. Các vùng có trọng số phơi nhiễm tương tự nhận các cú sốc tương quan với nhau, nên các sai số chuẩn thông thường và sai số chuẩn theo cụm vùng đánh giá quá cao độ chính xác. Adão, Kolesár, và Morales (2019) suy ra các sai số chuẩn vững trước tương quan do phơi nhiễm này. Một hồi quy phơi nhiễm xã hội được báo cáo mà không có chúng, và không có các kiểm định cân bằng của các cú dịch chuyển với đặc điểm vùng trước kỳ, thì chưa đủ sức thuyết phục.
47.7.3 Kết nối xã hội ở Việt Nam
Dữ liệu đã có sẵn. Bản phát hành SCI công khai của Meta bao phủ các quốc gia ngoài châu Âu ở cấp đơn vị hành chính thứ nhất, mà với Việt Nam nghĩa là 63 tỉnh theo phiên bản ranh giới được dùng trong bản phát hành. Facebook được sử dụng rộng rãi ở Việt Nam, nhưng nhiều liên lạc riêng tư lại đi qua Zalo, mà SCI không quan sát được. Do đó, mức độ kết nối giữa các cặp tỉnh từ Facebook chỉ đo lường một kênh tiếp xúc xã hội, không phải tất cả.
Hai đặc điểm của bối cảnh này cần được lưu ý trước khi chạy bất kỳ hồi quy nào. Thứ nhất, việc hợp nhất các tỉnh của Việt Nam từ 63 xuống 34 vào năm 2025 có nghĩa là các thống kê cấp tỉnh hiện hành phải được ánh xạ ngược về ranh giới cũ, hoặc SCI phải được tổng hợp xuôi theo trọng số dân số, và cả hai cách ánh xạ đều không vô hại đối với những tỉnh bị chia cho nhiều đơn vị mới. Thứ hai, di cư nội địa là yếu tố quyết định chính của mức độ kết nối, và tổng điều tra ghi nhận trực tiếp điều này. Các cuộc Tổng điều tra Dân số và Nhà ở năm 2009 và 2019 ghi nhận tỉnh cư trú năm năm trước của mỗi người trả lời, từ đó cho ra các ma trận di cư từ tỉnh này sang tỉnh khác. Một thước đo phơi nhiễm có trọng số theo di cư vừa là phép kiểm tra tính hợp lệ của SCI, vừa, do có trước hầu hết các kết quả cần quan tâm, là một ứng viên biến công cụ cho nó.
Có bốn thiết kế nghiên cứu có thể chuyển giao trực tiếp nhất.
Mức gần gũi xã hội với vốn đối với các doanh nghiệp niêm yết. Vốn đầu tư và hoạt động môi giới ở Việt Nam tập trung tại Thành phố Hồ Chí Minh và Hà Nội. Thước đo của Kuchler và c.s. (2022), được tính cho tỉnh đặt trụ sở của từng doanh nghiệp, với tiền gửi hay thu nhập cấp tỉnh thay cho vốn tổ chức, đặt câu hỏi liệu các doanh nghiệp có trụ sở ở những tỉnh gần gũi xã hội với các trung tâm này có thanh khoản, định giá và sở hữu nước ngoài cao hơn hay không. Tỉnh đặt trụ sở xuất hiện trong mọi bản cáo bạch niêm yết, và các biến kết quả là những biến tiêu chuẩn trong cuốn sách này.
Sự khuếch tán của việc tham gia thị trường của nhà đầu tư cá nhân. Sự gia tăng đột biến các tài khoản môi giới mới trong giai đoạn 2020–2022 là một sự kiện phía cầu đúng loại mà L. Allen, Peng, và Shan (2026) nghiên cứu: nhận thức về một sản phẩm tài chính lan truyền qua các mối quan hệ xã hội. Thiết kế đặt câu hỏi liệu việc mở tài khoản ở một tỉnh có tăng tốc sau khi việc mở tài khoản tăng ở các tỉnh ở xa nhưng có kết nối xã hội hay không. Ràng buộc chính là dữ liệu. Thống kê tài khoản được công bố ở cấp quốc gia, và việc phân tách theo tỉnh sẽ cần sự hợp tác của các công ty môi giới.
Cho vay trên nền tảng trong khung thử nghiệm có kiểm soát (sandbox). Nghị định 94/2025/ND-CP, có hiệu lực từ tháng 7 năm 2025, tạo ra cơ chế thử nghiệm có kiểm soát cho cho vay ngang hàng cùng với chấm điểm tín dụng và API mở. Một thị trường nền tảng trong những năm đầu là bối cảnh của kết quả về cầu trong L. Allen, Peng, và Shan (2026), và nếu dữ liệu về các bên tham gia sandbox được cung cấp cho các nhà nghiên cứu, thì sự lan rộng của các nền tảng giữa các tỉnh gần như là một cơ hội tái lập. Việt Nam cũng là nơi mà kết quả phía cung có thể quan trọng nhất. Như đã thảo luận trong Mục 2.7, độ bao phủ của trung tâm thông tin tín dụng vẫn còn mỏng, và L. Allen, Peng, và Shan (2026) gợi ý rằng các mối quan hệ xã hội với những vùng giàu hơn có thể một phần thay thế cho hạ tầng thông tin còn thiếu.
Các cú sốc cho thiết kế shift-share. Ở phía cầu, bão và lũ lụt tác động đến những tỉnh xác định được vào những thời điểm được ghi nhận rõ ràng, trong đó có bão Yagi ở các tỉnh phía Bắc vào tháng 9 năm 2024, và đây là những cú dịch chuyển tự nhiên. Ở phía cung, sự kiện tương ứng gần nhất ở Việt Nam với hành vi sai phạm của cố vấn tại Hoa Kỳ là việc bán trái phiếu doanh nghiệp cho người gửi tiền ngân hàng thông qua mạng lưới chi nhánh, bị phát hiện trong vụ Vạn Thịnh Phát và SCB năm 2022, một cú sốc đối với niềm tin vào tiết kiệm qua trung gian ngân hàng. Sử dụng nó làm cú dịch chuyển đòi hỏi phải biết các trái chủ bị ảnh hưởng sống ở đâu, thông tin này không công khai, và không nên thực hiện thiết kế này nếu thiếu thông tin đó.
Sáu quy tắc thực hành sau đây áp dụng cho nghiên cứu mạng lưới xã hội cấp tỉnh ở Việt Nam.
Định nghĩa “ở xa” theo vùng, không theo sự tiếp giáp. Hình dạng dài và hẹp của Việt Nam khiến các tỉnh lân cận có thể cách xa nhau về kinh tế còn các tỉnh ở xa lại hội nhập chặt chẽ. Hãy đo phơi nhiễm trên các tỉnh nằm ngoài vùng kinh tế–xã hội của chính bên đi vay, tương tự hạn chế ngoài bang, và chứng tỏ rằng kết quả vẫn vững với các ngưỡng cắt thay thế.
Cố định phiên bản ranh giới. Làm việc xuyên suốt với 63 tỉnh trước năm 2025, và ghi lại bảng chuyển đổi tương ứng cho mọi dữ liệu được báo cáo theo hệ thống 34 tỉnh.
Coi SCI là ảnh chụp của một mạng lưới nội sinh. Nó được quan sát tại một vài thời điểm, sau phần lớn quá trình di cư đã tạo ra nó. Với các kết quả có trước ảnh chụp này, hãy dùng ma trận di cư của tổng điều tra năm 2009 làm thước đo phiên bản sớm hơn.
Thực hiện phép kiểm định hiệu quả. Bất kỳ phát hiện nào cho rằng các mối quan hệ xã hội làm tăng phê duyệt, cấp vốn hay đầu tư đều phải được tiếp nối bằng một kiểm định trên các kết quả sau thực tế, vỡ nợ đối với tín dụng và lợi nhuận tiếp theo đối với cổ phiếu, phân biệt thông tin với sự thiên vị theo quan hệ thân quen, như L. Allen, Peng, và Shan (2026) đã làm.
Tôn trọng số lượng đơn vị nhỏ. Với 63 tỉnh, suy luận theo cụm dựa trên ít cụm. Hãy báo cáo các giá trị p của bootstrap cụm kiểu wild (wild-cluster bootstrap) bên cạnh các giá trị thông thường.
47.8 Mạng nơ-ron đồ thị cho dự báo tài chính
47.8.1 Từ đồ thị đến dự báo
Phân tích mạng lưới cổ điển tính các đặc trưng thủ công (độ trung tâm, phân cụm, thành viên cộng đồng) rồi đưa vào các mô hình hồi quy tiêu chuẩn. Các mạng nơ-ron đồ thị (GNN) học các đặc trưng trực tiếp từ cấu trúc đồ thị và thuộc tính của nút thông qua truyền thông điệp. Ý tưởng then chốt là biểu diễn của một nút không chỉ phụ thuộc vào đặc trưng của chính nó mà còn phụ thuộc vào đặc trưng của các nút lân cận, lân cận của chúng, và cứ thế tiếp tục.
47.8.2 Mạng tích chập đồ thị (GCN)
Mạng tích chập đồ thị (Graph Convolutional Network) của Kipf và Welling (2016) thực hiện phép tích chập phổ trên đồ thị. Quy tắc lan truyền theo từng lớp là:
trong đó \(\tilde{A} = A + I_n\) là ma trận kề có thêm các vòng tự nối, \(\tilde{D}\) là ma trận bậc tương ứng, \(H^{(\ell)}\) là ma trận đặc trưng nút ở lớp \(\ell\), \(W^{(\ell)}\) là ma trận trọng số có thể học, và \(\sigma\) là hàm phi tuyến (ReLU). Số hạng chuẩn hóa \(\tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2}\) lấy trung bình các đặc trưng của mỗi nút với đặc trưng của các nút lân cận, có trọng số theo bậc.
47.8.3 Mạng chú ý đồ thị (GAT)
Mạng chú ý đồ thị (Graph Attention Network) của Veličković và c.s. (2017) thay thế phép chuẩn hóa cố định trong GCN bằng các hệ số chú ý được học:
trong đó hệ số chú ý \(\alpha_{ij}\) được tính như sau:
\[
\alpha_{ij} = \frac{\exp\left(\text{LeakyReLU}\left(\mathbf{a}^\top [W h_i \| W h_j]\right)\right)}{\sum_{k \in \mathcal{N}(i)} \exp\left(\text{LeakyReLU}\left(\mathbf{a}^\top [W h_i \| W h_k]\right)\right)}
\tag{47.17}\]
Điều này cho phép mô hình học xem các nút lân cận nào mang nhiều thông tin nhất đối với từng nút, thay vì đối xử bình đẳng với mọi nút lân cận.
Hiện mã
class GCNLayer(nn.Module):"""Graph Convolutional Network layer (Kipf & Welling, 2016)."""def__init__(self, in_features, out_features, bias=True):super().__init__()self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features))if bias:self.bias = nn.Parameter(torch.FloatTensor(out_features))else:self.bias =Noneself.reset_parameters()def reset_parameters(self): nn.init.xavier_uniform_(self.weight)ifself.bias isnotNone: nn.init.zeros_(self.bias)def forward(self, x, adj_norm):""" Parameters ---------- x : Tensor (n_nodes, in_features) Node feature matrix. adj_norm : Tensor (n_nodes, n_nodes) Normalized adjacency matrix (D^{-1/2} A D^{-1/2}). """ support = x @self.weight output = adj_norm @ supportifself.bias isnotNone: output = output +self.biasreturn outputclass GATLayer(nn.Module):"""Graph Attention Network layer (Velickovic et al., 2017)."""def__init__(self, in_features, out_features, n_heads=4, dropout=0.1, concat=True):super().__init__()self.n_heads = n_headsself.out_features = out_featuresself.concat = concatself.W = nn.Parameter( torch.FloatTensor(n_heads, in_features, out_features) )self.a = nn.Parameter(torch.FloatTensor(n_heads, 2* out_features, 1))self.dropout = nn.Dropout(dropout)self.leaky_relu = nn.LeakyReLU(0.2)self.reset_parameters()def reset_parameters(self): nn.init.xavier_uniform_(self.W) nn.init.xavier_uniform_(self.a)def forward(self, x, adj):""" Parameters ---------- x : Tensor (n_nodes, in_features) adj : Tensor (n_nodes, n_nodes) Binary adjacency matrix (1 if edge, 0 otherwise). """ n = x.size(0)# Linear transformation for each head# x: (n, in_f) -> h: (heads, n, out_f) h = torch.einsum("ni,hio->hno", x, self.W)# Attention coefficients# Concatenate h_i and h_j for all pairs h_i = h.unsqueeze(2).expand(-1, -1, n, -1) # (heads, n, n, out_f) h_j = h.unsqueeze(1).expand(-1, n, -1, -1) # (heads, n, n, out_f) concat_h = torch.cat([h_i, h_j], dim=-1) # (heads, n, n, 2*out_f) e =self.leaky_relu( torch.einsum("hnmo,hoi->hnm", concat_h, self.a).squeeze(-1) )# Mask non-edges mask = adj.unsqueeze(0).expand(self.n_heads, -1, -1) e = e.masked_fill(mask ==0, float("-inf"))# Softmax attention alpha = F.softmax(e, dim=-1) alpha =self.dropout(alpha)# Weighted aggregation out = torch.einsum("hnm,hmo->hno", alpha, h) # (heads, n, out_f)ifself.concat: out = out.permute(1, 0, 2).reshape(n, -1) # (n, heads * out_f)else: out = out.mean(dim=0) # (n, out_f)return out, alphaclass FinancialGNN(nn.Module):""" Graph Neural Network for financial node prediction. Supports GCN and GAT layers with flexible architecture. """def__init__(self, input_dim, hidden_dim=64, output_dim=1, n_layers=2, n_heads=4, gnn_type="gat", dropout=0.3):super().__init__()self.gnn_type = gnn_typeself.dropout = nn.Dropout(dropout)if gnn_type =="gcn":self.layers = nn.ModuleList()self.layers.append(GCNLayer(input_dim, hidden_dim))for _ inrange(n_layers -1):self.layers.append(GCNLayer(hidden_dim, hidden_dim))elif gnn_type =="gat":self.layers = nn.ModuleList()self.layers.append( GATLayer(input_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True) )for _ inrange(n_layers -2):self.layers.append( GATLayer(hidden_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True) )# Last layer: single head, no concatself.layers.append( GATLayer(hidden_dim, hidden_dim, n_heads=1, concat=False) )# Prediction headself.head = nn.Sequential( nn.Linear(hidden_dim, hidden_dim //2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim //2, output_dim) )def forward(self, x, adj):""" Parameters ---------- x : Tensor (n_nodes, input_dim) Node features. adj : Tensor (n_nodes, n_nodes) Adjacency matrix. """ h = x attention_weights = []for i, layer inenumerate(self.layers):ifself.gnn_type =="gcn": h = layer(h, adj) h = F.relu(h) if i <len(self.layers) -1else helifself.gnn_type =="gat": h, alpha = layer(h, adj) attention_weights.append(alpha)if i <len(self.layers) -1: h = F.elu(h) h =self.dropout(h) predictions =self.head(h).squeeze(-1)return predictions, attention_weights
47.8.4 GNN cho dự báo lợi nhuận chéo (cross-sectional)
Chúng ta áp dụng GNN để dự báo lợi nhuận cổ phiếu theo chiều chéo (cross-sectional), trong đó đồ thị mã hóa các quan hệ sở hữu giữa các doanh nghiệp. Giả thuyết là các doanh nghiệp liên kết qua quan hệ sở hữu có động thái lợi nhuận tương quan mà GNN có thể khai thác.
Hiện mã
def prepare_gnn_data(firms_df, ownership_graph, returns_df, date):""" Prepare node features and adjacency matrix for GNN prediction. Parameters ---------- firms_df : DataFrame Firm characteristics. ownership_graph : nx.DiGraph Ownership network. returns_df : DataFrame Stock returns. Returns ------- tuple : (node_features, adjacency, target_returns, ticker_list) """# Get stocks with both features and network presence tickers =sorted(set(firms_df["ticker"]) &set(ownership_graph.nodes()) &set(returns_df["ticker"]) )ifnot tickers:returnNone, None, None, None# Node features feature_cols = ["log_size", "book_to_market", "momentum_12m","profitability", "investment", "leverage","beta", "volatility", "turnover" ] feat_df = firms_df[firms_df["ticker"].isin(tickers)].set_index("ticker") feat_df = feat_df.reindex(tickers) X = feat_df[feature_cols].fillna(0).values X = (X - X.mean(axis=0)) / (X.std(axis=0) +1e-8)# Adjacency matrix ticker_to_idx = {t: i for i, t inenumerate(tickers)} n =len(tickers) A = np.zeros((n, n))for u, v, d in ownership_graph.edges(data=True):if u in ticker_to_idx and v in ticker_to_idx: weight = d.get("weight", 1) A[ticker_to_idx[u], ticker_to_idx[v]] = weight A[ticker_to_idx[v], ticker_to_idx[u]] = weight # Symmetrize# Add self-loops A = A + np.eye(n)# Normalize: D^{-1/2} A D^{-1/2} D = np.diag(A.sum(axis=1)) D_inv_sqrt = np.diag(1.0/ np.sqrt(np.diag(D) +1e-10)) A_norm = D_inv_sqrt @ A @ D_inv_sqrt# Target returns ret_df = returns_df[ (returns_df["ticker"].isin(tickers)) & (returns_df["date"] == date) ].set_index("ticker").reindex(tickers) y = ret_df["ret"].fillna(0).valuesreturn ( torch.tensor(X, dtype=torch.float32), torch.tensor(A_norm, dtype=torch.float32), torch.tensor(y, dtype=torch.float32), tickers )def train_gnn_model(model, X_train, A_train, y_train, X_val, A_val, y_val, n_epochs=100, lr=1e-3):"""Train GNN model with validation-based early stopping.""" optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4) best_val_loss =float("inf") patience_counter =0for epoch inrange(n_epochs): model.train() optimizer.zero_grad() pred, _ = model(X_train, A_train) loss = F.mse_loss(pred, y_train) loss.backward() optimizer.step()# Validation model.eval()with torch.no_grad(): val_pred, _ = model(X_val, A_val) val_loss = F.mse_loss(val_pred, y_val).item()if val_loss < best_val_loss: best_val_loss = val_loss best_state = {k: v.clone() for k, v in model.state_dict().items()} patience_counter =0else: patience_counter +=1if patience_counter >=15:break model.load_state_dict(best_state)return model, best_val_loss
47.8.5 Mạng đồ thị theo thời gian
Các mạng lưới tài chính tiến triển theo thời gian. Tỷ lệ sở hữu thay đổi, các thành viên hội đồng quản trị luân chuyển, các tương quan dịch chuyển. Các GNN theo thời gian mở rộng GNN tĩnh bằng cách đưa chiều thời gian vào một cách tường minh. Mạng đồ thị theo thời gian (Temporal Graph Network, TGN) của Rossi và c.s. (2020) duy trì một trạng thái bộ nhớ cho mỗi nút, được cập nhật mỗi khi có một sự kiện liên quan đến nút đó:
trong đó \(\mathbf{s}_i(t)\) là trạng thái bộ nhớ của nút \(i\) tại thời điểm \(t\), \(\text{msg}(i, t)\) là thông điệp được tổng hợp từ các sự kiện tại thời điểm \(t\), và GRU là đơn vị hồi quy có cổng (gated recurrent unit).
Hiện mã
class TemporalFinancialGNN(nn.Module):""" Temporal GNN for dynamic financial networks. Combines graph convolution with temporal memory. """def__init__(self, node_dim, edge_dim=1, memory_dim=64, hidden_dim=64, output_dim=1, n_heads=4):super().__init__()self.memory_dim = memory_dim# Memory update (GRU)self.memory_updater = nn.GRUCell( input_size=node_dim + edge_dim + memory_dim, hidden_size=memory_dim )# Message functionself.message_fn = nn.Sequential( nn.Linear(memory_dim *2+ edge_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, memory_dim) )# Graph attention for spatial aggregationself.spatial_attn = GATLayer( memory_dim + node_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True )# Temporal attention for recent historyself.temporal_attn = nn.MultiheadAttention( embed_dim=hidden_dim, num_heads=n_heads, batch_first=True )# Prediction headself.head = nn.Sequential( nn.Linear(hidden_dim *2, hidden_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden_dim, output_dim) )def forward(self, node_features, adj_sequence, memory=None):""" Parameters ---------- node_features : list of Tensors Node features at each time step. adj_sequence : list of Tensors Adjacency matrices at each time step. memory : Tensor or None Initial memory state (n_nodes, memory_dim). Returns ------- predictions : Tensor Predictions for the last time step. """ n_nodes = node_features[0].shape[0] T =len(node_features)if memory isNone: memory = torch.zeros(n_nodes, self.memory_dim) temporal_states = []for t inrange(T): x_t = node_features[t] adj_t = adj_sequence[t]# Spatial aggregation via GAT combined = torch.cat([x_t, memory], dim=-1) spatial_out, _ =self.spatial_attn(combined, adj_t) temporal_states.append(spatial_out.unsqueeze(1))# Update memory with current state update_input = torch.cat([ x_t, spatial_out[:, :1].squeeze(1) if spatial_out.dim() >2else spatial_out, memory ], dim=-1)[:, :self.memory_updater.input_size]# Stack temporal states: (n_nodes, T, hidden) temporal_stack = torch.cat(temporal_states, dim=1)# Temporal attention across time steps temporal_out, _ =self.temporal_attn( temporal_stack, temporal_stack, temporal_stack )# Use last time step last_spatial = temporal_states[-1].squeeze(1) last_temporal = temporal_out[:, -1, :] combined = torch.cat([last_spatial, last_temporal], dim=-1) predictions =self.head(combined).squeeze(-1)return predictions
47.8.6 GNN cho rủi ro tín dụng và phát hiện gian lận
Ngoài dự báo lợi nhuận, GNN đặc biệt mạnh trong đánh giá rủi ro tín dụng và phát hiện gian lận, nơi bản thân cấu trúc mạng lưới mang thông tin. Trong rủi ro tín dụng, các doanh nghiệp liên kết với các đối tác đã vỡ nợ phải đối mặt với rủi ro gia tăng. Trong phát hiện gian lận, các mẫu hình giao dịch đáng ngờ lan truyền qua các mạng lưới thực thể liên quan.
Hiện mã
class CreditRiskGNN(nn.Module):""" GNN for credit default prediction using firm and bank networks. Node classification: predict default probability per firm. """def__init__(self, firm_features_dim, bank_features_dim=0, hidden_dim=64, n_heads=4):super().__init__() input_dim = firm_features_dim + bank_features_dim# GNN backboneself.gnn = FinancialGNN( input_dim=input_dim, hidden_dim=hidden_dim, output_dim=1, n_layers=3, n_heads=n_heads, gnn_type="gat" )# Additional head for probability outputself.sigmoid = nn.Sigmoid()def forward(self, x, adj): logits, attn =self.gnn(x, adj) probs =self.sigmoid(logits)return probs, attn
47.9 Khi nào nên dùng các phương pháp mạng lưới
47.9.1 Khung ra quyết định
Bảng 47.8: Khung ra quyết định cho các phương pháp mạng lưới
Câu hỏi
Cách tiếp cận tốt nhất
Ví dụ
Doanh nghiệp \(A\) có tác động đến doanh nghiệp \(B\) không?
Kiểm định từng cặp + biến kiểm soát mạng lưới
Động lượng chuỗi cung ứng
Doanh nghiệp nào quan trọng nhất về mặt hệ thống?
Các thước đo độ trung tâm
Lây lan liên ngân hàng
Các doanh nghiệp kết nối qua mạng lưới có đồng biến động không?
Tương quan so với mức trùng lặp kiêm nhiệm
Khuếch tán qua kiêm nhiệm hội đồng quản trị
Cấu trúc mạng lưới có dự báo được lợi nhuận không?
GNN hoặc hồi quy bổ sung biến mạng lưới
Dự báo lợi nhuận bằng GCN
Một cú sốc lan truyền như thế nào?
Mô phỏng dây chuyền
Mô hình vỡ nợ Eisenberg-Noe
Danh mục tối ưu là gì khi xét rủi ro mạng lưới?
Tối ưu hóa chính quy hóa theo mạng lưới
Lọc MST của tương quan
Mạng lưới thay đổi theo thời gian như thế nào?
GNN theo thời gian hoặc phân tích cửa sổ trượt
Kết nối trượt DY
Thông tin có truyền giữa các vùng qua quan hệ xã hội không?
Phơi nhiễm xã hội trên các vùng ở xa + IV shift-share
47.9.2 Yêu cầu dữ liệu và mức độ sẵn có ở Việt Nam
Bảng 47.9: Các nguồn dữ liệu mạng lưới tài chính ở Việt Nam
Loại mạng lưới
Dữ liệu cần thiết
Mức độ sẵn có ở Việt Nam
Tần suất cập nhật
Sở hữu
Sổ đăng ký cổ đông
Tốt (báo cáo bán niên)
Nửa năm
Kiêm nhiệm hội đồng quản trị
Bổ nhiệm thành viên
Tốt (hồ sơ công bố)
Hằng năm
Chuỗi cung ứng
Công bố khách hàng–nhà cung cấp, dữ liệu thương mại
Trung bình (công bố giao dịch với bên liên quan)
Hằng năm
Tương quan
Dữ liệu lợi nhuận
Rất tốt (hằng ngày từ sở giao dịch)
Hằng ngày
Liên ngân hàng
Dư nợ song phương
Hạn chế (dữ liệu NHNN, thuyết minh BCTC của ngân hàng)
Hằng quý
Cùng nắm giữ
Sở hữu của tổ chức
Trung bình (công bố bán niên)
Nửa năm
Cho vay
Dữ liệu cấp khoản vay
Hạn chế (dữ liệu giám sát ngân hàng)
Hằng quý
Kết nối xã hội
Liên kết bạn bè giữa các cặp vùng; ma trận di cư
Trung bình (SCI của Meta cho 63 tỉnh; di cư theo tổng điều tra 2009 và 2019)
Ảnh chụp định kỳ
47.10 Tóm tắt
Chương này đã phát triển bộ công cụ mạng lưới cho thị trường tài chính Việt Nam, trải từ lý thuyết đồ thị cổ điển, việc xây dựng các mạng lưới tài chính theo từng lĩnh vực, đến các mạng nơ-ron đồ thị hiện đại.
Hiểu biết trung tâm là các mạng lưới tài chính mã hóa thông tin mà các hồi quy dữ liệu bảng tiêu chuẩn không nhìn thấy. Các mạng lưới sở hữu bộc lộ cấu trúc kiểm soát kim tự tháp và rủi ro chuyển dịch lợi ích (tunneling), giải thích các khác biệt chéo về giá trị doanh nghiệp. Quan hệ kiêm nhiệm hội đồng quản trị đóng vai trò kênh thông tin qua đó các thông lệ trả thưởng, chính sách đầu tư và chuẩn mực quản trị khuếch tán. Các mạng lưới chuỗi cung ứng lan truyền các cú sốc kinh tế thực: hiện tượng bất thường động lượng khách hàng cho thấy giá thị trường phản ánh thông tin chuỗi cung ứng với độ trễ có thể dự báo. Các mạng lưới tương quan nắm bắt cấu trúc đồng biến động vừa cho thấy các cụm theo ngành vừa tiến triển qua các chế độ thị trường. Các mạng lưới liên ngân hàng quyết định liệu sự sụp đổ của từng ngân hàng có lan thành khủng hoảng hệ thống hay không. Và các mạng lưới xã hội giữa các địa phương mang thông tin mà các kênh chính thức bỏ sót: L. Allen, Peng, và Shan (2026) cho thấy các mối quan hệ giữa các cộng đồng mở rộng cả cầu lẫn cung tín dụng trực tuyến, nhất là ở các cộng đồng thiệt thòi, mà không làm tăng nợ quá hạn, điều này khiến Chỉ số Kết nối Xã hội trở thành công cụ đo lường có giá trị đặc biệt trong một thị trường mà thông tin tín dụng chính thức còn mỏng.
Thị trường Việt Nam có mật độ mạng lưới đặc biệt cao vì vai trò phổ biến của sở hữu nhà nước, vị thế nổi bật của các tập đoàn và sự tập trung của hệ thống ngân hàng. Khoảng cách giữa quyền kiểm soát và quyền dòng tiền (tức là chênh lệch giữa quyền kiểm soát và quyền dòng tiền do sở hữu kim tự tháp tạo ra) lớn hơn và biến động nhiều hơn so với các thị trường có mức bảo vệ cổ đông thiểu số mạnh hơn. Khoảng cách này, chỉ có thể tính được thông qua phân tích mạng lưới của các chuỗi sở hữu, vừa là thước đo rủi ro quản trị vừa là yếu tố dự báo giá trị doanh nghiệp.
Các mạng nơ-ron đồ thị mở rộng bộ công cụ từ các đặc trưng mạng lưới thủ công sang các biểu diễn được học. Các kiến trúc GCN và GAT tổng hợp thông tin trên các vùng lân cận mạng lưới, và GNN theo thời gian nắm bắt sự tiến triển của cấu trúc mạng lưới theo thời gian. Với dự báo lợi nhuận, câu hỏi thực nghiệm là liệu khả năng của GNN trong việc học các hàm linh hoạt của cấu trúc đồ thị có vượt trội so với cách tiếp cận đơn giản hơn là tính các thước đo độ trung tâm rồi đưa vào các mô hình tiêu chuẩn hay không. Các bài tập cung cấp một khung để trả lời câu hỏi này một cách chặt chẽ.
Góc nhìn mạng lưới không phải là sự thay thế cho các phương pháp định giá tài sản hay tài chính doanh nghiệp tiêu chuẩn; nó là một sự bổ sung. Các thiết kế nghiên cứu hiệu quả nhất kết hợp cấu trúc mạng lưới với các chiến lược nhận diện từ bộ công cụ suy luận nhân quả: sử dụng các cú sốc mạng lưới làm biến công cụ, khai thác các gián đoạn mạng lưới như các thí nghiệm tự nhiên, và kiểm soát vị trí trong mạng lưới khi ước lượng các tác động can thiệp. Giao điểm của mạng lưới và suy luận nhân quả (chẳng hạn, nhiễu chéo trong mạng lưới (network interference), nhận diện hiệu ứng ngang hàng, kinh tế lượng không gian) là biên giới của tài chính thực nghiệm.
Acemoglu, Daron, Asuman Ozdaglar, và Alireza Tahbaz-Salehi. 2015. “Systemic risk and stability in financial networks”. American Economic Review 105 (2): 564–608.
Adão, Rodrigo, Michal Kolesár, và Eduardo Morales. 2019. “Shift-share designs: Theory and inference”. The Quarterly Journal of Economics 134 (4): 1949–2010. https://doi.org/10.1093/qje/qjz025.
Allen, Franklin, và Douglas Gale. 2000. “Financial contagion”. Journal of political economy 108 (1): 1–33.
Allen, Linda, Lin Peng, và Yu Shan. 2026. “Social networks as information conduits for online credit supply and demand”. Management Science. https://doi.org/10.1287/mnsc.2024.06050.
Bailey, Michael, Rachel Cao, Theresa Kuchler, Johannes Stroebel, và Arlene Wong. 2018. “Social connectedness: Measurement, determinants, and effects”. Journal of Economic Perspectives 32 (3): 259–80. https://doi.org/10.1257/jep.32.3.259.
Bailey, Michael, Ruiqing Cao, Theresa Kuchler, và Johannes Stroebel. 2018. “The economic effects of social networks: Evidence from the housing market”. Journal of Political Economy 126 (6): 2224–76. https://doi.org/10.1086/700073.
Bebchuk, Lucian A. 1999. “A rent-protection theory of corporate ownership and control”. National Bureau of Economic Research Cambridge, Mass., USA.
Bizjak, John M, Michael L Lemmon, và Lalitha Naveen. 2008. “Does the use of peer groups contribute to higher pay and less efficient compensation?”Journal of Financial Economics 90 (2): 152–68.
Borusyak, Kirill, Peter Hull, và Xavier Jaravel. 2022. “Quasi-experimental shift-share research designs”. The Review of Economic Studies 89 (1): 181–213. https://doi.org/10.1093/restud/rdab030.
Cai, Ye, Dan S Dhaliwal, Yongtae Kim, và Carrie Pan. 2014. “Board interlocks and the diffusion of disclosure policy”. Review of Accounting Studies 19 (3): 1086–1119.
Cohen, Lauren, và Andrea Frazzini. 2008. “Economic links and predictable returns”. The Journal of Finance 63 (4): 1977–2011.
Diebold, Francis X, và Kamil Yılmaz. 2014. “On the network topology of variance decompositions: Measuring the connectedness of financial firms”. Journal of econometrics 182 (1): 119–34.
Duarte, Jefferson, Stephan Siegel, và Lance Young. 2012. “Trust and credit: The role of appearance in peer-to-peer lending”. The Review of Financial Studies 25 (8): 2455–84.
Egan, Mark, Gregor Matvos, và Amit Seru. 2019. “The market for financial adviser misconduct”. Journal of Political Economy 127 (1): 233–95. https://doi.org/10.1086/700735.
Eisenberg, Larry, và Thomas H Noe. 2001. “Systemic risk in financial systems”. Management Science 47 (2): 236–49.
Goldsmith-Pinkham, Paul, Isaac Sorkin, và Henry Swift. 2020. “Bartik instruments: What, when, why, and how”. American Economic Review 110 (8): 2586–2624. https://doi.org/10.1257/aer.20181047.
Hong, Harrison, Jeffrey D. Kubik, và Jeremy C. Stein. 2004. “Social interaction and stock-market participation”. The Journal of Finance 59 (1): 137–63. https://doi.org/10.1111/j.1540-6261.2004.00629.x.
Kipf, Thomas N, và Max Welling. 2016. “Semi-supervised classification with graph convolutional networks”. arXiv preprint arXiv:1609.02907.
Kuchler, Theresa, Yan Li, Lin Peng, Johannes Stroebel, và Dexin Zhou. 2022. “Social proximity to capital: Implications for investors and firms”. The Review of Financial Studies 35 (6): 2743–89. https://doi.org/10.1093/rfs/hhab111.
Lin, Mingfeng, Nagpurnanand R. Prabhala, và Siva Viswanathan. 2013. “Judging borrowers by the company they keep: Friendship networks and information asymmetry in online peer-to-peer lending”. Management Science 59 (1): 17–35. https://doi.org/10.1287/mnsc.1120.1560.
Menzly, Lior, và Oguzhan Ozbas. 2010. “Market segmentation and cross-predictability of returns”. The Journal of Finance 65 (4): 1555–80.
Rossi, Emanuele, Ben Chamberlain, Fabrizio Frasca, Davide Eynard, Federico Monti, và Michael Bronstein. 2020. “Temporal graph networks for deep learning on dynamic graphs”. arXiv preprint arXiv:2006.10637.
Veličković, Petar, Guillem Cucurull, Arantxa Casanova, Adriana Romero, Pietro Lio, và Yoshua Bengio. 2017. “Graph attention networks”. arXiv preprint arXiv:1710.10903.
# Mạng lưới và đồ thị trong tài chínhThị trường tài chính là những mạng lưới. Lợi nhuận của mỗi cổ phiếu đều chịu ảnh hưởng từ các mối liên kết: các doanh nghiệp có chung chuỗi cung ứng, thành viên hội đồng quản trị, kiểm toán viên, nhà đầu tư, bên cho vay và cơ quan quản lý. Các cú sốc lan truyền qua những liên kết này (chẳng hạn, một vụ phá sản gây hiệu ứng lan tỏa trong mạng lưới nhà cung cấp, một cú siết thanh khoản của ngân hàng trung ương lan ra qua các khoản dư nợ liên ngân hàng, còn thông tin khuếch tán qua các thành viên hội đồng quản trị kiêm nhiệm và qua việc các tổ chức cùng nắm giữ cổ phiếu). Tuy nhiên, mô thức thực nghiệm chiếm ưu thế trong tài chính lại coi các doanh nghiệp là những quan sát độc lập được lập chỉ số theo $(i, t)$, chỉ liên hệ với nhau thông qua mức độ phơi nhiễm chung với các nhân tố chung. Chương này giới thiệu các công cụ để mô hình hóa, đo lường và khai thác cấu trúc mạng lưới mà các hồi quy dữ liệu bảng (panel) tiêu chuẩn bỏ qua.Hệ thống tài chính Việt Nam có mật độ mạng lưới đặc biệt cao. Sở hữu nhà nước tạo ra một mạng lưới các doanh nghiệp đan xen: cùng một bộ chủ quản có thể giám sát cả bên đi vay, bên cho vay và bên bảo hiểm. Các tập đoàn kim tự tháp (như Vingroup, Masan và FPT) liên kết hàng chục thực thể niêm yết và chưa niêm yết thông qua các chuỗi sở hữu vốn cổ phần. Hệ thống ngân hàng có quy mô nhỏ và tập trung, với một số ít ngân hàng thương mại nhà nước nắm giữ phần lớn tài sản, từ đó hình thành các khoản dư nợ liên ngân hàng dày đặc. Việc thành viên hội đồng quản trị kiêm nhiệm (chẳng hạn, những thành viên đồng thời tham gia nhiều hội đồng quản trị) rất phổ biến và thường đi theo các quan hệ sở hữu. Bản thân thị trường cổ phiếu cũng thể hiện các mẫu hình đồng biến động lợi nhuận mà khi được biểu diễn dưới dạng mạng lưới tương quan, sẽ bộc lộ các cụm theo ngành và theo hình thức sở hữu mà phân tích nhân tố tiêu chuẩn không nhìn thấy.Chương này trình bày toàn bộ các phương pháp mạng lưới được sử dụng trong kinh tế học tài chính, từ lý thuyết đồ thị cổ điển đến các mạng nơ-ron đồ thị (GNN) hiện đại. Chúng ta tổ chức nội dung thành tám phần. Thứ nhất, các nền tảng của lý thuyết đồ thị và cách biểu diễn dữ liệu tài chính dưới dạng mạng lưới. Thứ hai, các mạng lưới sở hữu và kiểm soát, là cấu trúc mạng lưới đặc trưng nhất của thị trường Việt Nam. Thứ ba, các mạng lưới thành viên hội đồng quản trị kiêm nhiệm và quản trị công ty. Thứ tư, các mạng lưới chuỗi cung ứng và thương mại. Thứ năm, các mạng lưới tương quan và đồng biến động phục vụ xây dựng danh mục đầu tư và giám sát rủi ro hệ thống. Thứ sáu, các mạng lưới liên ngân hàng và lây lan. Thứ bảy, các mạng lưới xã hội giữa các địa phương, được đo bằng Chỉ số Kết nối Xã hội (Social Connectedness Index), và vai trò của chúng như kênh truyền dẫn thông tin tài chính [@allen2026social]. Thứ tám, các mạng nơ-ron đồ thị và học máy dựa trên đồ thị cho định giá tài sản, rủi ro tín dụng và phát hiện bất thường.```{python}#| label: setup#| message: falseimport pandas as pdimport numpy as npfrom pathlib import Pathimport warningswarnings.filterwarnings("ignore")# Graph librariesimport networkx as nxfrom scipy import sparsefrom scipy.spatial.distance import squareform# Statistical and econometricfrom scipy import statsimport statsmodels.api as smfrom linearmodels.panel import PanelOLS# Visualizationimport plotnine as p9from mizani.formatters import percent_format, comma_formatimport matplotlib.pyplot as pltimport matplotlib.patches as mpatches# Deep learning (for GNNs later)import torchimport torch.nn as nnimport torch.nn.functional as F``````{python}#| eval: false# DataCore.vn APIfrom datacore import DataCoredc = DataCore()```## Nền tảng lý thuyết đồ thị cho tài chính### Biểu diễn dữ liệu tài chính dưới dạng đồ thịMột đồ thị $G = (V, E)$ gồm một tập đỉnh (nút) $V$ và một tập cạnh (liên kết) $E \subseteq V \times V$. Trong các mạng lưới tài chính, các nút đại diện cho các tác nhân kinh tế, như doanh nghiệp, ngân hàng, nhà đầu tư, thành viên hội đồng quản trị, còn các cạnh đại diện cho các mối quan hệ, bao gồm tỷ lệ sở hữu, cho vay, ghế trong hội đồng quản trị, hợp đồng cung ứng và đồng biến động lợi nhuận.Các đồ thị tài chính có nhiều dạng khác nhau:**Có hướng so với vô hướng.** Quan hệ sở hữu là có hướng: doanh nghiệp $A$ sở hữu một tỷ lệ cổ phần trong doanh nghiệp $B$, nhưng không nhất thiết có chiều ngược lại. Quan hệ kiêm nhiệm hội đồng quản trị là vô hướng: nếu thành viên $d$ ngồi trong hội đồng quản trị của cả doanh nghiệp $A$ và doanh nghiệp $B$, thì mối liên kết là đối xứng. Chuỗi cung ứng là có hướng: $A$ cung cấp cho $B$.**Có trọng số so với không trọng số.** Các mạng lưới sở hữu tự nhiên có trọng số theo tỷ lệ sở hữu. Các mạng lưới tương quan có trọng số theo hệ số tương quan từng cặp. Quan hệ kiêm nhiệm hội đồng quản trị có thể được gán trọng số theo số thành viên dùng chung.**Tĩnh so với động.** Hầu hết các mạng lưới tài chính tiến triển theo thời gian khi quan hệ sở hữu thay đổi, các thành viên hội đồng quản trị luân chuyển và các tương quan dịch chuyển. Một đồ thị theo thời gian $G_t = (V_t, E_t)$ nắm bắt sự tiến triển này.**Hai phía so với một phía.** Dữ liệu thô của nhiều mạng lưới tài chính là đồ thị hai phía (bipartite): thành viên hội đồng quản trị $\times$ doanh nghiệp, nhà đầu tư $\times$ cổ phiếu, ngân hàng $\times$ bên đi vay. Phép chiếu một phía (one-mode projection) chuyển đồ thị này thành đồ thị một phía: hai doanh nghiệp được nối với nhau nếu có chung một thành viên hội đồng quản trị, hai cổ phiếu được nối với nhau nếu có chung một nhà đầu tư tổ chức, v.v.### Các thước đo chính của đồ thịVới một đồ thị $G$ có $n = |V|$ nút và $m = |E|$ cạnh, chúng ta định nghĩa các thước đo sau, mỗi thước đo có một cách diễn giải tài chính riêng.**Độ trung tâm bậc (degree centrality).** Bậc $k_i$ của nút $i$ là số cạnh liên thuộc với $i$. Trong đồ thị có hướng, chúng ta phân biệt bậc vào $k_i^{\text{in}}$ (các cạnh hướng tới $i$) và bậc ra $k_i^{\text{out}}$ (các cạnh xuất phát từ $i$). Độ trung tâm bậc chuẩn hóa là:$$C_D(i) = \frac{k_i}{n - 1}$$ {#eq-degree-centrality}Trong một mạng lưới sở hữu, bậc ra cao có nghĩa là doanh nghiệp sở hữu cổ phần trong nhiều doanh nghiệp khác (tập đoàn đa ngành); bậc vào cao có nghĩa là nhiều thực thể sở hữu cổ phần trong doanh nghiệp đó (sở hữu phân tán).**Độ trung tâm trung gian (betweenness centrality).** Tỷ lệ các đường đi ngắn nhất giữa mọi cặp nút đi qua nút $i$:$$C_B(i) = \sum_{s \neq i \neq t} \frac{\sigma_{st}(i)}{\sigma_{st}}$$ {#eq-betweenness}trong đó $\sigma_{st}$ là tổng số đường đi ngắn nhất từ $s$ đến $t$ và $\sigma_{st}(i)$ là số đường đi đi qua $i$. Độ trung gian cao xác định các nút "cầu nối" (tức là các doanh nghiệp hoặc ngân hàng nối những bộ phận vốn tách rời của hệ thống tài chính). Sự sụp đổ của một ngân hàng có độ trung gian cao có thể làm phân mảnh mạng lưới liên ngân hàng.**Độ trung tâm vector riêng (eigenvector centrality).** Một nút là trung tâm nếu nó được nối với các nút trung tâm khác. Độ trung tâm vector riêng là nghiệm của:$$\lambda \mathbf{c} = A \mathbf{c}$$ {#eq-eigenvector-centrality}trong đó $A$ là ma trận kề và $\lambda$ là trị riêng lớn nhất. PageRank của Google là một biến thể được chính quy hóa, thiết kế cho đồ thị có hướng. Trong các mạng lưới tài chính, độ trung tâm vector riêng xác định các tổ chức quan trọng về mặt hệ thống (tức là những tổ chức được nối với các tổ chức quan trọng khác).**Hệ số phân cụm (clustering coefficient).** Tỷ lệ các nút lân cận của một nút mà bản thân chúng cũng là lân cận của nhau:$$C_C(i) = \frac{2 T_i}{k_i(k_i - 1)}$$ {#eq-clustering}trong đó $T_i$ là số tam giác chứa nút $i$. Mức phân cụm cao cho thấy các nhóm gắn kết chặt chẽ (chẳng hạn, các tập đoàn, các vòng cho vay, các danh mục cùng đầu tư).**Cấu trúc cộng đồng.** Nhiều mạng lưới tài chính có cấu trúc mô-đun: các nhóm nút kết nối dày đặc với nhau nhưng liên kết thưa giữa các nhóm. Các thuật toán phát hiện cộng đồng (Louvain, lan truyền nhãn, phân cụm phổ) xác định các mô-đun này, mà trong các mạng lưới tài chính thường tương ứng với các tập đoàn, các ngành hoặc các cụm cho vay.```{python}#| label: graph-fundamentals#| eval: falsedef compute_network_metrics(G):""" Compute standard network metrics for a graph. Parameters ---------- G : nx.Graph or nx.DiGraph Input graph. Returns ------- dict : Node-level and graph-level metrics. """ is_directed = G.is_directed()# Node-level metricsif is_directed: in_degree =dict(G.in_degree()) out_degree =dict(G.out_degree()) degree = {n: in_degree[n] + out_degree[n] for n in G.nodes()}else: degree =dict(G.degree()) betweenness = nx.betweenness_centrality(G, weight="weight") eigenvector = nx.eigenvector_centrality_numpy( G, weight="weight" ) iflen(G) >0else {} clustering = nx.clustering(G, weight="weight") ifnot is_directed else {}# PageRank (works for both directed and undirected) pagerank = nx.pagerank(G, weight="weight")# Graph-level metrics n_nodes = G.number_of_nodes() n_edges = G.number_of_edges() density = nx.density(G)# Connected componentsif is_directed: n_weak_components = nx.number_weakly_connected_components(G) n_strong_components = nx.number_strongly_connected_components(G)else: n_components = nx.number_connected_components(G)# Degree distribution statistics degrees =list(degree.values()) avg_degree = np.mean(degrees) if degrees else0 max_degree =max(degrees) if degrees else0# Assortativity assortativity = nx.degree_assortativity_coefficient(G)# Community detection (Louvain)ifnot is_directed:try: communities = nx.community.louvain_communities(G) modularity = nx.community.modularity(G, communities) n_communities =len(communities)exceptException: modularity, n_communities = np.nan, 0else: modularity, n_communities = np.nan, 0 node_metrics = pd.DataFrame({"degree": degree,"betweenness": betweenness,"eigenvector": eigenvector,"pagerank": pagerank,"clustering": clustering if clustering else {n: np.nan for n in G.nodes()} }) graph_metrics = {"n_nodes": n_nodes,"n_edges": n_edges,"density": density,"avg_degree": avg_degree,"max_degree": max_degree,"assortativity": assortativity,"modularity": modularity,"n_communities": n_communities }return node_metrics, graph_metrics```### Ma trận kề và các tính chất phổMa trận kề $A \in \mathbb{R}^{n \times n}$ mã hóa cấu trúc đồ thị: $A_{ij} = w_{ij}$ nếu có một cạnh từ $i$ đến $j$ với trọng số $w_{ij}$, và $A_{ij} = 0$ trong trường hợp ngược lại. Với đồ thị vô hướng, $A$ là ma trận đối xứng.Ma trận Laplace của đồ thị $L = D - A$ (trong đó $D$ là ma trận bậc đường chéo) có các trị riêng $0 = \lambda_1 \leq \lambda_2 \leq \ldots \leq \lambda_n$ với những cách diễn giải cấu trúc quan trọng:- Bội của $\lambda = 0$ bằng số thành phần liên thông.- Trị riêng thứ hai $\lambda_2$ (độ liên thông đại số hay giá trị Fiedler) đo mức độ liên kết tốt của đồ thị. $\lambda_2$ thấp hàm ý đồ thị có một nút thắt cổ chai, tức là một điểm yếu mà việc cắt một vài cạnh sẽ làm đứt rời những phần lớn của đồ thị.- Các vector riêng của $L$ cung cấp phép nhúng phổ (spectral embedding) của đồ thị, là nền tảng cho phân cụm phổ và các mạng tích chập đồ thị.Ma trận Laplace chuẩn hóa $\tilde{L} = D^{-1/2} L D^{-1/2}$ được dùng trong GCN vì nó ổn định việc truyền thông điệp giữa các nút có bậc khác nhau.```{python}#| label: spectral-analysis#| eval: falsedef spectral_graph_analysis(A, n_components=10):""" Compute spectral properties of a financial network. Parameters ---------- A : np.ndarray or sparse matrix Adjacency matrix. n_components : int Number of eigenvalues/vectors to compute. Returns ------- dict : Eigenvalues, algebraic connectivity, spectral gap. """ n = A.shape[0]if sparse.issparse(A): A_dense = A.toarray()else: A_dense = A# Degree matrix D = np.diag(A_dense.sum(axis=1))# Graph Laplacian L = D - A_dense# Normalized Laplacian D_inv_sqrt = np.diag(1.0/ np.sqrt(np.diag(D) +1e-10)) L_norm = D_inv_sqrt @ L @ D_inv_sqrt# Eigendecomposition (smallest eigenvalues) eigenvalues, eigenvectors = np.linalg.eigh(L_norm)# Sort by eigenvalue idx = np.argsort(eigenvalues) eigenvalues = eigenvalues[idx] eigenvectors = eigenvectors[:, idx]# Algebraic connectivity (Fiedler value) fiedler_value = eigenvalues[1] if n >1else0 fiedler_vector = eigenvectors[:, 1] if n >1else np.zeros(n)# Spectral gap spectral_gap = eigenvalues[1] - eigenvalues[0] if n >1else0return {"eigenvalues": eigenvalues[:n_components],"fiedler_value": fiedler_value,"fiedler_vector": fiedler_vector,"spectral_gap": spectral_gap,"spectral_embedding": eigenvectors[:, 1:n_components +1] }```## Mạng lưới sở hữu và kiểm soát### Cấu trúc sở hữu ở Việt NamCó thể nói các mạng lưới sở hữu là cấu trúc đồ thị có hệ quả kinh tế lớn nhất trong thị trường Việt Nam. Bức tranh doanh nghiệp Việt Nam được đặc trưng bởi ba nét riêng tạo ra các cấu trúc sở hữu phức tạp:**State ownership pyramids.** The government holds equity in hundreds of firms through a hierarchy of holding entities: the State Capital Investment Corporation (SCIC), line ministries, provincial People's Committees, and state-owned economic groups (tập đoàn kinh tế nhà nước). These chains create multi-layered pyramids where the state's ultimate control rights may substantially exceed its cash flow rights.**Private business groups.** Vietnamese conglomerates (Vingroup, Masan, FPT, Hoà Phát, Thaco) create complex webs of cross-ownership, subsidiary relationships, and associate stakes. These structures serve multiple purposes: internal capital markets, tax optimization, regulatory arbitrage, and control enhancement.**Sở hữu vòng tròn và sở hữu chéo.** Các quy định của Việt Nam không ngăn chặn hiệu quả tình trạng sở hữu vòng tròn (doanh nghiệp $A$ sở hữu doanh nghiệp $B$, doanh nghiệp này sở hữu doanh nghiệp $C$, và doanh nghiệp sau cùng này lại sở hữu doanh nghiệp $A$), tạo ra các vòng lặp trong đồ thị sở hữu làm khuếch đại quyền kiểm soát vượt quá tỷ lệ sở hữu trực tiếp và làm phình vốn chủ sở hữu kế toán [@bebchuk1999rent].```{python}#| label: ownership-network-data#| eval: false# Load ownership dataownership = dc.get_ownership_network( date="2024-06-30", min_stake=1.0# Minimum 1% ownership stake)# Load firm characteristicsfirms = dc.get_firm_characteristics( start_date="2024-01-01", end_date="2024-06-30").groupby("ticker").last().reset_index()print(f"Ownership edges: {len(ownership)}")print(f"Unique owners: {ownership['owner_id'].nunique()}")print(f"Unique targets: {ownership['target_ticker'].nunique()}")``````{python}#| label: build-ownership-graph#| eval: false# Build directed ownership graphG_own = nx.DiGraph()# Add firm nodes with attributesfor _, row in firms.iterrows(): G_own.add_node( row["ticker"], node_type="firm", market_cap=row.get("market_cap", 0), industry=row.get("industry", "Unknown"), is_soe=row.get("state_ownership_pct", 0) >50 )# Add ownership edgesfor _, row in ownership.iterrows(): owner = row["owner_id"] target = row["target_ticker"] stake = row["ownership_pct"]# Add owner node if not presentif owner notin G_own: G_own.add_node( owner, node_type=row.get("owner_type", "entity"), market_cap=0, industry="Holding" ) G_own.add_edge(owner, target, weight=stake /100)node_metrics_own, graph_metrics_own = compute_network_metrics(G_own)print(f"\nOwnership Network Summary:")for k, v in graph_metrics_own.items():print(f" {k}: {v}")``````{python}#| label: tbl-ownership-network-stats#| eval: false#| tbl-cap: "Mạng lưới sở hữu: Thống kê cấp độ đồ thị"own_stats = pd.DataFrame([graph_metrics_own]).Town_stats.columns = ["Value"]own_stats = own_stats.round(4)own_stats```### Sở hữu cuối cùng và các chuỗi kiểm soátSở hữu trực tiếp đánh giá thấp quyền kiểm soát thực sự được thực thi qua các chuỗi kim tự tháp. Tỷ lệ sở hữu cuối cùng của thực thể $A$ trong doanh nghiệp $Z$ qua chuỗi $A \to B \to C \to Z$ là tích của các tỷ lệ sở hữu trung gian:$$\omega_{A \to Z}^{\text{ultimate}} = \prod_{(i,j) \in \text{path}(A, Z)} w_{ij}$$ {#eq-ultimate-ownership}trong đó $w_{ij}$ là tỷ lệ sở hữu trực tiếp của $i$ trong $j$. Khi tồn tại nhiều đường đi từ $A$ đến $Z$, tổng sở hữu cuối cùng là tổng trên các đường đi. Tuy nhiên, quyền kiểm soát được quyết định bởi mắt xích yếu nhất trong chuỗi (tỷ lệ sở hữu nhỏ nhất dọc theo đường đi), phản ánh nguyên tắc rằng kiểm soát đòi hỏi đa số ở mỗi tầng.```{python}#| label: ultimate-ownership#| eval: falsedef compute_ultimate_ownership(G, source, target, max_depth=10):""" Compute ultimate ownership of source in target through all paths. Parameters ---------- G : nx.DiGraph Ownership graph with edge weights as ownership fractions. source : str Ultimate owner node. target : str Target firm node. max_depth : int Maximum chain length to consider. Returns ------- dict : Total ownership, control rights, number of paths. """if source notin G or target notin G:return {"ownership": 0, "control": 0, "n_paths": 0} total_ownership =0 max_control =0 n_paths =0# Find all simple paths from source to targettry:for path in nx.all_simple_paths(G, source, target, cutoff=max_depth):# Cash flow rights: product of stakes ownership =1.0 min_stake =1.0for i inrange(len(path) -1): edge_data = G[path[i]][path[i +1]] stake = edge_data.get("weight", 0) ownership *= stake min_stake =min(min_stake, stake) total_ownership += ownership max_control =max(max_control, min_stake) n_paths +=1except nx.NetworkXNoPath:passreturn {"ownership": total_ownership,"control": max_control,"n_paths": n_paths }def compute_control_wedge(G, firms_list, max_depth=5):""" Compute the wedge between control and cash flow rights for the largest shareholder of each firm. The wedge = control rights - cash flow rights. Positive wedge → potential for tunneling. """ wedge_data = []for firm in firms_list:if firm notin G:continue# Find all direct owners predecessors =list(G.predecessors(firm))ifnot predecessors:continue# Find largest direct owner stakes = {p: G[p][firm]["weight"] for p in predecessors} largest_owner =max(stakes, key=stakes.get) direct_stake = stakes[largest_owner]# Compute ultimate ownership through all paths ultimate = compute_ultimate_ownership( G, largest_owner, firm, max_depth ) wedge_data.append({"ticker": firm,"largest_owner": largest_owner,"direct_stake": direct_stake,"ultimate_ownership": ultimate["ownership"],"control_rights": ultimate["control"],"n_ownership_paths": ultimate["n_paths"],"wedge": ultimate["control"] - ultimate["ownership"] })return pd.DataFrame(wedge_data)# Compute for all listed firmslisted_firms = [n for n, d in G_own.nodes(data=True)if d.get("node_type") =="firm"]wedge_df = compute_control_wedge(G_own, listed_firms)``````{python}#| label: tbl-control-wedge#| eval: false#| tbl-cap: "Khoảng cách giữa quyền kiểm soát và quyền dòng tiền: Thống kê tóm tắt"wedge_summary = wedge_df[ ["direct_stake", "ultimate_ownership", "control_rights","n_ownership_paths", "wedge"]].describe(percentiles=[0.1, 0.25, 0.5, 0.75, 0.9]).T.round(4)wedge_summary``````{python}#| label: fig-control-wedge#| eval: false#| fig-cap: "Phân phối khoảng cách giữa quyền kiểm soát và quyền dòng tiền trên các doanh nghiệp niêm yết Việt Nam"( p9.ggplot(wedge_df, p9.aes(x="wedge"))+ p9.geom_histogram(bins=50, fill="#2E5090", alpha=0.7)+ p9.geom_vline(xintercept=0, linetype="dashed", color="#C0392B")+ p9.labs( x="Control Wedge (Control Rights − Cash Flow Rights)", y="Count", title="Control-Ownership Wedge: Positive Values Indicate Tunneling Risk" )+ p9.theme_minimal()+ p9.theme(figure_size=(10, 5)))```### Độ trung tâm sở hữu và giá trị doanh nghiệpChúng ta kiểm định xem vị trí của một doanh nghiệp trong mạng lưới sở hữu có dự báo được định giá thị trường của doanh nghiệp đó hay không, sau khi kiểm soát các yếu tố quyết định tiêu chuẩn:$$Q_{i,t} = \beta_0 + \beta_1 \text{Centrality}_{i,t} + \beta_2 \text{Wedge}_{i,t} + \boldsymbol{\gamma}' \mathbf{X}_{i,t} + \alpha_{\text{ind}} + \delta_t + \varepsilon_{i,t}$$ {#eq-network-valuation}```{python}#| label: ownership-valuation#| eval: false# Merge network metrics with firm characteristicsnode_metrics_own_df = node_metrics_own.reset_index().rename( columns={"index": "ticker"})valuation_data = firms.merge(node_metrics_own_df, on="ticker", how="inner")valuation_data = valuation_data.merge( wedge_df[["ticker", "wedge", "n_ownership_paths"]], on="ticker", how="left")# Panel regressionval_clean = valuation_data.dropna( subset=["tobins_q", "eigenvector", "wedge","log_size", "profitability", "leverage"])iflen(val_clean) >50: model_network_val = sm.OLS( val_clean["tobins_q"], sm.add_constant(val_clean[["eigenvector", "betweenness", "wedge","log_size", "profitability", "leverage" ]]) ).fit(cov_type="HC1")print("Ownership Network Position and Firm Value:")for var in ["eigenvector", "betweenness", "wedge"]:print(f" {var}: {model_network_val.params[var]:.4f} "f"(t = {model_network_val.tvalues[var]:.3f})")```### Phát hiện tập đoànCác tập đoàn (tức là tập hợp các doanh nghiệp độc lập về mặt pháp lý nhưng liên kết với nhau bằng quan hệ sở hữu chung) là một đặc trưng nổi bật của cấu trúc doanh nghiệp Việt Nam. Chúng ta phát hiện chúng bằng thuật toán, sử dụng phương pháp phát hiện cộng đồng trên đồ thị sở hữu.```{python}#| label: business-groups#| eval: falsedef detect_business_groups(G, min_group_size=3, ownership_threshold=0.10):""" Detect business groups from ownership network. A business group is a connected component in the undirected projection of the ownership graph, filtered for economically meaningful ownership stakes. Parameters ---------- G : nx.DiGraph Directed ownership graph. min_group_size : int Minimum firms in a group. ownership_threshold : float Minimum ownership stake to count as a link. Returns ------- DataFrame : Group assignments with apex firm. """# Filter edges by threshold G_filtered = nx.DiGraph()for u, v, d in G.edges(data=True):if d.get("weight", 0) >= ownership_threshold: G_filtered.add_edge(u, v, **d)# Convert to undirected for component detection G_undirected = G_filtered.to_undirected()# Find connected components components =list(nx.connected_components(G_undirected))# Filter by size groups = [c for c in components iflen(c) >= min_group_size]# For each group, identify the apex (top of pyramid) group_data = []for group_id, members inenumerate(groups): subgraph = G_filtered.subgraph(members)# Apex: node with highest out-degree and lowest in-degree# (controls others but is not controlled) scores = {}for node in members: in_deg = subgraph.in_degree(node) out_deg = subgraph.out_degree(node) scores[node] = out_deg - in_deg apex =max(scores, key=scores.get) if scores elselist(members)[0]for member in members: node_data = G.nodes.get(member, {}) group_data.append({"ticker": member,"group_id": group_id,"group_size": len(members),"apex": apex,"is_apex": member == apex,"node_type": node_data.get("node_type", "unknown"),"industry": node_data.get("industry", "Unknown"),"market_cap": node_data.get("market_cap", 0) })return pd.DataFrame(group_data)groups_df = detect_business_groups(G_own)print(f"Business groups detected: {groups_df['group_id'].nunique()}")print(f"Firms in groups: {len(groups_df[groups_df['node_type'] =='firm'])}")``````{python}#| label: tbl-business-groups#| eval: false#| tbl-cap: "Các tập đoàn lớn nhất của Việt Nam theo phân tích mạng lưới sở hữu"top_groups = ( groups_df.groupby("group_id") .agg( apex=("apex", "first"), n_members=("ticker", "count"), n_listed=("node_type", lambda x: (x =="firm").sum()), total_mcap=("market_cap", "sum"), industries=("industry", lambda x: x.nunique()) ) .sort_values("total_mcap", ascending=False) .head(15) .reset_index())top_groups["total_mcap_bn"] = top_groups["total_mcap"] /1e9top_groups[["apex", "n_members", "n_listed", "total_mcap_bn", "industries"]]```## Mạng lưới thành viên hội đồng quản trị kiêm nhiệm### Xây dựng mạng lướiMột quan hệ kiêm nhiệm hội đồng quản trị tồn tại khi một thành viên đồng thời giữ ghế trong hội đồng quản trị của hai hay nhiều doanh nghiệp. Mạng lưới kiêm nhiệm hội đồng quản trị là phép chiếu một phía của đồ thị hai phía thành viên$\times$doanh nghiệp: hai doanh nghiệp được nối với nhau nếu có chung ít nhất một thành viên, với trọng số là số thành viên dùng chung.Quan hệ kiêm nhiệm là một kênh thông tin. @bizjak2008does chỉ ra rằng các thông lệ trả thưởng lan truyền qua mạng lưới hội đồng quản trị. @cai2014board chứng minh rằng các doanh nghiệp được nối với nhau qua quan hệ kiêm nhiệm có chính sách đầu tư tương quan với nhau. Trong thị trường Việt Nam, quan hệ kiêm nhiệm thường đi theo các quan hệ sở hữu (chẳng hạn, công ty mẹ bổ nhiệm thành viên vào hội đồng quản trị của công ty con), tạo ra một kênh quản trị củng cố kênh sở hữu.```{python}#| label: board-interlock-data#| eval: false# Load board membership databoard_data = dc.get_board_memberships( date="2024-06-30")print(f"Director-firm pairs: {len(board_data)}")print(f"Unique directors: {board_data['director_id'].nunique()}")print(f"Unique firms: {board_data['ticker'].nunique()}")# Build bipartite graphB = nx.Graph()for _, row in board_data.iterrows(): B.add_node(row["director_id"], bipartite=0) B.add_node(row["ticker"], bipartite=1) B.add_edge( row["director_id"], row["ticker"], role=row.get("role", "director"), is_independent=row.get("is_independent", False) )# Project to firm-firm interlock networkfirm_nodes = [n for n, d in B.nodes(data=True) if d.get("bipartite") ==1]G_interlock = nx.bipartite.weighted_projected_graph(B, firm_nodes)# Edge weight = number of shared directorsinterlock_metrics, interlock_graph = compute_network_metrics(G_interlock)print(f"\nBoard Interlock Network:")print(f" Firms: {G_interlock.number_of_nodes()}")print(f" Interlocking pairs: {G_interlock.number_of_edges()}")print(f" Density: {nx.density(G_interlock):.4f}")``````{python}#| label: interlock-vs-ownership#| eval: false# Do board interlocks follow ownership lines?# Compare interlock edges with ownership edgesinterlock_edges =set(G_interlock.edges())ownership_edges_undirected =set()for u, v in G_own.edges():if u in firm_nodes and v in firm_nodes: ownership_edges_undirected.add((min(u, v), max(u, v)))interlock_edges_normalized = { (min(u, v), max(u, v)) for u, v in interlock_edges}overlap = interlock_edges_normalized & ownership_edges_undirectedn_interlock =len(interlock_edges_normalized)n_ownership =len(ownership_edges_undirected)n_overlap =len(overlap)print(f"Interlock edges: {n_interlock}")print(f"Ownership edges (firm-firm): {n_ownership}")print(f"Overlap: {n_overlap}")if n_interlock >0:print(f"Fraction of interlocks with ownership link: "f"{n_overlap / n_interlock:.3f}")```### Kiêm nhiệm và đồng biến động lợi nhuậnNếu quan hệ kiêm nhiệm đóng vai trò kênh thông tin, các doanh nghiệp được nối với nhau qua quan hệ kiêm nhiệm sẽ thể hiện mức đồng biến động lợi nhuận vượt mức, vượt quá phần được giải thích bởi đặc điểm ngành hoặc quy mô chung. Chúng ta kiểm định điều này bằng phương pháp của @cohen2008economic:$$\rho_{ij,t} = \alpha + \beta \cdot \text{Interlock}_{ij,t} + \gamma \cdot \text{SameIndustry}_{ij} + \delta \cdot \text{SizeProximity}_{ij,t} + \varepsilon_{ij,t}$$ {#eq-interlock-comovement}```{python}#| label: interlock-comovement#| eval: false# Compute pairwise return correlations for interlocked and non-interlocked pairsmonthly_returns = dc.get_monthly_returns( start_date="2023-01-01", end_date="2024-06-30")# Pivot to wide formatreturns_wide = monthly_returns.pivot( index="date", columns="ticker", values="ret").dropna(axis=1, thresh=12)# Correlation matrixcorr_matrix = returns_wide.corr()# Compare correlations: interlocked vs non-interlockedinterlock_corrs = []non_interlock_corrs = []tickers_in_both =set(corr_matrix.columns) &set(G_interlock.nodes())for i, ticker_i inenumerate(tickers_in_both):for ticker_j inlist(tickers_in_both)[i +1:]: corr = corr_matrix.loc[ticker_i, ticker_j]if np.isnan(corr):continueif G_interlock.has_edge(ticker_i, ticker_j): interlock_corrs.append(corr)else: non_interlock_corrs.append(corr)if interlock_corrs and non_interlock_corrs: t_stat, p_val = stats.ttest_ind(interlock_corrs, non_interlock_corrs)print(f"Interlocked pairs: n={len(interlock_corrs)}, "f"mean corr={np.mean(interlock_corrs):.4f}")print(f"Non-interlocked: n={len(non_interlock_corrs)}, "f"mean corr={np.mean(non_interlock_corrs):.4f}")print(f"Difference t-stat: {t_stat:.3f}, p-value: {p_val:.4f}")```## Mạng lưới chuỗi cung ứng và thương mại### Xây dựng đồ thị chuỗi cung ứngCác quan hệ chuỗi cung ứng (liên kết khách hàng–nhà cung cấp) thể hiện những mối liên hệ kinh tế thực mà qua đó các cú sốc lan truyền. @acemoglu2015systemic chứng minh về mặt lý thuyết rằng khi tồn tại các liên kết chuỗi cung ứng, các cú sốc đặc thù của từng doanh nghiệp có thể tạo ra biến động tổng thể thay vì triệt tiêu lẫn nhau. @cohen2008economic và @menzly2010market cho thấy các liên kết chuỗi cung ứng dự báo được chênh lệch lợi nhuận chéo (cross-sectional): khi cổ phiếu của một khách hàng lớn giảm giá, cổ phiếu của các nhà cung cấp giảm theo với độ trễ.```{python}#| label: supply-chain-data#| eval: false# Load supply chain datasupply_chain = dc.get_supply_chain_data( start_date="2020-01-01", end_date="2024-12-31")# Build directed supply chain graphG_supply = nx.DiGraph()for _, row in supply_chain.iterrows(): G_supply.add_edge( row["supplier_ticker"], row["customer_ticker"], weight=row.get("transaction_value", 1), pct_of_supplier_revenue=row.get("pct_supplier_revenue", 0), pct_of_customer_cogs=row.get("pct_customer_cogs", 0), year=row.get("year", 2024) )print(f"Supply chain links: {G_supply.number_of_edges()}")print(f"Firms involved: {G_supply.number_of_nodes()}")```### Động lượng khách hàngChiến lược "động lượng khách hàng" của @cohen2008economic khai thác sự lan truyền chậm của thông tin qua các chuỗi cung ứng. Khi cổ phiếu của một doanh nghiệp khách hàng tăng giá, cổ phiếu của các nhà cung cấp có xu hướng tăng theo trong những tháng tiếp theo:$$r_{i,t+1} = \alpha + \beta \cdot \text{CustomerReturn}_{i,t} + \gamma \cdot r_{i,t} + \boldsymbol{\delta}' \mathbf{X}_{i,t} + \varepsilon_{i,t}$$ {#eq-customer-momentum}trong đó $\text{CustomerReturn}_{i,t} = \sum_{j \in \text{Customers}(i)} w_{ij} \cdot r_{j,t}$ là lợi nhuận bình quân gia quyền của các khách hàng của doanh nghiệp $i$.```{python}#| label: customer-momentum#| eval: false# Compute customer-weighted returns for each supplierdef compute_customer_returns(supply_graph, monthly_returns):""" Compute customer-weighted returns for each supplier firm. """# For each supplier, compute weighted average customer return results = []for date in monthly_returns["date"].unique(): date_returns = monthly_returns[monthly_returns["date"] == date] ret_dict =dict(zip(date_returns["ticker"], date_returns["ret"]))for supplier in supply_graph.nodes(): customers =list(supply_graph.successors(supplier))ifnot customers:continue customer_rets = [] customer_weights = []for cust in customers:if cust in ret_dict: edge_data = supply_graph[supplier][cust] weight = edge_data.get("pct_of_supplier_revenue", 1) customer_rets.append(ret_dict[cust]) customer_weights.append(weight)if customer_rets: weights = np.array(customer_weights)if weights.sum() >0: weights = weights / weights.sum()else: weights = np.ones(len(weights)) /len(weights) weighted_ret = np.average(customer_rets, weights=weights) results.append({"date": date,"ticker": supplier,"customer_ret": weighted_ret,"n_customers": len(customer_rets) })return pd.DataFrame(results)customer_rets = compute_customer_returns(G_supply, monthly_returns)# Merge with own returns and lagmomentum_data = monthly_returns.merge( customer_rets, on=["ticker", "date"], how="inner")momentum_data = momentum_data.sort_values(["ticker", "date"])momentum_data["own_ret_lag"] = momentum_data.groupby("ticker")["ret"].shift(1)momentum_data["customer_ret_lag"] = momentum_data.groupby("ticker")["customer_ret"].shift(1)momentum_data["ret_lead"] = momentum_data.groupby("ticker")["ret"].shift(-1)# Regressionmom_clean = momentum_data.dropna( subset=["ret_lead", "customer_ret_lag", "own_ret_lag"])model_mom = sm.OLS( mom_clean["ret_lead"], sm.add_constant(mom_clean[["customer_ret_lag", "own_ret_lag"]])).fit(cov_type="cluster", cov_kwds={"groups": mom_clean["ticker"]})``````{python}#| label: tbl-customer-momentum#| eval: false#| tbl-cap: "Động lượng khách hàng: Lợi nhuận của nhà cung cấp được dự báo bởi lợi nhuận của khách hàng"mom_results = pd.DataFrame({"Coefficient": model_mom.params.round(4),"Std Error": model_mom.bse.round(4),"t-stat": model_mom.tvalues.round(3),"p-value": model_mom.pvalues.round(4)})mom_results```### Lan truyền trong mạng lưới: Khuếch tán cú sốc qua các chuỗi cung ứngMô hình của @acemoglu2015systemic dự báo rằng tác động tổng thể của một cú sốc đặc thù phụ thuộc vào cấu trúc của mạng lưới. Trong mạng hình sao (một trung tâm duy nhất), các cú sốc tại trung tâm tạo ra biến động tổng thể. Trong mạng đối xứng, các cú sốc triệt tiêu lẫn nhau. Chúng ta triển khai một phương pháp dựa trên mô phỏng để đo lường sự lan truyền cú sốc trong chuỗi cung ứng của Việt Nam.```{python}#| label: shock-propagation#| eval: falsedef simulate_shock_propagation(G, shocked_node, shock_size=-0.10, decay=0.5, max_steps=5):""" Simulate the propagation of an idiosyncratic shock through a supply chain network. Parameters ---------- G : nx.DiGraph Supply chain graph (supplier → customer). shocked_node : str Node receiving the initial shock. shock_size : float Initial shock magnitude (e.g., -0.10 = -10% revenue shock). decay : float Fraction of shock transmitted per link (0 < decay < 1). max_steps : int Maximum propagation steps. Returns ------- dict : {node: cumulative shock received}. """ shocks = {shocked_node: shock_size} frontier = {shocked_node}for step inrange(max_steps): new_frontier =set()for node in frontier:# Propagate to customers (downstream)for customer in G.successors(node): edge_weight = G[node][customer].get("pct_of_customer_cogs", 0.1 ) transmitted = shocks[node] * decay * edge_weightifabs(transmitted) >0.001: shocks[customer] = shocks.get(customer, 0) + transmitted new_frontier.add(customer)# Propagate to suppliers (upstream)for supplier in G.predecessors(node): edge_weight = G[supplier][node].get("pct_of_supplier_revenue", 0.1 ) transmitted = shocks[node] * decay * edge_weightifabs(transmitted) >0.001: shocks[supplier] = shocks.get(supplier, 0) + transmitted new_frontier.add(supplier) frontier = new_frontierifnot frontier:breakreturn shocks# Identify systemically important supply chain nodes# (Those whose shock has largest aggregate impact)systemic_importance = {}listed_in_supply = [n for n in G_supply.nodes()if n inset(firms["ticker"])]for firm in listed_in_supply[:100]: # Sample for computational feasibility shocks = simulate_shock_propagation(G_supply, firm, -0.10) aggregate_impact =sum(abs(v) for k, v in shocks.items() if k != firm) systemic_importance[firm] = aggregate_impactsystemic_df = pd.DataFrame(list(systemic_importance.items()), columns=["ticker", "systemic_impact"]).sort_values("systemic_impact", ascending=False)``````{python}#| label: tbl-systemic-supply-chain#| eval: false#| tbl-cap: "Các doanh nghiệp quan trọng nhất về mặt hệ thống trong mạng lưới chuỗi cung ứng"top_systemic = systemic_df.head(20).merge( firms[["ticker", "industry", "market_cap"]], on="ticker", how="left")top_systemic["market_cap_bn"] = top_systemic["market_cap"] /1e9top_systemic[["ticker", "industry", "market_cap_bn", "systemic_impact"]].round(4)```## Mạng lưới tương quan và đồng biến động### Xây dựng từ dữ liệu lợi nhuậnMạng lưới tương quan nối các tài sản có lợi nhuận đồng biến động. Có lẽ đây là mạng lưới tài chính được sử dụng rộng rãi nhất vì nó chỉ cần dữ liệu lợi nhuận---không cần thông tin sở hữu hay chuỗi cung ứng độc quyền. Cách xây dựng tiêu chuẩn gồm ba bước:1. **Tính các tương quan từng cặp.** Với $n$ tài sản trong $T$ kỳ, ma trận tương quan mẫu $\hat{\rho} \in \mathbb{R}^{n \times n}$ có $n(n-1)/2$ phần tử riêng biệt.2. **Đặt ngưỡng hoặc biến đổi.** Chuyển ma trận tương quan dày thành một đồ thị thưa. Các cách tiếp cận phổ biến: ngưỡng cứng ($\rho_{ij} > \bar{\rho}$), cây khung nhỏ nhất (Minimum Spanning Tree, MST), hoặc đồ thị lọc cực đại phẳng (Planar Maximally Filtered Graph, PMFG).3. **Phân tích đồ thị.** Phát hiện cộng đồng cho thấy các cụm theo ngành; độ trung tâm xác định các cổ phiếu dẫn dắt thị trường; sự tiến triển theo thời gian theo dõi các thay đổi chế độ.```{python}#| label: correlation-network#| eval: false# Compute correlation matrix from daily returnsdaily_returns = dc.get_daily_returns( start_date="2023-01-01", end_date="2024-06-30")daily_wide = daily_returns.pivot( index="date", columns="ticker", values="ret").dropna(axis=1, thresh=200)corr = daily_wide.corr()# Minimum Spanning Tree (MST)# Convert correlation to distance: d = sqrt(2(1-ρ))dist = np.sqrt(2* (1- corr.values))np.fill_diagonal(dist, 0)# Build complete graph with distancesG_complete = nx.Graph()tickers =list(corr.columns)for i inrange(len(tickers)):for j inrange(i +1, len(tickers)): G_complete.add_edge( tickers[i], tickers[j], weight=dist[i, j], correlation=corr.iloc[i, j] )# MSTG_mst = nx.minimum_spanning_tree(G_complete, weight="weight")# Thresholded correlation networkthreshold =0.5G_corr = nx.Graph()for i inrange(len(tickers)):for j inrange(i +1, len(tickers)):if corr.iloc[i, j] > threshold: G_corr.add_edge( tickers[i], tickers[j], weight=corr.iloc[i, j] )print(f"MST: {G_mst.number_of_nodes()} nodes, "f"{G_mst.number_of_edges()} edges")print(f"Corr network (ρ > {threshold}): "f"{G_corr.number_of_nodes()} nodes, "f"{G_corr.number_of_edges()} edges")```### Khung kết nối Diebold–Yilmaz@diebold2014network đề xuất đo lường mức độ kết nối tài chính bằng phân rã phương sai từ mô hình tự hồi quy vector (VAR). Tỷ lệ phương sai sai số dự báo trước $H$ bước của biến $i$ do các cú sốc từ biến $j$ gây ra xác định mức kết nối có hướng theo từng cặp:$$C_{i \leftarrow j}(H) = \frac{\tilde{\theta}_{ij}^g(H)}{\sum_{j=1}^{n} \tilde{\theta}_{ij}^g(H)} \times 100$$ {#eq-dy-connectedness}trong đó $\tilde{\theta}_{ij}^g(H)$ là phân rã phương sai sai số dự báo tổng quát. Chỉ số kết nối tổng (total connectedness index, TCI) tổng hợp trên mọi cặp:$$\text{TCI}(H) = \frac{\sum_{i \neq j} C_{i \leftarrow j}(H)}{\sum_{i,j} C_{i \leftarrow j}(H)} \times 100$$ {#eq-tci}TCI cao cho thấy một hệ thống kết nối chặt chẽ, nơi các cú sốc lan truyền rộng; TCI thấp cho thấy mức độ cô lập tương đối.```{python}#| label: diebold-yilmaz#| eval: falsedef diebold_yilmaz_connectedness(returns_df, var_order=2, forecast_horizon=10):""" Compute the Diebold-Yilmaz (2014) connectedness table. Parameters ---------- returns_df : DataFrame Returns with columns as assets, index as dates. var_order : int VAR lag order. forecast_horizon : int Forecast horizon for variance decomposition. Returns ------- dict : Connectedness matrix, TCI, TO/FROM measures. """from statsmodels.tsa.api import VAR# Fit VAR model = VAR(returns_df.dropna()) results = model.fit(var_order)# Generalized Forecast Error Variance Decomposition fevd = results.fevd(forecast_horizon)# Extract decomposition matrix at horizon H n = returns_df.shape[1] C = np.zeros((n, n))for i inrange(n): decomp = fevd.decomp[i] # H x n array C[i, :] = decomp[-1, :] # Take horizon H values# Normalize rows to sum to 100 row_sums = C.sum(axis=1, keepdims=True) C_norm = C / row_sums *100# Connectedness measures# FROM: how much of i's variance comes from others FROM = C_norm.sum(axis=1) - np.diag(C_norm)# TO: how much of others' variance comes from i TO = C_norm.sum(axis=0) - np.diag(C_norm)# NET = TO - FROM NET = TO - FROM# Total Connectedness Index TCI = FROM.sum() / n names = returns_df.columns.tolist() connectedness_df = pd.DataFrame(C_norm, index=names, columns=names) connectedness_df["FROM_others"] = FROM connectedness_df.loc["TO_others"] =list(TO) + [TCI]return {"connectedness_matrix": connectedness_df,"TCI": TCI,"FROM": pd.Series(FROM, index=names),"TO": pd.Series(TO, index=names),"NET": pd.Series(NET, index=names) }# Apply to top Vietnamese stocks by liquiditytop_stocks = ( daily_returns.groupby("ticker")["volume"] .mean() .nlargest(20) .index)top_returns = daily_wide[ [c for c in top_stocks if c in daily_wide.columns]].dropna()dy_results = diebold_yilmaz_connectedness(top_returns)print(f"Total Connectedness Index: {dy_results['TCI']:.2f}%")``````{python}#| label: tbl-connectedness#| eval: false#| tbl-cap: "Kết nối ròng Diebold–Yilmaz: Bên truyền dẫn ròng (+) và bên tiếp nhận ròng (−)"net_df = pd.DataFrame({"TO_others": dy_results["TO"].round(2),"FROM_others": dy_results["FROM"].round(2),"NET": dy_results["NET"].round(2)}).sort_values("NET", ascending=False)net_df``````{python}#| label: rolling-connectedness#| eval: false# Rolling TCI to track systemic risk over timedef rolling_tci(returns_wide, window=252, step=21, var_order=1, forecast_horizon=10, min_stocks=10):"""Compute rolling Total Connectedness Index.""" dates = returns_wide.index tci_series = []for i inrange(window, len(dates), step): window_data = returns_wide.iloc[i - window:i]# Keep stocks with sufficient data valid_cols = window_data.dropna(axis=1, thresh=int(window *0.9))if valid_cols.shape[1] < min_stocks:continue# Use top stocks by variance (most informative) top_cols = valid_cols.var().nlargest(min_stocks).index subset = valid_cols[top_cols].dropna()iflen(subset) < window *0.8:continuetry: result = diebold_yilmaz_connectedness( subset, var_order, forecast_horizon ) tci_series.append({"date": dates[i],"TCI": result["TCI"],"n_stocks": len(top_cols) })exceptException:continuereturn pd.DataFrame(tci_series)# tci_df = rolling_tci(daily_wide, window=252, step=21)```## Mạng lưới liên ngân hàng và lây lan tài chính### Thị trường liên ngân hàng như một mạng lướiThị trường liên ngân hàng, nơi các ngân hàng cho nhau vay dự trữ, là mạng lưới tài chính điển hình. @allen2000financial chứng minh rằng cấu trúc các liên kết liên ngân hàng quyết định việc sự sụp đổ của một ngân hàng sẽ lan truyền thành khủng hoảng hệ thống hay được mạng lưới hấp thụ. Hai cấu trúc cực đoan minh họa điều này:**Mạng đầy đủ (mọi ngân hàng nối với mọi ngân hàng).** Mức dư nợ của mỗi ngân hàng với bất kỳ một đối tác nào đều nhỏ. Sự sụp đổ của một ngân hàng gây ra tổn thất nhỏ cho nhiều ngân hàng, mà từng ngân hàng có thể tự hấp thụ. Mạng lưới có khả năng chống chịu.**Mạng vòng (mỗi ngân hàng nối với một ngân hàng lân cận).** Mức dư nợ của mỗi ngân hàng tập trung vào một đối tác duy nhất. Sự sụp đổ của một ngân hàng có thể kéo đổ ngân hàng lân cận, kích hoạt một chuỗi vỡ nợ dây chuyền. Mạng lưới dễ đổ vỡ.Hệ thống ngân hàng Việt Nam, với sự tập trung của các ngân hàng nhà nước và việc cơ quan quản lý nhấn mạnh cho vay liên ngân hàng để quản lý thanh khoản, nằm giữa hai thái cực này.```{python}#| label: interbank-network#| eval: false# Load interbank exposure datainterbank = dc.get_interbank_exposures( date="2024-06-30")# Build interbank networkG_bank = nx.DiGraph()for _, row in interbank.iterrows(): G_bank.add_edge( row["lender_bank"], row["borrower_bank"], weight=row["exposure_bn_vnd"], exposure_pct_equity=row.get("exposure_pct_equity", 0) )# Add bank attributesbank_info = dc.get_bank_characteristics(date="2024-06-30")for _, row in bank_info.iterrows():if row["ticker"] in G_bank: G_bank.nodes[row["ticker"]].update({"total_assets": row.get("total_assets", 0),"equity": row.get("equity", 0),"is_soe": row.get("is_soe", False),"tier1_ratio": row.get("tier1_ratio", 0) })bank_metrics, bank_graph_stats = compute_network_metrics(G_bank)```### Mô phỏng vỡ nợ dây chuyềnChúng ta triển khai cơ chế bù trừ của @eisenberg2001systemic để mô phỏng các vụ vỡ nợ dây chuyền trong mạng lưới liên ngân hàng. Khi một ngân hàng sụp đổ, ngân hàng đó không thể thực hiện các nghĩa vụ liên ngân hàng, gây tổn thất cho các chủ nợ của nó, và đến lượt các chủ nợ này có thể sụp đổ:$$p_i^* = \min\left(\bar{p}_i, \; e_i + \sum_{j} \frac{L_{ji}}{\sum_k L_{jk}} p_j^*\right)$$ {#eq-eisenberg-noe}trong đó $p_i^*$ là khoản thanh toán thực tế của ngân hàng $i$, $\bar{p}_i$ là tổng nghĩa vụ của ngân hàng đó, $e_i$ là tài sản bên ngoài trừ nợ phải trả bên ngoài của nó, và $L_{ji}/\sum_k L_{jk}$ là phần nghĩa vụ của ngân hàng $j$ phải trả cho ngân hàng $i$.```{python}#| label: cascading-defaults#| eval: falsedef simulate_cascading_defaults(G, initial_failure, equity_buffer=0.08, max_rounds=20):""" Simulate cascading defaults in an interbank network. Parameters ---------- G : nx.DiGraph Interbank exposure graph (lender → borrower, weight = exposure). initial_failure : str Bank that fails initially. equity_buffer : float Fraction of equity that absorbs losses before default. max_rounds : int Maximum cascade rounds. Returns ------- dict : Defaulted banks, round-by-round losses, systemic loss. """ defaulted = {initial_failure} cascade_history = [{"round": 0, "defaults": [initial_failure],"total_defaults": 1}]for round_num inrange(1, max_rounds +1): new_defaults =set()for bank in G.nodes():if bank in defaulted:continue# Compute losses from defaulted counterparties total_loss =0for defaulted_bank in defaulted:if G.has_edge(bank, defaulted_bank):# bank lent to defaulted_bank → loss exposure = G[bank][defaulted_bank]["weight"]# Assume LGD = 60% loss = exposure *0.6 total_loss += loss# Check if losses exceed equity buffer bank_data = G.nodes.get(bank, {}) equity = bank_data.get("equity", float("inf"))if total_loss > equity * equity_buffer: new_defaults.add(bank)ifnot new_defaults:break defaulted |= new_defaults cascade_history.append({"round": round_num,"defaults": list(new_defaults),"total_defaults": len(defaulted) })# Compute systemic loss total_system_equity =sum( G.nodes[n].get("equity", 0) for n in G.nodes() ) defaulted_equity =sum( G.nodes[n].get("equity", 0) for n in defaulted ) systemic_loss_pct = ( defaulted_equity / total_system_equityif total_system_equity >0else0 )return {"defaulted_banks": list(defaulted),"n_defaults": len(defaulted),"cascade_rounds": len(cascade_history) -1,"cascade_history": cascade_history,"systemic_loss_pct": systemic_loss_pct }# Simulate cascade for each bankcascade_results = []for bank in G_bank.nodes(): result = simulate_cascading_defaults(G_bank, bank) cascade_results.append({"initial_failure": bank,"n_cascading_defaults": result["n_defaults"],"systemic_loss_pct": result["systemic_loss_pct"],"cascade_rounds": result["cascade_rounds"] })cascade_df = pd.DataFrame(cascade_results).sort_values("systemic_loss_pct", ascending=False)``````{python}#| label: tbl-systemic-banks#| eval: false#| tbl-cap: "Các ngân hàng quan trọng về mặt hệ thống: Phân tích vỡ nợ dây chuyền"cascade_df.head(15).round(4)```## Kết nối xã hội giữa các địa phương {#sec-networks-social}Mọi mạng lưới trong chương này cho đến giờ đều nối các doanh nghiệp hoặc tổ chức tài chính. Một loại mạng lưới khác nối con người, và qua con người nối các địa phương. Điều này quan trọng với tài chính vì các hộ gia đình tìm hiểu về sản phẩm tài chính từ những người họ quen biết, và vì bên cho vay và nhà đầu tư cũng một phần tìm hiểu về các bên đi vay và doanh nghiệp ở xa thông qua chính những mối quan hệ đó. Cho đến gần đây, mạng lưới này chưa thể được đo lường trên quy mô lớn. **Chỉ số Kết nối Xã hội** (Social Connectedness Index, SCI) của @bailey2018social đã thay đổi điều đó. Được xây dựng từ toàn bộ các liên kết bạn bè giữa người dùng Facebook, chỉ số này đo lường, với mỗi cặp vùng $i$ và $j$, xác suất tương đối để một người dùng ở $i$ và một người dùng ở $j$ là bạn bè:$$\text{SCI}_{ij} = \frac{\text{Connections}_{ij}}{\text{Users}_i \times \text{Users}_j}.$$ {#eq-networks-sci}Mức độ kết nối giảm mạnh theo khoảng cách địa lý, nhưng cũng phản ánh lịch sử di cư, các tuyến giao thông, và các ranh giới chính trị và văn hóa [@bailey2018social]. Với một vùng cho trước, SCI xác định một tập trọng số trên tất cả các vùng còn lại. Hữu ích nhất là các tỷ trọng bạn bè, tức tỷ lệ các liên kết bạn bè của vùng $i$ dẫn đến vùng $j$,$$w_{ij} = \frac{\text{SCI}_{ij} \times \text{Users}_j}{\sum_{k} \text{SCI}_{ik} \times \text{Users}_k},$$ {#eq-networks-friend-share}biến bất kỳ biến số vùng $X_j$ nào thành một **mức phơi nhiễm xã hội**:$$\text{SocialExp}_i(X) = \sum_{j \in \mathcal{N}(i)} w_{ij}\, X_j ,$$ {#eq-networks-social-exposure}trong đó $\mathcal{N}(i)$ là tập các vùng mà trên đó mức phơi nhiễm được tính: tất cả các vùng, hoặc chỉ những vùng ở xa $i$ về mặt địa lý. Hai khuôn mẫu xây dựng trên @eq-networks-social-exposure làm nền cho tài liệu tài chính xã hội được @kuchler2021social tổng quan. Trong @bailey2018economic, $X_j$ là tăng trưởng giá nhà ở các vùng của bạn bè: những cá nhân có bạn bè trải qua mức tăng giá nhà lớn hơn kỳ vọng mức tăng trong tương lai lớn hơn và mua nhà lớn hơn với giá cao hơn. Trong @kuchler2022social, $X_j$ là vốn tổ chức đặt tại vùng $j$: các doanh nghiệp có trụ sở ở những vùng có *mức gần gũi xã hội với vốn* cao thì có sở hữu tổ chức, định giá và thanh khoản cao hơn, với các tác động tập trung ở các doanh nghiệp nhỏ không được các nhà phân tích theo dõi. Các nghiên cứu trước đó đã xác lập phiên bản ở cấp độ cá nhân của ý tưởng này, từ việc tham gia thị trường cổ phiếu lan truyền qua tương tác xã hội [@hong2004social] đến việc những người cho vay trên một nền tảng cho vay ngang hàng coi mạng lưới bạn bè của bên đi vay là tín hiệu về chất lượng tín dụng [@lin2013judging].### Mạng lưới xã hội như kênh truyền dẫn tín dụng {#sec-networks-allen-peng-shan}@allen2026social đưa bộ công cụ này vào tín dụng tiêu dùng và, một cách hiếm có, vào cả hai phía của thị trường cùng lúc. Bối cảnh của họ là LendingClub, nền tảng cho vay trực tuyến lớn nhất Hoa Kỳ, có dữ liệu công khai bao gồm cả các đơn bị từ chối lẫn được chấp thuận, nhờ đó có thể nghiên cứu các *quyết định* cấp vốn chứ không chỉ các khoản vay đã được cấp. Phiên bản đầu của bài báo sử dụng các đơn vay từ năm 2010 đến 2017, xác định vị trí bên đi vay theo mã ZIP ba chữ số, và ánh xạ các khu vực đó sang SCI cấp hạt (county).**Cầu.** Số đơn vay trực tuyến ở một khu vực tăng lên sau khi hoạt động vay trực tuyến tăng ở những khu vực ở xa về địa lý nhưng có kết nối xã hội với khu vực đó. Giới hạn địa lý chính là yếu tố nhận diện. Bạn bè ở gần cùng chia sẻ điều kiện kinh tế địa phương, nên tương quan giữa hoạt động vay của một khu vực và của bạn bè nó có thể phản ánh một cú sốc chung chứ không phải quá trình học hỏi. Việc giới hạn $\mathcal{N}(i)$ trong @eq-networks-social-exposure vào các vùng ở xa (ngoài bang, trong bản thảo đầu, theo thiết kế của @bailey2018economic) loại bỏ thành phần chung đó. Phần còn lại phù hợp với việc nhận thức về một sản phẩm tài chính mới lan truyền qua các mối quan hệ xã hội: mọi người nghe về một nền tảng từ những người bạn đã dùng nó.**Cung.** Các đơn vay từ những khu vực có mức gần gũi xã hội cao với các vùng dồi dào tiền gửi, một thước đo theo tinh thần của @kuchler2022social với tiền gửi thay cho vốn tổ chức, có khả năng được cấp vốn cao hơn. Riêng kết quả này có thể phản ánh một trong hai kênh mà bài báo đặt đối chọi nhau. Các mối quan hệ xã hội có thể truyền *thông tin* khó có được về điều kiện tín dụng của một cộng đồng đến những người có khả năng cấp vốn cho cộng đồng đó. Hoặc chúng có thể truyền *sự thiên vị theo quan hệ thân quen* (affinity): sự quen thuộc, và việc lặp lại các ý kiến qua một mạng lưới tạo ra thiên lệch thuyết phục, khiến bên cấp vốn ưu ái các bên đi vay có kết nối bất kể chất lượng. Cả hai kênh đều dự báo rằng các bên đi vay có kết nối nhận được nhiều tín dụng hơn. Chúng bất đồng về hiệu quả của khoản vay cận biên. Sự thiên vị theo quan hệ thân quen hạ thấp tiêu chuẩn, nên các khoản vay bổ sung sẽ vỡ nợ thường xuyên hơn; thông tin làm sắc nét việc sàng lọc, nên chúng sẽ vỡ nợ ít hơn. Kết quả sau thực tế (ex post) tốt hơn, điều này chỉ ra kênh thông tin. @lin2013judging đi đến kết luận tương tự từ các liên kết bạn bè của từng bên đi vay trên Prosper, và khi @duarte2012trust áp dụng cùng logic cho ngoại hình của bên đi vay, họ thấy rằng những bên đi vay trông đáng tin mà người cho vay ưu ái thực sự vỡ nợ ít hơn.**Nhận diện.** Mức độ kết nối xã hội không được phân bổ ngẫu nhiên. Các khu vực mà cư dân có mạng lưới phân tán về địa lý thường giàu hơn, học vấn cao hơn và di động hơn [@bailey2018social], và mỗi đặc điểm đó đều ảnh hưởng trực tiếp đến kết quả tín dụng. Do đó, phiên bản đã công bố của bài báo thiết lập quan hệ nhân quả bằng các **biến công cụ shift-share**. Các tỷ trọng là các trọng số mạng lưới xã hội $w_{ij}$, còn các cú dịch chuyển là những sự kiện ở các vùng có kết nối làm thay đổi cầu hoặc cung tín dụng ở đó mà không phản ánh điều kiện ở khu vực của chính bên đi vay: thiên tai ở phía cầu và hành vi sai phạm của cố vấn tài chính ở phía cung. Hành vi sai phạm của cố vấn được đo lường đặc biệt tốt ở Hoa Kỳ vì hồ sơ kỷ luật của nhà môi giới là công khai, đây là dữ liệu mà @egan2019market khai thác.**Phân phối.** Cả tác động lên cầu lẫn lên cung đều mạnh nhất ở các cộng đồng thiệt thòi, và không đi kèm tỷ lệ nợ quá hạn cao hơn. Các mối quan hệ xã hội với những vùng thịnh vượng hơn dường như thay thế một phần cho hạ tầng thông tin địa phương (chi nhánh ngân hàng, lịch sử tín dụng dài, bên cho vay dựa trên quan hệ) mà các khu vực thiệt thòi thiếu.Bài báo đáng được đọc kỹ bởi bất kỳ ai xây dựng các thước đo mạng lưới, vì bốn lý do. Nó coi một mạng lưới giữa các địa phương là một đối tượng quan sát được, đo lường được ngang hàng với các liên kết sở hữu hay chuỗi cung ứng. Nó nghiên cứu cả hai phía của một thị trường thông qua cùng một mạng lưới, điều mà ít bài báo về mạng lưới làm được. Nó sử dụng kết quả sau thực tế làm phép kiểm định phân tách thông tin với thiên lệch, một phép kiểm định mà mọi khẳng định rằng mạng lưới "cải thiện" phân bổ đều nên được yêu cầu vượt qua. Và nó nêu rõ rằng biến phơi nhiễm xã hội là một cấu trúc shift-share, điều quyết định những gì có thể dùng để nhận diện nó.### Logic shift-share và suy luận {#sec-networks-ssiv}@eq-networks-social-exposure là tổng có trọng số các cú dịch chuyển theo vùng, cùng cấu trúc với các biến công cụ Bartik của kinh tế học lao động, và tài liệu kinh tế lượng về các thiết kế này nêu rất chính xác khi nào chúng nhận diện được một tác động nhân quả. Việc nhận diện có thể đến từ các tỷ trọng ngoại sinh [@goldsmith2020bartik]. Ở đây điều đó có nghĩa là cấu trúc các mối quan hệ xã hội của một khu vực không liên quan đến kết quả tín dụng của khu vực đó ngoại trừ thông qua các cú sốc mà các mối quan hệ truyền đi, một giả định mạnh khi các cấu trúc đó phản ánh di cư, dân tộc và cấu trúc ngành. Ngoài ra, việc nhận diện có thể đến từ các cú dịch chuyển được phân bổ giữa các vùng gần như ngẫu nhiên, có điều kiện theo các tỷ trọng [@borusyak2022quasi]. Thiên tai là những cú dịch chuyển hấp dẫn chính vì con đường thứ hai là hợp lý đối với chúng. Dù theo con đường nào, suy luận cũng đòi hỏi sự cẩn trọng. Các vùng có trọng số phơi nhiễm tương tự nhận các cú sốc tương quan với nhau, nên các sai số chuẩn thông thường và sai số chuẩn theo cụm vùng đánh giá quá cao độ chính xác. @adao2019shift suy ra các sai số chuẩn vững trước tương quan do phơi nhiễm này. Một hồi quy phơi nhiễm xã hội được báo cáo mà không có chúng, và không có các kiểm định cân bằng của các cú dịch chuyển với đặc điểm vùng trước kỳ, thì chưa đủ sức thuyết phục.### Kết nối xã hội ở Việt Nam {#sec-networks-social-vietnam}Dữ liệu đã có sẵn. Bản phát hành SCI công khai của Meta bao phủ các quốc gia ngoài châu Âu ở cấp đơn vị hành chính thứ nhất, mà với Việt Nam nghĩa là 63 tỉnh theo phiên bản ranh giới được dùng trong bản phát hành. Facebook được sử dụng rộng rãi ở Việt Nam, nhưng nhiều liên lạc riêng tư lại đi qua Zalo, mà SCI không quan sát được. Do đó, mức độ kết nối giữa các cặp tỉnh từ Facebook chỉ đo lường một kênh tiếp xúc xã hội, không phải tất cả.Hai đặc điểm của bối cảnh này cần được lưu ý trước khi chạy bất kỳ hồi quy nào. Thứ nhất, việc hợp nhất các tỉnh của Việt Nam từ 63 xuống 34 vào năm 2025 có nghĩa là các thống kê cấp tỉnh hiện hành phải được ánh xạ ngược về ranh giới cũ, hoặc SCI phải được tổng hợp xuôi theo trọng số dân số, và cả hai cách ánh xạ đều không vô hại đối với những tỉnh bị chia cho nhiều đơn vị mới. Thứ hai, di cư nội địa là yếu tố quyết định chính của mức độ kết nối, và tổng điều tra ghi nhận trực tiếp điều này. Các cuộc Tổng điều tra Dân số và Nhà ở năm 2009 và 2019 ghi nhận tỉnh cư trú năm năm trước của mỗi người trả lời, từ đó cho ra các ma trận di cư từ tỉnh này sang tỉnh khác. Một thước đo phơi nhiễm có trọng số theo di cư vừa là phép kiểm tra tính hợp lệ của SCI, vừa, do có trước hầu hết các kết quả cần quan tâm, là một ứng viên biến công cụ cho nó.Có bốn thiết kế nghiên cứu có thể chuyển giao trực tiếp nhất.**Mức gần gũi xã hội với vốn đối với các doanh nghiệp niêm yết.** Vốn đầu tư và hoạt động môi giới ở Việt Nam tập trung tại Thành phố Hồ Chí Minh và Hà Nội. Thước đo của @kuchler2022social, được tính cho tỉnh đặt trụ sở của từng doanh nghiệp, với tiền gửi hay thu nhập cấp tỉnh thay cho vốn tổ chức, đặt câu hỏi liệu các doanh nghiệp có trụ sở ở những tỉnh gần gũi xã hội với các trung tâm này có thanh khoản, định giá và sở hữu nước ngoài cao hơn hay không. Tỉnh đặt trụ sở xuất hiện trong mọi bản cáo bạch niêm yết, và các biến kết quả là những biến tiêu chuẩn trong cuốn sách này.**Sự khuếch tán của việc tham gia thị trường của nhà đầu tư cá nhân.** Sự gia tăng đột biến các tài khoản môi giới mới trong giai đoạn 2020–2022 là một sự kiện phía cầu đúng loại mà @allen2026social nghiên cứu: nhận thức về một sản phẩm tài chính lan truyền qua các mối quan hệ xã hội. Thiết kế đặt câu hỏi liệu việc mở tài khoản ở một tỉnh có tăng tốc sau khi việc mở tài khoản tăng ở các tỉnh ở xa nhưng có kết nối xã hội hay không. Ràng buộc chính là dữ liệu. Thống kê tài khoản được công bố ở cấp quốc gia, và việc phân tách theo tỉnh sẽ cần sự hợp tác của các công ty môi giới.**Cho vay trên nền tảng trong khung thử nghiệm có kiểm soát (sandbox).** Nghị định 94/2025/ND-CP, có hiệu lực từ tháng 7 năm 2025, tạo ra cơ chế thử nghiệm có kiểm soát cho cho vay ngang hàng cùng với chấm điểm tín dụng và API mở. Một thị trường nền tảng trong những năm đầu là bối cảnh của kết quả về cầu trong @allen2026social, và nếu dữ liệu về các bên tham gia sandbox được cung cấp cho các nhà nghiên cứu, thì sự lan rộng của các nền tảng giữa các tỉnh gần như là một cơ hội tái lập. Việt Nam cũng là nơi mà kết quả phía cung có thể quan trọng nhất. Như đã thảo luận trong @sec-data-alternative, độ bao phủ của trung tâm thông tin tín dụng vẫn còn mỏng, và @allen2026social gợi ý rằng các mối quan hệ xã hội với những vùng giàu hơn có thể một phần thay thế cho hạ tầng thông tin còn thiếu.**Các cú sốc cho thiết kế shift-share.** Ở phía cầu, bão và lũ lụt tác động đến những tỉnh xác định được vào những thời điểm được ghi nhận rõ ràng, trong đó có bão Yagi ở các tỉnh phía Bắc vào tháng 9 năm 2024, và đây là những cú dịch chuyển tự nhiên. Ở phía cung, sự kiện tương ứng gần nhất ở Việt Nam với hành vi sai phạm của cố vấn tại Hoa Kỳ là việc bán trái phiếu doanh nghiệp cho người gửi tiền ngân hàng thông qua mạng lưới chi nhánh, bị phát hiện trong vụ Vạn Thịnh Phát và SCB năm 2022, một cú sốc đối với niềm tin vào tiết kiệm qua trung gian ngân hàng. Sử dụng nó làm cú dịch chuyển đòi hỏi phải biết các trái chủ bị ảnh hưởng sống ở đâu, thông tin này không công khai, và không nên thực hiện thiết kế này nếu thiếu thông tin đó.Sáu quy tắc thực hành sau đây áp dụng cho nghiên cứu mạng lưới xã hội cấp tỉnh ở Việt Nam.1. **Định nghĩa "ở xa" theo vùng, không theo sự tiếp giáp.** Hình dạng dài và hẹp của Việt Nam khiến các tỉnh lân cận có thể cách xa nhau về kinh tế còn các tỉnh ở xa lại hội nhập chặt chẽ. Hãy đo phơi nhiễm trên các tỉnh nằm ngoài vùng kinh tế–xã hội của chính bên đi vay, tương tự hạn chế ngoài bang, và chứng tỏ rằng kết quả vẫn vững với các ngưỡng cắt thay thế.2. **Cố định phiên bản ranh giới.** Làm việc xuyên suốt với 63 tỉnh trước năm 2025, và ghi lại bảng chuyển đổi tương ứng cho mọi dữ liệu được báo cáo theo hệ thống 34 tỉnh.3. **Coi SCI là ảnh chụp của một mạng lưới nội sinh.** Nó được quan sát tại một vài thời điểm, sau phần lớn quá trình di cư đã tạo ra nó. Với các kết quả có trước ảnh chụp này, hãy dùng ma trận di cư của tổng điều tra năm 2009 làm thước đo phiên bản sớm hơn.4. **Báo cáo các chẩn đoán shift-share.** Cho thấy những tỷ trọng nào chi phối ước lượng [@goldsmith2020bartik], kiểm định cân bằng của các cú dịch chuyển với đặc điểm cấp tỉnh trước kỳ [@borusyak2022quasi], và dùng các sai số chuẩn vững với phơi nhiễm [@adao2019shift].5. **Thực hiện phép kiểm định hiệu quả.** Bất kỳ phát hiện nào cho rằng các mối quan hệ xã hội làm tăng phê duyệt, cấp vốn hay đầu tư đều phải được tiếp nối bằng một kiểm định trên các kết quả sau thực tế, vỡ nợ đối với tín dụng và lợi nhuận tiếp theo đối với cổ phiếu, phân biệt thông tin với sự thiên vị theo quan hệ thân quen, như @allen2026social đã làm.6. **Tôn trọng số lượng đơn vị nhỏ.** Với 63 tỉnh, suy luận theo cụm dựa trên ít cụm. Hãy báo cáo các giá trị p của bootstrap cụm kiểu wild (wild-cluster bootstrap) bên cạnh các giá trị thông thường.## Mạng nơ-ron đồ thị cho dự báo tài chính### Từ đồ thị đến dự báoPhân tích mạng lưới cổ điển tính các đặc trưng thủ công (độ trung tâm, phân cụm, thành viên cộng đồng) rồi đưa vào các mô hình hồi quy tiêu chuẩn. Các mạng nơ-ron đồ thị (GNN) học các đặc trưng trực tiếp từ cấu trúc đồ thị và thuộc tính của nút thông qua truyền thông điệp. Ý tưởng then chốt là biểu diễn của một nút không chỉ phụ thuộc vào đặc trưng của chính nó mà còn phụ thuộc vào đặc trưng của các nút lân cận, lân cận của chúng, và cứ thế tiếp tục.### Mạng tích chập đồ thị (GCN)Mạng tích chập đồ thị (Graph Convolutional Network) của @kipf2016semi thực hiện phép tích chập phổ trên đồ thị. Quy tắc lan truyền theo từng lớp là:$$H^{(\ell+1)} = \sigma\left(\tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2} H^{(\ell)} W^{(\ell)}\right)$$ {#eq-gcn}trong đó $\tilde{A} = A + I_n$ là ma trận kề có thêm các vòng tự nối, $\tilde{D}$ là ma trận bậc tương ứng, $H^{(\ell)}$ là ma trận đặc trưng nút ở lớp $\ell$, $W^{(\ell)}$ là ma trận trọng số có thể học, và $\sigma$ là hàm phi tuyến (ReLU). Số hạng chuẩn hóa $\tilde{D}^{-1/2} \tilde{A} \tilde{D}^{-1/2}$ lấy trung bình các đặc trưng của mỗi nút với đặc trưng của các nút lân cận, có trọng số theo bậc.### Mạng chú ý đồ thị (GAT)Mạng chú ý đồ thị (Graph Attention Network) của @velivckovic2017graph thay thế phép chuẩn hóa cố định trong GCN bằng các hệ số chú ý được học:$$h_i^{(\ell+1)} = \sigma\left(\sum_{j \in \mathcal{N}(i)} \alpha_{ij}^{(\ell)} W^{(\ell)} h_j^{(\ell)}\right)$$ {#eq-gat}trong đó hệ số chú ý $\alpha_{ij}$ được tính như sau:$$\alpha_{ij} = \frac{\exp\left(\text{LeakyReLU}\left(\mathbf{a}^\top [W h_i \| W h_j]\right)\right)}{\sum_{k \in \mathcal{N}(i)} \exp\left(\text{LeakyReLU}\left(\mathbf{a}^\top [W h_i \| W h_k]\right)\right)}$$ {#eq-gat-attention}Điều này cho phép mô hình học xem các nút lân cận nào mang nhiều thông tin nhất đối với từng nút, thay vì đối xử bình đẳng với mọi nút lân cận.```{python}#| label: gnn-models#| eval: falseclass GCNLayer(nn.Module):"""Graph Convolutional Network layer (Kipf & Welling, 2016)."""def__init__(self, in_features, out_features, bias=True):super().__init__()self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features))if bias:self.bias = nn.Parameter(torch.FloatTensor(out_features))else:self.bias =Noneself.reset_parameters()def reset_parameters(self): nn.init.xavier_uniform_(self.weight)ifself.bias isnotNone: nn.init.zeros_(self.bias)def forward(self, x, adj_norm):""" Parameters ---------- x : Tensor (n_nodes, in_features) Node feature matrix. adj_norm : Tensor (n_nodes, n_nodes) Normalized adjacency matrix (D^{-1/2} A D^{-1/2}). """ support = x @self.weight output = adj_norm @ supportifself.bias isnotNone: output = output +self.biasreturn outputclass GATLayer(nn.Module):"""Graph Attention Network layer (Velickovic et al., 2017)."""def__init__(self, in_features, out_features, n_heads=4, dropout=0.1, concat=True):super().__init__()self.n_heads = n_headsself.out_features = out_featuresself.concat = concatself.W = nn.Parameter( torch.FloatTensor(n_heads, in_features, out_features) )self.a = nn.Parameter(torch.FloatTensor(n_heads, 2* out_features, 1))self.dropout = nn.Dropout(dropout)self.leaky_relu = nn.LeakyReLU(0.2)self.reset_parameters()def reset_parameters(self): nn.init.xavier_uniform_(self.W) nn.init.xavier_uniform_(self.a)def forward(self, x, adj):""" Parameters ---------- x : Tensor (n_nodes, in_features) adj : Tensor (n_nodes, n_nodes) Binary adjacency matrix (1 if edge, 0 otherwise). """ n = x.size(0)# Linear transformation for each head# x: (n, in_f) -> h: (heads, n, out_f) h = torch.einsum("ni,hio->hno", x, self.W)# Attention coefficients# Concatenate h_i and h_j for all pairs h_i = h.unsqueeze(2).expand(-1, -1, n, -1) # (heads, n, n, out_f) h_j = h.unsqueeze(1).expand(-1, n, -1, -1) # (heads, n, n, out_f) concat_h = torch.cat([h_i, h_j], dim=-1) # (heads, n, n, 2*out_f) e =self.leaky_relu( torch.einsum("hnmo,hoi->hnm", concat_h, self.a).squeeze(-1) )# Mask non-edges mask = adj.unsqueeze(0).expand(self.n_heads, -1, -1) e = e.masked_fill(mask ==0, float("-inf"))# Softmax attention alpha = F.softmax(e, dim=-1) alpha =self.dropout(alpha)# Weighted aggregation out = torch.einsum("hnm,hmo->hno", alpha, h) # (heads, n, out_f)ifself.concat: out = out.permute(1, 0, 2).reshape(n, -1) # (n, heads * out_f)else: out = out.mean(dim=0) # (n, out_f)return out, alphaclass FinancialGNN(nn.Module):""" Graph Neural Network for financial node prediction. Supports GCN and GAT layers with flexible architecture. """def__init__(self, input_dim, hidden_dim=64, output_dim=1, n_layers=2, n_heads=4, gnn_type="gat", dropout=0.3):super().__init__()self.gnn_type = gnn_typeself.dropout = nn.Dropout(dropout)if gnn_type =="gcn":self.layers = nn.ModuleList()self.layers.append(GCNLayer(input_dim, hidden_dim))for _ inrange(n_layers -1):self.layers.append(GCNLayer(hidden_dim, hidden_dim))elif gnn_type =="gat":self.layers = nn.ModuleList()self.layers.append( GATLayer(input_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True) )for _ inrange(n_layers -2):self.layers.append( GATLayer(hidden_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True) )# Last layer: single head, no concatself.layers.append( GATLayer(hidden_dim, hidden_dim, n_heads=1, concat=False) )# Prediction headself.head = nn.Sequential( nn.Linear(hidden_dim, hidden_dim //2), nn.ReLU(), nn.Dropout(dropout), nn.Linear(hidden_dim //2, output_dim) )def forward(self, x, adj):""" Parameters ---------- x : Tensor (n_nodes, input_dim) Node features. adj : Tensor (n_nodes, n_nodes) Adjacency matrix. """ h = x attention_weights = []for i, layer inenumerate(self.layers):ifself.gnn_type =="gcn": h = layer(h, adj) h = F.relu(h) if i <len(self.layers) -1else helifself.gnn_type =="gat": h, alpha = layer(h, adj) attention_weights.append(alpha)if i <len(self.layers) -1: h = F.elu(h) h =self.dropout(h) predictions =self.head(h).squeeze(-1)return predictions, attention_weights```### GNN cho dự báo lợi nhuận chéo (cross-sectional)Chúng ta áp dụng GNN để dự báo lợi nhuận cổ phiếu theo chiều chéo (cross-sectional), trong đó đồ thị mã hóa các quan hệ sở hữu giữa các doanh nghiệp. Giả thuyết là các doanh nghiệp liên kết qua quan hệ sở hữu có động thái lợi nhuận tương quan mà GNN có thể khai thác.```{python}#| label: gnn-return-prediction#| eval: falsedef prepare_gnn_data(firms_df, ownership_graph, returns_df, date):""" Prepare node features and adjacency matrix for GNN prediction. Parameters ---------- firms_df : DataFrame Firm characteristics. ownership_graph : nx.DiGraph Ownership network. returns_df : DataFrame Stock returns. Returns ------- tuple : (node_features, adjacency, target_returns, ticker_list) """# Get stocks with both features and network presence tickers =sorted(set(firms_df["ticker"]) &set(ownership_graph.nodes()) &set(returns_df["ticker"]) )ifnot tickers:returnNone, None, None, None# Node features feature_cols = ["log_size", "book_to_market", "momentum_12m","profitability", "investment", "leverage","beta", "volatility", "turnover" ] feat_df = firms_df[firms_df["ticker"].isin(tickers)].set_index("ticker") feat_df = feat_df.reindex(tickers) X = feat_df[feature_cols].fillna(0).values X = (X - X.mean(axis=0)) / (X.std(axis=0) +1e-8)# Adjacency matrix ticker_to_idx = {t: i for i, t inenumerate(tickers)} n =len(tickers) A = np.zeros((n, n))for u, v, d in ownership_graph.edges(data=True):if u in ticker_to_idx and v in ticker_to_idx: weight = d.get("weight", 1) A[ticker_to_idx[u], ticker_to_idx[v]] = weight A[ticker_to_idx[v], ticker_to_idx[u]] = weight # Symmetrize# Add self-loops A = A + np.eye(n)# Normalize: D^{-1/2} A D^{-1/2} D = np.diag(A.sum(axis=1)) D_inv_sqrt = np.diag(1.0/ np.sqrt(np.diag(D) +1e-10)) A_norm = D_inv_sqrt @ A @ D_inv_sqrt# Target returns ret_df = returns_df[ (returns_df["ticker"].isin(tickers)) & (returns_df["date"] == date) ].set_index("ticker").reindex(tickers) y = ret_df["ret"].fillna(0).valuesreturn ( torch.tensor(X, dtype=torch.float32), torch.tensor(A_norm, dtype=torch.float32), torch.tensor(y, dtype=torch.float32), tickers )def train_gnn_model(model, X_train, A_train, y_train, X_val, A_val, y_val, n_epochs=100, lr=1e-3):"""Train GNN model with validation-based early stopping.""" optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4) best_val_loss =float("inf") patience_counter =0for epoch inrange(n_epochs): model.train() optimizer.zero_grad() pred, _ = model(X_train, A_train) loss = F.mse_loss(pred, y_train) loss.backward() optimizer.step()# Validation model.eval()with torch.no_grad(): val_pred, _ = model(X_val, A_val) val_loss = F.mse_loss(val_pred, y_val).item()if val_loss < best_val_loss: best_val_loss = val_loss best_state = {k: v.clone() for k, v in model.state_dict().items()} patience_counter =0else: patience_counter +=1if patience_counter >=15:break model.load_state_dict(best_state)return model, best_val_loss```### Mạng đồ thị theo thời gianCác mạng lưới tài chính tiến triển theo thời gian. Tỷ lệ sở hữu thay đổi, các thành viên hội đồng quản trị luân chuyển, các tương quan dịch chuyển. Các GNN theo thời gian mở rộng GNN tĩnh bằng cách đưa chiều thời gian vào một cách tường minh. Mạng đồ thị theo thời gian (Temporal Graph Network, TGN) của @rossi2020temporal duy trì một trạng thái bộ nhớ cho mỗi nút, được cập nhật mỗi khi có một sự kiện liên quan đến nút đó:$$\mathbf{s}_i(t) = \text{GRU}\left(\mathbf{s}_i(t^-), \; \text{msg}(i, t)\right)$$ {#eq-tgn}trong đó $\mathbf{s}_i(t)$ là trạng thái bộ nhớ của nút $i$ tại thời điểm $t$, $\text{msg}(i, t)$ là thông điệp được tổng hợp từ các sự kiện tại thời điểm $t$, và GRU là đơn vị hồi quy có cổng (gated recurrent unit).```{python}#| label: temporal-gnn#| eval: falseclass TemporalFinancialGNN(nn.Module):""" Temporal GNN for dynamic financial networks. Combines graph convolution with temporal memory. """def__init__(self, node_dim, edge_dim=1, memory_dim=64, hidden_dim=64, output_dim=1, n_heads=4):super().__init__()self.memory_dim = memory_dim# Memory update (GRU)self.memory_updater = nn.GRUCell( input_size=node_dim + edge_dim + memory_dim, hidden_size=memory_dim )# Message functionself.message_fn = nn.Sequential( nn.Linear(memory_dim *2+ edge_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, memory_dim) )# Graph attention for spatial aggregationself.spatial_attn = GATLayer( memory_dim + node_dim, hidden_dim // n_heads, n_heads=n_heads, concat=True )# Temporal attention for recent historyself.temporal_attn = nn.MultiheadAttention( embed_dim=hidden_dim, num_heads=n_heads, batch_first=True )# Prediction headself.head = nn.Sequential( nn.Linear(hidden_dim *2, hidden_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden_dim, output_dim) )def forward(self, node_features, adj_sequence, memory=None):""" Parameters ---------- node_features : list of Tensors Node features at each time step. adj_sequence : list of Tensors Adjacency matrices at each time step. memory : Tensor or None Initial memory state (n_nodes, memory_dim). Returns ------- predictions : Tensor Predictions for the last time step. """ n_nodes = node_features[0].shape[0] T =len(node_features)if memory isNone: memory = torch.zeros(n_nodes, self.memory_dim) temporal_states = []for t inrange(T): x_t = node_features[t] adj_t = adj_sequence[t]# Spatial aggregation via GAT combined = torch.cat([x_t, memory], dim=-1) spatial_out, _ =self.spatial_attn(combined, adj_t) temporal_states.append(spatial_out.unsqueeze(1))# Update memory with current state update_input = torch.cat([ x_t, spatial_out[:, :1].squeeze(1) if spatial_out.dim() >2else spatial_out, memory ], dim=-1)[:, :self.memory_updater.input_size]# Stack temporal states: (n_nodes, T, hidden) temporal_stack = torch.cat(temporal_states, dim=1)# Temporal attention across time steps temporal_out, _ =self.temporal_attn( temporal_stack, temporal_stack, temporal_stack )# Use last time step last_spatial = temporal_states[-1].squeeze(1) last_temporal = temporal_out[:, -1, :] combined = torch.cat([last_spatial, last_temporal], dim=-1) predictions =self.head(combined).squeeze(-1)return predictions```### GNN cho rủi ro tín dụng và phát hiện gian lậnNgoài dự báo lợi nhuận, GNN đặc biệt mạnh trong đánh giá rủi ro tín dụng và phát hiện gian lận, nơi bản thân cấu trúc mạng lưới mang thông tin. Trong rủi ro tín dụng, các doanh nghiệp liên kết với các đối tác đã vỡ nợ phải đối mặt với rủi ro gia tăng. Trong phát hiện gian lận, các mẫu hình giao dịch đáng ngờ lan truyền qua các mạng lưới thực thể liên quan.```{python}#| label: gnn-credit-risk#| eval: falseclass CreditRiskGNN(nn.Module):""" GNN for credit default prediction using firm and bank networks. Node classification: predict default probability per firm. """def__init__(self, firm_features_dim, bank_features_dim=0, hidden_dim=64, n_heads=4):super().__init__() input_dim = firm_features_dim + bank_features_dim# GNN backboneself.gnn = FinancialGNN( input_dim=input_dim, hidden_dim=hidden_dim, output_dim=1, n_layers=3, n_heads=n_heads, gnn_type="gat" )# Additional head for probability outputself.sigmoid = nn.Sigmoid()def forward(self, x, adj): logits, attn =self.gnn(x, adj) probs =self.sigmoid(logits)return probs, attn```## Khi nào nên dùng các phương pháp mạng lưới### Khung ra quyết định| Câu hỏi | Cách tiếp cận tốt nhất | Ví dụ ||----------------------|------------------------------|--------------------|| Doanh nghiệp $A$ có tác động đến doanh nghiệp $B$ không? | Kiểm định từng cặp + biến kiểm soát mạng lưới | Động lượng chuỗi cung ứng || Doanh nghiệp nào quan trọng nhất về mặt hệ thống? | Các thước đo độ trung tâm | Lây lan liên ngân hàng || Các doanh nghiệp kết nối qua mạng lưới có đồng biến động không? | Tương quan so với mức trùng lặp kiêm nhiệm | Khuếch tán qua kiêm nhiệm hội đồng quản trị || Cấu trúc mạng lưới có dự báo được lợi nhuận không? | GNN hoặc hồi quy bổ sung biến mạng lưới | Dự báo lợi nhuận bằng GCN || Một cú sốc lan truyền như thế nào? | Mô phỏng dây chuyền | Mô hình vỡ nợ Eisenberg-Noe || Danh mục tối ưu là gì khi xét rủi ro mạng lưới? | Tối ưu hóa chính quy hóa theo mạng lưới | Lọc MST của tương quan || Mạng lưới thay đổi theo thời gian như thế nào? | GNN theo thời gian hoặc phân tích cửa sổ trượt | Kết nối trượt DY || Thông tin có truyền giữa các vùng qua quan hệ xã hội không? | Phơi nhiễm xã hội trên các vùng ở xa + IV shift-share | Khuếch tán tín dụng trực tuyến [@allen2026social]|: Khung ra quyết định cho các phương pháp mạng lưới {#tbl-network-decision}### Yêu cầu dữ liệu và mức độ sẵn có ở Việt Nam| Loại mạng lưới | Dữ liệu cần thiết | Mức độ sẵn có ở Việt Nam | Tần suất cập nhật ||------------------|------------------|-------------------|------------------|| Sở hữu | Sổ đăng ký cổ đông | Tốt (báo cáo bán niên) | Nửa năm || Kiêm nhiệm hội đồng quản trị | Bổ nhiệm thành viên | Tốt (hồ sơ công bố) | Hằng năm || Chuỗi cung ứng | Công bố khách hàng–nhà cung cấp, dữ liệu thương mại | Trung bình (công bố giao dịch với bên liên quan) | Hằng năm || Tương quan | Dữ liệu lợi nhuận | Rất tốt (hằng ngày từ sở giao dịch) | Hằng ngày || Liên ngân hàng | Dư nợ song phương | Hạn chế (dữ liệu NHNN, thuyết minh BCTC của ngân hàng) | Hằng quý || Cùng nắm giữ | Sở hữu của tổ chức | Trung bình (công bố bán niên) | Nửa năm || Cho vay | Dữ liệu cấp khoản vay | Hạn chế (dữ liệu giám sát ngân hàng) | Hằng quý || Kết nối xã hội | Liên kết bạn bè giữa các cặp vùng; ma trận di cư | Trung bình (SCI của Meta cho 63 tỉnh; di cư theo tổng điều tra 2009 và 2019) | Ảnh chụp định kỳ |: Các nguồn dữ liệu mạng lưới tài chính ở Việt Nam {#tbl-network-data}<!-- ## Bài tập1. **Độ sâu kim tự tháp sở hữu và chuyển dịch lợi ích (tunneling).** Với mỗi doanh nghiệp niêm yết, hãy tính độ sâu của chuỗi sở hữu dài nhất từ chủ thể kiểm soát cuối cùng đến doanh nghiệp. Hồi quy Tobin's $Q$ và ROA theo độ sâu kim tự tháp, kiểm soát khoảng cách giữa quyền kiểm soát và quyền dòng tiền, quy mô doanh nghiệp, ngành và sàn niêm yết. Sở hữu kim tự tháp sâu hơn có phá hủy giá trị không? Tác động có khác nhau giữa các kim tự tháp nhà nước và tư nhân không? Sử dụng phương pháp của @claessens2002disentangling.2. **Khuếch tán thông tin qua mạng lưới hội đồng quản trị.** Xây dựng mạng lưới kiêm nhiệm hội đồng quản trị cho giai đoạn 2020-2024 và xác định các doanh nghiệp bổ sung thêm thành viên kiêm nhiệm mới. Sử dụng thiết kế sai biệt trong sai biệt (difference-in-differences), kiểm định xem các doanh nghiệp mới được kết nối có thể hiện mức đồng biến động lợi nhuận tăng lên so với nhóm đối chứng được ghép cặp hay không. Tác động có phụ thuộc vào việc thành viên dùng chung có được phân loại là độc lập hay không?3. **Lây lan chuỗi cung ứng trong giai đoạn COVID-19.** Tái dựng mạng lưới chuỗi cung ứng tại thời điểm tháng 12 năm 2019. Xác định các doanh nghiệp có liên kết chuỗi cung ứng trực tiếp với các nhà sản xuất Trung Quốc. Sử dụng thời điểm bắt đầu phong tỏa COVID-19 ở Trung Quốc (tháng 1 năm 2020) làm cú sốc, ước lượng mức giảm giá cổ phiếu chênh lệch giữa các doanh nghiệp có mức phơi nhiễm cao và thấp với nhà cung cấp Trung Quốc. Tác động có suy giảm theo khoảng cách mạng lưới (nhà cung cấp trực tiếp so với nhà cung cấp của nhà cung cấp) không?4. **Phát hiện chế độ trong mạng lưới tương quan.** Tính các mạng lưới tương quan cửa sổ trượt 6 tháng với cập nhật hằng tháng cho giai đoạn 2014-2024. Áp dụng phát hiện cộng đồng (thuật toán Louvain) tại mỗi thời điểm. Theo dõi số lượng cộng đồng, độ mô-đun (modularity) và thành phần của cộng đồng lớn nhất theo thời gian. Các thay đổi chế độ tương quan (việc hợp nhất các cộng đồng vốn tách biệt) có đi trước các giai đoạn biến động thị trường tăng cao không? Xây dựng một chỉ báo "độ mong manh của mạng lưới" dựa trên độ mô-đun giảm dần và kiểm định sức dự báo của nó đối với các đợt sụt giảm của VN-Index.5. **GNN so với các mô hình tuyến tính trong dự báo lợi nhuận.** Triển khai một cuộc đua ngựa (horse race) chặt chẽ giữa: (a) hồi quy chéo Fama-MacBeth với các đặc điểm tiêu chuẩn, (b) cùng hồi quy đó bổ sung các đặc trưng mạng lưới thủ công (bậc, độ trung gian, độ trung tâm vector riêng, thành viên cộng đồng), và (c) GCN và GAT nhận cùng các đặc trưng nút cộng với ma trận kề làm đầu vào. Sử dụng kiểm định chéo chuỗi thời gian với cửa sổ mở rộng và tái cân bằng hằng tháng. Báo cáo $R^2$ ngoài mẫu, hệ số thông tin (information coefficient) và tỷ số Sharpe của các danh mục mua–bán khống phân loại theo ngũ phân vị. Khả năng của GNN trong việc học đặc trưng từ cấu trúc đồ thị có vượt trội so với các đặc trưng mạng lưới thủ công không?6. **Chỉ số lây lan liên ngân hàng động.** Triển khai chỉ số kết nối Diebold-Yilmaz (2014) trượt cho ngành ngân hàng Việt Nam bằng dữ liệu hằng quý. Tính Chỉ số Kết nối Tổng (TCI), các thước đo có hướng FROM/TO và kết nối ròng cho từng ngân hàng. Vẽ biểu đồ TCI theo thời gian và xác định các đỉnh tương ứng với các sự kiện căng thẳng đã biết (hợp nhất ngân hàng năm 2011, khủng hoảng trái phiếu doanh nghiệp năm 2022). TCI tăng tại thời điểm $t$ có dự báo được ngành ngân hàng hoạt động kém tại $t+1$ không?7. **Mạng nơ-ron đồ thị không đồng nhất.** Xây dựng một đồ thị tài chính không đồng nhất với ba loại nút (doanh nghiệp, ngân hàng, thành viên hội đồng quản trị) và bốn loại cạnh (sở hữu, cho vay, thành viên hội đồng quản trị, chuỗi cung ứng). Triển khai một GAT không đồng nhất (HAN) sử dụng chú ý riêng theo loại cạnh. Đồ thị không đồng nhất có vượt trội hơn các đồ thị đồng nhất riêng lẻ trong dự báo lợi nhuận không? Loại cạnh nào nhận trọng số chú ý cao nhất?8. **Nhúng tài chính bằng Node2Vec.** Áp dụng thuật toán Node2Vec [@grover2016node2vec] cho mạng lưới sở hữu với các siêu tham số ($p$, $q$) khác nhau điều khiển sự cân bằng giữa khám phá kiểu BFS (vùng lân cận cục bộ) và kiểu DFS (vai trò cấu trúc). Trực quan hóa các vector nhúng 128 chiều bằng t-SNE, tô màu theo ngành. Các vector nhúng có nắm bắt được cấu trúc ngành không? Sử dụng các vector nhúng làm đặc trưng bổ sung trong mô hình dự báo lợi nhuận và so sánh với các đặc trưng độ trung tâm thủ công. -->## Tóm tắtChương này đã phát triển bộ công cụ mạng lưới cho thị trường tài chính Việt Nam, trải từ lý thuyết đồ thị cổ điển, việc xây dựng các mạng lưới tài chính theo từng lĩnh vực, đến các mạng nơ-ron đồ thị hiện đại.Hiểu biết trung tâm là các mạng lưới tài chính mã hóa thông tin mà các hồi quy dữ liệu bảng tiêu chuẩn không nhìn thấy. Các mạng lưới sở hữu bộc lộ cấu trúc kiểm soát kim tự tháp và rủi ro chuyển dịch lợi ích (tunneling), giải thích các khác biệt chéo về giá trị doanh nghiệp. Quan hệ kiêm nhiệm hội đồng quản trị đóng vai trò kênh thông tin qua đó các thông lệ trả thưởng, chính sách đầu tư và chuẩn mực quản trị khuếch tán. Các mạng lưới chuỗi cung ứng lan truyền các cú sốc kinh tế thực: hiện tượng bất thường động lượng khách hàng cho thấy giá thị trường phản ánh thông tin chuỗi cung ứng với độ trễ có thể dự báo. Các mạng lưới tương quan nắm bắt cấu trúc đồng biến động vừa cho thấy các cụm theo ngành vừa tiến triển qua các chế độ thị trường. Các mạng lưới liên ngân hàng quyết định liệu sự sụp đổ của từng ngân hàng có lan thành khủng hoảng hệ thống hay không. Và các mạng lưới xã hội giữa các địa phương mang thông tin mà các kênh chính thức bỏ sót: @allen2026social cho thấy các mối quan hệ giữa các cộng đồng mở rộng cả cầu lẫn cung tín dụng trực tuyến, nhất là ở các cộng đồng thiệt thòi, mà không làm tăng nợ quá hạn, điều này khiến Chỉ số Kết nối Xã hội trở thành công cụ đo lường có giá trị đặc biệt trong một thị trường mà thông tin tín dụng chính thức còn mỏng.Thị trường Việt Nam có mật độ mạng lưới đặc biệt cao vì vai trò phổ biến của sở hữu nhà nước, vị thế nổi bật của các tập đoàn và sự tập trung của hệ thống ngân hàng. Khoảng cách giữa quyền kiểm soát và quyền dòng tiền (tức là chênh lệch giữa quyền kiểm soát và quyền dòng tiền do sở hữu kim tự tháp tạo ra) lớn hơn và biến động nhiều hơn so với các thị trường có mức bảo vệ cổ đông thiểu số mạnh hơn. Khoảng cách này, chỉ có thể tính được thông qua phân tích mạng lưới của các chuỗi sở hữu, vừa là thước đo rủi ro quản trị vừa là yếu tố dự báo giá trị doanh nghiệp.Các mạng nơ-ron đồ thị mở rộng bộ công cụ từ các đặc trưng mạng lưới thủ công sang các biểu diễn được học. Các kiến trúc GCN và GAT tổng hợp thông tin trên các vùng lân cận mạng lưới, và GNN theo thời gian nắm bắt sự tiến triển của cấu trúc mạng lưới theo thời gian. Với dự báo lợi nhuận, câu hỏi thực nghiệm là liệu khả năng của GNN trong việc học các hàm linh hoạt của cấu trúc đồ thị có vượt trội so với cách tiếp cận đơn giản hơn là tính các thước đo độ trung tâm rồi đưa vào các mô hình tiêu chuẩn hay không. Các bài tập cung cấp một khung để trả lời câu hỏi này một cách chặt chẽ.Góc nhìn mạng lưới không phải là sự thay thế cho các phương pháp định giá tài sản hay tài chính doanh nghiệp tiêu chuẩn; nó là một sự bổ sung. Các thiết kế nghiên cứu hiệu quả nhất kết hợp cấu trúc mạng lưới với các chiến lược nhận diện từ bộ công cụ suy luận nhân quả: sử dụng các cú sốc mạng lưới làm biến công cụ, khai thác các gián đoạn mạng lưới như các thí nghiệm tự nhiên, và kiểm soát vị trí trong mạng lưới khi ước lượng các tác động can thiệp. Giao điểm của mạng lưới và suy luận nhân quả (chẳng hạn, nhiễu chéo trong mạng lưới (network interference), nhận diện hiệu ứng ngang hàng, kinh tế lượng không gian) là biên giới của tài chính thực nghiệm.