def evaluate_params_for_tickers(self, parameters: tuple, tickers: Sequence[Ticker], start_time: datetime, end_date: datetime): # Get the backtest element for the given list of tickers backtest_elements_for_tickers = [ el for el in self.params_backtest_summary_elem_dict[parameters] if set(el.tickers) == set(tickers) ] assert len(backtest_elements_for_tickers) == 1, "Check if the modeled_params passed to " \ "FastAlphaModelTesterConfig match those you want to test" backtest_elem = backtest_elements_for_tickers[0] returns_tms = backtest_elem.returns_tms.dropna(how="all") trades = backtest_elem.trades # Create the TradesEvaluationResult object ticker_evaluation = TradesEvaluationResult() ticker_evaluation.ticker = tickers ticker_evaluation.parameters = parameters ticker_evaluation.end_date = end_date # Compute the start date as the maximum value between the given start_time and the first date of returns tms in # case of 1 ticker backtest if len(tickers) == 1: start_date = max( start_time, returns_tms.index[0]) if not returns_tms.empty else end_date else: start_date = start_time ticker_evaluation.start_date = start_date if start_date >= end_date: # Do not compute further fields - return the default None values return ticker_evaluation avg_nr_of_trades = avg_nr_of_trades_per1y( QFSeries([ t for t in trades if t.start_time >= start_date and t.end_time <= end_date ]), start_date, end_date) ticker_evaluation.avg_nr_of_trades_1Y = avg_nr_of_trades ticker_evaluation.sqn_per_avg_nr_trades = sqn( QFSeries([ t.pnl for t in trades if t.start_time >= start_date and t.end_time <= end_date ])) * sqrt(avg_nr_of_trades) returns_tms = returns_tms.loc[start_date:end_date] if not returns_tms.empty: ticker_evaluation.annualised_return = cagr(returns_tms, Frequency.DAILY) return ticker_evaluation
def sqn_per_year(returns: QFSeries): sqn_per_year_value = sqn(returns) * sqrt(avg_nr_of_trades_per1y(returns, self.start_date, self.end_date)) return sqn_per_year_value
def _add_stats_table(self): statistics = [] # type: List[Tuple] def append_to_statistics(measure_description: str, function: Callable, trades_containers, percentage_style: bool = False): style_format = "{:.2%}" if percentage_style else "{:.2f}" returned_values = (function(tc) for tc in trades_containers) returned_values = (value if is_finite_number(value) else 0.0 for value in returned_values) statistics.append((measure_description, *(style_format.format(val) for val in returned_values))) # Prepare trades data frame, used to generate all statistics trades_df = QFDataFrame.from_records( data=[(t.start_time, t.end_time, t.percentage_pnl, t.direction) for t in self.trades], columns=["start time", "end time", "percentage pnl", "direction"] ) # In case if the initial risk is not set all the return statistic will be computed using the percentage pnl, # otherwise the r_multiply = percentage pnl / initial risk is used unit = "%" if self.initial_risk is None else "R" trades_df["returns"] = trades_df["percentage pnl"] if self.initial_risk is None \ else trades_df["percentage pnl"] / self.initial_risk # Filter out only long and only long_trades_df = trades_df[trades_df["direction"] > 0] short_trades_df = trades_df[trades_df["direction"] < 0] all_dfs = [trades_df, long_trades_df, short_trades_df] append_to_statistics("Number of trades", len, all_dfs) append_to_statistics("% of trades number", lambda df: len(df) / len(trades_df) if len(trades_df) > 0 else 0, all_dfs, percentage_style=True) period_length_in_years = Timedelta(self.end_date - self.start_date) / Timedelta(days=1) / DAYS_PER_YEAR_AVG append_to_statistics("Avg number of trades per year", lambda df: len(df) / period_length_in_years, all_dfs) append_to_statistics("Avg number of trades per year per asset", lambda df: len(df) / period_length_in_years / self.nr_of_assets_traded, all_dfs) def percentage_of_positive_trades(df: QFDataFrame): return len(df[df["returns"] > 0]) / len(df) if len(df) > 0 else 0.0 append_to_statistics("% of positive trades", percentage_of_positive_trades, all_dfs, percentage_style=True) def percentage_of_negative_trades(df: QFDataFrame): return len(df[df["returns"] < 0]) / len(df) if len(df) > 0 else 0.0 append_to_statistics("% of negative trades", percentage_of_negative_trades, all_dfs, percentage_style=True) def avg_trade_duration(df: QFDataFrame): trades_duration = (df["end time"] - df["start time"]) / Timedelta(days=1) return trades_duration.mean() append_to_statistics("Average trade duration [days]", avg_trade_duration, all_dfs) append_to_statistics("Average trade return [{}]".format(unit), lambda df: df["returns"].mean(), all_dfs, percentage_style=(self.initial_risk is None)) append_to_statistics("Std trade return [{}]".format(unit), lambda df: df["returns"].std(), all_dfs, percentage_style=(self.initial_risk is None)) def avg_positive_trade_return(df: QFDataFrame): positive_trades = df[df["returns"] > 0] return positive_trades["returns"].mean() append_to_statistics("Average positive return [{}]".format(unit), avg_positive_trade_return, all_dfs, percentage_style=(self.initial_risk is None)) def avg_negative_trade_return(df: QFDataFrame): negative_trades = df[df["returns"] < 0] return negative_trades["returns"].mean() append_to_statistics("Average negative return [{}]".format(unit), avg_negative_trade_return, all_dfs, percentage_style=(self.initial_risk is None)) append_to_statistics("Best trade return [{}]".format(unit), lambda df: df["returns"].max(), all_dfs, percentage_style=(self.initial_risk is None)) append_to_statistics("Worst trade return [{}]".format(unit), lambda df: df["returns"].min(), all_dfs, percentage_style=(self.initial_risk is None)) append_to_statistics("SQN (per trade) [{}]".format(unit), lambda df: sqn(df["returns"]), all_dfs, percentage_style=(self.initial_risk is None)) append_to_statistics("SQN (per 100 trades) [{}]".format(unit), lambda df: sqn_for100trades(df["returns"]), all_dfs, percentage_style=(self.initial_risk is None)) def sqn_per_year(returns: QFSeries): sqn_per_year_value = sqn(returns) * sqrt(avg_nr_of_trades_per1y(returns, self.start_date, self.end_date)) return sqn_per_year_value append_to_statistics("SQN (per year) [{}]".format(unit), lambda df: sqn_per_year(df["returns"]), all_dfs, percentage_style=(self.initial_risk is None)) statistics_df = QFDataFrame.from_records(statistics, columns=["Measure", "All trades", "Long trades", "Short trades"]) table = DFTable(statistics_df, css_classes=['table', 'left-align']) table.add_columns_classes(["Measure"], 'wide-column') self.document.add_element(table)
def test_sqn(self): expected_value = 0.178174161 actual_return = sqn(self.trades) self.assertAlmostEqual(expected_value, actual_return, places=4)
def test_sqn(self): expected_value = 0.178174161 actual_return = sqn( SimpleReturnsSeries(t.percentage_pnl for t in self.trades)) self.assertAlmostEqual(expected_value, actual_return, places=4)