Exemplo n.º 1
0
    def test_max_drawdown(self):
        expected_max_drawdown = 0.0298
        actual_max_drawdown = max_drawdown(self.test_prices_tms)
        self.assertAlmostEqual(expected_max_drawdown, actual_max_drawdown, delta=0.00000001)

        expected_max_drawdown = 0.3
        prices_tms = PricesSeries(data=[100, 90, 80, 85, 70, 100], index=date_range('2015-01-01', periods=6))
        actual_max_drawdown = max_drawdown(prices_tms)
        self.assertAlmostEqual(expected_max_drawdown, actual_max_drawdown, places=10)

        expected_max_drawdown = 0.35
        prices_tms = PricesSeries(data=[100, 90, 80, 85, 70, 100, 90, 95, 65],
                                  index=date_range('2015-01-01', periods=9))
        actual_max_drawdown = max_drawdown(prices_tms)
        self.assertEqual(expected_max_drawdown, actual_max_drawdown)
Exemplo n.º 2
0
    def test_get_weights_with_upper_limits(self):
        portfolio = MultiFactorPortfolio(self.assets_df.cov(),
                                         self.assets_df.var(),
                                         self.assets_df.mean(),
                                         max_drawdown(self.assets_df),
                                         self.assets_df.skew(),
                                         self.parameters,
                                         upper_constraint=0.1)
        actual_weights = portfolio.get_weights()

        expected_weights_vals = np.zeros(20)
        expected_weights_vals[0] = 0.1000
        expected_weights_vals[4] = 0.1000
        expected_weights_vals[6] = 0.1000
        expected_weights_vals[7] = 0.1000
        expected_weights_vals[8] = 0.0234
        expected_weights_vals[10] = 0.1000
        expected_weights_vals[11] = 0.0920
        expected_weights_vals[13] = 0.0718
        expected_weights_vals[16] = 0.1000
        expected_weights_vals[17] = 0.0128
        expected_weights_vals[18] = 0.1000
        expected_weights_vals[19] = 0.1000

        self.assertTrue(
            np.allclose(expected_weights_vals,
                        actual_weights.values,
                        rtol=0,
                        atol=5e-02))
Exemplo n.º 3
0
    def _calculate_risk_stats(self):
        self.cvar = cvar(self.returns_tms, 0.05)  # default is the 5% CVaR
        log_cvar = simple_to_log_return(self.cvar)
        annualised_log_cvar = annualise_with_sqrt(log_cvar, self.frequency)
        self.annualised_cvar = log_to_simple_return(annualised_log_cvar)

        prices_tms = self.returns_tms.to_prices()
        self.max_drawdown = max_drawdown(prices_tms)
        self.avg_drawdown = avg_drawdown(prices_tms)
        self.avg_drawdown_duration = avg_drawdown_duration(prices_tms)
Exemplo n.º 4
0
def trade_based_max_drawdown(trades: QFDataFrame):
    """
    Calculates the max drawdown on the series of returns of trades
    """
    if trades.shape[0] > 0:
        returns = trades[TradeField.Return]
        dates = trades[TradeField.EndDate]
        returns_tms = SimpleReturnsSeries(index=dates, data=returns.values)
        prices_tms = returns_tms.to_prices(frequency=Frequency.DAILY)
        return -max_drawdown(prices_tms)

    return None
Exemplo n.º 5
0
    def _add_statistics_table(self):
        table = Table(column_names=["Measure", "Value"], css_class="table stats-table")

        number_of_trades = self.returns_of_trades.count()
        table.add_row(["Number of trades", number_of_trades])

        period_length = self.end_date - self.start_date
        period_length_in_years = to_days(period_length) / DAYS_PER_YEAR_AVG
        avg_number_of_trades = number_of_trades / period_length_in_years / self.nr_of_assets_traded
        table.add_row(["Avg number of trades per year per asset", avg_number_of_trades])

        positive_trades = self.returns_of_trades[self.returns_of_trades > 0]
        negative_trades = self.returns_of_trades[self.returns_of_trades < 0]

        percentage_of_positive = positive_trades.count() / number_of_trades
        percentage_of_negative = negative_trades.count() / number_of_trades
        table.add_row(["% of positive trades", percentage_of_positive * 100])
        table.add_row(["% of negative trades", percentage_of_negative * 100])

        avg_positive = positive_trades.mean()
        avg_negative = negative_trades.mean()
        table.add_row(["Avg positive trade [%]", avg_positive * 100])
        table.add_row(["Avg negative trade [%]", avg_negative * 100])

        best_return = max(self.returns_of_trades)
        worst_return = min(self.returns_of_trades)
        table.add_row(["Best trade [%]", best_return * 100])
        table.add_row(["Worst trade [%]", worst_return * 100])

        max_dd = max_drawdown(self.returns_of_trades)
        table.add_row(["Max drawdown [%]", max_dd * 100])

        prices_tms = self.returns_of_trades.to_prices()
        total_return = prices_tms.iloc[-1] / prices_tms.iloc[0] - 1
        table.add_row(["Total return [%]", total_return * 100])

        annualised_ret = annualise_total_return(total_return, period_length_in_years, SimpleReturnsSeries)
        table.add_row(["Annualised return [%]", annualised_ret * 100])

        avg_return = self.returns_of_trades.mean()
        table.add_row(["Avg return of trade [%]", avg_return * 100])

        std_of_returns = self.returns_of_trades.std()
        table.add_row(["Std of return of trades [%]", std_of_returns * 100])

        # System Quality Number
        sqn = avg_return / std_of_returns
        table.add_row(["SQN", sqn])
        table.add_row(["SQN for 100 trades", sqn * 10])  # SQN * sqrt(100)
        table.add_row(["SQN * Sqrt(avg number of trades per year)", sqn * sqrt(avg_number_of_trades)])

        self.document.add_element(table)
Exemplo n.º 6
0
    def make_stats(self, initial_risks: Sequence[float],
                   scenarios_list: Sequence[QFDataFrame]) -> QFDataFrame:
        """
        Creates a pandas.DataFrame showing how many strategies failed (reached certain draw down level) and how many
        of them succeeded (that is: reached the target return and not failed on the way).

        Parameters
        ----------
        initial_risks: Sequence[float]
            list of initial_risk parameters where initial_risk is a float number
        scenarios_list: Sequence[pandas.DataFrame]
            list with scenarios (QFDataFrame) where each DataFrame corresponds to one initial_risk value
            Each DataFrame has columns corresponding to different scenarios and its indexed by Trades' ordinal number.
            Its values are returns of Trades.

        Returns
        -------
        pandas.DataFrame
            DataFrame indexed with initial_risk values and with columns FAILED (fraction of scenarios that failed)
            and SUCCEEDED (fraction of scenarios that met the objective and didn't fail on the way)
        """
        result = QFDataFrame(index=pd.Index(initial_risks),
                             columns=pd.Index([self.FAILED, self.SUCCEEDED]),
                             dtype=np.float64)

        for init_risk, scenarios in zip(initial_risks, scenarios_list):
            # calculate drawdown for each scenario

            scenarios_df = cast_dataframe(
                scenarios,
                SimpleReturnsDataFrame)  # type: SimpleReturnsDataFrame
            max_drawdowns = max_drawdown(scenarios_df)
            total_returns = scenarios_df.total_cumulative_return()

            failed = max_drawdowns >= self._max_accepted_dd
            reached_target_return = total_returns >= self._target_return
            succeeded = ~failed & reached_target_return

            num_of_scenarios = scenarios_df.num_of_columns
            failed_normalized = failed.sum() / num_of_scenarios
            succeeded_normalized = succeeded.sum() / num_of_scenarios

            result.loc[init_risk, [self.FAILED, self.SUCCEEDED]] = [
                failed_normalized, succeeded_normalized
            ]

        return result
Exemplo n.º 7
0
def calmar_ratio(qf_series: QFSeries, frequency: Frequency) -> float:
    """
    Calculates the Calmar ratio for a given timeseries of returns.
    calmar_ratio = CAGR / max drawdown

    Parameters
    ----------
    qf_series
        financial series
    frequency
        frequency of qf_series

    Returns
    -------
    calmar_ratio
    """

    annualised_growth_rate = cagr(qf_series, frequency)
    max_dd = max_drawdown(qf_series)
    ratio = annualised_growth_rate / max_dd
    return ratio
Exemplo n.º 8
0
    def test_get_weights(self):
        portfolio = MultiFactorPortfolio(self.assets_df.cov(),
                                         self.assets_df.var(),
                                         self.assets_df.mean(),
                                         max_drawdown(self.assets_df),
                                         self.assets_df.skew(),
                                         self.parameters)
        actual_weights = portfolio.get_weights()

        expected_weights_vals = np.zeros(20)
        expected_weights_vals[4] = 0.2802
        expected_weights_vals[6] = 0.0393
        expected_weights_vals[16] = 0.0537
        expected_weights_vals[18] = 0.4746
        expected_weights_vals[19] = 0.1521

        self.assertTrue(
            np.allclose(expected_weights_vals,
                        actual_weights.values,
                        rtol=0,
                        atol=5e-02))
Exemplo n.º 9
0
    def _get_monte_carlos_simulator_outputs(self, scenarios_df: PricesDataFrame, total_returns: SimpleReturnsSeries) \
            -> DFTable:
        _, all_scenarios_number = scenarios_df.shape
        rows = []

        # Add the Median Return value
        median_return = np.median(total_returns)
        rows.append(("Median Return", "{:.2%}".format(median_return)))

        # Add the Mean Return value
        mean_return = total_returns.mean()
        rows.append(("Mean Return", "{:.2%}".format(mean_return)))

        trade_returns = QFSeries(data=[trade.percentage_pnl for trade in self.trades])
        sample_len = int(self._average_number_of_trades_per_year())
        std = trade_returns.std()
        expectation_adj_series = np.ones(sample_len) * (trade_returns.mean() - 0.5 * std * std)
        expectation_adj_series = SimpleReturnsSeries(data=expectation_adj_series)
        expectation_adj_series = expectation_adj_series.to_prices(suggested_initial_date=0)
        mean_volatility_adjusted_return = expectation_adj_series.iloc[-1] / expectation_adj_series.iloc[0] - 1.0
        rows.append(("Mean Volatility Adjusted Return", "{:.2%}".format(mean_volatility_adjusted_return)))

        # Add the Median Drawdown
        max_drawdowns = max_drawdown(scenarios_df)
        median_drawdown = np.median(max_drawdowns)
        rows.append(("Median Maximum Drawdown", "{:.2%}".format(median_drawdown)))

        # Add the Median Return / Median Drawdown
        rows.append(("Return / Drawdown", "{:.2f}".format(median_return / median_drawdown)))

        # Probability, that the return will be > 0
        scenarios_with_positive_result = total_returns[total_returns > 0.0].count()
        probability = scenarios_with_positive_result / all_scenarios_number
        rows.append(("Probability of positive return", "{:.2%}".format(probability)))

        table = DFTable(data=QFDataFrame.from_records(rows, columns=["Measure", "Value"]),
                        css_classes=['table', 'left-align'])
        table.add_columns_classes(["Measure"], 'wide-column')

        return table