diff --git a/dcavslsi.py b/dcavslsi.py index 61561a4..fbb2e00 100644 --- a/dcavslsi.py +++ b/dcavslsi.py @@ -6,16 +6,22 @@ import matplotlib.pyplot as plt from pandas_datareader import data as pdr import backtrader as bt +stockList = ["VOO"] +total_days_in_market = 365 * 10 +month_sum = 500 # usd +period_months = 1 +reserve = 5 # usd for comms etc +period_sum = month_sum * period_months + + # import data def get_data(stocks, start, end): stockData = pdr.get_data_yahoo(stocks, start, end) return stockData -stockList = ["VOO"] endDate = datetime.datetime.now() -startDate = endDate - datetime.timedelta(days=365 * 10) - +startDate = endDate - datetime.timedelta(days=total_days_in_market) stockData = get_data(stockList[0], startDate, endDate) actualStart: datetime.datetime = stockData.index[0] @@ -47,11 +53,6 @@ class LSI(bt.Strategy): ) -month_sum = 500 # usd -period_months = 1 -period_sum = month_sum * period_months - - class PercentageCommisionScheme(bt.CommInfoBase): paras = ( ("commission", 0.004), @@ -63,19 +64,20 @@ class PercentageCommisionScheme(bt.CommInfoBase): return self.p.commission -class VA(bt.Strategy): +class FormulaInvesting(bt.Strategy): params = dict(monthly_cash=month_sum, monthly_range=[5, 20]) def __init__(self): self.order = None self.totalcost = 0 - self.cost_wo_bro = 0 # cost without comms + self.comms = 0 self.units = 0 self.times = 0 + self.periods = 0 def log(self, txt, dt=None): dt = dt or self.datas[0].datetime.date(0) - print("%s, %s" % (dt.isoformat(), txt)) + # print("%s, %s" % (dt.isoformat(), txt)) def start(self): self.broker.set_fundmode(fundmode=True, fundstartval=100.0) @@ -91,12 +93,6 @@ class VA(bt.Strategy): # timername='buytimer', ) - def notify_timer(self, timer, when, *args): - self.broker.add_cash(self.p.monthly_cash) - - target_value = self.broker.get_value() + self.p.monthly_cash - 10 - self.order_target_value(target=target_value) - def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: return @@ -115,78 +111,11 @@ class VA(bt.Strategy): self.units += order.executed.size self.totalcost += order.executed.value + order.executed.comm - self.cost_wo_bro += order.executed.value - self.times += 1 - - elif order.status in [order.Canceled, order.Margin, order.Rejected]: - self.log("Order Canceled/Margin/Rejected") - print(order.status, [order.Canceled, order.Margin, order.Rejected]) - - self.order = None + self.comms += order.executed.comm - def stop(self): - # calculate actual returns - self.roi = (self.broker.get_value() / self.cash_start) - 1 - self.froi = self.broker.get_fundvalue() - self.val_start - value = self.datas[0].close * self.units + self.broker.get_cash() - print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365)) - print("#Times: {:.0f}".format(self.times)) - print("Value: ${:,.2f}".format(value)) - print("Cost: ${:,.2f}".format(self.totalcost)) - print("Gross Return: ${:,.2f}".format(value - self.totalcost)) - print("Gross %: {:.2f}%".format((value / self.totalcost - 1) * 100)) - print("ROI: {:.2f}%".format(100.0 * self.roi)) - print("Fund Value: {:.2f}%".format(self.froi)) - print( - "Annualised: {:.2f}%".format( - 100 - * ((1 + self.froi / 100) ** (365 / (endDate - actualStart).days) - 1) - ) - ) - - -class DCA(bt.Strategy): - params = dict(monthly_cash=month_sum, monthly_range=[5, 20]) - - def __init__(self): - self.order = None - self.totalcost = 0 - self.cost_wo_bro = 0 - self.units = 0 - self.times = 0 - - def log(self, txt, dt=None): - dt = dt or self.datas[0].datetime.date(0) - print("%s, %s" % (dt.isoformat(), txt)) - - def start(self): - self.broker.set_fundmode(fundmode=True, fundstartval=100.0) - - self.cash_start = self.broker.get_cash() - self.val_start = 100.0 - - # ADD A TIMER - self.add_timer( - when=bt.timer.SESSION_START, - monthdays=[i for i in self.p.monthly_range], - monthcarry=True - # timername='buytimer', - ) - - def notify_timer(self, timer, when, *args): - self.broker.add_cash(self.p.monthly_cash) - - target_value = self.broker.get_value() + self.p.monthly_cash - 10 - self.order_target_value(target=target_value) - - def notify_order(self, order): - if order.status in [order.Submitted, order.Accepted]: - return - - if order.status in [order.Completed]: - if order.isbuy(): + elif order.issell(): self.log( - "BUY EXECUTED, Price %.2f, Cost %.2f, Comm %.2f, Size %.0f" + "SELL EXECUTED, Price %.2f, Cost %.2f, Comm %.2f, Size %.0f" % ( order.executed.price, order.executed.value, @@ -194,11 +123,12 @@ class DCA(bt.Strategy): order.executed.size, ) ) + self.units -= order.executed.size + # self.totalcost += order.executed.value + self.totalcost += order.executed.comm + self.comms -= order.executed.value - self.units += order.executed.size - self.totalcost += order.executed.value + order.executed.comm - self.cost_wo_bro += order.executed.value - self.times += 1 + self.times += 1 elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log("Order Canceled/Margin/Rejected") @@ -213,7 +143,9 @@ class DCA(bt.Strategy): value = self.datas[0].close * self.units + self.broker.get_cash() print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365)) print("#Times: {:.0f}".format(self.times)) + print("#Units: {:.0f}".format(self.units)) print("Value: ${:,.2f}".format(value)) + print("Commissions: ${:.2f}".format(self.froi)) print("Cost: ${:,.2f}".format(self.totalcost)) print("Gross Return: ${:,.2f}".format(value - self.totalcost)) print("Gross %: {:.2f}%".format((value / self.totalcost - 1) * 100)) @@ -226,9 +158,30 @@ class DCA(bt.Strategy): ) ) + def notify_timer(self, timer, when, *args): + self.periods += 1 + self.broker.add_cash(self.p.monthly_cash) + self.formula() + + +class VA(FormulaInvesting): + def formula(self): + target_value = (self.periods) * self.p.monthly_cash - reserve + self.order_target_value(target=target_value) + + +class DCA(FormulaInvesting): + def formula(self): + target_value = self.broker.get_value() - reserve + self.order_target_value(target=target_value) + + +def run(strategy, data): + cerebro = bt.Cerebro(stdstats=False) + + cerebro.addobserver(bt.observers.Broker) + cerebro.addobserver(bt.observers.BuySell) -def run(strategy, start_cash, data): - cerebro = bt.Cerebro() cerebro.adddata(data) cerebro.addstrategy(strategy) print("-" * 50) @@ -240,12 +193,12 @@ def run(strategy, start_cash, data): comminfo = PercentageCommisionScheme() cerebro.broker.addcommissioninfo(comminfo) - cerebro.broker.set_cash(start_cash) + cerebro.broker.set_cash(period_sum) cerebro.run() cerebro.plot(iplot=False, style="candlestick") if __name__ == "__main__": - run(LSI, 100000, data) - run(DCA, 1000, data) - # run(VA, 1000, data) + # run(LSI, 100000, data) + run(DCA, data) + run(VA, data)