<jupyter_start><jupyter_text>公募基金组合  本段代码利用quantOS系统进行公募股票和债券基金收益率分析及组合构建，比较了等权重和波动率倒数加权法两种组合构建方法  系统设置<jupyter_code># encoding: utf-8
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
import seaborn as sns
import matplotlib.mlab as mlab
import scipy.stats as stats
from matplotlib.ticker import Formatter

sns.set_style('darkgrid')
sns.set_context('poster')
%matplotlib inline
from jaqs.data import RemoteDataService, DataApi
import jaqs.util as jutil
from __future__ import print_function, unicode_literals, division, absolute_import

from jaqs.data import RemoteDataService, DataView
dataview_dir_path = '.'
backtest_result_dir_path = '.'

import os
phone = os.environ.get('QUANTOS_USER')
token = os.environ.get('QUANTOS_TOKEN')

data_config = {
  "remote.data.address": "tcp://data.quantos.org:8910",
  "remote.data.username": phone,
  "timeout": 3600,
  "remote.data.password": <PASSWORD>
}

api_uat = DataApi(addr='tcp://data.quantos.org:8910')
api_uat.login(phone, token)

ds = RemoteDataService()
ds.init_from_config(data_config)<jupyter_output>Begin: DataApi login 17321165656@tcp://data.quantos.org:8910
    login success<jupyter_text>设置参数<jupyter_code>START_DATE, END_DATE = 20160101, 20180511<jupyter_output><empty_output><jupyter_text>从数据库中取出符合条件的基金<jupyter_code>def get_fundlist(api, invest_type, invest_style,launch_date):
    df, msg = api.query(
            view = 'lb.mfInfo',
            fields = 'invest_type,invest_style,status',
            filter = "",
            data_format = 'pandas'
    )
    df = df[(df['invest_type'] == invest_type)
           & (df['status'] == 101001000)
           & (df['invest_style'] == invest_style)
           & (df['setup_date'] < launch_date)]
    return df<jupyter_output><empty_output><jupyter_text>取出基金历史净值和指数的历史价格信息，注意需要用复权因子校正后的净值<jupyter_code>def get_fundnav(api, symbol, start_date, end_date):
    df, msg = api.query(
            view = 'lb.mfNav',
            fields = 'price_date,nav_adjusted',
            filter = 'start_pdate=' + str(start_date) + '&end_pdate=' + str(end_date) + '&symbol=' + symbol,
            data_format = 'pandas'
    )
    
    if df is None:
        print(df, msg)
    df.index = df['price_date'].astype(np.integer)
    df.sort_index(inplace = True)
    return df<jupyter_output><empty_output><jupyter_text>计算每只基金的表现，包括return，volatility，sharpe ratio，beta，最大回撤等<jupyter_code>def cal_return(api, symbol, start, end):

    df_nav = get_fundnav(api, symbol, start, end)
    if len(df_nav) == 0:
        return None, None, None
    strategy_value = pd.DataFrame(df_nav['nav_accumulated']).fillna(method = 'ffill')
    strategy_value.columns = ['strat']
    df_returns = strategy_value.pct_change(periods = 1).fillna(0.0)
    df_returns = df_returns.join((df_returns.loc[:, ['strat']] + 1.0).cumprod(), rsuffix = '_cum')

    start = pd.to_datetime(start, format = '%Y%m%d')
    end   = pd.to_datetime(end, format = '%Y%m%d')
    years = (end - start).days / 365.0

    strat_cum = df_returns['strat_cum'].values

#     max_dd_end = np.argmax(np.maximum.accumulate(strat_cum) - strat_cum)   # end of the period
#     max_dd_start   = np.argmax(strat_cum[:max_dd_end])
#     max_dd = (strat_cum[max_dd_start] - strat_cum[max_dd_end])/strat_cum[max_dd_end]

    # 计算收益率相关指标
    performance_metrics = dict()

    performance_metrics['Annual Return (%)'] =\
        100 * (np.power(df_returns.loc[:, 'strat_cum'].values[-1], 1./years) - 1)
    performance_metrics['Annual volatility (%)'] =\
        100 * (df_returns.loc[:, 'strat'].std() * np.sqrt(242))
    performance_metrics['Sharpe Ratio'] = (performance_metrics['Annual Return (%)']/performance_metrics['Annual volatility (%)'])

    # 计算风险相关指标
    risk_metrics = dict()
#     risk_metrics['Maximum Drawdown (%)'] = max_dd * 100
#     risk_metrics['Maximum Drawdown Start'] = df_returns.index[max_dd_end]
#     risk_metrics['Maximum Drawdown End'] = df_returns.index[max_dd_start]
    
    output_returns = df_returns.copy()
    output_returns['symbol'] = symbol
    output_returns = output_returns.reset_index()
    output_returns.columns = ['trade_date', 'strat','strat_cum','symbol']
    
    return performance_metrics, risk_metrics, output_returns<jupyter_output><empty_output><jupyter_text>将上述函数按流程组织在一起，计算出所有符合条件基金的表现<jupyter_code># 剔除指数型基金以及2018年1月1日后发行的股票和债券基金，债券基金选择发行规模最大的1000支作为研究对象
df = get_fundlist(api_uat, '股票型', '股票型', str(20180101))
df_bond = get_fundlist(api_uat, '债券型', '债券型', str(20180101))
df_bond = df_bond.sort_values('issue_totals', ascending = False).head(1000)
symbols = df['symbol'].values.tolist()
symbols_bond = df_bond['symbol'].values.tolist()
indicators = list()
indicators_bond = list()
daily_returns = pd.DataFrame()
daily_returns_bond = pd.DataFrame()
for symbol in symbols:
    performance_metrics, risk_metrics, output_returns = cal_return(api_uat, symbol, START_DATE, END_DATE)
    
    if performance_metrics is None:
        continue
    daily_returns = pd.concat([daily_returns, output_returns], axis = 0)
    indicators.append((symbol,
                      performance_metrics['Annual Return (%)'],
                      performance_metrics['Annual volatility (%)'],
                      performance_metrics['Sharpe Ratio'],
                      output_returns['strat_cum'].iat[-1]))
for symbol in symbols_bond:
    performance_metrics, risk_metrics, output_returns = cal_return(api_uat, symbol, START_DATE, END_DATE)
    
    if performance_metrics is None:
        continue
    daily_returns_bond = pd.concat([daily_returns_bond, output_returns], axis = 0)
    indicators_bond.append((symbol,
                      performance_metrics['Annual Return (%)'],
                      performance_metrics['Annual volatility (%)'],
                      performance_metrics['Sharpe Ratio'],
                      output_returns['strat_cum'].iat[-1]))
labels = ['symbol', 'AnnualReturn', 'AnnualVolatility', 'SharpeRatio', 'StratCumReturn']<jupyter_output><empty_output><jupyter_text>股票基金收益率描述统计<jupyter_code>df_returns = pd.DataFrame.from_records(indicators, columns = labels)
df_returns.describe()
df_returns = pd.merge(left = df_returns, right = df[['symbol', 'issue_totals']], how = 'left', on = 'symbol')<jupyter_output><empty_output><jupyter_text>股票基金年化收益率分布<jupyter_code># 画日对数收益率分布直方图
fig = plt.figure(figsize=(18, 18))
v = df_returns['AnnualReturn'].dropna().values
avgRet = np.mean(v)
stdRet = np.std(v)
x = np.linspace(avgRet - 3*stdRet, avgRet + 3*stdRet, 100)
y = mlab.normpdf(x, avgRet, stdRet)
kde = stats.gaussian_kde(v)

# plot the histogram
plt.subplot(211)
plt.hist(v, 50, weights = np.ones(len(v))/len(v), alpha = 0.4)
plt.axvline(x = avgRet, color = 'red', linestyle = '--', linewidth = 0.8, label = 'Mean Value')
plt.axvline(x = avgRet - 2 * stdRet, color = 'blue', linestyle = '--', linewidth = 0.8, label = '-2 Standard Deviation')
plt.axvline(x = avgRet + 2 * stdRet, color = 'blue', linestyle = '--', linewidth = 0.8, label = '2 Standard Deviation')
plt.ylabel('Percentage', fontsize = 10)
plt.legend(fontsize = 10)

# plot the kde and normal fit
plt.subplot(212)
plt.plot(x, kde(x), label = 'Kernel Density Estimation')
plt.plot(x, y, color = 'black', linewidth=1, label = 'Normal Fit')
plt.ylabel('Probability', fontsize = 10)
plt.axvline(x = avgRet, color = 'red', linestyle = '--', linewidth = 0.8, label = 'Mean Count')
plt.legend(fontsize = 10)<jupyter_output><empty_output><jupyter_text>债券基金收益率描述统计<jupyter_code>df_returns_bond = pd.DataFrame.from_records(indicators_bond, columns = labels)
df_returns_bond.describe()
df_returns_bond = pd.merge(left = df_returns_bond, right = df_bond[['symbol', 'issue_totals']], how = 'left', on = 'symbol')<jupyter_output><empty_output><jupyter_text>债券基金年化收益率分布<jupyter_code># 画日对数收益率分布直方图
fig = plt.figure(figsize=(18, 18))
v = df_returns_bond['AnnualReturn'].dropna().values
avgRet = np.mean(v)
stdRet = np.std(v)
x = np.linspace(avgRet - 3*stdRet, avgRet + 3*stdRet, 100)
y = mlab.normpdf(x, avgRet, stdRet)
kde = stats.gaussian_kde(v)

# plot the histogram
plt.subplot(211)
plt.hist(v, 50, weights = np.ones(len(v))/len(v), alpha = 0.4)
plt.axvline(x = avgRet, color = 'red', linestyle = '--', linewidth = 0.8, label = 'Mean Value')
plt.axvline(x = avgRet - 2 * stdRet, color = 'blue', linestyle = '--', linewidth = 0.8, label = '-2 Standard Deviation')
plt.axvline(x = avgRet + 2 * stdRet, color = 'blue', linestyle = '--', linewidth = 0.8, label = '2 Standard Deviation')
plt.ylabel('Percentage', fontsize = 10)
plt.legend(fontsize = 10)

# plot the kde and normal fit
plt.subplot(212)
plt.plot(x, kde(x), label = 'Kernel Density Estimation')
plt.plot(x, y, color = 'black', linewidth=1, label = 'Normal Fit')
plt.ylabel('Probability', fontsize = 10)
plt.axvline(x = avgRet, color = 'red', linestyle = '--', linewidth = 0.8, label = 'Mean Count')
plt.legend(fontsize = 10)
df_daily_returns_bond = daily_returns_bond.pivot_table(index = 'trade_date', columns = 'symbol', values = 'strat')
df_daily_returns_bond = df_daily_returns_bond.fillna(0.0)
df_daily_returns = daily_returns.pivot_table(index = 'trade_date', columns = 'symbol', values = 'strat')
df_daily_returns = df_daily_returns.fillna(0.0)<jupyter_output><empty_output><jupyter_text>比较同一股票基金在不同年份的表现，检验是否有持续性<jupyter_code>df_daily_returns_2016 = df_daily_returns.loc[:20170101]
df_daily_returns_2017 = df_daily_returns.loc[20170101:20180101]
df_daily_returns_2018 = df_daily_returns.loc[20180101:]
df_daily_returns_2016_cum = pd.DataFrame((df_daily_returns_2016 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_2016_cum.columns = ['2016rank']
df_daily_returns_2017_cum = pd.DataFrame((df_daily_returns_2017 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_2017_cum.columns = ['2017rank']
df_daily_returns_2018_cum = pd.DataFrame((df_daily_returns_2018 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_2018_cum.columns = ['2018rank']
df_rank = pd.concat([df_daily_returns_2016_cum, df_daily_returns_2017_cum, df_daily_returns_2018_cum], axis = 1)
df_rank.corr()<jupyter_output><empty_output><jupyter_text>比较同一债券基金在不同年份的表现，检验是否有持续性<jupyter_code>df_daily_returns_bond_2016 = df_daily_returns_bond.loc[:20170101]
df_daily_returns_bond_2017 = df_daily_returns_bond.loc[20170101:20180101]
df_daily_returns_bond_2018 = df_daily_returns_bond.loc[20180101:]
df_daily_returns_bond_2016_cum = pd.DataFrame((df_daily_returns_bond_2016 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_bond_2016_cum.columns = ['2016rank']
df_daily_returns_bond_2017_cum = pd.DataFrame((df_daily_returns_bond_2017 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_bond_2017_cum.columns = ['2017rank']
df_daily_returns_bond_2018_cum = pd.DataFrame((df_daily_returns_bond_2018 + 1).cumprod().iloc[-1, :].rank(ascending = False))
df_daily_returns_bond_2018_cum.columns = ['2018rank']
df_rank_bond = pd.concat([df_daily_returns_bond_2016_cum, df_daily_returns_bond_2017_cum, df_daily_returns_bond_2018_cum], axis = 1)
df_rank_bond.corr()<jupyter_output><empty_output><jupyter_text>挑选出在2016年表现最好的三支股票基金和债券基金构建组合<jupyter_code>stock_fund = df_daily_returns_2016_cum.sort_values('2016rank').index[:3].values.tolist()
bond_fund = df_daily_returns_bond_2016_cum.sort_values('2016rank').index[:3].values.tolist()
daily_ret_stock = df_daily_returns[stock_fund]
daily_ret_bond = df_daily_returns_bond[bond_fund]
daily_ret_all = pd.concat([daily_ret_stock, daily_ret_bond], axis = 1)<jupyter_output><empty_output><jupyter_text>1. 等权重组合基金<jupyter_code>df_ew = pd.DataFrame(daily_ret_all.mean(axis = 1))
df_ew.columns = ['daily_ew_ret']
print('Annual Return is: %.4f' % (df_ew.mean() * 242))
print('Annual Volatility is: %.4f' % (df_ew.std() * np.sqrt(242)))
print('Sharpe Ratio is: %.4f' % (df_ew.mean()/df_ew.std() * np.sqrt(242)))<jupyter_output>Annual Return is: 0.1026
Annual Volatility is: 0.0664
Sharpe Ratio is: 1.5452<jupyter_text>2. 根据波动率倒数组合基金<jupyter_code># 计算各支基金过去30个交易日波动率
df_volatility = pd.rolling_std(daily_ret_all, 30)
df_volatility = df_volatility.dropna()
# 根据波动率确定各支基金在组合中的权重
df_inverse_volatility = 1.0/df_volatility
df_inverse_volatility = df_inverse_volatility.replace(np.inf, 0.0)
df_weight = df_inverse_volatility.divide(df_inverse_volatility.sum(axis = 1), axis = 0)
# 根据权重组合各支基金
df_rp = pd.DataFrame(df_daily_returns.loc[df_weight.index].mul(df_weight).sum(axis = 1))
df_rp.columns = ['daily_rp_ret']
print('Annual Return is: %.4f' % (df_rp.mean() * 242))
print('Annual Volatility is: %.4f' % (df_rp.std() * np.sqrt(242)))
print('Sharpe Ratio is: %.4f' % (df_rp.mean()/df_rp.std() * np.sqrt(242)))
class MyFormatter(Formatter):
    def __init__(self, dates, fmt='%Y%m'):
        self.dates = dates
        self.fmt = fmt

    def __call__(self, x, pos=0):
        """Return the label for time x at position pos"""
        ind = int(np.round(x))
        if ind >= len(self.dates) or ind < 0:
            return ''

        # return self.dates[ind].strftime(self.fmt)
        return pd.to_datetime(self.dates[ind], format="%Y%m%d").strftime(self.fmt)
df_ret_compare = pd.concat([df_ew, df_rp], axis = 1)
df_ret_compare = df_ret_compare.dropna()
df_cum_ret_compare = (df_ret_compare + 1).cumprod()
fig, ax1 = plt.subplots(1, 1, figsize=(21, 10))

idx0 = df_cum_ret_compare.index
idx = range(len(idx0))

# ax1.plot(idx, df_cum_ret_compare.daily_issue_ret, lw=1.5, color='indianred', label='Issue value weight')
ax1.plot(idx, df_cum_ret_compare.daily_ew_ret, lw=1.5, color='royalblue', label='Equal weight')
ax1.plot(idx, df_cum_ret_compare.daily_rp_ret, lw=1.5, color='purple', label='Reverse volatility weight')

# ax1.axhline(0.0, color='k', lw=0.5, ls='--')

ax1.legend(loc='upper left')
ax1.set_xlabel("Date")
ax1.set_ylabel("Return")
ax1.xaxis.set_major_formatter(MyFormatter(idx0, '%Y%m%d'))

plt.tight_layout()
# fig.savefig('lgt_brinson_cum.png')
# plt.close()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14495979322228605, 'fraction_numerical': 0.037765651924181506, 'mean_word_length': 5.368998628257888, 'pattern_counts': {'":': 4, '<': 62, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '522008', 'license': 'Apache-2.0', 'n_tokens_mistral': 6049, 'n_tokens_neox': 5524, 'n_words': 854, 'path': 'example/WallStreetLectures/ipython/lecture19_MutualFunds.ipynb', 'repo_name': 'quan8tum/JAQS', 'size': '13923'}