本周三上課講到了投資組合收益率的計算,課本上的例題把收益率計算涉及到的數據都給全了,同學們做了一個帶概率的加權平均收益率計算。從課堂反應來看,似乎同學們都能明白例題的計算過程;從課堂提問情況來看,似乎同學們又沒明白計算過程背後的數據信息。我在課後便針對收益率問題,做了一個梳理,查閱了好幾個教材,發現該主題的例題幾乎如出一轍,不同的僅為參數的調整。
為了換個思路來學習這個知識點,算是自我再學習,也是算我給學生們的答疑解惑。因剛好處在一周的因疫情居家隔離期間,消磨時間,我設計了一個案例:從中國股市中任意選取4支股票(實際上,我編寫的python程序可以處理N多支股票的組合),用1年的日交易收盤價(可以是N年的數據)來計算确定股票投資組合的收益率及風險。
介紹該案例的目的是讓我的學生們懂得實務中的收益率是怎麼計算出來的,風險是怎麼度量的,數據是如何取得的,數據是如何處理的......,最終,這麼大、這麼複雜的數據是怎麼通過python程序計算的......
本文先介紹收益率,下一篇文章将介紹風險。
時間無限,生命有限,強烈建議您趕緊使用計算機程序來幫您進行财務計算和推演,手算真的不足以支撐您的欲壑!
正文起:
對普通投資者來說,資産收益率體現了該資産的投資機會,并且與其投資規模無關;資産收益率序列比價格序列有更好的統計性質。為認知清楚收益率的本質,有必要先搞清下面幾個概念。
一、單項資産收益率1、單期簡單收益率
如果從第t-1 天到第t天(一個周期)持有某項資産,則該項資産的簡單收益率為:
則:
2、多期簡單收益率
如果從第t-k天到第t天,在這k天持有某項資産(k個周期),則該項資産的k期簡單收益率為:
則:
又:
即:
故:
即:單項資産持有k期的簡單收益率等于它所包含的k個簡單收益率的乘積減去1。
3、持有K期的年化收益率為:
二、投資組合收益率
投資組合收益率是投資組合中各單項資産收益率的加權平均數。權重為投資金額的占比。
所以:
即:
三、計算程序
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 29 17:08:53 2022
@author: zgr
weixin ID:
"""
import warnings
import pandas as pd
import numpy as np
import tushare as ts
import matplotlib.pyplot as plt
pro = ts.pro_api()
# 股票投資組合:盛和資源,北方稀土,廣晟有色,五礦稀土
tickers = '600392.SH, 600111.SH, 600259.SH, 000831.SZ'
# 投資權重
weight = pd.DataFrame([{'000831.SZ':0.3,'600111.SH':0.3,'600259.SH':0.15,'600392.SH':0.25}])
# 1年的交易數據
data = pro.daily(ts_code=tickers , start_date='20200101', end_date='20201231')
# 查看是否有空值:沒有空值
data.info()
# 對收盤價進行透視
close_pivot = data.pivot_table(values="close",index='trade_date',columns='ts_code')
close_pivot.info()
# 對返回值進行檢查
def _check_returns(returns):
# Check NaNs excluding leading NaNs
if np.any(np.isnan(returns.mask(returns.ffill().isnull(), 0))):
warnings.warn(
"Some returns are NaN. Please check your price data.", UserWarning
)
if np.any(np.isinf(returns)):
warnings.warn(
"Some returns are infinite. Please check your price data.", UserWarning
)
# 根據價格計算日收率
def returns_from_prices(prices, log_returns=False):
"""
Calculate the returns given prices.
:param prices: adjusted (daily) closing prices of the asset, each row is a
date and each column is a ticker/id.
:type prices: pd.DataFrame
:param log_returns: whether to compute using log returns
:type log_returns: bool, defaults to False
:return: (daily) returns
:rtype: pd.DataFrame
"""
if log_returns:
returns = np.log(1 prices.pct_change()).dropna(how="all")
else:
returns = prices.pct_change().dropna(how="all")
return returns
# 根據價格計算各單項資産年收益率
def mean_historical_return(
prices, returns_data=False, compounding=True, frequency=252, log_returns=False
):
"""
Calculate annualised mean (daily) historical return from input (daily) asset prices.
Use ``compounding`` to toggle between the default geometric mean (CAGR) and the
arithmetic mean.
:param prices: adjusted closing prices of the asset, each row is a date
and each column is a ticker/id.
:type prices: pd.DataFrame
:param returns_data: if true, the first argument is returns instead of prices.
These **should not** be log returns.
:type returns_data: bool, defaults to False.
:param compounding: computes geometric mean returns if True,
arithmetic otherwise, optional.
:type compounding: bool, defaults to True
:param frequency: number of time periods in a year, defaults to 252 (the number
of trading days in a year)
:type frequency: int, optional
:param log_returns: whether to compute using log returns
:type log_returns: bool, defaults to False
:return: annualised mean (daily) return for each asset
:rtype: pd.Series
"""
if not isinstance(prices, pd.DataFrame):
warnings.warn("prices are not in a dataframe", RuntimeWarning)
prices = pd.DataFrame(prices)
if returns_data:
returns = prices
else:
returns = returns_from_prices(prices, log_returns)
_check_returns(returns)
if compounding:
return (1 returns).prod() ** (frequency / returns.count()) - 1
else:
return returns.mean() * frequency
# 返回單項資産日收益率(根據收盤價計算)
returns_assets_daily = returns_from_prices(close_pivot, log_returns=False)
# 日收益率曲線(日收益可視化)
returns_assets_daily.plot(subplots=True,figsize=(8,6))
# 日收益率直方圖
returns_assets_daily.hist(bins=20)
# 日收益率極差、四分位差、方差、标準差和離散系數
ret_des = returns_assets_daily.describe().T
ret_des['var_coef'] = ret_des['std']/ret_des['mean']
ret_des = ret_des.T
# 計算投資組合的收益率
def _objective_value(w, obj):
"""
Helper method to return either the value of the objective function
or the objective function as a cvxpy object depending on whether
w is a cvxpy variable or np array.
:param w: weights
:type w: np.ndarray OR cp.Variable
:param obj: objective function expression
:type obj: cp.Expression
:return: value of the objective function OR objective function expression
:rtype: float OR cp.Expression
"""
if isinstance(w, np.ndarray):
if np.isscalar(obj):
return obj
elif np.isscalar(obj.value):
return obj.value
else:
return obj.value.item()
else:
return obj
def portfolio_return(w, expected_returns, negative=True):
"""
Calculate the (negative) mean return of a portfolio
:param w: asset weights in the portfolio
:type w: np.ndarray OR cp.Variable
:param expected_returns: expected return of each asset
:type expected_returns: np.ndarray
:param negative: whether quantity should be made negative (so we can minimise)
:type negative: boolean
:return: negative mean return
:rtype: float
"""
sign = 1 if negative else -1
mu = w @ expected_returns
return _objective_value(w, sign * mu)
# 返回單項資産年收益率(根據收盤價計算)
returns_assets = mean_historical_return(close_pivot)
# 投資組合的年收益率
returns_portfolio = portfolio_return(weight,returns_assets)
returns_assets['portfolio'] = float(np.around(returns_portfolio.values, 6))
# 收益率可視化
returns_assets.plot(kind='bar',rot=45)
plt.title('returns of assets and portfolio')
plt.xlabel('tscode')
plt.ylabel('annual returns')
結果:
【僅供參考】
授人玫瑰,手留餘香。請關注本公衆号!歡迎轉發!鼓勵分享!,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!