Notebook
In [1]:
bt = get_backtest('586600541456ae6293a7dfd7') #
100% Time: 0:00:19|###########################################################|
In [3]:
import matplotlib.pyplot as plt
import matplotlib
import datetime
from datetime import datetime
import pytz
from pytz import timezone
import pyfolio as pf
from __future__ import division
import pandas as pd
import seaborn as sns
import scipy as sp
import numpy as np
from math import copysign
from collections import OrderedDict, defaultdict, deque
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.research import run_pipeline
from quantopian.pipeline.filters.morningstar import Q500US, Q1500US
from quantopian.pipeline.data import morningstar as mstar
from quantopian.pipeline.classifiers.morningstar import Sector
import scipy.stats as stats


returns = bt.daily_performance.returns



def cum_returns(df, withStartingValue=None):
    if withStartingValue is None:
        return np.exp(np.log(1 + df).cumsum()) - 1
    else:
        return np.exp(np.log(1 + df).cumsum()) * withStartingValue

def aggregate_returns(df_daily_rets, convert_to):
    cumulate_returns = lambda x: cum_returns(x)[-1]
    
    
    if convert_to == 'daily':
        return df_daily_rets
    elif convert_to == 'weekly':
        return df_daily_rets.groupby([lambda x: x.year, lambda x: x.month, lambda x: x.isocalendar()[1]]).apply(cumulate_returns)
    elif convert_to == 'monthly':
        return df_daily_rets.groupby([lambda x: x.year, lambda x: x.month]).apply(cumulate_returns)
    elif convert_to == 'yearly':
        return df_daily_rets.groupby([lambda x: x.year]).apply(cumulate_returns)
    else:
        ValueError('convert_to must be daily, weekly, monthly or yearly')
    
def plot_calendar_returns_info_graphic(daily_rets_ts, x_dim=15, y_dim=6): 
    
    month_names = {
        1: 'Jan',
        2: 'Feb',
        3: 'Mar',
        4: 'Apr',
        5: 'May',
        6: 'Jun',
        7: 'Jul',
        8: 'Aug',
        9: 'Sept',
        10: 'Oct',
        11: 'Nov',
        12: 'Dec'
    }    
    
    ann_ret_df = pd.DataFrame( aggregate_returns(daily_rets_ts, 'yearly') )

    monthly_ret_table = aggregate_returns(daily_rets_ts, 'monthly')
    monthly_ret_table = monthly_ret_table.unstack()
    monthly_ret_table = np.round(monthly_ret_table, 3)
    monthly_ret_table.columns = monthly_ret_table.columns.map(lambda col: month_names[col])
    
    annual_ret_table = aggregate_returns(daily_rets_ts, 'yearly')
    annual_ret_table = np.round(annual_ret_table, 4) 
    
    vmin = np.round(min(monthly_ret_table.min(axis = 1)) - 0.02, 2)
    vmax = np.round(max(monthly_ret_table.max(axis = 1)) + 0.02, 2) 
    monthly_ret_table = monthly_ret_table.assign(Annual=annual_ret_table)
       

    fig,ax1 = plt.subplots(figsize=(15,15))
    sns.heatmap(monthly_ret_table.fillna(0)*100.0, annot=True, annot_kws={"size": 13}, alpha=1.0, center=3.0, cbar=True, cmap=matplotlib.cm.PiYG,
                linewidths=2.75, xticklabels =True, cbar_kws={"orientation": "horizontal"}, robust=True, vmin=vmin*100, 
                vmax = vmax*100, fmt='g' )

   
    for text in ax1.get_xticklabels():
        text.set_size(14)
        text.set_weight('bold')
        if str(text.get_unitless_position()[:-1]) == '(12.5,)':
            text.set_size(22)
            text.set_weight('bold')
            text.set_style('italic')        
    for text in ax1.get_yticklabels():
        text.set_size(14)
        text.set_weight('bold')
        
    for text in ax1.texts:
        text.set_size(14)
        if str(text.get_unitless_position()[:-1]) == '(12.5,)':
            text.set_size(22)
            text.set_weight('bold')

    ax1.axvline(x=12, color='#4D032D',linewidth=5)
    ax1.axvspan(12, 13, facecolor='0.05', alpha=0.4)
    
    ax1.set_ylabel("Year")
    ax1.set_xlabel("Monthly Returns (%)")
    ax1.set_title("Monthly Returns (%), " + \
            "\nFrom: " + str(bt.start_date.date()) + " to " + str(bt.end_date.date())); 
    plt.show()
    
pd.options.display.float_format = '{0:.3f}%'.format 
ytu = bt.cumulative_performance.ending_portfolio_value
ytu = pd.DataFrame(ytu)
ytu = ytu.groupby([ytu.index.year]).last()
ytu["opening"] = ytu.ending_portfolio_value.shift(1)
ytu = ytu.fillna(bt.capital_base)
ytu["annual_return"] = (ytu.ending_portfolio_value / ytu.opening)-1
ytu["total_return"] = 0
for i in range(len(ytu.index)):
    ytu.total_return[0:1] = ytu.annual_return[0:1]
    ytu["total_return"] =  ((1 + ytu.total_return.shift(1)) * (1 + ytu.annual_return)) - 1

ytu.total_return = ytu.total_return.fillna(ytu.annual_return)
ytu["CasdAGR"]=range(len(ytu.index))
ytu["CAGR"] = ((1 + ytu.total_return) ** (1 / (ytu.CasdAGR+1))) - 1
ytu.pop('CasdAGR')
ytu.pop('ending_portfolio_value')
ytu.pop('opening')
ytu.pop('total_return')
ytu.annual_return = ytu.annual_return * 100
ytu.CAGR = ytu.CAGR * 100
ytu = ytu.sort_index(ascending=False)
print ytu
print "note: CAGR assumes full year trading"
ytu.plot(x=None, y=None, kind='barh', ax=None, subplots=False, sharex=False, sharey=False, layout=None, figsize=(15,10), 
           use_index=True, title='Annual Return % & CAGR % at the end of each of Year', grid=None, legend=True, style=None, logx=False, logy=False, loglog=False, 
           xticks=None, yticks=None, xlim=None, ylim=None, rot=None, fontsize=12, colormap=None, table=False, 
           yerr=None, xerr=None, secondary_y=False, sort_columns=False)
plt.axvline(float(ytu.CAGR.mean()), color='g', linestyle='dashed', linewidth=2, label='CAGR Mean')
plt.axvline(float(ytu.annual_return.mean()), color='b', linestyle='dashed', linewidth=2, label='Annual Return Mean')
plt.legend()

matplotlib.pyplot.figure()
plot_calendar_returns_info_graphic(returns)  
matplotlib.pyplot.figure()
pf.plot_monthly_returns_dist(returns)
matplotlib.pyplot.figure()
pf.timeseries.cum_returns(returns).plot()
plt.ylabel('Cumulative Returns')
matplotlib.pyplot.figure()

ytu = pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=252)
ytu = pd.DataFrame(ytu)
ytu = ytu.groupby([ytu.index.year]).last()
ytu.plot(x=None, y=None, kind='barh', ax=None, subplots=False, sharex=False, sharey=False, layout=None, figsize=(15,10), 
           use_index=True, title='ROLLING 12 MONTH SHARPE AT YEAR END', grid=None, legend=False, style=None, logx=False, logy=False, loglog=False, 
           xticks=None, yticks=None, xlim=None, ylim=None, rot=None, fontsize=12, colormap=None, table=False, 
           yerr=None, xerr=None, secondary_y=False, sort_columns=False)
plt.axvline(float(ytu.mean()), color='m', linestyle='dashed', linewidth=2, label='Mean')
      annual_return    CAGR
2016        18.173% 15.309%
2015         9.655% 15.091%
2014         1.330% 15.556%
2013        19.009% 16.945%
2012        10.964% 16.740%
2011         9.795% 17.400%
2010         2.832% 18.387%
2009        38.180% 20.794%
2008         4.785% 18.117%
2007         2.356% 20.980%
2006        35.509% 26.143%
2005        12.556% 23.168%
2004        29.661% 28.843%
2003        28.030% 28.030%
note: CAGR assumes full year trading
/build/src/ipython/IPython/kernel/__main__.py:121: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
<matplotlib.figure.Figure at 0x7f67f181ae90>
Out[3]:
<matplotlib.lines.Line2D at 0x7f67f0ed58d0>
<matplotlib.figure.Figure at 0x7f67f20d6dd0>

================================================================

Top 15 most important features for predicting Sharpe ratio OOS as determined by a random forest regressor.

Source: Wiecki, Thomas and Campbell, Andrew and Lent, Justin and Stauth, Jessica, All that Glitters Is Not Gold: Comparing Backtest and Out-of-Sample Performance on a Large Cohort of Trading Algorithms (March 9, 2016). Available at SSRN: https://ssrn.com/abstract=2745220 or http://dx.doi.org/10.2139/ssrn.2745220

In [193]:
print "------------------------------------------------------------------"
print("\n"'ALL DATA BELOW IS OVER FULL BACKTEST OF {:2,.0f} days'.format(((bt.end_date.date() - bt.start_date.date()).days)))
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'TAIL RATIO'+'\x1b[0m'"\n")
print "Determines the ratio between the right (95%) and left tail (5%). For example, a ratio of 0.25 means that losses are four times as bad as profits."
print("\n"'Backtest tail ratio = {:4,.3f}'.format((pf.capacity.empyrical.tail_ratio(returns))))
print "------------------------------------------------------------------"

print("\n"'\x1b[1;31m'+'KURTOSIS'+'\x1b[0m'"\n")
print('Characterizes the relative ‘peakedness’ or flatness of an investment’s return distribution compared with the normal distribution. The higher the kurtosis, the more peaked the return distribution is; the lower the kurtosis, the more rounded the return distribution is. A normal distribution has a kurtosis of 3. Higher kurtosis indicates a return distribution with a more acute peak around the mean (higher probability than a normal distribution of more returns clustered around the mean) and a greater chance of extremely large deviations from the expected return (fatter tails, more big surprises). Investors view a greater percentage of extremely large deviations from the expected return as an increase in risk. Lower kurtosis has a smaller peak (lower probability of returns around the mean) and a lower probability than a normal distribution of extreme returns.')
print('source: Greenwich Alternative Investments: http://www.greenwichai.com/index.php/hf-essentials/measure-of-risk - retrieved Dec 27, 2016')
print("\n"'Kurtosis over entire test period = {:4,.3f}'.format((sp.stats.stats.kurtosis(returns))))  
xyz = sp.stats.stats.kurtosis(returns)-3
print("\n"'Excess kurtosis over entire test period = {:4,.3f}'.format(xyz))  
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'SKEWNESS'+'\x1b[0m'"\n")    
print("\n"'A distribution with no tail to the right or to the left is one that is not skewed in any direction. This is the same as a normal distribution i.e. a distribution which has zero skewness. If there is a large frequency of occurrence of negative returns compared to positive returns then the distribution displays a fat left tail or negative skewness. In case the frequency of positive returns exceeds that of negative returns then the distribution displays a fat right tail or positive skewness.')
print("\n"'Skewness over entire test period = {:4,.3f}'.format((sp.stats.stats.skew(returns))))     
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'SHARPE RATIO'+'\x1b[0m'"\n")
print("\n"'Sharpe ratio last year = {:4,.3f}'"\n".format((float(ytu.returns[-1:]))))
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'ROLLING SHARPE RATIO'+'\x1b[0m'"\n")
print("\n"'Rolling Monthly Sharpe Ratio over entire test period has a mean of {:4,.4f} and standard deviation of {:4,.4f}'
      .format((pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH)).mean()),
              pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH)).std()))    
print("\n"'Rolling 6 Month Sharpe Ratio over entire test period has a mean of {:4,.4f} and standard deviation of {:4,.4f}'
      .format((pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH * 6)).mean()),
              pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH * 6)).std()))   
print("\n"'Rolling 12 Month Sharpe Ratio over entire test period has a mean of {:4,.4f} and standard deviation of {:4,.4f}'
      .format((pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH * 12)).mean()),
              pf.timeseries.rolling_sharpe(returns, rolling_sharpe_window=(pf.capacity.empyrical.stats.APPROX_BDAYS_PER_MONTH * 12)).std()))   
print "------------------------------------------------------------------"
spy = get_pricing(symbols = bt.benchmark_security, start_date=bt.start_date.date(), end_date=bt.end_date.date(), symbol_reference_date=None, 
                 frequency='daily', fields={"price"}, handle_missing='ignore')
spy = pd.Series(spy.price.pct_change())
alpla_beta= pf.capacity.empyrical.alpha_beta(returns, spy, risk_free=0.0, period='daily', annualization=None)

print("\n"'\x1b[1;31m'+'ALPHA + BETA'+'\x1b[0m'"\n")
print("\n""\n"'Annualized Alpha is annualized returns in excess of returns resulting from correlations with the benchmark. Beta is the correlation between daily returns of algo and daily returns of the benchmark')  
print("\n"'Annualized Alpha over entire test period = {:10.5}'.format((alpla_beta[0])))   
print('Beta over entire test period = {:10.5}'.format((alpla_beta[1])))   
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'COMMON SENSE RATIO'+'\x1b[0m'"\n")
print"\n""Above 1: make money, below 1: lose money"
print"\n""Common Sense Ratio = Tail ratio * Gain to Pain Ratio"
print"Common Sense Ratio = [percentile(returns, 95%) * Sum(profits)] / [percentile(returns, 5%) * Sum(losses)]"
print"Trend following strategies have low win rate. Aggregate losses kill profitability. Therefore risk is in the aggregate which is measured by the profit ratio or Gain to Pain Ratio (Jack Schwager favourite measure btw). No surprise since GPR is the ratio version of gain expectancy or trading edge. Mean reverting strategies fail on knockouts. LTCM best case. A few blow-ups and game over. So the risk is in the tail. Therefore, use tail ratio. Now, a bit of arithmetical magic clled transitivity, or multiply GPR by tail and voila CSR. It recaptures risk on both sides."
print"source:http://alphasecurecapital.com/regardless-of-the-asset-class-there-are-only-two-types-of-strategies/"
print("\n"'Common Sense ratio over entire test period = {:4,.3f}'.format((pf.capacity.empyrical.tail_ratio(returns) * (1 + pf.capacity.empyrical.annual_return(returns)))))
print "------------------------------------------------------------------"
print("\n"'\x1b[1;31m'+'VALUE AT RISK(VaR)'+'\x1b[0m'"\n")
print "\n""\n""a statistical technique used to measure and quantify the possibility of losing money within an investment portfolio over a specific time frame to determine the extent and occurrence ratio of potential losses in their institutional portfolios. VaR quantifies market risk while it is being taken. It measures the odds of losing money but does not indicate certainty. VAR summarizes the predicted maximum loss (or worst loss) over a target horizon within a given confidence interval."
print("\n"'\x1b[1;34m'+'HISTORICAL METHOD VAR'+'\x1b[0m')
print("\n"'\x1b[1;31m'+'DAILY VAR - HISTORICAL'+'\x1b[0m'"\n")
yui = pd.DataFrame((returns*100).quantile([.01, .05, 0.1, 0.5]))
yui["percent"] = yui.index
yui.index = range(len(yui))
yui2 = pd.DataFrame({'percent':[stats.percentileofscore(returns*100,0)/100],
                     'returns':['0.0']})
yui = yui.append(yui2, ignore_index=True)
print('With 99% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 1% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[0],yui.returns[0]))
print("\n"'With 95% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 5% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[1],yui.returns[1]))
print("\n"'With 90% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 10% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[2],yui.returns[2]))
print("\n"'With 50% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 50% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[3],yui.returns[3]))
print("\n"'With {:4,.1f}% confidence, we expect that our worst daily loss will not exceed 0%, or in other words we can expect'
      ' that with a probability of {:4,.1f}% the daily loss of the portfolio will decrease by more than 0%.'
      .format((1-yui.percent[4])*100,yui.percent[4]*100))

print("\n"'\x1b[1;31m'+'MONTHLY VAR - HISTORICAL'+'\x1b[0m'"\n")
yui = pd.DataFrame((pf.capacity.empyrical.aggregate_returns(returns, convert_to='monthly')*100).quantile([.01, .05, 0.1, 0.5]))
yui["percent"] = yui.index
yui.index = range(len(yui))
yui2 = pd.DataFrame({'percent':[stats.percentileofscore(pf.capacity.empyrical.aggregate_returns(returns, convert_to='monthly')*100,0)/100],
                     'returns':['0.0']})
yui = yui.append(yui2, ignore_index=True)
print('With 99% confidence, we expect that our worst monthly loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 1% the monthly loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[0],yui.returns[0]))
print("\n"'With 95% confidence, we expect that our worst monthly loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 5% the monthly loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[1],yui.returns[1]))
print("\n"'With 90% confidence, we expect that our worst monthly loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 10% the monthly loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[2],yui.returns[2]))
print("\n"'With 50% confidence, we expect that our worst monthly loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 50% the monthly loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[3],yui.returns[3]))
print("\n"'With {:4,.1f}% confidence, we expect that our worst monthly loss will not exceed 0%, or in other words we can expect'
      ' that with a probability of {:4,.1f}% the monthly loss of the portfolio will decrease by more than 0%.'
      .format((1-yui.percent[4])*100,yui.percent[4]*100))

print("\n"'\x1b[1;31m'+'ANNUAL VAR - HISTORICAL'+'\x1b[0m'"\n")
yui = pd.DataFrame((pf.capacity.empyrical.aggregate_returns(returns, convert_to='yearly')*100).quantile([.01, .05, 0.1, 0.5]))
yui["percent"] = yui.index
yui.index = range(len(yui))
yui2 = pd.DataFrame({'percent':[stats.percentileofscore(pf.capacity.empyrical.aggregate_returns(returns, convert_to='yearly')*100,0)/100],
                     'returns':['0.0']})
yui = yui.append(yui2, ignore_index=True)
print('With 99% confidence, we expect that our worst annual loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 1% the annual loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[0],yui.returns[0]))
print("\n"'With 95% confidence, we expect that our worst annual loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 5% the annual loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[1],yui.returns[1]))
print("\n"'With 90% confidence, we expect that our worst annual loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 10% the annual loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[2],yui.returns[2]))
print("\n"'With 50% confidence, we expect that our worst annual loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 50% the annual loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[3],yui.returns[3]))
print("\n"'With {:4,.1f}% confidence, we expect that our worst annual loss will not exceed 0%, or in other words we can expect'
      ' that with a probability of {:4,.1f}% the annual loss of the portfolio will decrease by more than 0%.'
      .format((1-yui.percent[4])*100,yui.percent[4]*100))
------------------------------------------------------------------

ALL DATA BELOW IS OVER FULL BACKTEST OF 5,104 days
------------------------------------------------------------------

TAIL RATIO

Determines the ratio between the right (95%) and left tail (5%). For example, a ratio of 0.25 means that losses are four times as bad as profits.

Backtest tail ratio = 1.162
------------------------------------------------------------------

KURTOSIS

Characterizes the relative ‘peakedness’ or flatness of an investment’s return distribution compared with the normal distribution. The higher the kurtosis, the more peaked the return distribution is; the lower the kurtosis, the more rounded the return distribution is. A normal distribution has a kurtosis of 3. Higher kurtosis indicates a return distribution with a more acute peak around the mean (higher probability than a normal distribution of more returns clustered around the mean) and a greater chance of extremely large deviations from the expected return (fatter tails, more big surprises). Investors view a greater percentage of extremely large deviations from the expected return as an increase in risk. Lower kurtosis has a smaller peak (lower probability of returns around the mean) and a lower probability than a normal distribution of extreme returns.
source: Greenwich Alternative Investments: http://www.greenwichai.com/index.php/hf-essentials/measure-of-risk - retrieved Dec 27, 2016

Kurtosis over entire test period = 4.775

Excess kurtosis over entire test period = 1.775
------------------------------------------------------------------

SKEWNESS


A distribution with no tail to the right or to the left is one that is not skewed in any direction. This is the same as a normal distribution i.e. a distribution which has zero skewness. If there is a large frequency of occurrence of negative returns compared to positive returns then the distribution displays a fat left tail or negative skewness. In case the frequency of positive returns exceeds that of negative returns then the distribution displays a fat right tail or positive skewness.

Skewness over entire test period = 0.053
------------------------------------------------------------------

SHARPE RATIO


Sharpe ratio last year = 1.547

------------------------------------------------------------------

ROLLING SHARPE RATIO


Rolling Monthly Sharpe Ratio over entire test period has a mean of 1.8645 and standard deviation of 3.8545

Rolling 6 Month Sharpe Ratio over entire test period has a mean of 1.7176 and standard deviation of 1.6619

Rolling 12 Month Sharpe Ratio over entire test period has a mean of 1.6494 and standard deviation of 1.2094
------------------------------------------------------------------

ALPHA + BETA



Annualized Alpha is annualized returns in excess of returns resulting from correlations with the benchmark. Beta is the correlation between daily returns of algo and daily returns of the benchmark

Annualized Alpha over entire test period =    0.12576
Beta over entire test period =    0.25789
------------------------------------------------------------------

COMMON SENSE RATIO


Above 1: make money, below 1: lose money

Common Sense Ratio = Tail ratio * Gain to Pain Ratio
Common Sense Ratio = [percentile(returns, 95%) * Sum(profits)] / [percentile(returns, 5%) * Sum(losses)]
Trend following strategies have low win rate. Aggregate losses kill profitability. Therefore risk is in the aggregate which is measured by the profit ratio or Gain to Pain Ratio (Jack Schwager favourite measure btw). No surprise since GPR is the ratio version of gain expectancy or trading edge. Mean reverting strategies fail on knockouts. LTCM best case. A few blow-ups and game over. So the risk is in the tail. Therefore, use tail ratio. Now, a bit of arithmetical magic clled transitivity, or multiply GPR by tail and voila CSR. It recaptures risk on both sides.
source:http://alphasecurecapital.com/regardless-of-the-asset-class-there-are-only-two-types-of-strategies/

Common Sense ratio over entire test period = 1.340
------------------------------------------------------------------

VALUE AT RISK(VaR)



a statistical technique used to measure and quantify the possibility of losing money within an investment portfolio over a specific time frame to determine the extent and occurrence ratio of potential losses in their institutional portfolios. VaR quantifies market risk while it is being taken. It measures the odds of losing money but does not indicate certainty. VAR summarizes the predicted maximum loss (or worst loss) over a target horizon within a given confidence interval.

HISTORICAL METHOD VAR

DAILY VAR - HISTORICAL

With 99% confidence, we expect that our worst daily loss will not exceed -1.465%, or in other words we can expect that with a probability of 1% the daily loss of the portfolio will decrease by more than -1.465%.

With 95% confidence, we expect that our worst daily loss will not exceed -0.848%, or in other words we can expect that with a probability of 5% the daily loss of the portfolio will decrease by more than -0.848%.

With 90% confidence, we expect that our worst daily loss will not exceed -0.605%, or in other words we can expect that with a probability of 10% the daily loss of the portfolio will decrease by more than -0.605%.

With 50% confidence, we expect that our worst daily loss will not exceed 0.050%, or in other words we can expect that with a probability of 50% the daily loss of the portfolio will decrease by more than 0.050%.

With 53.9% confidence, we expect that our worst daily loss will not exceed 0%, or in other words we can expect that with a probability of 46.1% the daily loss of the portfolio will decrease by more than 0%.

MONTHLY VAR - HISTORICAL

With 99% confidence, we expect that our worst monthly loss will not exceed -5.321%, or in other words we can expect that with a probability of 1% the monthly loss of the portfolio will decrease by more than -5.321%.

With 95% confidence, we expect that our worst monthly loss will not exceed -3.556%, or in other words we can expect that with a probability of 5% the monthly loss of the portfolio will decrease by more than -3.556%.

With 90% confidence, we expect that our worst monthly loss will not exceed -2.748%, or in other words we can expect that with a probability of 10% the monthly loss of the portfolio will decrease by more than -2.748%.

With 50% confidence, we expect that our worst monthly loss will not exceed 1.237%, or in other words we can expect that with a probability of 50% the monthly loss of the portfolio will decrease by more than 1.237%.

With 71.4% confidence, we expect that our worst monthly loss will not exceed 0%, or in other words we can expect that with a probability of 28.6% the monthly loss of the portfolio will decrease by more than 0%.

ANNUAL VAR - HISTORICAL

With 99% confidence, we expect that our worst annual loss will not exceed 1.463%, or in other words we can expect that with a probability of 1% the annual loss of the portfolio will decrease by more than 1.463%.

With 95% confidence, we expect that our worst annual loss will not exceed 1.997%, or in other words we can expect that with a probability of 5% the annual loss of the portfolio will decrease by more than 1.997%.

With 90% confidence, we expect that our worst annual loss will not exceed 2.499%, or in other words we can expect that with a probability of 10% the annual loss of the portfolio will decrease by more than 2.499%.

With 50% confidence, we expect that our worst annual loss will not exceed 11.760%, or in other words we can expect that with a probability of 50% the annual loss of the portfolio will decrease by more than 11.760%.

With 100.0% confidence, we expect that our worst annual loss will not exceed 0%, or in other words we can expect that with a probability of  0.0% the annual loss of the portfolio will decrease by more than 0%.
In [130]:
print("\n"'With 99% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 1% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[0],yui.returns[0]))
print("\n"'With 95% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 5% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[1],yui.returns[1]))
print("\n"'With 90% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 10% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[2],yui.returns[2]))
print("\n"'With 50% confidence, we expect that our worst daily loss will not exceed {:4,.3f}%, or in other words we can expect'
      ' that with a probability of 50% the daily loss of the portfolio will decrease by more than {:4,.3f}%.'
      .format(yui.returns[3],yui.returns[3]))
print("\n"'With {:4,.1f}% confidence, we expect that our worst daily loss will not exceed 0%, or in other words we can expect'
      ' that with a probability of {:4,.1f}% the daily loss of the portfolio will decrease by more than 0%.'
      .format((1-yui.percent[4])*100,yui.percent[4]*100))
With 99% confidence, we expect that our worst daily loss will not exceed -5.321%, or in other words we can expect that with a probability of 1% the daily loss of the portfolio will decrease by more than -5.321%.

With 95% confidence, we expect that our worst daily loss will not exceed -3.556%, or in other words we can expect that with a probability of 5% the daily loss of the portfolio will decrease by more than -3.556%.

With 90% confidence, we expect that our worst daily loss will not exceed -2.748%, or in other words we can expect that with a probability of 10% the daily loss of the portfolio will decrease by more than -2.748%.

With 50% confidence, we expect that our worst daily loss will not exceed 1.237%, or in other words we can expect that with a probability of 50% the daily loss of the portfolio will decrease by more than 1.237%.

With 71.4% confidence, we expect that our worst daily loss will not exceed 0%, or in other words we can expect that with a probability of 28.6% the daily loss of the portfolio will decrease by more than 0%.
In [ ]:
 
In [18]:
def vwap(transaction):
    return (transaction.amount * transaction.price).sum() / transaction.amount.sum()

def _groupby_consecutive(txn, max_delta=pd.Timedelta('8h')):

    def vwap(transaction):
        if transaction.amount.sum() == 0:
            #warnings.warn('Zero transacted shares, setting vwap to nan.')
            print "Zero transacted shares, setting vwap to nan."
            return np.nan
        return (transaction.amount * transaction.price).sum() / \
            transaction.amount.sum()

    out = []
    for sym, t in txn.groupby('symbol'):
        t = t.sort_index()
        t.index.name = 'dt'
        t = t.reset_index()

        t['order_sign'] = t.amount > 0
        t['block_dir'] = (t.order_sign.shift(
            1) != t.order_sign).astype(int).cumsum()
        t['block_time'] = ((t.dt - t.dt.shift(1)) >
                           max_delta).astype(int).cumsum()
        grouped_price = (t.groupby(('block_dir',
                                   'block_time'))
                          .apply(vwap))
        grouped_price.name = 'price'
        grouped_rest = t.groupby(('block_dir', 'block_time')).agg({
            'amount': 'sum',
            'symbol': 'first',
            'dt': 'first'})

        grouped = grouped_rest.join(grouped_price)

        out.append(grouped)

    out = pd.concat(out)
    out = out.set_index('dt')
    return out

def extract_round_trips_stack(transactions,
                        starting_capital=None):

    transactions = _groupby_consecutive(transactions)
    roundtrips = []

    for sym, trans_sym in transactions.groupby('symbol'):
        trans_sym = trans_sym.sort_index()
        price_stack = deque()
        dt_stack = deque()
        trans_sym['signed_price'] = trans_sym.price * \
            np.sign(trans_sym.amount)
        trans_sym['abs_amount'] = trans_sym.amount.abs().astype(int)
        for dt, t in trans_sym.iterrows():
            if t.price < 0:
                #warnings.warn('Negative price detected, ignoring for'
                              #'round-trip.')
                print "Negative price detected, ignoring for round-trip."
                continue

            indiv_prices = [t.signed_price] * t.abs_amount
            if (len(price_stack) == 0) or \
               (copysign(1, price_stack[-1]) == copysign(1, t.amount)):
                price_stack.extend(indiv_prices)
                dt_stack.extend([dt] * len(indiv_prices))
            else:
                # Close round-trip
                pnl = 0
                invested = 0
                cur_open_dts = []

                for price in indiv_prices:
                    if len(price_stack) != 0 and \
                       (copysign(1, price_stack[-1]) != copysign(1, price)):
                        # Retrieve first dt, stock-price pair from
                        # stack
                        prev_price = price_stack.popleft()
                        prev_dt = dt_stack.popleft()

                        pnl += -(price + prev_price)
                        cur_open_dts.append(prev_dt)
                        invested += abs(prev_price)

                    else:
                        # Push additional stock-prices onto stack
                        price_stack.append(price)
                        dt_stack.append(dt)

                roundtrips.append({'pnl': pnl,
                                   'open_dt': cur_open_dts[0],
                                   'close_dt': dt,
                                   'long': price < 0,
                                   'rt_returns': pnl / invested,
                                   'symbol': sym,
                                   'invested': invested,
                                   })

    roundtrips = pd.DataFrame(roundtrips)

    roundtrips['duration'] = roundtrips['close_dt'] - roundtrips['open_dt']
    
    if starting_capital is not None:
        roundtrips['returns'] = roundtrips['pnl'] / starting_capital    
    '''
    if starting_capital is not None:
        # Need to normalize so that we can join
        pv = pd.DataFrame(starting_capital,
                          columns=['starting_capital'])\
            .assign(date=starting_capital.index)
                

        roundtrips['date'] = roundtrips.close_dt.apply(lambda x:
                                                       x.replace(hour=0,
                                                                 minute=0,
                                                                 second=0))

        tmp = roundtrips.join(pv, on='date', lsuffix='_')

        roundtrips['returns'] = tmp.pnl / tmp.starting_capital
        roundtrips = roundtrips.drop('date', axis='columns')
    '''
    return roundtrips

Application to algorithm

In [19]:
pnl_stats = OrderedDict([('Total profit', lambda x: np.round(x.sum(),0)),
                         ('Gross profit', lambda x: x[x>0].sum()),
                         ('Gross loss', lambda x: x[x<0].sum()),
                         ('Profit factor', lambda x: x[x>0].sum() / x[x<0].abs().sum() if x[x<0].abs().sum() != 0 else np.nan),
                         ('Avg. trade net profit', 'mean'),
                         ('Avg. winning trade', lambda x: x[x>0].mean()),
                         ('Avg. losing trade', lambda x: x[x<0].mean()),
                         ('Ratio Avg. Win:Avg. Loss', lambda x: x[x>0].mean() / x[x<0].abs().mean() if x[x<0].abs().mean() != 0 else np.nan),
                         ('Largest winning trade', 'max'),
                         ('Largest losing trade', 'min')])

summary_stats = OrderedDict([('Total number of trades', 'count'),
                          ('Percent profitable', lambda x: len(x[x>0]) / float(len(x))),
                          ('Winning trades', lambda x: len(x[x>0])),
                          ('Losing trades', lambda x: len(x[x<0])),
                          ('Even trades', lambda x: len(x[x==0])),])

return_stats = OrderedDict([('Avg returns all trades', lambda x: x.mean()),
                            ('Avg returns winning', lambda x: x[x>0].mean()),
                            ('Avg returns losing', lambda x: x[x<0].mean()),
                            ('Median returns all trades', lambda x: x.median()),
                            ('Median returns winning', lambda x: x[x>0].median()),
                            ('Median returns losing', lambda x: x[x<0].median()),
                            ('Profit factor', lambda x: x[x>0].mean() / x[x<0].abs().mean() if x[x<0].abs().mean() != 0 else np.nan),
                            ('Percent profitable', lambda x: len(x[x>0]) / float(len(x))),
                            ('Ratio Avg. Win:Avg. Loss', lambda x: x[x>0].mean() / x[x<0].abs().mean() if x[x<0].abs().mean() != 0 else np.nan),
                            ('Largest winning trade', 'max'),
                            ('Largest losing trade', 'min')])

duration_stats = OrderedDict([('Avg duration', lambda x: x.mean()),
                              ('Median duration', lambda x: x.median()),
                              ('Avg # trades per day', lambda x: (float(len(x)) / (x.max() - x.min()).days) if (x.max() - x.min()).days != 0 else np.nan),
                              ('Avg # trades per month', lambda x: (float(len(x)) / (((x.max() - x.min()).days) / pf.APPROX_BDAYS_PER_MONTH)) if (x.max() - x.min()).days != 0 else np.nan)
                             ])

First Map out Sectors to Backtest Results

In [20]:
# Add symbols
transactions = bt.transactions
transactions['symbol'] = map(lambda x: x.symbol, symbols(transactions.sid))
trades = extract_round_trips_stack(transactions, starting_capital=bt.daily_performance.starting_cash.iloc[0])
def get_SID(row):
    temp_ticker = row['symbol']
    start_date = row['open_dt']

    row['SID'] = symbols(temp_ticker)
    return row
            
trades = trades.apply(get_SID, axis=1)

sector = mstar.asset_classification.morningstar_sector_code.latest

pipe = Pipeline(
    columns={'Close': USEquityPricing.close.latest,
            'Sector Code': sector},
    screen=Q1500US()
)

#run_pipeline(pipe, bt.end_date.date(), bt.end_date.date()).head(10)  # head(10) shows just the first 10 rows
In [21]:
SECTOR_CODE_NAMES = {
    Sector.BASIC_MATERIALS: 'Basic Materials',
    Sector.CONSUMER_CYCLICAL: 'Consumer Cyclical',
    Sector.FINANCIAL_SERVICES: 'Financial Services',
    Sector.REAL_ESTATE: 'Real Estate',
    Sector.CONSUMER_DEFENSIVE: 'Consumer Defensive',
    Sector.HEALTHCARE: 'Healthcare',
    Sector.UTILITIES: 'Utilities',
    Sector.COMMUNICATION_SERVICES: 'Communication Services',
    Sector.ENERGY: 'Energy',
    Sector.INDUSTRIALS: 'Industrials',
    Sector.TECHNOLOGY: 'Technology',
    -1: 'Unknown'
}

def get_q1500_sector_codes(day1, day2):
    pipe = Pipeline(columns={'Sector': Sector()})#, screen=Q1500US())
    # Drop the datetime level of the index, since we only have one day of data.
    return run_pipeline(pipe, day1, day2).reset_index(level=0, drop=True)
def calculate_sector_counts(sectors):
    counts = (sectors.groupby('Sector').size())

    # Replace numeric sector codes with human-friendly names.
    counts.index = counts.index.map(lambda code: SECTOR_CODE_NAMES[code])
    return counts
In [22]:
#INSTEAD OF RUNNING FULL PIPELINE, MAYBE CUSTOM FACTOR ONLY ON SID IN TRADES??
run_pipeline(pipe, bt.end_date.date(), bt.end_date.date()).head(10) 
Out[22]:
Close Sector Code
2016-12-23 00:00:00+00:00 Equity(24 [AAPL]) 116.2900 311
Equity(53 [ABMD]) 111.5100 206
Equity(62 [ABT]) 38.2900 206
Equity(67 [ADSK]) 74.5800 311
Equity(76 [TAP]) 96.8600 205
Equity(114 [ADBE]) 104.7300 311
Equity(122 [ADI]) 73.4500 311
Equity(128 [ADM]) 45.1800 205
Equity(161 [AEP]) 63.2500 207
Equity(166 [AES]) 11.7800 207
In [23]:
zz = get_q1500_sector_codes(bt.end_date.date(),bt.end_date.date())
zz["sector_name"] = zz.Sector.map(lambda code: SECTOR_CODE_NAMES[code])
trades['sector']=trades.SID.map(zz.sector_name)
trades['sector']=trades.sector.fillna('Unknown')

================================================================

Summary trade stats

In [24]:
pd.options.display.float_format = '{:20,.2f}'.format
stats = trades.assign(ones=1).groupby('ones')['pnl'].agg(summary_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
stats2 = trades.groupby('long')['pnl'].agg(summary_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
stats = stats.join(stats2)


print "+++SHOULD SHOW UNIQUE TRADES ALSO!!+++"
stats[['All trades', 'Long trades', 'Short trades']]
+++SHOULD SHOW UNIQUE TRADES ALSO!!+++
Out[24]:
All trades Long trades Short trades
Total number of trades 3,157.00 2,880.00 277.00
Percent profitable 0.62 0.65 0.39
Winning trades 1,969.00 1,861.00 108.00
Losing trades 1,188.00 1,019.00 169.00
Even trades 0.00 0.00 0.00
In [25]:
ts = trades.groupby('sector')['pnl'].agg(summary_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
ts
Out[25]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
Total number of trades 258.00 86.00 573.00 176.00 220.00 137.00 132.00 546.00 13.00 296.00 717.00 3.00
Percent profitable 0.74 0.57 0.65 0.69 0.63 0.63 0.73 0.64 0.46 0.60 0.52 1.00
Winning trades 192.00 49.00 375.00 122.00 139.00 86.00 96.00 350.00 6.00 179.00 372.00 3.00
Losing trades 66.00 37.00 198.00 54.00 81.00 51.00 36.00 196.00 7.00 117.00 345.00 0.00
Even trades 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
In [26]:
ts2 = trades.groupby(['sector','long'])['pnl'].agg(summary_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts2
Out[26]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
long Short trades Long trades Long trades Short trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades
Total number of trades 5.00 253.00 86.00 19.00 554.00 10.00 166.00 2.00 218.00 137.00 3.00 129.00 11.00 535.00 13.00 2.00 294.00 225.00 492.00 3.00
Percent profitable 0.60 0.75 0.57 0.42 0.66 0.40 0.71 0.50 0.63 0.63 1.00 0.72 0.18 0.65 0.46 0.00 0.61 0.39 0.58 1.00
Winning trades 3.00 189.00 49.00 8.00 367.00 4.00 118.00 1.00 138.00 86.00 3.00 93.00 2.00 348.00 6.00 0.00 179.00 87.00 285.00 3.00
Losing trades 2.00 64.00 37.00 11.00 187.00 6.00 48.00 1.00 80.00 51.00 0.00 36.00 9.00 187.00 7.00 2.00 115.00 138.00 207.00 0.00
Even trades 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
In [27]:
tsx = trades[trades.sector == 'Financial Services']
tsxa = trades[trades.sector == 'Healthcare']
ts3a = tsxa.groupby(['sector','long'])['pnl'].agg(summary_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3b = tsx.groupby(['sector','long'])['pnl'].agg(summary_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3 = ts3a.join(ts3b)
ts3
Out[27]:
sector Healthcare Financial Services
long Short trades Long trades Long trades
Total number of trades 3.00 129.00 137.00
Percent profitable 1.00 0.72 0.63
Winning trades 3.00 93.00 86.00
Losing trades 0.00 36.00 51.00
Even trades 0.00 0.00 0.00

================================================================

PnL

In [28]:
pd.options.display.float_format = '{:20,.2f}'.format
stats = trades.assign(ones=1).groupby('ones')['pnl'].agg(pnl_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
stats2 = trades.groupby('long')['pnl'].agg(pnl_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
stats = stats.join(stats2)
stats[['All trades', 'Long trades', 'Short trades']]
Out[28]:
All trades Long trades Short trades
Total profit 639,770.00 755,744.00 -115,974.00
Gross profit 1,788,339.00 1,670,567.28 117,771.72
Gross loss -1,148,569.25 -914,823.35 -233,745.90
Profit factor 1.56 1.83 0.50
Avg. trade net profit 202.65 262.41 -418.68
Avg. winning trade 908.25 897.67 1,090.48
Avg. losing trade -966.81 -897.77 -1,383.11
Ratio Avg. Win:Avg. Loss 0.94 1.00 0.79
Largest winning trade 21,390.30 21,390.30 13,303.58
Largest losing trade -21,290.59 -11,813.16 -21,290.59
In [29]:
ts = trades.groupby('sector')['pnl'].agg(pnl_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
ts
Out[29]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
Total profit 116,905.00 4,376.00 113,273.00 54,363.00 27,714.00 26,706.00 40,158.00 158,423.00 -3,186.00 106,519.00 -8,295.00 2,814.00
Gross profit 185,085.85 30,278.32 246,748.71 118,462.02 174,712.02 59,544.42 72,752.93 301,537.03 13,979.91 199,941.70 382,482.08 2,814.02
Gross loss -68,180.57 -25,902.68 -133,475.88 -64,099.02 -146,997.90 -32,838.63 -32,594.75 -143,113.88 -17,165.44 -93,423.17 -390,777.34 0.00
Profit factor 2.71 1.17 1.85 1.85 1.19 1.81 2.23 2.11 0.81 2.14 0.98 nan
Avg. trade net profit 453.12 50.88 197.68 308.88 125.97 194.93 304.23 290.15 -245.04 359.86 -11.57 938.01
Avg. winning trade 963.99 617.92 658.00 971.00 1,256.92 692.38 757.84 861.53 2,329.98 1,116.99 1,028.18 938.01
Avg. losing trade -1,033.04 -700.07 -674.12 -1,187.02 -1,814.79 -643.89 -905.41 -730.17 -2,452.21 -798.49 -1,132.69 nan
Ratio Avg. Win:Avg. Loss 0.93 0.88 0.98 0.82 0.69 1.08 0.84 1.18 0.95 1.40 0.91 nan
Largest winning trade 13,017.40 4,520.24 11,591.24 13,013.69 21,250.92 7,038.14 9,097.78 13,188.00 10,307.13 15,078.25 21,390.30 1,283.82
Largest losing trade -7,162.82 -6,878.89 -7,352.97 -4,780.58 -9,122.47 -9,281.67 -4,396.59 -7,042.74 -7,284.31 -6,032.79 -21,290.59 359.88
In [30]:
ts2 = trades.groupby(['sector','long'])['pnl'].agg(pnl_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts2
Out[30]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
long Short trades Long trades Long trades Short trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades
Total profit 497.00 116,409.00 4,376.00 -443.00 113,716.00 -2,779.00 57,142.00 13.00 27,701.00 26,706.00 1,835.00 38,323.00 -471.00 158,894.00 -3,186.00 -23.00 106,542.00 -114,601.00 106,306.00 2,814.00
Gross profit 1,377.21 183,708.64 30,278.32 1,137.06 245,611.64 4,220.55 114,241.47 16.26 174,695.75 59,544.42 1,834.79 70,918.14 398.30 301,138.73 13,979.91 0.00 199,941.70 108,787.55 273,694.52 2,814.02
Gross loss -880.62 -67,299.95 -25,902.68 -1,580.50 -131,895.37 -6,999.98 -57,099.04 -2.87 -146,995.03 -32,838.63 0.00 -32,594.75 -869.61 -142,244.27 -17,165.44 -23.32 -93,399.85 -223,389.01 -167,388.33 0.00
Profit factor 1.56 2.73 1.17 0.72 1.86 0.60 2.00 5.67 1.19 1.81 nan 2.18 0.46 2.12 0.81 0.00 2.14 0.49 1.64 nan
Avg. trade net profit 99.32 460.11 50.88 -23.34 205.26 -277.94 344.23 6.70 127.07 194.93 611.60 297.08 -42.85 297.00 -245.04 -11.66 362.39 -509.34 216.07 938.01
Avg. winning trade 459.07 972.00 617.92 142.13 669.24 1,055.14 968.15 16.26 1,265.91 692.38 611.60 762.56 199.15 865.34 2,329.98 nan 1,116.99 1,250.43 960.33 938.01
Avg. losing trade -440.31 -1,051.56 -700.07 -143.68 -705.32 -1,166.66 -1,189.56 -2.87 -1,837.44 -643.89 nan -905.41 -96.62 -760.66 -2,452.21 -11.66 -812.17 -1,618.76 -808.64 nan
Ratio Avg. Win:Avg. Loss 1.04 0.92 0.88 0.99 0.95 0.90 0.81 5.67 0.69 1.08 nan 0.84 2.06 1.14 0.95 nan 1.38 0.77 1.19 nan
Largest winning trade 1,297.01 13,017.40 4,520.24 1,111.55 11,591.24 2,349.80 13,013.69 16.26 21,250.92 7,038.14 1,558.56 9,097.78 317.99 13,188.00 10,307.13 -1.71 15,078.25 13,303.58 21,390.30 1,283.82
Largest losing trade -473.64 -7,162.82 -6,878.89 -1,127.37 -7,352.97 -3,710.06 -4,780.58 -2.87 -9,122.47 -9,281.67 115.22 -4,396.59 -309.47 -7,042.74 -7,284.31 -21.61 -6,032.79 -21,290.59 -11,813.16 359.88
In [31]:
tsx = trades[trades.sector == 'Financial Services']
tsxa = trades[trades.sector == 'Healthcare']
ts3a = tsxa.groupby(['sector','long'])['pnl'].agg(pnl_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3b = tsx.groupby(['sector','long'])['pnl'].agg(pnl_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3 = ts3a.join(ts3b)
ts3
Out[31]:
sector Healthcare Financial Services
long Short trades Long trades Long trades
Total profit 1,835.00 38,323.00 26,706.00
Gross profit 1,834.79 70,918.14 59,544.42
Gross loss 0.00 -32,594.75 -32,838.63
Profit factor nan 2.18 1.81
Avg. trade net profit 611.60 297.08 194.93
Avg. winning trade 611.60 762.56 692.38
Avg. losing trade nan -905.41 -643.89
Ratio Avg. Win:Avg. Loss nan 0.84 1.08
Largest winning trade 1,558.56 9,097.78 7,038.14
Largest losing trade 115.22 -4,396.59 -9,281.67

================================================================

Duration

In [32]:
d_stats = trades.assign(ones=1).groupby('ones')['duration'].agg(duration_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
d_stats_wl = trades.groupby('long')['duration'].agg(duration_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
d_stats = d_stats.join(d_stats_wl)
d_stats[['All trades', 'Long trades', 'Short trades']]
Out[32]:
All trades Long trades Short trades
Avg duration 75 days 21:24:00.228064 64 days 06:05:30 196 days 21:24:02.599277
Median duration 40 days 00:00:00 34 days 00:00:00 126 days 00:00:00
Avg # trades per day 1.14 5.54 0.10
Avg # trades per month 23.92 116.31 2.10
In [33]:
ts = trades.groupby('sector')['duration'].agg(duration_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
ts
Out[33]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
Avg duration 82 days 08:27:54.418604 83 days 19:32:05.581395 57 days 00:30:09.424083 90 days 06:40:54.545454 56 days 23:40:21.818181 102 days 12:36:47.299270 57 days 14:00:00 63 days 04:02:38.241758 95 days 01:50:46.153846 72 days 04:12:58.378378 99 days 01:36:24.100418 89 days 08:00:00
Median duration 58 days 00:00:00 58 days 12:00:00 33 days 00:00:00 59 days 00:00:00 33 days 00:00:00 63 days 00:00:00 31 days 00:00:00 33 days 00:00:00 58 days 00:00:00 59 days 00:00:00 61 days 00:00:00 90 days 00:00:00
Avg # trades per day 0.40 0.28 0.30 0.06 0.73 0.41 0.07 1.05 0.05 0.57 0.86 1.50
Avg # trades per month 8.45 5.98 6.39 1.33 15.30 8.59 1.52 22.05 1.00 12.02 18.05 31.50
In [34]:
ts2 = trades.groupby(['sector','duration'])['duration'].agg(duration_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')    
ts2
Out[34]:
sector Basic Materials ... Unknown Utilities
duration 0 days 2 days 13 days 17 days 20 days 21 days 25 days 26 days 27 days 28 days ... 596 days 628 days 686 days 749 days 769 days 776 days 822 days 834 days 88 days 90 days
Avg duration 0 days 2 days 13 days 17 days 20 days 21 days 25 days 26 days 27 days 28 days ... 596 days 628 days 686 days 749 days 769 days 776 days 822 days 834 days 88 days 90 days
Median duration 0 days 2 days 13 days 17 days 20 days 21 days 25 days 26 days 27 days 28 days ... 596 days 628 days 686 days 749 days 769 days 776 days 822 days 834 days 88 days 90 days
Avg # trades per day NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT ... NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT
Avg # trades per month NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT ... NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT

4 rows × 860 columns

In [35]:
tsx = trades[trades.sector == 'Financial Services']
tsxa = trades[trades.sector == 'Healthcare']
ts3a = tsxa.groupby(['sector','duration'])['duration'].agg(duration_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3b = tsx.groupby(['sector','duration'])['duration'].agg(duration_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3 = ts3a.join(ts3b)


ts3
Out[35]:
sector Healthcare ... Financial Services
duration 0 days 1 days 2 days 4 days 8 days 13 days 14 days 15 days 18 days 19 days ... 244 days 245 days 246 days 247 days 249 days 272 days 273 days 276 days 333 days 335 days
Avg duration 0 days 1 days 2 days 4 days 8 days 13 days 14 days 15 days 18 days 19 days ... 244 days 245 days 246 days 247 days 249 days 272 days 273 days 276 days 333 days 335 days
Median duration 0 days 1 days 2 days 4 days 8 days 13 days 14 days 15 days 18 days 19 days ... 244 days 245 days 246 days 247 days 249 days 272 days 273 days 276 days 333 days 335 days
Avg # trades per day NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT ... NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT
Avg # trades per month NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT ... NaT NaT NaT NaT NaT NaT NaT NaT NaT NaT

4 rows × 115 columns

================================================================

Returns-based

In [36]:
pd.options.display.float_format = '{0:.5f}%'.format
stats = trades.assign(ones=1).groupby('ones')['returns'].agg(return_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
stats2 = trades.groupby('long')['returns'].agg(return_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
stats = stats.join(stats2)
print "CAUTION-MAX WIN/LOSING TRADE AT 100X"
(stats[['All trades', 'Long trades', 'Short trades']] * 100)
CAUTION-MAX WIN/LOSING TRADE AT 100X
Out[36]:
All trades Long trades Short trades
Avg returns all trades 0.20265% 0.26241% -0.41868%
Avg returns winning 0.90825% 0.89767% 1.09048%
Avg returns losing -0.96681% -0.89777% -1.38311%
Median returns all trades 0.02443% 0.03241% -0.05388%
Median returns winning 0.24286% 0.23106% 0.43619%
Median returns losing -0.25973% -0.22602% -0.47933%
Profit factor 93.94278% 99.98953% 78.84243%
Percent profitable 62.36934% 64.61806% 38.98917%
Ratio Avg. Win:Avg. Loss 93.94278% 99.98953% 78.84243%
Largest winning trade 21.39030% 21.39030% 13.30358%
Largest losing trade -21.29059% -11.81316% -21.29059%
In [37]:
ts = trades.groupby('sector')['returns'].agg(return_stats).T.rename_axis({1.0: 'All trades'}, axis='columns')
ts
Out[37]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
Avg returns all trades 0.00453% 0.00051% 0.00198% 0.00309% 0.00126% 0.00195% 0.00304% 0.00290% -0.00245% 0.00360% -0.00012% 0.00938%
Avg returns winning 0.00964% 0.00618% 0.00658% 0.00971% 0.01257% 0.00692% 0.00758% 0.00862% 0.02330% 0.01117% 0.01028% 0.00938%
Avg returns losing -0.01033% -0.00700% -0.00674% -0.01187% -0.01815% -0.00644% -0.00905% -0.00730% -0.02452% -0.00798% -0.01133% nan%
Median returns all trades 0.00085% 0.00018% 0.00007% 0.00066% 0.00023% 0.00039% 0.00011% 0.00050% -0.00002% 0.00041% 0.00002% 0.01170%
Median returns winning 0.00302% 0.00136% 0.00122% 0.00332% 0.00350% 0.00252% 0.00099% 0.00313% 0.00727% 0.00340% 0.00334% 0.01170%
Median returns losing -0.00485% -0.00019% -0.00075% -0.00629% -0.00563% -0.00138% -0.00297% -0.00261% -0.00373% -0.00232% -0.00357% nan%
Profit factor 0.93316% 0.88266% 0.97608% 0.81802% 0.69260% 1.07530% 0.83702% 1.17990% 0.95016% 1.39888% 0.90773% nan%
Percent profitable 0.74419% 0.56977% 0.65445% 0.69318% 0.63182% 0.62774% 0.72727% 0.64103% 0.46154% 0.60473% 0.51883% 1.00000%
Ratio Avg. Win:Avg. Loss 0.93316% 0.88266% 0.97608% 0.81802% 0.69260% 1.07530% 0.83702% 1.17990% 0.95016% 1.39888% 0.90773% nan%
Largest winning trade 0.13017% 0.04520% 0.11591% 0.13014% 0.21251% 0.07038% 0.09098% 0.13188% 0.10307% 0.15078% 0.21390% 0.01284%
Largest losing trade -0.07163% -0.06879% -0.07353% -0.04781% -0.09122% -0.09282% -0.04397% -0.07043% -0.07284% -0.06033% -0.21291% 0.00360%
In [38]:
ts2 = trades.groupby(['sector','long'])['returns'].agg(return_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts2
Out[38]:
sector Basic Materials Communication Services Consumer Cyclical Consumer Defensive Energy Financial Services Healthcare Industrials Real Estate Technology Unknown Utilities
long Short trades Long trades Long trades Short trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades Short trades Long trades Short trades Long trades Long trades
Avg returns all trades 0.00099% 0.00460% 0.00051% -0.00023% 0.00205% -0.00278% 0.00344% 0.00007% 0.00127% 0.00195% 0.00612% 0.00297% -0.00043% 0.00297% -0.00245% -0.00012% 0.00362% -0.00509% 0.00216% 0.00938%
Avg returns winning 0.00459% 0.00972% 0.00618% 0.00142% 0.00669% 0.01055% 0.00968% 0.00016% 0.01266% 0.00692% 0.00612% 0.00763% 0.00199% 0.00865% 0.02330% nan% 0.01117% 0.01250% 0.00960% 0.00938%
Avg returns losing -0.00440% -0.01052% -0.00700% -0.00144% -0.00705% -0.01167% -0.01190% -0.00003% -0.01837% -0.00644% nan% -0.00905% -0.00097% -0.00761% -0.02452% -0.00012% -0.00812% -0.01619% -0.00809% nan%
Median returns all trades 0.00000% 0.00091% 0.00018% -0.00001% 0.00009% -0.00092% 0.00068% 0.00007% 0.00028% 0.00039% 0.00161% 0.00009% -0.00016% 0.00054% -0.00002% -0.00012% 0.00043% -0.00109% 0.00012% 0.01170%
Median returns winning 0.00080% 0.00303% 0.00136% 0.00002% 0.00127% 0.00732% 0.00285% 0.00016% 0.00361% 0.00252% 0.00161% 0.00088% 0.00199% 0.00313% 0.00727% nan% 0.00340% 0.00501% 0.00252% 0.01170%
Median returns losing -0.00440% -0.00512% -0.00019% -0.00024% -0.00077% -0.00504% -0.00629% -0.00003% -0.00596% -0.00138% nan% -0.00297% -0.00057% -0.00280% -0.00373% -0.00012% -0.00241% -0.00761% -0.00111% nan%
Profit factor 1.04261% 0.92434% 0.88266% 0.98922% 0.94884% 0.90441% 0.81387% 5.67251% 0.68895% 1.07530% nan% 0.84223% 2.06108% 1.13761% 0.95016% nan% 1.37531% 0.77246% 1.18759% nan%
Percent profitable 0.60000% 0.74704% 0.56977% 0.42105% 0.66245% 0.40000% 0.71084% 0.50000% 0.63303% 0.62774% 1.00000% 0.72093% 0.18182% 0.65047% 0.46154% 0.00000% 0.60884% 0.38667% 0.57927% 1.00000%
Ratio Avg. Win:Avg. Loss 1.04261% 0.92434% 0.88266% 0.98922% 0.94884% 0.90441% 0.81387% 5.67251% 0.68895% 1.07530% nan% 0.84223% 2.06108% 1.13761% 0.95016% nan% 1.37531% 0.77246% 1.18759% nan%
Largest winning trade 0.01297% 0.13017% 0.04520% 0.01112% 0.11591% 0.02350% 0.13014% 0.00016% 0.21251% 0.07038% 0.01559% 0.09098% 0.00318% 0.13188% 0.10307% -0.00002% 0.15078% 0.13304% 0.21390% 0.01284%
Largest losing trade -0.00474% -0.07163% -0.06879% -0.01127% -0.07353% -0.03710% -0.04781% -0.00003% -0.09122% -0.09282% 0.00115% -0.04397% -0.00309% -0.07043% -0.07284% -0.00022% -0.06033% -0.21291% -0.11813% 0.00360%
In [39]:
tsx = trades[trades.sector == 'Financial Services']
tsxa = trades[trades.sector == 'Healthcare']
ts3a = tsxa.groupby(['sector','long'])['returns'].agg(return_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3b = tsx.groupby(['sector','long'])['returns'].agg(return_stats).T.rename_axis({False: 'Short trades', True: 'Long trades'}, axis='columns')
ts3 = ts3a.join(ts3b)
ts3
Out[39]:
sector Healthcare Financial Services
long Short trades Long trades Long trades
Avg returns all trades 0.00612% 0.00297% 0.00195%
Avg returns winning 0.00612% 0.00763% 0.00692%
Avg returns losing nan% -0.00905% -0.00644%
Median returns all trades 0.00161% 0.00009% 0.00039%
Median returns winning 0.00161% 0.00088% 0.00252%
Median returns losing nan% -0.00297% -0.00138%
Profit factor nan% 0.84223% 1.07530%
Percent profitable 1.00000% 0.72093% 0.62774%
Ratio Avg. Win:Avg. Loss nan% 0.84223% 1.07530%
Largest winning trade 0.01559% 0.09098% 0.07038%
Largest losing trade 0.00115% -0.04397% -0.09282%

================================================================

Symbol-wise

In [40]:
pd.options.display.float_format = '{0:.3f}%'.format 
lp = trades.groupby('symbol')['returns'].agg(return_stats) * 100
lp = lp.sort_index()
qwe = pd.DataFrame(data = {'symbol':[], 'number of trades':[]})
for i in lp.index.values:
    yu = len(trades[trades.symbol == i])
    qwe = qwe.append({'symbol':i, 'number of trades':yu}, ignore_index=True)
qwe = qwe.set_index('symbol')  
lp = lp.merge(qwe, left_index=True, right_index=True, how='left')
cols = lp.columns.tolist()
cols = cols[-1:] + cols[:-1]
lp = lp[cols]
lp = lp.sort('Avg returns all trades',ascending=False)
lp['number of trades'] = lp['number of trades'].map('{:,.0f}'.format)
lp['Profit factor'] = (lp['Profit factor'] / 100 ).map('{:,.3f}'.format) 

lp
    
    
    
Out[40]:
number of trades Avg returns all trades Avg returns winning Avg returns losing Median returns all trades Median returns winning Median returns losing Profit factor Percent profitable Ratio Avg. Win:Avg. Loss Largest winning trade Largest losing trade
symbol
OIS 1 7.771% 7.771% nan% 7.771% 7.771% nan% nan 100.000% nan% 7.771% 7.771%
GDOT 1 7.038% 7.038% nan% 7.038% 7.038% nan% nan 100.000% nan% 7.038% 7.038%
MPC 1 6.735% 6.735% nan% 6.735% 6.735% nan% nan 100.000% nan% 6.735% 6.735%
SHO 2 6.090% 6.090% nan% 6.090% 6.090% nan% nan 100.000% nan% 10.307% 1.873%
MOH 1 5.437% 5.437% nan% 5.437% 5.437% nan% nan 100.000% nan% 5.437% 5.437%
HA 1 5.418% 5.418% nan% 5.418% 5.418% nan% nan 100.000% nan% 5.418% 5.418%
FSLR 3 5.389% 8.734% -1.302% 2.390% 8.734% -1.302% 6.711 66.667% 671.057% 15.078% -1.302%
SCL 1 5.177% 5.177% nan% 5.177% 5.177% nan% nan 100.000% nan% 5.177% 5.177%
PATK 2 4.738% 4.738% nan% 4.738% 4.738% nan% nan 100.000% nan% 9.001% 0.474%
UFI 1 4.679% 4.679% nan% 4.679% 4.679% nan% nan 100.000% nan% 4.679% 4.679%
IQNT 1 4.520% 4.520% nan% 4.520% 4.520% nan% nan 100.000% nan% 4.520% 4.520%
ADUS 3 4.286% 4.286% nan% 2.460% 2.460% nan% nan 100.000% nan% 9.098% 1.300%
NUS 1 4.250% 4.250% nan% 4.250% 4.250% nan% nan 100.000% nan% 4.250% 4.250%
MTZ 1 4.241% 4.241% nan% 4.241% 4.241% nan% nan 100.000% nan% 4.241% 4.241%
AIR 1 4.158% 4.158% nan% 4.158% 4.158% nan% nan 100.000% nan% 4.158% 4.158%
FOSL 1 4.124% 4.124% nan% 4.124% 4.124% nan% nan 100.000% nan% 4.124% 4.124%
VEC 2 4.054% 4.054% nan% 4.054% 4.054% nan% nan 100.000% nan% 5.031% 3.077%
HMA 4 3.936% 3.936% nan% 1.762% 1.762% nan% nan 100.000% nan% 12.209% 0.012%
CRNT 3 3.751% 3.751% nan% 0.268% 0.268% nan% nan 100.000% nan% 10.804% 0.180%
ONE 3 3.468% 3.468% nan% 2.825% 2.825% nan% nan 100.000% nan% 7.255% 0.324%
RIGP 4 3.434% 3.434% nan% 1.068% 1.068% nan% nan 100.000% nan% 11.448% 0.152%
MRC 5 3.342% 5.270% -4.372% 3.792% 4.006% -4.372% 1.205 80.000% 120.532% 12.731% -4.372%
VNCE 2 3.310% 3.310% nan% 3.310% 3.310% nan% nan 100.000% nan% 3.556% 3.063%
RYAM 1 3.291% 3.291% nan% 3.291% 3.291% nan% nan 100.000% nan% 3.291% 3.291%
GRMN 2 3.169% 3.169% nan% 3.169% 3.169% nan% nan 100.000% nan% 3.607% 2.732%
ENTA 1 3.047% 3.047% nan% 3.047% 3.047% nan% nan 100.000% nan% 3.047% 3.047%
CACC 2 2.991% 2.991% nan% 2.991% 2.991% nan% nan 100.000% nan% 5.796% 0.187%
AN 1 2.819% 2.819% nan% 2.819% 2.819% nan% nan 100.000% nan% 2.819% 2.819%
GVA 1 2.737% 2.737% nan% 2.737% 2.737% nan% nan 100.000% nan% 2.737% 2.737%
WNR 2 2.642% 2.642% nan% 2.642% 2.642% nan% nan 100.000% nan% 4.864% 0.420%
... ... ... ... ... ... ... ... ... ... ... ... ...
NAFC 1 -1.449% nan% -1.449% -1.449% nan% -1.449% nan 0.000% nan% -1.449% -1.449%
KFRC 1 -1.561% nan% -1.561% -1.561% nan% -1.561% nan 0.000% nan% -1.561% -1.561%
IHR 2 -1.593% 0.000% -3.187% -1.593% 0.000% -3.187% 0.000 50.000% 0.014% 0.000% -3.187%
MTGE 3 -1.686% nan% -1.686% -0.037% nan% -0.037% nan 0.000% nan% -0.005% -5.017%
NE 7 -1.708% 1.526% -4.134% -2.921% 2.035% -4.332% 0.369 42.857% 36.922% 2.399% -4.952%
PRX 2 -1.853% nan% -1.853% -1.853% nan% -1.853% nan 0.000% nan% -0.002% -3.705%
GCI 1 -1.955% nan% -1.955% -1.955% nan% -1.955% nan 0.000% nan% -1.955% -1.955%
PEIX 7 -1.981% 3.883% -4.327% -0.001% 3.883% -6.486% 0.898 28.571% 89.756% 4.109% -8.059%
LZB 1 -2.059% nan% -2.059% -2.059% nan% -2.059% nan 0.000% nan% -2.059% -2.059%
URS 4 -2.060% nan% -2.060% -1.658% nan% -1.658% nan 0.000% nan% -0.001% -4.925%
OME 5 -2.236% 0.006% -2.796% -2.449% 0.006% -3.197% 0.002 20.000% 0.219% 0.006% -4.781%
ARC 3 -2.271% nan% -2.271% -2.768% nan% -2.768% nan 0.000% nan% -0.025% -4.020%
GT 1 -2.344% nan% -2.344% -2.344% nan% -2.344% nan 0.000% nan% -2.344% -2.344%
CPA 3 -2.428% nan% -2.428% -2.361% nan% -2.361% nan 0.000% nan% -0.909% -4.013%
CTCM 1 -2.441% nan% -2.441% -2.441% nan% -2.441% nan 0.000% nan% -2.441% -2.441%
VPHM 2 -2.551% nan% -2.551% -2.551% nan% -2.551% nan 0.000% nan% -0.358% -4.744%
AHG 1 -2.625% nan% -2.625% -2.625% nan% -2.625% nan 0.000% nan% -2.625% -2.625%
XRTX 2 -2.787% 0.216% -5.790% -2.787% 0.216% -5.790% 0.037 50.000% 3.732% 0.216% -5.790%
FRO 1 -3.063% nan% -3.063% -3.063% nan% -3.063% nan 0.000% nan% -3.063% -3.063%
GIFI 2 -3.085% nan% -3.085% -3.085% nan% -3.085% nan 0.000% nan% -2.328% -3.842%
CALL 3 -3.297% nan% -3.297% -2.321% nan% -2.321% nan 0.000% nan% -0.693% -6.879%
MITT 2 -3.643% nan% -3.643% -3.643% nan% -3.643% nan 0.000% nan% -0.002% -7.284%
SPN 3 -3.768% nan% -3.768% -2.923% nan% -2.923% nan 0.000% nan% -1.881% -6.502%
HGG 2 -4.218% nan% -4.218% -4.218% nan% -4.218% nan 0.000% nan% -1.762% -6.674%
SB 3 -4.253% 0.274% -6.517% -5.991% 0.274% -6.517% 0.042 33.333% 4.203% 0.274% -7.043%
VRA 1 -4.269% nan% -4.269% -4.269% nan% -4.269% nan 0.000% nan% -4.269% -4.269%
JOE 1 -4.447% nan% -4.447% -4.447% nan% -4.447% nan 0.000% nan% -4.447% -4.447%
BTH 1 -4.925% nan% -4.925% -4.925% nan% -4.925% nan 0.000% nan% -4.925% -4.925%
NR 1 -5.506% nan% -5.506% -5.506% nan% -5.506% nan 0.000% nan% -5.506% -5.506%
FU 1 -5.605% nan% -5.605% -5.605% nan% -5.605% nan 0.000% nan% -5.605% -5.605%

465 rows × 12 columns

================================================================

Median holding time per symbol

In [41]:
med_hold = trades.groupby('symbol')['duration'].agg(lambda x: x.median())
#not super helpful ... for me atleast when using larger universe size
med_hold.sort(axis=0, ascending=True, kind='quicksort', na_position='last', inplace=True)
med_hold
Out[41]:
symbol
PERY       0 days 00:00:00
BBOX       0 days 00:00:00
HNI        0 days 00:00:00
DEPO       1 days 00:00:00
CITP       2 days 00:00:00
SPAR       3 days 12:00:00
PKD        4 days 00:00:00
SFN       10 days 00:00:00
CVI       14 days 00:00:00
RCII      14 days 12:00:00
STRL      17 days 12:00:00
BTH       19 days 00:00:00
ITG       19 days 00:00:00
PEIX      26 days 00:00:00
DLA       26 days 12:00:00
VPHM      27 days 00:00:00
CTCM      27 days 00:00:00
DW        27 days 00:00:00
KFRC      27 days 00:00:00
EBS       27 days 00:00:00
CAS       27 days 00:00:00
NR        27 days 00:00:00
DFZ       27 days 12:00:00
SHLO      28 days 00:00:00
UFI       28 days 00:00:00
UTMD      28 days 00:00:00
LYTS      28 days 00:00:00
GOGL      28 days 00:00:00
ICFI      28 days 00:00:00
CLF       28 days 00:00:00
                ...       
AEO       95 days 00:00:00
ODC       97 days 00:00:00
NHC       99 days 00:00:00
WSTC     103 days 12:00:00
WCG      104 days 00:00:00
ALG      104 days 12:00:00
WCRX     106 days 00:00:00
KND      106 days 12:00:00
RYI      108 days 00:00:00
RTI      109 days 00:00:00
VSEC     119 days 00:00:00
CMC      122 days 00:00:00
NCIT     123 days 12:00:00
GSOL     124 days 00:00:00
TECD     124 days 00:00:00
UDF      133 days 00:00:00
INT      136 days 12:00:00
IWM      150 days 00:00:00
MITT     151 days 12:00:00
NUE      153 days 00:00:00
NHTC     153 days 00:00:00
HNNA     154 days 00:00:00
AGX      165 days 00:00:00
XEC      166 days 12:00:00
CLW      215 days 00:00:00
WDC      230 days 00:00:00
BELF_A   239 days 00:00:00
MTGE     240 days 00:00:00
HALL     261 days 12:00:00
FU       268 days 00:00:00
Name: duration, dtype: timedelta64[ns]

================================================================

Regular Tearsheet

In [42]:
returns = bt.daily_performance.returns
cash = bt.daily_performance['ending_cash']
positions = pf.capacity.pos.extract_pos(bt.positions, cash)
#transactions = _groupby_consecutive(bt.transactions)
gross_lev=bt.recorded_vars.leverage
In [43]:
print "Downside deviation is a measure of downside risk that focuses on returns that fall below a minimum threshold or minimum acceptable return (MAR)."
print("\n"'Annualized downside deviation below 0% per month = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.0, period='monthly'))* 100))
print("\n"'Annualized downside deviation below 1% per month = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.01, period='monthly'))* 100))
print("\n"'Annualized downside deviation below 2% per month = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.02, period='monthly'))* 100))
print("\n"'Annualized downside deviation below 3% per month = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.03, period='monthly'))* 100))
print "\n"
print("\n"'Annualized downside deviation below 0% per year = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.0, period='yearly'))* 100))
print("\n"'Annualized downside deviation below 1% per year = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.01, period='yearly'))* 100))
print("\n"'Annualized downside deviation below 2% per year = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.02, period='yearly'))* 100))
print("\n"'Annualized downside deviation below 3% per year = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.03, period='yearly'))* 100))
print("\n"'Annualized downside deviation below 5% per year = {:3}%'.format((pf.capacity.empyrical.stats.downside_risk(returns, required_return=0.05, period='yearly'))* 100))
Downside deviation is a measure of downside risk that focuses on returns that fall below a minimum threshold or minimum acceptable return (MAR).

Annualized downside deviation below 0% per month = 1.37701878364%

Annualized downside deviation below 1% per month = 3.84147751727%

Annualized downside deviation below 2% per month = 7.04333770968%

Annualized downside deviation below 3% per month = 10.4045265056%



Annualized downside deviation below 0% per year = 0.397511082706%

Annualized downside deviation below 1% per year = 1.10893903934%

Annualized downside deviation below 2% per year = 2.03323646134%

Annualized downside deviation below 3% per year = 3.00352808939%

Annualized downside deviation below 5% per year = 4.97872372413%
In [44]:
# Run model that assumes returns to be T-distributed
#returns = bt.daily_performance.returns
trace = pf.bayesian.run_model('t', returns)
print("\n"'Probability of Sharpe ratio > 0 = {:3}%'.format((trace['sharpe'] > 0).mean() * 100))
print("\n"'Probability of Sharpe ratio > 0.25 = {:3}%'.format((trace['sharpe'] > 0.25).mean() * 100))
print("\n"'Probability of Sharpe ratio > 0.5 = {:3}%'.format((trace['sharpe'] > 0.5).mean() * 100))
print("\n"'Probability of Sharpe ratio > 0.75 = {:3}%'.format((trace['sharpe'] > 0.75).mean() * 100))
print("\n"'Probability of Sharpe ratio > 1 = {:3}%'.format((trace['sharpe'] > 1).mean() * 100))
print("\n"'Probability of Sharpe ratio > 1.25 = {:3}%'.format((trace['sharpe'] > 1.25).mean() * 100))
print("\n"'Probability of Sharpe ratio > 1.5 = {:3}%'.format((trace['sharpe'] > 1.5).mean() * 100))
#import pymc3 as pm #IMPORT RESTRICTION!!!
#pm.traceplot(trace);
 [-----------------100%-----------------] 500 of 500 complete in 4.9 sec
Probability of Sharpe ratio > 0 = 100.0%

Probability of Sharpe ratio > 0.25 = 100.0%

Probability of Sharpe ratio > 0.5 = 100.0%

Probability of Sharpe ratio > 0.75 = 100.0%

Probability of Sharpe ratio > 1 = 95.4%

Probability of Sharpe ratio > 1.25 = 71.8%

Probability of Sharpe ratio > 1.5 = 34.8%
In [45]:
pf.plot_prob_profit_trade(trades)
matplotlib.pyplot.figure()
pf.plot_slippage_sensitivity(returns, positions = positions, transactions = bt.transactions)
matplotlib.pyplot.figure()
pf.plot_slippage_sweep(returns, positions = positions, transactions = bt.transactions, slippage_params=(3, 8, 12, 18, 30, 50))
matplotlib.pyplot.figure()
plt.plot(bt.risk.alpha.index, bt.risk.alpha.values);
plt.ylabel('Single Factor Market Alpha');