The volatility of simple trading strategy returns
Marton Trencseni - Sat 09 March 2024 - Finance
Introduction
In the previous post, I looked into the stability of volatilities of various securities, and found that there are significant year-over-year differences. The inspiration was to get a sense of the predictive power of frameworks such as Markowitz portfolio theory and CAPM, which rely on an accurate estimate of volatility (and returns) for portfolio choice.
In this post, I look at an alternative volatility measure, one more in line with the strategy of an imaginary trader. The thinking is simple: if the trader is following a strategy and of a sound mind (or is a soul-less algorithm), she doesn't care about short term price changes, only long-term outcomes of following the strategy. Of course, there is a big caveat: long-term outcomes are literally the sum of short-term daily changes. Still, I find it interesting to directly plot the returns (and volatility) of such imaginary traders.
Trading algorithm
The algorithm is simple: buy the security on day 1, and exit early (sell) it the price goes up (take_profit
) or down (stop_loss
) by a certain amount. Otherwise, stick with it, but exit (sell) after a certain number of days (position_length
), whatever the price:
for daily_price in tail:
if daily_price >= start_price * take_profit:
final_price = start_price * take_profit
break
if daily_price <= start_price * stop_loss:
final_price = start_price * stop_loss
break
final_price = daily_price
The returns are calculated by launching this strategy every day, and either exiting early, or running it until position_length
days. This means that both the returs and variances/volatilities are highly correlated. The following function implements this strategy, prints out a histogram of daily returns, and year-over-year standard deviations:
def show_strategy_returns(df, price_col='Adj Close', position_length=66, take_profit=1.5, stop_loss=0.5):
df['Year'] = df.index.year
years = list(df['Year'])
sigmas = defaultdict(lambda: [])
tickers = set([x[1] for x in list(df.columns) if x[1] != ''])
for t in tickers:
li = list(df[price_col][t])
returns = []
for i, start_price in enumerate(li):
tail = li[i+1:i+1+position_length]
if len(tail) < position_length: break
for daily_price in tail:
if daily_price >= start_price * take_profit:
final_price = start_price * take_profit
break
if daily_price <= start_price * stop_loss:
final_price = start_price * stop_loss
break
final_price = daily_price
r = (final_price - start_price) / start_price
returns.append(r)
df_returns = pd.DataFrame(returns, columns=['returns'])
df_returns.hist(bins=50, density=True)
plt.title(t)
# compute annual variance
years = years[:len(returns)]
sigmas[t] = [stdev([x[1] for x in zip(years, returns) if x[0] == year and x[1] == x[1]]) for year in set(years)]
plt.clf()
legend = []
for t, s in sigmas.items():
legend.append(t)
plt.plot(sorted(set(years)), s, marker='o')
plt.legend(legend)
plt.xlabel('year')
plt.ylabel(f'sigma of strategy')
plt.ylim(0, )
Let's run it for our tech stocks (Tesla, Google, Apple, Meta, Microsoft) in a greedy mode, where we want to make +50%, or stop loss at -50%, in a 3 month (66 trading days) window:
show_strategy_returns(df_tech, position_length=66, take_profit=1.5, stop_loss=0.5)
It's striking how different the shapes of the returns are:
How about if we run the same strategy, but with more patience, so we're willing to wait 1 whole year (252 trading days)?
show_strategy_returns(df_tech, position_length=252, take_profit=1.5, stop_loss=0.5)
It's worth noting that in both cases, like with simple daily volatility measures, the year-over-year changes are significant. It seems hard to predict the upcoming year's volatility from previous years.
Let's run similar strategies on indices:
show_strategy_returns(df_indices, position_length=66, take_profit=1.2, stop_loss=0.8)
show_strategy_returns(df_indices, position_length=252, take_profit=1.2, stop_loss=0.8)
Conclusion
I will continue to investigate volatility measures, slowly working my ways to the Capital Asset Pricing Model.