def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None, auto_reload=False): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.client = DATABASE.risk self.client.create_index([("account_cookie", ASCENDING), ("user_cookie", ASCENDING), ("portfolio_cookie", ASCENDING)], unique=True) if auto_reload: pass else: self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if market_data == None: if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = QA_fetch_future_day_adv( self.account.code, self.account.start_date, self.account.end_date) else: self.market_data = market_data self.if_fq = if_fq if self.account.market_type == MARKET_TYPE.FUTURE_CN: self.if_fq = False # 如果是期货, 默认设为FALSE if self.market_value is not None: self._assets = ( self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') else: self._assets = self.account.daily_cash.set_index( 'date').cash.fillna(method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold TODO: 资金利用率 反应资金的利用程度 股票周转率 反应股票的持仓天数 预期PNL/统计学PNL """ def __init__(self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None, auto_reload=False): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.client = DATABASE.risk self.client.create_index([("account_cookie", ASCENDING), ("user_cookie", ASCENDING), ("portfolio_cookie", ASCENDING)], unique=True) if auto_reload: pass else: self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if market_data == None: if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = QA_fetch_future_day_adv( self.account.code, self.account.start_date, self.account.end_date) else: self.market_data = market_data self.if_fq = if_fq if self.account.market_type == MARKET_TYPE.FUTURE_CN: self.if_fq = False # 如果是期货, 默认设为FALSE if self.market_value is not None: if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True: print('margin!') self._assets = ( self.account.daily_frozen + self.account.daily_cash.set_index('date').cash ).dropna() else: self._assets = ( self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash).fillna( method='pad') else: self._assets = self.account.daily_cash.set_index( 'date').cash.fillna(method='pad') self.time_gap = QA_util_get_trade_gap(self.account.start_date, self.account.end_date) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property def total_timeindex(self): return self.account.trade_range @property def market_value(self): """每日每个股票持仓市值表 Returns: pd.DataFrame -- 市值表 """ if self.account.daily_hold is not None: if self.if_fq: return (self.market_data.to_qfq().pivot('close').fillna( method='ffill') * self.account.daily_hold.apply(abs)).fillna( method='ffill') else: return ( self.market_data.pivot('close').fillna(method='ffill') * self.account.daily_hold.apply(abs)).fillna(method='ffill') else: return None @property def daily_market_value(self): """每日持仓总市值表 Returns: pd.DataFrame -- 市值表 """ if self.market_value is not None: return self.market_value.sum(axis=1) else: return None @property def assets(self): x1 = self._assets.reset_index() return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0] @property def max_dropback(self): """最大回撤 """ return round( float( max([(self.assets.iloc[idx] - self.assets.iloc[idx::].min()) / self.assets.iloc[idx] for idx in range(len(self.assets))])), 2) @property def total_commission(self): """总手续费 """ return float( -abs(round(self.account.history_table.commission.sum(), 2))) @property def total_tax(self): """总印花税 """ return float(-abs(round(self.account.history_table.tax.sum(), 2))) @property def profit_construct(self): """利润构成 Returns: dict -- 利润构成表 """ return { 'total_buyandsell': round(self.profit_money - self.total_commission - self.total_tax, 2), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'total_profit': self.profit_money } @property def profit_money(self): """盈利额 Returns: [type] -- [description] """ return float(round(self.assets.iloc[-1] - self.assets.iloc[0], 2)) @property def profit(self): """盈利率(百分比) Returns: [type] -- [description] """ return round(float(self.calc_profit(self.assets)), 2) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return round( float(self.calc_annualize_return(self.assets, self.time_gap)), 2) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return round(float(self.profit_pct.std() * math.sqrt(250)), 2) @property def ir(self): return round(self.calc_IR(), 2) @property @lru_cache() def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': round(self.annualize_return, 2), 'profit': round(self.profit, 2), 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'bm_annualizereturn': self.benchmark_annualize_return, 'bm_profit': self.benchmark_profit, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_cash': "%0.2f" % (float(self.assets[0])), 'last_assets': "%0.2f" % (float(self.assets.iloc[-1])), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'profit_money': self.profit_money, 'assets': list(self.assets), 'benchmark_assets': list(self.benchmark_assets), 'timeindex': self.account.trade_day, 'totaltimeindex': self.total_timeindex, 'ir': self.ir, 'month_profit': self.month_assets_profit.to_dict() # 'init_assets': round(float(self.init_assets), 2), # 'last_assets': round(float(self.assets.iloc[-1]), 2) } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type](self.benchmark_code, self.account.start_date, self.account.end_date) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return (self.benchmark_data.close / float(self.benchmark_data.close.iloc[0]) * float(self.assets[0])) @property def benchmark_profit(self): """ 基准组合的收益 """ return round(float(self.calc_profit(self.benchmark_assets)), 2) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return round( float( self.calc_annualize_return(self.benchmark_assets, self.time_gap)), 2) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ try: res = round( float( self.calc_beta(self.profit_pct.dropna(), self.benchmark_profitpct.dropna())), 2) except: print('贝塔计算错误。。') res = 0 return res @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return round( float( self.calc_alpha(self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05)), 2) @property def sharpe(self): """ 夏普比率 """ return round( float( self.calc_sharpe(self.annualize_return, self.volatility, 0.05)), 2) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return round((float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) / (float(days) / 250), 2) def calc_profitpctchange(self, assets): return assets[::-1].pct_change()[::-1] def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha(self, annualized_returns, benchmark_annualized_returns, beta, r=0.05): alpha = (annualized_returns - r) - (beta) *\ (benchmark_annualized_returns - r) return alpha def calc_IR(self): """计算信息比率 Returns: [type] -- [description] """ if self.volatility == 0: return 0 else: return self.annualize_return / self.volatility def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ # 会出现0 if volatility_year == 0: return 0 return (annualized_returns - r) / volatility_year @property def max_holdmarketvalue(self): """最大持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.max() else: return 0 @property def min_holdmarketvalue(self): """最小持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.min() else: return 0 @property def average_holdmarketvalue(self): """平均持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.mean() else: return 0 @property def max_cashhold(self): """最大闲置资金 """ return self.account.daily_cash.cash.max() @property def min_cashhold(self): """最小闲置资金 """ return self.account.daily_cash.cash.min() @property def average_cashhold(self): """平均闲置资金 Returns: [type] -- [description] """ return self.account.daily_cash.cash.mean() def save(self): """save to mongodb """ save_riskanalysis(self.message) def plot_assets_curve(self, length=14, height=12): """ 资金曲线叠加图 @Roy T.Burns 2018/05/29 修改百分比显示错误 """ plt.style.use('ggplot') plt.figure(figsize=(length, height)) plt.subplot(211) plt.title('BASIC INFO', fontsize=12) plt.axis([0, length, 0, 0.6]) plt.axis('off') i = 0 for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']: plt.text(i, 0.5, '{} : {}'.format(item, self.message[item]), fontsize=10, rotation=0, wrap=True) i += (length / 2.8) i = 0 for item in ['benchmark_code', 'time_gap', 'max_dropback']: plt.text(i, 0.4, '{} : {}'.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += (length / 2.8) i = 0 for item in ['annualize_return', 'bm_annualizereturn', 'profit']: plt.text(i, 0.3, '{} : {} %'.format(item, self.message.get(item, 0) * 100), fontsize=10, ha='left', rotation=0, wrap=True) i += length / 2.8 i = 0 for item in ['init_cash', 'last_assets', 'volatility']: plt.text(i, 0.2, '{} : {} '.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True) i += length / 2.8 i = 0 for item in ['alpha', 'beta', 'sharpe']: plt.text(i, 0.1, '{} : {}'.format(item, self.message[item]), ha='left', fontsize=10, rotation=0, wrap=True) i += length / 2.8 plt.subplot(212) self.assets.plot() self.benchmark_assets.xs(self.benchmark_code, level=1).plot() asset_p = mpatches.Patch(color='red', label='{}'.format( self.account.account_cookie)) asset_b = mpatches.Patch( label='benchmark {}'.format(self.benchmark_code)) plt.legend(handles=[asset_p, asset_b], loc=1) plt.title('ASSET AND BENCKMARK') return plt @property def month_assets(self): return self.assets.resample('M').last() @property def month_assets_profit(self): res = pd.concat([pd.Series(self.assets.iloc[0]), self.month_assets]).diff().dropna() res.index = res.index.map(str) return res @property def daily_assets_profit(self): return self.assets.diff() def plot_dailyhold(self, start=None, end=None): """ 使用热力图画出每日持仓 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 8)) sns.heatmap(self.account.daily_hold.reset_index().drop( 'account_cookie', axis=1).set_index('date').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title('HOLD TABLE --ACCOUNT: {}'.format( self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def plot_signal(self, start=None, end=None): """ 使用热力图画出买卖信号 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 18)) sns.heatmap(self.account.trade.reset_index().drop( 'account_cookie', axis=1).set_index('datetime').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax) ax.set_title('SIGNAL TABLE --ACCOUNT: {}'.format( self.account.account_cookie)) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def generate_plots(self): """ 生成图像 """ self.plot_assets_curve() self.plot_dailyhold() self.plot_signal()
def __init__( self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None, auto_reload=False ): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.client = DATABASE.risk self.client.create_index( [ ("account_cookie", ASCENDING), ("user_cookie", ASCENDING), ("portfolio_cookie", ASCENDING) ], unique=True ) if auto_reload: pass else: self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if market_data == None: if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date ) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = QA_fetch_future_day_adv( self.account.code, self.account.start_date, self.account.end_date ) else: self.market_data = market_data self.if_fq = if_fq if self.account.market_type == MARKET_TYPE.FUTURE_CN: self.if_fq = False # 如果是期货, 默认设为FALSE if self.market_value is not None: if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True: print('margin!') self._assets = ( self.account.daily_frozen + self.account.daily_cash.set_index('date').cash ).dropna() else: self._assets = ( self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash ).fillna(method='pad') else: self._assets = self.account.daily_cash.set_index('date' ).cash.fillna( method='pad' ) self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date ) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets
class QA_Risk(): """QARISK 是一个风险插件 需要加载一个account/portfolio类进来: 需要有 code,start_date,end_date,daily_cash,daily_hold TODO: 资金利用率 反应资金的利用程度 股票周转率 反应股票的持仓天数 预期PNL/统计学PNL """ def __init__( self, account, benchmark_code='000300', benchmark_type=MARKET_TYPE.INDEX_CN, if_fq=True, market_data=None, auto_reload=False ): """ account: QA_Account类/QA_PortfolioView类 benchmark_code: [str]对照参数代码 benchmark_type: [QA.PARAM]对照参数的市场 if_fq: [Bool]原account是否使用复权数据 if_fq选项是@尧提出的,关于回测的时候成交价格问题(如果按不复权撮合 应该按不复权价格计算assets) """ self.account = account self.benchmark_code = benchmark_code # 默认沪深300 self.benchmark_type = benchmark_type self.client = DATABASE.risk self.client.create_index( [ ("account_cookie", ASCENDING), ("user_cookie", ASCENDING), ("portfolio_cookie", ASCENDING) ], unique=True ) if auto_reload: pass else: self.fetch = { MARKET_TYPE.STOCK_CN: QA_fetch_stock_day_adv, MARKET_TYPE.INDEX_CN: QA_fetch_index_day_adv } if market_data == None: if self.account.market_type == MARKET_TYPE.STOCK_CN: self.market_data = QA_fetch_stock_day_adv( self.account.code, self.account.start_date, self.account.end_date ) elif self.account.market_type == MARKET_TYPE.FUTURE_CN: self.market_data = QA_fetch_future_day_adv( self.account.code, self.account.start_date, self.account.end_date ) else: self.market_data = market_data self.if_fq = if_fq if self.account.market_type == MARKET_TYPE.FUTURE_CN: self.if_fq = False # 如果是期货, 默认设为FALSE if self.market_value is not None: if self.account.market_type == MARKET_TYPE.FUTURE_CN and self.account.allow_margin == True: print('margin!') self._assets = ( self.account.daily_frozen + self.account.daily_cash.set_index('date').cash ).dropna() else: self._assets = ( self.market_value.sum(axis=1) + self.account.daily_cash.set_index('date').cash ).fillna(method='pad') else: self._assets = self.account.daily_cash.set_index('date' ).cash.fillna( method='pad' ) self.time_gap = QA_util_get_trade_gap( self.account.start_date, self.account.end_date ) self.init_cash = self.account.init_cash self.init_assets = self.account.init_assets def __repr__(self): return '< QA_RISK ANALYSIS ACCOUNT/PORTFOLIO >' def __call__(self): return pd.DataFrame([self.message]) @property def total_timeindex(self): return self.account.trade_range @property def market_value(self): """每日每个股票持仓市值表 Returns: pd.DataFrame -- 市值表 """ if self.account.daily_hold is not None: if self.if_fq: return ( self.market_data.to_qfq().pivot('close').fillna( method='ffill' ) * self.account.daily_hold.apply(abs) ).fillna(method='ffill') else: return ( self.market_data.pivot('close').fillna(method='ffill') * self.account.daily_hold.apply(abs) ).fillna(method='ffill') else: return None @property def daily_market_value(self): """每日持仓总市值表 Returns: pd.DataFrame -- 市值表 """ if self.market_value is not None: return self.market_value.sum(axis=1) else: return None @property def assets(self): x1 = self._assets.reset_index() return x1.assign(date=pd.to_datetime(x1.date)).set_index('date')[0] @property def max_dropback(self): """最大回撤 """ return round( float( max( [ (self.assets.iloc[idx] - self.assets.iloc[idx::].min()) / self.assets.iloc[idx] for idx in range(len(self.assets)) ] ) ), 2 ) @property def total_commission(self): """总手续费 """ return float( -abs(round(self.account.history_table.commission.sum(), 2)) ) @property def total_tax(self): """总印花税 """ return float(-abs(round(self.account.history_table.tax.sum(), 2))) @property def profit_construct(self): """利润构成 Returns: dict -- 利润构成表 """ return { 'total_buyandsell': round( self.profit_money - self.total_commission - self.total_tax, 2 ), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'total_profit': self.profit_money } @property def profit_money(self): """盈利额 Returns: [type] -- [description] """ return float(round(self.assets.iloc[-1] - self.assets.iloc[0], 2)) @property def profit(self): """盈利率(百分比) Returns: [type] -- [description] """ return round(float(self.calc_profit(self.assets)), 2) @property def profit_pct(self): """利润 """ return self.calc_profitpctchange(self.assets) @property def annualize_return(self): """年化收益 Returns: [type] -- [description] """ return round( float(self.calc_annualize_return(self.assets, self.time_gap)), 2 ) @property def volatility(self): """波动率 Returns: [type] -- [description] """ return round(float(self.profit_pct.std() * math.sqrt(250)), 2) @property def ir(self): return round(self.calc_IR(), 2) @property @lru_cache() def message(self): return { 'account_cookie': self.account.account_cookie, 'portfolio_cookie': self.account.portfolio_cookie, 'user_cookie': self.account.user_cookie, 'annualize_return': round(self.annualize_return, 2), 'profit': round(self.profit, 2), 'max_dropback': self.max_dropback, 'time_gap': self.time_gap, 'volatility': self.volatility, 'benchmark_code': self.benchmark_code, 'bm_annualizereturn': self.benchmark_annualize_return, 'bm_profit': self.benchmark_profit, 'beta': self.beta, 'alpha': self.alpha, 'sharpe': self.sharpe, 'init_cash': "%0.2f" % (float(self.assets[0])), 'last_assets': "%0.2f" % (float(self.assets.iloc[-1])), 'total_tax': self.total_tax, 'total_commission': self.total_commission, 'profit_money': self.profit_money, 'assets': list(self.assets), 'benchmark_assets': list(self.benchmark_assets), 'timeindex': self.account.trade_day, 'totaltimeindex': self.total_timeindex, 'ir': self.ir, 'month_profit': self.month_assets_profit.to_dict() # 'init_assets': round(float(self.init_assets), 2), # 'last_assets': round(float(self.assets.iloc[-1]), 2) } @property def benchmark_data(self): """ 基准组合的行情数据(一般是组合,可以调整) """ return self.fetch[self.benchmark_type]( self.benchmark_code, self.account.start_date, self.account.end_date ) @property def benchmark_assets(self): """ 基准组合的账户资产队列 """ return ( self.benchmark_data.close / float(self.benchmark_data.close.iloc[0]) * float(self.assets[0]) ) @property def benchmark_profit(self): """ 基准组合的收益 """ return round(float(self.calc_profit(self.benchmark_assets)), 2) @property def benchmark_annualize_return(self): """基准组合的年化收益 Returns: [type] -- [description] """ return round( float( self.calc_annualize_return( self.benchmark_assets, self.time_gap ) ), 2 ) @property def benchmark_profitpct(self): """ benchmark 基准组合的收益百分比计算 """ return self.calc_profitpctchange(self.benchmark_assets) @property def beta(self): """ beta比率 组合的系统性风险 """ try: res = round( float( self.calc_beta( self.profit_pct.dropna(), self.benchmark_profitpct.dropna() ) ), 2 ) except: print('贝塔计算错误。。') res = 0 return res @property def alpha(self): """ alpha比率 与市场基准收益无关的超额收益率 """ return round( float( self.calc_alpha( self.annualize_return, self.benchmark_annualize_return, self.beta, 0.05 ) ), 2 ) @property def sharpe(self): """ 夏普比率 """ return round( float( self.calc_sharpe(self.annualize_return, self.volatility, 0.05) ), 2 ) @property def sortino(self): """ 索提诺比率 投资组合收益和下行风险比值 """ pass @property def calmar(self): """ 卡玛比率 """ pass def set_benchmark(self, code, market_type): self.benchmark_code = code self.benchmark_type = market_type def calc_annualize_return(self, assets, days): return round( (float(assets.iloc[-1]) / float(assets.iloc[0]) - 1) / (float(days) / 250), 2 ) def calc_profitpctchange(self, assets): return assets[::-1].pct_change()[::-1] def calc_beta(self, assest_profit, benchmark_profit): calc_cov = np.cov(assest_profit, benchmark_profit) beta = calc_cov[0, 1] / calc_cov[1, 1] return beta def calc_alpha( self, annualized_returns, benchmark_annualized_returns, beta, r=0.05 ): alpha = (annualized_returns - r) - (beta) *\ (benchmark_annualized_returns - r) return alpha def calc_IR(self): """计算信息比率 Returns: [type] -- [description] """ if self.volatility == 0: return 0 else: return self.annualize_return / self.volatility def calc_profit(self, assets): """ 计算账户收益 期末资产/期初资产 -1 """ return (float(assets.iloc[-1]) / float(assets.iloc[0])) - 1 def calc_sharpe(self, annualized_returns, volatility_year, r=0.05): """ 计算夏普比率 r是无风险收益 """ # 会出现0 if volatility_year == 0: return 0 return (annualized_returns - r) / volatility_year @property def max_holdmarketvalue(self): """最大持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.max() else: return 0 @property def min_holdmarketvalue(self): """最小持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.min() else: return 0 @property def average_holdmarketvalue(self): """平均持仓市值 Returns: [type] -- [description] """ if self.daily_market_value is not None: return self.daily_market_value.mean() else: return 0 @property def max_cashhold(self): """最大闲置资金 """ return self.account.daily_cash.cash.max() @property def min_cashhold(self): """最小闲置资金 """ return self.account.daily_cash.cash.min() @property def average_cashhold(self): """平均闲置资金 Returns: [type] -- [description] """ return self.account.daily_cash.cash.mean() def save(self): """save to mongodb """ save_riskanalysis(self.message) def plot_assets_curve(self, length=14, height=12): """ 资金曲线叠加图 @Roy T.Burns 2018/05/29 修改百分比显示错误 """ plt.style.use('ggplot') plt.figure(figsize=(length, height)) plt.subplot(211) plt.title('BASIC INFO', fontsize=12) plt.axis([0, length, 0, 0.6]) plt.axis('off') i = 0 for item in ['account_cookie', 'portfolio_cookie', 'user_cookie']: plt.text( i, 0.5, '{} : {}'.format(item, self.message[item]), fontsize=10, rotation=0, wrap=True ) i += (length / 2.8) i = 0 for item in ['benchmark_code', 'time_gap', 'max_dropback']: plt.text( i, 0.4, '{} : {}'.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True ) i += (length / 2.8) i = 0 for item in ['annualize_return', 'bm_annualizereturn', 'profit']: plt.text( i, 0.3, '{} : {} %'.format(item, self.message.get(item, 0) * 100), fontsize=10, ha='left', rotation=0, wrap=True ) i += length / 2.8 i = 0 for item in ['init_cash', 'last_assets', 'volatility']: plt.text( i, 0.2, '{} : {} '.format(item, self.message[item]), fontsize=10, ha='left', rotation=0, wrap=True ) i += length / 2.8 i = 0 for item in ['alpha', 'beta', 'sharpe']: plt.text( i, 0.1, '{} : {}'.format(item, self.message[item]), ha='left', fontsize=10, rotation=0, wrap=True ) i += length / 2.8 plt.subplot(212) self.assets.plot() self.benchmark_assets.xs(self.benchmark_code, level=1).plot() asset_p = mpatches.Patch( color='red', label='{}'.format(self.account.account_cookie) ) asset_b = mpatches.Patch( label='benchmark {}'.format(self.benchmark_code) ) plt.legend(handles=[asset_p, asset_b], loc=0) plt.title('ASSET AND BENCKMARK') return plt @property def month_assets(self): return self.assets.resample('M').last() @property def month_assets_profit(self): res = pd.concat([pd.Series(self.assets.iloc[0]), self.month_assets]).diff().dropna() res.index = res.index.map(str) return res @property def daily_assets_profit(self): return self.assets.diff() def plot_dailyhold(self, start=None, end=None): """ 使用热力图画出每日持仓 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 8)) sns.heatmap( self.account.daily_hold.reset_index().drop( 'account_cookie', axis=1 ).set_index('date').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax ) ax.set_title( 'HOLD TABLE --ACCOUNT: {}'.format(self.account.account_cookie) ) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def plot_signal(self, start=None, end=None): """ 使用热力图画出买卖信号 """ start = self.account.start_date if start is None else start end = self.account.end_date if end is None else end _, ax = plt.subplots(figsize=(20, 18)) sns.heatmap( self.account.trade.reset_index().drop( 'account_cookie', axis=1 ).set_index('datetime').loc[start:end], cmap="YlGnBu", linewidths=0.05, ax=ax ) ax.set_title( 'SIGNAL TABLE --ACCOUNT: {}'.format(self.account.account_cookie) ) ax.set_xlabel('Code') ax.set_ylabel('DATETIME') return plt def generate_plots(self): """ 生成图像 """ self.plot_assets_curve() self.plot_dailyhold() self.plot_signal()