|
|
|
|
@ -10,7 +10,8 @@ stockList = ["VOO"]
|
|
|
|
|
total_days_in_market = 365 * 10
|
|
|
|
|
month_sum = 500 # usd
|
|
|
|
|
reserve = 5 # usd for comms etc
|
|
|
|
|
startDate = datetime.datetime.fromisoformat("2018-01-01")
|
|
|
|
|
startDate = datetime.datetime.fromisoformat("2000-01-01")
|
|
|
|
|
endDate = datetime.datetime.fromisoformat("2022-01-30")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# import data
|
|
|
|
|
@ -19,12 +20,11 @@ def get_data(stocks, start, end):
|
|
|
|
|
return stockData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
endDate = datetime.datetime.now()
|
|
|
|
|
# endDate = datetime.datetime.now()
|
|
|
|
|
# startDate = endDate - datetime.timedelta(days=total_days_in_market)
|
|
|
|
|
stockData = get_data(stockList[0], startDate, endDate)
|
|
|
|
|
|
|
|
|
|
actualStart: datetime.datetime = stockData.index[0]
|
|
|
|
|
print(actualStart)
|
|
|
|
|
|
|
|
|
|
data = bt.feeds.PandasData(dataname=stockData)
|
|
|
|
|
|
|
|
|
|
@ -59,14 +59,11 @@ class PercentageCommisionScheme(bt.CommInfoBase):
|
|
|
|
|
("commtype", bt.CommInfoBase.COMM_PERC),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _getcommission(self, size, price, pseudoexec):
|
|
|
|
|
return self.p.commission
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FormulaInvesting(bt.Strategy):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.order = None
|
|
|
|
|
self.totalcost = 0
|
|
|
|
|
self.cost = 0 # no comms
|
|
|
|
|
self.comms = 0
|
|
|
|
|
self.units = 0
|
|
|
|
|
self.times = 0
|
|
|
|
|
@ -107,7 +104,7 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.units += order.executed.size
|
|
|
|
|
self.totalcost += order.executed.value + order.executed.comm
|
|
|
|
|
self.cost += order.executed.value
|
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
|
|
|
|
|
|
elif order.issell():
|
|
|
|
|
@ -121,10 +118,8 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
self.units -= order.executed.size
|
|
|
|
|
# self.totalcost += order.executed.value
|
|
|
|
|
self.totalcost += order.executed.comm
|
|
|
|
|
self.comms -= order.executed.value
|
|
|
|
|
|
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
|
self.times += 1
|
|
|
|
|
|
|
|
|
|
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
|
|
|
|
|
@ -138,24 +133,17 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
def calc_params(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("#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))
|
|
|
|
|
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)
|
|
|
|
|
)
|
|
|
|
|
return (
|
|
|
|
|
# Annual
|
|
|
|
|
100 * ((1 + self.froi / 100) ** (365 / (endDate - actualStart).days) - 1),
|
|
|
|
|
self.froi,
|
|
|
|
|
self.cost,
|
|
|
|
|
value,
|
|
|
|
|
self.times,
|
|
|
|
|
self.units,
|
|
|
|
|
self.comms,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def notify_timer(self, timer, when, *args):
|
|
|
|
|
@ -188,11 +176,13 @@ class QDCA(DCA):
|
|
|
|
|
super().formula()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(strategy, data):
|
|
|
|
|
cerebro = bt.Cerebro(stdstats=False)
|
|
|
|
|
|
|
|
|
|
cerebro.addobserver(bt.observers.Broker)
|
|
|
|
|
cerebro.addobserver(bt.observers.BuySell)
|
|
|
|
|
def run(strategy, data, fund_mode=False):
|
|
|
|
|
if fund_mode:
|
|
|
|
|
cerebro = bt.Cerebro()
|
|
|
|
|
else:
|
|
|
|
|
cerebro = bt.Cerebro(stdstats=False)
|
|
|
|
|
cerebro.addobserver(bt.observers.Broker)
|
|
|
|
|
cerebro.addobserver(bt.observers.BuySell)
|
|
|
|
|
|
|
|
|
|
cerebro.adddata(data)
|
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
|
@ -205,20 +195,50 @@ def run(strategy, data):
|
|
|
|
|
comminfo = PercentageCommisionScheme()
|
|
|
|
|
cerebro.broker.addcommissioninfo(comminfo)
|
|
|
|
|
|
|
|
|
|
if fund_mode:
|
|
|
|
|
cerebro.broker.set_fundmode(True)
|
|
|
|
|
|
|
|
|
|
cerebro.broker.set_cash(month_sum)
|
|
|
|
|
# cerebro.addobserver(bt.observers.FundValue)
|
|
|
|
|
# cerebro.addobserver(bt.observers.FundShares)
|
|
|
|
|
cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")
|
|
|
|
|
cerebro.addanalyzer(bt.analyzers.VWR, _name="returns")
|
|
|
|
|
cerebro.addanalyzer(
|
|
|
|
|
bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.NoTimeFrame, _name="tr"
|
|
|
|
|
)
|
|
|
|
|
thestrats = cerebro.run()
|
|
|
|
|
thestrat = thestrats[0]
|
|
|
|
|
dd = thestrat.analyzers.drawdown
|
|
|
|
|
print(dd.get_analysis().max)
|
|
|
|
|
ret = thestrat.analyzers.returns
|
|
|
|
|
print(ret.get_analysis())
|
|
|
|
|
cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
|
return thestrat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
for strategy in (DCA, QDCA, VA, QVA):
|
|
|
|
|
run(strategy, data)
|
|
|
|
|
df = pd.DataFrame(
|
|
|
|
|
columns=[
|
|
|
|
|
"annual%",
|
|
|
|
|
"froi",
|
|
|
|
|
"cost",
|
|
|
|
|
"total_value",
|
|
|
|
|
"times",
|
|
|
|
|
"units",
|
|
|
|
|
"comms",
|
|
|
|
|
"max_dd_len",
|
|
|
|
|
"max_dd",
|
|
|
|
|
"max_md",
|
|
|
|
|
"twr",
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
for i, strategy in enumerate((DCA, QDCA, VA, QVA)):
|
|
|
|
|
therun = run(strategy, data)
|
|
|
|
|
dd = therun.analyzers.drawdown
|
|
|
|
|
ret = therun.analyzers.returns
|
|
|
|
|
# tr = thestrat.analyzers.tr
|
|
|
|
|
# print(next(reversed(tr.get_analysis())))
|
|
|
|
|
# print(tr.get_analysis())
|
|
|
|
|
df.loc[strategy.__name__] = therun.calc_params() + (
|
|
|
|
|
dd.get_analysis().max.len,
|
|
|
|
|
dd.get_analysis().max.drawdown,
|
|
|
|
|
dd.get_analysis().max.moneydown,
|
|
|
|
|
ret.get_analysis()["vwr"],
|
|
|
|
|
)
|
|
|
|
|
print("Starting from:", actualStart)
|
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
|
print(df)
|