Created
May 5, 2023 19:57
-
-
Save joelschopp/192976e992c2179131ce9fb4c004e56a to your computer and use it in GitHub Desktop.
rebalance royalty backtest
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
import backtrader as bt | |
import backtrader.analyzers as btanalyzers | |
class Strategy(bt.Strategy): | |
params = ( | |
("buffer", 0.05), | |
("threshold", 0.05), | |
) | |
def log(self, txt, dt=None): | |
""" Logging function fot this strategy""" | |
dt = dt or self.data.datetime[0] | |
if isinstance(dt, float): | |
dt = bt.num2date(dt) | |
print("%s, %s" % (dt.date(), txt)) | |
def print_signal(self): | |
self.log( | |
f"o {self.datas[0].open[0]:7.2f} " | |
f"h {self.datas[0].high[0]:7.2f} " | |
f"l {self.datas[0].low[0]:7.2f} " | |
f"c {self.datas[0].close[0]:7.2f} " | |
f"v {self.datas[0].volume[0]:7.0f} " | |
) | |
def notify_order(self, order): | |
""" Triggered upon changes to orders. """ | |
# Suppress notification if it is just a submitted order. | |
if order.status == order.Submitted: | |
return | |
# Print out the date, security name, order number and status. | |
type = "Buy" if order.isbuy() else "Sell" | |
#self.log( | |
# f"{order.data._name:<6} Order: {order.ref:3d} " | |
# f"Type: {type:<5}\tStatus" | |
# f" {order.getstatusname():<8} \t" | |
# f"Size: {order.created.size:9.4f} Price: {order.created.price:9.4f} " | |
# f"Position: {self.getposition(order.data).size:5.2f}" | |
#) | |
if order.status == order.Margin: | |
return | |
# Check if an order has been completed | |
#if order.status in [order.Completed]: | |
# self.log( | |
# f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} " | |
# # f"EXECUTED for: {dn} " | |
# f"Price: {order.executed.price:6.2f} " | |
# f"Cost: {order.executed.value:6.2f} " | |
# f"Comm: {order.executed.comm:4.2f} " | |
# f"Size: {order.created.size:9.4f} " | |
# ) | |
def notify_trade(self, trade): | |
"""Provides notification of closed trades.""" | |
#if trade.isclosed: | |
# self.log( | |
# "{} Closed: PnL Gross {}, Net {},".format( | |
# trade.data._name, | |
# round(trade.pnl, 2), | |
# round(trade.pnlcomm, 1), | |
# ) | |
# ) | |
def next(self): | |
track_trades = dict() | |
total_value = self.broker.get_value() * (1 - self.p.buffer) | |
for d in self.datas: | |
track_trades[d] = dict() | |
value = self.broker.get_value(datas=[d]) | |
allocation = value / total_value | |
units_to_trade = (d.target - allocation) * total_value / d.close[0] | |
track_trades[d]["units"] = units_to_trade | |
# Can check to make sure there is enough distance away from ideal to trade. | |
track_trades[d]["threshold"] = abs(d.target - allocation) > self.p.threshold | |
rebalance = False | |
for values in track_trades.values(): | |
if values['threshold']: | |
rebalance = True | |
if not rebalance: | |
return | |
# Sell shares first | |
for d, value in track_trades.items(): | |
if value["units"] < 0: | |
self.sell(d, size=value["units"]) | |
# Buy shares second | |
for d, value in track_trades.items(): | |
if value["units"] > 0: | |
self.buy(d, size=value["units"]) | |
if __name__ == "__main__": | |
cerebro = bt.Cerebro() | |
#tickers = {"FNV": 0.3333, "WPM": 0.3333, "RGLD": 0.3333, "GDXJ": -.25} | |
#tickers = {"FNV": 0.5, "WPM": 0.5, "RGLD": 0.5, "GDXJ":-0.5} | |
#tickers = {"SPY": 0.375, "FNV": 0.375, "WPM": 0.375, "RGLD": 0.375, "GDXJ":-0.5} | |
tickers = {"SPY": 0.25, "FNV": 0.25, "WPM": 0.25, "RGLD": 0.25, "GDXJ":-0.25} | |
for ticker, target in tickers.items(): | |
data = bt.feeds.YahooFinanceCSVData( | |
dataname="/Users/joelschopp/Google Drive/My Drive/stockdata/yahoofinance/" + ticker + ".csv", | |
timeframe=bt.TimeFrame.Days, | |
fromdate=datetime.datetime(2008, 2, 1), | |
todate=datetime.datetime(2023, 5, 2), | |
reverse=False, | |
) | |
data.target = target | |
cerebro.adddata(data, name=ticker) | |
cerebro.addstrategy(Strategy) | |
# Analyzer | |
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='mysharpe') | |
cerebro.addanalyzer(btanalyzers.DrawDown, _name='mydrawdown') | |
cerebro.addanalyzer(btanalyzers.Returns, _name='myreturns') | |
thestrats = cerebro.run() | |
thestrat = thestrats[0] | |
print('Sharpe Ratio:', thestrat.analyzers.mysharpe.get_analysis()) | |
print('Drawdown:', thestrat.analyzers.mydrawdown.get_analysis()) | |
print('Returns:', thestrat.analyzers.myreturns.get_analysis()) | |
cerebro.plot() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment