|
|
|
@ -6,16 +6,22 @@ import matplotlib.pyplot as plt
|
|
|
|
from pandas_datareader import data as pdr
|
|
|
|
from pandas_datareader import data as pdr
|
|
|
|
import backtrader as bt
|
|
|
|
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
|
|
|
|
# import data
|
|
|
|
def get_data(stocks, start, end):
|
|
|
|
def get_data(stocks, start, end):
|
|
|
|
stockData = pdr.get_data_yahoo(stocks, start, end)
|
|
|
|
stockData = pdr.get_data_yahoo(stocks, start, end)
|
|
|
|
return stockData
|
|
|
|
return stockData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
stockList = ["VOO"]
|
|
|
|
|
|
|
|
endDate = datetime.datetime.now()
|
|
|
|
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)
|
|
|
|
stockData = get_data(stockList[0], startDate, endDate)
|
|
|
|
|
|
|
|
|
|
|
|
actualStart: datetime.datetime = stockData.index[0]
|
|
|
|
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):
|
|
|
|
class PercentageCommisionScheme(bt.CommInfoBase):
|
|
|
|
paras = (
|
|
|
|
paras = (
|
|
|
|
("commission", 0.004),
|
|
|
|
("commission", 0.004),
|
|
|
|
@ -63,19 +64,20 @@ class PercentageCommisionScheme(bt.CommInfoBase):
|
|
|
|
return self.p.commission
|
|
|
|
return self.p.commission
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VA(bt.Strategy):
|
|
|
|
class FormulaInvesting(bt.Strategy):
|
|
|
|
params = dict(monthly_cash=month_sum, monthly_range=[5, 20])
|
|
|
|
params = dict(monthly_cash=month_sum, monthly_range=[5, 20])
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
|
self.order = None
|
|
|
|
self.order = None
|
|
|
|
self.totalcost = 0
|
|
|
|
self.totalcost = 0
|
|
|
|
self.cost_wo_bro = 0 # cost without comms
|
|
|
|
self.comms = 0
|
|
|
|
self.units = 0
|
|
|
|
self.units = 0
|
|
|
|
self.times = 0
|
|
|
|
self.times = 0
|
|
|
|
|
|
|
|
self.periods = 0
|
|
|
|
|
|
|
|
|
|
|
|
def log(self, txt, dt=None):
|
|
|
|
def log(self, txt, dt=None):
|
|
|
|
dt = dt or self.datas[0].datetime.date(0)
|
|
|
|
dt = dt or self.datas[0].datetime.date(0)
|
|
|
|
print("%s, %s" % (dt.isoformat(), txt))
|
|
|
|
# print("%s, %s" % (dt.isoformat(), txt))
|
|
|
|
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
def start(self):
|
|
|
|
self.broker.set_fundmode(fundmode=True, fundstartval=100.0)
|
|
|
|
self.broker.set_fundmode(fundmode=True, fundstartval=100.0)
|
|
|
|
@ -91,12 +93,6 @@ class VA(bt.Strategy):
|
|
|
|
# timername='buytimer',
|
|
|
|
# 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):
|
|
|
|
def notify_order(self, order):
|
|
|
|
if order.status in [order.Submitted, order.Accepted]:
|
|
|
|
if order.status in [order.Submitted, order.Accepted]:
|
|
|
|
return
|
|
|
|
return
|
|
|
|
@ -115,78 +111,11 @@ class VA(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
|
|
self.units += order.executed.size
|
|
|
|
self.units += order.executed.size
|
|
|
|
self.totalcost += order.executed.value + order.executed.comm
|
|
|
|
self.totalcost += order.executed.value + order.executed.comm
|
|
|
|
self.cost_wo_bro += order.executed.value
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
elif order.issell():
|
|
|
|
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():
|
|
|
|
|
|
|
|
self.log(
|
|
|
|
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.price,
|
|
|
|
order.executed.value,
|
|
|
|
order.executed.value,
|
|
|
|
@ -194,10 +123,11 @@ class DCA(bt.Strategy):
|
|
|
|
order.executed.size,
|
|
|
|
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]:
|
|
|
|
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
|
|
|
@ -213,7 +143,9 @@ class DCA(bt.Strategy):
|
|
|
|
value = self.datas[0].close * self.units + self.broker.get_cash()
|
|
|
|
value = self.datas[0].close * self.units + self.broker.get_cash()
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
print("#Times: {:.0f}".format(self.times))
|
|
|
|
print("#Times: {:.0f}".format(self.times))
|
|
|
|
|
|
|
|
print("#Units: {:.0f}".format(self.units))
|
|
|
|
print("Value: ${:,.2f}".format(value))
|
|
|
|
print("Value: ${:,.2f}".format(value))
|
|
|
|
|
|
|
|
print("Commissions: ${:.2f}".format(self.froi))
|
|
|
|
print("Cost: ${:,.2f}".format(self.totalcost))
|
|
|
|
print("Cost: ${:,.2f}".format(self.totalcost))
|
|
|
|
print("Gross Return: ${:,.2f}".format(value - self.totalcost))
|
|
|
|
print("Gross Return: ${:,.2f}".format(value - self.totalcost))
|
|
|
|
print("Gross %: {:.2f}%".format((value / self.totalcost - 1) * 100))
|
|
|
|
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.adddata(data)
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
print("-" * 50)
|
|
|
|
print("-" * 50)
|
|
|
|
@ -240,12 +193,12 @@ def run(strategy, start_cash, data):
|
|
|
|
comminfo = PercentageCommisionScheme()
|
|
|
|
comminfo = PercentageCommisionScheme()
|
|
|
|
cerebro.broker.addcommissioninfo(comminfo)
|
|
|
|
cerebro.broker.addcommissioninfo(comminfo)
|
|
|
|
|
|
|
|
|
|
|
|
cerebro.broker.set_cash(start_cash)
|
|
|
|
cerebro.broker.set_cash(period_sum)
|
|
|
|
cerebro.run()
|
|
|
|
cerebro.run()
|
|
|
|
cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if __name__ == "__main__":
|
|
|
|
run(LSI, 100000, data)
|
|
|
|
# run(LSI, 100000, data)
|
|
|
|
run(DCA, 1000, data)
|
|
|
|
run(DCA, data)
|
|
|
|
# run(VA, 1000, data)
|
|
|
|
run(VA, data)
|
|
|
|
|