分散之必須
「分散」是消除風險的重要方法。當我們把資金分散到多個標的,並不能真正安心,因為許多標的存在著相關性。比如說同產業的多標的,容易同漲同跌,導致雖然標的分散了,實際上風險卻沒有分散。分散的方法可以基於基本面的知識,也可以基於量化分析,而最好是兩者相輔相成。比如說最近華邦電與力積電一同飆漲,若有基本面知識的人,就知道力積電是記憶體的代工廠 (其實才大約三成),所以一同受惠漲價題材。若基本面不熟悉,用量化的方式能不能發現其相關性?應該是可以的。本文就來實作這部分。
分散之量化方法
我以半年期間的「日報酬率」當作各標的的「特徵向量」,計算向量間的「距離」。距離有幾種算法,常用的有 Euclidean Distance,其意義主要反應標的的「報酬率幅度」;我則是用 Cosine Distance,意思則是反應「報酬率的方向」,因為這個是我比較在意的。
首先把資料整理成以下 dataFrame 格式,這資料整理的基本功,就不贅述程式碼了:
0050-元大台灣50 3034-聯詠 2634-漢翔
date ...
2025-04-24 0.000000 0.000000 0.000000
2025-04-25 0.023780 0.043569 0.009636
2025-04-28 0.008446 0.019881 0.012725
2025-04-29 0.003889 -0.009747 0.001047
2025-04-30 0.004768 0.021654 -0.024059
... ... ... ...
2025-10-14 -0.004918 -0.015700 -0.046392
2025-10-15 0.018122 -0.002454 0.001802
2025-10-16 0.019417 0.009840 0.025180
2025-10-17 -0.015079 -0.012180 -0.017544
2025-10-20 0.016922 0.020962 -0.010714
[123 rows x 3 columns]
接下來計算各個標的之間的距離,引入 sklearn 套件,就一行搞定的事。注意,輸入的資料必須經過轉置,成為 row vectors 形式:
from sklearn.metrics import pairwise_distances
features = df.T.values # shape: (num_stocks, num_days)
distance_type = 'cosine' # 'euclidean' 報酬率的幅度 or 'cosine' 波動方向
distances = pairwise_distances(features, metric=distance_type)
# distances shape: (num_stocks, num_stocks)
最簡單的視覺化方法-矩陣熱力圖
這個 distances 就是長寬各為標的數目的方形矩陣,可以初步用最簡單的視覺化方式,就是熱力圖。數值大的顏色深,數值小的顏色淺,我們把 distances 矩陣用 1 去減,變成「similarity」矩陣,比較符合常規習慣:
import matplotlib.pyplot as plt
import seaborn as sns
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_title(f"Portfolio Relation Analysis")
plt.tight_layout()
sns.heatmap(
1 - distances,
annot=True,
fmt=".2f",
cmap='coolwarm', # 'YlGnBu',
center=0,
ax=ax,
cbar=True,
square=True,
linewidths=0.5,
annot_kws={"size": 5}
)
plt.show()

這個熱力圖是挺美觀的,但老實說...沒什麼用!頂多看到說哪一格,顏色有點深,是哪兩支比較高度相關,所以不要同時配置太多資金,但這一切都很籠統。我們需要更強而有力的方法,就是把這半年長度的向量「降維」到二度空間,顯示成 scatter plot,那就可以很直觀的看見有哪些點擠在一起。此外我還想把「部位大小」反應成泡泡的大小,那視覺效果更佳,若有些泡泡不只很擠,甚至「強碰」在一起,那資訊的強度就更夠了。
更佳的視覺化方法-降維 + 泡泡圖
降維也有很多種方法,常用的有 PCA (Pricipal Component Analysis) 和 MDS (Multi-Dimensional Scaling)。此兩者的原始演算法皆是以保留 Euclidean Distance 為目標,經實測無法拉開差異性,我還是希望以 cosine distance 為輸入。經查有一變形的演算法稱為 Kernel PCA 可以用,實測效果較佳,因此就用它了。一樣引入 sklearn 套件,也是一行解決。因我的資料順序是依照部位由大到小,有多有空,多方指標 long index 之後為空方標的,故 scatter plot 分兩次繪製,以不同顏色區分,預期將有最好效果:
from sklearn.decomposition import PCA
from sklearn.decomposition import KernelPCA
# 2d projection using PCA, only accepts feature vectors
# pca = PCA(n_components=2)
# X_projection = pca.fit_transform(features) # distances)
# projection_type = 'pca'
# 2d projection using kernel PCA
kpca = KernelPCA(n_components=2, kernel='rbf', random_state=42)
X_projection = kpca.fit_transform(distances)
projection_type = 'kernel pca'
# scatter plot of projection result
ax.set_title(f"2d projection of {distance_type} distances using {projection_type}")
plt.scatter(X_projection[0:long_index, 0], X_projection[0:long_index, 1],
s = abs(np.array(exposure)[0:long_index]) / (expo_long + expo_short) * 15000,
color='#fadbd8', label='long') # , edgecolors='k') # , label='x')
plt.scatter(X_projection[long_index:, 0], X_projection[long_index:, 1],
s = abs(np.array(exposure)[long_index:]) / (expo_long + expo_short) * 15000,
color='#d5f5e3', label='short') # , edgecolors='k') # , label='x')
for i, name in enumerate(df.columns):
plt.text(X_projection[i,0]+0.01, X_projection[i,1]+0.01, name)
plt.legend(markerscale=0.2, fontsize=10)
終於出現我期望的理想圖形:

此圖形精準顯示華邦電和力積電「強碰」,又很大顆,風險過度集中。奇鋐,南電,雖然不同產業,一個 pcb 一個散熱,但卻高度相似,原來都跟 ai 資料中心的建置有關。
此圖一出,輕易搓破 Beta 中立而沾沾自喜的假象,必須趕快調整部位,將風險盡量分配到「遠距」的泡泡。雖說風險永遠只是個機率問題,再怎麼管都會有例外,但這都不是我們可以逃避的理由,盡力維持資產的穩定就是管理者最重要的本分工作了。這個重要功能,值得整合到產品「精明管家-風險部位管理系統」。
從 2025/4 超大 dip 之後,市場驚奇的絕地大反攻,已經走了大半年井噴走勢了,這時候講 beta 中立的確並不討喜,好像大家都跳下去賺趨勢的錢比較快,落寞的我還在辛苦的做那小小 2% 的波動。但我清楚,在多年來慢慢形成的個人投資邏輯,甚至沉澱成為自己的一種投資哲學,都有其合理性,不容易說改就改,跟不上大多頭群眾的狂歡,我就認了。我明確的知道,為了在一定會到來的中期回調,甚至是熊市中,仍能維持資產穩定增值,堅持這種策略是值得的。
Newman 2025/10/23
導覽頁:紐曼的技術筆記-索引
導覽頁:精明管家




















