การทำ Portfolio optimization

การทำ Portfolio optimization ด้วย Python

NUTHDANAI WANGPRATHAM
4 min readDec 31, 2020

การลงทุนตามดัชนีที่แบ่งนํ้าหนักตาม 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

อ่านตอนอื่นๆได้ที่

  1. การคำนวณผลตอบแทนการลงทุนด้วย Python
  2. การหาความผันผวนของพอร์ตการลงทุนด้วย Python
  3. การหา Max Drawdown ด้วย Python
  4. การวัด การเบี่ยงเบนของผลตอบแทนด้วย Python
  5. การวัด SemiDeviation ด้วย Python
  6. การวัด VaR. และ CVaR. ด้วย Python
  7. รีวิวการใช้ ffn. ใน Python
  8. การหา Top Drawdown ด้วย Python
  9. การหาค่า Sharpe ratio ด้วย Python
  10. การหากลุ่มหลักรัพย์ที่เส้นประสิทธิภาพ Efficient Frontier ด้วย Python
  11. การหา shape ratio สูงสุดและเส้น CML ด้วย Python
  12. การสร้างมูลค่าตลาดแบบถ่วงน้ำหนักด้วย PYTHON
  13. ข้อจำกัดของการกระจายความเสี่ยงและการทำประกันพอร์ตการลงทุน
  14. การจำลอง ผลตอบแทนด้วย RANDOM WALK Generation และ Montecarlo simulation
  15. Sharpe Style Analysis
  16. Factor Investing ด้วย Python
  17. วิเคราะห์ ประเภทกองทุนรวมด้วย Python
  18. การทำ Portfolio optimization
  19. สร้างแนวรับแนวต้านวิเคราะห์หุ้นด้วย Python

--

--

NUTHDANAI WANGPRATHAM
NUTHDANAI WANGPRATHAM

Written by NUTHDANAI WANGPRATHAM

I am a learner and have a multipotential life. You can contact me at nutdnuy@gmail.com

No responses yet