import matplotlib.pyplot as plt import pandas as pd import seaborn as sns from reportlab.platypus.flowables import HRFlowable from reportlab.lib.colors import HexColor from reportlab.lib.styles import ParagraphStyle, TA_CENTER import tia.rlab as rlab from tia.analysis.model.port import PortfolioSummary import tia.analysis.perf as perf from tia.util.fmt import new_datetime_formatter from tia.util.mplot import AxesFormat, FigureHelper from tia.analysis.util import insert_level class _Result(object): def __init__(self, port, sid, desc): self.port = port self.buyhold = port.buy_and_hold() self.sid = sid self.desc = desc class ShortTermReport(object): """Used when showing 2 years or less worth of portfolio returns""" def __init__(self, path, title, author=None, table_style=None): self.path = path self.pdf = None self.results = [] self.title = title self.author = author self.table_style = table_style or rlab.Style.Black self.figures = FigureHelper(dpi=300) self.long_color = plt.rcParams['axes.color_cycle'][0] self.short_color = plt.rcParams['axes.color_cycle'][1] def add_port(self, port, sid, desc): self.results.append(_Result(port, sid, desc)) def create_ax(self, figsize=(5, 3)): return plt.subplots(1, 1, figsize=figsize) def define_portfolio_summary_template(self): t = rlab.GridTemplate('portfolio', 100, 100) r1 = slice(5, 30) r2 = slice(30, 55) r3 = slice(55, 100) c1 = slice(1, 33) c2 = slice(33, 65) c3 = slice(66, 98) t.define_frames({'F1': t[r1, c1], 'F2': t[r1, c2], 'F3': t[r1, c3], 'F4': t[r2, c1], 'F5': t[r2, c2], 'F6': t[r2, c3], 'F7': t[r3, c1], 'F8': t[r3, 33:], 'HDR': t[:5, :]}) t.register(self.pdf) def define_position_summary_template(self): t = rlab.GridTemplate('positions', 100, 100) r1 = slice(5, 30) r2 = slice(30, 55) r3 = slice(55, 100) c1 = slice(1, 50) c2 = slice(50, 98) t.define_frames({'F1': t[r1, c1], 'F2': t[5:55, c2], 'F3': t[r2, c1], 'F5': t[r3, 1:33], 'F6': t[r3, 33:], 'HDR': t[:5, :]}) t.register(self.pdf) def define_summary_template(self): t = rlab.GridTemplate('summary', 100, 100) t.define_frames({'F1': t[1:99, 1:99]}) t.register(self.pdf) def add_summary_page(self): """Build a table which is shown on the first page which gives an overview of the portfolios""" s = PortfolioSummary() s.include_long_short() pieces = [] for r in self.results: tmp = s(r.port, PortfolioSummary.analyze_returns) tmp['desc'] = r.desc tmp['sid'] = r.sid tmp = tmp.set_index(['sid', 'desc'], append=1).reorder_levels([2, 1, 0]) pieces.append(tmp) frame = pd.concat(pieces) tf = self.pdf.table_formatter(frame) tf.apply_basic_style(cmap=self.table_style) # [col.guess_format(pcts=1, trunc_dot_zeros=1) for col in tf.cells.iter_cols()] tf.cells.match_column_labels(['nmonths', 'cnt', 'win cnt', 'lose cnt', 'dur max']).int_format() tf.cells.match_column_labels(['sharpe ann', 'sortino', 'dur avg']).float_format(precision=1) tf.cells.match_column_labels(['maxdd dt']).apply_format(new_datetime_formatter('%d-%b-%y')) tf.cells.match_column_labels(['cagr', 'mret avg', 'mret std ann', 'ret std', 'mret avg ann', 'maxdd', 'avg dd', 'winpct', 'ret avg', 'ret min', 'ret max']).percent_format() self.pdf.build_page('summary', {'F1': tf.build()}) def title_bar(self, title): # Build a title bar for top of page w, t, c = '100%', 2, HexColor('#404040') title = '<b>{0}</b>'.format(title) return [HRFlowable(width=w, thickness=t, color=c, spaceAfter=2, vAlign='MIDDLE', lineCap='square'), self.pdf.new_paragraph(title, 'TitleBar'), HRFlowable(width=w, thickness=t, color=c, spaceBefore=2, vAlign='MIDDLE', lineCap='square')] def run(self): cp = rlab.CoverPage(self.title, subtitle2=self.author) self.pdf = pdf = rlab.PdfBuilder(self.path, coverpage=cp) # Setup stylesheet tb = ParagraphStyle('TitleBar', parent=pdf.stylesheet['Normal'], fontName='Helvetica-Bold', fontSize=10, leading=10, alignment=TA_CENTER) 'TitleBar' not in pdf.stylesheet and pdf.stylesheet.add(tb) # define templates self.define_portfolio_summary_template() self.define_position_summary_template() self.define_summary_template() # Show the summary page self.add_summary_page() # Build the portfolio and position details for each result for r in self.results: self.add_portfolio_page(r) self.add_position_page(r) pdf.save() def add_portfolio_page(self, result): def alpha_beta(p, bm): model = pd.ols(x=bm.rets, y=p.rets) beta = model.beta[0] alpha = p.total_ann - beta * bm.total_ann s = pd.Series({'alpha': alpha, 'beta': beta}) return s def rs(port1, port2, kind='dly_ret_stats'): stats = getattr(port1, kind) ab = alpha_beta(stats, getattr(port2, kind)) tmp = stats.series.append(ab) tmp.name = stats.series.name return tmp def dofmt(t): t.apply_basic_style(cmap=self.table_style) [row.guess_format(pcts=1, trunc_dot_zeros=1) for row in t.cells.iter_rows()] ncols = len(t.formatted_values.columns) t.set_col_widths(pcts=[1. / ncols] * ncols) def do_rename(df): d = {'consecutive_win_cnt_max': 'win_streak', 'consecutive_loss_cnt_max': 'lose_streak'} return df.rename(index=lambda c: d.get(c, c)) # Build the pdf tables pdf = self.pdf figures = self.figures port = result.port buyhold = result.buyhold sframe = pd.DataFrame([rs(port, buyhold, 'dly_ret_stats'), rs(port, buyhold, 'weekly_ret_stats'), rs(port, buyhold, 'monthly_ret_stats'), rs(port, buyhold, 'quarterly_ret_stats')]).T tf = pdf.table_formatter(insert_level(sframe, 'Portfolio', copy=True)) dofmt(tf) stable = tf.build() s = PortfolioSummary() s.include_long_short().include_win_loss() dframe = s(port, PortfolioSummary.analyze_returns).T tf = pdf.table_formatter(do_rename(insert_level(dframe.ix['port'], 'Portfolio', copy=True))) dofmt(tf) dtable = tf.build() # Return on $1 image f, ax = self.create_ax() buyhold.plot_ret_on_dollar('B', label='Buy & Hold', ax=ax) port.plot_ret_on_dollar('B', label=result.desc, ax=ax, color='k') ax.legend(loc='upper left') ax.set_title('vs Buy & Hold') plt.tight_layout() figures.savefig(key='buyhold', clear=1) # Drawdown image f, ax = self.create_ax() port.dly_ret_stats.plot_ltd(ax=ax) plt.tight_layout() figures.savefig(key='dd', clear=1) # Long / Short Returns f, ax = self.create_ax() port.plot_ret_on_dollar('B', label='All', color='k', ax=ax) port.long.plot_ret_on_dollar('B', label='Long', ax=ax) port.short.plot_ret_on_dollar('B', label='Short', ax=ax) ax.legend(loc='upper left') figures.savefig(key='ls', clear=1) # Sharpe / Ann Vol f, ax = self.create_ax() perf.sharpe_annualized(port.monthly_rets, expanding=1).iloc[3:].plot(ax=ax, color='k', label='sharpe') ax.set_ylabel('sharpe ann', color='k') ax2 = ax.twinx() perf.std_annualized(port.monthly_rets, expanding=1).iloc[3:].plot(ax=ax2, label='vol', color='b', alpha=1) ax2.set_ylabel('vol ann', color='b') plt.tight_layout() figures.savefig(key='sharpe', clear=1) # Monthly Returns Bar Chart f, ax = self.create_ax() tmp = pd.DataFrame({'All': port.monthly_rets.to_period('M'), 'Long': port.long.monthly_rets.to_period('M'), 'Short': port.short.monthly_rets.to_period('M')}) tmp.plot(kind='bar', ax=ax, color=['k', self.long_color, self.short_color]) AxesFormat().Y.percent().X.rotate().apply() plt.tight_layout() ax.set_title('Monthly Returns') figures.savefig(key='mrets', clear=1) # Monthly Returns Box Plot f, ax = self.create_ax() sns.boxplot(tmp, ax=ax, color=['gray', self.long_color, self.short_color]) ax.set_title('Monthly Returns') AxesFormat().Y.percent().apply() plt.tight_layout() figures.savefig(key='mrets_box', clear=1) # Build the PDF Page toimg = lambda path: rlab.new_dynamic_image(path) itms = { 'F1': toimg(figures['buyhold']), 'F2': toimg(figures['dd']), 'F3': toimg(figures['ls']), 'F4': toimg(figures['mrets']), 'F5': toimg(figures['sharpe']), 'F6': toimg(figures['mrets_box']), 'F7': stable, 'F8': dtable, 'HDR': self.title_bar('{0} - {1} - portfolio summary'.format(result.sid, result.desc)) } pdf.build_page('portfolio', itms) def add_position_page(self, result): def dofmt(t): t.apply_basic_style(cmap=self.table_style) [row.guess_format(pcts=1, trunc_dot_zeros=1) for row in t.cells.iter_rows()] ncols = len(t.formatted_values.columns) t.set_col_widths(pcts=[1. / ncols] * ncols) return t def do_rename(df): d = {'consecutive_win_cnt_max': 'win_streak', 'consecutive_loss_cnt_max': 'lose_streak'} return df.rename(index=lambda c: d.get(c, c)) pdf = self.pdf figures = self.figures port = result.port buyhold = result.buyhold sframe = pd.DataFrame({'all': port.positions.stats.series, 'long': port.long.positions.stats.series, 'short': port.short.positions.stats.series}) tf = pdf.table_formatter(insert_level(sframe, 'Position', copy=True)) stable = dofmt(tf).build() s = PortfolioSummary() s.include_long_short().include_win_loss() dframe = s(port, PortfolioSummary.analyze_returns).T.ix['pos'] tf = pdf.table_formatter(do_rename(insert_level(dframe, 'Position', copy=True))) dtable = dofmt(tf).build() # Plot Position Returns f, ax = self.create_ax() port.positions.plot_rets(ax=ax) plt.tight_layout() figures.savefig(key='pos_ls', clear=1) # Plot Position Ranges f, ax = self.create_ax(figsize=(8, 3)) port.positions.plot_ret_range(ls=1, dur=1, ax=ax) plt.tight_layout() figures.savefig(key='pos_rng', clear=1) # Plot Long Short Positions with regression line tmp = port.position_frame[['side', 'ret']].reset_index() g = sns.lmplot("pid", "ret", col="side", hue="side", data=tmp, size=3) AxesFormat().Y.percent().apply() figures.savefig(key='pos_ls', clear=1) # Plot Return vs Duration tmp = port.position_frame[['ret', 'duration', 'side']] diag_kws = {} if len(port.position_frame.index) <= 1: diag_kws = {'range': (-100, 100)} sns.pairplot(tmp, hue="side", size=3, diag_kws=diag_kws) figures.savefig(key='pos_pair', clear=1) toimg = lambda path: rlab.new_dynamic_image(path) itms = { 'F1': toimg(figures['pos_rng']), 'F3': toimg(figures['pos_ls']), 'F2': toimg(figures['pos_pair']), 'F5': stable, 'F6': dtable, 'HDR': self.title_bar('{0} - {1} - position summary'.format(result.sid, result.desc)) } pdf.build_page('positions', itms)