def plot_sector_exposures_gross(gross_exposures, sector_dict=None, ax=None):
    """
    Plots output of compute_sector_exposures as area charts

    Parameters
    ----------
    gross_exposures : arrays
        Arrays of gross sector exposures (output of compute_sector_exposures).

    sector_dict : dict or OrderedDict
        Dictionary of all sectors
        - See full description in compute_sector_exposures
    """

    if ax is None:
        ax = plt.gca()

    if sector_dict is None:
        sector_names = SECTORS.values()
    else:
        sector_names = sector_dict.values()

    color_list = plt.cm.gist_rainbow(np.linspace(0, 1, 11))

    ax.stackplot(gross_exposures[0].index, gross_exposures,
                 labels=sector_names, colors=color_list, alpha=0.8,
                 baseline='zero')
    ax.axhline(0, color='k', linestyle='-')
    ax.set(title='Gross exposure to sectors',
           ylabel='Proportion of gross exposure \n in sectors')

    return ax


def plot_sector_exposures_net(net_exposures, sector_dict=None, ax=None):
    """
    Plots output of compute_sector_exposures as line graphs

    Parameters
    ----------
    net_exposures : arrays
        Arrays of net sector exposures (output of compute_sector_exposures).

    sector_dict : dict or OrderedDict
        Dictionary of all sectors
        - See full description in compute_sector_exposures
    """

    if ax is None:
        ax = plt.gca()

    if sector_dict is None:
        sector_names = SECTORS.values()
    else:
        sector_names = sector_dict.values()

    color_list = plt.cm.gist_rainbow(np.linspace(0, 1, 11))

    for i in range(len(net_exposures)):
        ax.plot(net_exposures[i], color=color_list[i], alpha=0.8,
                label=sector_names[i])
    ax.set(title='Net exposures to sectors',
           ylabel='Proportion of net exposure \n in sectors')

    return ax


def compute_cap_exposures(positions, caps):
    """
    Returns arrays of long, short and gross market cap exposures of an
    algorithm's positions

    Parameters
    ----------
    positions : pd.DataFrame
        Daily equity positions of algorithm, in dollars.
        - See full explanation in compute_style_factor_exposures.

    caps : pd.DataFrame
        Daily Morningstar sector code per asset
        - See full explanation in create_risk_tear_sheet
    """

    long_exposures = []
    short_exposures = []
    gross_exposures = []
    net_exposures = []

    positions_wo_cash = positions.drop('cash', axis='columns')
    tot_gross_exposure = positions_wo_cash.abs().sum(axis='columns')
    tot_long_exposure = positions_wo_cash[positions_wo_cash > 0] \
        .sum(axis='columns')
    tot_short_exposure = positions_wo_cash[positions_wo_cash < 0] \
        .abs().sum(axis='columns')

    for bucket_name, boundaries in CAP_BUCKETS.items():
        in_bucket = positions_wo_cash[(caps >= boundaries[0]) &
                                      (caps <= boundaries[1])]

        gross_bucket = in_bucket.abs().sum(axis='columns') \
            .divide(tot_gross_exposure)
        long_bucket = in_bucket[in_bucket > 0] \
            .sum(axis='columns').divide(tot_long_exposure)
        short_bucket = in_bucket[in_bucket < 0] \
            .sum(axis='columns').divide(tot_short_exposure)
        net_bucket = long_bucket.subtract(short_bucket)

        gross_exposures.append(gross_bucket)
        long_exposures.append(long_bucket)
        short_exposures.append(short_bucket)
        net_exposures.append(net_bucket)

    return long_exposures, short_exposures, gross_exposures, net_exposures

def compute_cap_exposures ( positions , caps ) : long_exposures = [ ] short_exposures = [ ] gross_exposures = [ ] net_exposures = [ ] positions_wo_cash = positions . drop ( 'cash' , axis = 'columns' ) tot_gross_exposure = positions_wo_cash . abs ( ) . sum ( axis = 'columns' ) tot_long_exposure = positions_wo_cash [ positions_wo_cash > 0 ] . sum ( axis = 'columns' ) tot_short_exposure = positions_wo_cash [ positions_wo_cash < 0 ] . abs ( ) . sum ( axis = 'columns' ) for bucket_name , boundaries in CAP_BUCKETS . items ( ) : in_bucket = positions_wo_cash [ ( caps >= boundaries [ 0 ] ) & ( caps <= boundaries [ 1 ] ) ] gross_bucket = in_bucket . abs ( ) . sum ( axis = 'columns' ) . divide ( tot_gross_exposure ) long_bucket = in_bucket [ in_bucket > 0 ] . sum ( axis = 'columns' ) . divide ( tot_long_exposure ) short_bucket = in_bucket [ in_bucket < 0 ] . sum ( axis = 'columns' ) . divide ( tot_short_exposure ) net_bucket = long_bucket . subtract ( short_bucket ) gross_exposures . append ( gross_bucket ) long_exposures . append ( long_bucket ) short_exposures . append ( short_bucket ) net_exposures . append ( net_bucket ) return long_exposures , short_exposures , gross_exposures , net_exposures

Returns arrays of long short and gross market cap exposures of an algorithm s positions


def plot_cap_exposures_net(net_exposures, ax=None):
    """
    Plots outputs of compute_cap_exposures as line graphs

    Parameters
    ----------
    net_exposures : array
        Arrays of gross market cap exposures (output of compute_cap_exposures).
    """

    if ax is None:
        ax = plt.gca()

    color_list = plt.cm.gist_rainbow(np.linspace(0, 1, 5))

    cap_names = CAP_BUCKETS.keys()
    for i in range(len(net_exposures)):
        ax.plot(net_exposures[i], color=color_list[i], alpha=0.8,
                label=cap_names[i])
    ax.axhline(0, color='k', linestyle='-')
    ax.set(title='Net exposure to market caps',
           ylabel='Proportion of net exposure \n in market cap buckets')

    return ax


def compute_volume_exposures(shares_held, volumes, percentile):
    """
    Returns arrays of pth percentile of long, short and gross volume exposures
    of an algorithm's held shares

    Parameters
    ----------
    shares_held : pd.DataFrame
        Daily number of shares held by an algorithm.
        - See full explanation in create_risk_tear_sheet

    volume : pd.DataFrame
        Daily volume per asset
        - See full explanation in create_risk_tear_sheet

    percentile : float
        Percentile to use when computing and plotting volume exposures
        - See full explanation in create_risk_tear_sheet
    """

    shares_held = shares_held.replace(0, np.nan)

    shares_longed = shares_held[shares_held > 0]
    shares_shorted = -1 * shares_held[shares_held < 0]
    shares_grossed = shares_held.abs()

    longed_frac = shares_longed.divide(volumes)
    shorted_frac = shares_shorted.divide(volumes)
    grossed_frac = shares_grossed.divide(volumes)

    # NOTE: To work around a bug in `quantile` with nan-handling in
    #       pandas 0.18, use np.nanpercentile by applying to each row of
    #       the dataframe. This is fixed in pandas 0.19.
    #
    # longed_threshold = 100*longed_frac.quantile(percentile, axis='columns')
    # shorted_threshold = 100*shorted_frac.quantile(percentile, axis='columns')
    # grossed_threshold = 100*grossed_frac.quantile(percentile, axis='columns')

    longed_threshold = 100 * longed_frac.apply(
        partial(np.nanpercentile, q=100 * percentile),
        axis='columns',
    )
    shorted_threshold = 100 * shorted_frac.apply(
        partial(np.nanpercentile, q=100 * percentile),
        axis='columns',
    )
    grossed_threshold = 100 * grossed_frac.apply(
        partial(np.nanpercentile, q=100 * percentile),
        axis='columns',
    )

    return longed_threshold, shorted_threshold, grossed_threshold

Returns arrays of pth percentile of long short and gross volume exposures of an algorithm s held shares


def create_full_tear_sheet(returns,
                           positions=None,
                           transactions=None,
                           market_data=None,
                           benchmark_rets=None,
                           slippage=None,
                           live_start_date=None,
                           sector_mappings=None,
                           bayesian=False,
                           round_trips=False,
                           estimate_intraday='infer',
                           hide_positions=False,
                           cone_std=(1.0, 1.5, 2.0),
                           bootstrap=False,
                           unadjusted_returns=None,
                           style_factor_panel=None,
                           sectors=None,
                           caps=None,
                           shares_held=None,
                           volumes=None,
                           percentile=None,
                           turnover_denom='AGB',
                           set_context=True,
                           factor_returns=None,
                           factor_loadings=None,
                           pos_in_dollars=True,
                           header_rows=None,
                           factor_partitions=FACTOR_PARTITIONS):
    """
    Generate a number of tear sheets that are useful
    for analyzing a strategy's performance.

    - Fetches benchmarks if needed.
    - Creates tear sheets for returns, and significant events.
        If possible, also creates tear sheets for position analysis,
        transaction analysis, and Bayesian analysis.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - Time series with decimal returns.
         - Example:
            2015-07-16    -0.012143
            2015-07-17    0.045350
            2015-07-20    0.030957
            2015-07-21    0.004902
    positions : pd.DataFrame, optional
        Daily net position values.
         - Time series of dollar amount invested in each position and cash.
         - Days where stocks are not held can be represented by 0 or NaN.
         - Non-working capital is labelled 'cash'
         - Example:
            index         'AAPL'         'MSFT'          cash
            2004-01-09    13939.3800     -14012.9930     711.5585
            2004-01-12    14492.6300     -14624.8700     27.1821
            2004-01-13    -13853.2800    13653.6400      -43.6375
    transactions : pd.DataFrame, optional
        Executed trade volumes and fill prices.
        - One row per trade.
        - Trades on different names that occur at the
          same time will have identical indicies.
        - Example:
            index                  amount   price    symbol
            2004-01-09 12:18:01    483      324.12   'AAPL'
            2004-01-09 12:18:01    122      83.10    'MSFT'
            2004-01-13 14:12:23    -75      340.43   'AAPL'
    market_data : pd.Panel, optional
        Panel with items axis of 'price' and 'volume' DataFrames.
        The major and minor axes should match those of the
        the passed positions DataFrame (same dates and symbols).
    slippage : int/float, optional
        Basis points of slippage to apply to returns before generating
        tearsheet stats and plots.
        If a value is provided, slippage parameter sweep
        plots will be generated from the unadjusted returns.
        Transactions and positions must also be passed.
        - See txn.adjust_returns_for_slippage for more details.
    live_start_date : datetime, optional
        The point in time when the strategy began live trading,
        after its backtest period. This datetime should be normalized.
    hide_positions : bool, optional
        If True, will not output any symbol names.
    bayesian: boolean, optional
        If True, causes the generation of a Bayesian tear sheet.
    round_trips: boolean, optional
        If True, causes the generation of a round trip tear sheet.
    sector_mappings : dict or pd.Series, optional
        Security identifier to sector mapping.
        Security ids as keys, sectors as values.
    estimate_intraday: boolean or str, optional
        Instead of using the end-of-day positions, use the point in the day
        where we have the most $ invested. This will adjust positions to
        better approximate and represent how an intraday strategy behaves.
        By default, this is 'infer', and an attempt will be made to detect
        an intraday strategy. Specifying this value will prevent detection.
    cone_std : float, or tuple, optional
        If float, The standard deviation to use for the cone plots.
        If tuple, Tuple of standard deviation values to use for the cone plots
         - The cone is a normal distribution with this standard deviation
             centered around a linear regression.
    bootstrap : boolean (optional)
        Whether to perform bootstrap analysis for the performance
        metrics. Takes a few minutes longer.
    turnover_denom : str
        Either AGB or portfolio_value, default AGB.
        - See full explanation in txn.get_turnover.
    factor_returns : pd.Dataframe, optional
        Returns by factor, with date as index and factors as columns
    factor_loadings : pd.Dataframe, optional
        Factor loadings for all days in the date range, with date and
        ticker as index, and factors as columns.
    pos_in_dollars : boolean, optional
        indicates whether positions is in dollars
    header_rows : dict or OrderedDict, optional
        Extra rows to display at the top of the perf stats table.
    set_context : boolean, optional
        If True, set default plotting style context.
         - See plotting.context().
    factor_partitions : dict, optional
        dict specifying how factors should be separated in perf attrib
        factor returns and risk exposures plots
        - See create_perf_attrib_tear_sheet().
    """

    if (unadjusted_returns is None) and (slippage is not None) and\
       (transactions is not None):
        unadjusted_returns = returns.copy()
        returns = txn.adjust_returns_for_slippage(returns, positions,
                                                  transactions, slippage)

    positions = utils.check_intraday(estimate_intraday, returns,
                                     positions, transactions)

    create_returns_tear_sheet(
        returns,
        positions=positions,
        transactions=transactions,
        live_start_date=live_start_date,
        cone_std=cone_std,
        benchmark_rets=benchmark_rets,
        bootstrap=bootstrap,
        turnover_denom=turnover_denom,
        header_rows=header_rows,
        set_context=set_context)

    create_interesting_times_tear_sheet(returns,
                                        benchmark_rets=benchmark_rets,
                                        set_context=set_context)

    if positions is not None:
        create_position_tear_sheet(returns, positions,
                                   hide_positions=hide_positions,
                                   set_context=set_context,
                                   sector_mappings=sector_mappings,
                                   estimate_intraday=False)

        if transactions is not None:
            create_txn_tear_sheet(returns, positions, transactions,
                                  unadjusted_returns=unadjusted_returns,
                                  estimate_intraday=False,
                                  set_context=set_context)
            if round_trips:
                create_round_trip_tear_sheet(
                    returns=returns,
                    positions=positions,
                    transactions=transactions,
                    sector_mappings=sector_mappings,
                    estimate_intraday=False)

            if market_data is not None:
                create_capacity_tear_sheet(returns, positions, transactions,
                                           market_data,
                                           liquidation_daily_vol_limit=0.2,
                                           last_n_days=125,
                                           estimate_intraday=False)

        if style_factor_panel is not None:
            create_risk_tear_sheet(positions, style_factor_panel, sectors,
                                   caps, shares_held, volumes, percentile)

        if factor_returns is not None and factor_loadings is not None:
            create_perf_attrib_tear_sheet(returns, positions, factor_returns,
                                          factor_loadings, transactions,
                                          pos_in_dollars=pos_in_dollars,
                                          factor_partitions=factor_partitions)

    if bayesian:
        create_bayesian_tear_sheet(returns,
                                   live_start_date=live_start_date,
                                   benchmark_rets=benchmark_rets,
                                   set_context=set_context)

def create_full_tear_sheet ( returns , positions = None , transactions = None , market_data = None , benchmark_rets = None , slippage = None , live_start_date = None , sector_mappings = None , bayesian = False , round_trips = False , estimate_intraday = 'infer' , hide_positions = False , cone_std = ( 1.0 , 1.5 , 2.0 ) , bootstrap = False , unadjusted_returns = None , style_factor_panel = None , sectors = None , caps = None , shares_held = None , volumes = None , percentile = None , turnover_denom = 'AGB' , set_context = True , factor_returns = None , factor_loadings = None , pos_in_dollars = True , header_rows = None , factor_partitions = FACTOR_PARTITIONS ) : if ( unadjusted_returns is None ) and ( slippage is not None ) and ( transactions is not None ) : unadjusted_returns = returns . copy ( ) returns = txn . adjust_returns_for_slippage ( returns , positions , transactions , slippage ) positions = utils . check_intraday ( estimate_intraday , returns , positions , transactions ) create_returns_tear_sheet ( returns , positions = positions , transactions = transactions , live_start_date = live_start_date , cone_std = cone_std , benchmark_rets = benchmark_rets , bootstrap = bootstrap , turnover_denom = turnover_denom , header_rows = header_rows , set_context = set_context ) create_interesting_times_tear_sheet ( returns , benchmark_rets = benchmark_rets , set_context = set_context ) if positions is not None : create_position_tear_sheet ( returns , positions , hide_positions = hide_positions , set_context = set_context , sector_mappings = sector_mappings , estimate_intraday = False ) if transactions is not None : create_txn_tear_sheet ( returns , positions , transactions , unadjusted_returns = unadjusted_returns , estimate_intraday = False , set_context = set_context ) if round_trips : create_round_trip_tear_sheet ( returns = returns , positions = positions , transactions = transactions , sector_mappings = sector_mappings , estimate_intraday = False ) if market_data is not None : create_capacity_tear_sheet ( returns , positions , transactions , market_data , liquidation_daily_vol_limit = 0.2 , last_n_days = 125 , estimate_intraday = False ) if style_factor_panel is not None : create_risk_tear_sheet ( positions , style_factor_panel , sectors , caps , shares_held , volumes , percentile ) if factor_returns is not None and factor_loadings is not None : create_perf_attrib_tear_sheet ( returns , positions , factor_returns , factor_loadings , transactions , pos_in_dollars = pos_in_dollars , factor_partitions = factor_partitions ) if bayesian : create_bayesian_tear_sheet ( returns , live_start_date = live_start_date , benchmark_rets = benchmark_rets , set_context = set_context )

Generate a number of tear sheets that are useful for analyzing a strategy s performance .


def create_position_tear_sheet(returns, positions,
                               show_and_plot_top_pos=2, hide_positions=False,
                               return_fig=False, sector_mappings=None,
                               transactions=None, estimate_intraday='infer'):
    """
    Generate a number of plots for analyzing a
    strategy's positions and holdings.

    - Plots: gross leverage, exposures, top positions, and holdings.
    - Will also print the top positions held.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    show_and_plot_top_pos : int, optional
        By default, this is 2, and both prints and plots the
        top 10 positions.
        If this is 0, it will only plot; if 1, it will only print.
    hide_positions : bool, optional
        If True, will not output any symbol names.
        Overrides show_and_plot_top_pos to 0 to suppress text output.
    return_fig : boolean, optional
        If True, returns the figure that was plotted on.
    sector_mappings : dict or pd.Series, optional
        Security identifier to sector mapping.
        Security ids as keys, sectors as values.
    transactions : pd.DataFrame, optional
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
    estimate_intraday: boolean or str, optional
        Approximate returns for intraday strategies.
        See description in create_full_tear_sheet.
    """

    positions = utils.check_intraday(estimate_intraday, returns,
                                     positions, transactions)

    if hide_positions:
        show_and_plot_top_pos = 0
    vertical_sections = 7 if sector_mappings is not None else 6

    fig = plt.figure(figsize=(14, vertical_sections * 6))
    gs = gridspec.GridSpec(vertical_sections, 3, wspace=0.5, hspace=0.5)
    ax_exposures = plt.subplot(gs[0, :])
    ax_top_positions = plt.subplot(gs[1, :], sharex=ax_exposures)
    ax_max_median_pos = plt.subplot(gs[2, :], sharex=ax_exposures)
    ax_holdings = plt.subplot(gs[3, :], sharex=ax_exposures)
    ax_long_short_holdings = plt.subplot(gs[4, :])
    ax_gross_leverage = plt.subplot(gs[5, :], sharex=ax_exposures)

    positions_alloc = pos.get_percent_alloc(positions)

    plotting.plot_exposures(returns, positions, ax=ax_exposures)

    plotting.show_and_plot_top_positions(
        returns,
        positions_alloc,
        show_and_plot=show_and_plot_top_pos,
        hide_positions=hide_positions,
        ax=ax_top_positions)

    plotting.plot_max_median_position_concentration(positions,
                                                    ax=ax_max_median_pos)

    plotting.plot_holdings(returns, positions_alloc, ax=ax_holdings)

    plotting.plot_long_short_holdings(returns, positions_alloc,
                                      ax=ax_long_short_holdings)

    plotting.plot_gross_leverage(returns, positions,
                                 ax=ax_gross_leverage)

    if sector_mappings is not None:
        sector_exposures = pos.get_sector_exposures(positions,
                                                    sector_mappings)
        if len(sector_exposures.columns) > 1:
            sector_alloc = pos.get_percent_alloc(sector_exposures)
            sector_alloc = sector_alloc.drop('cash', axis='columns')
            ax_sector_alloc = plt.subplot(gs[6, :], sharex=ax_exposures)
            plotting.plot_sector_allocations(returns, sector_alloc,
                                             ax=ax_sector_alloc)

    for ax in fig.axes:
        plt.setp(ax.get_xticklabels(), visible=True)

    if return_fig:
        return fig

def create_position_tear_sheet ( returns , positions , show_and_plot_top_pos = 2 , hide_positions = False , return_fig = False , sector_mappings = None , transactions = None , estimate_intraday = 'infer' ) : positions = utils . check_intraday ( estimate_intraday , returns , positions , transactions ) if hide_positions : show_and_plot_top_pos = 0 vertical_sections = 7 if sector_mappings is not None else 6 fig = plt . figure ( figsize = ( 14 , vertical_sections * 6 ) ) gs = gridspec . GridSpec ( vertical_sections , 3 , wspace = 0.5 , hspace = 0.5 ) ax_exposures = plt . subplot ( gs [ 0 , : ] ) ax_top_positions = plt . subplot ( gs [ 1 , : ] , sharex = ax_exposures ) ax_max_median_pos = plt . subplot ( gs [ 2 , : ] , sharex = ax_exposures ) ax_holdings = plt . subplot ( gs [ 3 , : ] , sharex = ax_exposures ) ax_long_short_holdings = plt . subplot ( gs [ 4 , : ] ) ax_gross_leverage = plt . subplot ( gs [ 5 , : ] , sharex = ax_exposures ) positions_alloc = pos . get_percent_alloc ( positions ) plotting . plot_exposures ( returns , positions , ax = ax_exposures ) plotting . show_and_plot_top_positions ( returns , positions_alloc , show_and_plot = show_and_plot_top_pos , hide_positions = hide_positions , ax = ax_top_positions ) plotting . plot_max_median_position_concentration ( positions , ax = ax_max_median_pos ) plotting . plot_holdings ( returns , positions_alloc , ax = ax_holdings ) plotting . plot_long_short_holdings ( returns , positions_alloc , ax = ax_long_short_holdings ) plotting . plot_gross_leverage ( returns , positions , ax = ax_gross_leverage ) if sector_mappings is not None : sector_exposures = pos . get_sector_exposures ( positions , sector_mappings ) if len ( sector_exposures . columns ) > 1 : sector_alloc = pos . get_percent_alloc ( sector_exposures ) sector_alloc = sector_alloc . drop ( 'cash' , axis = 'columns' ) ax_sector_alloc = plt . subplot ( gs [ 6 , : ] , sharex = ax_exposures ) plotting . plot_sector_allocations ( returns , sector_alloc , ax = ax_sector_alloc ) for ax in fig . axes : plt . setp ( ax . get_xticklabels ( ) , visible = True ) if return_fig : return fig

Generate a number of plots for analyzing a strategy s positions and holdings .


def create_txn_tear_sheet(returns, positions, transactions,
                          unadjusted_returns=None, estimate_intraday='infer',
                          return_fig=False):
    """
    Generate a number of plots for analyzing a strategy's transactions.

    Plots: turnover, daily volume, and a histogram of daily volume.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
    unadjusted_returns : pd.Series, optional
        Daily unadjusted returns of the strategy, noncumulative.
        Will plot additional swippage sweep analysis.
         - See pyfolio.plotting.plot_swippage_sleep and
           pyfolio.plotting.plot_slippage_sensitivity
    estimate_intraday: boolean or str, optional
        Approximate returns for intraday strategies.
        See description in create_full_tear_sheet.
    return_fig : boolean, optional
        If True, returns the figure that was plotted on.
    """

    positions = utils.check_intraday(estimate_intraday, returns,
                                     positions, transactions)

    vertical_sections = 6 if unadjusted_returns is not None else 4

    fig = plt.figure(figsize=(14, vertical_sections * 6))
    gs = gridspec.GridSpec(vertical_sections, 3, wspace=0.5, hspace=0.5)
    ax_turnover = plt.subplot(gs[0, :])
    ax_daily_volume = plt.subplot(gs[1, :], sharex=ax_turnover)
    ax_turnover_hist = plt.subplot(gs[2, :])
    ax_txn_timings = plt.subplot(gs[3, :])

    plotting.plot_turnover(
        returns,
        transactions,
        positions,
        ax=ax_turnover)

    plotting.plot_daily_volume(returns, transactions, ax=ax_daily_volume)

    try:
        plotting.plot_daily_turnover_hist(transactions, positions,
                                          ax=ax_turnover_hist)
    except ValueError:
        warnings.warn('Unable to generate turnover plot.', UserWarning)

    plotting.plot_txn_time_hist(transactions, ax=ax_txn_timings)

    if unadjusted_returns is not None:
        ax_slippage_sweep = plt.subplot(gs[4, :])
        plotting.plot_slippage_sweep(unadjusted_returns,
                                     positions,
                                     transactions,
                                     ax=ax_slippage_sweep
                                     )
        ax_slippage_sensitivity = plt.subplot(gs[5, :])
        plotting.plot_slippage_sensitivity(unadjusted_returns,
                                           positions,
                                           transactions,
                                           ax=ax_slippage_sensitivity
                                           )
    for ax in fig.axes:
        plt.setp(ax.get_xticklabels(), visible=True)

    if return_fig:
        return fig

def
create_txn_tear_sheet
(
returns
,
positions
,
transactions
,
unadjusted_returns
=
,
estimate_intraday
=
'infer'
,
return_fig
=
False
)
:
positions
=
utils
.
check_intraday
(
estimate_intraday
,
returns
,
positions
,
transactions
)
vertical_sections
=
6
if
unadjusted_returns
is
not
else
4
fig
=
plt
.
figure
(
figsize
=
(
14
,
vertical_sections
*
6
)
)
gs
=
gridspec
.
GridSpec
(
vertical_sections
,
3
,
wspace
=
0.5
,
hspace
=
0.5
)
ax_turnover
=
plt
.
subplot
(
gs
[
0
,
:
]
)
ax_daily_volume
=
plt
.
subplot
(
gs
[
1
,
:
]
,
sharex
=
ax_turnover
)
ax_turnover_hist
=
plt
.
subplot
(
gs
[
2
,
:
]
)
ax_txn_timings
=
plt
.
subplot
(
gs
[
3
,
:
]
)
plotting
.
plot_turnover
(
returns
,
transactions
,
positions
,
ax
=
ax_turnover
)
plotting
.
plot_daily_volume
(
returns
,
transactions
,
ax
=
ax_daily_volume
)
try
:
plotting
.
plot_daily_turnover_hist
(
transactions
,
positions
,
ax
=
ax_turnover_hist
)
except
ValueError
:
warnings
.
warn
(
'Unable to generate turnover plot.'
,
UserWarning
)
plotting
.
plot_txn_time_hist
(
transactions
,
ax
=
ax_txn_timings
)
if
unadjusted_returns
is
not
:
ax_slippage_sweep
=
plt
.
subplot
(
gs
[
4
,
:
]
)
plotting
.
plot_slippage_sweep
(
unadjusted_returns
,
positions
,
transactions
,
ax
=
ax_slippage_sweep
)
ax_slippage_sensitivity
=
plt
.
subplot
(
gs
[
5
,
:
]
)
plotting
.
plot_slippage_sensitivity
(
unadjusted_returns
,
positions
,
transactions
,
ax
=
ax_slippage_sensitivity
)
for
ax
in
fig
.
axes
:
plt
.
setp
(
ax
.
get_xticklabels
(
)
,
visible
=
True
)
if
return_fig
:
return
fig

Generate a number of plots for analyzing a strategy s transactions .


def create_capacity_tear_sheet(returns, positions, transactions,
                               market_data,
                               liquidation_daily_vol_limit=0.2,
                               trade_daily_vol_limit=0.05,
                               last_n_days=utils.APPROX_BDAYS_PER_MONTH * 6,
                               days_to_liquidate_limit=1,
                               estimate_intraday='infer'):
    """
    Generates a report detailing portfolio size constraints set by
    least liquid tickers. Plots a "capacity sweep," a curve describing
    projected sharpe ratio given the slippage penalties that are
    applied at various capital bases.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
    market_data : pd.Panel
        Panel with items axis of 'price' and 'volume' DataFrames.
        The major and minor axes should match those of the
        the passed positions DataFrame (same dates and symbols).
    liquidation_daily_vol_limit : float
        Max proportion of a daily bar that can be consumed in the
        process of liquidating a position in the
        "days to liquidation" analysis.
    trade_daily_vol_limit : float
        Flag daily transaction totals that exceed proportion of
        daily bar.
    last_n_days : integer
        Compute max position allocation and dollar volume for only
        the last N days of the backtest
    days_to_liquidate_limit : integer
        Display all tickers with greater max days to liquidation.
    estimate_intraday: boolean or str, optional
        Approximate returns for intraday strategies.
        See description in create_full_tear_sheet.
    """

    positions = utils.check_intraday(estimate_intraday, returns,
                                     positions, transactions)

    print("Max days to liquidation is computed for each traded name "
          "assuming a 20% limit on daily bar consumption \n"
          "and trailing 5 day mean volume as the available bar volume.\n\n"
          "Tickers with >1 day liquidation time at a"
          " constant $1m capital base:")

    max_days_by_ticker = capacity.get_max_days_to_liquidate_by_ticker(
        positions, market_data,
        max_bar_consumption=liquidation_daily_vol_limit,
        capital_base=1e6,
        mean_volume_window=5)
    max_days_by_ticker.index = (
        max_days_by_ticker.index.map(utils.format_asset))

    print("Whole backtest:")
    utils.print_table(
        max_days_by_ticker[max_days_by_ticker.days_to_liquidate >
                           days_to_liquidate_limit])

    max_days_by_ticker_lnd = capacity.get_max_days_to_liquidate_by_ticker(
        positions, market_data,
        max_bar_consumption=liquidation_daily_vol_limit,
        capital_base=1e6,
        mean_volume_window=5,
        last_n_days=last_n_days)
    max_days_by_ticker_lnd.index = (
        max_days_by_ticker_lnd.index.map(utils.format_asset))

    print("Last {} trading days:".format(last_n_days))
    utils.print_table(
        max_days_by_ticker_lnd[max_days_by_ticker_lnd.days_to_liquidate > 1])

    llt = capacity.get_low_liquidity_transactions(transactions, market_data)
    llt.index = llt.index.map(utils.format_asset)

    print('Tickers with daily transactions consuming >{}% of daily bar \n'
          'all backtest:'.format(trade_daily_vol_limit * 100))
    utils.print_table(
        llt[llt['max_pct_bar_consumed'] > trade_daily_vol_limit * 100])

    llt = capacity.get_low_liquidity_transactions(
        transactions, market_data, last_n_days=last_n_days)

    print("Last {} trading days:".format(last_n_days))
    utils.print_table(
        llt[llt['max_pct_bar_consumed'] > trade_daily_vol_limit * 100])

    bt_starting_capital = positions.iloc[0].sum() / (1 + returns.iloc[0])
    fig, ax_capacity_sweep = plt.subplots(figsize=(14, 6))
    plotting.plot_capacity_sweep(returns, transactions, market_data,
                                 bt_starting_capital,
                                 min_pv=100000,
                                 max_pv=300000000,
                                 step_size=1000000,
                                 ax=ax_capacity_sweep)

Generates a report detailing portfolio size constraints set by least liquid tickers . Plots a capacity sweep a curve describing projected sharpe ratio given the slippage penalties that are applied at various capital bases .


def create_perf_attrib_tear_sheet(returns,
                                  positions,
                                  factor_returns,
                                  factor_loadings,
                                  transactions=None,
                                  pos_in_dollars=True,
                                  return_fig=False,
                                  factor_partitions=FACTOR_PARTITIONS):
    """
    Generate plots and tables for analyzing a strategy's performance.

    Parameters
    ----------
    returns : pd.Series
        Returns for each day in the date range.

    positions: pd.DataFrame
        Daily holdings (in dollars or percentages), indexed by date.
        Will be converted to percentages if positions are in dollars.
        Short positions show up as cash in the 'cash' column.

    factor_returns : pd.DataFrame
        Returns by factor, with date as index and factors as columns

    factor_loadings : pd.DataFrame
        Factor loadings for all days in the date range, with date
        and ticker as index, and factors as columns.

    transactions : pd.DataFrame, optional
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
         - Default is None.

    pos_in_dollars : boolean, optional
        Flag indicating whether `positions` are in dollars or percentages
        If True, positions are in dollars.

    return_fig : boolean, optional
        If True, returns the figure that was plotted on.

    factor_partitions : dict
        dict specifying how factors should be separated in factor returns
        and risk exposures plots
        - Example:
          {'style': ['momentum', 'size', 'value', ...],
           'sector': ['technology', 'materials', ... ]}
    """
    portfolio_exposures, perf_attrib_data = perf_attrib.perf_attrib(
        returns, positions, factor_returns, factor_loadings, transactions,
        pos_in_dollars=pos_in_dollars
    )

    display(Markdown("## Performance Relative to Common Risk Factors"))

    # aggregate perf attrib stats and show summary table
    perf_attrib.show_perf_attrib_stats(returns, positions, factor_returns,
                                       factor_loadings, transactions,
                                       pos_in_dollars)

    # one section for the returns plot, and for each factor grouping
    # one section for factor returns, and one for risk exposures
    vertical_sections = 1 + 2 * max(len(factor_partitions), 1)
    current_section = 0

    fig = plt.figure(figsize=[14, vertical_sections * 6])

    gs = gridspec.GridSpec(vertical_sections, 1,
                           wspace=0.5, hspace=0.5)

    perf_attrib.plot_returns(perf_attrib_data,
                             ax=plt.subplot(gs[current_section]))
    current_section += 1

    if factor_partitions is not None:

        for factor_type, partitions in factor_partitions.iteritems():

            columns_to_select = perf_attrib_data.columns.intersection(
                partitions
            )

            perf_attrib.plot_factor_contribution_to_perf(
                perf_attrib_data[columns_to_select],
                ax=plt.subplot(gs[current_section]),
                title=(
                    'Cumulative common {} returns attribution'
                ).format(factor_type)
            )
            current_section += 1

        for factor_type, partitions in factor_partitions.iteritems():

            perf_attrib.plot_risk_exposures(
                portfolio_exposures[portfolio_exposures.columns
                                    .intersection(partitions)],
                ax=plt.subplot(gs[current_section]),
                title='Daily {} factor exposures'.format(factor_type)
            )
            current_section += 1

    else:

        perf_attrib.plot_factor_contribution_to_perf(
            perf_attrib_data,
            ax=plt.subplot(gs[current_section])
        )
        current_section += 1

        perf_attrib.plot_risk_exposures(
            portfolio_exposures,
            ax=plt.subplot(gs[current_section])
        )

    gs.tight_layout(fig)

    if return_fig:
        return fig

Generate plots and tables for analyzing a strategy s performance .


def daily_txns_with_bar_data(transactions, market_data):
    """
    Sums the absolute value of shares traded in each name on each day.
    Adds columns containing the closing price and total daily volume for
    each day-ticker combination.

    Parameters
    ----------
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
        - See full explanation in tears.create_full_tear_sheet
    market_data : pd.Panel
        Contains "volume" and "price" DataFrames for the tickers
        in the passed positions DataFrames

    Returns
    -------
    txn_daily : pd.DataFrame
        Daily totals for transacted shares in each traded name.
        price and volume columns for close price and daily volume for
        the corresponding ticker, respectively.
    """

    transactions.index.name = 'date'
    txn_daily = pd.DataFrame(transactions.assign(
        amount=abs(transactions.amount)).groupby(
        ['symbol', pd.TimeGrouper('D')]).sum()['amount'])
    txn_daily['price'] = market_data['price'].unstack()
    txn_daily['volume'] = market_data['volume'].unstack()

    txn_daily = txn_daily.reset_index().set_index('date')

    return txn_daily

def daily_txns_with_bar_data ( transactions , market_data ) : transactions . index . name = 'date' txn_daily = pd . DataFrame ( transactions . assign ( amount = abs ( transactions . amount ) ) . groupby ( [ 'symbol' , pd . TimeGrouper ( 'D' ) ] ) . sum ( ) [ 'amount' ] ) txn_daily [ 'price' ] = market_data [ 'price' ] . unstack ( ) txn_daily [ 'volume' ] = market_data [ 'volume' ] . unstack ( ) txn_daily = txn_daily . reset_index ( ) . set_index ( 'date' ) return txn_daily

Sums the absolute value of shares traded in each name on each day . Adds columns containing the closing price and total daily volume for each day - ticker combination .


def days_to_liquidate_positions(positions, market_data,
                                max_bar_consumption=0.2,
                                capital_base=1e6,
                                mean_volume_window=5):
    """
    Compute the number of days that would have been required
    to fully liquidate each position on each day based on the
    trailing n day mean daily bar volume and a limit on the proportion
    of a daily bar that we are allowed to consume.

    This analysis uses portfolio allocations and a provided capital base
    rather than the dollar values in the positions DataFrame to remove the
    effect of compounding on days to liquidate. In other words, this function
    assumes that the net liquidation portfolio value will always remain
    constant at capital_base.

    Parameters
    ----------
    positions: pd.DataFrame
        Contains daily position values including cash
        - See full explanation in tears.create_full_tear_sheet
    market_data : pd.Panel
        Panel with items axis of 'price' and 'volume' DataFrames.
        The major and minor axes should match those of the
        the passed positions DataFrame (same dates and symbols).
    max_bar_consumption : float
        Max proportion of a daily bar that can be consumed in the
        process of liquidating a position.
    capital_base : integer
        Capital base multiplied by portfolio allocation to compute
        position value that needs liquidating.
    mean_volume_window : float
        Trailing window to use in mean volume calculation.

    Returns
    -------
    days_to_liquidate : pd.DataFrame
        Number of days required to fully liquidate daily positions.
        Datetime index, symbols as columns.
    """

    DV = market_data['volume'] * market_data['price']
    roll_mean_dv = DV.rolling(window=mean_volume_window,
                              center=False).mean().shift()
    roll_mean_dv = roll_mean_dv.replace(0, np.nan)

    positions_alloc = pos.get_percent_alloc(positions)
    positions_alloc = positions_alloc.drop('cash', axis=1)

    days_to_liquidate = (positions_alloc * capital_base) / \
        (max_bar_consumption * roll_mean_dv)

    return days_to_liquidate.iloc[mean_volume_window:]

def days_to_liquidate_positions ( positions , market_data , max_bar_consumption = 0.2 , capital_base = 1e6 , mean_volume_window = 5 ) : DV = market_data [ 'volume' ] * market_data [ 'price' ] roll_mean_dv = DV . rolling ( window = mean_volume_window , center = False ) . mean ( ) . shift ( ) roll_mean_dv = roll_mean_dv . replace ( 0 , np . nan ) positions_alloc = pos . get_percent_alloc ( positions ) positions_alloc = positions_alloc . drop ( 'cash' , axis = 1 ) days_to_liquidate = ( positions_alloc * capital_base ) / ( max_bar_consumption * roll_mean_dv ) return days_to_liquidate . iloc [ mean_volume_window : ]

Compute the number of days that would have been required to fully liquidate each position on each day based on the trailing n day mean daily bar volume and a limit on the proportion of a daily bar that we are allowed to consume .


def get_low_liquidity_transactions(transactions, market_data,
                                   last_n_days=None):
    """
    For each traded name, find the daily transaction total that consumed
    the greatest proportion of available daily bar volume.

    Parameters
    ----------
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
    market_data : pd.Panel
        Panel with items axis of 'price' and 'volume' DataFrames.
        The major and minor axes should match those of the
        the passed positions DataFrame (same dates and symbols).
    last_n_days : integer
        Compute for only the last n days of the passed backtest data.
    """

    txn_daily_w_bar = daily_txns_with_bar_data(transactions, market_data)
    txn_daily_w_bar.index.name = 'date'
    txn_daily_w_bar = txn_daily_w_bar.reset_index()

    if last_n_days is not None:
        md = txn_daily_w_bar.date.max() - pd.Timedelta(days=last_n_days)
        txn_daily_w_bar = txn_daily_w_bar[txn_daily_w_bar.date > md]

    bar_consumption = txn_daily_w_bar.assign(
        max_pct_bar_consumed=(
            txn_daily_w_bar.amount/txn_daily_w_bar.volume)*100
    ).sort_values('max_pct_bar_consumed', ascending=False)
    max_bar_consumption = bar_consumption.groupby('symbol').first()

    return max_bar_consumption[['date', 'max_pct_bar_consumed']]

def get_low_liquidity_transactions ( transactions , market_data , last_n_days = None ) : txn_daily_w_bar = daily_txns_with_bar_data ( transactions , market_data ) txn_daily_w_bar . index . name = 'date' txn_daily_w_bar = txn_daily_w_bar . reset_index ( ) if last_n_days is not None : md = txn_daily_w_bar . date . max ( ) - pd . Timedelta ( days = last_n_days ) txn_daily_w_bar = txn_daily_w_bar [ txn_daily_w_bar . date > md ] bar_consumption = txn_daily_w_bar . assign ( max_pct_bar_consumed = ( txn_daily_w_bar . amount / txn_daily_w_bar . volume ) * 100 ) . sort_values ( 'max_pct_bar_consumed' , ascending = False ) max_bar_consumption = bar_consumption . groupby ( 'symbol' ) . first ( ) return max_bar_consumption [ [ 'date' , 'max_pct_bar_consumed' ] ]

For each traded name find the daily transaction total that consumed the greatest proportion of available daily bar volume .


def apply_slippage_penalty(returns, txn_daily, simulate_starting_capital,
                           backtest_starting_capital, impact=0.1):
    """
    Applies quadratic volumeshare slippage model to daily returns based
    on the proportion of the observed historical daily bar dollar volume
    consumed by the strategy's trades. Scales the size of trades based
    on the ratio of the starting capital we wish to test to the starting
    capital of the passed backtest data.

    Parameters
    ----------
    returns : pd.Series
        Time series of daily returns.
    txn_daily : pd.Series
        Daily transaciton totals, closing price, and daily volume for
        each traded name. See price_volume_daily_txns for more details.
    simulate_starting_capital : integer
        capital at which we want to test
    backtest_starting_capital: capital base at which backtest was
        origionally run. impact: See Zipline volumeshare slippage model
    impact : float
        Scales the size of the slippage penalty.

    Returns
    -------
    adj_returns : pd.Series
        Slippage penalty adjusted daily returns.
    """

    mult = simulate_starting_capital / backtest_starting_capital
    simulate_traded_shares = abs(mult * txn_daily.amount)
    simulate_traded_dollars = txn_daily.price * simulate_traded_shares
    simulate_pct_volume_used = simulate_traded_shares / txn_daily.volume

    penalties = simulate_pct_volume_used**2 \
        * impact * simulate_traded_dollars

    daily_penalty = penalties.resample('D').sum()
    daily_penalty = daily_penalty.reindex(returns.index).fillna(0)

    # Since we are scaling the numerator of the penalties linearly
    # by capital base, it makes the most sense to scale the denominator
    # similarly. In other words, since we aren't applying compounding to
    # simulate_traded_shares, we shouldn't apply compounding to pv.
    portfolio_value = ep.cum_returns(
        returns, starting_value=backtest_starting_capital) * mult

    adj_returns = returns - (daily_penalty / portfolio_value)

    return adj_returns

Applies quadratic volumeshare slippage model to daily returns based on the proportion of the observed historical daily bar dollar volume consumed by the strategy s trades . Scales the size of trades based on the ratio of the starting capital we wish to test to the starting capital of the passed backtest data .


def map_transaction(txn):
    """
    Maps a single transaction row to a dictionary.

    Parameters
    ----------
    txn : pd.DataFrame
        A single transaction object to convert to a dictionary.

    Returns
    -------
    dict
        Mapped transaction.
    """

    if isinstance(txn['sid'], dict):
        sid = txn['sid']['sid']
        symbol = txn['sid']['symbol']
    else:
        sid = txn['sid']
        symbol = txn['sid']

    return {'sid': sid,
            'symbol': symbol,
            'price': txn['price'],
            'order_id': txn['order_id'],
            'amount': txn['amount'],
            'commission': txn['commission'],
            'dt': txn['dt']}

def map_transaction ( txn ) : if isinstance ( txn [ 'sid' ] , dict ) : sid = txn [ 'sid' ] [ 'sid' ] symbol = txn [ 'sid' ] [ 'symbol' ] else : sid = txn [ 'sid' ] symbol = txn [ 'sid' ] return { 'sid' : sid , 'symbol' : symbol , 'price' : txn [ 'price' ] , 'order_id' : txn [ 'order_id' ] , 'amount' : txn [ 'amount' ] , 'commission' : txn [ 'commission' ] , 'dt' : txn [ 'dt' ] }

Maps a single transaction row to a dictionary .


def make_transaction_frame(transactions):
    """
    Formats a transaction DataFrame.

    Parameters
    ----------
    transactions : pd.DataFrame
        Contains improperly formatted transactional data.

    Returns
    -------
    df : pd.DataFrame
        Daily transaction volume and dollar ammount.
         - See full explanation in tears.create_full_tear_sheet.
    """

    transaction_list = []
    for dt in transactions.index:
        txns = transactions.loc[dt]
        if len(txns) == 0:
            continue

        for txn in txns:
            txn = map_transaction(txn)
            transaction_list.append(txn)
    df = pd.DataFrame(sorted(transaction_list, key=lambda x: x['dt']))
    df['txn_dollars'] = -df['amount'] * df['price']

    df.index = list(map(pd.Timestamp, df.dt.values))
    return df

def make_transaction_frame ( transactions ) : transaction_list = [ ] for dt in transactions . index : txns = transactions . loc [ dt ] if len ( txns ) == 0 : continue for txn in txns : txn = map_transaction ( txn ) transaction_list . append ( txn ) df = pd . DataFrame ( sorted ( transaction_list , key = lambda x : x [ 'dt' ] ) ) df [ 'txn_dollars' ] = - df [ 'amount' ] * df [ 'price' ] df . index = list ( map ( pd . Timestamp , df . dt . values ) ) return df


def get_txn_vol(transactions):
    """
    Extract daily transaction data from set of transaction objects.

    Parameters
    ----------
    transactions : pd.DataFrame
        Time series containing one row per symbol (and potentially
        duplicate datetime indices) and columns for amount and
        price.

    Returns
    -------
    pd.DataFrame
        Daily transaction volume and number of shares.
         - See full explanation in tears.create_full_tear_sheet.
    """

    txn_norm = transactions.copy()
    txn_norm.index = txn_norm.index.normalize()
    amounts = txn_norm.amount.abs()
    prices = txn_norm.price
    values = amounts * prices
    daily_amounts = amounts.groupby(amounts.index).sum()
    daily_values = values.groupby(values.index).sum()
    daily_amounts.name = "txn_shares"
    daily_values.name = "txn_volume"
    return pd.concat([daily_values, daily_amounts], axis=1)

def get_txn_vol ( transactions ) : txn_norm = transactions . copy ( ) txn_norm . index = txn_norm . index . normalize ( ) amounts = txn_norm . amount . abs ( ) prices = txn_norm . price values = amounts * prices daily_amounts = amounts . groupby ( amounts . index ) . sum ( ) daily_values = values . groupby ( values . index ) . sum ( ) daily_amounts . name = "txn_shares" daily_values . name = "txn_volume" return pd . concat ( [ daily_values , daily_amounts ] , axis = 1 )

Extract daily transaction data from set of transaction objects .


def adjust_returns_for_slippage(returns, positions, transactions,
                                slippage_bps):
    """
    Apply a slippage penalty for every dollar traded.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.
    slippage_bps: int/float
        Basis points of slippage to apply.

    Returns
    -------
    pd.Series
        Time series of daily returns, adjusted for slippage.
    """

    slippage = 0.0001 * slippage_bps
    portfolio_value = positions.sum(axis=1)
    pnl = portfolio_value * returns
    traded_value = get_txn_vol(transactions).txn_volume
    slippage_dollars = traded_value * slippage
    adjusted_pnl = pnl.add(-slippage_dollars, fill_value=0)
    adjusted_returns = returns * adjusted_pnl / pnl

    return adjusted_returns

def adjust_returns_for_slippage ( returns , positions , transactions , slippage_bps ) : slippage = 0.0001 * slippage_bps portfolio_value = positions . sum ( axis = 1 ) pnl = portfolio_value * returns traded_value = get_txn_vol ( transactions ) . txn_volume slippage_dollars = traded_value * slippage adjusted_pnl = pnl . add ( - slippage_dollars , fill_value = 0 ) adjusted_returns = returns * adjusted_pnl / pnl return adjusted_returns

Apply a slippage penalty for every dollar traded .


def get_turnover(positions, transactions, denominator='AGB'):
    """
     - Value of purchases and sales divided
    by either the actual gross book or the portfolio value
    for the time step.

    Parameters
    ----------
    positions : pd.DataFrame
        Contains daily position values including cash.
        - See full explanation in tears.create_full_tear_sheet
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
        - See full explanation in tears.create_full_tear_sheet
    denominator : str, optional
        Either 'AGB' or 'portfolio_value', default AGB.
        - AGB (Actual gross book) is the gross market
        value (GMV) of the specific algo being analyzed.
        Swapping out an entire portfolio of stocks for
        another will yield 200% turnover, not 100%, since
        transactions are being made for both sides.
        - We use average of the previous and the current end-of-period
        AGB to avoid singularities when trading only into or
        out of an entire book in one trading period.
        - portfolio_value is the total value of the algo's
        positions end-of-period, including cash.

    Returns
    -------
    turnover_rate : pd.Series
        timeseries of portfolio turnover rates.
    """

    txn_vol = get_txn_vol(transactions)
    traded_value = txn_vol.txn_volume

    if denominator == 'AGB':
        # Actual gross book is the same thing as the algo's GMV
        # We want our denom to be avg(AGB previous, AGB current)
        AGB = positions.drop('cash', axis=1).abs().sum(axis=1)
        denom = AGB.rolling(2).mean()

        # Since the first value of pd.rolling returns NaN, we
        # set our "day 0" AGB to 0.
        denom.iloc[0] = AGB.iloc[0] / 2
    elif denominator == 'portfolio_value':
        denom = positions.sum(axis=1)
    else:
        raise ValueError(
            "Unexpected value for denominator '{}'. The "
            "denominator parameter must be either 'AGB'"
            " or 'portfolio_value'.".format(denominator)
        )

    denom.index = denom.index.normalize()
    turnover = traded_value.div(denom, axis='index')
    turnover = turnover.fillna(0)
    return turnover

- Value of purchases and sales divided by either the actual gross book or the portfolio value for the time step .


def _groupby_consecutive(txn, max_delta=pd.Timedelta('8h')):
    """Merge transactions of the same direction separated by less than
    max_delta time duration.

    Parameters
    ----------
    transactions : pd.DataFrame
        Prices and amounts of executed round_trips. One row per trade.
        - See full explanation in tears.create_full_tear_sheet

    max_delta : pandas.Timedelta (optional)
        Merge transactions in the same direction separated by less
        than max_delta time duration.

    Returns
    -------
    transactions : pd.DataFrame

    """
    def vwap(transaction):
        if transaction.amount.sum() == 0:
            warnings.warn('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.sub(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

Merge transactions of the same direction separated by less than max_delta time duration .


def extract_round_trips(transactions,
                        portfolio_value=None):
    """Group transactions into "round trips". First, transactions are
    grouped by day and directionality. Then, long and short
    transactions are matched to create round-trip round_trips for which
    PnL, duration and returns are computed. Crossings where a position
    changes from long to short and vice-versa are handled correctly.

    Under the hood, we reconstruct the individual shares in a
    portfolio over time and match round_trips in a FIFO-order.

    For example, the following transactions would constitute one round trip:
    index                  amount   price    symbol
    2004-01-09 12:18:01    10       50      'AAPL'
    2004-01-09 15:12:53    10       100      'AAPL'
    2004-01-13 14:41:23    -10      100      'AAPL'
    2004-01-13 15:23:34    -10      200       'AAPL'

    First, the first two and last two round_trips will be merged into a two
    single transactions (computing the price via vwap). Then, during
    the portfolio reconstruction, the two resulting transactions will
    be merged and result in 1 round-trip trade with a PnL of
    (150 * 20) - (75 * 20) = 1500.

    Note, that round trips do not have to close out positions
    completely. For example, we could have removed the last
    transaction in the example above and still generated a round-trip
    over 10 shares with 10 shares left in the portfolio to be matched
    with a later transaction.

    Parameters
    ----------
    transactions : pd.DataFrame
        Prices and amounts of executed round_trips. One row per trade.
        - See full explanation in tears.create_full_tear_sheet

    portfolio_value : pd.Series (optional)
        Portfolio value (all net assets including cash) over time.
        Note that portfolio_value needs to beginning of day, so either
        use .shift() or positions.sum(axis='columns') / (1+returns).

    Returns
    -------
    round_trips : pd.DataFrame
        DataFrame with one row per round trip.  The returns column
        contains returns in respect to the portfolio value while
        rt_returns are the returns in regards to the invested capital
        into that partiulcar round-trip.
    """

    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.')
                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,
                                   })

    roundtrips = pd.DataFrame(roundtrips)

    roundtrips['duration'] = roundtrips['close_dt'].sub(roundtrips['open_dt'])

    if portfolio_value is not None:
        # Need to normalize so that we can join
        pv = pd.DataFrame(portfolio_value,
                          columns=['portfolio_value'])\
            .assign(date=portfolio_value.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.portfolio_value
        roundtrips = roundtrips.drop('date', axis='columns')

    return roundtrips

Group transactions into round trips . First transactions are grouped by day and directionality . Then long and short transactions are matched to create round - trip round_trips for which PnL duration and returns are computed . Crossings where a position changes from long to short and vice - versa are handled correctly .


def add_closing_transactions(positions, transactions):
    """
    Appends transactions that close out all positions at the end of
    the timespan covered by positions data. Utilizes pricing information
    in the positions DataFrame to determine closing price.

    Parameters
    ----------
    positions : pd.DataFrame
        The positions that the strategy takes over time.
    transactions : pd.DataFrame
        Prices and amounts of executed round_trips. One row per trade.
        - See full explanation in tears.create_full_tear_sheet

    Returns
    -------
    closed_txns : pd.DataFrame
        Transactions with closing transactions appended.
    """

    closed_txns = transactions[['symbol', 'amount', 'price']]

    pos_at_end = positions.drop('cash', axis=1).iloc[-1]
    open_pos = pos_at_end.replace(0, np.nan).dropna()
    # Add closing round_trips one second after the close to be sure
    # they don't conflict with other round_trips executed at that time.
    end_dt = open_pos.name + pd.Timedelta(seconds=1)

    for sym, ending_val in open_pos.iteritems():
        txn_sym = transactions[transactions.symbol == sym]

        ending_amount = txn_sym.amount.sum()

        ending_price = ending_val / ending_amount
        closing_txn = {'symbol': sym,
                       'amount': -ending_amount,
                       'price': ending_price}

        closing_txn = pd.DataFrame(closing_txn, index=[end_dt])
        closed_txns = closed_txns.append(closing_txn)

    closed_txns = closed_txns[closed_txns.amount != 0]

    return closed_txns

Appends transactions that close out all positions at the end of the timespan covered by positions data . Utilizes pricing information in the positions DataFrame to determine closing price .


def apply_sector_mappings_to_round_trips(round_trips, sector_mappings):
    """
    Translates round trip symbols to sectors.

    Parameters
    ----------
    round_trips : pd.DataFrame
        DataFrame with one row per round trip trade.
        - See full explanation in round_trips.extract_round_trips
    sector_mappings : dict or pd.Series, optional
        Security identifier to sector mapping.
        Security ids as keys, sectors as values.

    Returns
    -------
    sector_round_trips : pd.DataFrame
        Round trips with symbol names replaced by sector names.
    """

    sector_round_trips = round_trips.copy()
    sector_round_trips.symbol = sector_round_trips.symbol.apply(
        lambda x: sector_mappings.get(x, 'No Sector Mapping'))
    sector_round_trips = sector_round_trips.dropna(axis=0)

    return sector_round_trips

def
apply_sector_mappings_to_round_trips
(
round_trips
,
sector_mappings
)
:
sector_round_trips
=
round_trips
.
copy
(
)
sector_round_trips
.
symbol
=
sector_round_trips
.
symbol
.
apply
(
lambda
x
:
sector_mappings
.
get
(
x
,
'No Sector Mapping'
)
)
sector_round_trips
=
sector_round_trips
.
dropna
(
axis
=
0
)
return
sector_round_trips

Translates round trip symbols to sectors .


def gen_round_trip_stats(round_trips):
    """Generate various round-trip statistics.

    Parameters
    ----------
    round_trips : pd.DataFrame
        DataFrame with one row per round trip trade.
        - See full explanation in round_trips.extract_round_trips

    Returns
    -------
    stats : dict
       A dictionary where each value is a pandas DataFrame containing
       various round-trip statistics.

    See also
    --------
    round_trips.print_round_trip_stats
    """

    stats = {}
    stats['pnl'] = agg_all_long_short(round_trips, 'pnl', PNL_STATS)
    stats['summary'] = agg_all_long_short(round_trips, 'pnl',
                                          SUMMARY_STATS)
    stats['duration'] = agg_all_long_short(round_trips, 'duration',
                                           DURATION_STATS)
    stats['returns'] = agg_all_long_short(round_trips, 'returns',
                                          RETURN_STATS)

    stats['symbols'] = \
        round_trips.groupby('symbol')['returns'].agg(RETURN_STATS).T

    return stats

def gen_round_trip_stats ( round_trips ) : stats = { } stats [ 'pnl' ] = agg_all_long_short ( round_trips , 'pnl' , PNL_STATS ) stats [ 'summary' ] = agg_all_long_short ( round_trips , 'pnl' , SUMMARY_STATS ) stats [ 'duration' ] = agg_all_long_short ( round_trips , 'duration' , DURATION_STATS ) stats [ 'returns' ] = agg_all_long_short ( round_trips , 'returns' , RETURN_STATS ) stats [ 'symbols' ] = round_trips . groupby ( 'symbol' ) [ 'returns' ] . agg ( RETURN_STATS ) . T return stats

Generate various round - trip statistics .


def print_round_trip_stats(round_trips, hide_pos=False):
    """Print various round-trip statistics. Tries to pretty-print tables
    with HTML output if run inside IPython NB.

    Parameters
    ----------
    round_trips : pd.DataFrame
        DataFrame with one row per round trip trade.
        - See full explanation in round_trips.extract_round_trips

    See also
    --------
    round_trips.gen_round_trip_stats
    """

    stats = gen_round_trip_stats(round_trips)

    print_table(stats['summary'], float_format='{:.2f}'.format,
                name='Summary stats')
    print_table(stats['pnl'], float_format='${:.2f}'.format, name='PnL stats')
    print_table(stats['duration'], float_format='{:.2f}'.format,
                name='Duration stats')
    print_table(stats['returns'] * 100, float_format='{:.2f}%'.format,
                name='Return stats')

    if not hide_pos:
        stats['symbols'].columns = stats['symbols'].columns.map(format_asset)
        print_table(stats['symbols'] * 100,
                    float_format='{:.2f}%'.format, name='Symbol stats')

def
print_round_trip_stats
(
round_trips
,
hide_pos
=
False
)
:
stats
=
gen_round_trip_stats
(
round_trips
)
print_table
(
stats
[
'summary'
]
,
float_format
=
'{:.2f}'
.
format
,
name
=
'Summary stats'
)
print_table
(
stats
[
'pnl'
]
,
float_format
=
'${:.2f}'
.
format
,
name
=
'PnL stats'
)
print_table
(
stats
[
'duration'
]
,
float_format
=
'{:.2f}'
.
format
,
name
=
'Duration stats'
)
print_table
(
stats
[
'returns'
]
*
100
,
float_format
=
'{:.2f}%'
.
format
,
name
=
'Return stats'
)
if
not
hide_pos
:
stats
[
'symbols'
]
.
columns
=
stats
[
'symbols'
]
.
columns
.
map
(
format_asset
)
print_table
(
stats
[
'symbols'
]
*
100
,
float_format
=
'{:.2f}%'
.
format
,
name
=
'Symbol stats'
)

Print various round - trip statistics . Tries to pretty - print tables with HTML output if run inside IPython NB .


def perf_attrib(returns,
                positions,
                factor_returns,
                factor_loadings,
                transactions=None,
                pos_in_dollars=True):
    """
    Attributes the performance of a returns stream to a set of risk factors.

    Preprocesses inputs, and then calls empyrical.perf_attrib. See
    empyrical.perf_attrib for more info.

    Performance attribution determines how much each risk factor, e.g.,
    momentum, the technology sector, etc., contributed to total returns, as
    well as the daily exposure to each of the risk factors. The returns that
    can be attributed to one of the given risk factors are the
    `common_returns`, and the returns that _cannot_ be attributed to a risk
    factor are the `specific_returns`, or the alpha. The common_returns and
    specific_returns summed together will always equal the total returns.

    Parameters
    ----------
    returns : pd.Series
        Returns for each day in the date range.
        - Example:
            2017-01-01   -0.017098
            2017-01-02    0.002683
            2017-01-03   -0.008669

    positions: pd.DataFrame
        Daily holdings (in dollars or percentages), indexed by date.
        Will be converted to percentages if positions are in dollars.
        Short positions show up as cash in the 'cash' column.
        - Examples:
                        AAPL  TLT  XOM  cash
            2017-01-01    34   58   10     0
            2017-01-02    22   77   18     0
            2017-01-03   -15   27   30    15

                            AAPL       TLT       XOM  cash
            2017-01-01  0.333333  0.568627  0.098039   0.0
            2017-01-02  0.188034  0.658120  0.153846   0.0
            2017-01-03  0.208333  0.375000  0.416667   0.0

    factor_returns : pd.DataFrame
        Returns by factor, with date as index and factors as columns
        - Example:
                        momentum  reversal
            2017-01-01  0.002779 -0.005453
            2017-01-02  0.001096  0.010290

    factor_loadings : pd.DataFrame
        Factor loadings for all days in the date range, with date and ticker as
        index, and factors as columns.
        - Example:
                               momentum  reversal
            dt         ticker
            2017-01-01 AAPL   -1.592914  0.852830
                       TLT     0.184864  0.895534
                       XOM     0.993160  1.149353
            2017-01-02 AAPL   -0.140009 -0.524952
                       TLT    -1.066978  0.185435
                       XOM    -1.798401  0.761549

    transactions : pd.DataFrame, optional
        Executed trade volumes and fill prices. Used to check the turnover of
        the algorithm. Default is None, in which case the turnover check is
        skipped.

        - One row per trade.
        - Trades on different names that occur at the
          same time will have identical indicies.
        - Example:
            index                  amount   price    symbol
            2004-01-09 12:18:01    483      324.12   'AAPL'
            2004-01-09 12:18:01    122      83.10    'MSFT'
            2004-01-13 14:12:23    -75      340.43   'AAPL'

    pos_in_dollars : bool
        Flag indicating whether `positions` are in dollars or percentages
        If True, positions are in dollars.

    Returns
    -------
    tuple of (risk_exposures_portfolio, perf_attribution)

    risk_exposures_portfolio : pd.DataFrame
        df indexed by datetime, with factors as columns
        - Example:
                        momentum  reversal
            dt
            2017-01-01 -0.238655  0.077123
            2017-01-02  0.821872  1.520515

    perf_attribution : pd.DataFrame
        df with factors, common returns, and specific returns as columns,
        and datetimes as index
        - Example:
                        momentum  reversal  common_returns  specific_returns
            dt
            2017-01-01  0.249087  0.935925        1.185012          1.185012
            2017-01-02 -0.003194 -0.400786       -0.403980         -0.403980
    """
    (returns,
     positions,
     factor_returns,
     factor_loadings) = _align_and_warn(returns,
                                        positions,
                                        factor_returns,
                                        factor_loadings,
                                        transactions=transactions,
                                        pos_in_dollars=pos_in_dollars)

    # Note that we convert positions to percentages *after* the checks
    # above, since get_turnover() expects positions in dollars.
    positions = _stack_positions(positions, pos_in_dollars=pos_in_dollars)

    return ep.perf_attrib(returns, positions, factor_returns, factor_loadings)

Attributes the performance of a returns stream to a set of risk factors .


def compute_exposures(positions, factor_loadings, stack_positions=True,
                      pos_in_dollars=True):
    """
    Compute daily risk factor exposures.

    Normalizes positions (if necessary) and calls ep.compute_exposures.
    See empyrical.compute_exposures for more info.

    Parameters
    ----------
    positions: pd.DataFrame or pd.Series
        Daily holdings (in dollars or percentages), indexed by date, OR
        a series of holdings indexed by date and ticker.
        - Examples:
                        AAPL  TLT  XOM  cash
            2017-01-01    34   58   10     0
            2017-01-02    22   77   18     0
            2017-01-03   -15   27   30    15

                            AAPL       TLT       XOM  cash
            2017-01-01  0.333333  0.568627  0.098039   0.0
            2017-01-02  0.188034  0.658120  0.153846   0.0
            2017-01-03  0.208333  0.375000  0.416667   0.0

            dt          ticker
            2017-01-01  AAPL      0.417582
                        TLT       0.010989
                        XOM       0.571429
            2017-01-02  AAPL      0.202381
                        TLT       0.535714
                        XOM       0.261905

    factor_loadings : pd.DataFrame
        Factor loadings for all days in the date range, with date and ticker as
        index, and factors as columns.
        - Example:
                               momentum  reversal
            dt         ticker
            2017-01-01 AAPL   -1.592914  0.852830
                       TLT     0.184864  0.895534
                       XOM     0.993160  1.149353
            2017-01-02 AAPL   -0.140009 -0.524952
                       TLT    -1.066978  0.185435
                       XOM    -1.798401  0.761549

    stack_positions : bool
        Flag indicating whether `positions` should be converted to long format.

    pos_in_dollars : bool
        Flag indicating whether `positions` are in dollars or percentages
        If True, positions are in dollars.

    Returns
    -------
    risk_exposures_portfolio : pd.DataFrame
        df indexed by datetime, with factors as columns.
        - Example:
                        momentum  reversal
            dt
            2017-01-01 -0.238655  0.077123
            2017-01-02  0.821872  1.520515
    """
    if stack_positions:
        positions = _stack_positions(positions, pos_in_dollars=pos_in_dollars)

    return ep.compute_exposures(positions, factor_loadings)

def compute_exposures ( positions , factor_loadings , stack_positions = True , pos_in_dollars = True ) : if stack_positions : positions = _stack_positions ( positions , pos_in_dollars = pos_in_dollars ) return ep . compute_exposures ( positions , factor_loadings )


def create_perf_attrib_stats(perf_attrib, risk_exposures):
    """
    Takes perf attribution data over a period of time and computes annualized
    multifactor alpha, multifactor sharpe, risk exposures.
    """
    summary = OrderedDict()
    total_returns = perf_attrib['total_returns']
    specific_returns = perf_attrib['specific_returns']
    common_returns = perf_attrib['common_returns']

    summary['Annualized Specific Return'] =\
        ep.annual_return(specific_returns)
    summary['Annualized Common Return'] =\
        ep.annual_return(common_returns)
    summary['Annualized Total Return'] =\
        ep.annual_return(total_returns)

    summary['Specific Sharpe Ratio'] =\
        ep.sharpe_ratio(specific_returns)

    summary['Cumulative Specific Return'] =\
        ep.cum_returns_final(specific_returns)
    summary['Cumulative Common Return'] =\
        ep.cum_returns_final(common_returns)
    summary['Total Returns'] =\
        ep.cum_returns_final(total_returns)

    summary = pd.Series(summary, name='')

    annualized_returns_by_factor = [ep.annual_return(perf_attrib[c])
                                    for c in risk_exposures.columns]
    cumulative_returns_by_factor = [ep.cum_returns_final(perf_attrib[c])
                                    for c in risk_exposures.columns]

    risk_exposure_summary = pd.DataFrame(
        data=OrderedDict([
            (
                'Average Risk Factor Exposure',
                risk_exposures.mean(axis='rows')
            ),
            ('Annualized Return', annualized_returns_by_factor),
            ('Cumulative Return', cumulative_returns_by_factor),
        ]),
        index=risk_exposures.columns,
    )

    return summary, risk_exposure_summary

def
create_perf_attrib_stats
(
perf_attrib
,
risk_exposures
)
:
summary
=
OrderedDict
(
)
total_returns
=
perf_attrib
[
'total_returns'
]
specific_returns
=
perf_attrib
[
'specific_returns'
]
common_returns
=
perf_attrib
[
'common_returns'
]
summary
[
'Annualized Specific Return'
]
=
ep
.
annual_return
(
specific_returns
)
summary
[
'Annualized Common Return'
]
=
ep
.
annual_return
(
common_returns
)
summary
[
'Annualized Total Return'
]
=
ep
.
annual_return
(
total_returns
)
summary
[
'Specific Sharpe Ratio'
]
=
ep
.
sharpe_ratio
(
specific_returns
)
summary
[
'Cumulative Specific Return'
]
=
ep
.
cum_returns_final
(
specific_returns
)
summary
[
'Cumulative Common Return'
]
=
ep
.
cum_returns_final
(
common_returns
)
summary
[
'Total Returns'
]
=
ep
.
cum_returns_final
(
total_returns
)
summary
=
pd
.
Series
(
summary
,
name
=
''
)
annualized_returns_by_factor
=
[
ep
.
annual_return
(
perf_attrib
[
c
]
)
for
c
in
risk_exposures
.
columns
]
cumulative_returns_by_factor
=
[
ep
.
cum_returns_final
(
perf_attrib
[
c
]
)
for
c
in
risk_exposures
.
columns
]
risk_exposure_summary
=
pd
.
DataFrame
(
data
=
OrderedDict
(
[
(
'Average Risk Factor Exposure'
,
risk_exposures
.
mean
(
axis
=
'rows'
)
)
,
(
'Annualized Return'
,
annualized_returns_by_factor
)
,
(
'Cumulative Return'
,
cumulative_returns_by_factor
)
,
]
)
,
index
=
risk_exposures
.
columns
,
)
return
summary
,
risk_exposure_summary

Takes perf attribution data over a period of time and computes annualized multifactor alpha multifactor sharpe risk exposures .


def show_perf_attrib_stats(returns,
                           positions,
                           factor_returns,
                           factor_loadings,
                           transactions=None,
                           pos_in_dollars=True):
    """
    Calls `perf_attrib` using inputs, and displays outputs using
    `utils.print_table`.
    """
    risk_exposures, perf_attrib_data = perf_attrib(
        returns,
        positions,
        factor_returns,
        factor_loadings,
        transactions,
        pos_in_dollars=pos_in_dollars,
    )

    perf_attrib_stats, risk_exposure_stats =\
        create_perf_attrib_stats(perf_attrib_data, risk_exposures)

    percentage_formatter = '{:.2%}'.format
    float_formatter = '{:.2f}'.format

    summary_stats = perf_attrib_stats.loc[['Annualized Specific Return',
                                           'Annualized Common Return',
                                           'Annualized Total Return',
                                           'Specific Sharpe Ratio']]

    # Format return rows in summary stats table as percentages.
    for col_name in (
        'Annualized Specific Return',
        'Annualized Common Return',
        'Annualized Total Return',
    ):
        summary_stats[col_name] = percentage_formatter(summary_stats[col_name])

    # Display sharpe to two decimal places.
    summary_stats['Specific Sharpe Ratio'] = float_formatter(
        summary_stats['Specific Sharpe Ratio']
    )

    print_table(summary_stats, name='Summary Statistics')

    print_table(
        risk_exposure_stats,
        name='Exposures Summary',
        # In exposures table, format exposure column to 2 decimal places, and
        # return columns  as percentages.
        formatters={
            'Average Risk Factor Exposure': float_formatter,
            'Annualized Return': percentage_formatter,
            'Cumulative Return': percentage_formatter,
        },
    )

Calls perf_attrib using inputs and displays outputs using utils . print_table .


def plot_returns(perf_attrib_data, cost=None, ax=None):
    """
    Plot total, specific, and common returns.

    Parameters
    ----------
    perf_attrib_data : pd.DataFrame
        df with factors, common returns, and specific returns as columns,
        and datetimes as index. Assumes the `total_returns` column is NOT
        cost adjusted.
        - Example:
                        momentum  reversal  common_returns  specific_returns
            dt
            2017-01-01  0.249087  0.935925        1.185012          1.185012
            2017-01-02 -0.003194 -0.400786       -0.403980         -0.403980

    cost : pd.Series, optional
        if present, gets subtracted from `perf_attrib_data['total_returns']`,
        and gets plotted separately

    ax :  matplotlib.axes.Axes
        axes on which plots are made. if None, current axes will be used

    Returns
    -------
    ax :  matplotlib.axes.Axes
    """

    if ax is None:
        ax = plt.gca()

    returns = perf_attrib_data['total_returns']
    total_returns_label = 'Total returns'

    cumulative_returns_less_costs = _cumulative_returns_less_costs(
        returns,
        cost
    )
    if cost is not None:
        total_returns_label += ' (adjusted)'

    specific_returns = perf_attrib_data['specific_returns']
    common_returns = perf_attrib_data['common_returns']

    ax.plot(cumulative_returns_less_costs, color='b',
            label=total_returns_label)
    ax.plot(ep.cum_returns(specific_returns), color='g',
            label='Cumulative specific returns')
    ax.plot(ep.cum_returns(common_returns), color='r',
            label='Cumulative common returns')

    if cost is not None:
        ax.plot(-ep.cum_returns(cost), color='k',
                label='Cumulative cost spent')

    ax.set_title('Time series of cumulative returns')
    ax.set_ylabel('Returns')

    configure_legend(ax)

    return ax

def
plot_returns
(
perf_attrib_data
,
cost
=
,
ax
=
)
:
if
ax
is
:
ax
=
plt
.
gca
(
)
returns
=
perf_attrib_data
[
'total_returns'
]
total_returns_label
=
'Total returns'
cumulative_returns_less_costs
=
_cumulative_returns_less_costs
(
returns
,
cost
)
if
cost
is
not
:
total_returns_label
+=
' (adjusted)'
specific_returns
=
perf_attrib_data
[
'specific_returns'
]
common_returns
=
perf_attrib_data
[
'common_returns'
]
ax
.
plot
(
cumulative_returns_less_costs
,
color
=
'b'
,
label
=
total_returns_label
)
ax
.
plot
(
ep
.
cum_returns
(
specific_returns
)
,
color
=
'g'
,
label
=
'Cumulative specific returns'
)
ax
.
plot
(
ep
.
cum_returns
(
common_returns
)
,
color
=
'r'
,
label
=
'Cumulative common returns'
)
if
cost
is
not
:
ax
.
plot
(
ep
.
cum_returns
(
cost
)
,
color
=
'k'
,
label
=
'Cumulative cost spent'
)
ax
.
set_title
(
'Time series of cumulative returns'
)
ax
.
set_ylabel
(
'Returns'
)
configure_legend
(
ax
)
return
ax

Plot total specific and common returns .


def plot_factor_contribution_to_perf(
        perf_attrib_data,
        ax=None,
        title='Cumulative common returns attribution',
):
    """
    Plot each factor's contribution to performance.

    Parameters
    ----------
    perf_attrib_data : pd.DataFrame
        df with factors, common returns, and specific returns as columns,
        and datetimes as index
        - Example:
                        momentum  reversal  common_returns  specific_returns
            dt
            2017-01-01  0.249087  0.935925        1.185012          1.185012
            2017-01-02 -0.003194 -0.400786       -0.403980         -0.403980

    ax :  matplotlib.axes.Axes
        axes on which plots are made. if None, current axes will be used

    title : str, optional
        title of plot

    Returns
    -------
    ax :  matplotlib.axes.Axes
    """
    if ax is None:
        ax = plt.gca()

    factors_to_plot = perf_attrib_data.drop(
        ['total_returns', 'common_returns'], axis='columns', errors='ignore'
    )

    factors_cumulative = pd.DataFrame()
    for factor in factors_to_plot:
        factors_cumulative[factor] = ep.cum_returns(factors_to_plot[factor])

    for col in factors_cumulative:
        ax.plot(factors_cumulative[col])

    ax.axhline(0, color='k')
    configure_legend(ax, change_colors=True)

    ax.set_ylabel('Cumulative returns by factor')
    ax.set_title(title)

    return ax

def
plot_factor_contribution_to_perf
(
perf_attrib_data
,
ax
=
,
title
=
'Cumulative common returns attribution'
,
)
:
if
ax
is
:
ax
=
plt
.
gca
(
)
factors_to_plot
=
perf_attrib_data
.
drop
(
[
'total_returns'
,
'common_returns'
]
,
axis
=
'columns'
,
errors
=
'ignore'
)
factors_cumulative
=
pd
.
DataFrame
(
)
for
factor
in
factors_to_plot
:
factors_cumulative
[
factor
]
=
ep
.
cum_returns
(
factors_to_plot
[
factor
]
)
for
col
in
factors_cumulative
:
ax
.
plot
(
factors_cumulative
[
col
]
)
ax
.
axhline
(
0
,
color
=
'k'
)
configure_legend
(
ax
,
change_colors
=
True
)
ax
.
set_ylabel
(
'Cumulative returns by factor'
)
ax
.
set_title
(
title
)
return
ax

Plot each factor s contribution to performance .


def _stack_positions(positions, pos_in_dollars=True):
    """
    Convert positions to percentages if necessary, and change them
    to long format.

    Parameters
    ----------
    positions: pd.DataFrame
        Daily holdings (in dollars or percentages), indexed by date.
        Will be converted to percentages if positions are in dollars.
        Short positions show up as cash in the 'cash' column.

    pos_in_dollars : bool
        Flag indicating whether `positions` are in dollars or percentages
        If True, positions are in dollars.
    """
    if pos_in_dollars:
        # convert holdings to percentages
        positions = get_percent_alloc(positions)

    # remove cash after normalizing positions
    positions = positions.drop('cash', axis='columns')

    # convert positions to long format
    positions = positions.stack()
    positions.index = positions.index.set_names(['dt', 'ticker'])

    return positions

Convert positions to percentages if necessary and change them to long format .


def _cumulative_returns_less_costs(returns, costs):
    """
    Compute cumulative returns, less costs.
    """
    if costs is None:
        return ep.cum_returns(returns)
    return ep.cum_returns(returns - costs)

def _cumulative_returns_less_costs ( returns , costs ) : if costs is None : return ep . cum_returns ( returns ) return ep . cum_returns ( returns - costs )


def format_asset(asset):
    """
    If zipline asset objects are used, we want to print them out prettily
    within the tear sheet. This function should only be applied directly
    before displaying.
    """

    try:
        import zipline.assets
    except ImportError:
        return asset

    if isinstance(asset, zipline.assets.Asset):
        return asset.symbol
    else:
        return asset

def format_asset ( asset ) : try : import zipline . assets except ImportError : return asset if isinstance ( asset , zipline . assets . Asset ) : return asset . symbol else : return asset

If zipline asset objects are used we want to print them out prettily within the tear sheet . This function should only be applied directly before displaying .


def vectorize(func):
    """
    Decorator so that functions can be written to work on Series but
    may still be called with DataFrames.
    """

    def wrapper(df, *args, **kwargs):
        if df.ndim == 1:
            return func(df, *args, **kwargs)
        elif df.ndim == 2:
            return df.apply(func, *args, **kwargs)

    return wrapper

def vectorize ( func ) : def wrapper ( df , * args , * * kwargs ) : if df . ndim == 1 : return func ( df , * args , * * kwargs ) elif df . ndim == 2 : return df . apply ( func , * args , * * kwargs ) return wrapper

Decorator so that functions can be written to work on Series but may still be called with DataFrames .


def print_table(table,
                name=None,
                float_format=None,
                formatters=None,
                header_rows=None):
    """
    Pretty print a pandas DataFrame.

    Uses HTML output if running inside Jupyter Notebook, otherwise
    formatted text output.

    Parameters
    ----------
    table : pandas.Series or pandas.DataFrame
        Table to pretty-print.
    name : str, optional
        Table name to display in upper left corner.
    float_format : function, optional
        Formatter to use for displaying table elements, passed as the
        `float_format` arg to pd.Dataframe.to_html.
        E.g. `'{0:.2%}'.format` for displaying 100 as '100.00%'.
    formatters : list or dict, optional
        Formatters to use by column, passed as the `formatters` arg to
        pd.Dataframe.to_html.
    header_rows : dict, optional
        Extra rows to display at the top of the table.
    """

    if isinstance(table, pd.Series):
        table = pd.DataFrame(table)

    if name is not None:
        table.columns.name = name

    html = table.to_html(float_format=float_format, formatters=formatters)

    if header_rows is not None:
        # Count the number of columns for the text to span
        n_cols = html.split('
')[1].split('
')[0].count('')

        # Generate the HTML for the extra rows
        rows = ''
        for name, value in header_rows.items():
            rows += ('\n
%s	' +
                     '%s
') % (name, n_cols, value)

        # Inject the new HTML
        html = html.replace('
', '
' + rows)

    display(HTML(html))


def detect_intraday(positions, transactions, threshold=0.25):
    """
    Attempt to detect an intraday strategy. Get the number of
    positions held at the end of the day, and divide that by the
    number of unique stocks transacted every day. If the average quotient
    is below a threshold, then an intraday strategy is detected.

    Parameters
    ----------
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.

    Returns
    -------
    boolean
        True if an intraday strategy is detected.
    """

    daily_txn = transactions.copy()
    daily_txn.index = daily_txn.index.date
    txn_count = daily_txn.groupby(level=0).symbol.nunique().sum()
    daily_pos = positions.drop('cash', axis=1).replace(0, np.nan)
    return daily_pos.count(axis=1).sum() / txn_count < threshold

def detect_intraday ( positions , transactions , threshold = 0.25 ) : daily_txn = transactions . copy ( ) daily_txn . index = daily_txn . index . date txn_count = daily_txn . groupby ( level = 0 ) . symbol . nunique ( ) . sum ( ) daily_pos = positions . drop ( 'cash' , axis = 1 ) . replace ( 0 , np . nan ) return daily_pos . count ( axis = 1 ) . sum ( ) / txn_count < threshold

Attempt to detect an intraday strategy . Get the number of positions held at the end of the day and divide that by the number of unique stocks transacted every day . If the average quotient is below a threshold then an intraday strategy is detected .


def check_intraday(estimate, returns, positions, transactions):
    """
    Logic for checking if a strategy is intraday and processing it.

    Parameters
    ----------
    estimate: boolean or str, optional
        Approximate returns for intraday strategies.
        See description in tears.create_full_tear_sheet.
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.

    Returns
    -------
    pd.DataFrame
        Daily net position values, adjusted for intraday movement.
    """

    if estimate == 'infer':
        if positions is not None and transactions is not None:
            if detect_intraday(positions, transactions):
                warnings.warn('Detected intraday strategy; inferring positi' +
                              'ons from transactions. Set estimate_intraday' +
                              '=False to disable.')
                return estimate_intraday(returns, positions, transactions)
            else:
                return positions
        else:
            return positions

    elif estimate:
        if positions is not None and transactions is not None:
            return estimate_intraday(returns, positions, transactions)
        else:
            raise ValueError('Positions and txns needed to estimate intraday')
    else:
        return positions

Logic for checking if a strategy is intraday and processing it .


def estimate_intraday(returns, positions, transactions, EOD_hour=23):
    """
    Intraday strategies will often not hold positions at the day end.
    This attempts to find the point in the day that best represents
    the activity of the strategy on that day, and effectively resamples
    the end-of-day positions with the positions at this point of day.
    The point of day is found by detecting when our exposure in the
    market is at its maximum point. Note that this is an estimate.

    Parameters
    ----------
    returns : pd.Series
        Daily returns of the strategy, noncumulative.
         - See full explanation in create_full_tear_sheet.
    positions : pd.DataFrame
        Daily net position values.
         - See full explanation in create_full_tear_sheet.
    transactions : pd.DataFrame
        Prices and amounts of executed trades. One row per trade.
         - See full explanation in create_full_tear_sheet.

    Returns
    -------
    pd.DataFrame
        Daily net position values, resampled for intraday behavior.
    """

    # Construct DataFrame of transaction amounts
    txn_val = transactions.copy()
    txn_val.index.names = ['date']
    txn_val['value'] = txn_val.amount * txn_val.price
    txn_val = txn_val.reset_index().pivot_table(
        index='date', values='value',
        columns='symbol').replace(np.nan, 0)

    # Cumulate transaction amounts each day
    txn_val['date'] = txn_val.index.date
    txn_val = txn_val.groupby('date').cumsum()

    # Calculate exposure, then take peak of exposure every day
    txn_val['exposure'] = txn_val.abs().sum(axis=1)
    condition = (txn_val['exposure'] == txn_val.groupby(
        pd.TimeGrouper('24H'))['exposure'].transform(max))
    txn_val = txn_val[condition].drop('exposure', axis=1)

    # Compute cash delta
    txn_val['cash'] = -txn_val.sum(axis=1)

    # Shift EOD positions to positions at start of next trading day
    positions_shifted = positions.copy().shift(1).fillna(0)
    starting_capital = positions.iloc[0].sum() / (1 + returns[0])
    positions_shifted.cash[0] = starting_capital

    # Format and add start positions to intraday position changes
    txn_val.index = txn_val.index.normalize()
    corrected_positions = positions_shifted.add(txn_val, fill_value=0)
    corrected_positions.index.name = 'period_close'
    corrected_positions.columns.name = 'sid'

    return corrected_positions

Intraday strategies will often not hold positions at the day end . This attempts to find the point in the day that best represents the activity of the strategy on that day and effectively resamples the end - of - day positions with the positions at this point of day . The point of day is found by detecting when our exposure in the market is at its maximum point . Note that this is an estimate .