機器人理財 — 理財機器人試用與比較分析

Vincent Ko
25 min readMay 8, 2024

--

最近會把我寫的作業分享上來給大家參考,如果大家覺得有料的話可以來看看。

題目:請從國內機器人理財名單中挑選兩家業者然後試用,請從下面 3 個觀點來
分析 這兩家機器人理財業者的優缺點:
1. 資產的數量和風險分散程度
2. 風險問卷的完整度與效度(KYC)
3. 將業者提供的投資組合,利用隨機回溯 5 年的資料(假設每半年進行投
資組合再平衡一次)來計算該投資組合的年化報酬率、年化標準差、
max drawdown 和夏普值
機器人理財業者名單
中國信託、阿爾發投顧、兆豐銀行、國泰世華、永豐銀行、華南銀行、
第一銀行、富邦銀行奈米投

B10703049 財金三 柯宥圻

大綱:

一、 前言 P1

二、 資產數量以及風險分散程度 P2

三、 風險問卷的完整度與效度 P5

四、 投資組合回測 P9

五、 其他 P10

六、 附件:程式碼 P10

一、前言

我總共挑選了四家公司進行機器人理財的規劃,最後選擇阿爾發理財和富邦奈米投來進行報告,另外兩家雖然有進行試算,但是決定不納入考慮的是Charles Schwab以及永豐IbrAin兩家。美國來的機器人理財服務如Betterment, Wealth front等,都需要有帳號才能夠進行試算,而申請帳號需要美國的手機號碼,所以沒有辦法看到最純粹的機器人理財樣貌。Charles Schwab雖然有幫我在沒有登入的情況下完成投組配置,但是沒有給予明確的標的名稱,而只有給予一些他投資標的方向,因此沒有辦法進行很準確地回測,Charles Schwab的判定結果概略如圖(一)。此外,Schwab需要最多第一期投入金額(5000美金)來啟動計畫,因此對於資產規模不大的投資人來說可能是勸退他們的挑戰,又或者是台灣的公司以理財新手、年輕人做為宣傳主要目標,讓富邦、阿爾發等公司都可以用100美元以內的初期投入金額來完成目標,如果發展成熟、得宜的話莫不為好事。

圖(一):Schwab只會給如US Large company stock等敘述,但不會告訴我們實際的標的。
圖(一):Schwab只會給如US Large company stock等敘述,但不會告訴我們實際的標的。

永豐金控出的IbrAin的問題很明確,他的資產完全不夠分散,他甚至只用兩檔標的來做投資組合!在我的狀況中,他只選擇了兩個ETF,就這樣子而已,雖然永豐金控只收0.5%的手續費,但是過於未分散的投資組合使人望之卻步,如圖(二)。

圖(二):各投資組合配置皆是由這五檔ETF進行配置,令人不齒。

綜合以上,我決定將老師所推薦的阿爾發理財以及富邦奈米投作為研究的對象,針對所詢問的問題進行初步的分析,並且透過投資組合優化來焚悉這兩家機器人理財的優缺點,並在最後進行統整。

二、資產的數量以及風險分散程度。

首先,我將富邦奈米投(奈米投)與阿爾發投顧給我的投資組合截圖下來,我的設定是一個積極型的大學生,願意承擔風險,並且希望可以得到高報酬來讓我提早退休,兩間公司大略都是股債比85:15的配置,詳情如圖(三)-圖(五)

由於阿爾發理財的介面沒有概覽表,所以在這邊備註六檔ETF分別代表的部分

美國整體股市ETF (VTI) 28.6%

全球已開發國家股市ETF(VEA) 24.2%

全球中小型股市ETF(VSS) 19.7%

美國不動產(VNQ)ETF 8.25%

美國以外不動產ETF (VNQI) 4.25%

全球總體債券ETF(BNDW) 15%

在這個面向上,不管是以分散程度或者以量為角度,都是富邦奈米投勝出,我們可以看到富邦在最低35美金的規模下,可以將資產拆分成15大塊(包含現金),而阿爾發理財則是最低100美金的規模下,將資產拆分成六大塊,可以看到兩者在切割股份上仍然具有差距。值得注意的是,在不同的風險屬性下,這兩間公司都是選擇將已經選定好的這些公司進行比例上的分配,而沒有選擇新增或移除某些資產。雖然兩間公司的負責人員都跟我說在需要的時候會選擇重新賣出或買入不同的標的,但是在公開資訊上我沒有找到相關的資料。分散程度可以使用資產間的相關係數來去做衡量,以下是兩間公司的correlation matrix,如圖(六)、圖(七):

左圖為阿爾發投顧,右圖為富邦奈米投投組

大致可以看到股市和債券間的負相關性,也有兩兩標的間存在低相關性的情況。在標的選擇上,富邦的優點就是分散程度高,但我想這份清單也有部分受到了一些「觀感上的限制」,我發現她買了3%的AWSR ETF,這款雖然選擇了許多不同的永續公司,但是流動性很差,這份清單裡面也有許多流動性較差的部分,尤其是在和日本與日本以外的這些亞太地區債券,流動性都不太高,甚至會有一檔股票的收盤價呈現null狀態,在程式上花了一些時間排除。雖然是投資全球(和奈米二號投資台灣股市不同),但是他有特別放了許多和日本相關的ETF進入資產池中,因為我沒有看過這種操作,所以感到相當新奇。最後,跟阿爾發比起來,他選的標的更有可能透過特殊的方式找到表現最好股票,因為相對來說,阿爾發都是買一些大型、流動性前幾ETF,而富邦奈米投則會選一些流動性較差,但是結果可能被專家認定為還不錯的標的,至於哪款的結果比較好呢? 第四部份在回測的時候就會揭曉了。

另一方面,阿爾發理財選擇了六檔股票,其中包含美國、美國以外,以及不動產,不動產普遍被認為較為安全的投資標的,風險屬性介於股市ETF跟債券中間,目前只有該公司有做相關的配置,接者總體債券並沒有選擇公司債ETF,而是直接購買總體債券,也是兩者不同的地方。最後,阿爾發理財收取1%手續費,而富邦奈米投收取0.88%。

三、風險問卷的完整度與效度。

完整度(Content Validity):確保問卷涵蓋了所有重要的風險因素。可以由專家評估問卷的內容是否充分、全面,並提出改進建議。

效度(Validity):

1. 建構效度(Construct Validity):確保問卷所衡量的風險概念是有效的。可以透過因素分析等方法來驗證問卷的建構效度。

2. 內容效度(Content Validity):與完整度相關,確保問卷的內容反映出客戶風險的真實情況。

3. 準據效度(Criterion Validity):確保問卷的結果與其他已經驗證過的衡量工具或實際表現之間有相關性。

概括來說,富邦奈米投的完整度低於阿爾發理財,但建構效度是兩者相當,因為他們都有詢問到與客戶風險相關的問題,並衡量客戶對應的風險。然而,內容效度和準據效度就比較難以直接用目前的資料衡量,因為我們沒有其他實際的工具或實際表現來去證明問卷衡量結果和實際表現具有多高的相關性。首先,這兩家公司都擁有互動式的介面,以及精緻的動畫,帶領使用者判定自己的財務風險,我認為這兩間公司也都能夠妥善問到用戶的風險屬性。但是整體來說,阿爾發的問題遠比富邦的好很多。圖(八)至圖(十一)為富邦的風險屬性問卷。

在這裡,他總共詢問有關於風險的問題只有第二題,而且用戶其實是對自己的回答是有自覺的,也就是說,用戶可以假裝自己是另外一個模樣,只要用戶想要選的激進一點,他就可以選擇比較高風險屬性的回答。實際上,Q2的四個問題恰好對應到四個風險屬性,這樣看來有點不夠完備。圖(十)~圖(十五)則是阿爾發理財的問券內容,內容稍多,而且有關於風險屬性就問了七題,最後將投資人分成13種不同的屬性,且問題設計比較隱晦,比較能夠捕捉到用戶的真實想法。圖(十)至圖(十五)內容如下。

我認為阿爾發投顧關於風險管理的問卷,是最具有客觀性,也最能夠探測到用戶真實的風險屬性的。有些投資者會錯估自己真實的風險屬性,舉例來說,有些人會認為自己是長期投資人,但實際上卻是在做短線操作等。舉例來說,第四題「假如薪資組成可以選擇,你偏好哪一種投資組合?」,這個問題並不直接和投資理財相關,但是根據這個問題,我們可以推測說選擇「以固定的薪酬為主」的人會比較傾向於保守配置,反之亦然。第七題也有一個數據化的投資組合讓填答人填寫,我覺得這張表格很清楚地讓投資者選擇他偏好的資產組合類型。因此,阿爾發理財的完整度和效度都比較高。

最後,有關於風險以外的「個人投資規劃類別」屬性,阿爾發也做得比較好,像是奈米投他只有詢問理財的用途,以及預計的投資金額,而阿爾發有特別詢問想要幾歲退休、退休後的預計金流等,這些資訊可以讓阿爾發理財去計算模擬的報酬率(用蒙地卡羅法),並且顯示在主頁面上,如圖(十六)和圖(十七)。雖然蒙地卡羅法式初步的未來資產價值判斷方式,但還是能成功地讓用戶了解未來可能成功的機率。

阿爾發投顧有確實地做到根據目標,年齡不同做出投組建議、退休規劃以及投資組合健檢等等,在提供服務與判斷投資人風險屬性上,阿爾發投顧的問卷都能夠達到相較之下較精準的結果。

四、業者提供的投資組合的相關指標

首先,我將要求計算的結果和報酬顯示在圖(十八)至圖(二十一)。阿爾發的結果勉強比無風險利率高,Sharpe ratio為0.02,而奈米投的結果就較佳,但是年化的波動程度較大。(註:我現在寫另外一份作業的時候發現code有錯,所以我會再進行修改)。

這段程式碼是用來計算機器人理財投資組合的表現和風險指標,主要功能包括:

1. 讀取股票收盤價資料並建立資料結構。

2. 計算投資組合的資產比例,以便進行再平衡。

3. 在每次再平衡時計算投資組合的價值和報酬率。

4. 計算投資組合的年化報酬率、年化標準差、最大回撤和Sharpe比率。

5. 繪製股票收盤價格的相關性矩陣和投資組合價值隨時間變化的圖表。

這段程式碼有一些需要注意的地方:

我使用了迴圈和條件判斷來進行再平衡和計算指標,確保了程式的彈性和準確性,同時使用了Pandas和NumPy等庫來處理資料和進行統計計算,提高了程式的效率和易讀性。最後,我成功回測了資產比例、報酬率、標準差、最大回撤和Sharpe比率等指標,全面評估了投資組合的風險和報酬。

這段程式碼用於分析和評估機器人理財投資組合的表現和風險,並提供了相關的統計指標和圖表來輔助分析和決策。

五、其他發現

我和阿爾發投顧的理財有進行半小時的訪談,以下是我覺得有趣的地方

1. 商品:ETF的績效應該能夠遠勝大多數的主動基金,這是阿爾發理財驕傲並且持續努力的地方

2. 我們不能夠自己選自己的投資組合,而是透過科學化的方式進行理財,不讓你自己改,反而會是回測後最好的情形。

3. 碎股機制在台灣特別盛行,因為台灣人特別喜歡拿小錢進行投資。

code有分成算阿爾發的跟算富邦納米投的

import numpy as np
import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt

# 總共十次再平衡,包含最後一次即將領出錢時有再平衡一次
# ->1256/10=125.6循環,用125天為一單位進行再平衡,最後一天再平衡一次
INTERVAL = 125
principal = 1000000

# 假設你有五檔股票的收盤價資料,存在一個名為data的字典中,key是股票名稱,value是包含五年收盤價的list
data = dict()
for i in ('VTI', "VEA", "VSS", "VNQ", "VNQI", "BNDW"):
df = pd.read_csv(i+'.csv')
days = df['Date'].tolist()
if i == "BNDW":
a = 0
for j in days:
days[a] = datetime.strptime(j, '%Y/%m/%d').date()
a+=1
df = df['Close'].tolist()
data[i] = df

# 本來以為Sharpe ratio需要的rf要和其他六檔股票1:1對應,但是後來發現不用,只需要算個整體的平均值就好了(sum/len)
tnx = pd.read_csv("TNX.csv")
rf = list()
for i, d in tnx.iterrows():
if datetime.strptime(d["Date"], '%Y/%m/%d').date() in days:
rf.append(d["Close"])

pct = [0.286, 0.242, 0.197, 0.082, 0.043, 0.15]

# 計算它在不同時期,各股票的持有張數與金額
nowday = 125
num_stock = [0,0,0,0,0,0]
inner_cnt = 0

# 第一期,計算剛開始的時候不同的股票各買幾張可以維持平衡
for i in data:
num_stock[inner_cnt] = (principal*pct[inner_cnt])/data[i][0]
inner_cnt += 1
returns = []
principal_halfyear = [1000000]
principal_his = [1000000]

max_value = 1000000
min_value = 1000000
for nowday in range(1, 1256):
#當半年過去或者是到達最後一期的時候,進行再平衡,概念上是希望每次再平衡後,資產比例可以維持在pct所顯示的比例上
if (nowday%125 == 0 or nowday == 1255):
if nowday == 1255:
nowday = -1
inner_cnt = 0
newprincipal = 0
for i in data:
stock_price = num_stock[inner_cnt] * data[i][nowday]
newprincipal += stock_price
inner_cnt += 1
inner_cnt = 0
price = []
for i in data:
num_stock[inner_cnt] = (newprincipal*pct[inner_cnt])/data[i][nowday]
price.append(data[i][nowday])
inner_cnt += 1
print((num_stock[0]*data["VTI"][nowday])/newprincipal) #這行code代表我希望每次再平衡之後,檢查是否都是維持在相同比例
principal_halfyear.append((newprincipal / principal) - 1)
nowday += INTERVAL

# 後來發現我們只需要記錄每一期的資料,所以不用那麼麻煩,下面就只是算max value跟Min value的
else:
inner_cnt = 0
newprincipal = 0
for i in data:
stock_price = num_stock[inner_cnt] * data[i][nowday]
newprincipal += stock_price
inner_cnt += 1

#存最大和最小值算Max dropdown
if newprincipal > max_value:
max_value = newprincipal
if newprincipal < min_value:
min_value = newprincipal

returns.append((newprincipal / principal) - 1)
principal_his.append(newprincipal)

final_principal = newprincipal
print("=============阿爾發結果=============")
print("期末本金:{:6.9}元".format(final_principal))
print("年化報酬率:{:.2%}".format((final_principal/principal)**(1/5)-1))

overall_std = np.std(principal_halfyear[2:])
semiannual_std = overall_std * np.sqrt(2)
print("(半年)年化標準差:{:.2%}".format(semiannual_std))

annual = list()
cnt = 0
for i in principal_halfyear:
if cnt%2==1:
annual.append(i)
cnt += 1
annual_std = np.std(annual)
print("年化標準差:{:.2%}".format(annual_std))

# maxdrawdown以半年為一個單位計算
print("Max Drawdown:{:.2%}".format((max_value-min_value)/max_value))
rf_mean = (sum(rf) / len(rf))/100
sharpe_ratio = (((final_principal/principal)**(1/5)-1) - rf_mean) / annual_std
print("Sharpe比率:{:.5f}".format(sharpe_ratio))
print("===================================")

#計算關聯程度(這裡使用相關係數)
data = pd.DataFrame(data)
correlation_matrix = data.corr()
# 繪製關聯程度圖
plt.figure(figsize=(10, 8))
plt.matshow(correlation_matrix, cmap='coolwarm', fignum=1)
plt.xticks(range(len(correlation_matrix.columns)), correlation_matrix.columns, rotation='vertical')
plt.yticks(range(len(correlation_matrix.columns)), correlation_matrix.columns)
plt.colorbar()
plt.title('Correlation Matrix of Stock Prices')
plt.show()

plt.figure(figsize=(10, 6))
plt.plot(principal_his, linestyle='-')
# 加入標籤及標題
plt.ylabel('principal')
plt.title('ALPHA - principal changes with time')
# 顯示圖表
plt.grid(True)
plt.show()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 總共十次再平衡,包含最後一次即將領出錢時有再平衡一次
# ->1256/10=125.6循環,用125天為一單位進行再平衡,最後一天再平衡一次
INTERVAL = 120
principal = 1000000
data = dict()
#不知道為甚麼時間對不起來(我在想可能是美國和英國的開盤時間)
# 所以接下來和阿爾發投顧不同的部分都是在處理時間不一致的狀況,最後挑選出1209天15檔標的都有的日期
day_data = dict()
for i in ("CSPX.L", "IDP6.L","IEMU.L", "EIMI.L","CPXJ.L","LCJD.L", "IDJP.L", "AWSRIW.SW", "CCAU.L", "ISFU.L", "VDTY.L", "IDTL.L","LQDA", "WING"):
df = pd.read_csv('./data/'+i+'.csv')
day_data[i] = df['Date'].tolist()
df = df['Close'].tolist()
data[i] = df

# 計算現金報酬
money_daily = []
orig = 1
for x in range(1260):
# 年報酬(2.18%)轉成日報酬,(1.0218)^(1/252)
orig *= 1.00008558215
money_daily.append(orig)
data["MONEY"] = money_daily
day_data["MONEY"] = day_data["CSPX.L"]

# 計算所有會同時包含的日期,並刪除多餘的日期
intersection = list(set(day_data["CSPX.L"]) & set(day_data["AWSRIW.SW"]) & set(day_data["LQDA"])& set(day_data["WING"]))
for i in ("CSPX.L", "IDP6.L","IEMU.L", "EIMI.L","CPXJ.L","LCJD.L", "IDJP.L", "AWSRIW.SW", "CCAU.L", "ISFU.L", "VDTY.L", "IDTL.L","LQDA", "WING","MONEY"):
a = 0
for j in day_data[i]:
if j not in intersection:
data[i].pop(a)
else:
a+=1

# 計算它在不同時期,各股票的持有張數與金額
pct = [0.365, 0.0725, 0.1175, 0.0775, 0.02, 0.07, 0.01, 0.03, 0.0125, 0.045, 0.0575,0.015,0.065,0.0275,0.015]
nowday = 120
num_stock = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
inner_cnt = 0

# 第一期,計算剛開始的時候不同的股票各買幾張可以維持平衡
for i in data:
#print(i)
num_stock[inner_cnt] = (principal*pct[inner_cnt])/data[i][0]
inner_cnt += 1
returns = []
principal_halfyear = [1000000]
principal_his = [1000000]
max_value = 1000000
min_value = 1000000
for nowday in range(1, 1207):
if (nowday%120 == 0 or nowday == 1206):
if nowday == 1206:
nowday = -1
inner_cnt = 0
newprincipal = 0
for i in data:
stock_price = num_stock[inner_cnt] * data[i][nowday]
newprincipal += stock_price
inner_cnt += 1
inner_cnt = 0
price = []
for i in data:
num_stock[inner_cnt] = (newprincipal*pct[inner_cnt])/data[i][nowday]
price.append(data[i][nowday])
inner_cnt += 1
#print((num_stock[0]*data["CSPX.L"][nowday])/newprincipal) #這行code代表我希望每次再平衡之後,檢查是否都是維持在相同比例
principal_halfyear.append((newprincipal / principal) - 1)
nowday += INTERVAL

# 後來發現我們只需要記錄每一期的資料,所以不用那麼麻煩,下面就只是算max value跟Min value的
else:
inner_cnt = 0
newprincipal = 0
for i in data:
stock_price = num_stock[inner_cnt] * data[i][nowday]
newprincipal += stock_price
inner_cnt += 1

if newprincipal > max_value:
max_value = newprincipal
if newprincipal < min_value:
min_value = newprincipal
returns.append((newprincipal / principal) - 1)
principal_his.append(newprincipal)

final_principal = newprincipal
print("=============奈米投結果=============")
print("期末本金:{:6.9}元".format(final_principal))
print("年化報酬率:{:.2%}".format((final_principal/principal)**(1/5)-1))

overall_std = np.std(principal_halfyear[2:])
semiannual_std = overall_std * np.sqrt(2)
print("(半年)年化標準差:{:.2%}".format(semiannual_std))

annual = list()
cnt = 0
for i in principal_halfyear:
if cnt%2==1:
annual.append(i)
cnt += 1
annual_std = np.std(annual)
print("年化標準差:{:.2%}".format(annual_std))

# maxdrawdown以半年為一個單位計算
print("Max Drawdown:{:.2%}".format((max_value-min_value)/max_value))
rf_mean = 0.0218069347133758
sharpe_ratio = (((final_principal/principal)**(1/5)-1) - rf_mean) / annual_std
print("Sharpe比率:{:.5f}".format(sharpe_ratio))
print("===================================")

data = pd.DataFrame(data)
#計算關聯程度(這裡使用相關係數)
correlation_matrix = data.corr()
# 繪製關聯程度圖
plt.figure(figsize=(10, 8))
plt.matshow(correlation_matrix, cmap='coolwarm', fignum=1)
plt.xticks(range(len(correlation_matrix.columns)), correlation_matrix.columns, rotation='vertical')
plt.yticks(range(len(correlation_matrix.columns)), correlation_matrix.columns)
plt.colorbar()
plt.title('Correlation Matrix of Stock Prices')
plt.show()

plt.figure(figsize=(10, 6))
plt.plot(principal_his, linestyle='-')
# 加入標籤及標題
plt.ylabel('principal')
plt.title('FUBON - principal changes with time')
# 顯示圖表
plt.grid(True)
plt.show()

--

--

Vincent Ko
Vincent Ko

Written by Vincent Ko

又名為黑翅鳶羽札,2024年即將邁向大四,正在國泰銀行資訊部門實習,可能會帶來第一手GenAI相關知識。LLM、人工智慧、資料分析與處理;財金、管理、財金數據分析。

No responses yet