Notebook

Run the cell below to create your tear sheet, or return to your algorithm.

In [1]:
bt = get_backtest('5a80a23c6f9feb46a6f0a497')
bt.create_full_tear_sheet()
import empyrical as ep
import pyfolio as pf
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from quantopian.research import returns
from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.filters import QTradableStocksUS

def get_tradable_universe(start, end):
    """
    Gets the tradable universe in a format that can be compared to the positions
    of a backtest.
    """
    pipe = Pipeline(
        columns={'qtu':QTradableStocksUS()}
    )
    df = run_pipeline(pipe, start, end)
    df = df.unstack()
    df.columns = df.columns.droplevel()
    df = df.astype(float).replace(0, np.nan)
    return df
def volatility_adjusted_daily_return(trailing_algorithm_returns):
    """
    Normalize the last daily return in `trailing_algorithm_returns` by the annualized
    volatility of `trailing_algorithm_returns`.
    """
    
    todays_return = trailing_algorithm_returns[-1]
    # Volatility is floored at 2%.
    volatility = max(ep.annual_volatility(trailing_algorithm_returns), 0.02)
    score = (todays_return / volatility)
    
    return score
def compute_score(algorithm_returns):
    """
    Compute the score of a backtest from its algorithm_returns.
    """
    
    result = []
    
    cumulative_score = 0
    count = 0
    
    daily_scores = roll(
        algorithm_returns,
        function=volatility_adjusted_daily_return,
        window=63
    )
    
    cumulative_score = np.cumsum(daily_scores[441:])
    latest_score = cumulative_score[-1]
    
    print ''
    print 'Score computed between %s and %s.' % (cumulative_score.index[0].date(), daily_scores.index[-1].date())
    
    plt.plot(cumulative_score)
    plt.title('Out-of-Sample Score Over Time')
    print 'Cumulative Score: %f' % latest_score
    
    return cumulative_score
# This code is copied from the empyrical repository.
# Source: https://github.com/quantopian/empyrical/blob/master/empyrical/utils.py#L49
# Includes a fix to the bug reported here: https://github.com/quantopian/empyrical/issues/79
def roll(*args, **kwargs):
    """
    Calculates a given statistic across a rolling time period.
    Parameters
    ----------
    returns : pd.Series or np.ndarray
        Daily returns of the strategy, noncumulative.
        - See full explanation in :func:`~empyrical.stats.cum_returns`.
    factor_returns (optional): float / series
        Benchmark return to compare returns against.
    function:
        the function to run for each rolling window.
    window (keyword): int
        the number of periods included in each calculation.
    (other keywords): other keywords that are required to be passed to the
        function in the 'function' argument may also be passed in.
    Returns
    -------
    np.ndarray, pd.Series
        depends on input type
        ndarray(s) ==> ndarray
        Series(s) ==> pd.Series
        A Series or ndarray of the results of the stat across the rolling
        window.
    """
    func = kwargs.pop('function')
    window = kwargs.pop('window')
    if len(args) > 2:
        raise ValueError("Cannot pass more than 2 return sets")

    if len(args) == 2:
        if not isinstance(args[0], type(args[1])):
            raise ValueError("The two returns arguments are not the same.")

    if isinstance(args[0], np.ndarray):
        return _roll_numpy(func, window, *args, **kwargs)
    return _roll_pandas(func, window, *args, **kwargs)

def _roll_ndarray(func, window, *args, **kwargs):
    data = []
    for i in range(window, len(args[0]) + 1):
        rets = [s[i-window:i] for s in args]
        data.append(func(*rets, **kwargs))
    return np.array(data)


def _roll_pandas(func, window, *args, **kwargs):
    data = {}
    for i in range(window, len(args[0]) + 1):
        rets = [s.iloc[i-window:i] for s in args]
        data[args[0].index[i - 1]] = func(*rets, **kwargs)
    return pd.Series(data)
SECTORS = [
    'basic_materials', 'consumer_cyclical', 'financial_services',
    'real_estate', 'consumer_defensive', 'health_care', 'utilities',
    'communication_services', 'energy', 'industrials', 'technology'
]

STYLES = [
    'momentum', 'size', 'value', 'short_term_reversal', 'volatility'
]

POSITION_CONCENTRATION_98TH_MAX = 0.05
POSITION_CONCENTRATION_100TH_MAX = 0.1
LEVERAGE_0TH_MIN = 0.7
LEVERAGE_2ND_MIN = 0.8
LEVERAGE_98TH_MAX = 1.1
LEVERAGE_100TH_MAX = 1.2
DAILY_TURNOVER_0TH_MIN = 0.03
DAILY_TURNOVER_2ND_MIN = 0.05
DAILY_TURNOVER_98TH_MAX = 0.65
DAILY_TURNOVER_100TH_MAX = 0.8
NET_EXPOSURE_LIMIT_98TH_MAX = 0.1
NET_EXPOSURE_LIMIT_100TH_MAX = 0.2
BETA_TO_SPY_98TH_MAX = 0.3
BETA_TO_SPY_100TH_MAX = 0.4
SECTOR_EXPOSURE_98TH_MAX = 0.2
SECTOR_EXPOSURE_100TH_MAX = 0.25
STYLE_EXPOSURE_98TH_MAX = 0.4
STYLE_EXPOSURE_100TH_MAX = 0.5
TRADABLE_UNIVERSE_0TH_MIN = 0.9
TRADABLE_UNIVERSE_2ND_MIN = 0.95


def check_constraints(positions, transactions, algorithm_returns, risk_exposures):
    
    sector_constraints = True
    style_constraints = True
    constraints_met = 0
    num_constraints = 9
    
    # Position Concentration Constraint
    print 'Checking positions concentration limit...'
    try:
        percent_allocations = pf.pos.get_percent_alloc(positions[5:])
        daily_absolute_percent_allocations = percent_allocations.abs().drop('cash', axis=1)
        daily_max_absolute_position = daily_absolute_percent_allocations.max(axis=1)
        
        position_concentration_98 = daily_max_absolute_position.quantile(0.98)
        position_concentration_100 = daily_max_absolute_position.max()
        
    except IndexError:
        position_concentration_98 = -1
        position_concentration_100 = -1
        
    if (position_concentration_98 > POSITION_CONCENTRATION_98TH_MAX):
        print 'FAIL: 98th percentile position concentration of %.2f > %.1f.' % (
        position_concentration_98*100,
        POSITION_CONCENTRATION_98TH_MAX*100
    )
    elif (position_concentration_100 > POSITION_CONCENTRATION_100TH_MAX):
        print 'FAIL: 100th percentile position concentration of %.2f > %.1f.' % (
        position_concentration_100*100,
        POSITION_CONCENTRATION_100TH_MAX*100
    )
    else:
        print 'PASS: Max position concentration of %.2f%% <= %.1f%%.' % (
            position_concentration_98*100,
            POSITION_CONCENTRATION_98TH_MAX*100
        )
        constraints_met += 1

        
    # Leverage Constraint
    print ''
    print 'Checking leverage limits...'
    leverage = pf.timeseries.gross_lev(positions[5:])
    leverage_0 = leverage.min()
    leverage_2 = leverage.quantile(0.02)
    leverage_98 = leverage.quantile(0.98)
    leverage_100 = leverage.max()
    leverage_passed = True
    
    if (leverage_0 < LEVERAGE_0TH_MIN):
        print 'FAIL: Minimum leverage of %.2fx is below %.1fx' % (
            leverage_0,
            LEVERAGE_0TH_MIN
        )
        leverage_passed = False
    if (leverage_2 < LEVERAGE_2ND_MIN):
        print 'FAIL: 2nd percentile leverage of %.2fx is below %.1fx' % (
            leverage_2,
            LEVERAGE_2ND_MIN
        )
        leverage_passed = False
    if (leverage_98 > LEVERAGE_98TH_MAX):
        print 'FAIL: 98th percentile leverage of %.2fx is above %.1fx' % (
            leverage_98,
            LEVERAGE_98TH_MAX
        )
        leverage_passed = False
    if (leverage_100 > LEVERAGE_100TH_MAX):
        print 'FAIL: Maximum leverage of %.2fx is above %.1fx' % (
            leverage_0,
            LEVERAGE_0TH_MAX
        )
        leverage_passed = False
    if leverage_passed:
        print 'PASS: Leverage range of %.2fx-%.2fx is between %.1fx-%.1fx.' % (
            leverage_2,
            leverage_98,
            LEVERAGE_2ND_MIN,
            LEVERAGE_98TH_MAX
        )
        constraints_met += 1
      
    # Turnover Constraint
    print ''
    print 'Checking turnover limits...'
    turnover = pf.txn.get_turnover(positions, transactions, denominator='portfolio_value')
    # Compute mean rolling 63 trading day turnover.
    rolling_mean_turnover = roll(
        turnover, 
        function=pd.Series.mean,
        window=63)[62:]
    rolling_mean_turnover_0 = rolling_mean_turnover.min()
    rolling_mean_turnover_2 = rolling_mean_turnover.quantile(0.02)
    rolling_mean_turnover_98 = rolling_mean_turnover.quantile(0.98)
    rolling_mean_turnover_100 = rolling_mean_turnover.max()  
    rolling_mean_turnover_passed = True
    
    if (rolling_mean_turnover_0 < DAILY_TURNOVER_0TH_MIN):
        print 'FAIL: Minimum turnover of %.2f%% is below %.1f%%.' % (
            rolling_mean_turnover_0*100,
            DAILY_TURNOVER_0TH_MIN*100
        )
        rolling_mean_turnover_passed = False
    if (rolling_mean_turnover_2 < DAILY_TURNOVER_2ND_MIN):
        print 'FAIL: 2nd percentile turnover of %.2f%% is below %.1fx' % (
            rolling_mean_turnover_2*100,
            DAILY_TURNOVER_2ND_MIN*100
        )
        rolling_mean_turnover_passed = False
    if (rolling_mean_turnover_98 > DAILY_TURNOVER_98TH_MAX):
        print 'FAIL: 98th percentile turnover of %.2f%% is above %.1fx' % (
            rolling_mean_turnover_98*100,
            DAILY_TURNOVER_98TH_MAX*100
        )
        rolling_mean_turnover_passed = False
    if (rolling_mean_turnover_100 > DAILY_TURNOVER_100TH_MAX):
        print 'FAIL: Maximum turnover of %.2f%% is above %.1fx' % (
            rolling_mean_turnover_100*100,
            DAILY_TURNOVER_100TH_MAX*100
        )
        rolling_mean_turnover_passed = False
    if rolling_mean_turnover_passed:
        print 'PASS: Mean turnover range of %.2f%%-%.2f%% is between %.1f%%-%.1f%%.' % (
            rolling_mean_turnover_2*100,
            rolling_mean_turnover_98*100,
            DAILY_TURNOVER_2ND_MIN*100,
            DAILY_TURNOVER_98TH_MAX*100
        )
        constraints_met += 1

        
    # Net Exposure Constraint
    print ''
    print 'Checking net exposure limit...'
    net_exposure = pf.pos.get_long_short_pos(positions[5:])['net exposure'].abs()
    net_exposure_98 = net_exposure.quantile(0.98)
    net_exposure_100 = net_exposure.max()
    
    if (net_exposure_98 > NET_EXPOSURE_LIMIT_98TH_MAX):
        print 'FAIL: 98th percentile net exposure (absolute value) of %.2f > %.1f.' % (
        net_exposure_98*100,
        NET_EXPOSURE_LIMIT_98TH_MAX*100
    )
    elif (net_exposure_100 > NET_EXPOSURE_LIMIT_100TH_MAX):
        print 'FAIL: 100th percentile net exposure (absolute value) of %.2f > %.1f.' % (
        net_exposure_100*100,
        NET_EXPOSURE_LIMIT_100TH_MAX*100
    )
    else:
        print 'PASS: Net exposure (absolute value) of %.2f%% <= %.1f%%.' % (
            net_exposure_98*100,
            NET_EXPOSURE_LIMIT_98TH_MAX*100
        )
        constraints_met += 1
    
        
    # Beta Constraint
    print ''
    print 'Checking beta-to-SPY limit...'
    spy_returns = returns(
        symbols('SPY'),
        algorithm_returns.index[0],
        algorithm_returns.index[-1],
    )
    beta = roll(
        algorithm_returns,
        spy_returns,
        function=ep.beta,
        window=126
    ).reindex_like(algorithm_returns).fillna(0).abs()
    beta_98 = beta.quantile(0.98)
    beta_100 = beta.max()
    if (beta_98 > BETA_TO_SPY_98TH_MAX):
            print 'FAIL: 98th percentile absolute beta of %.3f > %.1f.' % (
            beta_98,
            BETA_TO_SPY_98TH_MAX
        )
    elif (beta_100 > BETA_TO_SPY_100TH_MAX):
        print 'FAIL: 100th percentile absolute beta of %.3f > %.1f.' % (
            beta_100,
            BETA_TO_SPY_100TH_MAX
        )
    else:
        print 'PASS: Max absolute beta of %.3f <= %.1f.' % (
            beta_98,
            BETA_TO_SPY_98TH_MAX
        )
        constraints_met += 1
        
    # Risk Exposures
    rolling_mean_risk_exposures = risk_exposures.rolling(63, axis=0).mean()[62:].fillna(0)
    
    # Sector Exposures
    print ''
    print 'Checking sector exposure limits...'
    for sector in SECTORS:
        absolute_mean_sector_exposure = rolling_mean_risk_exposures[sector].abs()
        abs_mean_sector_exposure_98 = absolute_mean_sector_exposure.quantile(0.98)
        abs_mean_sector_exposure_100 = absolute_mean_sector_exposure.max()
        if (abs_mean_sector_exposure_98 > SECTOR_EXPOSURE_98TH_MAX):
            print 'FAIL: 98th percentile %s exposure of %.3f (absolute value) is greater than %.2f.' % (
                sector,
                abs_mean_sector_exposure_98,
                SECTOR_EXPOSURE_98TH_MAX
            )
            sector_constraints = False
        elif (abs_mean_sector_exposure_100 > SECTOR_EXPOSURE_100TH_MAX):
            max_sector_exposure_day = absolute_mean_sector_exposure.idxmax()
            print 'FAIL: Max %s exposure of %.3f (absolute value) on %s is greater than %.2f.' % (
                sector,
                abs_mean_sector_exposure_100,
                max_sector_exposure_day,
                SECTOR_EXPOSURE_100TH_MAX
            )
            sector_constraints = False
    if sector_constraints:
        print 'PASS: All sector exposures were between +/-%.2f.' % SECTOR_EXPOSURE_98TH_MAX
        constraints_met += 1
        
    # Style Exposures
    print ''
    print 'Checking style exposure limits...'
    for style in STYLES:
        absolute_mean_style_exposure = rolling_mean_risk_exposures[style].abs()
        abs_mean_style_exposure_98 = absolute_mean_style_exposure.quantile(0.98)
        abs_mean_style_exposure_100 = absolute_mean_style_exposure.max()
        if (abs_mean_style_exposure_98 > STYLE_EXPOSURE_98TH_MAX):
            print 'FAIL: 98th percentile %s exposure of %.3f (absolute value) is greater than %.2f.' % (
                style, 
                abs_mean_style_exposure_98, 
                STYLE_EXPOSURE_98TH_MAX
            )
            style_constraints = False
        elif (abs_mean_style_exposure_100 > STYLE_EXPOSURE_100TH_MAX):
            max_style_exposure_day = absolute_mean_style_exposure.idxmax()
            print 'FAIL: Max %s exposure of %.3f (absolute value) on %s is greater than %.2f.' % (
                style, 
                abs_mean_style_exposure_100, 
                max_style_exposure_day.date(),
                STYLE_EXPOSURE_100TH_MAX
            )
            style_constraints = False
    if style_constraints:
        print 'PASS: All style exposures were between +/-%.2f.' % STYLE_EXPOSURE_98TH_MAX
        constraints_met += 1
    
    
    # Tradable Universe
    print ''
    print 'Checking investment in tradable universe...'
    positions_wo_cash = positions.drop('cash', axis=1)
    positions_wo_cash = positions_wo_cash.abs()
    total_investment = positions_wo_cash.fillna(0).sum(axis=1)
    daily_qtu_investment = universe.multiply(positions_wo_cash).fillna(0).sum(axis=1)
    percent_in_qtu = daily_qtu_investment / total_investment
    percent_in_qtu = percent_in_qtu[5:].fillna(0)
    
    percent_in_qtu_0 = percent_in_qtu.min()
    percent_in_qtu_2 = percent_in_qtu.quantile(0.02)
        
    if percent_in_qtu_0 < TRADABLE_UNIVERSE_0TH_MIN:
        min_percent_in_qtu_date = percent_in_qtu.argmin()
        print 'FAIL: Minimum investment in QTradableStocksUS of %.2f%% on %s is < %.1f%%.' % (
            percent_in_qtu_0*100, 
            min_percent_in_qtu_date.date(),
            TRADABLE_UNIVERSE_0TH_MIN*100
        )
    elif percent_in_qtu_2 < TRADABLE_UNIVERSE_2ND_MIN:
        print 'FAIL: Investment in QTradableStocksUS (2nd percentile) of %.2f%% is < %.1f%%.' % (
            percent_in_qtu_2*100, 
            TRADABLE_UNIVERSE_2ND_MIN*100
        )
    else:
        print 'PASS: Investment in QTradableStocksUS is >= %.1f%%.' % (
            TRADABLE_UNIVERSE_2ND_MIN*100
        )
        constraints_met += 1
        
        
    # Total algorithm_returns Constraint
    print ''
    print 'Checking that algorithm has positive algorithm_returns...'
    cumulative_algorithm_returns = ep.cum_returns_final(algorithm_returns)
    if (cumulative_algorithm_returns > 0):
        print 'PASS: Cumulative algorithm_returns of %.2f is positive.' % (
            cumulative_algorithm_returns
        )
        constraints_met += 1
    else:
        print 'FAIL: Cumulative algorithm_returns of %.2f is negative.' % (
            cumulative_algorithm_returns
        )
    
    print ''
    print 'Results:'
    if constraints_met == num_constraints:
        print 'All constraints met!'
    else:
        print '%d/%d tests passed.' % (constraints_met, num_constraints)
def evaluate_backtest(positions, transactions, algorithm_returns, risk_exposures):
    if len(positions.index) > 504:
        check_constraints(positions, transactions, algorithm_returns, risk_exposures)
        score = compute_score(algorithm_returns[start:end])
    else:
        print 'ERROR: Backtest must be longer than 2 years to be evaluated.'
        
positions = bt.pyfolio_positions
transactions = bt.pyfolio_transactions
algorithm_returns = bt.daily_performance.returns
factor_exposures = bt.factor_exposures

start = positions.index[0]
end = positions.index[-1]
universe = get_tradable_universe(start, end)
universe.columns = universe.columns.map(lambda x: '%s-%s' % (x.symbol, x.sid))

evaluate_backtest(positions, transactions, algorithm_returns, factor_exposures)
100% Time: 0:00:03|###########################################################|
Start date2016-02-10
End date2018-02-09
Total months24
Backtest
Annual return 7.5%
Cumulative returns 15.6%
Annual volatility 6.7%
Sharpe ratio 1.11
Calmar ratio 1.42
Stability 0.23
Max drawdown -5.3%
Omega ratio 1.20
Sortino ratio 1.70
Skew 0.08
Kurtosis 1.57
Tail ratio 1.14
Daily value at risk -0.8%
Gross leverage 0.99
Daily turnover 23.7%
Alpha 0.06
Beta 0.09
Worst drawdown periods Net drawdown in % Peak date Valley date Recovery date Duration
0 5.25 2017-03-10 2017-12-12 NaT NaN
1 3.88 2016-06-08 2016-07-06 2016-10-19 96
2 2.77 2016-11-21 2016-12-22 2017-01-10 37
3 2.29 2017-01-10 2017-02-01 2017-02-17 29
4 2.06 2016-03-14 2016-03-21 2016-04-12 22
Stress Events mean min max
New Normal 0.03% -2.05% 1.66%
Top 10 long positions of all time max
EPE-46191 2.10%
UNT-7806 1.91%
SM-4664 1.70%
LDRH-45619 1.66%
NVAX-14112 1.58%
SUNE-13306 1.52%
ALNY-26335 1.51%
CBI-1287 1.49%
AMD-351 1.45%
SNCR-32234 1.45%
Top 10 short positions of all time max
TLRD-7203 -1.45%
SPPI-24517 -1.44%
SAAS-31137 -1.44%
CSC-1898 -1.42%
SAGE-47332 -1.40%
MNKD-26524 -1.37%
EXEL-21383 -1.35%
FOSL-8816 -1.32%
CLF-1595 -1.32%
INSY-44665 -1.31%
Top 10 positions of all time max
EPE-46191 2.10%
UNT-7806 1.91%
SM-4664 1.70%
LDRH-45619 1.66%
NVAX-14112 1.58%
SUNE-13306 1.52%
ALNY-26335 1.51%
CBI-1287 1.49%
AMD-351 1.45%
SNCR-32234 1.45%
All positions ever held max
EPE-46191 2.10%
UNT-7806 1.91%
SM-4664 1.70%
LDRH-45619 1.66%
NVAX-14112 1.58%
SUNE-13306 1.52%
ALNY-26335 1.51%
CBI-1287 1.49%
AMD-351 1.45%
SNCR-32234 1.45%
TLRD-7203 1.45%
SPPI-24517 1.44%
SAAS-31137 1.44%
INFN-33979 1.43%
RH-43599 1.42%
CSC-1898 1.42%
CIE-39073 1.42%
FRAN-41737 1.41%
CHK-8461 1.41%
AKRX-270 1.40%
SAGE-47332 1.40%
NAK-26776 1.38%
MDCA-12800 1.38%
MNKD-26524 1.37%
EPZM-44830 1.36%
EXEL-21383 1.35%
AAOI-45503 1.35%
PBYI-42689 1.35%
SRPT-16999 1.35%
CXRX-46079 1.34%
AGEN-21104 1.34%
TDOC-49222 1.33%
FOSL-8816 1.32%
JCP-4118 1.32%
RRC-19249 1.32%
CLF-1595 1.32%
ARIA-11880 1.32%
INSY-44665 1.31%
CWEI-8941 1.31%
ECPG-25320 1.31%
BLCM-48304 1.30%
LJPC-11512 1.30%
HTZ-50070 1.30%
ESPR-44989 1.29%
CRSP-50400 1.29%
PTCT-44955 1.28%
AKS-10897 1.28%
VECO-12267 1.28%
EROS-45867 1.28%
CRAY-21374 1.28%
MDCO-21906 1.28%
NLNK-42147 1.28%
ENDP-21750 1.28%
AGIO-45143 1.27%
KND-23221 1.27%
ZIOP-31341 1.26%
PACB-40319 1.26%
CFMS-49211 1.26%
GKOS-49178 1.26%
CRC-48073 1.25%
AERI-45733 1.25%
GSAT-32819 1.25%
SEDG-48823 1.25%
ANAC-40442 1.25%
EFII-2460 1.25%
XON-45239 1.24%
PBF-43713 1.24%
STKL-7158 1.24%
CSTM-44780 1.24%
SCMP-34477 1.24%
GBT-49335 1.24%
FRED-2989 1.23%
PVG-42366 1.23%
WFT-19336 1.23%
REN-34800 1.23%
VRX-10908 1.23%
JOY-22996 1.23%
ICPT-43505 1.23%
GRPN-42118 1.23%
BCRX-10905 1.23%
CO-33188 1.23%
ENLC-25850 1.22%
BPMC-49000 1.22%
INVN-42165 1.22%
FCX-13197 1.22%
IMGN-3885 1.22%
SDRL-39495 1.22%
VRAY-48015 1.22%
FWON_K-47272 1.22%
NVCR-49460 1.22%
GRUB-46693 1.22%
OSUR-22151 1.22%
EGRX-46354 1.22%
SA-26203 1.22%
CONN-25646 1.22%
CRZO-17358 1.21%
ESV-2621 1.21%
AGI-44156 1.21%
VSLR-47820 1.21%
FTR-2069 1.21%
HK-31032 1.21%
NKTR-24572 1.21%
RTRX-41663 1.21%
ITCI-46053 1.20%
MACK-42735 1.20%
QUOT-46497 1.20%
RMTI-18196 1.20%
AVXS-49751 1.20%
AVP-660 1.20%
RUBI-46671 1.20%
HDSN-12185 1.20%
ASNA-2105 1.20%
GPRE-28159 1.20%
THC-5343 1.20%
GNW-26323 1.20%
HEES-28023 1.20%
CPE-12011 1.20%
LC-48220 1.20%
BOFI-27108 1.20%
PI-50138 1.20%
TERP-47334 1.20%
LNTH-49189 1.19%
TSRO-43124 1.19%
INO-3150 1.19%
ATGE-2371 1.19%
WRLD-8268 1.19%
DDD-12959 1.19%
NTRI-21697 1.19%
CDE-1374 1.19%
NGD-27323 1.19%
JUNO-48317 1.19%
TGI-15905 1.19%
EXAS-22364 1.19%
HRTX-22651 1.19%
RXDX-44685 1.19%
ADMS-46722 1.19%
ALJ-27500 1.19%
WTW-23269 1.19%
PSTG-49464 1.19%
ARRY-22192 1.19%
NE-5249 1.18%
SPXC-7086 1.18%
XENT-47373 1.18%
IMMU-3891 1.18%
LPI-42263 1.18%
MDR-4752 1.18%
AMAG-659 1.18%
STRP-45130 1.18%
CLVS-42166 1.18%
GCO-3131 1.18%
CARA-46283 1.18%
SGRY-49456 1.18%
BLUE-44935 1.18%
FLXN-46344 1.18%
RDC-6392 1.18%
X-8329 1.18%
CLDX-19187 1.18%
CLR-33856 1.18%
BRS-5645 1.18%
TECK-31886 1.18%
P-41579 1.18%
WGO-8168 1.18%
TGTX-13984 1.17%
HLX-17180 1.17%
SLCA-42436 1.17%
AAC-47842 1.17%
HSC-3686 1.17%
ZBRA-8388 1.17%
EVH-49100 1.17%
MMYT-40028 1.17%
OMER-38827 1.17%
CPA-27908 1.17%
OCN-15697 1.17%
BGC-3129 1.17%
SSW-27550 1.17%
BTE-28232 1.17%
SCTY-43721 1.17%
CRS-1874 1.17%
SSTK-43494 1.17%
SHLM-6851 1.17%
PIR-6000 1.17%
PLUG-20776 1.17%
ONDK-48290 1.17%
GLOB-47330 1.17%
GLBL-49312 1.17%
BLDP-13798 1.17%
ASPS-38633 1.17%
XLRN-45431 1.17%
CRUS-1882 1.17%
GPRO-47208 1.17%
BPT-1068 1.17%
AXON-49107 1.17%
IMPV-42131 1.17%
CC-49210 1.17%
BCC-44089 1.16%
FPRX-45430 1.16%
RUN-49321 1.16%
SHLD-26169 1.16%
EBAY-24819 1.16%
AFSI-32871 1.16%
HDP-48257 1.16%
RICE-46240 1.16%
KERX-21789 1.16%
CPHD-21603 1.16%
TTI-7633 1.16%
GLYC-46135 1.16%
DEPO-18010 1.16%
KEM-4265 1.16%
SGMS-22637 1.16%
GLOG-42746 1.16%
TRUE-46929 1.16%
CENX-14484 1.16%
ACIA-33321 1.16%
HIIQ-44113 1.16%
NG-25781 1.16%
OLED-14774 1.16%
ACAD-26322 1.16%
HOMB-32302 1.16%
DPLO-47883 1.16%
TMST-47162 1.15%
AG-40607 1.15%
ARNC-2 1.15%
EVHC-50499 1.15%
CRBP-47955 1.15%
ACHN-32790 1.15%
CORT-26191 1.15%
NAV-5199 1.15%
LPLA-40445 1.15%
ATW-624 1.15%
NMBL-46002 1.15%
SSYS-12107 1.15%
SSNI-44270 1.15%
CUTR-26157 1.15%
ACOR-28077 1.15%
EDIT-49736 1.15%
SSRM-15591 1.15%
LITE-49288 1.15%
AUPH-47621 1.15%
DVN-2368 1.15%
CHRS-48026 1.15%
WTTR-50789 1.15%
XNCR-45942 1.15%
KGC-9189 1.15%
CYH-21608 1.15%
EIGI-45735 1.14%
PCRX-40815 1.14%
NPTN-40807 1.14%
FIVN-46695 1.14%
TREE-36742 1.14%
NVDQ-42595 1.14%
ATRA-47901 1.14%
TNK-35260 1.14%
WETF-31288 1.14%
ERII-36518 1.14%
SGYP-32331 1.14%
GIII-3210 1.14%
MX-41048 1.14%
LSCC-4549 1.14%
MGNX-45643 1.14%
OFG-16394 1.14%
IPXL-37849 1.14%
TXMD-28966 1.14%
BDC-26479 1.14%
OBE-32293 1.14%
DNR-15789 1.14%
ANIP-25565 1.14%
HZNP-41766 1.14%
UIS-7761 1.14%
DDS-2126 1.14%
PRTY-48933 1.14%
QUAD-39860 1.14%
KITE-47169 1.14%
MTRX-5105 1.14%
RCII-24827 1.13%
TROX-40530 1.13%
TK-13289 1.13%
SALE-45114 1.13%
SVU-7233 1.13%
HRI-32887 1.13%
PDS-5855 1.13%
STNG-39422 1.13%
PGNX-17908 1.13%
DO-13635 1.13%
AMBA-43495 1.13%
DYN-43462 1.13%
EXPR-39626 1.13%
OCLR-21366 1.13%
GOGO-44965 1.13%
FGEN-48088 1.13%
RDUS-46871 1.13%
ANAB-50616 1.13%
NUS-16059 1.13%
PLSE-49957 1.13%
LCI-23602 1.13%
TRIP-42230 1.13%
AXDX-15934 1.13%
MRO-5035 1.13%
SHAK-48543 1.13%
QDEL-6297 1.13%
WLL-25707 1.13%
RATE-41601 1.13%
FET-42784 1.13%
YELP-42596 1.13%
PTLA-44770 1.13%
CSTE-42704 1.13%
SKX-20284 1.13%
AXL-19672 1.13%
WPM-27437 1.13%
LILA_K-49206 1.13%
MEOH-4795 1.13%
LOCO-47382 1.13%
DY-2385 1.12%
GLNG-24489 1.12%
MGPI-24094 1.12%
SPWR-27817 1.12%
SCHN-10268 1.12%
VSTO-48531 1.12%
HL-3585 1.12%
ITGR-22015 1.12%
TIVO-16661 1.12%
CVI-22766 1.12%
SGMO-21447 1.12%
DRYS-26994 1.12%
ALKS-301 1.12%
UVE-31185 1.12%
ALDR-46869 1.12%
CAR-17991 1.12%
OMF-45670 1.12%
OPK-23120 1.12%
TVTY-371 1.12%
CSII-32325 1.12%
TWOU-46648 1.12%
SUPN-42877 1.12%
KS-30759 1.12%
BBG-26865 1.12%
DXCM-27173 1.12%
BLDR-27369 1.12%
MNK-44917 1.12%
ANW-33014 1.12%
SXC-41733 1.12%
MFRM-42184 1.12%
BCOR-24011 1.12%
NR-5413 1.12%
DDC-39 1.12%
VSAR-46582 1.12%
OPHT-45498 1.12%
BW-49208 1.12%
KNX-40606 1.12%
GNC-41182 1.12%
NFLX-23709 1.12%
WIN-27019 1.12%
TRVN-46272 1.12%
MTW-4656 1.12%
LNKD-41451 1.12%
DERM-47845 1.12%
ACLS-21717 1.12%
CASH-9700 1.12%
PHH-26956 1.11%
USNA-15408 1.11%
RVNC-46315 1.11%
INSM-21778 1.11%
HALO-26766 1.11%
TWTR-45815 1.11%
MKTO-44738 1.11%
FARO-17508 1.11%
PRO-34118 1.11%
ONCE-48547 1.11%
IPHI-40399 1.11%
DB-23113 1.11%
MYGN-13698 1.11%
HBM-40301 1.11%
SHOP-49060 1.11%
GSM-38638 1.11%
MUX-7845 1.11%
CATM-35253 1.11%
SCOR-34111 1.11%
MTSI-42646 1.11%
CYTK-26232 1.11%
DLTH-49615 1.11%
CVE-38896 1.11%
S-2938 1.11%
ADPT-47191 1.11%
IONS-4031 1.11%
MBLY-47430 1.11%
GPOR-28116 1.11%
CZR-42461 1.11%
WMB-8214 1.11%
AOBC-24519 1.11%
CPG-46206 1.11%
RARE-46285 1.10%
CVTI-12023 1.10%
CLD-38971 1.10%
KRO-25764 1.10%
MU-5121 1.10%
DVAX-25972 1.10%
ADRO-48925 1.10%
ITG-25095 1.10%
IAG-24491 1.10%
QTNA-50417 1.10%
KNDI-32972 1.10%
STRL-9430 1.10%
KOS-41416 1.10%
FN-39824 1.10%
HZO-18917 1.10%
MCRB-49195 1.10%
CSIQ-32856 1.10%
AROC-34575 1.10%
QEP-39778 1.10%
FIT-49139 1.10%
ERF-22215 1.10%
FNSR-20866 1.10%
RYAM-47128 1.10%
AMC-46027 1.10%
AY-47123 1.10%
FRO-22983 1.10%
SFS-47776 1.10%
AKAO-46528 1.10%
MDXG-34049 1.10%
LL-35036 1.10%
MRTX-45080 1.10%
DGI-38374 1.10%
FOLD-33949 1.10%
AMKR-18655 1.10%
ZUMZ-27229 1.10%
SRCI-35051 1.10%
ESND-7866 1.10%
FTK-27496 1.10%
LOXO-47432 1.10%
MDVN-28160 1.10%
WYNN-24124 1.10%
PAH-45531 1.10%
SBLK-28112 1.10%
PGTI-32324 1.10%
RLYP-45851 1.10%
UCTT-26146 1.10%
MXL-39393 1.10%
LGIH-45818 1.10%
FBP-2806 1.10%
ICON-6856 1.10%
TBPH-46932 1.10%
INOV-48629 1.10%
BEAT-35929 1.09%
CACC-1216 1.09%
CHGG-45847 1.09%
OUTR-24791 1.09%
FEYE-45451 1.09%
FRTA-50398 1.09%
ANGI-42175 1.09%
XPO-26287 1.09%
NVRO-48025 1.09%
NBIX-14972 1.09%
GIMO-44892 1.09%
KTOS-20947 1.09%
DWRE-42638 1.09%
PRAA-24440 1.09%
SKYW-6925 1.09%
ARWR-10417 1.09%
STX-24518 1.09%
ELGX-23769 1.09%
HABT-48126 1.09%
PTEN-10254 1.09%
DATA-44747 1.09%
UAA-27822 1.09%
LTRP_A-47578 1.09%
NSM-42611 1.09%
BNFT-45428 1.09%
ECHO-38798 1.09%
LOCK-43467 1.09%
OSIS-17718 1.09%
FINL-2845 1.09%
MTOR-21723 1.09%
DK-32042 1.09%
MEDP-50194 1.09%
ISLE-19391 1.09%
ELLI-41243 1.09%
CCJ-14479 1.09%
CZZ-34560 1.09%
NRG-26143 1.09%
CRR-14700 1.09%
SPN-14141 1.09%
QLYS-43454 1.09%
TRTN-50119 1.09%
TAHO-39938 1.09%
CLNE-33924 1.09%
EGO-24547 1.09%
GEO-11710 1.09%
FSLR-32902 1.09%
ZGNX-40461 1.09%
OME-18601 1.09%
SWIR-21561 1.09%
RAD-6330 1.09%
TTMI-22072 1.09%
BKE-915 1.09%
BETR-49318 1.09%
MRD-47126 1.09%
SPKE-47372 1.09%
ATRO-617 1.09%
KN-46369 1.09%
USCR-40241 1.09%
HUN-27030 1.08%
PDLI-5847 1.08%
RGNX-49409 1.08%
NBR-5214 1.08%
TRN-7583 1.08%
IRBT-27780 1.08%
IVC-4084 1.08%
SN-42264 1.08%
LGF-19491 1.08%
CARB-41820 1.08%
ILMN-21774 1.08%
W-47833 1.08%
ATHN-34692 1.08%
AXLL-3189 1.08%
MRC-42786 1.08%
AIMT-49323 1.08%
NTLA-49934 1.08%
AR-45618 1.08%
OLN-5643 1.08%
TWI-9066 1.08%
GLUU-33566 1.08%
TGH-34810 1.08%
AUY-25714 1.08%
BKS-9693 1.08%
STMP-26286 1.08%
TELL-4982 1.08%
RIG-9038 1.08%
INCY-10187 1.08%
RRGB-23904 1.08%
CYBR-47779 1.08%
PRTA-43730 1.08%
WPX-42251 1.08%
TCMD-50169 1.08%
WLH-43733 1.08%
SALT-45996 1.08%
OAS-39797 1.08%
MUR-5126 1.08%
ABX-64 1.08%
ENV-39958 1.08%
ARNA-21724 1.08%
WDC-8132 1.08%
BTG-44884 1.08%
LMAT-32750 1.08%
FMI-45499 1.08%
TLN-49003 1.08%
XXII-32588 1.08%
ETSY-48934 1.08%
GTN-23945 1.08%
ATI-24840 1.08%
HMHC-45861 1.08%
SYNA-23398 1.08%
SPNC-7064 1.08%
KRNT-48872 1.08%
SGEN-22563 1.08%
ARCB-41 1.08%
RYI-47464 1.08%
KRA-39079 1.07%
NCMI-33317 1.07%
DXPE-17196 1.07%
FSM-41915 1.07%
VRTU-34483 1.07%
OPB-46768 1.07%
CF-27558 1.07%
ADXS-40992 1.07%
FRGI-42856 1.07%
CUDA-45797 1.07%
XOG-50368 1.07%
CCRN-24893 1.07%
ASIX-50260 1.07%
SMCI-33609 1.07%
TTD-50288 1.07%
LXRX-21413 1.07%
SWN-7244 1.07%
CMC-1636 1.07%
BZH-10728 1.07%
SNAP-50683 1.07%
ABCO-23176 1.07%
NNBR-11003 1.07%
ECA-23021 1.07%
FLOW-49434 1.07%
COLL-49016 1.07%
SAND-43329 1.07%
SQ-49610 1.07%
DKS-24070 1.07%
MATX-289 1.07%
HRTG-46988 1.07%
CTRL-45212 1.07%
TPX-25802 1.07%
PAAS-13083 1.07%
RFP-40576 1.07%
SHEN-22166 1.07%
ALRM-49192 1.07%
XPER-25705 1.07%
VRTX-8045 1.07%
MNTA-26381 1.07%
NVGS-45915 1.07%
CATO-1219 1.07%
GBX-11645 1.07%
ANF-15622 1.07%
TMH-39076 1.07%
SEMG-39358 1.07%
ABMD-53 1.07%
BKD-27830 1.07%
PAYC-46744 1.07%
GTE-31619 1.07%
HZN-49218 1.07%
ARCO-41242 1.07%
ICHR-50509 1.07%
GDOT-39932 1.07%
YRCW-8370 1.07%
MBI-4684 1.06%
WWE-20818 1.06%
CROX-28078 1.06%
NOMD-47572 1.06%
RES-6426 1.06%
TPC-5824 1.06%
MYOK-49535 1.06%
IIVI-3854 1.06%
CCRC-49668 1.06%
KTWO-46870 1.06%
ACRS-49465 1.06%
SODA-40353 1.06%
RRD-2248 1.06%
KORS-42270 1.06%
EGN-2470 1.06%
GPT-26520 1.06%
GG-22226 1.06%
AEM-154 1.06%
EVRI-27639 1.06%
HSKA-17227 1.06%
DRII-45112 1.06%
OTIC-47495 1.06%
HTWR-38150 1.06%
TWLO-50077 1.06%
TNET-46633 1.06%
MTDR-42446 1.06%
FL-8383 1.06%
AMN-23178 1.06%
NTNX-50338 1.06%
CBAY-46189 1.06%
ACHC-42091 1.06%
ENTA-44332 1.06%
RBBN-21557 1.06%
KLDX-49463 1.06%
INVA-26676 1.06%
CIR-20696 1.06%
COHR-1751 1.06%
MB-49156 1.06%
APA-448 1.06%
SIG-9774 1.06%
VRTV-47143 1.06%
EZPW-2671 1.06%
BWLD-25642 1.06%
DECK-9909 1.06%
IOVA-36209 1.06%
RGLD-6455 1.06%
EXTR-19973 1.06%
MULE-50719 1.06%
AAXN-22846 1.06%
APTI-50318 1.05%
TTS-40545 1.05%
PETS-25867 1.05%
HLF-26892 1.05%
GTLS-32433 1.05%
SNBR-19559 1.05%
AMPH-47193 1.05%
INST-49594 1.05%
BANC-23943 1.05%
RGEN-6449 1.05%
PCTY-46569 1.05%
ARII-27998 1.05%
TEAM-49655 1.05%
CBM-1297 1.05%
DBD-2100 1.05%
FCAU-47888 1.05%
DHT-27705 1.05%
RAX-36714 1.05%
NVDA-19725 1.05%
VNDA-28326 1.05%
GES-24811 1.05%
PODD-33858 1.05%
CNX-24758 1.05%
FRAC-50612 1.05%
IIIN-3849 1.05%
MHLD-36164 1.05%
QRVO-48384 1.05%
TRGP-40547 1.05%
LKSD-50312 1.05%
URBN-10303 1.05%
MTCH-49608 1.05%
ZOES-46742 1.05%
SC-46215 1.05%
KMI-40852 1.05%
SGY-9458 1.05%
FCPT-49543 1.05%
LGND-12200 1.05%
COUP-50350 1.05%
HFC-3620 1.05%
MIME-49606 1.05%
SINA-21448 1.05%
AGX-28450 1.05%
APC-455 1.05%
SYNT-24790 1.04%
HES-216 1.04%
MTZ-4667 1.04%
CPSI-23667 1.04%
LB-4564 1.04%
SDLP-43539 1.04%
EVHC-45269 1.04%
RIC-16681 1.04%
SYNH-48027 1.04%
DNOW-46949 1.04%
EGL-43151 1.04%
BB-19831 1.04%
NAT-17553 1.04%
RSPP-46182 1.04%
VDSI-21457 1.04%
VRA-40287 1.04%
OKTA-50758 1.04%
CORE-27864 1.04%
REI-42894 1.04%
ATRS-22418 1.04%
FMSA-47848 1.04%
WIX-45800 1.04%
EVBG-50264 1.04%
MOD-4958 1.04%
HUBS-47872 1.04%
PMC-34241 1.04%
BOX-48486 1.04%
PRTK-32095 1.04%
CHUY-43215 1.04%
WIFI-41374 1.04%
URI-18113 1.04%
FORM-25182 1.04%
NRF-26740 1.04%
TISI-7487 1.04%
GOOS-50713 1.04%
KOP-28039 1.04%
AMCC-17799 1.03%
DFIN-50310 1.03%
NK-49298 1.03%
FLWS-20490 1.03%
PEIX-27129 1.03%
HMSY-3607 1.03%
TIME-46965 1.03%
KATE-4479 1.03%
AVAV-33194 1.03%
MEET-30658 1.03%
VG-32143 1.03%
LULU-34395 1.03%
ARAY-33310 1.03%
CMRE-40354 1.03%
EBS-32878 1.03%
TEX-7408 1.03%
HIBB-15815 1.03%
WAIR-41757 1.03%
IPI-36093 1.03%
MOH-25349 1.03%
AKG-35382 1.03%
MSGN-39171 1.03%
CERS-16333 1.03%
AEL-25710 1.03%
MLNT-42455 1.03%
LZB-4621 1.03%
ZAGG-32502 1.03%
HNI-26259 1.03%
ECR-47168 1.03%
WNR-27997 1.03%
ANDE-14329 1.03%
ATRC-27532 1.03%
MEI-4803 1.03%
IOC-26617 1.03%
EXTN-49560 1.03%
OKE-5634 1.03%
HPQ-3735 1.03%
LPSN-21415 1.02%
BAS-27886 1.02%
MELI-34525 1.02%
CHS-8612 1.02%
PANW-43202 1.02%
CAI-33864 1.02%
MMI-45771 1.02%
ANET-47063 1.02%
HOV-3645 1.02%
RPTP-21364 1.02%
AA-50428 1.02%
CPLA-32860 1.02%
PEN-49413 1.02%
ENVA-47979 1.02%
RPD-49275 1.02%
VRTS-37869 1.02%
SCLN-6714 1.02%
BRKS-12512 1.02%
RGLS-43472 1.02%
IQNT-34997 1.02%
NVMI-21427 1.02%
EXK-33236 1.02%
NYLD-49043 1.02%
THS-27406 1.02%
NHTC-18595 1.02%
TCX-3929 1.01%
COHU-1749 1.01%
AHT-25398 1.01%
BHVN-50839 1.01%
ATEN-46598 1.01%
MSG-49458 1.01%
AAWW-28378 1.01%
MITL-39526 1.01%
BBY-754 1.01%
PTHN-50143 1.01%
UNFI-16129 1.01%
MAG-34175 1.01%
WRD-50537 1.01%
ONVO-41829 1.00%
CALA-47830 1.00%
PES-22551 1.00%
CJ-50690 1.00%
NSH-32386 1.00%
VSI-38882 1.00%
AKBA-46583 1.00%
WATT-46650 1.00%
KSS-4313 1.00%
NFX-10231 1.00%
M-2754 1.00%
REV-14420 1.00%
MYCC-45453 1.00%
COWN-32377 0.99%
SCVL-8733 0.99%
NEM-5261 0.99%
MWW-24923 0.99%
XTLY-49199 0.99%
VERI-50868 0.99%
PBI-5773 0.98%
QURE-46310 0.98%
RRTS-39628 0.98%
PE-46989 0.97%
JONE-45141 0.97%
CTMX-49470 0.97%
CXW-22102 0.96%
BOJA-49024 0.95%
AYX-50735 0.94%
DSW-27409 0.91%
ELF-50320 0.90%
NWL-5520 0.15%
TPL-7551 0.05%
ANFI-43493 0.05%
CBL-9890 0.05%
CORR-33284 0.05%
GPS-3321 0.05%
TAL-27704 0.05%
WTI-26986 0.05%
TUES-24770 0.05%
NYRT-46760 0.05%

Performance Relative to Common Risk Factors

Summary Statistics
Annualized Specific Return 0.74%
Annualized Common Return 6.70%
Annualized Total Return 7.47%
Specific Sharpe Ratio 0.15
Exposures Summary Average Risk Factor Exposure Annualized Return Cumulative Return
basic_materials -0.01 0.16% 0.32%
consumer_cyclical -0.00 0.11% 0.21%
financial_services 0.00 -0.05% -0.09%
real_estate -0.00 -0.02% -0.03%
consumer_defensive 0.00 0.05% 0.11%
health_care 0.01 0.10% 0.19%
utilities -0.00 -0.02% -0.04%
communication_services 0.00 -0.06% -0.12%
energy 0.01 0.40% 0.81%
industrials -0.01 -0.06% -0.12%
technology 0.01 0.13% 0.26%
momentum -0.06 0.55% 1.11%
size -0.09 0.38% 0.77%
value 0.14 0.21% 0.42%
short_term_reversal 0.80 3.69% 7.55%
volatility 0.08 0.99% 1.99%
Checking positions concentration limit...
PASS: Max position concentration of 1.51% <= 5.0%.

Checking leverage limits...
PASS: Leverage range of 0.96x-1.04x is between 0.8x-1.1x.

Checking turnover limits...
PASS: Mean turnover range of 21.58%-25.71% is between 5.0%-65.0%.

Checking net exposure limit...
PASS: Net exposure (absolute value) of 1.74% <= 10.0%.

Checking beta-to-SPY limit...
PASS: Max absolute beta of 0.243 <= 0.3.

Checking sector exposure limits...
PASS: All sector exposures were between +/-0.20.

Checking style exposure limits...
FAIL: 98th percentile short_term_reversal exposure of 0.986 (absolute value) is greater than 0.40.

Checking investment in tradable universe...
PASS: Investment in QTradableStocksUS is >= 95.0%.

Checking that algorithm has positive algorithm_returns...
PASS: Cumulative algorithm_returns of 0.16 is positive.

Results:
8/9 tests passed.

Score computed between 2018-02-07 and 2018-02-09.
Cumulative Score: 0.161720