示例#1
0
    def test_get_aggregate_returns_with_simple_returns(self):
        test_returns = [1, 1, 1, 1]
        dates = DatetimeIndex(
            ['2015-12-01', '2016-05-05', '2016-10-01', '2017-01-05'])
        simple_returns_series = SimpleReturnsSeries(data=test_returns,
                                                    index=dates)

        expected_cumulative_returns = [1.0, 3.0, 1.0]
        expected_result = SimpleReturnsSeries(
            data=expected_cumulative_returns,
            index=DatetimeIndex(['2015-12-31', '2016-12-31', '2017-12-31']))
        actual_result = get_aggregate_returns(simple_returns_series,
                                              convert_to=Frequency.YEARLY)
        assert_series_equal(expected_result, actual_result)

        expected_result = SimpleReturnsSeries(data=[1, 1, 1, 1],
                                              index=DatetimeIndex([
                                                  '2015-12-31', '2016-05-31',
                                                  '2016-10-31', '2017-01-31'
                                              ]))
        actual_result = get_aggregate_returns(simple_returns_series,
                                              convert_to=Frequency.MONTHLY)
        assert_series_equal(expected_result, actual_result)

        actual_result = get_aggregate_returns(simple_returns_series,
                                              convert_to=Frequency.MONTHLY,
                                              multi_index=True)
        actual_result = actual_result.unstack()
        self.assertEqual(actual_result[1].values[2], 1.0)
        self.assertEqual(actual_result[5].values[1], 1.0)
        self.assertEqual(actual_result[10].values[1], 1.0)
        self.assertEqual(actual_result[12].values[0], 1.0)
示例#2
0
    def test_get_aggregate_returns_with_log_returns(self):
        test_returns = [0.01] * 22
        dates = DatetimeIndex([
            '2000-01-15', '2000-01-17', '2000-01-19', '2000-01-21',
            '2000-01-23', '2000-01-25', '2000-01-27', '2000-01-29',
            '2000-01-31', '2000-02-02', '2000-02-04', '2000-03-05',
            '2000-04-04', '2000-05-04', '2000-06-03', '2000-07-03',
            '2001-01-19', '2001-08-07', '2002-02-23', '2002-09-11',
            '2003-03-30', '2003-10-16'
        ])
        log_returns_series = LogReturnsSeries(data=test_returns, index=dates)

        expected_result = log_returns_series.to_simple_returns()
        actual_result = get_aggregate_returns(log_returns_series,
                                              convert_to=Frequency.DAILY)
        assert_series_equal(expected_result, actual_result)

        expected_cumulative_returns = \
            [0.01, 0.04, 0.03, 0.03, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
        expected_dates = DatetimeIndex([
            '2000-01-14', '2000-01-21', '2000-01-28', '2000-02-04',
            '2000-03-03', '2000-04-07', '2000-05-05', '2000-06-02',
            '2000-07-07', '2001-01-19', '2001-08-10', '2002-02-22',
            '2002-09-13', '2003-03-28', '2003-10-17'
        ])
        expected_result = LogReturnsSeries(
            data=expected_cumulative_returns,
            index=expected_dates).to_simple_returns()
        actual_result = get_aggregate_returns(log_returns_series,
                                              convert_to=Frequency.WEEKLY)
        assert_series_equal(expected_result, actual_result)

        expected_cumulative_returns = \
            [0.09, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
        expected_dates = DatetimeIndex([
            '2000-01-31', '2000-02-29', '2000-03-31', '2000-04-30',
            '2000-05-31', '2000-06-30', '2000-07-31', '2001-01-31',
            '2001-08-31', '2002-02-28', '2002-09-30', '2003-03-31',
            '2003-10-31'
        ])
        expected_result = LogReturnsSeries(
            data=expected_cumulative_returns,
            index=expected_dates).to_simple_returns()
        actual_result = get_aggregate_returns(log_returns_series,
                                              convert_to=Frequency.MONTHLY)
        assert_series_equal(expected_result, actual_result)

        expected_cumulative_returns = [0.16, 0.02, 0.02, 0.02]
        expected_dates = DatetimeIndex(
            ['2000-12-31', '2001-12-31', '2002-12-31', '2003-12-31'])
        expected_result = LogReturnsSeries(
            data=expected_cumulative_returns,
            index=expected_dates).to_simple_returns()
        actual_result = get_aggregate_returns(log_returns_series,
                                              convert_to=Frequency.YEARLY)
        assert_series_equal(expected_result, actual_result)
示例#3
0
def _get_simple_quantile_chart(simple_returns):
    if len(simple_returns) > 0:
        simple_returns_weekly = get_aggregate_returns(simple_returns, Frequency.WEEKLY, multi_index=True)
        simple_returns_monthly = get_aggregate_returns(simple_returns, Frequency.MONTHLY, multi_index=True)

        chart = BoxplotChart([simple_returns, simple_returns_weekly, simple_returns_monthly], linewidth=1)
    else:
        chart = BoxplotChart([QFSeries(), QFSeries(), QFSeries()], linewidth=1)

    tick_decorator = AxisTickLabelsDecorator(labels=["daily", "weekly", "monthly"], axis=Axis.X)
    return chart, tick_decorator
示例#4
0
def create_return_quantiles(returns: QFSeries, live_start_date: datetime = None, x_axis_labels_rotation: int = 20) \
        -> BoxplotChart:
    """
    Creates a new return quantiles boxplot chart based on the returns specified.

    A swarm plot is also rendered on the chart if the ``live_start_date`` is specified.

    Parameters
    ----------
    returns
        The returns series to plot on the chart.
    live_start_date
        The live start date that will determine whether a swarm plot should be rendered.
    x_axis_labels_rotation

    Returns
    -------
    A new ``BoxplotChart`` instance.
    """

    simple_returns = returns.to_simple_returns()

    # case when we can plot IS together with OOS
    if live_start_date is not None:
        oos_returns = simple_returns.loc[simple_returns.index >= live_start_date]
        if len(oos_returns) > 0:
            in_sample_returns = simple_returns.loc[simple_returns.index < live_start_date]
            in_sample_weekly = get_aggregate_returns(in_sample_returns, Frequency.WEEKLY, multi_index=True)
            in_sample_monthly = get_aggregate_returns(in_sample_returns, Frequency.MONTHLY, multi_index=True)

            oos_weekly = get_aggregate_returns(oos_returns, Frequency.WEEKLY, multi_index=True)
            oos_monthly = get_aggregate_returns(oos_returns, Frequency.MONTHLY, multi_index=True)

            chart = BoxplotChart([in_sample_returns, oos_returns, in_sample_weekly,
                                  oos_weekly, in_sample_monthly, oos_monthly], linewidth=1)

            x_labels = ["daily IS", "daily OOS", "weekly IS", "weekly OOS", "monthly IS", "monthly OOS"]
            tick_decorator = AxisTickLabelsDecorator(labels=x_labels, axis=Axis.X, rotation=x_axis_labels_rotation)
        else:
            chart, tick_decorator = _get_simple_quantile_chart(simple_returns)

    else:  # case where there is only one set of data
        chart, tick_decorator = _get_simple_quantile_chart(simple_returns)

    # fixed_format_decorator = AxesFormatterDecorator(x_major=fixed_formatter)
    chart.add_decorator(tick_decorator)

    # Set title.
    title = TitleDecorator("Return Quantiles")
    chart.add_decorator(title)
    chart.add_decorator(AxesLabelDecorator(y_label="Returns"))
    return chart
示例#5
0
    def plot(self, figsize: Tuple[float, float] = None):
        self._setup_axes_if_necessary(figsize)

        ret_table = get_aggregate_returns(self._returns, Frequency.MONTHLY, multi_index=True).unstack().round(3)
        ret_table = ret_table.sort_index(ascending=False, axis=0)  # show most recent year at the top
        ret_table = ret_table.fillna(0) * 100
        ret_table.rename(columns={1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
                                  7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec'}, inplace=True)

        # Format Y axis to make sure we have a tick for each year or 2 years if there is more that 10 years
        year_step = 1
        if len(ret_table.index) > 10:
            year_step = 2

        sns.heatmap(
            ret_table,
            annot=True,
            annot_kws={"size": 6},
            alpha=1.0,
            center=0.0,
            cbar=False,
            cmap=cm.Blues,
            fmt=".1f",
            ax=self.axes,
            yticklabels=year_step  # use the column names but plot only every year_step label.
        )
        self._adjust_style()
        self.axes.yaxis.set_tick_params(rotation=0)

        self.axes.set_xlabel('Month')
        self.axes.set_ylabel('Year')
        self.axes.set_title(self.title)
def convert_dataframe_frequency(
        dataframe: QFDataFrame,
        frequency: Frequency) -> SimpleReturnsDataFrame:
    """
    Converts each column in the dataframe to the specified frequency.
    ValueError is raised when a column has a lower frequency than the one we are converting to.
    """
    # Verify that all columns in the dataframe have a lower frequency.
    data_frequencies = dataframe.get_frequency()
    for column, col_frequency in data_frequencies.items():
        if col_frequency < frequency:
            raise ValueError(
                "Column '{}' cannot be converted to '{}' frequency because its frequency is '{}'."
                .format(column, frequency, col_frequency))

    if frequency == Frequency.DAILY:
        return dataframe.to_simple_returns()

    filled_df = dataframe.to_prices().fillna(method="ffill")
    new_columns = {}
    for column in filled_df:
        new_columns[column] = get_aggregate_returns(filled_df[column],
                                                    frequency)

    return SimpleReturnsDataFrame(new_columns)
示例#7
0
def gain_to_pain_ratio(qf_series: QFSeries) -> float:
    """
    Calculates the gain to pain ratio for a given timeseries of returns.
    gain_to_pain_ratio is calculated for monthly returns
    gain_to_pain_ratio = sum(all returns) / abs(sum(negative returns)

    Parameters
    ----------
    qf_series: QFSeries
        financial series

    Returns
    -------
    float
        < 0 is bad
        > 1 is good
        > 1.5 is exceptionally good
    """

    aggregated_series = get_aggregate_returns(qf_series, Frequency.MONTHLY, multi_index=True)
    negative_returns = aggregated_series.loc[aggregated_series < 0]
    negative_sum = np.abs(negative_returns.sum())
    if negative_sum != 0:
        gain_to_pain = aggregated_series.sum() / negative_sum
    else:
        gain_to_pain = float("inf")
    return gain_to_pain
示例#8
0
def information_ratio(portfolio: QFSeries, benchmark: QFSeries) -> float:
    """
    The function calculates information ratio based on monthly returns.
    Return of higher frequency will be aggregated into monthly.
    """
    portfolio_monthly = get_aggregate_returns(portfolio, Frequency.MONTHLY)
    benchmark_monthly = get_aggregate_returns(benchmark, Frequency.MONTHLY)

    portfolio_avg = portfolio_monthly.mean()
    benchmark_avg = benchmark_monthly.mean()

    excess_ret = portfolio_monthly - benchmark_monthly
    tracking_error = excess_ret.std()

    information_ratio_value = (portfolio_avg - benchmark_avg) / tracking_error
    return information_ratio_value
示例#9
0
def capture_ratio(strategy: QFSeries, benchmark: QFSeries, upside_capture: bool,
                  frequency: Frequency = Frequency.MONTHLY) -> float:
    """
    Upside / Downside Capture Ratio
    This ratio is a statistical measure of a Fund Manager's overall performance in down or up markets.
    For example Downside Capture Ratio is used to evaluate how well or poorly the Manager performed
    relative to a specific index during periods when that index has dropped (had negative returns).
    The ratio is calculated by dividing the Strategy returns by the returns of the index during the down-market
    Downside Capture Ratio = sum(Strategy  Returns) / sum(Index Returns)

    A Fund Manager who has a downside capture ratio less than 1 has outperformed the index during the down-market
    by falling less than the index. For instance, a ratio of 0.75 indicates that the portfolio declined only 75%
    as much as the index during the period under consideration.

    Parameters
    ----------
    strategy:
        series of the strategy that will be evaluated
    benchmark:
        series of the benchmark for the strategy
    upside_capture: bool
        True - for upside capture ratio
        False - for downside capture ratio
    frequency: Frequency
        Frequency on which the the ratio is evaluated.
        For example Frequency.MONTHLY will result in evaluating the ration based on Monthly returns.

    Returns
    -------
    float
    """

    aggregated_strategy = get_aggregate_returns(strategy, frequency)
    aggregated_benchmark = get_aggregate_returns(benchmark, frequency)

    if upside_capture:
        selected_dates = aggregated_benchmark.loc[aggregated_benchmark > 0].index
    else:
        selected_dates = aggregated_benchmark.loc[aggregated_benchmark < 0].index

    selected_strategy_returns = aggregated_strategy.loc[selected_dates]
    selected_benchmark_returns = aggregated_benchmark.loc[selected_dates]
    benchmark_sum = selected_benchmark_returns.sum()

    if benchmark_sum != 0.0 and is_finite_number(benchmark_sum):
        return selected_strategy_returns.sum() / selected_benchmark_returns.sum()
    return float('nan')
def create_returns_distribution(returns: QFSeries, frequency: Frequency = Frequency.MONTHLY, title: str = None) -> \
        HistogramChart:
    """
    Creates a new returns distribution histogram with the specified frequency.

    Parameters
    ----------
    returns: QFSeries
        The returns series to use in the histogram.
    frequency: Frequency
        frequency of the returns after aggregation
    title
        title of the chart
    Returns
    -------
    HistogramChart
        A new ``HistogramChart`` instance.
    """
    colors = Chart.get_axes_colors()
    aggregate_returns = get_aggregate_returns(returns,
                                              frequency,
                                              multi_index=True).multiply(100)

    chart = HistogramChart(aggregate_returns)

    # Format the x-axis so that its labels are shown as a percentage.
    x_axis_formatter = FormatStrFormatter("%.0f%%")
    axes_formatter_decorator = AxesFormatterDecorator(x_major=x_axis_formatter,
                                                      key="axes_formatter")
    chart.add_decorator(axes_formatter_decorator)
    # Only show whole numbers on the y-axis.
    y_axis_locator = MaxNLocator(integer=True)
    axes_locator_decorator = AxesLocatorDecorator(y_major=y_axis_locator,
                                                  key="axes_locator")
    chart.add_decorator(axes_locator_decorator)

    # Add an average line.
    avg_line = VerticalLineDecorator(aggregate_returns.values.mean(),
                                     color=colors[1],
                                     key="average_line_decorator",
                                     linestyle="--",
                                     alpha=0.8)
    chart.add_decorator(avg_line)

    # Add a legend.
    legend = LegendDecorator(key="legend_decorator")
    legend.add_entry(avg_line, "Mean")
    chart.add_decorator(legend)

    # Add a title.
    if title is None:
        title = "Distribution of " + str(frequency).capitalize() + " Returns"
    title = TitleDecorator(title, key="title_decorator")
    chart.add_decorator(title)
    chart.add_decorator(AxesLabelDecorator("Returns", "Occurrences"))

    return chart
示例#11
0
def create_returns_bar_chart(
        returns: QFSeries,
        frequency: Frequency = Frequency.YEARLY) -> BarChart:
    """
    Constructs a new returns bar chart based on the returns specified. By default a new annual returns bar chart will
    be created.
    """
    colors = Chart.get_axes_colors()
    # Calculate data.
    aggregate_returns = get_aggregate_returns(returns,
                                              frequency,
                                              multi_index=True)
    data_series = QFSeries(aggregate_returns.sort_index(ascending=True))

    chart = BarChart(Orientation.Horizontal, align="center")
    chart.add_decorator(DataElementDecorator(data_series))
    chart.add_decorator(BarValuesDecorator(data_series))

    # Format the x-axis so that its labels are shown as a percentage.
    chart.add_decorator(AxesFormatterDecorator(x_major=PercentageFormatter()))

    # Format Y axis to make sure we have a tick for each year or 2 years
    if len(data_series) > 10:
        y_labels = data_series[data_series.index % 2 == 1].index
    else:
        y_labels = data_series.index
    chart.add_decorator(
        AxisTickLabelsDecorator(labels=y_labels,
                                axis=Axis.Y,
                                tick_values=y_labels))

    # Add an average line.
    avg_line = VerticalLineDecorator(aggregate_returns.values.mean(),
                                     color=colors[1],
                                     key="avg_line",
                                     linestyle="--",
                                     alpha=0.8)
    chart.add_decorator(avg_line)

    # Add a legend.
    legend = LegendDecorator(key="legend_decorator")
    legend.add_entry(avg_line, "Mean")
    chart.add_decorator(legend)

    # Add a title.
    title = TitleDecorator(str(frequency).capitalize() + " Returns",
                           key="title_decorator")
    chart.add_decorator(title)
    chart.add_decorator(AxesLabelDecorator("Returns", "Year"))

    return chart
示例#12
0
def import_data(frequency: Frequency, file_path: str):
    xlsx = ExcelImporter()
    df = xlsx.import_container(file_path,
                               'A1',
                               'J1888',
                               sheet_name="Data",
                               include_index=True,
                               include_column_names=True)
    weights = xlsx.import_container(file_path,
                                    'M15',
                                    'N23',
                                    sheet_name="Data",
                                    include_index=True,
                                    include_column_names=False)

    simple_ret_df = DataFrame()
    for column in df:
        prices = PricesSeries(df[column])
        simple_returns = get_aggregate_returns(prices, frequency)
        simple_ret_df[column] = simple_returns

    return simple_ret_df, weights
def create_returns_similarity(strategy: QFSeries,
                              benchmark: QFSeries,
                              mean_normalization: bool = True,
                              std_normalization: bool = True,
                              frequency: Frequency = None) -> KDEChart:
    """
    Creates a new returns similarity chart. The frequency is determined by the specified returns series.

    Parameters
    ----------
    strategy: QFSeries
        The strategy series to plot.
    benchmark: QFSeries
        The benchmark series to plot.
    mean_normalization: bool
        Whether to perform mean normalization on the series data.
    std_normalization: bool
        Whether to perform variance normalization on the series data.
    frequency: Frequency
        Returns can be aggregated in to specific frequency before plotting the chart
    Returns
    -------
    KDEChart
        A newly created KDEChart instance.
    """
    chart = KDEChart()
    colors = Chart.get_axes_colors()

    if frequency is not None:
        aggregate_strategy = get_aggregate_returns(
            strategy.to_simple_returns(), frequency)
        aggregate_benchmark = get_aggregate_returns(
            benchmark.to_simple_returns(), frequency)
    else:
        aggregate_strategy = strategy.to_simple_returns()
        aggregate_benchmark = benchmark.to_simple_returns()

    scaled_strategy = preprocessing.scale(aggregate_strategy,
                                          with_mean=mean_normalization,
                                          with_std=std_normalization)
    strategy_data_element = DataElementDecorator(scaled_strategy,
                                                 bw="scott",
                                                 shade=True,
                                                 label=strategy.name,
                                                 color=colors[0])
    chart.add_decorator(strategy_data_element)

    scaled_benchmark = preprocessing.scale(aggregate_benchmark,
                                           with_mean=mean_normalization,
                                           with_std=std_normalization)
    benchmark_data_element = DataElementDecorator(scaled_benchmark,
                                                  bw="scott",
                                                  shade=True,
                                                  label=benchmark.name,
                                                  color=colors[1])
    chart.add_decorator(benchmark_data_element)

    # Add a title.
    title = _get_title(mean_normalization, std_normalization, frequency)
    title_decorator = TitleDecorator(title, key="title")
    chart.add_decorator(title_decorator)
    chart.add_decorator(AxesLabelDecorator("Returns", "Similarity"))
    return chart
示例#14
0
 def agg_series_by_year(series):
     return get_aggregate_returns(series=series,
                                  convert_to=Frequency.YEARLY)
示例#15
0
    def _prepare_data_to_plot(self):
        simple_returns_tms = self.strategy_tms.to_simple_returns()
        annual_returns_tms = get_aggregate_returns(series=simple_returns_tms, convert_to=Frequency.YEARLY)

        return annual_returns_tms