Rotational Trading in Python

Ed West
3 min readMay 24, 2023

--

Rotational trading is a strategy used by investors that involves purchasing top-performing assets and simultaneously selling the underperforming ones in their portfolio. It’s a great way to periodically manage a portfolio by holding winners and selling losers.

Backtesting a rotational trading strategy is easy using PyBroker, an open-source Python framework for developing trading strategies. To begin using PyBroker, you can install the library with pip:

pip install -U lib-pybroker

Or you can clone the Github repository:

git clone https://github.com/edtechre/pybroker

Once installed, let’s create a new notebook and add some required imports:

import pybroker as pyb
from pybroker import ExecContext, Strategy, StrategyConfig, YFinance

Rotational Strategy

Our strategy will involve ranking and buying stocks with the highest price rate-of-change (ROC). To start, we’ll define a 20-day ROC indicator using TA-Lib, a widely used technical analysis library that implements many financial indicators:

import talib as ta

roc_20 = pyb.indicator(
'roc_20', lambda data: ta.ROC(data.adj_close, timeperiod=20))

Next, let’s define the rules of our strategy:

  • Buy the two stocks with the highest 20-day ROC.
  • Allocate 50% of our capital to each stock.
  • If either of the stocks is no longer ranked among the top five 20-day ROCs, then we will liquidate that stock.
  • Trade these rules daily.

Let’s set up our config and some parameters for the above rules:

config = StrategyConfig(max_long_positions=2)
pyb.param('target_size', 1 / config.max_long_positions)
pyb.param('rank_threshold', 5)

To proceed with our strategy, we will implement a rank function that ranks each stock by their 20-day ROC in descending order, from highest to lowest.

def rank(ctxs: dict[str, ExecContext]):
scores = {
symbol: ctx.indicator('roc_20')[-1]
for symbol, ctx in ctxs.items()
}
sorted_scores = sorted(
scores.items(),
key=lambda score: score[1],
reverse=True
)
threshold = pyb.param('rank_threshold')
top_scores = sorted_scores[:threshold]
top_symbols = [score[0] for score in top_scores]
pyb.param('top_symbols', top_symbols)

The top_symbols global parameter contains the symbols of the stocks with the top five highest 20-day ROCs.

Now that we have a method for ranking stocks by their ROC, we can proceed with implementing a rotate function to manage the rotational trading.

def rotate(ctx: ExecContext):
if ctx.long_pos():
if ctx.symbol not in pyb.param('top_symbols'):
ctx.sell_all_shares()
else:
target_size = pyb.param('target_size')
ctx.buy_shares = ctx.calc_target_shares(target_size)
ctx.score = ctx.indicator('roc_20')[-1]

We liquidate the currently held stock if it is no longer ranked among the top five 20-day ROCs. Otherwise, we rank all stocks by their 20-day ROC and buy up to the top two ranked. For more information on ranking when placing buy orders, see the Ranking and Position Sizing notebook in PyBroker’s documentation.

We will use the set_before_exec method to execute our ranking with rank before running the rotate function. For this backtest, we will use a universe of 10 stocks:

strategy = Strategy(
YFinance(),
start_date='1/1/2018',
end_date='1/1/2023',
config=config
)
strategy.set_before_exec(rank)
strategy.add_execution(rotate, [
'TSLA',
'NFLX',
'AAPL',
'NVDA',
'AMZN',
'MSFT',
'GOOG',
'AMD',
'INTC',
'META'
], indicators=roc_20)

Now, let’s run our backtest!

result = strategy.backtest(warmup=20)

The result contains trades and performance metrics from the backtest. Here is a sample of a few:

result.metrics_df.round(2)
trade_count                       129.00
initial_market_value 100000.00
end_market_value 484715.10
total_pnl 399906.80
max_drawdown -429073.10

(Ouch! Hopefully you can come up with a better strategy with less drawdown!)

We can also access the orders that were placed by the strategy:

result.orders.head()
type       symbol    date         shares   limit_price  fill_price  fees
buy NFLX 2018-02-01 184 NaN 267.67 0.0
buy AMD 2018-02-01 3639 NaN 11.75 0.0
sell AMD 2018-02-05 3639 NaN 11.56 0.0
buy AMZN 2018-02-05 627 NaN 69.49 0.0
sell AMD 2018-04-03 627 NaN 69.23 0.0

And that’s a wrap! You can now begin backtesting your own rotational trading strategies with PyBroker to improve your portfolio’s performance. You can also find more tutorials on using PyBroker on https://www.pybroker.com, with all code available in the Github repository.

Thanks for reading!

--

--