Backtest Period
Markets Traded
Equities
Maximum Drawdown
Period of Rebalancing
Monthly
Return (Annual)
Sharpe Ratio
Standard Deviation (Annual)
Original paper
SSRN-id2468061.pdf941.8KB
Trading rules
- Investment universe: All Closed-End funds with sufficient liquidity
- Basic approach: Sort CEFs into quintiles based on last month’s discount
- Go long on funds with largest discount
- Go short on funds with largest premium
- Rebalance monthly
- Enhanced approach: Utilize regression model (refer to page 6, equations 4a & 4b)
- Incorporate past discounts and past monthly changes of discount
- Predict next month’s performance for each fund
- Go long on funds with highest predicted performance
- Go short on funds with lowest predicted performance
- Rebalance monthly
Python code
Backtrader
Here’s a basic outline of the Backtrader code for the trading strategy. Keep in mind that this is just a template and not a complete, ready-to-run code. You’ll need to add the relevant data feeds, configure the cerebro object, and specify the appropriate indicators and order execution logic.
import backtrader as bt
class ClosedEndFundMeanReversion(bt.Strategy):
params = (
('quintile_size', 0.2),
)
def __init__(self):
# Add any indicators or calculations you need for the regression model
pass
def prenext(self):
self.next()
def next(self):
# Implement the logic for sorting CEFs into quintiles based on discount
# and calculating the predictions from the regression model
cefs_sorted = sorted(self.datas, key=lambda data: data.discount[0])
quintile_length = int(len(cefs_sorted) * self.params.quintile_size)
long_cefs = cefs_sorted[:quintile_length]
short_cefs = cefs_sorted[-quintile_length:]
# Enter long positions
for data in long_cefs:
if self.getposition(data).size == 0:
self.buy(data=data)
# Enter short positions
for data in short_cefs:
if self.getposition(data).size == 0:
self.sell(data=data)
# Close positions that are no longer in the quintiles
for data in self.datas:
if data not in long_cefs and data not in short_cefs:
if self.getposition(data).size != 0:
self.close(data=data)
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Add data feeds to cerebro
# cerebro.adddata(...)
cerebro.addstrategy(ClosedEndFundMeanReversion)
# Configure cerebro settings
# cerebro.broker.setcash(...)
# cerebro.broker.setcommission(...)
cerebro.run()
cerebro.plot()
This code snippet provides a starting point for implementing the strategy. Be sure to import and process the data for closed-end funds, add any required indicators, and adjust the order execution logic as needed.