Introducing the Consistency-Weighted Return (CWR)

Carlos Barredo Lago
8 min readSep 13, 2023

An Alternative Measure for Evaluating Trading Strategies

1. Introduction

In the vast world of quantitative trading and investment metrics, the spotlight often falls on headline-grabbing returns. Strategies that boast huge profits can steal the show, but are they consistently reliable?

Enter the Consistency-Weighted Return (CWR) — a metric designed to combine raw profit with the strategy’s consistency.

2. What is the Consistency-Weighted Return (CWR)?

In the realm of trading and investing, a consistent upward trajectory in profits is often considered the “holy grail. This desired consistency is more than just a dream; it’s a reflection of stability and reliability in the chosen strategy.

The CWR offers an analytical approach to gauge the proximity of a trading strategy to this ideal scenario. It amalgamates the total returns of a strategy with its R² value, also known as the coefficient of determination.

But what exactly is this R² or coefficient of determination? In the discipline of statistics, when we aim to understand the linearity of a set of data points, we deploy a method called linear regression. Through this method, we strive to fit a line (often referred to as the “line of best fit”) amidst these points. The R² value emanates from this procedure. It quantifies the proportion of variance in the dependent variable (in this context, the returns from a trading strategy) that’s predictable from the independent variable(s). Essentially, an R² value of 1 denotes a perfect linear relationship, implying the data aligns flawlessly with the line of best fit. Conversely, a value nearing 0 suggests a lack of linearity, indicating that the data points are dispersed widely around the line.

Thus, the CWR isn’t merely a reflection of the absolute profits of a strategy. It poses a more intricate question: “While generating these profits, did the strategy maintain a semblance of uniformity and predictability?”

Formula: CWR = R² × Annualized Returns

Where:

R² (Coefficient of Determination): It’s a metric of reliability. An indicator of how harmoniously the strategy’s returns adhered to a predictable, linear progression.

Annualized Returns: This is the sum of all the profits and losses for the period, scaled to represent a yearly return. Specifically, the formula for annualizing simple returns is:

Annualized Returns = Total Returns × (duration in periods / periods per year​)

Where:

  • Total Returns: Total Returns is the cumulative sum of daily returns.
  • periods per year: periods per year would be 365 for cryptocurrencies and 252 for most other assets.
  • duration in periods: duration in periods is the total number of days in your time series.

3. Why CWR? The Advantage of Consistency

  1. Reliable Forecasts: Strategies with a higher consistency are more predictable. They tend to exhibit fewer wild fluctuations, making them easier to manage and less stressful for traders.
  2. Lower Risk: Consistent returns can suggest that the strategy isn’t reliant on occasional big wins (which can be followed by big losses). This reduces the risk of severe drawdowns.
  3. Long-Term Viability: While ‘flash in the pan’ strategies might offer dramatic returns for a short period, they often fizzle out. Consistent strategies, on the other hand, can offer sustained performance.

4. Computing the Consistency-Weighted Return (CWR) with Python

To calculate R², the foundational step is to perform linear regression on our dataset to determine the best-fit line. An important distinction in our approach is that we opt not to use an intercept. This choice stems from the foundational principle that a profitability curve is inherently assumed to commence at zero.

In this section, we’ll explore two distinct methods to perform linear regression and subsequently derive the R² value:

  1. Ordinary Least Squares (OLS): A widely-used method in statistics, OLS is particularly suited for multivariate linear regression. In our analysis, we’ll employ the statsmodels package in Python to execute OLS.
  2. Direct Calculation Method: This method, designed for univariate linear regression, calculates the regression slope using the covariance between the variables and the variance of the independent variable. We’ll use this method to determine the R² value, differentiating between total variation and the variation explained by the regression model.

Let’s dive into both methods. Here’s a step-by-step guide using a hypothetical dataframe named trades:

1. Set up your environment:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm

2. Create a sample trades dataframe:

# Simulating a random but profitable pnl series
bias = 0.25 # This constant ensures an upward trend on average
data = {
'time': pd.date_range(start='2023-06-01', periods=100, freq='D'),
'pnl': np.random.randn(100) + bias
}
trades = pd.DataFrame(data)
trades['pnl'] = trades['pnl'] / len(trades)

3. Calculate the Cumulative PnL and extract the ‘X’ and ‘y’ arrays:

# Cumulative PnL.
trades['cumpnl'] = trades['pnl'].cumsum()

# Create the X array based on the length of trades.
X = np.array(range(len(trades)))

# Set y to the 'cumpnl' column of trades.
y = trades['cumpnl'].values

4.1. Calculate R² using OLS:

# Fit the OLS model without intercept.
model = sm.OLS(y, X).fit()

# Extract R^2 directly from the model.
r_squared_ols = model.rsquared

4.2. Calculate R² using the direct method:

# Calculate the beta coefficient without intercept.
beta = np.sum(X * y) / np.sum(X**2)

# Predict y using the calculated beta.
y_pred_direct = beta * X

# Calculate R^2 directly for the model without intercept.
ss_tot_direct = np.sum(y**2)
ss_res_direct = np.sum((y - y_pred_direct)**2)
r_squared_direct = 1 - (ss_res_direct / ss_tot_direct)

5. Calculate CWR:

# Parameters for annualization
periods_per_year = 365 # Using 365 for assets like cryptocurrencies

# Calculate the duration in periods (days in this case, since 'trades' is a dataframe of daily data)
duration_in_periods = len(trades)

# Calculate the Annualized Returns
annualized_returns_ols = trades['cumpnl'].iloc[-1] * (periods_per_year / duration_in_periods)
annualized_returns_direct = trades['cumpnl'].iloc[-1] * (periods_per_year / duration_in_periods)

# Calculate the CWRs using the R^2 values and Annualized Returns
cwr_ols = annualized_returns_ols * r_squared_ols
cwr_direct = annualized_returns_direct * r_squared_direct

# Print the results for a quick comparison
print(f"CWR using OLS method: {cwr_ols:.6f}")
print(f"CWR using Direct Calculation method: {cwr_direct:.6f}")
CWR using OLS method: 0.935333
CWR using Direct Calculation method: 0.935333

6. Plot returns compared to the linear regressions:

# Regression line for the OLS method:
regression_line_ols = model.predict(X)

# Regression line for the Direct Calculation method:
regression_line_direct = beta * X

# Plotting
plt.figure(figsize=(12, 5))

# Plotting the actual cumulative PnL
plt.plot(trades['time'], trades['cumpnl'], label='Cumulative PnL', color='green')

# Plotting the regression line obtained from the OLS method
plt.plot(trades['time'], regression_line_ols, label='Regression (OLS)', color='black', linestyle='--')

# Plotting the regression line obtained from the Direct Calculation method
plt.plot(trades['time'], regression_line_direct, label='Regression (Direct)', color='grey', linestyle='--')

# Setting titles and labels
plt.title('Cumulative PnL compared with Regression Lines')
plt.xlabel('Time')
plt.ylabel('Cumulative PnL')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Cumulative PnL compared with the Regression Lines

5. Flashy Returns vs. Steady Growth

Let’s illustrate the importance of CWR with a practical example.

Imagine two trading strategies over a span of 100 days:

  • Strategy A: Characterized by volatile returns, this strategy often delivers significant gains followed by sharp drops, resembling a financial roller coaster. It’s worth noting that, by design, Strategy A ends up with a return that is 10% higher than Strategy B by the end of the period.
  • Strategy B: This strategy emphasizes steadier growth. Although the returns are more consistent, there’s a touch of realistic noise, making it less predictable but far steadier than Strategy A.
# Setting the seed for reproducibility
np.random.seed(17)

# Strategy A: More volatile returns
volatile_returns = np.random.randn(100) * 0.1
# Strategy B: Steady returns but with slight noise
steady_returns = np.full(100, 0.005) + np.random.randn(100) * 0.01 # Adding noise to the steady return

# Adjusting to ensure it ends up 10% higher at the end of the period
cum_returns_A = volatile_returns.cumsum()
target_final_return = steady_returns.cumsum()[-1] + 0.10 # 10% higher than strategy B
scale_factor = target_final_return / cum_returns_A[-1]
volatile_returns *= scale_factor

# Creating DataFrames for both strategies
time_range = pd.date_range(start='2023-06-01', periods=100, freq='D')
trades_A = pd.DataFrame({'time': time_range, 'returns': volatile_returns})
trades_B = pd.DataFrame({'time': time_range, 'returns': steady_returns})

# Calculating the cumulative PnL for both strategies
trades_A['cumpnl'] = trades_A['returns'].cumsum()
trades_B['cumpnl'] = trades_B['returns'].cumsum()

# Plot
plt.figure(figsize=(12, 6))
plt.plot(trades_A['time'], trades_A['cumpnl'], label='Strategy A', color='red')
plt.plot(trades_B['time'], trades_B['cumpnl'], label='Strategy B', color='green')
plt.title('Comparison of Cumulative PnL: Flashy Returns vs. Steady Growth')
plt.xlabel('Time')
plt.ylabel('Cumulative PnL')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Comparison of Cumulative PnL: Flashy Returns vs. Steady Growth

Despite Strategy A’s higher cumulative return by the end of the 100 days, their journeys differ significantly. When we evaluate these strategies with the CWR metric, a more revealing narrative emerges.

# Parameters for annualization
periods_per_year = 365 # Using 365 for assets like cryptocurrencies

# Calculate the duration in periods (days in this case, since trades_A and trades_B are dataframes of daily data)
duration_in_periods = len(trades_A) # Assuming trades_A and trades_B have the same duration

# Strategy A: CWR Calculation
X = np.arange(len(trades_A))
y_A = trades_A['cumpnl'].values
model_A = sm.OLS(y_A, X)
results_A = model_A.fit()
r_squared_A = results_A.rsquared
annualized_returns_A = trades_A['cumpnl'].iloc[-1] * (periods_per_year / duration_in_periods)
cwr_A = annualized_returns_A * r_squared_A

# Strategy B: CWR Calculation
y_B = trades_B['cumpnl'].values
model_B = sm.OLS(y_B, X)
results_B = model_B.fit()
r_squared_B = results_B.rsquared
annualized_returns_B = trades_B['cumpnl'].iloc[-1] * (periods_per_year / duration_in_periods)
cwr_B = annualized_returns_B * r_squared_B

# Printing the CWR for both strategies
print(f"CWR for Strategy A: {cwr_A:.4f}")
print(f"CWR for Strategy B: {cwr_B:.4f}")
CWR for Strategy A: 1.6648
CWR for Strategy B: 1.8462

CWR encapsulates not only the destination but also the journey taken. A strategy with more predictable and steady growth is likely to have a higher CWR, highlighting its consistent performance. This consistency can be more appealing to traders and investors since it reduces the unpredictability and potential stress of erratic performance swings.

Upon calculating the CWR for both strategies using the OLS method, Strategy B showcased a higher value, emphasizing its reliability despite having a lower overall return than Strategy A.

6. Incorporating CWR into Your Evaluation:

  1. Compare & Contrast: Use CWR alongside other metrics like the Sharpe Ratio, Sortino Ratio, or Maximum Drawdown to get a more holistic view of a strategy’s performance.
  2. Portfolio Construction: For diversified portfolios, ensure you have a mix of strategies. Prioritize those with a higher CWR to bring more stability to your portfolio.
  3. Risk Management: Recognize strategies with lower CWRs as they may introduce more volatility. Adjust your capital allocation accordingly.

7. Conclusion

While raw returns will always be a cornerstone of evaluating performance, the Consistency-Weighted Return (CWR) offers a fresh perspective, emphasizing the importance of steady, reliable growth. In the tumultuous seas of the trading world, a consistent strategy can be the anchor that ensures long-term success.

Remember, when exploring new metrics like CWR, it’s vital to understand their limitations and to use them in conjunction with other tools and insights. Good trading!

--

--

Carlos Barredo Lago

Quant researcher & developer | Data scientist | Engineer