macrosynergy.pnl.naive_pnl#

“Naive” profit-and-loss (PnL) calculations with basic signal options, disregarding transaction costs.

class NaivePnL(df, ret, sigs, cids=None, bms=None, start=None, end=None, blacklist=None)[source]#

Bases: object

Computes and collects illustrative PnLs with limited signal options and disregarding transaction costs.

Parameters:
  • df (Dataframe) – standardized DataFrame with the following necessary columns: ‘cid’, ‘xcat’, ‘real_date’ and ‘value’.

  • ret (str) – return category.

  • sigs (List[str]) – signal categories. Able to pass in multiple possible signals to the Class’ constructor and their respective vintages will be held on the instance’s DataFrame. The signals can subsequently be referenced through the self.make_pnl() method which receives a single signal per call.

  • cids (List[str]) – cross sections that are traded. Default is all in the dataframe.

  • bms (str, List[str]) – list of benchmark tickers for which correlations are displayed against PnL strategies.

  • start (str) – earliest date in ISO format. Default is None and earliest date in df is used.

  • end (str) – latest date in ISO format. Default is None and latest date in df is used.

  • blacklist (dict) – cross-sections with date ranges that should be excluded from the dataframe.

make_pnl(sig, sig_op='zn_score_pan', sig_add=0, sig_mult=1, sig_neg=False, pnl_name=None, rebal_freq='daily', rebal_slip=0, vol_scale=None, leverage=1.0, min_obs=261, iis=True, sequential=True, neutral='zero', thresh=None, entry_barrier=None, exit_barrier=None, winsorize_first=False, normalized_weights=False)[source]#

Calculate daily PnL and add to class instance.

Parameters:
  • sig (str) – name of raw signal that is basis for positioning. The signal is assumed to be recorded at the end of the day prior to position taking.

  • sig_op (str) – signal transformation options; must be one of ‘zn_score_pan’, ‘zn_score_cs’, ‘binary’, or ‘raw’. The default is ‘zn_score_pan’. See notes below for further details.

  • sig_add (float) – add a constant to the signal after initial transformation. This allows to give PnLs a long or short bias relative to the signal score. Default is 0.

  • sig_mult (float) – multiply a constant to the signal after initial tranformation and after sig_add has been added too. Default is 1.

  • sig_neg (str) – if True the PnL is based on the negative value of the transformed signal. Default is False.

  • pnl_name (str) – name of the PnL to be generated and stored. Default is None, i.e. a default name is given. The default name will be: ‘PNL_<signal name>[_<NEG>]’, with the last part added if sig_neg has been set to True. Previously calculated PnLs of the same name will be overwritten. This means that if a set of PnLs are to be compared, each PnL requires a distinct name.

  • rebal_freq (str) – re-balancing frequency for positions according to signal must be one of ‘daily’ (default), ‘weekly’, ‘monthly’, ‘quarterly’, or ‘annual’. The re-balancing is only concerned with the signal value on the re-balancing date which is delimited by the frequency chosen. Additionally, the re-balancing frequency will be applied to make_zn_scores() if used as the method to produce the raw signals.

  • rebal_slip (str) – re-balancing slippage in days. Default is 1 which means that it takes one day to re-balance the position and that the new positions produce PnL from the second day after the signal has been recorded.

  • vol_scale (bool) – ex-post scaling of PnL to annualized volatility given. This is for comparative visualization and not out-of-sample. Default is none.

  • leverage (float) – leverage applied to the raw signal when a vol_scale is not defined. Default is 1.0, i.e., position size is 1 or 100% of implied risk capital.

  • min_obs (int) – the minimum number of observations required to calculate zn_scores. Default is 252.

  • iis (bool) – if True (default) zn-scores are also calculated for the initial sample period defined by min_obs, on an in-sample basis, to avoid losing history.

  • sequential (bool) – if True (default) score parameters (neutral level and standard deviations) are estimated sequentially with concurrently available information only.

  • neutral (str) – method to determine neutral level. Default is ‘zero’. Alternatives are ‘mean’ and “median”.

  • thresh (float) – threshold value beyond which scores are winsorized, i.e. contained at that threshold. Therefore, the threshold is the maximum absolute score value that the function is allowed to produce. The minimum threshold is one standard deviation. Default is no threshold.

  • entry_barrier (float) – Threshold in terms of absolute signal value to enter a position in a binary strategy. This prevents binary strategies from excessive position flipping. Default is None, i.e., the binary strategy always takes its full position, no matter how small the signal value.

  • exit_barrier (float) – Threshold in terms of absolute signal value to exit a position in a binary strategy. In conjunction with an entry barrier, this determines the probability of position liquidations when the sign of the signal does not change. The value must be below the entry barrier. Without an entry barrier there can be no exit barrier. Default is None, which means that a position is only liquidated of the sign of the signal flips.

  • winsorize_first (bool) – if True, the signal is winsorized before any signal manipulation is applied. This means that the signal is clipped to a specified range before any further processing is done. Default is False, meaning that the signal is winsorized after all other transformations have been applied.

  • normalized_weights (bool) – if True, the PnL is computed using normalized weights, meaning that the PnL is computed as the mean of the signal-adjusted returns across all cross-sections. Default is False, meaning that the PnL is computed as the sum of the signal-adjusted returns across all cross-sections.

Notes

When sig_op = “zn_score_pan”, raw signals are transformed into zn-scores around a neutral value where pooled panel statistics are calculated.

When sig_op = “zn_score_cs”, raw signals are transformed into zn-scores around a neutral value where statistics are calculated by cross section alone.

When sig_op = “binary”, transforms signals into uniform long/shorts (1/-1) across all cross sections.

When sig_op = “raw”, no transformation is applied to the signal.

Entry and exit barriers are only applicable when the signal operation is binary.

See also

macrosynergy.panel.make_zn_scores

compute zn-scores for a panel.

make_long_pnl(vol_scale=None, label=None, leverage=1.0, normalized_weights=False)[source]#

Computes long-only returns which may act as a basis for comparison against the signal-adjusted returns. Will take a long-only position in the category passed to the parameter ‘self.ret’.

Parameters:
  • vol_scale (bool) – ex-post scaling of PnL to annualized volatility given. This is for comparative visualization and not out-of-sample, and is applied to the long-only position. Default is None.

  • label (str) – associated label that will be mapped to the long-only DataFrame. The label will be used in the plotting graphic for plot_pnls(). If a label is not defined, the default will be the name of the return category.

  • leverage (float) – leverage applied to the raw signal when a vol_scale is not defined. Default is 1.0, i.e., position size is 1 or 100% of implied risk capital.

add_bm(df, bms, tickers)[source]#

Returns a dictionary with benchmark return series.

Parameters:
  • df (DataFrame) – aggregate DataFrame passed into the Class.

  • bms (List[str]) – benchmark return tickers.

  • tickers (List[str]) – the available tickers held in the reduced DataFrame. The reduced DataFrame consists exclusively of the signal & return categories.

static rebalancing(dfw, rebal_freq='daily', rebal_slip=0)[source]#

The signals are calculated daily and for each individual cross-section defined in the panel. However, re-balancing a position can occur more infrequently than daily. Therefore, produce the re-balancing values according to the more infrequent timeline (weekly or monthly).

Parameters:
  • dfw (Dataframe) – DataFrame with each category represented by a column and the daily signal is also included with the column name ‘psig’.

  • rebal_freq (str) – re-balancing frequency for positions according to signal must be one of ‘daily’ (default), ‘weekly’, ‘monthly’, ‘quarterly’, or ‘annual’.

  • rebal_slip (str) – re-balancing slippage in days.

Returns:

will return a pd.Series containing the associated signals according to the re-balancing frequency.

Return type:

Series

static long_only_pnl(dfw, vol_scale=None, label=None, leverage=1.0, normalized_weights=False)[source]#

Method used to compute the PnL accrued from simply taking a long-only position in the category, ‘self.ret’. The returns from the category are not predicated on any exogenous signal.

Parameters:
  • dfw (DataFrame) –

  • vol_scale (bool) – ex-post scaling of PnL to annualized volatility given. This is for comparative visualization and not out-of-sample. Default is none.

  • label (str) – associated label that will be mapped to the long-only DataFrame.

  • leverage (float) – leverage applied to the raw signal when a vol_scale is not defined. Default is 1.0, i.e., position size is 1 or 100% of implied risk capital.

Returns:

standardised dataframe containing exclusively the return category, and the long-only panel return.

Return type:

DataFrame

plot_pnls(pnl_cats=None, pnl_cids=['ALL'], start=None, end=None, compounding=False, facet=False, ncol=3, same_y=True, title='Cumulative Naive PnL', title_fontsize=20, tick_fontsize=12, xcat_labels=None, xlab='', ylab='% of risk capital', label_fontsize=12, share_axis_labels=True, figsize=(12, 7), aspect=1.7, height=3, label_adj=0.05, title_adj=0.95, y_label_adj=0.95, legend_fontsize=None, return_fig=False)[source]#

Plot line chart of cumulative PnLs, single PnL, multiple PnL types per cross section, or multiple cross sections per PnL type.

Parameters:
  • pnl_cats (List[str]) – list of PnL categories that should be plotted.

  • pnl_cids (List[str]) – list of cross sections to be plotted; default is ‘ALL’ (global PnL). Note: one can only have multiple PnL categories or multiple cross sections, not both.

  • start (str) – earliest date in ISO format. Default is None and earliest date in df is used.

  • end (str) – latest date in ISO format. Default is None and latest date in df is used.

  • compounding (bool) – parameter to control whether the PnLs are compounded daily. Default is False.

  • facet (bool) – parameter to control whether each PnL series is plotted on its own respective grid using Seaborn’s FacetGrid. Default is False and all series will be plotted in the same graph.

  • ncol (int) – number of columns in facet grid. Default is 3. If the total number of PnLs is less than ncol, the number of columns will be adjusted on runtime.

  • same_y (bool) – if True (default) all plots in facet grid share same y axis.

  • title (str) – allows entering text for a custom chart header.

  • title_fontsize (int) – font size for the title. Default is 20.

  • xcat_labels (List[str]) – custom labels to be used for the PnLs.

  • xlab (str) – label for x-axis of the plot (or subplots if faceted), default is None (empty string)..

  • ylab (str) – label for y-axis of the plot (or subplots if faceted), default is ‘% of risk capital’ with a note on compounding.

  • share_axis_labels (bool) – if True (default) the axis labels are shared by all subplots in the facet grid.

  • figsize (tuple) – tuple of plot width and height. Default is (12 , 7).

  • aspect (float) – width-height ratio for plots in facet. Default is 1.7.

  • height (float) – height of plots in facet. Default is 3.

  • label_adj (float) – parameter that sets bottom of figure to fit the label. Default is 0.05.

  • title_adj (float) – parameter that sets top of figure to accommodate title. Default is 0.95.

  • y_label_adj (float) – parameter that sets left of figure to fit the y-label.

Return type:

None

get_input_signals()[source]#

Returns a DataFrame containing the input signals (post any filtering).

Returns:

DataFrame containing the input signals (post any filtering).

Return type:

DataFrame

get_generated_signals()[source]#

Returns a DataFrame containing signals generated by NaivePNL to produce the PnLs.

Returns:

DataFrame containing the generated signals.

Return type:

DataFrame

get_asset_returns_data()[source]#

Returns a DataFrame containing the returns used to generate the PnLs.

Returns:

DataFrame containing the returns used to generate the PnLs.

Return type:

DataFrame

get_pnls_returns_data()[source]#

Returns a DataFrame containing the PnLs generated.

Returns:

DataFrame containing the PnLs generated.

Return type:

DataFrame

signal_heatmap(pnl_name, pnl_cids=None, start=None, end=None, freq='M', title='Average applied signal values', title_fontsize=None, x_label='', y_label='', figsize=None, tick_fontsize=None, return_fig=False)[source]#

Display heatmap of signals across times and cross-sections.

Parameters:
  • pnl_name (str) – name of naive PnL whose signals are displayed.

  • pnl_cids (List[str]) – cross-sections. Default is all available.

  • start (str) – earliest date in ISO format. Default is None and earliest date in df is used.

  • end (str) – latest date in ISO format. Default is None and latest date in df is used.

  • freq (str) – frequency for which signal average is displayed. Default is monthly (‘m’). The only alternative is quarterly (‘q’).

  • title (str) – allows entering text for a custom chart header.

  • title_fontsize (int) – font size of the title. Default is None, uses matplotlib default.

  • x_label (str) – label for the x-axis. Default is None.

  • y_label (str) – label for the y-axis. Default is None.

  • figsize ((float, float)) – width and height in inches. Default is (14, number of cross sections).

  • tick_fontsize (int) – font size for the ticks. Default is None.

Note

Signal is here is the value that actually determines the concurrent PnL.

agg_signal_bars(pnl_name, freq='m', metric='direction', title=None, title_fontsize=None, y_label='Sum of Std. across the Panel', return_fig=False)[source]#

Display aggregate signal strength and - potentially - direction.

Parameters:
  • pnl_name (str) – name of the PnL whose signal is to be visualized.

  • freq (str) – frequency at which the signal is visualized. Default is monthly (‘m’). The alternative is quarterly (‘q’).

  • metric (str) – the type of signal value. Default is “direction”. Alternative is “strength”.

  • title (str) – allows entering text for a custom chart header. Default will be “Directional Bar Chart of <pnl_name>.”.

  • title_fontsize (int) – font size of the title. Default is None, uses matplotlib default.

  • y_label (str) – label for the y-axis. Default is the sum of standard deviations across the panel corresponding to the default signal transformation: ‘zn_score_pan’.

Note

The referenced signal corresponds to the series that determines the concurrent PnL.

evaluate_pnls(pnl_cats=None, pnl_cids=['ALL'], start=None, end=None, label_dict=None, sr_thresholds=None)[source]#
Returns a table of PnL statistics containing the following metrics:
  • Return - percentage, annualized

  • Standard Deviation - percentage, annualized

  • Sharpe Ratio

  • Sortino Ratio

  • Max 21-Day Draw - percentage

  • Max 6-Month Draw - percentage

  • Peak to Trough Draw - percentage

  • Top 5% Monthly PnL Share

  • Sharpe Stability Ratio - HAC-robust t-stat for the mean rolling Sharpe ratio (see sharpe_stability_ratio); accounts for sample size and serial dependence

  • Max Draw Recovery (months) - time from the peak preceding the worst drawdown to its full recovery, in 21-trading-day months; 0 if there was never a drawdown, or Traded Months if the worst drawdown hasn’t recovered by the end of the sample

  • Prob. Sharpe Ratio > {threshold} - one-sided asymptotic probability that the mean rolling Sharpe ratio is above each threshold in sr_thresholds, if provided

  • Traded Months

Parameters:
  • pnl_cats (List[str], optional) – list of PnL categories that should be plotted. Default is None and all available PnL categories are used.

  • pnl_cids (List[str]) – list of cross-sections to be plotted; default is ‘ALL’ (global PnL). Note: one can only have multiple PnL categories or multiple cross-sections, not both.

  • start (str) – earliest date in ISO format. Default is None and earliest date in df is used.

  • end (str) – latest date in ISO format. Default is None and latest date in df is used.

  • label_dict (dict[str, str]) – dictionary with keys as pnl_cats and values as new labels for the PnLs.

  • sr_thresholds (List[float], optional) – Sharpe ratio thresholds for which one-sided probabilities are reported. Default is None, in which case [0.25, 0.5, 0.75] is used. Pass an empty list to suppress the threshold probability rows.

Returns:

standardized DataFrame with key PnL performance statistics.

Return type:

DataFrame

evaluate_pnls_html(headline, bench=None, headline_label=None, bench_label=None, groups=None, order=None, row_labels=None, pnl_cids=['ALL'], start=None, end=None, sr_thresholds=None, title='Naive PnL statistics', subtitle='10% vol target | daily rebalance | gross of costs', custom_css=None, footnotes=True)[source]#

Presentation-ready HTML rendering of the evaluate_pnls() statistics table - a grouped, ledger-style layout for reports and notebooks, or for saving to an HTML file to open in Word or embed in a web page. The statistics shown are those computed by evaluate_pnls(), selected and grouped for presentation.

Parameters:
  • headline (str or List[str]) – PnL categor(y/ies) to show as the (non-benchmark) column(s), in display order.

  • bench (str or List[str], optional) – PnL categor(y/ies) to show as benchmark column(s), styled distinctly (muted) from the headline column(s), in display order. Default is None, in which case the benchmark ticker(s) passed as bms to the constructor are used - the same benchmark(s) shown in the correlation row(s). Raises ValueError if omitted when no bms were configured.

  • headline_label (str or List[str], optional) – Column header labels. Default is the category names themselves. If bench is a list, bench_label must be a matching list (or omitted).

  • bench_label (str or List[str], optional) – Column header labels. Default is the category names themselves. If bench is a list, bench_label must be a matching list (or omitted).

  • groups (dict of {section title: [metric names]}, optional) – Section headings and the metrics listed under each, in order, e.g. {"Performance": ["Return %", "Sharpe Ratio"]}. Default is None, in which case a standard set of performance, risk & drawdown, and robustness metrics is used. Raises KeyError if a requested metric is not one of the rows produced by evaluate_pnls().

  • order (dict of {section title: [metric names in desired order]}, optional) – Override just the row order within a section from groups, without redeclaring which metrics belong to it.

  • row_labels (dict of {row name: display label or [labels]}, optional) –

    Rename individual rows for display, without changing which rows are selected by groups/order. Keys are matched against the row names produced by evaluate_pnls(), e.g. {"St. Dev. %": "Vol %"}.

    The special key "Benchmark correlation" relabels the benchmark-correlation row(s) - named after the benchmark ticker, e.g. "USD_DUXR correl" - without having to spell the ticker(s) out:

    • a single label, e.g. {"Benchmark correlation": "Benchmark correlation"}, is applied to the row when one benchmark is configured. With several benchmarks, the label is suffixed with a number (“Benchmark correlation 1”, “… 2”, …) so each row keeps a distinct name.

    • a list of labels, one per benchmark in the order bms was passed to the constructor, e.g. {"Benchmark correlation": ["5Y", "2Y"]}. Must match the number of benchmarks exactly.

  • pnl_cids (see evaluate_pnls().) –

  • start (see evaluate_pnls().) –

  • end (see evaluate_pnls().) –

  • sr_thresholds (see evaluate_pnls().) –

  • title (str) – Header text shown above the table. subtitle defaults to “10% vol target | daily rebalance | gross of costs” - override it if this instance’s PnLs don’t share that configuration (e.g. a different vol_scale or rebal_freq in make_pnl()).

  • subtitle (str) – Header text shown above the table. subtitle defaults to “10% vol target | daily rebalance | gross of costs” - override it if this instance’s PnLs don’t share that configuration (e.g. a different vol_scale or rebal_freq in make_pnl()).

  • custom_css (str, optional) – Extra CSS appended after the table’s own stylesheet to customize its appearance. Because it comes last, a rule targeting one of the table’s classes overrides the default without needing !important, e.g. custom_css=".pnl-title { color: #7a1f1f; }". See pnl_table_html for the classes that can be targeted.

  • footnotes (bool, default True) – Whether to render the explanatory footnotes below the table (see Notes). Set to False to suppress them - the underlying “+” prefix on cells is unaffected, only the footnote text is toggled.

Returns:

The formatted table. Displays inline when returned from a notebook cell; its .data attribute holds the raw HTML string, e.g. to save with Path("table.html").write_text(...) for use in Word or on a web page.

Return type:

HTMLTable

Notes

A “Max Draw Recovery (months)” figure is prefixed with “+” wherever the worst drawdown hadn’t recovered by the end of the sample - it reads as “at least this many months and still running”, not a completed recovery time. “Top 5% Monthly PnL Share” is top-5%-months PnL divided by total PnL, so it reads negative whenever total PnL is negative. Both get an explanatory footnote below the table whenever they occur (subject to footnotes).

print_pnl_names()[source]#

Print list of names of available PnLs in the class instance.

pnl_df(pnl_names=None, cs=False)[source]#

Return dataframe with PnLs.

Parameters:
  • pnl_names (List[str]) – list of names of PnLs to be returned. Default is ‘ALL’.

  • cs (bool) – inclusion of cross section PnLs. Default is False.

Returns:

custom DataFrame with PnLs

Return type:

DataFrame

create_results_dataframe(title, pnl, sigs_renamed=None, **srr_kwargs)[source]#

Create a DataFrame with key performance metrics for signals and PnLs from a precomputed NaivePnL object.

Parameters:
  • title (str) – Title of the DataFrame.

  • pnl (NaivePnL) – NaivePnL object with pnl.make_pnl() already applied.

  • bm (str, optional) – Benchmark name for correlation.

  • sigs_renamed (dict, optional) – Dictionary for renaming signals in the output.

Returns:

DataFrame with performance metrics.

Return type:

DataFrame or Styler

class PnLParams(signal, sig_op, sig_neg, sig_add, pnl_name, rebal_freq, rebal_slip, vol_scale, neutral, thresh)[source]#

Bases: object

Dataclass to store the parameters for the PnL creation.

signal: str#
sig_op: str#
sig_neg: bool#
sig_add: float#
pnl_name: str#
rebal_freq: str#
rebal_slip: int#
vol_scale: int#
neutral: str#
thresh: float#