การทำ Portfolio optimization
การทำ Portfolio optimization ด้วย Python
การลงทุนตามดัชนีที่แบ่งนํ้าหนักตาม Market cap หรือมูลค่าตลาด เป็นการลงทุนที่ง่ายและมีค่าใช้จ่ายถูกเป็นทางเลือกเริ่มต้นสำหรับนักลงทุนจำนวนมาก
อย่างไรก็ตามเราได้พบว่าการลงทุนแบบนั้นมีข้อเสียในการกระจายความเสี่ยงของการลงทุนซึ่งเราสามารถปรับปรุงสิ่งนี้ได้ หนึ่งในนั้นคือเทคนิค Effective weight
ตอนนี้เราจะดำเนินการเกี่ยวกับเทคนิคการสร้างพอร์ตโฟลิโอที่ซับซ้อนมากขึ้น แต่จะทำให้เรามีส่วนร่วมในการประมาณราคาซึ่งเป็นสิ่งที่เราหลีกเลี่ยงไปแล้ว …
IMPORT_LIBERLY
สิ่งแรกที่เราต้องทำเหมือนทุกครั้งคือ IMPORT LIBERLY
ดึงข้อมูล
ข้อมูลที่เราใชคือข้อมูลรายอุตสาหกรรมของตลาดอเมริกา เข้าไปโหลดได้ที่นี้นะครับ https://raw.githubusercontent.com/nutdnuy/Portfolio_optimization_with_Python อย่างแรกคือดึงผลตอบแทนรายปี
ind_return = pd.read_csv("https://raw.githubusercontent.com/nutdnuy/Portfolio_optimization_with_Python/master/data/ind49_m_ew_rets.csv", header=0, index_col=0)/100ind_return.index = pd.to_datetime(ind_return.index, format="%Y%m").to_period('M')ind_rets = ind_return[("1970"):]ind_rets
ดึงจำนวนบริษัท
ind_nfirms = pd.read_csv("https://raw.githubusercontent.com/nutdnuy/Portfolio_optimization_with_Python/master/data/ind49_m_nfirms.csv", header=0, index_col=0)ind_nfirms.index = pd.to_datetime(ind_nfirms.index, format="%Y%m").to_period('M')ind_nfirms = ind_nfirms[("1970"):]
ดึงขนาดของแต่ละอุตสาหกรรม
ind_size = pd.read_csv(“https://raw.githubusercontent.com/nutdnuy/Portfolio_optimization_with_Python/master/data/ind49_m_size.csv", header=0, index_col=0)ind_size.index = pd.to_datetime(ind_size.index, format=”%Y%m”).to_period(‘M’)ind_size = ind_size[(“1970”):]
หามูลค่าตลาดของอุตสาหกรรม
ind_mktcap = ind_nfirms * ind_sizetotal_mktcap = ind_mktcap.sum(axis=1)ind_capweight = ind_mktcap.divide(total_mktcap, axis="rows")
ind_mcap
BACK TEST EW vs CW
เราจะลองหาผลตอบแทนย้อนหลังของทั้งสองวิธี CW คือการลงทุนตามมูลต่าตลาด ส่วน EW คือEffective weight หรือนํ้าหนักสัดส่วนที่ให้ค่าความแปรปรวนร่วมดีที่สุด
def weight_ew(r, cap_weights=None, max_cw_mult=None, microcap_threshold=None, **kwargs):n = len(r.columns)ew = pd.Series(1/n, index=r.columns)if cap_weights is not None:cw = cap_weights.loc[r.index[0]] # starting cap weight## exclude microcapsif microcap_threshold is not None and microcap_threshold > 0:microcap = cw < microcap_thresholdew[microcap] = 0ew = ew/ew.sum()#limit weight to a multiple of capweightif max_cw_mult is not None and max_cw_mult > 0:ew = np.minimum(ew, cw*max_cw_mult)ew = ew/ew.sum() #reweightreturn ew
สร้างฟังชั่น CW
def weight_cw(r, cap_weights, **kwargs):"""Returns the weights of the CW portfolio based on the time series of capweights"""w = cap_weights.loc[r.index[0]]return w/w.sum()
ทดสอบรูปแบบการถ่วงน้ำหนักที่กำหนดโดยมีพารามิเตอร์บางตัว: r: ผลตอบแทนของสินทรัพย์เพื่อใช้ในการสร้างผลงาน Estation_window: คาบเวลาที่ใช้ในการประมาณค่าพารามิเตอร์ สัดส่วน: รูปแบบการถ่วงน้ำหนักที่จะใช้ต้องเป็นฟังก์ชันที่ใช้ “r” และ ค่าตัวแปร
def backtest_ws(r, estimation_window=60, weighting=weight_ew, verbose=False, **kwargs):n_periods = r.shape[0]# return windowswindows = [(start, start+estimation_window) for start in range(n_periods-estimation_window)]weights = [weighting(r.iloc[win[0]:win[1]], **kwargs) for win in windows]# convert List of weights to DataFrameweights = pd.DataFrame(weights, index=r.iloc[estimation_window:].index, columns=r.columns)returns = (weights * r).sum(axis="columns", min_count=1) #mincount is to generate NAs if all inputs are NAsreturn returns
หลังจากนั้นเรามาลอง รันดู
ewr = backtest_ws(ind_rets, estimation_window=36, weighting=weight_ew)cwr = backtest_ws(ind_rets, estimation_window=36, weighting=weight_cw, cap_weights=ind_mcap)btr = pd.DataFrame({"EW": ewr, "CW": cwr})(1+btr).cumprod().plot(figsize=(12,6), title="Industry Portfolios - CW vs EW")summary_stats(btr.dropna())
effective weight จะเห็นได้ว่า EW ให้ผลตอบแแทนต่อความเสี่ยงที่ดีกว่า
อีกวิธีหนึ่งคือสัดส่วนที่เป็นGlobal Minimum Variance Portfolio ตามทฤษฎีPortfolio ของ Markowitz
การสร้าง Global Minimum Variance Portfolio
อันดับเเรกเราต้องสร้างฟังชั่นเพื่อหาความแปรปรวนร่วมหรือ Covariance
def sample_cov(r, **kwargs):return r.cov()
แล้วสร้างสัดส่วนการลงทุน GMV จากค่า Covariance
def weight_gmv(r, cov_estimator=sample_cov, **kwargs):est_cov = cov_estimator(r, **kwargs)return gmv(est_cov)
สร้างฟังชันเพื่อหาสัดส่วนการลงทุนที่ให้ความผันผวนที่ตํ่าที่สุด
def gmv(cov):n = cov.shape[0]return msr(0, np.repeat(1, n), cov)
เรามาลองอสดงผลดูกัน
mv_s_r = backtest_ws(ind_rets, estimation_window=36, weighting=weight_gmv, cov_estimator=sample_cov)btr = pd.DataFrame({"EW": ewr, "CW": cwr, "GMV-Sample": mv_s_r})(1+btr).cumprod().plot(figsize=(12,6), title="Industry Portfolios")summary_stats(btr.dropna())
เราจะเห็นได้ว่าพอร์ต GMV ให้ค่าความเสี่ยงที่ตํ่าที่สุด
ตอนนี้เรามาลองตัวประมาณค่าใหม่ — ความสัมพันธ์คงที่ แนวคิดง่ายๆคือใช้เมทริกซ์สหสัมพันธ์ตัวอย่างคำนวณสหสัมพันธ์เฉลี่ยแล้วสร้างเมทริกซ์ความแปรปรวนร่วมขึ้นใหม่ ความสัมพันธ์ระหว่างสหสัมพันธ์ρและความแปรปรวนร่วมσกำหนดโดย:
#import statsmodels.stats.moment_helpers as mhdef cc_cov(r, **kwargs):"""Estimates a covariance matrix by using the Elton/Gruber Constant Correlation model"""rhos = r.corr()n = rhos.shape[0]# this is a symmetric matrix with diagonals all 1 - so the mean correlation is ...rho_bar = (rhos.values.sum()-n)/(n*(n-1))ccor = np.full_like(rhos, rho_bar)np.fill_diagonal(ccor, 1.)sd = r.std()ccov = ccor * np.outer(sd, sd)# mh.corr2cov(ccor, sd)return pd.DataFrame(ccov, index=r.columns, columns=r.columns)
หาสัดส่วนจากการลงทุนในแต่ละพอร์ต
wts = pd.DataFrame({"EW": weight_ew(ind_rets["2016":]),"CW": weight_cw(ind_rets["2016":], cap_weights=ind_mcap),"GMV-Sample": weight_gmv(ind_rets["2016":], cov_estimator=sample_cov),"GMV-ConstCorr": weight_gmv(ind_rets["2016":], cov_estimator=cc_cov),})wts.T.plot.bar(stacked=True, figsize=(15,6), legend=False);
mv_cc_r = backtest_ws(ind_rets, estimation_window=36, weighting=weight_gmv, cov_estimator=cc_cov)btr = pd.DataFrame({"EW": ewr, "CW": cwr, "GMV-Sample": mv_s_r, "GMV-CC": mv_cc_r})(1+btr).cumprod().plot(figsize=(12,6), title="Industry Portfolios")summary_stats(btr.dropna())
Statistical Shrinkage
เราสามารถผสมแบบจำลองและค่าประมาณตัวอย่างได้โดยเลือกพารามิเตอร์การหดตัว คุณสามารถปล่อยให้ตัวเลขกำหนดค่าการหดตัวที่เหมาะสมที่สุดสำหรับ delta แม้ว่าในทางปฏิบัติผู้ปฏิบัติงานหลายคนเลือก 0.5 ลองใช้ตัวประมาณค่าความแปรปรวนร่วมแบบการหดตัวแบบง่ายที่ย่อขนาดเข้าสู่การประมาณค่าความสัมพันธ์คงที่
def shrinkage_cov(r, delta=0.5, **kwargs):"""Covariance estimator that shrinks between the Sample Covariance and the Constant Correlation Estimators"""prior = cc_cov(r, **kwargs)sample = sample_cov(r, **kwargs)return delta*prior + (1-delta)*sample
mv_sh_r = backtest_ws(ind_rets, estimation_window=36, weighting=weight_gmv, cov_estimator=shrinkage_cov, delta=0.5)btr = pd.DataFrame({"EW": ewr, "CW": cwr, "GMV-Sample": mv_s_r, "GMV-CC": mv_cc_r, 'GMV-Shrink 0.5': mv_sh_r})(1+btr).cumprod().plot(figsize=(12,6), title="49 Industry Portfolios")summary_stats(btr.dropna())
Note book
อ่านตอนอื่นๆได้ที่
- การคำนวณผลตอบแทนการลงทุนด้วย Python
- การหาความผันผวนของพอร์ตการลงทุนด้วย Python
- การหา Max Drawdown ด้วย Python
- การวัด การเบี่ยงเบนของผลตอบแทนด้วย Python
- การวัด SemiDeviation ด้วย Python
- การวัด VaR. และ CVaR. ด้วย Python
- รีวิวการใช้ ffn. ใน Python
- การหา Top Drawdown ด้วย Python
- การหาค่า Sharpe ratio ด้วย Python
- การหากลุ่มหลักรัพย์ที่เส้นประสิทธิภาพ Efficient Frontier ด้วย Python
- การหา shape ratio สูงสุดและเส้น CML ด้วย Python
- การสร้างมูลค่าตลาดแบบถ่วงน้ำหนักด้วย PYTHON
- ข้อจำกัดของการกระจายความเสี่ยงและการทำประกันพอร์ตการลงทุน
- การจำลอง ผลตอบแทนด้วย RANDOM WALK Generation และ Montecarlo simulation
- Sharpe Style Analysis
- Factor Investing ด้วย Python
- วิเคราะห์ ประเภทกองทุนรวมด้วย Python
- การทำ Portfolio optimization
- สร้างแนวรับแนวต้านวิเคราะห์หุ้นด้วย Python